Compare commits
358 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
018750154d | ||
![]() |
b19216d58b | ||
![]() |
84fc1a3663 | ||
![]() |
1c40be26c6 | ||
![]() |
2c5cf19c6d | ||
![]() |
0dacf6d5f1 | ||
![]() |
e9f1d44015 | ||
![]() |
1d9024ee9a | ||
![]() |
6a6164ab4f | ||
![]() |
7231f5e26a | ||
![]() |
86415223cb | ||
![]() |
269f5acc54 | ||
![]() |
6e6c866de9 | ||
![]() |
732ed92cb7 | ||
![]() |
53a807ae09 | ||
![]() |
3342a8253b | ||
![]() |
630c19f52d | ||
![]() |
af8c2fa4be | ||
![]() |
0789601dd6 | ||
![]() |
ce0ac46e42 | ||
![]() |
b531d87580 | ||
![]() |
23a2154817 | ||
![]() |
76d2496f24 | ||
![]() |
b85cb410f1 | ||
![]() |
1be22df0df | ||
![]() |
a805839d38 | ||
![]() |
3ac61a7550 | ||
![]() |
57ea33bc5c | ||
![]() |
4891649d68 | ||
![]() |
93aba1bb1c | ||
![]() |
f983a6c615 | ||
![]() |
18f1b016e5 | ||
![]() |
591dcc26af | ||
![]() |
4acd322128 | ||
![]() |
32a814bdfa | ||
![]() |
912068e71c | ||
![]() |
df8e57dc5d | ||
![]() |
f27f6e498f | ||
![]() |
01bfbcb84a | ||
![]() |
a15abbb324 | ||
![]() |
28c29d560e | ||
![]() |
65ca7fd4aa | ||
![]() |
3e3f918bb3 | ||
![]() |
fc1dcffbdd | ||
![]() |
ee3c681f98 | ||
![]() |
6225fedcb8 | ||
![]() |
2905069559 | ||
![]() |
47202dd747 | ||
![]() |
fb130c4eae | ||
![]() |
734378c90b | ||
![]() |
3959b7ef28 | ||
![]() |
e292548523 | ||
![]() |
c2506a78a1 | ||
![]() |
34f70c6de2 | ||
![]() |
3d9c2a6352 | ||
![]() |
3232125650 | ||
![]() |
c9f8718d2a | ||
![]() |
f89ac92e5e | ||
![]() |
f82efcf942 | ||
![]() |
dc22f4dc69 | ||
![]() |
173ccf03ab | ||
![]() |
1d5c3c1c9b | ||
![]() |
e4dac421db | ||
![]() |
c639cdd3b3 | ||
![]() |
5bcfa7cdfe | ||
![]() |
d8fd892e71 | ||
![]() |
074a997302 | ||
![]() |
62616f541a | ||
![]() |
156442f80d | ||
![]() |
37112f549a | ||
![]() |
cf4b4d53ba | ||
![]() |
d45e3aa433 | ||
![]() |
ccc25ee901 | ||
![]() |
3052063c05 | ||
![]() |
e107322f5c | ||
![]() |
f0774254de | ||
![]() |
6c66031c4a | ||
![]() |
8f668807cf | ||
![]() |
f98eb35179 | ||
![]() |
cc8ae32503 | ||
![]() |
95f3b1dbe6 | ||
![]() |
6b96b069bf | ||
![]() |
ecfe85b06c | ||
![]() |
5439d4cd49 | ||
![]() |
df91fd032d | ||
![]() |
2d5ef15e08 | ||
![]() |
544a7ad0a5 | ||
![]() |
ec2c1bec4a | ||
![]() |
e064d2607d | ||
![]() |
d489ad595d | ||
![]() |
0a3753bcce | ||
![]() |
7fc2dceaf5 | ||
![]() |
c3b39bac52 | ||
![]() |
2cd88bfb0f | ||
![]() |
0cefb0b79b | ||
![]() |
55a7443974 | ||
![]() |
f9ebaf1b90 | ||
![]() |
bc81fbdd27 | ||
![]() |
c7aa8a132d | ||
![]() |
5c99b01512 | ||
![]() |
992ba33a28 | ||
![]() |
17e7c58981 | ||
![]() |
b8f15d2b77 | ||
![]() |
5f9a10aab9 | ||
![]() |
04bc5257a6 | ||
![]() |
5bca58e748 | ||
![]() |
526a67f8f4 | ||
![]() |
53a27e07b1 | ||
![]() |
ba0fb1e055 | ||
![]() |
ed2c8aa6f8 | ||
![]() |
9226760120 | ||
![]() |
b9ee8b46a0 | ||
![]() |
e7eb0b8b7c | ||
![]() |
ae52851458 | ||
![]() |
e63cf3b89e | ||
![]() |
9624fda082 | ||
![]() |
e19b6a8f38 | ||
![]() |
2aa6b54201 | ||
![]() |
fe8550d278 | ||
![]() |
e2e8ba6ffa | ||
![]() |
60cdf9dc38 | ||
![]() |
0371538d10 | ||
![]() |
351331a81b | ||
![]() |
0a54713f8a | ||
![]() |
49d122e560 | ||
![]() |
ff8729423e | ||
![]() |
0c72f7d358 | ||
![]() |
818f4a751e | ||
![]() |
4e7d2bd5bf | ||
![]() |
395ac510f7 | ||
![]() |
b20c5d2c37 | ||
![]() |
ea02b1ccfa | ||
![]() |
0bafd03e85 | ||
![]() |
e8d1858c64 | ||
![]() |
d1efc5ae90 | ||
![]() |
86f7b4170c | ||
![]() |
9d93c859d7 | ||
![]() |
5baa069b16 | ||
![]() |
12b7d00747 | ||
![]() |
d777d79c61 | ||
![]() |
062a772e48 | ||
![]() |
3618cc6d34 | ||
![]() |
9413ca8a2e | ||
![]() |
1302e54947 | ||
![]() |
edf423af3d | ||
![]() |
37086748bf | ||
![]() |
6c3f1f70ba | ||
![]() |
8a8c4d37aa | ||
![]() |
f6a7183c52 | ||
![]() |
c86e16db5f | ||
![]() |
c3907893c1 | ||
![]() |
d61c6f996a | ||
![]() |
bd04cd61f8 | ||
![]() |
96dcfafe45 | ||
![]() |
991a802a8e | ||
![]() |
48ae1a6574 | ||
![]() |
dd2d1be006 | ||
![]() |
5369de80d6 | ||
![]() |
552dbfc2f1 | ||
![]() |
0aa4cbdbc8 | ||
![]() |
9f16c2620c | ||
![]() |
9b256a9144 | ||
![]() |
0f1476be33 | ||
![]() |
769b2aada2 | ||
![]() |
301236e1a5 | ||
![]() |
aeb98dbcdf | ||
![]() |
8036d05412 | ||
![]() |
7d137d7f88 | ||
![]() |
b8b551cf2b | ||
![]() |
7ec28fe9af | ||
![]() |
1cc3fe21b6 | ||
![]() |
ba7d70e5e0 | ||
![]() |
497e0178cc | ||
![]() |
8821723526 | ||
![]() |
a203a959ae | ||
![]() |
dfe2bad663 | ||
![]() |
f09a266e01 | ||
![]() |
3853fff795 | ||
![]() |
641be64544 | ||
![]() |
bcf211bdb3 | ||
![]() |
ee5591d583 | ||
![]() |
1f43713506 | ||
![]() |
325b9b4562 | ||
![]() |
88abdbd50b | ||
![]() |
14d34c9bdf | ||
![]() |
e1f45a33b7 | ||
![]() |
9a754f9f0f | ||
![]() |
c3dcbf9cb3 | ||
![]() |
5d68c830d2 | ||
![]() |
ac58914b97 | ||
![]() |
77ebd2b020 | ||
![]() |
fec3b1a0e9 | ||
![]() |
3b571a4f3d | ||
![]() |
efee81eb57 | ||
![]() |
a7a698c36f | ||
![]() |
b5f1dc32d1 | ||
![]() |
eef28144ce | ||
![]() |
f9b290570e | ||
![]() |
4852f55875 | ||
![]() |
793f31b9b3 | ||
![]() |
7e94405271 | ||
![]() |
6076a8d7bb | ||
![]() |
a1624f217c | ||
![]() |
b2f4d53bf0 | ||
![]() |
7662cefe6f | ||
![]() |
1cb607697a | ||
![]() |
1990c3c722 | ||
![]() |
b589d85d6f | ||
![]() |
03ec3a2169 | ||
![]() |
a5baed6b97 | ||
![]() |
259fc91305 | ||
![]() |
a618d6e4ce | ||
![]() |
b315a84ba0 | ||
![]() |
972538be7a | ||
![]() |
d7be4f12b5 | ||
![]() |
b9c1c913c1 | ||
![]() |
06e479ff66 | ||
![]() |
0065868f37 | ||
![]() |
77fa3c3e48 | ||
![]() |
f4cb45345d | ||
![]() |
9329bfb86a | ||
![]() |
3efc88fb81 | ||
![]() |
954b09022a | ||
![]() |
71f5e78bcb | ||
![]() |
f0c3ed0f14 | ||
![]() |
c8fd3f5237 | ||
![]() |
e0660b1b72 | ||
![]() |
5a165ebcef | ||
![]() |
3212f8c826 | ||
![]() |
c421ccaae9 | ||
![]() |
bbec7db7ba | ||
![]() |
00134ae4e0 | ||
![]() |
07bd459baa | ||
![]() |
302adf1081 | ||
![]() |
1a6a13425b | ||
![]() |
072a772ca6 | ||
![]() |
5f0e0a46fd | ||
![]() |
c7b72aa575 | ||
![]() |
732eb61957 | ||
![]() |
e7e7622971 | ||
![]() |
4176832266 | ||
![]() |
71de92a189 | ||
![]() |
e0021d4cf5 | ||
![]() |
4e44102e31 | ||
![]() |
111b70d108 | ||
![]() |
5e4054b8f3 | ||
![]() |
5afc7abcb0 | ||
![]() |
65d0888708 | ||
![]() |
adfd2373b8 | ||
![]() |
3a82af3bde | ||
![]() |
3af62306b4 | ||
![]() |
afe339396f | ||
![]() |
c4b51bf689 | ||
![]() |
b65fe3e44e | ||
![]() |
116ee334fb | ||
![]() |
dbc5c5817a | ||
![]() |
baf4ce0dd0 | ||
![]() |
24db1ed938 | ||
![]() |
82798e9d04 | ||
![]() |
da8bb1b45b | ||
![]() |
f5cbc2ee25 | ||
![]() |
cbc1986c6f | ||
![]() |
0982f993cb | ||
![]() |
a5a29b0591 | ||
![]() |
a8f3197f24 | ||
![]() |
e6f37120fe | ||
![]() |
6840b7bda9 | ||
![]() |
68f458909a | ||
![]() |
12acecf756 | ||
![]() |
cfbed40ab6 | ||
![]() |
fe1a6b8e42 | ||
![]() |
13e29c4e89 | ||
![]() |
fd52b178ed | ||
![]() |
ca1f071b2e | ||
![]() |
296adbbb72 | ||
![]() |
c795ee1176 | ||
![]() |
b550618afd | ||
![]() |
d08d6eebff | ||
![]() |
e9789abd05 | ||
![]() |
f2ec2cbb99 | ||
![]() |
8de2ace80a | ||
![]() |
c977994864 | ||
![]() |
12b8e1af55 | ||
![]() |
9a188485f5 | ||
![]() |
45a10419bc | ||
![]() |
2245748c14 | ||
![]() |
bcea196530 | ||
![]() |
b9e32c833a | ||
![]() |
be49e0ee93 | ||
![]() |
bf95655a1a | ||
![]() |
6bf5b84fa4 | ||
![]() |
4836565ca7 | ||
![]() |
750e4e8156 | ||
![]() |
a0846194b7 | ||
![]() |
bcf6b90c95 | ||
![]() |
3ca2a0aa37 | ||
![]() |
b4be96c65d | ||
![]() |
434ff5fecb | ||
![]() |
a1bb9c2d42 | ||
![]() |
7e626bef0a | ||
![]() |
a1e83a8ed2 | ||
![]() |
cbeb197aa5 | ||
![]() |
0330fa6b82 | ||
![]() |
97135e8fd5 | ||
![]() |
35bd07fc7b | ||
![]() |
a8ac77b645 | ||
![]() |
9ecd377a51 | ||
![]() |
76171bd8b4 | ||
![]() |
1f106d75bc | ||
![]() |
a4fae8c405 | ||
![]() |
33c07b3394 | ||
![]() |
c9d06e676f | ||
![]() |
c7c65d9fda | ||
![]() |
257a9e3e6f | ||
![]() |
c7c0a1688e | ||
![]() |
7e95e2b0ba | ||
![]() |
ddc286f4b5 | ||
![]() |
3d17a3672e | ||
![]() |
61d253f5fd | ||
![]() |
54be25a7a1 | ||
![]() |
b1757037fb | ||
![]() |
f0476fcff0 | ||
![]() |
a5c4bb5b96 | ||
![]() |
4c1f32b0db | ||
![]() |
383d8969ab | ||
![]() |
333ffd8d32 | ||
![]() |
d4679a0bc2 | ||
![]() |
4ce29f3a5b | ||
![]() |
17b7bc3e06 | ||
![]() |
f19bd5f4f3 | ||
![]() |
d503d25f29 | ||
![]() |
5d275e994a | ||
![]() |
d8c8b13bb8 | ||
![]() |
4671168635 | ||
![]() |
1ac78bfd5d | ||
![]() |
4340beacea | ||
![]() |
ec89f378fc | ||
![]() |
4dd6863bc2 | ||
![]() |
37c626e673 | ||
![]() |
f0a110928b | ||
![]() |
c39e7d1eb2 | ||
![]() |
799bffb431 | ||
![]() |
fda607cc2f | ||
![]() |
cc3aa68123 | ||
![]() |
306621d2d6 | ||
![]() |
d204f7aa2a | ||
![]() |
a94f5e8cbb | ||
![]() |
1390afef23 | ||
![]() |
b0346a6e45 | ||
![]() |
e5da059994 | ||
![]() |
ac92c3bb26 | ||
![]() |
87157d7089 | ||
![]() |
611dd12f0f | ||
![]() |
969ce9dc2b | ||
![]() |
34834a9e79 | ||
![]() |
6e2ddccc2c | ||
![]() |
55742e4737 | ||
![]() |
0091b1e8db |
@ -11,7 +11,7 @@
|
||||
anchor_1: &job_defaults
|
||||
working_directory: ~/ng
|
||||
docker:
|
||||
- image: angular/ngcontainer
|
||||
- image: angular/ngcontainer:0.0.2
|
||||
|
||||
# After checkout, rebase on top of master.
|
||||
# Similar to travis behavior, but not quite the same.
|
||||
@ -27,10 +27,9 @@ jobs:
|
||||
- checkout:
|
||||
<<: *post_checkout
|
||||
- restore_cache:
|
||||
key: angular-{{ .Branch }}-{{ checksum "npm-shrinkwrap.json" }}
|
||||
key: angular-{{ .Branch }}-{{ checksum "yarn.lock" }}
|
||||
|
||||
- run: npm install
|
||||
- run: npm run postinstall
|
||||
- run: yarn install --freeze-lockfile --non-interactive
|
||||
- run: ./node_modules/.bin/gulp lint
|
||||
|
||||
build:
|
||||
@ -39,12 +38,12 @@ jobs:
|
||||
- checkout:
|
||||
<<: *post_checkout
|
||||
- restore_cache:
|
||||
key: angular-{{ .Branch }}-{{ checksum "npm-shrinkwrap.json" }}
|
||||
key: angular-{{ .Branch }}-{{ checksum "yarn.lock" }}
|
||||
|
||||
- run: bazel run @io_bazel_rules_typescript_node//:bin/npm install
|
||||
- run: bazel run @yarn//:yarn
|
||||
- run: bazel build ...
|
||||
- save_cache:
|
||||
key: angular-{{ .Branch }}-{{ checksum "npm-shrinkwrap.json" }}
|
||||
key: angular-{{ .Branch }}-{{ checksum "yarn.lock" }}
|
||||
paths:
|
||||
- "node_modules"
|
||||
|
||||
|
@ -104,7 +104,7 @@ groups:
|
||||
animations:
|
||||
conditions:
|
||||
files:
|
||||
- "packages/animation/*"
|
||||
- "packages/animations/*"
|
||||
- "packages/platform-browser/animations/*"
|
||||
users:
|
||||
- matsko #primary
|
||||
@ -256,6 +256,8 @@ groups:
|
||||
files:
|
||||
include:
|
||||
- "aio/*"
|
||||
exclude:
|
||||
- "aio/content/*"
|
||||
users:
|
||||
- petebacondarwin #primary
|
||||
- IgorMinar
|
||||
@ -276,6 +278,8 @@ groups:
|
||||
- Foxandxss
|
||||
- stephenfluin
|
||||
- wardbell
|
||||
- petebacondarwin
|
||||
- gkalpak
|
||||
- IgorMinar #fallback
|
||||
- mhevery #fallback
|
||||
|
||||
@ -289,5 +293,7 @@ groups:
|
||||
users:
|
||||
- juleskremer #primary
|
||||
- stephenfluin
|
||||
- petebacondarwin
|
||||
- gkalpak
|
||||
- IgorMinar #fallback
|
||||
- mhevery #fallback
|
||||
|
11
.travis.yml
11
.travis.yml
@ -1,5 +1,6 @@
|
||||
language: node_js
|
||||
sudo: false
|
||||
dist: trusty
|
||||
node_js:
|
||||
- '6.9.5'
|
||||
|
||||
@ -47,19 +48,21 @@ env:
|
||||
- CI_MODE=e2e_2
|
||||
- CI_MODE=js
|
||||
- CI_MODE=saucelabs_required
|
||||
- CI_MODE=browserstack_required
|
||||
# deactivated, see #19768
|
||||
# - CI_MODE=browserstack_required
|
||||
- CI_MODE=saucelabs_optional
|
||||
- CI_MODE=browserstack_optional
|
||||
- CI_MODE=docs_test
|
||||
- CI_MODE=aio_tools_test
|
||||
- CI_MODE=aio
|
||||
- CI_MODE=aio_e2e
|
||||
- CI_MODE=aio_e2e AIO_SHARD=0
|
||||
- CI_MODE=aio_e2e AIO_SHARD=1
|
||||
- CI_MODE=bazel
|
||||
|
||||
matrix:
|
||||
fast_finish: true
|
||||
allow_failures:
|
||||
- env: "CI_MODE=saucelabs_optional"
|
||||
- env: "CI_MODE=browserstack_optional"
|
||||
- env: "CI_MODE=aio_e2e"
|
||||
|
||||
before_install:
|
||||
# source the env.sh script so that the exported variables are available to other scripts later on
|
||||
|
@ -11,8 +11,15 @@ filegroup(
|
||||
# This won't scale in the general case.
|
||||
# TODO(alexeagle): figure out what to do
|
||||
"node_modules/typescript/**",
|
||||
"node_modules/zone.js/**/*.d.ts",
|
||||
"node_modules/zone.js/**",
|
||||
"node_modules/rxjs/**/*.d.ts",
|
||||
"node_modules/rxjs/**/*.js",
|
||||
"node_modules/@types/**/*.d.ts",
|
||||
"node_modules/tsickle/**",
|
||||
"node_modules/hammerjs/**/*.d.ts",
|
||||
"node_modules/protobufjs/**",
|
||||
"node_modules/bytebuffer/**",
|
||||
"node_modules/reflect-metadata/**",
|
||||
"node_modules/minimist/**/*.js",
|
||||
]),
|
||||
)
|
189
CHANGELOG.md
189
CHANGELOG.md
@ -1,3 +1,191 @@
|
||||
<a name="4.4.7"></a>
|
||||
## [4.4.7](https://github.com/angular/angular/compare/4.4.6...4.4.7) (2018-04-16)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **core:** use appropriate inert document strategy for Firefox & Safari ([#22077](https://github.com/angular/angular/issues/22077)) ([2c5cf19](https://github.com/angular/angular/commit/2c5cf19))
|
||||
|
||||
|
||||
|
||||
<a name="4.4.6"></a>
|
||||
## [4.4.6](https://github.com/angular/angular/compare/4.4.5...4.4.6) (2017-10-18)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **animations:** properly support boolean-based transitions and state changes ([#19672](https://github.com/angular/angular/issues/19672)) ([f983a6c](https://github.com/angular/angular/commit/f983a6c)), closes [#9396](https://github.com/angular/angular/issues/9396) [#12337](https://github.com/angular/angular/issues/12337)
|
||||
* **common:** attempt to JSON.parse errors for JSON responses ([#19773](https://github.com/angular/angular/issues/19773)) ([269f5ac](https://github.com/angular/angular/commit/269f5ac))
|
||||
* **router:** RouterLinkActive should update its state right after checking the children ([53a807a](https://github.com/angular/angular/commit/53a807a)), closes [#18983](https://github.com/angular/angular/issues/18983)
|
||||
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
* **animations:** reduce size of bundle by removing AST classes ([#19673](https://github.com/angular/angular/issues/19673)) ([76d2496](https://github.com/angular/angular/commit/76d2496))
|
||||
|
||||
|
||||
|
||||
<a name="4.4.5"></a>
|
||||
## [4.4.5](https://github.com/angular/angular/compare/4.4.4...4.4.5) (2017-10-12)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **compiler:** `TestBed.overrideProvider` should keep imported `NgModule`s eager ([#19624](https://github.com/angular/angular/issues/19624)) ([734378c](https://github.com/angular/angular/commit/734378c))
|
||||
* **compiler:** correctly instantiate eager providers that are used via `Injector.get` ([#19558](https://github.com/angular/angular/issues/19558)) ([e292548](https://github.com/angular/angular/commit/e292548)), closes [#15501](https://github.com/angular/angular/issues/15501)
|
||||
* **compiler:** disallow references for select and index evaluation ([95f3b1d](https://github.com/angular/angular/commit/95f3b1d))
|
||||
* **core:** make dynamic & inline code checking behave the same ([#19189](https://github.com/angular/angular/issues/19189)) ([6c66031](https://github.com/angular/angular/commit/6c66031))
|
||||
* **platform-browser:** support customEqualityTesters when overriding Jasmine toEqual ([cc8ae32](https://github.com/angular/angular/commit/cc8ae32))
|
||||
* **tsc-wrapped:** don't rewrite imports when annotating for closure ([#19579](https://github.com/angular/angular/issues/19579)) ([c9f8718](https://github.com/angular/angular/commit/c9f8718))
|
||||
|
||||
|
||||
|
||||
<a name="4.4.4"></a>
|
||||
## [4.4.4](https://github.com/angular/angular/compare/4.4.3...4.4.4) (2017-09-28)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **animations:** support negative query limit value ([#19419](https://github.com/angular/angular/issues/19419)) ([bc81fbd](https://github.com/angular/angular/commit/bc81fbd)), closes [#19232](https://github.com/angular/angular/issues/19232)
|
||||
* **compiler:** correctly map error message locations ([#19424](https://github.com/angular/angular/issues/19424)) ([c3b39ba](https://github.com/angular/angular/commit/c3b39ba))
|
||||
* **compiler:** do not consider a reference with members as a reference ([#19466](https://github.com/angular/angular/issues/19466)) ([7fc2dce](https://github.com/angular/angular/commit/7fc2dce))
|
||||
* **compiler:** skip when trimming / removing whitespaces ([#19310](https://github.com/angular/angular/issues/19310)) ([c7aa8a1](https://github.com/angular/angular/commit/c7aa8a1)), closes [#19304](https://github.com/angular/angular/issues/19304)
|
||||
* **tsc-wrapped:** add metadata for `type` declarations ([#19040](https://github.com/angular/angular/issues/19040)) ([ae52851](https://github.com/angular/angular/commit/ae52851))
|
||||
|
||||
|
||||
<a name="4.4.3"></a>
|
||||
## [4.4.3](https://github.com/angular/angular/compare/4.4.2...4.4.3) (2017-09-19)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **tsc-wrapped:** deduplicate metadata only when the module is the same ([#19261](https://github.com/angular/angular/issues/19261)) ([0371538](https://github.com/angular/angular/commit/0371538)), closes [#19219](https://github.com/angular/angular/issues/19219)
|
||||
|
||||
|
||||
<a name="4.4.2"></a>
|
||||
## [4.4.2](https://github.com/angular/angular/compare/4.4.1...4.4.2) (2017-09-18)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **platform-server**: fix for packaging issues [#19250](https://github.com/angular/angular/issues/19250)
|
||||
|
||||
|
||||
<a name="4.4.1"></a>
|
||||
# [4.4.1](https://github.com/angular/angular/compare/4.3.6...4.4.1) (2017-09-15)
|
||||
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **animations:** do not leak DOM nodes/styling for host triggered animations ([#18853](https://github.com/angular/angular/issues/18853)) ([1cc3fe2](https://github.com/angular/angular/commit/1cc3fe2)), closes [#18606](https://github.com/angular/angular/issues/18606)
|
||||
* **common:** fix improper packaging for [@angular](https://github.com/angular)/common/http ([#18613](https://github.com/angular/angular/issues/18613)) ([a203a95](https://github.com/angular/angular/commit/a203a95))
|
||||
* **common:** fix XSSI prefix stripping by using JSON.parse always ([#18466](https://github.com/angular/angular/issues/18466)) ([8821723](https://github.com/angular/angular/commit/8821723)), closes [#18396](https://github.com/angular/angular/issues/18396) [#18453](https://github.com/angular/angular/issues/18453)
|
||||
* **compiler:** normalize the locale name ([#18963](https://github.com/angular/angular/issues/18963)) ([497e017](https://github.com/angular/angular/commit/497e017))
|
||||
* **core:** complete EventEmitter in QueryList on component destroy ([#18902](https://github.com/angular/angular/issues/18902)) ([7d137d7](https://github.com/angular/angular/commit/7d137d7)), closes [#18741](https://github.com/angular/angular/issues/18741)
|
||||
* **tsc-wrapped:** deduplicate metadata for re-exported modules ([48ae1a6](https://github.com/angular/angular/commit/48ae1a6))
|
||||
* **tsc-wrapped:** fix metadata symbol reference ([f6a7183](https://github.com/angular/angular/commit/f6a7183))
|
||||
* **upgrade:** remove code setting id attribute. ([#19182](https://github.com/angular/angular/issues/19182)) ([b20c5d2](https://github.com/angular/angular/commit/b20c5d2)), closes [#18446](https://github.com/angular/angular/issues/18446)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **compiler:** allow multiple exportAs names ([#18723](https://github.com/angular/angular/issues/18723)) ([7ec28fe](https://github.com/angular/angular/commit/7ec28fe))
|
||||
* **core:** add option to remove blank text nodes from compiled templates ([#18823](https://github.com/angular/angular/issues/18823)) ([b8b551c](https://github.com/angular/angular/commit/b8b551c))
|
||||
|
||||
|
||||
Note: the 4.4.0 release on npm accidentally glitched-out midway, so we cut 4.4.1 instead. oops :-)
|
||||
|
||||
|
||||
|
||||
<a name="4.3.6"></a>
|
||||
## [4.3.6](https://github.com/angular/angular/compare/4.3.5...4.3.6) (2017-08-23)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **animations:** ensure animations are disabled on the element containing the @.disabled flag ([#18714](https://github.com/angular/angular/issues/18714)) ([5d68c83](https://github.com/angular/angular/commit/5d68c83))
|
||||
* **animations:** make sure @.disabled respects disabled parent/sub animation sequences ([#18715](https://github.com/angular/angular/issues/18715)) ([c3dcbf9](https://github.com/angular/angular/commit/c3dcbf9))
|
||||
* **animations:** make sure animation cancellations respect AUTO style values ([#18787](https://github.com/angular/angular/issues/18787)) ([9a754f9](https://github.com/angular/angular/commit/9a754f9)), closes [#17450](https://github.com/angular/angular/issues/17450)
|
||||
* **animations:** resolve error when using AnimationBuilder with platform-server ([#18642](https://github.com/angular/angular/issues/18642)) ([f9b2905](https://github.com/angular/angular/commit/f9b2905)), closes [#18635](https://github.com/angular/angular/issues/18635)
|
||||
* **animations:** restore auto-style support for removed DOM nodes ([#18787](https://github.com/angular/angular/issues/18787)) ([e1f45a3](https://github.com/angular/angular/commit/e1f45a3))
|
||||
* **core:** correct order in ContentChildren query result ([#18326](https://github.com/angular/angular/issues/18326)) ([fec3b1a](https://github.com/angular/angular/commit/fec3b1a)), closes [#16568](https://github.com/angular/angular/issues/16568)
|
||||
* **core:** make sure onStable runs in the right zone ([#18706](https://github.com/angular/angular/issues/18706)) ([ee5591d](https://github.com/angular/angular/commit/ee5591d))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **animations:** allow @.disabled property to work without an expression ([#18713](https://github.com/angular/angular/issues/18713)) ([ac58914](https://github.com/angular/angular/commit/ac58914))
|
||||
* **common:** add an empty DeprecatedI18NPipesModule module ([793f31b](https://github.com/angular/angular/commit/793f31b))
|
||||
|
||||
|
||||
|
||||
<a name="4.3.5"></a>
|
||||
## [4.3.5](https://github.com/angular/angular/compare/4.3.4...4.3.5) (2017-08-16)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **core:** forbid destroyed views to be inserted or moved in VC ([972538b](https://github.com/angular/angular/commit/972538b)), closes [#18615](https://github.com/angular/angular/issues/18615)
|
||||
* **forms:** re-assigning options should not clear select ([a1624f2](https://github.com/angular/angular/commit/a1624f2)), closes [#18330](https://github.com/angular/angular/issues/18330)
|
||||
|
||||
|
||||
<a name="4.3.4"></a>
|
||||
## [4.3.4](https://github.com/angular/angular/compare/4.3.3...4.3.4) (2017-08-10)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **animations:** revert container/queried animations accordingly during cancel ([#18516](https://github.com/angular/angular/issues/18516)) ([5a165eb](https://github.com/angular/angular/commit/5a165eb))
|
||||
* **animations:** support persisting dynamic styles within animation states ([#18468](https://github.com/angular/angular/issues/18468)) ([e0660b1](https://github.com/angular/angular/commit/e0660b1)), closes [#18423](https://github.com/angular/angular/issues/18423) [#17505](https://github.com/angular/angular/issues/17505)
|
||||
* **benchpress:** compile cleanly with TS 2.4 ([#18455](https://github.com/angular/angular/issues/18455)) ([5afc7ab](https://github.com/angular/angular/commit/5afc7ab))
|
||||
* **compiler:** cleanly compile with TypeScript 2.4 ([#18456](https://github.com/angular/angular/issues/18456)) ([5e4054b](https://github.com/angular/angular/commit/5e4054b))
|
||||
* **compiler:** ignore [@import](https://github.com/import) in multi-line css ([#18452](https://github.com/angular/angular/issues/18452)) ([e7e7622](https://github.com/angular/angular/commit/e7e7622)), closes [#18038](https://github.com/angular/angular/issues/18038)
|
||||
|
||||
<a name="4.3.3"></a>
|
||||
## [4.3.3](https://github.com/angular/angular/compare/4.3.2...4.3.3) (2017-08-02)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **compiler:** fix for element needing implicit parent placed in top-level ng-container ([f5cbc2e](https://github.com/angular/angular/commit/f5cbc2e)), closes [#18314](https://github.com/angular/angular/issues/18314)
|
||||
|
||||
<a name="4.3.2"></a>
|
||||
## [4.3.2](https://github.com/angular/angular/compare/4.3.1...4.3.2) (2017-07-26)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **animations:** export BrowserModule as apart of BrowserAnimationsModule ([#18263](https://github.com/angular/angular/issues/18263)) ([cbeb197](https://github.com/angular/angular/commit/cbeb197))
|
||||
* **compiler:** add equiv & disp attributes to Xliff2 ICU placeholders ([#18283](https://github.com/angular/angular/issues/18283)) ([a084619](https://github.com/angular/angular/commit/a084619)), closes [#17344](https://github.com/angular/angular/issues/17344)
|
||||
* **compiler:** allow numbers for ICU message cases in lexer ([#18095](https://github.com/angular/angular/issues/18095)) ([a8ac77b](https://github.com/angular/angular/commit/a8ac77b)), closes [#17799](https://github.com/angular/angular/issues/17799)
|
||||
* **core:** invoke error handler outside of the Angular Zone ([#18269](https://github.com/angular/angular/issues/18269)) ([a1bb9c2](https://github.com/angular/angular/commit/a1bb9c2)), closes [#17073](https://github.com/angular/angular/issues/17073) [#7774](https://github.com/angular/angular/issues/7774)
|
||||
* **platform-server:** don't clobber parse5 properties when setting ([#18237](https://github.com/angular/angular/issues/18237)) ([97135e8](https://github.com/angular/angular/commit/97135e8)), closes [#17050](https://github.com/angular/angular/issues/17050)
|
||||
* **router:** child CanActivate guard should wait for parent to complete ([#18110](https://github.com/angular/angular/issues/18110)) ([b9e32c8](https://github.com/angular/angular/commit/b9e32c8)), closes [#15670](https://github.com/angular/angular/issues/15670)
|
||||
* **router:** should throw when lazy loaded module doesn't define any routes ([#15001](https://github.com/angular/angular/issues/15001)) ([be49e0e](https://github.com/angular/angular/commit/be49e0e)), closes [#14596](https://github.com/angular/angular/issues/14596)
|
||||
* **upgrade:** throw error if trying to get injector before setting ([#18209](https://github.com/angular/angular/issues/18209)) ([1f106d7](https://github.com/angular/angular/commit/1f106d7))
|
||||
|
||||
|
||||
|
||||
<a name="4.3.1"></a>
|
||||
## [4.3.1](https://github.com/angular/angular/compare/4.3.0...4.3.1) (2017-07-19)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **animations:** always camelcase style property names that contain auto styles ([383d896](https://github.com/angular/angular/commit/383d896)), closes [#17938](https://github.com/angular/angular/issues/17938)
|
||||
* **animations:** capture cancelled animation styles within grouped animations ([333ffd8](https://github.com/angular/angular/commit/333ffd8)), closes [#17170](https://github.com/angular/angular/issues/17170)
|
||||
* **animations:** do not crash animations if a nested component fires CD during CD ([4c1f32b](https://github.com/angular/angular/commit/4c1f32b)), closes [#18193](https://github.com/angular/angular/issues/18193)
|
||||
* **animations:** make sure @.disabled works in non-animation components ([a5c4bb5](https://github.com/angular/angular/commit/a5c4bb5))
|
||||
* **common:** send flushed body as error instead of null ([17b7bc3](https://github.com/angular/angular/commit/17b7bc3)), closes [#18181](https://github.com/angular/angular/issues/18181)
|
||||
* **compiler:** ensure jit external id arguments names are unique ([4671168](https://github.com/angular/angular/commit/4671168))
|
||||
* **compiler-cli:** don't generate empty `<target/>` when extracting xliff ([f0476fc](https://github.com/angular/angular/commit/f0476fc)), closes [#15754](https://github.com/angular/angular/issues/15754)
|
||||
* **platform-server:** provide XhrFactory for HttpClient ([4ce29f3](https://github.com/angular/angular/commit/4ce29f3))
|
||||
* **router:** canDeactivate guards should run from bottom to top ([1ac78bf](https://github.com/angular/angular/commit/1ac78bf)), closes [#15657](https://github.com/angular/angular/issues/15657)
|
||||
* **router:** should navigate to the same url when config changes ([4340bea](https://github.com/angular/angular/commit/4340bea)), closes [#15535](https://github.com/angular/angular/issues/15535)
|
||||
* **router:** should run resolvers for the same route concurrently ([ec89f37](https://github.com/angular/angular/commit/ec89f37)), closes [#14279](https://github.com/angular/angular/issues/14279)
|
||||
* **router:** terminal route in custom matcher ([5d275e9](https://github.com/angular/angular/commit/5d275e9))
|
||||
|
||||
|
||||
|
||||
<a name="4.3.0"></a>
|
||||
# [4.3.0](https://github.com/angular/angular/compare/4.3.0-rc.0...4.3.0) (2017-07-14)
|
||||
|
||||
@ -1048,7 +1236,6 @@ templates is unaffected. We expect no or little impact on apps from this change,
|
||||
|
||||
### Features
|
||||
|
||||
* **aio:** add initial angular-cli scaffold ([#14118](https://github.com/angular/angular/issues/14118)) ([e130bc1](https://github.com/angular/angular/commit/e130bc1))
|
||||
* **common:** rename underlying `NgFor` class and add a type parameter ([#14104](https://github.com/angular/angular/issues/14104)) ([86b2b25](https://github.com/angular/angular/commit/86b2b25))
|
||||
* **compiler:** allow missing translations ([#14113](https://github.com/angular/angular/issues/14113)) ([8775ab9](https://github.com/angular/angular/commit/8775ab9)), closes [#13861](https://github.com/angular/angular/issues/13861)
|
||||
* **compiler:** do not parse xtb messages not needed by angular ([#14111](https://github.com/angular/angular/issues/14111)) ([f7fba74](https://github.com/angular/angular/commit/f7fba74)), closes [#14046](https://github.com/angular/angular/issues/14046)
|
||||
|
12
WORKSPACE
12
WORKSPACE
@ -1,11 +1,17 @@
|
||||
load("@bazel_tools//tools/build_defs/repo:git.bzl", "git_repository")
|
||||
|
||||
git_repository(
|
||||
name = "io_bazel_rules_typescript",
|
||||
name = "build_bazel_rules_typescript",
|
||||
remote = "https://github.com/bazelbuild/rules_typescript.git",
|
||||
commit = "3a8404d",
|
||||
tag = "0.0.5",
|
||||
)
|
||||
|
||||
load("@io_bazel_rules_typescript//:defs.bzl", "node_repositories")
|
||||
load("@build_bazel_rules_typescript//:defs.bzl", "node_repositories")
|
||||
|
||||
node_repositories(package_json = "//:package.json")
|
||||
|
||||
git_repository(
|
||||
name = "build_bazel_rules_angular",
|
||||
remote = "https://github.com/bazelbuild/rules_angular.git",
|
||||
tag = "0.0.1",
|
||||
)
|
@ -31,8 +31,9 @@
|
||||
"environmentSource": "environments/environment.ts",
|
||||
"environments": {
|
||||
"dev": "environments/environment.ts",
|
||||
"stage": "environments/environment.stage.ts",
|
||||
"prod": "environments/environment.prod.ts"
|
||||
"next": "environments/environment.next.ts",
|
||||
"stable": "environments/environment.stable.ts",
|
||||
"archive": "environments/environment.archive.ts"
|
||||
}
|
||||
}
|
||||
],
|
||||
|
@ -13,7 +13,11 @@ You should run all these tasks from the `angular/aio` folder.
|
||||
Here are the most important tasks you might need to use:
|
||||
|
||||
* `yarn` - install all the dependencies.
|
||||
* `yarn setup` - Install all the dependencies, boilerplate, plunkers, zips and runs dgeni on the docs.
|
||||
* `yarn setup` - install all the dependencies, boilerplate, plunkers, zips and run dgeni on the docs.
|
||||
* `yarn setup-local` - same as `setup`, but use the locally built Angular packages for aio and docs examples boilerplate.
|
||||
|
||||
* `yarn build` - create a production build of the application (after installing dependencies, boilerplate, etc).
|
||||
* `yarn build-local` - same as `build`, but use `setup-local` instead of `setup`.
|
||||
|
||||
* `yarn start` - run a development web server that watches the files; then builds the doc-viewer and reloads the page, as necessary.
|
||||
* `yarn serve-and-sync` - run both the `docs-watch` and `start` in the same console.
|
||||
@ -26,32 +30,33 @@ Here are the most important tasks you might need to use:
|
||||
* `yarn docs-lint` - check that the doc gen code follows our style rules.
|
||||
* `yarn docs-test` - run the unit tests for the doc generation code.
|
||||
|
||||
* `yarn boilerplate:add` - generate all the boilerplate code for the examples, so that they can be run locally.
|
||||
* `yarn boilerplate:add` - generate all the boilerplate code for the examples, so that they can be run locally. Add the option `--local` to use your local version of Angular contained in the "dist" folder.
|
||||
* `yarn boilerplate:remove` - remove all the boilerplate code that was added via `yarn boilerplate:add`.
|
||||
* `yarn generate-plunkers` - generate the plunker files that are used by the `live-example` tags in the docs.
|
||||
* `yarn generate-zips` - generate the zip files from the examples. Zip available via the `live-example` tags in the docs.
|
||||
|
||||
* `yarn example-e2e` - run all e2e tests for examples
|
||||
- `yarn example-e2e -- --setup` - force webdriver update & other setup, then run tests
|
||||
- `yarn example-e2e -- --filter=foo` - limit e2e tests to those containing the word "foo"
|
||||
- `yarn example-e2e --setup` - force webdriver update & other setup, then run tests
|
||||
- `yarn example-e2e --filter=foo` - limit e2e tests to those containing the word "foo"
|
||||
- `yarn example-e2e --setup --local` - run e2e tests with the local version of Angular contained in the "dist" folder
|
||||
|
||||
* `yarn build-ie-polyfills` - generates a js file of polyfills that can be loaded in Internet Explorer.
|
||||
|
||||
## Using ServiceWorker locally
|
||||
|
||||
Since abb36e3cb, running `yarn start -- --prod` will no longer set up the ServiceWorker, which
|
||||
Since abb36e3cb, running `yarn start --prod` will no longer set up the ServiceWorker, which
|
||||
would require manually running `yarn sw-manifest` and `yarn sw-copy` (something that is not possible
|
||||
with webpack serving the files from memory).
|
||||
|
||||
If you want to test ServiceWorker locally, you can use `yarn build` and serve the files in `dist/`
|
||||
with `yarn http-server -- dist -p 4200`.
|
||||
with `yarn http-server dist -p 4200`.
|
||||
|
||||
For more details see #16745.
|
||||
|
||||
|
||||
## Guide to authoring
|
||||
|
||||
There are two types of content in the documentatation:
|
||||
There are two types of content in the documentation:
|
||||
|
||||
* **API docs**: descriptions of the modules, classes, interfaces, decorators, etc that make up the Angular platform.
|
||||
API docs are generated directly from the source code.
|
||||
@ -106,8 +111,16 @@ yarn start
|
||||
yarn docs-watch
|
||||
```
|
||||
|
||||
>Alternatively, try the consolidated `serve-and-sync` command that builds, watches and serves in the same terminal window
|
||||
```bash
|
||||
yarn serve-and-sync
|
||||
```
|
||||
|
||||
* Open a browser at https://localhost:4200/ and navigate to the document on which you want to work.
|
||||
You can automatically open the browser by using `yarn start -- -o` in the first terminal.
|
||||
You can automatically open the browser by using `yarn start -o` in the first terminal.
|
||||
|
||||
* Make changes to the page's associated doc or example files. Every time a file is saved, the doc will
|
||||
be regenerated, the app will rebuild and the page will reload.
|
||||
|
||||
* If you get a build error complaining about examples or any other odd behavior, be sure to consult
|
||||
the [Authors Style Guide](https://angular.io/guide/docs-style-guide).
|
||||
|
@ -156,7 +156,7 @@ RUN find $AIO_SCRIPTS_SH_DIR -maxdepth 1 -type f -printf "%P\n" \
|
||||
# Set up the Node.js scripts
|
||||
COPY scripts-js/ $AIO_SCRIPTS_JS_DIR/
|
||||
WORKDIR $AIO_SCRIPTS_JS_DIR/
|
||||
RUN yarn install --production
|
||||
RUN yarn install --production --freeze-lockfile
|
||||
|
||||
|
||||
# Set up health check
|
||||
|
@ -6,7 +6,7 @@ server=8.8.4.4
|
||||
# Listen for DHCP and DNS requests only on this address.
|
||||
listen-address=127.0.0.1
|
||||
|
||||
# Force an IP addres for these domains.
|
||||
# Force an IP address for these domains.
|
||||
address=/{{$AIO_NGINX_HOSTNAME}}/127.0.0.1
|
||||
address=/{{$AIO_UPLOAD_HOSTNAME}}/127.0.0.1
|
||||
address=/{{$TEST_AIO_NGINX_HOSTNAME}}/127.0.0.1
|
||||
|
@ -32,7 +32,8 @@ export class BuildCreator extends EventEmitter {
|
||||
then(() => Promise.all([this.exists(prDir), this.exists(shaDir)])).
|
||||
then(([prDirExisted, shaDirExisted]) => {
|
||||
if (shaDirExisted) {
|
||||
throw new UploadError(409, `Request to overwrite existing directory: ${shaDir}`);
|
||||
const publicOrNot = isPublic ? 'public' : 'non-public';
|
||||
throw new UploadError(409, `Request to overwrite existing ${publicOrNot} directory: ${shaDir}`);
|
||||
}
|
||||
|
||||
dirToRemoveOnError = prDirExisted ? shaDir : prDir;
|
||||
|
@ -13,10 +13,8 @@ const AIO_REPO_SLUG = getEnvVar('AIO_REPO_SLUG');
|
||||
const AIO_TRUSTED_PR_LABEL = getEnvVar('AIO_TRUSTED_PR_LABEL');
|
||||
const AIO_UPLOAD_HOSTNAME = getEnvVar('AIO_UPLOAD_HOSTNAME');
|
||||
const AIO_UPLOAD_PORT = +getEnvVar('AIO_UPLOAD_PORT');
|
||||
const AIO_WWW_USER = getEnvVar('AIO_WWW_USER');
|
||||
|
||||
// Run
|
||||
process.setuid(AIO_WWW_USER); // TODO(gkalpak): Find more suitable way to run as `www-data`.
|
||||
_main();
|
||||
|
||||
// Functions
|
||||
|
@ -18,7 +18,7 @@ const TEST_AIO_UPLOAD_PORT = +getEnvVar('TEST_AIO_UPLOAD_PORT');
|
||||
const WWW_USER = getEnvVar('AIO_WWW_USER');
|
||||
|
||||
// Interfaces - Types
|
||||
export interface CmdResult { success: boolean; err: Error; stdout: string; stderr: string; }
|
||||
export interface CmdResult { success: boolean; err: Error | null; stdout: string; stderr: string; }
|
||||
export interface FileSpecs { content?: string; size?: number; }
|
||||
|
||||
export type CleanUpFn = () => void;
|
||||
@ -143,7 +143,7 @@ class Helper {
|
||||
statusText = status[1];
|
||||
} else {
|
||||
statusCode = status;
|
||||
statusText = http.STATUS_CODES[statusCode];
|
||||
statusText = http.STATUS_CODES[statusCode] || 'UNKNOWN_STATUS_CODE';
|
||||
}
|
||||
|
||||
return (result: CmdResult) => {
|
||||
@ -196,7 +196,7 @@ class Helper {
|
||||
}
|
||||
|
||||
// Methods - Protected
|
||||
protected createCleanUpFn(fn: Function): CleanUpFn {
|
||||
protected createCleanUpFn(fn: () => void): CleanUpFn {
|
||||
const cleanUpFn = () => {
|
||||
const idx = this.cleanUpFns.indexOf(cleanUpFn);
|
||||
if (idx !== -1) {
|
||||
|
@ -110,6 +110,7 @@ describe('upload-server (on HTTP)', () => {
|
||||
const authorizationHeader2 = isPublic ?
|
||||
authorizationHeader : `--header "Authorization: ${c.BV_verify_verifiedNotTrusted}"`;
|
||||
const cmdPrefix = curl('', `${authorizationHeader2} ${xFileHeader}`);
|
||||
const overwriteRe = RegExp(`^Request to overwrite existing ${isPublic ? 'public' : 'non-public'} directory`);
|
||||
|
||||
|
||||
it('should not overwrite existing builds', done => {
|
||||
@ -120,7 +121,7 @@ describe('upload-server (on HTTP)', () => {
|
||||
expect(h.readBuildFile(pr, sha9, 'index.html', isPublic)).toBe('My content');
|
||||
|
||||
h.runCmd(`${cmdPrefix} http://${host}/create-build/${pr}/${sha9}`).
|
||||
then(h.verifyResponse(409, /^Request to overwrite existing directory/)).
|
||||
then(h.verifyResponse(409, overwriteRe)).
|
||||
then(() => expect(h.readBuildFile(pr, sha9, 'index.html', isPublic)).toBe('My content')).
|
||||
then(done);
|
||||
});
|
||||
@ -141,7 +142,7 @@ describe('upload-server (on HTTP)', () => {
|
||||
expect(h.readBuildFile(pr, sha9, 'index.html', isPublic)).toBe('My content');
|
||||
|
||||
h.runCmd(`${cmdPrefix} http://${host}/create-build/${pr}/${sha9Almost}`).
|
||||
then(h.verifyResponse(409, /^Request to overwrite existing directory/)).
|
||||
then(h.verifyResponse(409, overwriteRe)).
|
||||
then(() => expect(h.readBuildFile(pr, sha9, 'index.html', isPublic)).toBe('My content')).
|
||||
then(done);
|
||||
});
|
||||
@ -310,7 +311,7 @@ describe('upload-server (on HTTP)', () => {
|
||||
expect(h.buildExists(pr, sha0, isPublic)).toBe(false);
|
||||
|
||||
uploadBuild(sha0).
|
||||
then(h.verifyResponse(409, /^Request to overwrite existing directory/)).
|
||||
then(h.verifyResponse(409, overwriteRe)).
|
||||
then(() => {
|
||||
checkPrVisibility(isPublic);
|
||||
expect(h.readBuildFile(pr, sha0, 'index.html', isPublic)).toContain(pr);
|
||||
|
@ -8,7 +8,7 @@
|
||||
"scripts": {
|
||||
"prebuild": "yarn clean-dist",
|
||||
"build": "tsc",
|
||||
"build-watch": "yarn tsc -- --watch",
|
||||
"build-watch": "yarn tsc --watch",
|
||||
"clean-dist": "node --eval \"require('shelljs').rm('-rf', 'dist')\"",
|
||||
"dev": "concurrently --kill-others --raw --success first \"yarn build-watch\" \"yarn test-watch\"",
|
||||
"lint": "tslint --project tsconfig.json",
|
||||
@ -20,26 +20,26 @@
|
||||
"test-watch": "nodemon --exec \"yarn ~~test-only\" --watch dist"
|
||||
},
|
||||
"dependencies": {
|
||||
"body-parser": "^1.17.2",
|
||||
"express": "^4.14.1",
|
||||
"jasmine": "^2.5.3",
|
||||
"jsonwebtoken": "^7.3.0",
|
||||
"shelljs": "^0.7.6"
|
||||
"body-parser": "^1.18.2",
|
||||
"express": "^4.15.4",
|
||||
"jasmine": "^2.8.0",
|
||||
"jsonwebtoken": "^8.0.1",
|
||||
"shelljs": "^0.7.8",
|
||||
"tslib": "^1.7.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/body-parser": "^1.16.4",
|
||||
"@types/express": "^4.0.35",
|
||||
"@types/jasmine": "^2.5.43",
|
||||
"@types/jsonwebtoken": "^7.2.0",
|
||||
"@types/node": "^7.0.5",
|
||||
"@types/shelljs": "^0.7.0",
|
||||
"@types/supertest": "^2.0.0",
|
||||
"concurrently": "^3.3.0",
|
||||
"eslint": "^3.15.0",
|
||||
"eslint-plugin-jasmine": "^2.2.0",
|
||||
"nodemon": "^1.11.0",
|
||||
"@types/body-parser": "^1.16.5",
|
||||
"@types/express": "^4.0.37",
|
||||
"@types/jasmine": "^2.6.0",
|
||||
"@types/jsonwebtoken": "^7.2.3",
|
||||
"@types/node": "^8.0.30",
|
||||
"@types/shelljs": "^0.7.4",
|
||||
"@types/supertest": "^2.0.3",
|
||||
"concurrently": "^3.5.0",
|
||||
"nodemon": "^1.12.1",
|
||||
"supertest": "^3.0.0",
|
||||
"tslint": "^4.4.2",
|
||||
"typescript": "^2.1.6"
|
||||
"tslint": "^5.7.0",
|
||||
"tslint-jasmine-noSkipOrFocus": "^1.0.8",
|
||||
"typescript": "^2.5.2"
|
||||
}
|
||||
}
|
||||
|
@ -39,8 +39,8 @@ describe('BuildCleaner', () => {
|
||||
let cleanerGetExistingBuildNumbersSpy: jasmine.Spy;
|
||||
let cleanerGetOpenPrNumbersSpy: jasmine.Spy;
|
||||
let cleanerRemoveUnnecessaryBuildsSpy: jasmine.Spy;
|
||||
let existingBuildsDeferred: {resolve: Function, reject: Function};
|
||||
let openPrsDeferred: {resolve: Function, reject: Function};
|
||||
let existingBuildsDeferred: {resolve: (v?: any) => void, reject: (e?: any) => void};
|
||||
let openPrsDeferred: {resolve: (v?: any) => void, reject: (e?: any) => void};
|
||||
let promise: Promise<void>;
|
||||
|
||||
beforeEach(() => {
|
||||
@ -195,7 +195,7 @@ describe('BuildCleaner', () => {
|
||||
|
||||
|
||||
describe('getOpenPrNumbers()', () => {
|
||||
let prDeferred: {resolve: Function, reject: Function};
|
||||
let prDeferred: {resolve: (v: any) => void, reject: (v: any) => void};
|
||||
let promise: Promise<number[]>;
|
||||
|
||||
beforeEach(() => {
|
||||
@ -277,7 +277,10 @@ describe('BuildCleaner', () => {
|
||||
|
||||
it('should catch errors and log them', () => {
|
||||
const consoleErrorSpy = spyOn(console, 'error');
|
||||
shellRmSpy.and.callFake(() => { throw 'Test'; });
|
||||
shellRmSpy.and.callFake(() => {
|
||||
// tslint:disable-next-line: no-string-throw
|
||||
throw 'Test';
|
||||
});
|
||||
|
||||
(cleaner as any).removeDir('/foo/bar');
|
||||
|
||||
|
@ -56,7 +56,7 @@ describe('GithubApi', () => {
|
||||
|
||||
|
||||
it('should not pass data to \'request()\'', () => {
|
||||
(api.get as Function)('foo', {}, {});
|
||||
(api.get as any)('foo', {}, {});
|
||||
|
||||
expect(apiRequestSpy).toHaveBeenCalled();
|
||||
expect(apiRequestSpy.calls.argsFor(0)[2]).toBeUndefined();
|
||||
@ -144,7 +144,7 @@ describe('GithubApi', () => {
|
||||
|
||||
|
||||
describe('getPaginated()', () => {
|
||||
let deferreds: {resolve: Function, reject: Function}[];
|
||||
let deferreds: {resolve: (v: any) => void, reject: (v: any) => void}[];
|
||||
|
||||
beforeEach(() => {
|
||||
deferreds = [];
|
||||
@ -292,7 +292,7 @@ describe('GithubApi', () => {
|
||||
|
||||
|
||||
describe('onResponse', () => {
|
||||
let promise: Promise<Object>;
|
||||
let promise: Promise<object>;
|
||||
let respond: (statusCode: number) => IncomingMessage;
|
||||
|
||||
beforeEach(() => {
|
||||
|
@ -22,7 +22,7 @@ describe('GithubPullRequests', () => {
|
||||
|
||||
describe('addComment()', () => {
|
||||
let prs: GithubPullRequests;
|
||||
let deferred: {resolve: Function, reject: Function};
|
||||
let deferred: {resolve: (v: any) => void, reject: (v: any) => void};
|
||||
|
||||
beforeEach(() => {
|
||||
prs = new GithubPullRequests('12345', 'foo/bar');
|
||||
|
@ -153,7 +153,8 @@ describe('BuildCreator', () => {
|
||||
it('should abort and skip further operations if the build does already exist', done => {
|
||||
existsValues[shaDir] = true;
|
||||
bc.create(pr, sha, archive, isPublic).catch(err => {
|
||||
expectToBeUploadError(err, 409, `Request to overwrite existing directory: ${shaDir}`);
|
||||
const publicOrNot = isPublic ? 'public' : 'non-public';
|
||||
expectToBeUploadError(err, 409, `Request to overwrite existing ${publicOrNot} directory: ${shaDir}`);
|
||||
expect(shellMkdirSpy).not.toHaveBeenCalled();
|
||||
expect(bcExtractArchiveSpy).not.toHaveBeenCalled();
|
||||
expect(bcEmitSpy).not.toHaveBeenCalled();
|
||||
@ -169,7 +170,8 @@ describe('BuildCreator', () => {
|
||||
expect(bcExistsSpy(shaDir)).toBe(false);
|
||||
|
||||
bc.create(pr, sha, archive, isPublic).catch(err => {
|
||||
expectToBeUploadError(err, 409, `Request to overwrite existing directory: ${shaDir}`);
|
||||
const publicOrNot = isPublic ? 'public' : 'non-public';
|
||||
expectToBeUploadError(err, 409, `Request to overwrite existing ${publicOrNot} directory: ${shaDir}`);
|
||||
expect(shellMkdirSpy).not.toHaveBeenCalled();
|
||||
expect(bcExtractArchiveSpy).not.toHaveBeenCalled();
|
||||
expect(bcEmitSpy).not.toHaveBeenCalled();
|
||||
@ -221,6 +223,7 @@ describe('BuildCreator', () => {
|
||||
|
||||
|
||||
it('should reject with an UploadError', done => {
|
||||
// tslint:disable-next-line: no-string-throw
|
||||
shellMkdirSpy.and.callFake(() => { throw 'Test'; });
|
||||
bc.create(pr, sha, archive, isPublic).catch(err => {
|
||||
expectToBeUploadError(err, 500, `Error while uploading to directory: ${shaDir}\nTest`);
|
||||
@ -405,6 +408,7 @@ describe('BuildCreator', () => {
|
||||
|
||||
|
||||
it('should reject with an UploadError', done => {
|
||||
// tslint:disable-next-line: no-string-throw
|
||||
shellMvSpy.and.callFake(() => { throw 'Test'; });
|
||||
bc.updatePrVisibility(pr, makePublic).catch(err => {
|
||||
expectToBeUploadError(err, 500, `Error while making PR ${pr} ${makePublic ? 'public' : 'hidden'}.\nTest`);
|
||||
@ -432,11 +436,11 @@ describe('BuildCreator', () => {
|
||||
|
||||
describe('exists()', () => {
|
||||
let fsAccessSpy: jasmine.Spy;
|
||||
let fsAccessCbs: Function[];
|
||||
let fsAccessCbs: ((v?: any) => void)[];
|
||||
|
||||
beforeEach(() => {
|
||||
fsAccessCbs = [];
|
||||
fsAccessSpy = spyOn(fs, 'access').and.callFake((_: string, cb: Function) => fsAccessCbs.push(cb));
|
||||
fsAccessSpy = spyOn(fs, 'access').and.callFake((_: string, cb: (v?: any) => void) => fsAccessCbs.push(cb));
|
||||
});
|
||||
|
||||
|
||||
@ -480,7 +484,7 @@ describe('BuildCreator', () => {
|
||||
let shellChmodSpy: jasmine.Spy;
|
||||
let shellRmSpy: jasmine.Spy;
|
||||
let cpExecSpy: jasmine.Spy;
|
||||
let cpExecCbs: Function[];
|
||||
let cpExecCbs: ((...args: any[]) => void)[];
|
||||
|
||||
beforeEach(() => {
|
||||
cpExecCbs = [];
|
||||
@ -488,7 +492,7 @@ describe('BuildCreator', () => {
|
||||
consoleWarnSpy = spyOn(console, 'warn');
|
||||
shellChmodSpy = spyOn(shell, 'chmod');
|
||||
shellRmSpy = spyOn(shell, 'rm');
|
||||
cpExecSpy = spyOn(cp, 'exec').and.callFake((_: string, cb: Function) => cpExecCbs.push(cb));
|
||||
cpExecSpy = spyOn(cp, 'exec').and.callFake((_: string, cb: (...args: any[]) => void) => cpExecCbs.push(cb));
|
||||
});
|
||||
|
||||
|
||||
@ -554,7 +558,11 @@ describe('BuildCreator', () => {
|
||||
done();
|
||||
});
|
||||
|
||||
shellChmodSpy.and.callFake(() => { throw 'Test'; });
|
||||
shellChmodSpy.and.callFake(() => {
|
||||
// tslint:disable-next-line: no-string-throw
|
||||
throw 'Test';
|
||||
});
|
||||
|
||||
cpExecCbs[0]();
|
||||
});
|
||||
|
||||
@ -567,7 +575,11 @@ describe('BuildCreator', () => {
|
||||
done();
|
||||
});
|
||||
|
||||
shellRmSpy.and.callFake(() => { throw 'Test'; });
|
||||
shellRmSpy.and.callFake(() => {
|
||||
// tslint:disable-next-line: no-string-throw
|
||||
throw 'Test';
|
||||
});
|
||||
|
||||
cpExecCbs[0]();
|
||||
});
|
||||
|
||||
|
@ -116,7 +116,7 @@ describe('uploadServerFactory', () => {
|
||||
|
||||
it('should log the server address info on \'listening\'', () => {
|
||||
const consoleInfoSpy = spyOn(console, 'info');
|
||||
const server = createUploadServer('builds/dir');
|
||||
const server = createUploadServer();
|
||||
server.address = () => ({address: 'foo', family: '', port: 1337});
|
||||
|
||||
expect(consoleInfoSpy).not.toHaveBeenCalled();
|
||||
|
@ -1,25 +1,66 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"alwaysStrict": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"inlineSourceMap": true,
|
||||
/* Basic Options */
|
||||
"target": "es5", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', or 'ESNEXT'. */
|
||||
"module": "commonjs", /* Specify module code generation: 'none', commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */
|
||||
"lib": [
|
||||
"es2016"
|
||||
],
|
||||
"noImplicitAny": true,
|
||||
"noImplicitReturns": true,
|
||||
"noImplicitThis": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"outDir": "dist",
|
||||
"pretty": true,
|
||||
"rootDir": ".",
|
||||
"skipLibCheck": true,
|
||||
"strictNullChecks": true,
|
||||
"target": "es5",
|
||||
"es2015",
|
||||
"es2016.array.include"
|
||||
], /* Specify library files to be included in the compilation: */
|
||||
// "allowJs": true, /* Allow javascript files to be compiled. */
|
||||
// "checkJs": true, /* Report errors in .js files. */
|
||||
// "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */
|
||||
// "declaration": true, /* Generates corresponding '.d.ts' file. */
|
||||
// "sourceMap": true, /* Generates corresponding '.map' file. */
|
||||
// "outFile": "./", /* Concatenate and emit output to single file. */
|
||||
"outDir": "dist", /* Redirect output structure to the directory. */
|
||||
// "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */
|
||||
// "removeComments": true, /* Do not emit comments to output. */
|
||||
// "noEmit": true, /* Do not emit outputs. */
|
||||
"importHelpers": true, /* Import emit helpers from 'tslib'. */
|
||||
// "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */
|
||||
// "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */
|
||||
|
||||
/* Strict Type-Checking Options */
|
||||
"strict": true, /* Enable all strict type-checking options. */
|
||||
// "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */
|
||||
// "strictNullChecks": true, /* Enable strict null checks. */
|
||||
// "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */
|
||||
// "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */
|
||||
|
||||
/* Additional Checks */
|
||||
"noUnusedLocals": true, /* Report errors on unused locals. */
|
||||
"noUnusedParameters": true, /* Report errors on unused parameters. */
|
||||
"noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
|
||||
// "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
|
||||
|
||||
/* Module Resolution Options */
|
||||
// "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
|
||||
// "baseUrl": "./", /* Base directory to resolve non-absolute module names. */
|
||||
// "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
|
||||
// "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */
|
||||
"typeRoots": [
|
||||
"node_modules/@types"
|
||||
]
|
||||
], /* List of folders to include type definitions from. */
|
||||
// "types": [], /* Type declaration files to be included in compilation. */
|
||||
// "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */
|
||||
// "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */
|
||||
|
||||
/* Source Map Options */
|
||||
// "sourceRoot": "./", /* Specify the location where debugger should locate TypeScript files instead of source locations. */
|
||||
// "mapRoot": "./", /* Specify the location where debugger should locate map files instead of generated locations. */
|
||||
"inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */
|
||||
// "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */
|
||||
|
||||
/* Experimental Options */
|
||||
// "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */
|
||||
// "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */
|
||||
|
||||
/* Other */
|
||||
"forceConsistentCasingInFileNames": true, /* Disallow inconsistently-cased references to the same file. */
|
||||
"newLine": "LF", /* Use the specified end of line sequence to be used when emitting files: "crlf" (windows) or "lf" (unix). */
|
||||
"pretty": true, /* Stylize errors and messages using color and context. */
|
||||
"skipLibCheck": true /* Skip type checking of all declaration files (*.d.ts). */
|
||||
},
|
||||
"include": [
|
||||
"lib/**/*",
|
||||
|
@ -1,15 +1,24 @@
|
||||
{
|
||||
"extends": "tslint:recommended",
|
||||
"defaultSeverity": "error",
|
||||
"extends": [
|
||||
"tslint:recommended"
|
||||
],
|
||||
"jsRules": {},
|
||||
"rules": {
|
||||
"array-type": [true, "array"],
|
||||
"arrow-parens": [true, "ban-single-arg-parens"],
|
||||
"interface-name": [true, "never-prefix"],
|
||||
"max-classes-per-file": [true, 4],
|
||||
"no-consecutive-blank-lines": [true, 2],
|
||||
"no-console": false,
|
||||
"no-console": [false],
|
||||
"no-focused-test": true,
|
||||
"no-namespace": [true, "allow-declarations"],
|
||||
"no-skipped-test": true,
|
||||
"no-string-literal": false,
|
||||
"quotemark": [true, "single"],
|
||||
"variable-name": [true, "ban-keywords", "check-format", "allow-leading-underscore"]
|
||||
}
|
||||
},
|
||||
"rulesDirectory": [
|
||||
"node_modules/tslint-jasmine-noSkipOrFocus"
|
||||
]
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -6,10 +6,9 @@ export AIO_GITHUB_TOKEN=$(head -c -1 /aio-secrets/GITHUB_TOKEN 2>/dev/null || ec
|
||||
export AIO_PREVIEW_DEPLOYMENT_TOKEN=$(head -c -1 /aio-secrets/PREVIEW_DEPLOYMENT_TOKEN 2>/dev/null || echo "MISSING_PREVIEW_DEPLOYMENT_TOKEN")
|
||||
|
||||
# Start the upload-server instance
|
||||
# TODO(gkalpak): Ideally, the upload server should be run as a non-privileged user.
|
||||
# (Currently, there doesn't seem to be a straight forward way.)
|
||||
action=$([ "$1" == "stop" ] && echo "stop" || echo "start")
|
||||
pm2 $action $AIO_SCRIPTS_JS_DIR/dist/lib/upload-server \
|
||||
--uid $AIO_WWW_USER \
|
||||
--log /var/log/aio/upload-server-prod.log \
|
||||
--name aio-upload-server-prod \
|
||||
${@:2}
|
||||
|
@ -15,13 +15,12 @@ export AIO_GITHUB_TOKEN=$(head -c -1 /aio-secrets/TEST_GITHUB_TOKEN 2>/dev/null
|
||||
export AIO_PREVIEW_DEPLOYMENT_TOKEN=$(head -c -1 /aio-secrets/TEST_PREVIEW_DEPLOYMENT_TOKEN 2>/dev/null || echo "TEST_PREVIEW_DEPLOYMENT_TOKEN")
|
||||
|
||||
# Start the upload-server instance
|
||||
# TODO(gkalpak): Ideally, the upload server should be run as a non-privileged user.
|
||||
# (Currently, there doesn't seem to be a straight forward way.)
|
||||
appName=aio-upload-server-test
|
||||
if [[ "$1" == "stop" ]]; then
|
||||
pm2 delete $appName
|
||||
else
|
||||
pm2 start $AIO_SCRIPTS_JS_DIR/dist/lib/verify-setup/start-test-upload-server.js \
|
||||
--uid $AIO_WWW_USER \
|
||||
--log /var/log/aio/upload-server-test.log \
|
||||
--name $appName \
|
||||
--no-autorestart \
|
||||
|
@ -46,8 +46,8 @@ with a bried explanation of what they mean:
|
||||
Request method other than POST.
|
||||
|
||||
- **409 (Conflict)**:
|
||||
Request to overwrite existing directory (e.g. deploy existing build or change PR visibility when
|
||||
the destination directory does already exist).
|
||||
Request to overwrite existing (public or non-public) directory (e.g. deploy existing build or
|
||||
change PR visibility when the destination directory does already exist).
|
||||
|
||||
- **413 (Payload Too Large)**:
|
||||
Payload larger than size specified in `AIO_UPLOAD_MAX_SIZE`.
|
||||
@ -71,7 +71,8 @@ with a bried explanation of what they mean:
|
||||
Request method other than POST.
|
||||
|
||||
- **409 (Conflict)**:
|
||||
Request to overwrite existing directory (i.e. directories for both visibilities exist).
|
||||
Request to overwrite existing (public or non-public) directory (i.e. directories for both
|
||||
visibilities exist).
|
||||
(Normally, this should not happen.)
|
||||
|
||||
|
||||
|
@ -9,7 +9,7 @@ VM host to update the preview server based on changes in the source code.
|
||||
The script will pull the latest changes from the origin's master branch and examine if there have
|
||||
been any changes in files inside the preview server source code directory (see below). If there are,
|
||||
it will create a new image and verify that is works as expected. Finally, it will stop and remove
|
||||
the old docker container and image, create and new container based on the new image and start it.
|
||||
the old docker container and image, create a new container based on the new image and start it.
|
||||
|
||||
The script assumes that the preview server source code is in the repository's
|
||||
`aio/aio-builds-setup/` directory and expects the following inputs:
|
||||
|
@ -9,7 +9,7 @@ readonly defaultImageNameAndTag="aio-builds:latest"
|
||||
# (Necessary, because only `scripts-js/dist/` is copied to the docker image.)
|
||||
(
|
||||
cd "$SCRIPTS_JS_DIR"
|
||||
yarn install
|
||||
yarn install --freeze-lockfile --non-interactive
|
||||
yarn build
|
||||
)
|
||||
|
||||
|
@ -7,6 +7,6 @@ source "`dirname $0`/_env.sh"
|
||||
# Test `scripts-js/`
|
||||
(
|
||||
cd "$SCRIPTS_JS_DIR"
|
||||
yarn install
|
||||
yarn install --freeze-lockfile --non-interactive
|
||||
yarn test
|
||||
)
|
||||
|
14
aio/content/examples/.gitignore
vendored
14
aio/content/examples/.gitignore
vendored
@ -43,13 +43,9 @@ dist/
|
||||
**/app/**/*.ajs.js
|
||||
|
||||
# aot
|
||||
**/*.ngfactory.ts
|
||||
**/*.ngsummary.json
|
||||
**/*.ngsummary.ts
|
||||
**/*.shim.ngstyle.ts
|
||||
**/*.metadata.json
|
||||
!aot/bs-config.json
|
||||
!aot/index.html
|
||||
*/aot/**/*
|
||||
!*/aot/bs-config.json
|
||||
!*/aot/index.html
|
||||
!rollup-config.js
|
||||
|
||||
# i18n
|
||||
@ -59,10 +55,6 @@ dist/
|
||||
!testing/src/browser-test-shim.js
|
||||
!testing/karma*.js
|
||||
|
||||
# TS to JS
|
||||
!ts-to-js/js*/**/*.js
|
||||
ts-to-js/js*/**/system*.js
|
||||
|
||||
# webpack
|
||||
!webpack/**/config/*.js
|
||||
!webpack/**/*webpack*.js
|
||||
|
@ -335,17 +335,17 @@ describe('Animation Tests', () => {
|
||||
});
|
||||
}
|
||||
|
||||
function getBoundingClientWidth(el: ElementFinder): promise.Promise<number> {
|
||||
function getBoundingClientWidth(el: ElementFinder) {
|
||||
return browser.executeScript(
|
||||
'return arguments[0].getBoundingClientRect().width',
|
||||
el.getWebElement()
|
||||
);
|
||||
) as PromiseLike<number>;
|
||||
}
|
||||
|
||||
function getOffsetWidth(el: ElementFinder): promise.Promise<number> {
|
||||
function getOffsetWidth(el: ElementFinder) {
|
||||
return browser.executeScript(
|
||||
'return arguments[0].offsetWidth',
|
||||
el.getWebElement()
|
||||
);
|
||||
) as PromiseLike<number>;
|
||||
}
|
||||
});
|
||||
|
@ -1,5 +1,6 @@
|
||||
// #docregion animations-module
|
||||
// #docplaster
|
||||
import { NgModule } from '@angular/core';
|
||||
// #docregion animations-module
|
||||
import { BrowserModule } from '@angular/platform-browser';
|
||||
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
|
||||
// #enddocregion animations-module
|
||||
@ -15,11 +16,12 @@ import { HeroListAutoComponent } from './hero-list-auto.component';
|
||||
import { HeroListGroupsComponent } from './hero-list-groups.component';
|
||||
import { HeroListMultistepComponent } from './hero-list-multistep.component';
|
||||
import { HeroListTimingsComponent } from './hero-list-timings.component';
|
||||
// #docregion animations-module
|
||||
|
||||
// #docregion animation-module
|
||||
@NgModule({
|
||||
imports: [ BrowserModule, BrowserAnimationsModule ],
|
||||
// #enddocregion animation-module
|
||||
// ... more stuff ...
|
||||
// #enddocregion animations-module
|
||||
declarations: [
|
||||
HeroTeamBuilderComponent,
|
||||
HeroListBasicComponent,
|
||||
@ -34,5 +36,8 @@ import { HeroListTimingsComponent } from './hero-list-timings.component';
|
||||
HeroListGroupsComponent
|
||||
],
|
||||
bootstrap: [ HeroTeamBuilderComponent ]
|
||||
// #docregion animations-module
|
||||
})
|
||||
export class AppModule { }
|
||||
// #enddocregion animations-module
|
||||
|
||||
|
@ -10,7 +10,7 @@ import {
|
||||
transition
|
||||
} from '@angular/animations';
|
||||
|
||||
import { Heroes } from './hero.service';
|
||||
import { Hero } from './hero.service';
|
||||
|
||||
@Component({
|
||||
selector: 'hero-list-auto',
|
||||
@ -43,5 +43,5 @@ import { Heroes } from './hero.service';
|
||||
// #enddocregion animationdef
|
||||
})
|
||||
export class HeroListAutoComponent {
|
||||
@Input() heroes: Heroes;
|
||||
@Input() heroes: Hero[];
|
||||
}
|
||||
|
@ -14,7 +14,7 @@ import {
|
||||
} from '@angular/animations';
|
||||
// #enddocregion imports
|
||||
|
||||
import { Heroes } from './hero.service';
|
||||
import { Hero } from './hero.service';
|
||||
|
||||
@Component({
|
||||
selector: 'hero-list-basic',
|
||||
@ -66,5 +66,5 @@ import { Heroes } from './hero.service';
|
||||
// #enddocregion animationdef
|
||||
})
|
||||
export class HeroListBasicComponent {
|
||||
@Input() heroes: Heroes;
|
||||
@Input() heroes: Hero[];
|
||||
}
|
||||
|
@ -13,7 +13,7 @@ import {
|
||||
} from '@angular/animations';
|
||||
// #enddocregion imports
|
||||
|
||||
import { Heroes } from './hero.service';
|
||||
import { Hero } from './hero.service';
|
||||
|
||||
@Component({
|
||||
selector: 'hero-list-combined-transitions',
|
||||
@ -55,5 +55,5 @@ import { Heroes } from './hero.service';
|
||||
// #enddocregion animationdef
|
||||
})
|
||||
export class HeroListCombinedTransitionsComponent {
|
||||
@Input() heroes: Heroes;
|
||||
@Input() heroes: Hero[];
|
||||
}
|
||||
|
@ -10,7 +10,7 @@ import {
|
||||
transition
|
||||
} from '@angular/animations';
|
||||
|
||||
import { Heroes } from './hero.service';
|
||||
import { Hero } from './hero.service';
|
||||
|
||||
@Component({
|
||||
selector: 'hero-list-enter-leave-states',
|
||||
@ -59,5 +59,5 @@ import { Heroes } from './hero.service';
|
||||
// #enddocregion animationdef
|
||||
})
|
||||
export class HeroListEnterLeaveStatesComponent {
|
||||
@Input() heroes: Heroes;
|
||||
@Input() heroes: Hero[];
|
||||
}
|
||||
|
@ -10,7 +10,7 @@ import {
|
||||
transition
|
||||
} from '@angular/animations';
|
||||
|
||||
import { Heroes } from './hero.service';
|
||||
import { Hero } from './hero.service';
|
||||
|
||||
@Component({
|
||||
selector: 'hero-list-enter-leave',
|
||||
@ -47,5 +47,5 @@ import { Heroes } from './hero.service';
|
||||
// #enddocregion animationdef
|
||||
})
|
||||
export class HeroListEnterLeaveComponent {
|
||||
@Input() heroes: Heroes;
|
||||
@Input() heroes: Hero[];
|
||||
}
|
||||
|
@ -11,7 +11,7 @@ import {
|
||||
group
|
||||
} from '@angular/animations';
|
||||
|
||||
import { Heroes } from './hero.service';
|
||||
import { Hero } from './hero.service';
|
||||
|
||||
@Component({
|
||||
selector: 'hero-list-groups',
|
||||
@ -76,5 +76,5 @@ import { Heroes } from './hero.service';
|
||||
// #enddocregion animationdef
|
||||
})
|
||||
export class HeroListGroupsComponent {
|
||||
@Input() heroes: Heroes;
|
||||
@Input() heroes: Hero[];
|
||||
}
|
||||
|
@ -12,7 +12,7 @@ import {
|
||||
} from '@angular/animations';
|
||||
// #enddocregion imports
|
||||
|
||||
import { Heroes } from './hero.service';
|
||||
import { Hero } from './hero.service';
|
||||
|
||||
@Component({
|
||||
selector: 'hero-list-inline-styles',
|
||||
@ -56,5 +56,5 @@ import { Heroes } from './hero.service';
|
||||
// #enddocregion animationdef
|
||||
})
|
||||
export class HeroListInlineStylesComponent {
|
||||
@Input() heroes: Heroes;
|
||||
@Input() heroes: Hero[];
|
||||
}
|
||||
|
@ -12,7 +12,7 @@ import {
|
||||
AnimationEvent
|
||||
} from '@angular/animations';
|
||||
|
||||
import { Heroes } from './hero.service';
|
||||
import { Hero } from './hero.service';
|
||||
|
||||
@Component({
|
||||
selector: 'hero-list-multistep',
|
||||
@ -59,7 +59,7 @@ import { Heroes } from './hero.service';
|
||||
// #enddocregion animationdef
|
||||
})
|
||||
export class HeroListMultistepComponent {
|
||||
@Input() heroes: Heroes;
|
||||
@Input() heroes: Hero[];
|
||||
|
||||
animationStarted(event: AnimationEvent) {
|
||||
console.warn('Animation started: ', event);
|
||||
|
@ -10,7 +10,7 @@ import {
|
||||
transition
|
||||
} from '@angular/animations';
|
||||
|
||||
import { Heroes } from './hero.service';
|
||||
import { Hero } from './hero.service';
|
||||
|
||||
@Component({
|
||||
selector: 'hero-list-timings',
|
||||
@ -54,5 +54,5 @@ import { Heroes } from './hero.service';
|
||||
// #enddocregion animationdef
|
||||
})
|
||||
export class HeroListTimingsComponent {
|
||||
@Input() heroes: Heroes;
|
||||
@Input() heroes: Hero[];
|
||||
}
|
||||
|
@ -13,7 +13,7 @@ import {
|
||||
} from '@angular/animations';
|
||||
// #enddocregion imports
|
||||
|
||||
import { Heroes } from './hero.service';
|
||||
import { Hero } from './hero.service';
|
||||
|
||||
@Component({
|
||||
selector: 'hero-list-twoway',
|
||||
@ -54,5 +54,5 @@ import { Heroes } from './hero.service';
|
||||
// #enddocregion animationdef
|
||||
})
|
||||
export class HeroListTwowayComponent {
|
||||
@Input() heroes: Heroes;
|
||||
@Input() heroes: Hero[];
|
||||
}
|
||||
|
@ -1,40 +1,41 @@
|
||||
import { Component } from '@angular/core';
|
||||
|
||||
import { Heroes } from './hero.service';
|
||||
import { Hero, HeroService } from './hero.service';
|
||||
|
||||
@Component({
|
||||
selector: 'hero-team-builder',
|
||||
template: `
|
||||
<div class="buttons">
|
||||
<button [disabled]="!heroes.canAdd()" (click)="heroes.addInactive()">Add inactive hero</button>
|
||||
<button [disabled]="!heroes.canAdd()" (click)="heroes.addActive()">Add active hero</button>
|
||||
<button [disabled]="!heroes.canRemove()" (click)="heroes.remove()">Remove hero</button>
|
||||
<button [disabled]="!heroService.canAdd()" (click)="heroService.addInactive()">Add inactive hero</button>
|
||||
<button [disabled]="!heroService.canAdd()" (click)="heroService.addActive()">Add active hero</button>
|
||||
<button [disabled]="!heroService.canRemove()" (click)="heroService.remove()">Remove hero</button>
|
||||
</div>
|
||||
|
||||
<div class="columns">
|
||||
<div class="column">
|
||||
<h4>Basic State</h4>
|
||||
<p>Switch between active/inactive on click.</p>
|
||||
<hero-list-basic [heroes]=heroes></hero-list-basic>
|
||||
<hero-list-basic [heroes]="heroes"></hero-list-basic>
|
||||
</div>
|
||||
<div class="column">
|
||||
<h4>Styles inline in transitions</h4>
|
||||
<p>Animated effect on click, no persistend end styles.</p>
|
||||
<hero-list-inline-styles [heroes]=heroes></hero-list-inline-styles>
|
||||
<p>Animated effect on click, no persistent end styles.</p>
|
||||
<hero-list-inline-styles [heroes]="heroes"></hero-list-inline-styles>
|
||||
</div>
|
||||
<div class="column">
|
||||
<h4>Combined transition syntax</h4>
|
||||
<p>Switch between active/inactive on click. Define just one transition used in both directions.</p>
|
||||
<hero-list-combined-transitions [heroes]=heroes></hero-list-combined-transitions>
|
||||
<hero-list-combined-transitions [heroes]="heroes"></hero-list-combined-transitions>
|
||||
</div>
|
||||
<div class="column">
|
||||
<h4>Two-way transition syntax</h4>
|
||||
<p>Switch between active/inactive on click. Define just one transition used in both directions using the <=> syntax.</p>
|
||||
<hero-list-twoway [heroes]=heroes></hero-list-twoway>
|
||||
<hero-list-twoway [heroes]="heroes"></hero-list-twoway>
|
||||
</div>
|
||||
<div class="column">
|
||||
<h4>Enter & Leave</h4>
|
||||
<p>Enter and leave animations using the void state.</p>
|
||||
<hero-list-enter-leave [heroes]=heroes></hero-list-enter-leave>
|
||||
<hero-list-enter-leave [heroes]="heroes"></hero-list-enter-leave>
|
||||
</div>
|
||||
</div>
|
||||
<div class="columns">
|
||||
@ -44,27 +45,27 @@ import { Heroes } from './hero.service';
|
||||
Enter and leave animations combined with active/inactive state animations.
|
||||
Different enter and leave transitions depending on state.
|
||||
</p>
|
||||
<hero-list-enter-leave-states [heroes]=heroes></hero-list-enter-leave-states>
|
||||
<hero-list-enter-leave-states [heroes]="heroes"></hero-list-enter-leave-states>
|
||||
</div>
|
||||
<div class="column">
|
||||
<h4>Auto Style Calc</h4>
|
||||
<p>Leave animation from the current computed height using the auto-style value *.</p>
|
||||
<hero-list-auto [heroes]=heroes></hero-list-auto>
|
||||
<hero-list-auto [heroes]="heroes"></hero-list-auto>
|
||||
</div>
|
||||
<div class="column">
|
||||
<h4>Different Timings</h4>
|
||||
<p>Enter and leave animations with different easings, ease-in for enter, ease-out for leave.</p>
|
||||
<hero-list-timings [heroes]=heroes></hero-list-timings>
|
||||
<hero-list-timings [heroes]="heroes"></hero-list-timings>
|
||||
</div>
|
||||
<div class="column">
|
||||
<h4>Multiple Keyframes</h4>
|
||||
<p>Enter and leave animations with three keyframes in each, to give the transition some bounce.</p>
|
||||
<hero-list-multistep [heroes]=heroes></hero-list-multistep>
|
||||
<hero-list-multistep [heroes]="heroes"></hero-list-multistep>
|
||||
</div>
|
||||
<div class="column">
|
||||
<h4>Parallel Groups</h4>
|
||||
<p>Enter and leave animations with multiple properties animated in parallel with different timings.</p>
|
||||
<hero-list-groups [heroes]=heroes></hero-list-groups>
|
||||
<hero-list-groups [heroes]="heroes"></hero-list-groups>
|
||||
</div>
|
||||
</div>
|
||||
`,
|
||||
@ -87,8 +88,12 @@ import { Heroes } from './hero.service';
|
||||
min-height: 6em;
|
||||
}
|
||||
`],
|
||||
providers: [Heroes]
|
||||
providers: [HeroService]
|
||||
})
|
||||
export class HeroTeamBuilderComponent {
|
||||
constructor(private heroes: Heroes) { }
|
||||
heroes: Hero[];
|
||||
|
||||
constructor(private heroService: HeroService) {
|
||||
this.heroes = heroService.heroes;
|
||||
}
|
||||
}
|
||||
|
@ -1,16 +1,16 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
|
||||
class Hero {
|
||||
constructor(public name: string,
|
||||
public state = 'inactive') {
|
||||
}
|
||||
// #docregion hero
|
||||
export class Hero {
|
||||
constructor(public name: string, public state = 'inactive') { }
|
||||
|
||||
toggleState() {
|
||||
this.state = (this.state === 'active' ? 'inactive' : 'active');
|
||||
this.state = this.state === 'active' ? 'inactive' : 'active';
|
||||
}
|
||||
}
|
||||
// #enddocregion hero
|
||||
|
||||
let ALL_HEROES = [
|
||||
const ALL_HEROES = [
|
||||
'Windstorm',
|
||||
'RubberMan',
|
||||
'Bombasto',
|
||||
@ -25,36 +25,30 @@ let ALL_HEROES = [
|
||||
].map(name => new Hero(name));
|
||||
|
||||
@Injectable()
|
||||
export class Heroes implements Iterable<Hero> {
|
||||
export class HeroService {
|
||||
|
||||
currentHeroes: Hero[] = [];
|
||||
|
||||
[Symbol.iterator]() {
|
||||
return this.currentHeroes.values();
|
||||
}
|
||||
heroes: Hero[] = [];
|
||||
|
||||
canAdd() {
|
||||
return this.currentHeroes.length < ALL_HEROES.length;
|
||||
return this.heroes.length < ALL_HEROES.length;
|
||||
}
|
||||
|
||||
canRemove() {
|
||||
return this.currentHeroes.length > 0;
|
||||
return this.heroes.length > 0;
|
||||
}
|
||||
|
||||
addActive() {
|
||||
let hero = ALL_HEROES[this.currentHeroes.length];
|
||||
hero.state = 'active';
|
||||
this.currentHeroes.push(hero);
|
||||
addActive(active = true) {
|
||||
let hero = ALL_HEROES[this.heroes.length];
|
||||
hero.state = active ? 'active' : 'inactive';
|
||||
this.heroes.push(hero);
|
||||
}
|
||||
|
||||
addInactive() {
|
||||
let hero = ALL_HEROES[this.currentHeroes.length];
|
||||
hero.state = 'inactive';
|
||||
this.currentHeroes.push(hero);
|
||||
this.addActive(false);
|
||||
}
|
||||
|
||||
remove() {
|
||||
this.currentHeroes.splice(this.currentHeroes.length - 1, 1);
|
||||
this.heroes.length -= 1;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -12,8 +12,10 @@ describe('Component Style Tests', function () {
|
||||
let componentH1 = element(by.css('hero-app > h1'));
|
||||
let externalH1 = element(by.css('body > h1'));
|
||||
|
||||
expect(componentH1.getCssValue('fontWeight')).toEqual('normal');
|
||||
expect(externalH1.getCssValue('fontWeight')).not.toEqual('normal');
|
||||
// Note: sometimes webdriver returns the fontWeight as "normal",
|
||||
// othertimes as "400", both of which are equal in CSS terms.
|
||||
expect(componentH1.getCssValue('fontWeight')).toMatch(/normal|400/);
|
||||
expect(externalH1.getCssValue('fontWeight')).not.toMatch(/normal|400/);
|
||||
});
|
||||
|
||||
|
||||
|
@ -9,30 +9,20 @@ describe('Form Validation Tests', function () {
|
||||
browser.get('');
|
||||
});
|
||||
|
||||
describe('Hero Form 1', () => {
|
||||
describe('Template-driven form', () => {
|
||||
beforeAll(() => {
|
||||
getPage('hero-form-template1');
|
||||
getPage('hero-form-template');
|
||||
});
|
||||
|
||||
tests();
|
||||
tests('Template-Driven Form');
|
||||
});
|
||||
|
||||
describe('Hero Form 2', () => {
|
||||
describe('Reactive form', () => {
|
||||
beforeAll(() => {
|
||||
getPage('hero-form-template2');
|
||||
getPage('hero-form-reactive');
|
||||
});
|
||||
|
||||
tests();
|
||||
bobTests();
|
||||
});
|
||||
|
||||
describe('Hero Form 3 (Reactive)', () => {
|
||||
beforeAll(() => {
|
||||
getPage('hero-form-reactive3');
|
||||
makeNameTooLong();
|
||||
});
|
||||
|
||||
tests();
|
||||
tests('Reactive Form');
|
||||
bobTests();
|
||||
});
|
||||
});
|
||||
@ -48,6 +38,7 @@ let page: {
|
||||
nameInput: ElementFinder,
|
||||
alterEgoInput: ElementFinder,
|
||||
powerSelect: ElementFinder,
|
||||
powerOption: ElementFinder,
|
||||
errorMessages: ElementArrayFinder,
|
||||
heroFormButtons: ElementArrayFinder,
|
||||
heroSubmitted: ElementFinder
|
||||
@ -64,19 +55,21 @@ function getPage(sectionTag: string) {
|
||||
nameInput: section.element(by.css('#name')),
|
||||
alterEgoInput: section.element(by.css('#alterEgo')),
|
||||
powerSelect: section.element(by.css('#power')),
|
||||
powerOption: section.element(by.css('#power option')),
|
||||
errorMessages: section.all(by.css('div.alert')),
|
||||
heroFormButtons: buttons,
|
||||
heroSubmitted: section.element(by.css('hero-submitted > div'))
|
||||
heroSubmitted: section.element(by.css('.submitted-message'))
|
||||
};
|
||||
}
|
||||
|
||||
function tests() {
|
||||
function tests(title: string) {
|
||||
|
||||
it('should display correct title', function () {
|
||||
expect(page.title.getText()).toContain('Hero Form');
|
||||
expect(page.title.getText()).toContain(title);
|
||||
});
|
||||
|
||||
it('should not display submitted message before submit', function () {
|
||||
expect(page.heroSubmitted.isElementPresent(by.css('h2'))).toBe(false);
|
||||
expect(page.heroSubmitted.isElementPresent(by.css('p'))).toBe(false);
|
||||
});
|
||||
|
||||
it('should have form buttons', function () {
|
||||
@ -130,11 +123,11 @@ function tests() {
|
||||
|
||||
it('should hide form after submit', function () {
|
||||
page.heroFormButtons.get(0).click();
|
||||
expect(page.title.isDisplayed()).toBe(false);
|
||||
expect(page.heroFormButtons.get(0).isDisplayed()).toBe(false);
|
||||
});
|
||||
|
||||
it('submitted form should be displayed', function () {
|
||||
expect(page.heroSubmitted.isElementPresent(by.css('h2'))).toBe(true);
|
||||
expect(page.heroSubmitted.isElementPresent(by.css('p'))).toBe(true);
|
||||
});
|
||||
|
||||
it('submitted form should have new hero name', function () {
|
||||
@ -142,9 +135,9 @@ function tests() {
|
||||
});
|
||||
|
||||
it('clicking edit button should reveal form again', function () {
|
||||
const editBtn = page.heroSubmitted.element(by.css('button'));
|
||||
editBtn.click();
|
||||
expect(page.heroSubmitted.isElementPresent(by.css('h2')))
|
||||
const newFormBtn = page.heroSubmitted.element(by.css('button'));
|
||||
newFormBtn.click();
|
||||
expect(page.heroSubmitted.isElementPresent(by.css('p')))
|
||||
.toBe(false, 'submitted hidden again');
|
||||
expect(page.title.isDisplayed()).toBe(true, 'can see form title');
|
||||
});
|
||||
@ -159,9 +152,13 @@ function expectFormIsInvalid() {
|
||||
}
|
||||
|
||||
function bobTests() {
|
||||
const emsg = 'Someone named "Bob" cannot be a hero.';
|
||||
const emsg = 'Name cannot be Bob.';
|
||||
|
||||
it('should produce "no bob" error after setting name to "Bobby"', function () {
|
||||
// Re-populate select element
|
||||
page.powerSelect.click();
|
||||
page.powerOption.click();
|
||||
|
||||
page.nameInput.clear();
|
||||
page.nameInput.sendKeys('Bobby');
|
||||
expectFormIsInvalid();
|
||||
@ -174,8 +171,3 @@ function bobTests() {
|
||||
expectFormIsValid();
|
||||
});
|
||||
}
|
||||
|
||||
function makeNameTooLong() {
|
||||
// make the first name invalid
|
||||
page.nameInput.sendKeys('ThisHeroNameHasWayWayTooManyLetters');
|
||||
}
|
||||
|
@ -3,10 +3,8 @@ import { Component } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'my-app',
|
||||
template: `<hero-form-template1></hero-form-template1>
|
||||
template: `<hero-form-template></hero-form-template>
|
||||
<hr>
|
||||
<hero-form-template2></hero-form-template2>
|
||||
<hr>
|
||||
<hero-form-reactive3></hero-form-reactive3>`
|
||||
<hero-form-reactive></hero-form-reactive>`
|
||||
})
|
||||
export class AppComponent { }
|
||||
|
@ -1,18 +1,26 @@
|
||||
// #docregion
|
||||
import { NgModule } from '@angular/core';
|
||||
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
|
||||
import { BrowserModule } from '@angular/platform-browser';
|
||||
|
||||
import { AppComponent } from './app.component';
|
||||
import { HeroFormTemplateModule } from './template/hero-form-template.module';
|
||||
import { HeroFormReactiveModule } from './reactive/hero-form-reactive.module';
|
||||
import { HeroFormTemplateComponent } from './template/hero-form-template.component';
|
||||
import { HeroFormReactiveComponent } from './reactive/hero-form-reactive.component';
|
||||
import { ForbiddenValidatorDirective } from './shared/forbidden-name.directive';
|
||||
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
BrowserModule,
|
||||
HeroFormTemplateModule,
|
||||
HeroFormReactiveModule
|
||||
FormsModule,
|
||||
ReactiveFormsModule
|
||||
],
|
||||
declarations: [
|
||||
AppComponent,
|
||||
HeroFormTemplateComponent,
|
||||
HeroFormReactiveComponent,
|
||||
ForbiddenValidatorDirective
|
||||
],
|
||||
declarations: [ AppComponent ],
|
||||
bootstrap: [ AppComponent ]
|
||||
})
|
||||
export class AppModule { }
|
||||
|
@ -1,26 +1,38 @@
|
||||
<!-- #docregion -->
|
||||
<div class="container">
|
||||
<div [hidden]="submitted">
|
||||
<h1>Hero Form 3 (Reactive)</h1>
|
||||
<!-- #docregion form-tag-->
|
||||
<form [formGroup]="heroForm" *ngIf="active" (ngSubmit)="onSubmit()">
|
||||
<!-- #enddocregion form-tag-->
|
||||
<div class="form-group">
|
||||
<!-- #docregion name-with-error-msg -->
|
||||
<label for="name">Name</label>
|
||||
|
||||
<input type="text" id="name" class="form-control"
|
||||
<h1>Reactive Form</h1>
|
||||
|
||||
<form [formGroup]="heroForm" #formDir="ngForm">
|
||||
|
||||
<div [hidden]="formDir.submitted">
|
||||
|
||||
<div class="form-group">
|
||||
|
||||
<label for="name">Name</label>
|
||||
<!-- #docregion name-with-error-msg -->
|
||||
<input id="name" class="form-control"
|
||||
formControlName="name" required >
|
||||
|
||||
<div *ngIf="formErrors.name" class="alert alert-danger">
|
||||
{{ formErrors.name }}
|
||||
<div *ngIf="name.invalid && (name.dirty || name.touched)"
|
||||
class="alert alert-danger">
|
||||
|
||||
<div *ngIf="name.errors.required">
|
||||
Name is required.
|
||||
</div>
|
||||
<div *ngIf="name.errors.minlength">
|
||||
Name must be at least 4 characters long.
|
||||
</div>
|
||||
<div *ngIf="name.errors.forbiddenName">
|
||||
Name cannot be Bob.
|
||||
</div>
|
||||
</div>
|
||||
<!-- #enddocregion name-with-error-msg -->
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="alterEgo">Alter Ego</label>
|
||||
<input type="text" id="alterEgo" class="form-control"
|
||||
<input id="alterEgo" class="form-control"
|
||||
formControlName="alterEgo" >
|
||||
</div>
|
||||
|
||||
@ -31,17 +43,20 @@
|
||||
<option *ngFor="let p of powers" [value]="p">{{p}}</option>
|
||||
</select>
|
||||
|
||||
<div *ngIf="formErrors.power" class="alert alert-danger">
|
||||
{{ formErrors.power }}
|
||||
<div *ngIf="power.invalid && power.touched" class="alert alert-danger">
|
||||
<div *ngIf="power.errors.required">Power is required.</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn btn-default"
|
||||
[disabled]="!heroForm.valid">Submit</button>
|
||||
[disabled]="heroForm.invalid">Submit</button>
|
||||
<button type="button" class="btn btn-default"
|
||||
(click)="addHero()">New Hero</button>
|
||||
</form>
|
||||
</div>
|
||||
(click)="formDir.resetForm({})">Reset</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<hero-submitted [hero]="hero" [(submitted)]="submitted"></hero-submitted>
|
||||
<div class="submitted-message" *ngIf="formDir.submitted">
|
||||
<p>You've submitted your hero, {{ heroForm.value.name }}!</p>
|
||||
<button (click)="formDir.resetForm({})">Add new hero</button>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -2,115 +2,39 @@
|
||||
// #docplaster
|
||||
// #docregion
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { FormGroup, FormBuilder, Validators } from '@angular/forms';
|
||||
|
||||
import { Hero } from '../shared/hero';
|
||||
import { FormControl, FormGroup, Validators } from '@angular/forms';
|
||||
import { forbiddenNameValidator } from '../shared/forbidden-name.directive';
|
||||
|
||||
@Component({
|
||||
selector: 'hero-form-reactive3',
|
||||
selector: 'hero-form-reactive',
|
||||
templateUrl: './hero-form-reactive.component.html'
|
||||
})
|
||||
export class HeroFormReactiveComponent implements OnInit {
|
||||
|
||||
powers = ['Really Smart', 'Super Flexible', 'Weather Changer'];
|
||||
|
||||
hero = new Hero(18, 'Dr. WhatIsHisName', this.powers[0], 'Dr. What');
|
||||
hero = {name: 'Dr.', alterEgo: 'Dr. What', power: this.powers[0]};
|
||||
|
||||
submitted = false;
|
||||
|
||||
// #docregion on-submit
|
||||
onSubmit() {
|
||||
this.submitted = true;
|
||||
this.hero = this.heroForm.value;
|
||||
}
|
||||
// #enddocregion on-submit
|
||||
// #enddocregion
|
||||
|
||||
// Reset the form with a new hero AND restore 'pristine' class state
|
||||
// by toggling 'active' flag which causes the form
|
||||
// to be removed/re-added in a tick via NgIf
|
||||
// TODO: Workaround until NgForm has a reset method (#6822)
|
||||
active = true;
|
||||
// #docregion class
|
||||
// #docregion add-hero
|
||||
addHero() {
|
||||
this.hero = new Hero(42, '', '');
|
||||
this.buildForm();
|
||||
// #enddocregion add-hero
|
||||
// #enddocregion class
|
||||
|
||||
this.active = false;
|
||||
setTimeout(() => this.active = true, 0);
|
||||
// #docregion
|
||||
// #docregion add-hero
|
||||
}
|
||||
// #enddocregion add-hero
|
||||
|
||||
// #docregion form-builder
|
||||
heroForm: FormGroup;
|
||||
constructor(private fb: FormBuilder) { }
|
||||
|
||||
// #docregion form-group
|
||||
ngOnInit(): void {
|
||||
this.buildForm();
|
||||
}
|
||||
|
||||
buildForm(): void {
|
||||
this.heroForm = this.fb.group({
|
||||
// #docregion name-validators
|
||||
'name': [this.hero.name, [
|
||||
Validators.required,
|
||||
Validators.minLength(4),
|
||||
Validators.maxLength(24),
|
||||
forbiddenNameValidator(/bob/i)
|
||||
]
|
||||
],
|
||||
// #enddocregion name-validators
|
||||
'alterEgo': [this.hero.alterEgo],
|
||||
'power': [this.hero.power, Validators.required]
|
||||
// #docregion custom-validator
|
||||
this.heroForm = new FormGroup({
|
||||
'name': new FormControl(this.hero.name, [
|
||||
Validators.required,
|
||||
Validators.minLength(4),
|
||||
forbiddenNameValidator(/bob/i) // <-- Here's how you pass in the custom validator.
|
||||
]),
|
||||
'alterEgo': new FormControl(this.hero.alterEgo),
|
||||
'power': new FormControl(this.hero.power, Validators.required)
|
||||
});
|
||||
|
||||
this.heroForm.valueChanges
|
||||
.subscribe(data => this.onValueChanged(data));
|
||||
|
||||
this.onValueChanged(); // (re)set validation messages now
|
||||
// #enddocregion custom-validator
|
||||
}
|
||||
|
||||
// #enddocregion form-builder
|
||||
get name() { return this.heroForm.get('name'); }
|
||||
|
||||
onValueChanged(data?: any) {
|
||||
if (!this.heroForm) { return; }
|
||||
const form = this.heroForm;
|
||||
|
||||
for (const field in this.formErrors) {
|
||||
// clear previous error message (if any)
|
||||
this.formErrors[field] = '';
|
||||
const control = form.get(field);
|
||||
|
||||
if (control && control.dirty && !control.valid) {
|
||||
const messages = this.validationMessages[field];
|
||||
for (const key in control.errors) {
|
||||
this.formErrors[field] += messages[key] + ' ';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
formErrors = {
|
||||
'name': '',
|
||||
'power': ''
|
||||
};
|
||||
|
||||
validationMessages = {
|
||||
'name': {
|
||||
'required': 'Name is required.',
|
||||
'minlength': 'Name must be at least 4 characters long.',
|
||||
'maxlength': 'Name cannot be more than 24 characters long.',
|
||||
'forbiddenName': 'Someone named "Bob" cannot be a hero.'
|
||||
},
|
||||
'power': {
|
||||
'required': 'Power is required.'
|
||||
}
|
||||
};
|
||||
get power() { return this.heroForm.get('power'); }
|
||||
// #enddocregion form-group
|
||||
}
|
||||
// #enddocregion
|
||||
|
@ -1,13 +0,0 @@
|
||||
// #docregion
|
||||
import { NgModule } from '@angular/core';
|
||||
import { ReactiveFormsModule } from '@angular/forms';
|
||||
|
||||
import { SharedModule } from '../shared/shared.module';
|
||||
import { HeroFormReactiveComponent } from './hero-form-reactive.component';
|
||||
|
||||
@NgModule({
|
||||
imports: [ SharedModule, ReactiveFormsModule ],
|
||||
declarations: [ HeroFormReactiveComponent ],
|
||||
exports: [ HeroFormReactiveComponent ]
|
||||
})
|
||||
export class HeroFormReactiveModule { }
|
@ -6,9 +6,8 @@ import { AbstractControl, NG_VALIDATORS, Validator, ValidatorFn, Validators } fr
|
||||
/** A hero's name can't match the given regular expression */
|
||||
export function forbiddenNameValidator(nameRe: RegExp): ValidatorFn {
|
||||
return (control: AbstractControl): {[key: string]: any} => {
|
||||
const name = control.value;
|
||||
const no = nameRe.test(name);
|
||||
return no ? {'forbiddenName': {name}} : null;
|
||||
const forbidden = nameRe.test(control.value);
|
||||
return forbidden ? {'forbiddenName': {value: control.value}} : null;
|
||||
};
|
||||
}
|
||||
// #enddocregion custom-validator
|
||||
@ -20,23 +19,12 @@ export function forbiddenNameValidator(nameRe: RegExp): ValidatorFn {
|
||||
providers: [{provide: NG_VALIDATORS, useExisting: ForbiddenValidatorDirective, multi: true}]
|
||||
// #enddocregion directive-providers
|
||||
})
|
||||
export class ForbiddenValidatorDirective implements Validator, OnChanges {
|
||||
export class ForbiddenValidatorDirective implements Validator {
|
||||
@Input() forbiddenName: string;
|
||||
private valFn = Validators.nullValidator;
|
||||
|
||||
ngOnChanges(changes: SimpleChanges): void {
|
||||
const change = changes['forbiddenName'];
|
||||
if (change) {
|
||||
const val: string | RegExp = change.currentValue;
|
||||
const re = val instanceof RegExp ? val : new RegExp(val, 'i');
|
||||
this.valFn = forbiddenNameValidator(re);
|
||||
} else {
|
||||
this.valFn = Validators.nullValidator;
|
||||
}
|
||||
}
|
||||
|
||||
validate(control: AbstractControl): {[key: string]: any} {
|
||||
return this.valFn(control);
|
||||
return this.forbiddenName ? forbiddenNameValidator(new RegExp(this.forbiddenName, 'i'))(control)
|
||||
: null;
|
||||
}
|
||||
}
|
||||
// #enddocregion directive
|
||||
|
@ -1,9 +0,0 @@
|
||||
// #docregion
|
||||
export class Hero {
|
||||
constructor(
|
||||
public id: number,
|
||||
public name: string,
|
||||
public power: string,
|
||||
public alterEgo?: string
|
||||
) { }
|
||||
}
|
@ -1,14 +0,0 @@
|
||||
// #docregion
|
||||
import { NgModule } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
|
||||
import { ForbiddenValidatorDirective } from './forbidden-name.directive';
|
||||
import { SubmittedComponent } from './submitted.component';
|
||||
|
||||
@NgModule({
|
||||
imports: [ CommonModule],
|
||||
declarations: [ ForbiddenValidatorDirective, SubmittedComponent ],
|
||||
exports: [ ForbiddenValidatorDirective, SubmittedComponent,
|
||||
CommonModule ]
|
||||
})
|
||||
export class SharedModule { }
|
@ -1,32 +0,0 @@
|
||||
// #docregion
|
||||
import { Component, EventEmitter, Input, Output } from '@angular/core';
|
||||
|
||||
import { Hero } from './hero';
|
||||
|
||||
@Component({
|
||||
selector: 'hero-submitted',
|
||||
template: `
|
||||
<div *ngIf="submitted">
|
||||
<h2>You submitted the following:</h2>
|
||||
<div class="row">
|
||||
<div class="col-xs-3">Name</div>
|
||||
<div class="col-xs-9 pull-left">{{ hero.name }}</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-xs-3">Alter Ego</div>
|
||||
<div class="col-xs-9 pull-left">{{ hero.alterEgo }}</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-xs-3">Power</div>
|
||||
<div class="col-xs-9 pull-left">{{ hero.power }}</div>
|
||||
</div>
|
||||
<br>
|
||||
<button class="btn btn-default" (click)="onClick()">Edit</button>
|
||||
</div>`
|
||||
})
|
||||
export class SubmittedComponent {
|
||||
@Input() hero: Hero;
|
||||
@Input() submitted = false;
|
||||
@Output() submittedChange = new EventEmitter<boolean>();
|
||||
onClick() { this.submittedChange.emit(false); }
|
||||
}
|
@ -0,0 +1,66 @@
|
||||
<!-- #docregion -->
|
||||
<div class="container">
|
||||
|
||||
<h1>Template-Driven Form</h1>
|
||||
<!-- #docregion form-tag-->
|
||||
<form #heroForm="ngForm">
|
||||
<!-- #enddocregion form-tag-->
|
||||
<div [hidden]="heroForm.submitted">
|
||||
|
||||
<div class="form-group">
|
||||
<label for="name">Name</label>
|
||||
<!-- #docregion name-with-error-msg -->
|
||||
<!-- #docregion name-input -->
|
||||
<input id="name" name="name" class="form-control"
|
||||
required minlength="4" forbiddenName="bob"
|
||||
[(ngModel)]="hero.name" #name="ngModel" >
|
||||
<!-- #enddocregion name-input -->
|
||||
|
||||
<div *ngIf="name.invalid && (name.dirty || name.touched)"
|
||||
class="alert alert-danger">
|
||||
|
||||
<div *ngIf="name.errors.required">
|
||||
Name is required.
|
||||
</div>
|
||||
<div *ngIf="name.errors.minlength">
|
||||
Name must be at least 4 characters long.
|
||||
</div>
|
||||
<div *ngIf="name.errors.forbiddenName">
|
||||
Name cannot be Bob.
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<!-- #enddocregion name-with-error-msg -->
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="alterEgo">Alter Ego</label>
|
||||
<input id="alterEgo" class="form-control"
|
||||
name="alterEgo" [(ngModel)]="hero.alterEgo" >
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="power">Hero Power</label>
|
||||
<select id="power" name="power" class="form-control"
|
||||
required [(ngModel)]="hero.power" #power="ngModel" >
|
||||
<option *ngFor="let p of powers" [value]="p">{{p}}</option>
|
||||
</select>
|
||||
|
||||
<div *ngIf="power.errors && power.touched" class="alert alert-danger">
|
||||
<div *ngIf="power.errors.required">Power is required.</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn btn-default"
|
||||
[disabled]="heroForm.invalid">Submit</button>
|
||||
<button type="button" class="btn btn-default"
|
||||
(click)="heroForm.resetForm({})">Reset</button>
|
||||
</div>
|
||||
|
||||
<div class="submitted-message" *ngIf="heroForm.submitted">
|
||||
<p>You've submitted your hero, {{ heroForm.value.name }}!</p>
|
||||
<button (click)="heroForm.resetForm({})">Add new hero</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
</div>
|
@ -0,0 +1,16 @@
|
||||
/* tslint:disable: member-ordering */
|
||||
// #docplaster
|
||||
// #docregion
|
||||
import { Component } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'hero-form-template',
|
||||
templateUrl: './hero-form-template.component.html'
|
||||
})
|
||||
export class HeroFormTemplateComponent {
|
||||
|
||||
powers = ['Really Smart', 'Super Flexible', 'Weather Changer'];
|
||||
|
||||
hero = {name: 'Dr.', alterEgo: 'Dr. What', power: this.powers[0]};
|
||||
|
||||
}
|
@ -1,14 +0,0 @@
|
||||
// #docregion
|
||||
import { NgModule } from '@angular/core';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
|
||||
import { SharedModule } from '../shared/shared.module';
|
||||
import { HeroFormTemplate1Component } from './hero-form-template1.component';
|
||||
import { HeroFormTemplate2Component } from './hero-form-template2.component';
|
||||
|
||||
@NgModule({
|
||||
imports: [ SharedModule, FormsModule ],
|
||||
declarations: [ HeroFormTemplate1Component, HeroFormTemplate2Component ],
|
||||
exports: [ HeroFormTemplate1Component, HeroFormTemplate2Component ]
|
||||
})
|
||||
export class HeroFormTemplateModule { }
|
@ -1,61 +0,0 @@
|
||||
<!-- #docregion -->
|
||||
<div class="container">
|
||||
<div [hidden]="submitted">
|
||||
<h1>Hero Form 1 (Template)</h1>
|
||||
<!-- #docregion form-tag-->
|
||||
<form #heroForm="ngForm" *ngIf="active" (ngSubmit)="onSubmit()">
|
||||
<!-- #enddocregion form-tag-->
|
||||
<div class="form-group">
|
||||
<!-- #docregion name-with-error-msg -->
|
||||
<label for="name">Name</label>
|
||||
|
||||
<input type="text" id="name" class="form-control"
|
||||
required minlength="4" maxlength="24"
|
||||
name="name" [(ngModel)]="hero.name"
|
||||
#name="ngModel" >
|
||||
|
||||
<div *ngIf="name.errors && (name.dirty || name.touched)"
|
||||
class="alert alert-danger">
|
||||
<div [hidden]="!name.errors.required">
|
||||
Name is required
|
||||
</div>
|
||||
<div [hidden]="!name.errors.minlength">
|
||||
Name must be at least 4 characters long.
|
||||
</div>
|
||||
<div [hidden]="!name.errors.maxlength">
|
||||
Name cannot be more than 24 characters long.
|
||||
</div>
|
||||
</div>
|
||||
<!-- #enddocregion name-with-error-msg -->
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="alterEgo">Alter Ego</label>
|
||||
<input type="text" id="alterEgo" class="form-control"
|
||||
name="alterEgo"
|
||||
[(ngModel)]="hero.alterEgo" >
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="power">Hero Power</label>
|
||||
<select id="power" class="form-control"
|
||||
name="power"
|
||||
[(ngModel)]="hero.power" required
|
||||
#power="ngModel" >
|
||||
<option *ngFor="let p of powers" [value]="p">{{p}}</option>
|
||||
</select>
|
||||
|
||||
<div *ngIf="power.errors && power.touched" class="alert alert-danger">
|
||||
<div [hidden]="!power.errors.required">Power is required</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn btn-default"
|
||||
[disabled]="!heroForm.form.valid">Submit</button>
|
||||
<button type="button" class="btn btn-default"
|
||||
(click)="addHero()">New Hero</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<hero-submitted [hero]="hero" [(submitted)]="submitted"></hero-submitted>
|
||||
</div>
|
@ -1,47 +0,0 @@
|
||||
/* tslint:disable: member-ordering */
|
||||
// #docplaster
|
||||
// #docregion
|
||||
import { Component } from '@angular/core';
|
||||
|
||||
|
||||
import { Hero } from '../shared/hero';
|
||||
|
||||
@Component({
|
||||
selector: 'hero-form-template1',
|
||||
templateUrl: './hero-form-template1.component.html'
|
||||
})
|
||||
// #docregion class
|
||||
export class HeroFormTemplate1Component {
|
||||
|
||||
powers = ['Really Smart', 'Super Flexible', 'Weather Changer'];
|
||||
|
||||
hero = new Hero(18, 'Dr. WhatIsHisWayTooLongName', this.powers[0], 'Dr. What');
|
||||
|
||||
submitted = false;
|
||||
|
||||
onSubmit() {
|
||||
this.submitted = true;
|
||||
}
|
||||
// #enddocregion class
|
||||
// #enddocregion
|
||||
// Reset the form with a new hero AND restore 'pristine' class state
|
||||
// by toggling 'active' flag which causes the form
|
||||
// to be removed/re-added in a tick via NgIf
|
||||
// TODO: Workaround until NgForm has a reset method (#6822)
|
||||
active = true;
|
||||
// #docregion
|
||||
// #docregion class
|
||||
|
||||
addHero() {
|
||||
this.hero = new Hero(42, '', '');
|
||||
// #enddocregion class
|
||||
// #enddocregion
|
||||
|
||||
this.active = false;
|
||||
setTimeout(() => this.active = true, 0);
|
||||
// #docregion
|
||||
// #docregion class
|
||||
}
|
||||
}
|
||||
// #enddocregion class
|
||||
// #enddocregion
|
@ -1,52 +0,0 @@
|
||||
<!-- #docregion -->
|
||||
<div class="container">
|
||||
<div [hidden]="submitted">
|
||||
<h1>Hero Form 2 (Template & Messages)</h1>
|
||||
<!-- #docregion form-tag-->
|
||||
<form #heroForm="ngForm" *ngIf="active" (ngSubmit)="onSubmit()">
|
||||
<!-- #enddocregion form-tag-->
|
||||
<div class="form-group">
|
||||
<!-- #docregion name-with-error-msg -->
|
||||
<label for="name">Name</label>
|
||||
|
||||
<!-- #docregion name-input -->
|
||||
<input type="text" id="name" class="form-control"
|
||||
required minlength="4" maxlength="24" forbiddenName="bob"
|
||||
name="name" [(ngModel)]="hero.name" >
|
||||
<!-- #enddocregion name-input -->
|
||||
|
||||
<div *ngIf="formErrors.name" class="alert alert-danger">
|
||||
{{ formErrors.name }}
|
||||
</div>
|
||||
<!-- #enddocregion name-with-error-msg -->
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="alterEgo">Alter Ego</label>
|
||||
<input type="text" id="alterEgo" class="form-control"
|
||||
name="alterEgo"
|
||||
[(ngModel)]="hero.alterEgo" >
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="power">Hero Power</label>
|
||||
<select id="power" class="form-control"
|
||||
name="power"
|
||||
[(ngModel)]="hero.power" required >
|
||||
<option *ngFor="let p of powers" [value]="p">{{p}}</option>
|
||||
</select>
|
||||
|
||||
<div *ngIf="formErrors.power" class="alert alert-danger">
|
||||
{{ formErrors.power }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn btn-default"
|
||||
[disabled]="!heroForm.form.valid">Submit</button>
|
||||
<button type="button" class="btn btn-default"
|
||||
(click)="addHero()">New Hero</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<hero-submitted [hero]="hero" [(submitted)]="submitted"></hero-submitted>
|
||||
</div>
|
@ -1,99 +0,0 @@
|
||||
/* tslint:disable: member-ordering forin */
|
||||
// #docplaster
|
||||
// #docregion
|
||||
import { Component, AfterViewChecked, ViewChild } from '@angular/core';
|
||||
import { NgForm } from '@angular/forms';
|
||||
|
||||
import { Hero } from '../shared/hero';
|
||||
|
||||
@Component({
|
||||
selector: 'hero-form-template2',
|
||||
templateUrl: './hero-form-template2.component.html'
|
||||
})
|
||||
export class HeroFormTemplate2Component implements AfterViewChecked {
|
||||
|
||||
powers = ['Really Smart', 'Super Flexible', 'Weather Changer'];
|
||||
|
||||
hero = new Hero(18, 'Dr. WhatIsHisWayTooLongName', this.powers[0], 'Dr. What');
|
||||
|
||||
submitted = false;
|
||||
|
||||
onSubmit() {
|
||||
this.submitted = true;
|
||||
}
|
||||
// #enddocregion
|
||||
|
||||
// Reset the form with a new hero AND restore 'pristine' class state
|
||||
// by toggling 'active' flag which causes the form
|
||||
// to be removed/re-added in a tick via NgIf
|
||||
// TODO: Workaround until NgForm has a reset method (#6822)
|
||||
active = true;
|
||||
// #docregion
|
||||
|
||||
addHero() {
|
||||
this.hero = new Hero(42, '', '');
|
||||
// #enddocregion
|
||||
|
||||
this.active = false;
|
||||
setTimeout(() => this.active = true, 0);
|
||||
// #docregion
|
||||
}
|
||||
|
||||
// #docregion view-child
|
||||
heroForm: NgForm;
|
||||
@ViewChild('heroForm') currentForm: NgForm;
|
||||
|
||||
ngAfterViewChecked() {
|
||||
this.formChanged();
|
||||
}
|
||||
|
||||
formChanged() {
|
||||
if (this.currentForm === this.heroForm) { return; }
|
||||
this.heroForm = this.currentForm;
|
||||
if (this.heroForm) {
|
||||
this.heroForm.valueChanges
|
||||
.subscribe(data => this.onValueChanged(data));
|
||||
}
|
||||
}
|
||||
// #enddocregion view-child
|
||||
|
||||
// #docregion handler
|
||||
onValueChanged(data?: any) {
|
||||
if (!this.heroForm) { return; }
|
||||
const form = this.heroForm.form;
|
||||
|
||||
for (const field in this.formErrors) {
|
||||
// clear previous error message (if any)
|
||||
this.formErrors[field] = '';
|
||||
const control = form.get(field);
|
||||
|
||||
if (control && control.dirty && !control.valid) {
|
||||
const messages = this.validationMessages[field];
|
||||
for (const key in control.errors) {
|
||||
this.formErrors[field] += messages[key] + ' ';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
formErrors = {
|
||||
'name': '',
|
||||
'power': ''
|
||||
};
|
||||
// #enddocregion handler
|
||||
|
||||
// #docregion messages
|
||||
validationMessages = {
|
||||
'name': {
|
||||
'required': 'Name is required.',
|
||||
'minlength': 'Name must be at least 4 characters long.',
|
||||
'maxlength': 'Name cannot be more than 24 characters long.',
|
||||
'forbiddenName': 'Someone named "Bob" cannot be a hero.'
|
||||
},
|
||||
'power': {
|
||||
'required': 'Power is required.'
|
||||
}
|
||||
};
|
||||
// #enddocregion messages
|
||||
}
|
||||
// #enddocregion
|
@ -1,3 +1,4 @@
|
||||
|
||||
.ng-valid[required], .ng-valid.required {
|
||||
border-left: 5px solid #42A948; /* green */
|
||||
}
|
||||
|
@ -9,7 +9,7 @@ export class AppComponent {
|
||||
wolves = 0;
|
||||
gender = 'f';
|
||||
fly = true;
|
||||
logo = 'https://angular.io/resources/images/logos/angular/angular.png';
|
||||
logo = 'https://angular.io/assets/images/logos/angular/angular.png';
|
||||
count = 3;
|
||||
heroes: string[] = ['Magneta', 'Celeritas', 'Dynama'];
|
||||
inc(i: number) {
|
||||
|
@ -12,13 +12,13 @@ describe('Router', () => {
|
||||
beforeAll(() => browser.get(''));
|
||||
|
||||
function getPageStruct() {
|
||||
const hrefEles = element.all(by.css('my-app a'));
|
||||
const hrefEles = element.all(by.css('my-app > nav a'));
|
||||
const crisisDetail = element.all(by.css('my-app > ng-component > ng-component > ng-component > div')).first();
|
||||
const heroDetail = element(by.css('my-app > ng-component > div'));
|
||||
|
||||
return {
|
||||
hrefs: hrefEles,
|
||||
activeHref: element(by.css('my-app a.active')),
|
||||
activeHref: element(by.css('my-app > nav a.active')),
|
||||
|
||||
crisisHref: hrefEles.get(0),
|
||||
crisisList: element.all(by.css('my-app > ng-component > ng-component li')),
|
||||
|
@ -15,6 +15,10 @@
|
||||
height: 1.6em;
|
||||
border-radius: 4px;
|
||||
}
|
||||
.items li a {
|
||||
display: block;
|
||||
text-decoration: none;
|
||||
}
|
||||
.items li:hover {
|
||||
color: #607D8B;
|
||||
background-color: #DDD;
|
||||
|
@ -28,7 +28,7 @@ const appRoutes: Routes = [
|
||||
data: { preload: true }
|
||||
},
|
||||
// #enddocregion preload-v2
|
||||
{ path: '', redirectTo: '/heroes', pathMatch: 'full' },
|
||||
{ path: '', redirectTo: '/superheroes', pathMatch: 'full' },
|
||||
{ path: '**', component: PageNotFoundComponent }
|
||||
];
|
||||
|
||||
|
23
aio/content/examples/router/src/app/app.component.6.ts
Normal file
23
aio/content/examples/router/src/app/app.component.6.ts
Normal file
@ -0,0 +1,23 @@
|
||||
// #docplaster
|
||||
// #docregion
|
||||
import { Component } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'my-app',
|
||||
// #docregion template
|
||||
template: `
|
||||
<h1 class="title">Angular Router</h1>
|
||||
<nav>
|
||||
<a routerLink="/crisis-center" routerLinkActive="active">Crisis Center</a>
|
||||
<a routerLink="/heroes" routerLinkActive="active">Heroes</a>
|
||||
<a routerLink="/admin" routerLinkActive="active">Admin</a>
|
||||
<a routerLink="/login" routerLinkActive="active">Login</a>
|
||||
<a [routerLink]="[{ outlets: { popup: ['compose'] } }]">Contact</a>
|
||||
</nav>
|
||||
<router-outlet></router-outlet>
|
||||
<router-outlet name="popup"></router-outlet>
|
||||
`
|
||||
// #enddocregion template
|
||||
})
|
||||
export class AppComponent {
|
||||
}
|
@ -9,7 +9,7 @@ import { Component } from '@angular/core';
|
||||
<h1 class="title">Angular Router</h1>
|
||||
<nav>
|
||||
<a routerLink="/crisis-center" routerLinkActive="active">Crisis Center</a>
|
||||
<a routerLink="/heroes" routerLinkActive="active">Heroes</a>
|
||||
<a routerLink="/superheroes" routerLinkActive="active">Heroes</a>
|
||||
<a routerLink="/admin" routerLinkActive="active">Admin</a>
|
||||
<a routerLink="/login" routerLinkActive="active">Login</a>
|
||||
<a [routerLink]="[{ outlets: { popup: ['compose'] } }]">Contact</a>
|
||||
|
@ -1,5 +1,6 @@
|
||||
// #docregion
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
import { CanDeactivate,
|
||||
ActivatedRouteSnapshot,
|
||||
RouterStateSnapshot } from '@angular/router';
|
||||
@ -13,7 +14,7 @@ export class CanDeactivateGuard implements CanDeactivate<CrisisDetailComponent>
|
||||
component: CrisisDetailComponent,
|
||||
route: ActivatedRouteSnapshot,
|
||||
state: RouterStateSnapshot
|
||||
): Promise<boolean> | boolean {
|
||||
): Observable<boolean> | boolean {
|
||||
// Get the Crisis Center ID
|
||||
console.log(route.paramMap.get('id'));
|
||||
|
||||
@ -25,7 +26,7 @@ export class CanDeactivateGuard implements CanDeactivate<CrisisDetailComponent>
|
||||
return true;
|
||||
}
|
||||
// Otherwise ask the user with the dialog service and return its
|
||||
// promise which resolves to true or false when the user decides
|
||||
// observable which resolves to true or false when the user decides
|
||||
return component.dialogService.confirm('Discard changes?');
|
||||
}
|
||||
}
|
||||
|
@ -1,18 +1,21 @@
|
||||
// #docregion
|
||||
import 'rxjs/add/operator/map';
|
||||
import 'rxjs/add/operator/take';
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
import { Router, Resolve, RouterStateSnapshot,
|
||||
ActivatedRouteSnapshot } from '@angular/router';
|
||||
|
||||
import { Crisis, CrisisService } from './crisis.service';
|
||||
import { Crisis, CrisisService } from './crisis.service';
|
||||
|
||||
@Injectable()
|
||||
export class CrisisDetailResolver implements Resolve<Crisis> {
|
||||
constructor(private cs: CrisisService, private router: Router) {}
|
||||
|
||||
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Promise<Crisis> {
|
||||
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<Crisis> {
|
||||
let id = route.paramMap.get('id');
|
||||
|
||||
return this.cs.getCrisis(id).then(crisis => {
|
||||
return this.cs.getCrisis(id).take(1).map(crisis => {
|
||||
if (crisis) {
|
||||
return crisis;
|
||||
} else { // id not found
|
||||
|
@ -1,8 +1,9 @@
|
||||
// #docplaster
|
||||
// #docregion
|
||||
import 'rxjs/add/operator/switchMap';
|
||||
import { Component, OnInit, HostBinding } from '@angular/core';
|
||||
import { Component, OnInit, HostBinding } from '@angular/core';
|
||||
import { ActivatedRoute, Router, ParamMap } from '@angular/router';
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
|
||||
import { slideInDownAnimation } from '../animations';
|
||||
import { Crisis, CrisisService } from './crisis.service';
|
||||
@ -45,7 +46,8 @@ export class CrisisDetailComponent implements OnInit {
|
||||
// #docregion ngOnInit
|
||||
ngOnInit() {
|
||||
this.route.paramMap
|
||||
.switchMap((params: ParamMap) => this.service.getCrisis(params.get('id')))
|
||||
.switchMap((params: ParamMap) =>
|
||||
this.service.getCrisis(params.get('id')))
|
||||
.subscribe((crisis: Crisis) => {
|
||||
if (crisis) {
|
||||
this.editName = crisis.name;
|
||||
@ -66,13 +68,13 @@ export class CrisisDetailComponent implements OnInit {
|
||||
this.gotoCrises();
|
||||
}
|
||||
|
||||
canDeactivate(): Promise<boolean> | boolean {
|
||||
canDeactivate(): Observable<boolean> | boolean {
|
||||
// Allow synchronous navigation (`true`) if no crisis or the crisis is unchanged
|
||||
if (!this.crisis || this.crisis.name === this.editName) {
|
||||
return true;
|
||||
}
|
||||
// Otherwise ask the user with the dialog service and return its
|
||||
// promise which resolves to true or false when the user decides
|
||||
// observable which resolves to true or false when the user decides
|
||||
return this.dialogService.confirm('Discard changes?');
|
||||
}
|
||||
|
||||
|
@ -2,6 +2,7 @@
|
||||
// #docregion
|
||||
import { Component, OnInit, HostBinding } from '@angular/core';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
|
||||
import { slideInDownAnimation } from '../animations';
|
||||
import { Crisis } from './crisis.service';
|
||||
@ -62,13 +63,13 @@ export class CrisisDetailComponent implements OnInit {
|
||||
// #enddocregion cancel-save
|
||||
|
||||
// #docregion canDeactivate
|
||||
canDeactivate(): Promise<boolean> | boolean {
|
||||
canDeactivate(): Observable<boolean> | boolean {
|
||||
// Allow synchronous navigation (`true`) if no crisis or the crisis is unchanged
|
||||
if (!this.crisis || this.crisis.name === this.editName) {
|
||||
return true;
|
||||
}
|
||||
// Otherwise ask the user with the dialog service and return its
|
||||
// promise which resolves to true or false when the user decides
|
||||
// observable which resolves to true or false when the user decides
|
||||
return this.dialogService.confirm('Discard changes?');
|
||||
}
|
||||
// #enddocregion canDeactivate
|
||||
|
@ -1,7 +1,7 @@
|
||||
import 'rxjs/add/operator/do';
|
||||
|
||||
import 'rxjs/add/operator/switchMap';
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { ActivatedRoute, Router, ParamMap } from '@angular/router';
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { ActivatedRoute, ParamMap } from '@angular/router';
|
||||
|
||||
import { Crisis, CrisisService } from './crisis.service';
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
@ -10,35 +10,34 @@ import { Observable } from 'rxjs/Observable';
|
||||
// #docregion relative-navigation-router-link
|
||||
template: `
|
||||
<ul class="items">
|
||||
<li *ngFor="let crisis of crises | async">
|
||||
<a [routerLink]="[crisis.id]"
|
||||
[class.selected]="isSelected(crisis)">
|
||||
<span class="badge">{{ crisis.id }}</span>
|
||||
{{ crisis.name }}
|
||||
<li *ngFor="let crisis of crises$ | async"
|
||||
[class.selected]="crisis.id === selectedId">
|
||||
<a [routerLink]="[crisis.id]">
|
||||
<span class="badge">{{ crisis.id }}</span>{{ crisis.name }}
|
||||
</a>
|
||||
</li>
|
||||
</ul>`
|
||||
</ul>
|
||||
|
||||
<router-outlet></router-outlet>
|
||||
`
|
||||
// #enddocregion relative-navigation-router-link
|
||||
})
|
||||
export class CrisisListComponent implements OnInit {
|
||||
crises: Observable<Crisis[]>;
|
||||
crises$: Observable<Crisis[]>;
|
||||
selectedId: number;
|
||||
|
||||
|
||||
constructor(
|
||||
private service: CrisisService,
|
||||
private route: ActivatedRoute,
|
||||
private router: Router
|
||||
private route: ActivatedRoute
|
||||
) {}
|
||||
|
||||
|
||||
ngOnInit() {
|
||||
this.crises = this.route.paramMap
|
||||
this.crises$ = this.route.paramMap
|
||||
.switchMap((params: ParamMap) => {
|
||||
this.selectedId = +params.get('id');
|
||||
return this.service.getCrises();
|
||||
});
|
||||
}
|
||||
|
||||
isSelected(crisis: Crisis) {
|
||||
return crisis.id === this.selectedId;
|
||||
}
|
||||
}
|
||||
|
@ -1,20 +1,19 @@
|
||||
// #docregion
|
||||
import 'rxjs/add/operator/switchMap';
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { ActivatedRoute, Router, ParamMap } from '@angular/router';
|
||||
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { ActivatedRoute, ParamMap } from '@angular/router';
|
||||
|
||||
import { Crisis, CrisisService } from './crisis.service';
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
|
||||
@Component({
|
||||
template: `
|
||||
<ul class="items">
|
||||
<li *ngFor="let crisis of crises | async"
|
||||
(click)="onSelect(crisis)"
|
||||
[class.selected]="isSelected(crisis)">
|
||||
<span class="badge">{{ crisis.id }}</span>
|
||||
{{ crisis.name }}
|
||||
<li *ngFor="let crisis of crises$ | async"
|
||||
[class.selected]="crisis.id === selectedId">
|
||||
<a [routerLink]="[crisis.id]">
|
||||
<span class="badge">{{ crisis.id }}</span>{{ crisis.name }}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
@ -22,35 +21,21 @@ import { Crisis, CrisisService } from './crisis.service';
|
||||
`
|
||||
})
|
||||
export class CrisisListComponent implements OnInit {
|
||||
crises: Observable<Crisis[]>;
|
||||
crises$: Observable<Crisis[]>;
|
||||
selectedId: number;
|
||||
|
||||
// #docregion ctor
|
||||
constructor(
|
||||
private service: CrisisService,
|
||||
private route: ActivatedRoute,
|
||||
private router: Router
|
||||
private route: ActivatedRoute
|
||||
) {}
|
||||
// #enddocregion ctor
|
||||
|
||||
isSelected(crisis: Crisis) {
|
||||
return crisis.id === this.selectedId;
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.crises = this.route.paramMap
|
||||
this.crises$ = this.route.paramMap
|
||||
.switchMap((params: ParamMap) => {
|
||||
this.selectedId = +params.get('id');
|
||||
return this.service.getCrises();
|
||||
});
|
||||
}
|
||||
|
||||
// #docregion onSelect
|
||||
onSelect(crisis: Crisis) {
|
||||
this.selectedId = crisis.id;
|
||||
|
||||
// Navigate with relative link
|
||||
this.router.navigate([crisis.id], { relativeTo: this.route });
|
||||
}
|
||||
// #enddocregion onSelect
|
||||
}
|
||||
|
@ -1,5 +1,9 @@
|
||||
// #docplaster
|
||||
// #docregion , mock-crises
|
||||
import 'rxjs/add/observable/of';
|
||||
import 'rxjs/add/operator/map';
|
||||
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
|
||||
|
||||
export class Crisis {
|
||||
constructor(public id: number, public name: string) { }
|
||||
}
|
||||
@ -12,20 +16,18 @@ const CRISES = [
|
||||
];
|
||||
// #enddocregion mock-crises
|
||||
|
||||
let crisesPromise = Promise.resolve(CRISES);
|
||||
|
||||
import { Injectable } from '@angular/core';
|
||||
|
||||
@Injectable()
|
||||
export class CrisisService {
|
||||
|
||||
static nextCrisisId = 100;
|
||||
private crises$: BehaviorSubject<Crisis[]> = new BehaviorSubject<Crisis[]>(CRISES);
|
||||
|
||||
getCrises() { return crisesPromise; }
|
||||
getCrises() { return this.crises$; }
|
||||
|
||||
getCrisis(id: number | string) {
|
||||
return crisesPromise
|
||||
.then(crises => crises.find(crisis => crisis.id === +id));
|
||||
return this.getCrises()
|
||||
.map(crises => crises.find(crisis => crisis.id === +id));
|
||||
}
|
||||
|
||||
// #enddocregion
|
||||
@ -33,7 +35,8 @@ export class CrisisService {
|
||||
name = name.trim();
|
||||
if (name) {
|
||||
let crisis = new Crisis(CrisisService.nextCrisisId++, name);
|
||||
crisesPromise.then(crises => crises.push(crisis));
|
||||
CRISES.push(crisis);
|
||||
this.crises$.next(CRISES);
|
||||
}
|
||||
}
|
||||
// #docregion
|
||||
|
@ -1,5 +1,8 @@
|
||||
// #docregion
|
||||
import 'rxjs/add/observable/of';
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
|
||||
/**
|
||||
* Async modal dialog service
|
||||
* DialogService makes this app easier to test by faking this service.
|
||||
@ -9,11 +12,11 @@ import { Injectable } from '@angular/core';
|
||||
export class DialogService {
|
||||
/**
|
||||
* Ask user to confirm an action. `message` explains the action and choices.
|
||||
* Returns promise resolving to `true`=confirm or `false`=cancel
|
||||
* Returns observable resolving to `true`=confirm or `false`=cancel
|
||||
*/
|
||||
confirm(message?: string) {
|
||||
return new Promise<boolean>(resolve => {
|
||||
return resolve(window.confirm(message || 'Is it OK?'));
|
||||
});
|
||||
confirm(message?: string): Observable<boolean> {
|
||||
const confirmation = window.confirm(message || 'Is it OK?');
|
||||
|
||||
return Observable.of(confirmation);
|
||||
};
|
||||
}
|
||||
|
@ -4,6 +4,7 @@
|
||||
import 'rxjs/add/operator/switchMap';
|
||||
// #enddocregion rxjs-operator-import
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
// #docregion imports
|
||||
import { Router, ActivatedRoute, ParamMap } from '@angular/router';
|
||||
// #enddocregion imports
|
||||
@ -13,7 +14,7 @@ import { Hero, HeroService } from './hero.service';
|
||||
@Component({
|
||||
template: `
|
||||
<h2>HEROES</h2>
|
||||
<div *ngIf="hero">
|
||||
<div *ngIf="hero$ | async as hero">
|
||||
<h3>"{{ hero.name }}"</h3>
|
||||
<div>
|
||||
<label>Id: </label>{{ hero.id }}</div>
|
||||
@ -28,7 +29,7 @@ import { Hero, HeroService } from './hero.service';
|
||||
`
|
||||
})
|
||||
export class HeroDetailComponent implements OnInit {
|
||||
hero: Hero;
|
||||
hero$: Observable<Hero>;
|
||||
|
||||
// #docregion ctor
|
||||
constructor(
|
||||
@ -40,10 +41,9 @@ export class HeroDetailComponent implements OnInit {
|
||||
|
||||
// #docregion ngOnInit
|
||||
ngOnInit() {
|
||||
this.route.paramMap
|
||||
this.hero$ = this.route.paramMap
|
||||
.switchMap((params: ParamMap) =>
|
||||
this.service.getHero(params.get('id')))
|
||||
.subscribe((hero: Hero) => this.hero = hero);
|
||||
this.service.getHero(params.get('id')));
|
||||
}
|
||||
// #enddocregion ngOnInit
|
||||
|
||||
|
@ -2,13 +2,14 @@
|
||||
// #docregion
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
|
||||
import { Hero, HeroService } from './hero.service';
|
||||
|
||||
@Component({
|
||||
template: `
|
||||
<h2>HEROES</h2>
|
||||
<div *ngIf="hero">
|
||||
<div *ngIf="hero$ | async as hero">
|
||||
<h3>"{{ hero.name }}"</h3>
|
||||
<div>
|
||||
<label>Id: </label>{{ hero.id }}</div>
|
||||
@ -23,7 +24,7 @@ import { Hero, HeroService } from './hero.service';
|
||||
`
|
||||
})
|
||||
export class HeroDetailComponent implements OnInit {
|
||||
hero: Hero;
|
||||
hero$: Observable<Hero>;
|
||||
|
||||
constructor(
|
||||
private route: ActivatedRoute,
|
||||
@ -35,8 +36,7 @@ export class HeroDetailComponent implements OnInit {
|
||||
ngOnInit() {
|
||||
let id = this.route.snapshot.paramMap.get('id');
|
||||
|
||||
this.service.getHero(id)
|
||||
.then((hero: Hero) => this.hero = hero);
|
||||
this.hero$ = this.service.getHero(id);
|
||||
}
|
||||
// #enddocregion snapshot
|
||||
|
||||
|
@ -4,6 +4,7 @@
|
||||
import 'rxjs/add/operator/switchMap';
|
||||
// #enddocregion rxjs-operator-import
|
||||
import { Component, OnInit, HostBinding } from '@angular/core';
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
import { Router, ActivatedRoute, ParamMap } from '@angular/router';
|
||||
|
||||
import { slideInDownAnimation } from '../animations';
|
||||
@ -13,7 +14,7 @@ import { Hero, HeroService } from './hero.service';
|
||||
@Component({
|
||||
template: `
|
||||
<h2>HEROES</h2>
|
||||
<div *ngIf="hero">
|
||||
<div *ngIf="hero$ | async as hero">
|
||||
<h3>"{{ hero.name }}"</h3>
|
||||
<div>
|
||||
<label>Id: </label>{{ hero.id }}</div>
|
||||
@ -22,7 +23,7 @@ import { Hero, HeroService } from './hero.service';
|
||||
<input [(ngModel)]="hero.name" placeholder="name"/>
|
||||
</div>
|
||||
<p>
|
||||
<button (click)="gotoHeroes()">Back</button>
|
||||
<button (click)="gotoHeroes(hero)">Back</button>
|
||||
</p>
|
||||
</div>
|
||||
`,
|
||||
@ -35,7 +36,7 @@ export class HeroDetailComponent implements OnInit {
|
||||
@HostBinding('style.position') position = 'absolute';
|
||||
// #enddocregion host-bindings
|
||||
|
||||
hero: Hero;
|
||||
hero$: Observable<Hero>;
|
||||
|
||||
// #docregion ctor
|
||||
constructor(
|
||||
@ -47,16 +48,15 @@ export class HeroDetailComponent implements OnInit {
|
||||
|
||||
// #docregion ngOnInit
|
||||
ngOnInit() {
|
||||
this.route.paramMap
|
||||
this.hero$ = this.route.paramMap
|
||||
.switchMap((params: ParamMap) =>
|
||||
this.service.getHero(params.get('id')))
|
||||
.subscribe((hero: Hero) => this.hero = hero);
|
||||
this.service.getHero(params.get('id')));
|
||||
}
|
||||
// #enddocregion ngOnInit
|
||||
|
||||
// #docregion gotoHeroes
|
||||
gotoHeroes() {
|
||||
let heroId = this.hero ? this.hero.id : null;
|
||||
gotoHeroes(hero: Hero) {
|
||||
let heroId = hero ? hero.id : null;
|
||||
// Pass along the hero id if available
|
||||
// so that the HeroList component can select that hero.
|
||||
// Include a junk 'foo' property for fun.
|
||||
|
@ -3,6 +3,7 @@
|
||||
// TODO SOMEDAY: Feature Componetized like HeroCenter
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { Router } from '@angular/router';
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
|
||||
import { Hero, HeroService } from './hero.service';
|
||||
|
||||
@ -11,9 +12,12 @@ import { Hero, HeroService } from './hero.service';
|
||||
template: `
|
||||
<h2>HEROES</h2>
|
||||
<ul class="items">
|
||||
<li *ngFor="let hero of heroes | async"
|
||||
(click)="onSelect(hero)">
|
||||
<span class="badge">{{ hero.id }}</span> {{ hero.name }}
|
||||
<li *ngFor="let hero of heroes$ | async">
|
||||
// #docregion nav-to-detail
|
||||
<a [routerLink]="['/hero', hero.id]">
|
||||
<span class="badge">{{ hero.id }}</span>{{ hero.name }}
|
||||
</a>
|
||||
// #enddocregion nav-to-detail
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
@ -22,7 +26,7 @@ import { Hero, HeroService } from './hero.service';
|
||||
// #enddocregion template
|
||||
})
|
||||
export class HeroListComponent implements OnInit {
|
||||
heroes: Promise<Hero[]>;
|
||||
heroes$: Observable<Hero[]>;
|
||||
|
||||
// #docregion ctor
|
||||
constructor(
|
||||
@ -32,16 +36,8 @@ export class HeroListComponent implements OnInit {
|
||||
// #enddocregion ctor
|
||||
|
||||
ngOnInit() {
|
||||
this.heroes = this.service.getHeroes();
|
||||
this.heroes$ = this.service.getHeroes();
|
||||
}
|
||||
|
||||
// #docregion select
|
||||
onSelect(hero: Hero) {
|
||||
// #docregion nav-to-detail
|
||||
this.router.navigate(['/hero', hero.id]);
|
||||
// #enddocregion nav-to-detail
|
||||
}
|
||||
// #enddocregion select
|
||||
}
|
||||
// #enddocregion
|
||||
|
||||
|
@ -7,7 +7,7 @@ import { Observable } from 'rxjs/Observable';
|
||||
// #enddocregion rxjs-imports
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
// #docregion import-router
|
||||
import { Router, ActivatedRoute, ParamMap } from '@angular/router';
|
||||
import { ActivatedRoute, ParamMap } from '@angular/router';
|
||||
// #enddocregion import-router
|
||||
|
||||
import { Hero, HeroService } from './hero.service';
|
||||
@ -17,10 +17,11 @@ import { Hero, HeroService } from './hero.service';
|
||||
template: `
|
||||
<h2>HEROES</h2>
|
||||
<ul class="items">
|
||||
<li *ngFor="let hero of heroes | async"
|
||||
[class.selected]="isSelected(hero)"
|
||||
(click)="onSelect(hero)">
|
||||
<span class="badge">{{ hero.id }}</span> {{ hero.name }}
|
||||
<li *ngFor="let hero of heroes$ | async"
|
||||
[class.selected]="hero.id === selectedId">
|
||||
<a [routerLink]="['/hero', hero.id]">
|
||||
<span class="badge">{{ hero.id }}</span>{{ hero.name }}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
@ -30,18 +31,17 @@ import { Hero, HeroService } from './hero.service';
|
||||
})
|
||||
// #docregion ctor
|
||||
export class HeroListComponent implements OnInit {
|
||||
heroes: Observable<Hero[]>;
|
||||
heroes$: Observable<Hero[]>;
|
||||
|
||||
private selectedId: number;
|
||||
|
||||
constructor(
|
||||
private service: HeroService,
|
||||
private route: ActivatedRoute,
|
||||
private router: Router
|
||||
private route: ActivatedRoute
|
||||
) {}
|
||||
|
||||
ngOnInit() {
|
||||
this.heroes = this.route.paramMap
|
||||
this.heroes$ = this.route.paramMap
|
||||
.switchMap((params: ParamMap) => {
|
||||
// (+) before `params.get()` turns the string into a number
|
||||
this.selectedId = +params.get('id');
|
||||
@ -49,16 +49,6 @@ export class HeroListComponent implements OnInit {
|
||||
});
|
||||
}
|
||||
// #enddocregion ctor
|
||||
|
||||
// #docregion isSelected
|
||||
isSelected(hero: Hero) { return hero.id === this.selectedId; }
|
||||
// #enddocregion isSelected
|
||||
|
||||
// #docregion select
|
||||
onSelect(hero: Hero) {
|
||||
this.router.navigate(['/hero', hero.id]);
|
||||
}
|
||||
// #enddocregion select
|
||||
// #docregion ctor
|
||||
}
|
||||
// #enddocregion
|
||||
|
@ -1,11 +1,14 @@
|
||||
// #docregion
|
||||
import 'rxjs/add/observable/of';
|
||||
import 'rxjs/add/operator/map';
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
|
||||
export class Hero {
|
||||
constructor(public id: number, public name: string) { }
|
||||
}
|
||||
|
||||
let HEROES = [
|
||||
const HEROES = [
|
||||
new Hero(11, 'Mr. Nice'),
|
||||
new Hero(12, 'Narco'),
|
||||
new Hero(13, 'Bombasto'),
|
||||
@ -14,15 +17,13 @@ let HEROES = [
|
||||
new Hero(16, 'RubberMan')
|
||||
];
|
||||
|
||||
let heroesPromise = Promise.resolve(HEROES);
|
||||
|
||||
@Injectable()
|
||||
export class HeroService {
|
||||
getHeroes() { return heroesPromise; }
|
||||
getHeroes() { return Observable.of(HEROES); }
|
||||
|
||||
getHero(id: number | string) {
|
||||
return heroesPromise
|
||||
return this.getHeroes()
|
||||
// (+) before `id` turns the string into a number
|
||||
.then(heroes => heroes.find(hero => hero.id === +id));
|
||||
.map(heroes => heroes.find(hero => hero.id === +id));
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,24 @@
|
||||
// #docregion
|
||||
import { NgModule } from '@angular/core';
|
||||
import { RouterModule, Routes } from '@angular/router';
|
||||
|
||||
import { HeroListComponent } from './hero-list.component';
|
||||
import { HeroDetailComponent } from './hero-detail.component';
|
||||
|
||||
const heroesRoutes: Routes = [
|
||||
{ path: 'heroes', component: HeroListComponent },
|
||||
// #docregion hero-detail-route
|
||||
{ path: 'hero/:id', component: HeroDetailComponent }
|
||||
// #enddocregion hero-detail-route
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
RouterModule.forChild(heroesRoutes)
|
||||
],
|
||||
exports: [
|
||||
RouterModule
|
||||
]
|
||||
})
|
||||
export class HeroRoutingModule { }
|
||||
// #enddocregion
|
@ -6,10 +6,10 @@ import { HeroListComponent } from './hero-list.component';
|
||||
import { HeroDetailComponent } from './hero-detail.component';
|
||||
|
||||
const heroesRoutes: Routes = [
|
||||
{ path: 'heroes', component: HeroListComponent },
|
||||
// #docregion hero-detail-route
|
||||
{ path: 'hero/:id', component: HeroDetailComponent }
|
||||
// #enddocregion hero-detail-route
|
||||
{ path: 'heroes', redirectTo: '/superheroes' },
|
||||
{ path: 'hero/:id', redirectTo: '/superhero/:id' },
|
||||
{ path: 'superheroes', component: HeroListComponent },
|
||||
{ path: 'superhero/:id', component: HeroDetailComponent }
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
|
@ -35,7 +35,7 @@ export class HeroDetailComponent {
|
||||
@Input() prefix = '';
|
||||
|
||||
// #docregion deleteRequest
|
||||
// This component make a request but it can't actually delete a hero.
|
||||
// This component makes a request but it can't actually delete a hero.
|
||||
deleteRequest = new EventEmitter<Hero>();
|
||||
|
||||
delete() {
|
||||
|
@ -44,6 +44,7 @@ System.config({
|
||||
map: {
|
||||
'@angular/core/testing': 'npm:@angular/core/bundles/core-testing.umd.js',
|
||||
'@angular/common/testing': 'npm:@angular/common/bundles/common-testing.umd.js',
|
||||
'@angular/common/http/testing': 'npm:@angular/common/bundles/common-http-testing.umd.js',
|
||||
'@angular/compiler/testing': 'npm:@angular/compiler/bundles/compiler-testing.umd.js',
|
||||
'@angular/platform-browser/testing': 'npm:@angular/platform-browser/bundles/platform-browser-testing.umd.js',
|
||||
'@angular/platform-browser-dynamic/testing': 'npm:@angular/platform-browser-dynamic/bundles/platform-browser-dynamic-testing.umd.js',
|
||||
|
@ -52,6 +52,10 @@ module.exports = function(config) {
|
||||
{ pattern: 'node_modules/rxjs/**/*.js', included: false, watched: false },
|
||||
{ pattern: 'node_modules/rxjs/**/*.js.map', included: false, watched: false },
|
||||
|
||||
// tslib (TS helper fns such as `__extends`)
|
||||
{ pattern: 'node_modules/tslib/**/*.js', included: false, watched: false },
|
||||
{ pattern: 'node_modules/tslib/**/*.js.map', included: false, watched: false },
|
||||
|
||||
// Paths loaded via module imports:
|
||||
// Angular itself
|
||||
{ pattern: 'node_modules/@angular/**/*.js', included: false, watched: false },
|
||||
|
@ -1,116 +0,0 @@
|
||||
/* #docregion , quickstart, toh */
|
||||
/* Master Styles */
|
||||
h1 {
|
||||
color: #369;
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
font-size: 250%;
|
||||
}
|
||||
h2, h3 {
|
||||
color: #444;
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
font-weight: lighter;
|
||||
}
|
||||
body {
|
||||
margin: 2em;
|
||||
}
|
||||
/* #enddocregion quickstart */
|
||||
body, input[text], button {
|
||||
color: #888;
|
||||
font-family: Cambria, Georgia;
|
||||
}
|
||||
/* #enddocregion toh */
|
||||
a {
|
||||
cursor: pointer;
|
||||
cursor: hand;
|
||||
}
|
||||
button {
|
||||
font-family: Arial;
|
||||
background-color: #eee;
|
||||
border: none;
|
||||
padding: 5px 10px;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
cursor: hand;
|
||||
}
|
||||
button:hover {
|
||||
background-color: #cfd8dc;
|
||||
}
|
||||
button:disabled {
|
||||
background-color: #eee;
|
||||
color: #aaa;
|
||||
cursor: auto;
|
||||
}
|
||||
|
||||
/* Navigation link styles */
|
||||
nav a {
|
||||
padding: 5px 10px;
|
||||
text-decoration: none;
|
||||
margin-right: 10px;
|
||||
margin-top: 10px;
|
||||
display: inline-block;
|
||||
background-color: #eee;
|
||||
border-radius: 4px;
|
||||
}
|
||||
nav a:visited, a:link {
|
||||
color: #607D8B;
|
||||
}
|
||||
nav a:hover {
|
||||
color: #039be5;
|
||||
background-color: #CFD8DC;
|
||||
}
|
||||
nav a.active {
|
||||
color: #039be5;
|
||||
}
|
||||
|
||||
/* items class */
|
||||
.items {
|
||||
margin: 0 0 2em 0;
|
||||
list-style-type: none;
|
||||
padding: 0;
|
||||
width: 24em;
|
||||
}
|
||||
.items li {
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
left: 0;
|
||||
background-color: #EEE;
|
||||
margin: .5em;
|
||||
padding: .3em 0;
|
||||
height: 1.6em;
|
||||
border-radius: 4px;
|
||||
}
|
||||
.items li:hover {
|
||||
color: #607D8B;
|
||||
background-color: #DDD;
|
||||
left: .1em;
|
||||
}
|
||||
.items li.selected {
|
||||
background-color: #CFD8DC;
|
||||
color: white;
|
||||
}
|
||||
.items li.selected:hover {
|
||||
background-color: #BBD8DC;
|
||||
}
|
||||
.items .text {
|
||||
position: relative;
|
||||
top: -3px;
|
||||
}
|
||||
.items .badge {
|
||||
display: inline-block;
|
||||
font-size: small;
|
||||
color: white;
|
||||
padding: 0.8em 0.7em 0 0.7em;
|
||||
background-color: #607D8B;
|
||||
line-height: 1em;
|
||||
position: relative;
|
||||
left: -1px;
|
||||
top: -4px;
|
||||
height: 1.8em;
|
||||
margin-right: .8em;
|
||||
border-radius: 4px 0 0 4px;
|
||||
}
|
||||
/* #docregion toh */
|
||||
/* everywhere else */
|
||||
* {
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
}
|
@ -1,77 +0,0 @@
|
||||
'use strict'; // necessary for es6 output in node
|
||||
|
||||
import { browser, element, by } from 'protractor';
|
||||
|
||||
describe('TypeScript to Javascript tests', function () {
|
||||
|
||||
beforeAll(function () {
|
||||
browser.get('');
|
||||
});
|
||||
|
||||
it('should display the basic component example', function () {
|
||||
testTag('hero-view', 'Hero Detail: Windstorm');
|
||||
});
|
||||
|
||||
it('should display the component example with lifecycle methods', function () {
|
||||
testTag('hero-lifecycle', 'Hero: Windstorm');
|
||||
});
|
||||
|
||||
it('should display component with DI example', function () {
|
||||
testTag('hero-di', 'Hero: Windstorm');
|
||||
});
|
||||
|
||||
it('should display component with DI using @Inject example', function () {
|
||||
testTag('hero-di-inject', 'Hero: Windstorm');
|
||||
});
|
||||
|
||||
it('should support optional, attribute, and query injections', function () {
|
||||
let app = element(by.css('hero-di-inject-additional'));
|
||||
let h1 = app.element(by.css('h1'));
|
||||
let okMsg = app.element(by.css('p'));
|
||||
|
||||
expect(h1.getText()).toBe('Tour of Heroes');
|
||||
app.element(by.buttonText('OK')).click();
|
||||
expect(okMsg.getText()).toBe('OK!');
|
||||
});
|
||||
|
||||
it('should support component with inputs and outputs', function () {
|
||||
let app = element(by.css('hero-io'));
|
||||
let confirmComponent = app.element(by.css('app-confirm'));
|
||||
|
||||
confirmComponent.element(by.buttonText('OK')).click();
|
||||
expect(app.element(by.cssContainingText('span', 'OK clicked')).isPresent()).toBe(true);
|
||||
|
||||
confirmComponent.element(by.buttonText('Cancel')).click();
|
||||
expect(app.element(by.cssContainingText('span', 'Cancel clicked')).isPresent()).toBe(true);
|
||||
});
|
||||
|
||||
it('should support host bindings and host listeners', function() {
|
||||
let app = element(by.css('hero-host'));
|
||||
let h1 = app.element(by.css('h1'));
|
||||
|
||||
expect(app.getAttribute('class')).toBe('heading');
|
||||
expect(app.getAttribute('title')).toContain('Tooltip');
|
||||
|
||||
h1.click();
|
||||
expect(h1.getAttribute('class')).toBe('active');
|
||||
|
||||
h1.click();
|
||||
browser.actions().doubleClick(h1.getWebElement()).perform();
|
||||
expect(h1.getAttribute('class')).toBe('active');
|
||||
});
|
||||
|
||||
it('should support content and view queries', function() {
|
||||
let app = element(by.css('hero-queries'));
|
||||
let windstorm = app.element(by.css('view-child:first-child'));
|
||||
|
||||
app.element(by.css('button')).click();
|
||||
expect(windstorm.element(by.css('h2')).getAttribute('class')).toBe('active');
|
||||
expect(windstorm.element(by.css('content-child')).getText()).toBe('Active');
|
||||
});
|
||||
|
||||
function testTag(selector: string, expectedText: string) {
|
||||
let component = element(by.css(selector));
|
||||
expect(component.getText()).toBe(expectedText);
|
||||
}
|
||||
|
||||
});
|
@ -1,3 +0,0 @@
|
||||
{
|
||||
"build": "build:babel"
|
||||
}
|
@ -1,9 +0,0 @@
|
||||
{
|
||||
"description": "TypeScript to JavaScript",
|
||||
"basePath": "src/",
|
||||
"files":[
|
||||
"!**/*.d.ts",
|
||||
"!**/*.js"
|
||||
],
|
||||
"tags":["cookbook"]
|
||||
}
|
@ -1,6 +0,0 @@
|
||||
{
|
||||
"presets": [
|
||||
"es2015",
|
||||
"angular2"
|
||||
]
|
||||
}
|
@ -1,14 +0,0 @@
|
||||
import { Component } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'my-app',
|
||||
templateUrl: './app.component.html',
|
||||
styles: [
|
||||
// See hero-di-inject-additional.component
|
||||
'hero-host, hero-host-meta { border: 1px dashed black; display: block; padding: 4px;}',
|
||||
'.heading {font-style: italic}'
|
||||
]
|
||||
})
|
||||
export class AppComponent {
|
||||
title = 'ES6 JavaScript with Decorators';
|
||||
}
|
@ -1,31 +0,0 @@
|
||||
<a id="toc"></a>
|
||||
<h1>{{title}}</h1>
|
||||
<a href="#class-metadata">Classes and Class Metadata</a><br>
|
||||
<a href="#io-metadata">Input and Output Decorators</a><br>
|
||||
<a href="#dependency-injection">Dependency Injection</a><br>
|
||||
<a href="#host-metadata">Host Metadata</a><br>
|
||||
<a href="#view-child-metadata">View and Child Metadata</a><br>
|
||||
|
||||
<hr>
|
||||
<h4 id="class-metadata">Classes and Class Metadata</h4>
|
||||
<hero-view></hero-view>
|
||||
<hero-lifecycle></hero-lifecycle>
|
||||
|
||||
<hr>
|
||||
<h4 id="io-metadata">Input and Output Metadata</h4>
|
||||
<hero-io></hero-io>
|
||||
|
||||
<hr>
|
||||
<h4 id="dependency-injection">Dependency Injection</h4>
|
||||
<hero-di></hero-di>
|
||||
<hero-di-inject></hero-di-inject>
|
||||
<hero-di-inject-additional></hero-di-inject-additional>
|
||||
|
||||
<hr>
|
||||
<h4 id="host-metadata">Host Metadata</h4>
|
||||
<hero-host></hero-host>
|
||||
<hero-host-meta></hero-host-meta>
|
||||
|
||||
<hr>
|
||||
<h4 id="view-child-metadata">View and Child Metadata</h4>
|
||||
<hero-queries></hero-queries>
|
@ -1,55 +0,0 @@
|
||||
import { NgModule, NO_ERRORS_SCHEMA } from '@angular/core';
|
||||
import { BrowserModule } from '@angular/platform-browser';
|
||||
|
||||
import { AppComponent } from './app.component';
|
||||
import { ConfirmComponent } from './confirm.component';
|
||||
// #docregion appimport
|
||||
import { HeroComponent } from './hero.component';
|
||||
// #enddocregion appimport
|
||||
import { HeroComponent as HeroDIComponent } from './hero-di.component';
|
||||
import { HeroComponent as HeroDIInjectComponent } from './hero-di-inject.component';
|
||||
import { HeroComponent as HeroDIInjectAdditionalComponent } from './hero-di-inject-additional.component';
|
||||
import { HeroHostComponent } from './hero-host.component';
|
||||
import { HeroHostMetaComponent } from './hero-host-meta.component';
|
||||
import { HeroIOComponent } from './hero-io.component';
|
||||
import { HeroComponent as HeroLifecycleComponent } from './hero-lifecycle.component';
|
||||
import { HeroQueriesComponent, ViewChildComponent, ContentChildComponent } from './hero-queries.component';
|
||||
import { HeroTitleComponent } from './hero-title.component';
|
||||
|
||||
import { DataService } from './data.service';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
BrowserModule
|
||||
],
|
||||
declarations: [
|
||||
AppComponent,
|
||||
ConfirmComponent,
|
||||
HeroComponent,
|
||||
HeroDIComponent,
|
||||
HeroDIInjectComponent,
|
||||
HeroDIInjectAdditionalComponent,
|
||||
HeroHostComponent, HeroHostMetaComponent,
|
||||
HeroIOComponent,
|
||||
HeroLifecycleComponent,
|
||||
HeroQueriesComponent, ViewChildComponent, ContentChildComponent,
|
||||
HeroTitleComponent
|
||||
],
|
||||
providers: [
|
||||
DataService,
|
||||
{ provide: 'heroName', useValue: 'Windstorm' }
|
||||
],
|
||||
bootstrap: [ AppComponent ],
|
||||
|
||||
// schemas: [ NO_ERRORS_SCHEMA ] // helpful for debugging
|
||||
})
|
||||
export class AppModule { }
|
||||
|
||||
/* tslint:disable no-unused-variable */
|
||||
// #docregion ng2import
|
||||
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
|
||||
import {
|
||||
LocationStrategy,
|
||||
HashLocationStrategy
|
||||
} from '@angular/common';
|
||||
// #enddocregion ng2import
|
@ -1,21 +0,0 @@
|
||||
import { Component, EventEmitter, Input, Output } from '@angular/core';
|
||||
|
||||
// #docregion
|
||||
@Component({
|
||||
selector: 'app-confirm',
|
||||
templateUrl: './confirm.component.html'
|
||||
})
|
||||
export class ConfirmComponent {
|
||||
@Input() okMsg = '';
|
||||
@Input('cancelMsg') notOkMsg = '';
|
||||
@Output() ok = new EventEmitter();
|
||||
@Output('cancel') notOk = new EventEmitter();
|
||||
|
||||
onOkClick() {
|
||||
this.ok.emit(true);
|
||||
}
|
||||
onNotOkClick() {
|
||||
this.notOk.emit(true);
|
||||
}
|
||||
}
|
||||
// #enddocregion
|
@ -1,6 +0,0 @@
|
||||
<button (click)="onOkClick()">
|
||||
{{okMsg}}
|
||||
</button>
|
||||
<button (click)="onNotOkClick()">
|
||||
{{notOkMsg}}
|
||||
</button>
|
@ -1,10 +0,0 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
|
||||
@Injectable()
|
||||
export class DataService {
|
||||
constructor() { }
|
||||
|
||||
getHeroName() {
|
||||
return 'Windstorm';
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user