Compare commits
421 Commits
6.0.x
...
add-topnav
Author | SHA1 | Date | |
---|---|---|---|
97c944f076 | |||
794584e353 | |||
45862d0812 | |||
f3625e424b | |||
ccbda9de65 | |||
27bc7dcb43 | |||
0f7e4fae20 | |||
a45fad3dd9 | |||
f00ae516eb | |||
6d246d6c72 | |||
7c8159b3e2 | |||
5aa12c73ae | |||
d8f7b293d7 | |||
39af314e29 | |||
8daadf360c | |||
859a3d5784 | |||
66f6a48210 | |||
8a4c577917 | |||
2b15108f7e | |||
bc4f10ca20 | |||
e6516b0229 | |||
77309e2ea4 | |||
e371b226fa | |||
ccb19fea68 | |||
38a0d1fac5 | |||
e7b392bf3a | |||
8977b9690e | |||
d4d8125b2d | |||
62443b04a0 | |||
3c1eb9413f | |||
4168c946c6 | |||
293ec78069 | |||
131d0d8e8a | |||
5fb0b567ce | |||
03f93b3772 | |||
3ccb4490a4 | |||
2ea197b99f | |||
503a524d27 | |||
a577c9e1f4 | |||
52ce9d5dcb | |||
2b49bf77af | |||
92b278c097 | |||
513f645894 | |||
0bd2d7bac6 | |||
82c5313740 | |||
c8e865ac8e | |||
d9bf6e37ae | |||
e3c54e4465 | |||
153ba4dff3 | |||
5731d0741a | |||
70ef061fa6 | |||
c2b5ebfa24 | |||
0d07d273dc | |||
131602474d | |||
282d3510cf | |||
3ed2d75336 | |||
4d55dfd9d9 | |||
86bf5f3912 | |||
dbfb6b9d45 | |||
8dd99ac550 | |||
014949f74c | |||
5e8bf2f88d | |||
ea143e7498 | |||
29eb24b142 | |||
dc4a3d00d0 | |||
8aa70c2477 | |||
49c5234c68 | |||
1b253e14ff | |||
8c1ac28275 | |||
5ef7a07c4b | |||
113556357a | |||
7983f0a69b | |||
8be6892777 | |||
9f877f4416 | |||
4664226b97 | |||
d4c66d5edb | |||
ce1543fcde | |||
a6e797b8f5 | |||
ca79e11bfa | |||
f781f741ea | |||
bd02b27ee1 | |||
e3759f7a73 | |||
7de2ba0e22 | |||
07b4c8be42 | |||
3128b26e5c | |||
4f5b01a98a | |||
c151f9cdc8 | |||
24ab0a7db0 | |||
31988a6ff9 | |||
8ac74da016 | |||
355e0b0587 | |||
d96ae123b2 | |||
7e73287676 | |||
9dd647b087 | |||
47814b4cdf | |||
700e55ce14 | |||
68d37ef0c1 | |||
acf270d724 | |||
1007d1ad27 | |||
51e9e64c5a | |||
1915e47d11 | |||
e994b11105 | |||
856ee73464 | |||
0d06c866c6 | |||
1208a35373 | |||
b415010222 | |||
d6989c80d3 | |||
81e4b2a4bf | |||
c494d3cf60 | |||
22b58a717a | |||
86b13ccf80 | |||
8db928df9d | |||
9367e91402 | |||
87b16710e7 | |||
20c463e97c | |||
57eacf4b5a | |||
d814eaad95 | |||
678fd32406 | |||
3e938279d0 | |||
d700a409da | |||
b750919ce0 | |||
9c403753e2 | |||
83a06863f9 | |||
08a18b82de | |||
255463ed48 | |||
b4bbdb4ce2 | |||
7623d74607 | |||
ccaa199366 | |||
069062236c | |||
5794506c64 | |||
cb65724761 | |||
44856bfc2f | |||
5db4f1a5ba | |||
0561b66a2b | |||
5cbcb5680b | |||
6948ef125c | |||
08f943a1f3 | |||
f69ac670ee | |||
60aa943e2d | |||
68a799e950 | |||
5f178f3a5a | |||
81c13e2f86 | |||
2d9111bfb6 | |||
a5c47d0045 | |||
7e3f8f77a9 | |||
6a663a4073 | |||
ec57133b61 | |||
3647cb7f3b | |||
49d5de68f6 | |||
4ab70fb93d | |||
5d6074eaff | |||
b86d4dee4d | |||
9add50129d | |||
9d364203a6 | |||
4247176b6e | |||
3b9c5c849c | |||
e79b845a45 | |||
b492b9e12b | |||
b99ef2b80a | |||
27d811a7ce | |||
accda00190 | |||
c25e6142d2 | |||
b96a3c8def | |||
c917e5b5bb | |||
2a78d5e6fe | |||
95074ca303 | |||
1cd9e6c2eb | |||
855d9c00e0 | |||
49d97f1ba0 | |||
62f751cd87 | |||
646b42a113 | |||
24e5c5b425 | |||
42a7295203 | |||
7c39216083 | |||
223882aeb6 | |||
aafb46a8fe | |||
c73196eb59 | |||
d6595ebd39 | |||
5b25c07795 | |||
3ed7fc6686 | |||
7c1bd7170e | |||
2e21690c66 | |||
f6f44edcc0 | |||
90bf5d8961 | |||
b87d650da2 | |||
e53179ef8c | |||
31795b620f | |||
41cd8f3efb | |||
3fd3c2ac4c | |||
1eafd04eb3 | |||
00c4751f37 | |||
c2e131119b | |||
7866684f2b | |||
6e05ae02a2 | |||
431a42a238 | |||
7a9c987e56 | |||
ae86cb3be0 | |||
a6f34be9f5 | |||
d74078fb88 | |||
ddd6124802 | |||
96a0e131bf | |||
e43d3fa4b7 | |||
7657535718 | |||
3de80fc7fb | |||
729c797890 | |||
188ff848d2 | |||
280a784fe3 | |||
4f36340de7 | |||
83bb5d1922 | |||
3e39fef274 | |||
36cc72ee5b | |||
01b5acd7cf | |||
186118e684 | |||
609e6b9787 | |||
68bf8c36c6 | |||
8216657681 | |||
13cb75da8b | |||
23a98b9e51 | |||
bd149e5d67 | |||
fb906a87e8 | |||
0bdd30e34f | |||
373fa78d7f | |||
26fbf1d13c | |||
608c3748e8 | |||
6d8c847e7b | |||
919f42fea1 | |||
1b6b936ef4 | |||
db2329ef6a | |||
de267e97c9 | |||
f8c6947205 | |||
41fea84957 | |||
a7b07defe1 | |||
6e7d071c6b | |||
99d330a1b7 | |||
3cdf5afc6e | |||
ea4321d912 | |||
88ab1d0e55 | |||
20d76374ed | |||
8ee25e6b58 | |||
43597279d6 | |||
55103419e9 | |||
547efb5f4d | |||
091b11a4ab | |||
4042a84ad6 | |||
6a24c02d73 | |||
b7c417f618 | |||
313bdce590 | |||
5cf82f8f3f | |||
e5e5c24d48 | |||
1d378e2987 | |||
017d67cdf8 | |||
83631b28cb | |||
d4b6c41a5f | |||
66b2d78305 | |||
67b8d57a8d | |||
02acb5e3e5 | |||
a2e8b3a6a8 | |||
d4b8b24406 | |||
cfde36da84 | |||
d889f57ae2 | |||
816bc8af17 | |||
d2a86872a9 | |||
474dbf09ec | |||
e129b18d17 | |||
8a27a034c4 | |||
4ecae6449e | |||
5e307d5ba7 | |||
089fe83865 | |||
b1cda3639f | |||
c4221dad11 | |||
fe3679a356 | |||
72eab4d254 | |||
db2d67cc00 | |||
117c7eebc3 | |||
89f64e58c3 | |||
553a680817 | |||
858e48a794 | |||
e942d8b681 | |||
f1e4a153f0 | |||
e0ed59e55f | |||
d6b1466c81 | |||
d1abf4e897 | |||
08e7efc69e | |||
46674d5fac | |||
c5ca5c0d9f | |||
61170856ee | |||
a800ccd922 | |||
971e78dc35 | |||
b0eca85e51 | |||
d01ec03f54 | |||
9e2d87f5b8 | |||
fc034270ce | |||
77ff72f93b | |||
44095d95c9 | |||
e3518967ad | |||
005dc8f68b | |||
7e9649bdf1 | |||
e3e15773ee | |||
b25e15c317 | |||
3b067c8579 | |||
57cf5509e6 | |||
3f20a5c7c8 | |||
14d8a98001 | |||
5cb36ed706 | |||
490e39a23f | |||
33c1c1df36 | |||
d8d4f654a6 | |||
2c4850dc58 | |||
2ef4760ff7 | |||
52f0e3cc3b | |||
61265b42ef | |||
6601d0f7ba | |||
cccc328a52 | |||
65211f46cf | |||
da9ff255dd | |||
2cf6244b1d | |||
b45fa5e263 | |||
d7ed9c9e9e | |||
266d97de95 | |||
d71329d55c | |||
7ba26b140b | |||
297723d0bc | |||
bb07fbde76 | |||
d7e8d15578 | |||
bfad6b4fa1 | |||
fd9d1888ce | |||
94fbe3b5ac | |||
56828e43b6 | |||
c5cfc3a1b6 | |||
b76f5a6a7d | |||
fb41b7dc30 | |||
ca1019a950 | |||
9ebf0c8e5e | |||
8062f7de9e | |||
cc6c4346c2 | |||
4cb46ce10c | |||
7ef9d4a582 | |||
a522bb9f03 | |||
31b96e99ff | |||
b7a6e1fef7 | |||
84b4593d01 | |||
0c6dc45c85 | |||
5b96078624 | |||
1a44a0b4a8 | |||
b1f040f5a2 | |||
eb031c6ff1 | |||
b4c252bcc5 | |||
db77d8dc92 | |||
ab5bc42da0 | |||
f567e1898f | |||
8d0ee34939 | |||
43a49d3f64 | |||
811a7f2863 | |||
9ed5fb6d2c | |||
e1c4930a1a | |||
dab5df9734 | |||
b1d03fe70b | |||
06c0d9666f | |||
1c9200eca8 | |||
ace6440460 | |||
b26ac1c22f | |||
60e5507076 | |||
4cfa571258 | |||
999ab0a690 | |||
ba47997715 | |||
a35bf114eb | |||
6761a64522 | |||
0b47902ad7 | |||
4662878a1f | |||
ca776c59dd | |||
f2563ca800 | |||
9757347e71 | |||
a19e018439 | |||
6ff164be0e | |||
84f024309a | |||
c6b206ee4b | |||
1d1e75ee2b | |||
acf6781ccc | |||
fd48e53986 | |||
fe312ccb4c | |||
764f471dc0 | |||
8b02c0e769 | |||
5a2ee7a6f5 | |||
529d4fc9ee | |||
fac7dde5b1 | |||
1f005908a4 | |||
2278fe8f0e | |||
aad3444a58 | |||
44377adbcc | |||
b28b3acb83 | |||
7493435911 | |||
937f7cea37 | |||
7d1990e4d1 | |||
76f8ae31ad | |||
103846a51d | |||
0a536af093 | |||
4f29287399 | |||
62e6c1f43a | |||
c3c513ed9e | |||
ed495bc9f1 | |||
a3de5f8f20 | |||
2491b7249a | |||
a851ba3781 | |||
0468a649af | |||
47d3acdc49 | |||
acbfb9eb4d | |||
d35f84a167 | |||
87e9f333d4 | |||
08fc4f3ad8 | |||
c6c79ab5dc | |||
6837491f08 | |||
fc5af69fb2 | |||
81ccb718b1 | |||
0c56dfadef | |||
7be7abdebd | |||
5a1ddee88c | |||
99f8e10809 | |||
d665d9a18c | |||
8b2101be9f | |||
0d56cee9e1 | |||
7f612fc828 |
@ -12,8 +12,8 @@
|
|||||||
## IMPORTANT
|
## IMPORTANT
|
||||||
# If you change the `docker_image` version, also change the `cache_key` suffix and the version of
|
# If you change the `docker_image` version, also change the `cache_key` suffix and the version of
|
||||||
# `com_github_bazelbuild_buildtools` in the `/WORKSPACE` file.
|
# `com_github_bazelbuild_buildtools` in the `/WORKSPACE` file.
|
||||||
var_1: &docker_image angular/ngcontainer:0.3.3
|
var_1: &docker_image angular/ngcontainer:0.3.2
|
||||||
var_2: &cache_key v2-angular-{{ .Branch }}-{{ checksum "yarn.lock" }}-0.3.3
|
var_2: &cache_key v2-angular-{{ .Branch }}-{{ checksum "yarn.lock" }}-0.3.2
|
||||||
|
|
||||||
# Define common ENV vars
|
# Define common ENV vars
|
||||||
var_3: &define_env_vars
|
var_3: &define_env_vars
|
||||||
@ -85,7 +85,7 @@ jobs:
|
|||||||
# This avoids waiting for the slowest build target to finish before running the first test
|
# This avoids waiting for the slowest build target to finish before running the first test
|
||||||
# See https://github.com/bazelbuild/bazel/issues/4257
|
# See https://github.com/bazelbuild/bazel/issues/4257
|
||||||
# NOTE: Angular developers should typically just bazel build //packages/... or bazel test //packages/...
|
# NOTE: Angular developers should typically just bazel build //packages/... or bazel test //packages/...
|
||||||
- run: bazel query --output=label //... | xargs bazel test
|
- run: bazel query --output=label //... | xargs bazel test --build_tag_filters=-ivy-only --test_tag_filters=-manual,-ivy-only
|
||||||
|
|
||||||
# CircleCI will allow us to go back and view/download these artifacts from past builds.
|
# CircleCI will allow us to go back and view/download these artifacts from past builds.
|
||||||
# Also we can use a service like https://buildsize.org/ to automatically track binary size of these artifacts.
|
# Also we can use a service like https://buildsize.org/ to automatically track binary size of these artifacts.
|
||||||
@ -111,6 +111,42 @@ jobs:
|
|||||||
paths:
|
paths:
|
||||||
- "node_modules"
|
- "node_modules"
|
||||||
- "~/bazel_repository_cache"
|
- "~/bazel_repository_cache"
|
||||||
|
# Temporary job to test what will happen when we flip the Ivy flag to true
|
||||||
|
test_ivy_jit:
|
||||||
|
<<: *job_defaults
|
||||||
|
resource_class: xlarge
|
||||||
|
steps:
|
||||||
|
- *define_env_vars
|
||||||
|
- checkout:
|
||||||
|
<<: *post_checkout
|
||||||
|
# See remote cache documentation in /docs/BAZEL.md
|
||||||
|
- run: .circleci/setup_cache.sh
|
||||||
|
- run: sudo cp .circleci/bazel.rc /etc/bazel.bazelrc
|
||||||
|
- *setup-bazel-remote-cache
|
||||||
|
|
||||||
|
- restore_cache:
|
||||||
|
key: *cache_key
|
||||||
|
|
||||||
|
- run: bazel run @yarn//:yarn
|
||||||
|
- run: bazel query --output=label //... | xargs bazel test --define=compile=jit --build_tag_filters=ivy-jit --test_tag_filters=-manual,ivy-jit
|
||||||
|
|
||||||
|
test_ivy_aot:
|
||||||
|
<<: *job_defaults
|
||||||
|
resource_class: xlarge
|
||||||
|
steps:
|
||||||
|
- *define_env_vars
|
||||||
|
- checkout:
|
||||||
|
<<: *post_checkout
|
||||||
|
# See remote cache documentation in /docs/BAZEL.md
|
||||||
|
- run: .circleci/setup_cache.sh
|
||||||
|
- run: sudo cp .circleci/bazel.rc /etc/bazel.bazelrc
|
||||||
|
- *setup-bazel-remote-cache
|
||||||
|
|
||||||
|
- restore_cache:
|
||||||
|
key: *cache_key
|
||||||
|
|
||||||
|
- run: bazel run @yarn//:yarn
|
||||||
|
- run: bazel query --output=label //... | xargs bazel test --define=compile=local --build_tag_filters=ivy-local --test_tag_filters=-manual,ivy-local
|
||||||
|
|
||||||
# This job exists only for backwards-compatibility with old scripts and tests
|
# This job exists only for backwards-compatibility with old scripts and tests
|
||||||
# that rely on the pre-Bazel dist/packages-dist layout.
|
# that rely on the pre-Bazel dist/packages-dist layout.
|
||||||
@ -140,6 +176,8 @@ jobs:
|
|||||||
root: dist
|
root: dist
|
||||||
paths:
|
paths:
|
||||||
- packages-dist
|
- packages-dist
|
||||||
|
- packages-dist-ivy-jit
|
||||||
|
- packages-dist-ivy-local
|
||||||
|
|
||||||
# We run the integration tests outside of Bazel for now.
|
# We run the integration tests outside of Bazel for now.
|
||||||
# They are a separate workflow job so that they can be easily re-run.
|
# They are a separate workflow job so that they can be easily re-run.
|
||||||
@ -204,6 +242,8 @@ workflows:
|
|||||||
jobs:
|
jobs:
|
||||||
- lint
|
- lint
|
||||||
- test
|
- test
|
||||||
|
- test_ivy_jit
|
||||||
|
- test_ivy_aot
|
||||||
- build-packages-dist
|
- build-packages-dist
|
||||||
- integration_test:
|
- integration_test:
|
||||||
requires:
|
requires:
|
||||||
@ -216,6 +256,8 @@ workflows:
|
|||||||
requires:
|
requires:
|
||||||
# Only publish if tests and integration tests pass
|
# Only publish if tests and integration tests pass
|
||||||
- test
|
- test
|
||||||
|
- test_ivy_jit
|
||||||
|
- test_ivy_aot
|
||||||
- integration_test
|
- integration_test
|
||||||
# Get the artifacts to publish from the build-packages-dist job
|
# Get the artifacts to publish from the build-packages-dist job
|
||||||
# since the publishing script expects the legacy outputs layout.
|
# since the publishing script expects the legacy outputs layout.
|
||||||
|
@ -23,6 +23,7 @@
|
|||||||
# petebacondarwin - Pete Bacon Darwin
|
# petebacondarwin - Pete Bacon Darwin
|
||||||
# pkozlowski-opensource - Pawel Kozlowski
|
# pkozlowski-opensource - Pawel Kozlowski
|
||||||
# robwormald - Rob Wormald
|
# robwormald - Rob Wormald
|
||||||
|
# tinayuangao - Tina Gao
|
||||||
# vicb - Victor Berchet
|
# vicb - Victor Berchet
|
||||||
# vikerman - Vikram Subramanian
|
# vikerman - Vikram Subramanian
|
||||||
|
|
||||||
@ -162,7 +163,7 @@ groups:
|
|||||||
files:
|
files:
|
||||||
- "packages/compiler/*"
|
- "packages/compiler/*"
|
||||||
users:
|
users:
|
||||||
- chuckjaz #primary
|
- alxhub #primary
|
||||||
- vicb
|
- vicb
|
||||||
- mhevery
|
- mhevery
|
||||||
- IgorMinar #fallback
|
- IgorMinar #fallback
|
||||||
@ -217,6 +218,7 @@ groups:
|
|||||||
- "aio/content/examples/reactive-forms/*"
|
- "aio/content/examples/reactive-forms/*"
|
||||||
users:
|
users:
|
||||||
- kara #primary
|
- kara #primary
|
||||||
|
- tinayuangao #secondary
|
||||||
- IgorMinar #fallback
|
- IgorMinar #fallback
|
||||||
- mhevery #fallback
|
- mhevery #fallback
|
||||||
|
|
||||||
|
110
CHANGELOG.md
110
CHANGELOG.md
@ -1,62 +1,37 @@
|
|||||||
<a name="6.0.9"></a>
|
<a name="6.1.0-beta.1"></a>
|
||||||
## [6.0.9](https://github.com/angular/angular/compare/6.0.8...6.0.9) (2018-07-11)
|
# [6.1.0-beta.1](https://github.com/angular/angular/compare/6.1.0-beta.0...6.1.0-beta.1) (2018-06-13)
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
### Bug Fixes
|
||||||
|
|
||||||
* **common:** format fractional seconds ([#24844](https://github.com/angular/angular/issues/24844)) ([3c93d07](https://github.com/angular/angular/commit/3c93d07)), closes [#24831](https://github.com/angular/angular/issues/24831)
|
* **animations:** always render end-state styles for orphaned DOM nodes ([#24236](https://github.com/angular/angular/issues/24236)) ([dc4a3d0](https://github.com/angular/angular/commit/dc4a3d0))
|
||||||
|
* **bazel:** Allow ng_module to depend on targets w no deps ([#24446](https://github.com/angular/angular/issues/24446)) ([282d351](https://github.com/angular/angular/commit/282d351))
|
||||||
|
* **docs-infra:** use script nomodule to load IE polyfills, skip other polyfills ([#24317](https://github.com/angular/angular/issues/24317)) ([8be6892](https://github.com/angular/angular/commit/8be6892)), closes [#23647](https://github.com/angular/angular/issues/23647)
|
||||||
|
* **ivy:** compute transitive scopes from NgModuleDef only ([#24334](https://github.com/angular/angular/issues/24334)) ([1135563](https://github.com/angular/angular/commit/1135563))
|
||||||
<a name="6.0.8"></a>
|
* **ivy:** correctly handle queries with embedded views ([#24418](https://github.com/angular/angular/issues/24418)) ([014949f](https://github.com/angular/angular/commit/014949f))
|
||||||
## [6.0.8](https://github.com/angular/angular/compare/6.0.7...6.0.8) (2018-07-11)
|
* **ivy:** remove debugger statement ([#24480](https://github.com/angular/angular/issues/24480)) ([70ef061](https://github.com/angular/angular/commit/70ef061))
|
||||||
|
* **ivy:** special case [style] and [class] bindings for future use ([#23232](https://github.com/angular/angular/issues/23232)) ([1b253e1](https://github.com/angular/angular/commit/1b253e1))
|
||||||
|
* **router:** fix lazy loading of aux routes ([#23459](https://github.com/angular/angular/issues/23459)) ([5731d07](https://github.com/angular/angular/commit/5731d07)), closes [#10981](https://github.com/angular/angular/issues/10981)
|
||||||
### Bug Fixes
|
* **service-worker:** fix `SwPush.unsubscribe()` ([#24162](https://github.com/angular/angular/issues/24162)) ([3ed2d75](https://github.com/angular/angular/commit/3ed2d75)), closes [#24095](https://github.com/angular/angular/issues/24095)
|
||||||
|
|
||||||
* **common:** do not round factional seconds ([#24831](https://github.com/angular/angular/issues/24831)) ([0746485](https://github.com/angular/angular/commit/0746485)), closes [#24384](https://github.com/angular/angular/issues/24384)
|
|
||||||
* **common:** properly update collection reference in NgForOf ([#24684](https://github.com/angular/angular/issues/24684)) ([9a98de9](https://github.com/angular/angular/commit/9a98de9)), closes [#24155](https://github.com/angular/angular/issues/24155)
|
|
||||||
* **common:** use correct currency format for locale de-AT ([#24658](https://github.com/angular/angular/issues/24658)) ([a92f111](https://github.com/angular/angular/commit/a92f111)), closes [#24609](https://github.com/angular/angular/issues/24609)
|
|
||||||
* **compiler-cli:** Use typescript to resolve modules for metadata ([#22856](https://github.com/angular/angular/issues/22856)) ([7717ff1](https://github.com/angular/angular/commit/7717ff1))
|
|
||||||
* **core:** use addCustomEqualityTester instead of overriding toEqual ([#22983](https://github.com/angular/angular/issues/22983)) ([b8975a9](https://github.com/angular/angular/commit/b8975a9)), closes [#22939](https://github.com/angular/angular/issues/22939)
|
|
||||||
* **language-service:** do not overwrite native `Reflect` ([#24299](https://github.com/angular/angular/issues/24299)) ([de1c44f](https://github.com/angular/angular/commit/de1c44f)), closes [#21420](https://github.com/angular/angular/issues/21420)
|
|
||||||
* **router:** add ability to recover from malformed url ([#23283](https://github.com/angular/angular/issues/23283)) ([2d4f4b5](https://github.com/angular/angular/commit/2d4f4b5)), closes [#21468](https://github.com/angular/angular/issues/21468)
|
|
||||||
* **service-worker:** avoid network requests when looking up hashed resources in cache ([#24127](https://github.com/angular/angular/issues/24127)) ([183b079](https://github.com/angular/angular/commit/183b079))
|
|
||||||
|
|
||||||
|
|
||||||
### Features
|
### Features
|
||||||
|
|
||||||
* **core:** add support for ShadowDOM v1 ([#24718](https://github.com/angular/angular/issues/24718)) ([6c55a13](https://github.com/angular/angular/commit/6c55a13))
|
* **common:** introduce KeyValuePipe ([#24319](https://github.com/angular/angular/issues/24319)) ([2b49bf7](https://github.com/angular/angular/commit/2b49bf7))
|
||||||
|
* **core:** export defaultKeyValueDiffers to private api ([#24319](https://github.com/angular/angular/issues/24319)) ([92b278c](https://github.com/angular/angular/commit/92b278c))
|
||||||
|
* **core:** expose a Compiler API for accessing module ids from NgModule types ([#24258](https://github.com/angular/angular/issues/24258)) ([bd02b27](https://github.com/angular/angular/commit/bd02b27))
|
||||||
|
* **core:** KeyValueDiffer#diff allows null values ([#24319](https://github.com/angular/angular/issues/24319)) ([52ce9d5](https://github.com/angular/angular/commit/52ce9d5))
|
||||||
<a name="6.0.7"></a>
|
* **ivy:** a generic visitor which allows prefixing nodes for ngtsc ([#24230](https://github.com/angular/angular/issues/24230)) ([ca79e11](https://github.com/angular/angular/commit/ca79e11))
|
||||||
## [6.0.7](https://github.com/angular/angular/compare/6.0.6...6.0.7) (2018-06-27)
|
* **ivy:** add support of ApplicationRef.bootstrapModuleFactory ([#23811](https://github.com/angular/angular/issues/23811)) ([e3759f7](https://github.com/angular/angular/commit/e3759f7))
|
||||||
|
* **ivy:** namespaced attributes added to output instructions ([#24386](https://github.com/angular/angular/issues/24386)) ([82c5313](https://github.com/angular/angular/commit/82c5313))
|
||||||
|
* **ivy:** now supports SVG and MathML elements ([#24377](https://github.com/angular/angular/issues/24377)) ([8c1ac28](https://github.com/angular/angular/commit/8c1ac28))
|
||||||
### Bug Fixes
|
* **router:** implement scrolling restoration service ([#20030](https://github.com/angular/angular/issues/20030)) ([49c5234](https://github.com/angular/angular/commit/49c5234)), closes [#13636](https://github.com/angular/angular/issues/13636) [#10929](https://github.com/angular/angular/issues/10929) [#7791](https://github.com/angular/angular/issues/7791) [#6595](https://github.com/angular/angular/issues/6595)
|
||||||
|
|
||||||
* **animations:** set animations styles properly on platform-server ([#24624](https://github.com/angular/angular/issues/24624)) ([0b356d4](https://github.com/angular/angular/commit/0b356d4))
|
|
||||||
* **common:** use correct ICU plural for locale mk ([#24659](https://github.com/angular/angular/issues/24659)) ([64a8584](https://github.com/angular/angular/commit/64a8584))
|
|
||||||
|
|
||||||
|
|
||||||
<a name="6.0.6"></a>
|
|
||||||
## [6.0.6](https://github.com/angular/angular/compare/6.0.5...6.0.6) (2018-06-20)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* **compiler:** support `.` in import statements. ([#20634](https://github.com/angular/angular/issues/20634)) ([e543c73](https://github.com/angular/angular/commit/e543c73)), closes [#20363](https://github.com/angular/angular/issues/20363)
|
|
||||||
* **core:** Injector correctly honors the @Self flag ([#24520](https://github.com/angular/angular/issues/24520)) ([f5b3661](https://github.com/angular/angular/commit/f5b3661))
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<a name="6.0.5"></a>
|
<a name="6.0.5"></a>
|
||||||
## [6.0.5](https://github.com/angular/angular/compare/6.0.4...6.0.5) (2018-06-13)
|
## [6.0.5](https://github.com/angular/angular/compare/6.0.4...6.0.5) (2018-06-13)
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* **animations:** always render end-state styles for orphaned DOM nodes ([#24236](https://github.com/angular/angular/issues/24236)) ([0139173](https://github.com/angular/angular/commit/0139173))
|
* **animations:** always render end-state styles for orphaned DOM nodes ([#24236](https://github.com/angular/angular/issues/24236)) ([0139173](https://github.com/angular/angular/commit/0139173))
|
||||||
* **bazel:** Allow ng_module to depend on targets w no deps ([#24446](https://github.com/angular/angular/issues/24446)) ([ea3669e](https://github.com/angular/angular/commit/ea3669e))
|
* **bazel:** Allow ng_module to depend on targets w no deps ([#24446](https://github.com/angular/angular/issues/24446)) ([ea3669e](https://github.com/angular/angular/commit/ea3669e))
|
||||||
* **docs-infra:** use script nomodule to load IE polyfills, skip other polyfills ([#24317](https://github.com/angular/angular/issues/24317)) ([e876535](https://github.com/angular/angular/commit/e876535)), closes [#23647](https://github.com/angular/angular/issues/23647)
|
* **docs-infra:** use script nomodule to load IE polyfills, skip other polyfills ([#24317](https://github.com/angular/angular/issues/24317)) ([e876535](https://github.com/angular/angular/commit/e876535)), closes [#23647](https://github.com/angular/angular/issues/23647)
|
||||||
@ -65,6 +40,51 @@
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<a name="6.1.0-beta.0"></a>
|
||||||
|
## [6.1.0-beta.0](https://github.com/angular/angular/compare/6.0.0-rc.5...6.1.0-beta.0) (2018-06-06)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **animations:** do not throw errors when a destroyed component is animated ([#23836](https://github.com/angular/angular/issues/23836)) ([d2a8687](https://github.com/angular/angular/commit/d2a8687))
|
||||||
|
* **animations:** Fix browser detection logic ([#24188](https://github.com/angular/angular/issues/24188)) ([b492b9e](https://github.com/angular/angular/commit/b492b9e))
|
||||||
|
* **animations:** properly clean up queried element styles in safari/edge ([#23633](https://github.com/angular/angular/issues/23633)) ([da9ff25](https://github.com/angular/angular/commit/da9ff25))
|
||||||
|
* **animations:** retain state styling for nodes that are moved around ([#23534](https://github.com/angular/angular/issues/23534)) ([65211f4](https://github.com/angular/angular/commit/65211f4))
|
||||||
|
* **animations:** retain trigger-state for nodes that are moved around ([#24238](https://github.com/angular/angular/issues/24238)) ([8db928d](https://github.com/angular/angular/commit/8db928d))
|
||||||
|
* **benchpress:** Fix promise chain in chrome_driver_extension. ([#23458](https://github.com/angular/angular/issues/23458)) ([d4b6c41](https://github.com/angular/angular/commit/d4b6c41))
|
||||||
|
* **compiler:** avoid a crash in ngc-wrapped. ([#23468](https://github.com/angular/angular/issues/23468)) ([e1c4930](https://github.com/angular/angular/commit/e1c4930))
|
||||||
|
* **compiler:** generate constant array for i18n attributes ([#23837](https://github.com/angular/angular/issues/23837)) ([cfde36d](https://github.com/angular/angular/commit/cfde36d))
|
||||||
|
* **compiler:** generate core-compliant hostBindings property ([#24087](https://github.com/angular/angular/issues/24087)) ([01b5acd](https://github.com/angular/angular/commit/01b5acd)), closes [#24013](https://github.com/angular/angular/issues/24013)
|
||||||
|
* **compiler:** handle undefined annotation metadata ([#23349](https://github.com/angular/angular/issues/23349)) ([ca776c5](https://github.com/angular/angular/commit/ca776c5))
|
||||||
|
* **compiler-cli:** don't rely on incompatible TS method ([#23550](https://github.com/angular/angular/issues/23550)) ([b1f040f](https://github.com/angular/angular/commit/b1f040f))
|
||||||
|
* **core:** avoid eager providers re-initialization ([#23559](https://github.com/angular/angular/issues/23559)) ([0c6dc45](https://github.com/angular/angular/commit/0c6dc45))
|
||||||
|
* **core:** call ngOnDestroy on all services that have it ([#23755](https://github.com/angular/angular/issues/23755)) ([fc03427](https://github.com/angular/angular/commit/fc03427)), closes [#22466](https://github.com/angular/angular/issues/22466) [#22240](https://github.com/angular/angular/issues/22240) [#14818](https://github.com/angular/angular/issues/14818)
|
||||||
|
* **elements:** always check to create strategy ([#23825](https://github.com/angular/angular/issues/23825)) ([b1cda36](https://github.com/angular/angular/commit/b1cda36))
|
||||||
|
* **elements:** prevent closure renaming of platform properties ([#23843](https://github.com/angular/angular/issues/23843)) ([d4b8b24](https://github.com/angular/angular/commit/d4b8b24))
|
||||||
|
* **forms:** properly handle special properties in FormGroup.get ([#22249](https://github.com/angular/angular/issues/22249)) ([9367e91](https://github.com/angular/angular/commit/9367e91)), closes [#17195](https://github.com/angular/angular/issues/17195)
|
||||||
|
* **platform-server:** avoid clash between server and client style encapsulation attributes ([#24158](https://github.com/angular/angular/issues/24158)) ([b96a3c8](https://github.com/angular/angular/commit/b96a3c8))
|
||||||
|
* **platform-server:** avoid dependency cycle when using http interceptor ([#24229](https://github.com/angular/angular/issues/24229)) ([60aa943](https://github.com/angular/angular/commit/60aa943)), closes [#23023](https://github.com/angular/angular/issues/23023)
|
||||||
|
* **platform-server:** don't reflect innerHTML property to attibute ([#24213](https://github.com/angular/angular/issues/24213)) ([6a663a4](https://github.com/angular/angular/commit/6a663a4)), closes [#19278](https://github.com/angular/angular/issues/19278)
|
||||||
|
* **platform-server:** provide Domino DOM types globally ([#24116](https://github.com/angular/angular/issues/24116)) ([c73196e](https://github.com/angular/angular/commit/c73196e)), closes [#23280](https://github.com/angular/angular/issues/23280) [#23133](https://github.com/angular/angular/issues/23133)
|
||||||
|
* **router:** avoid freezing queryParams in-place ([#22663](https://github.com/angular/angular/issues/22663)) ([89f64e5](https://github.com/angular/angular/commit/89f64e5)), closes [#22617](https://github.com/angular/angular/issues/22617)
|
||||||
|
* **router:** cache route handle if found ([#22475](https://github.com/angular/angular/issues/22475)) ([4cfa571](https://github.com/angular/angular/commit/4cfa571)), closes [#22474](https://github.com/angular/angular/issues/22474)
|
||||||
|
* **router:** correct the segment parsing so it won't break on ampersand ([#23684](https://github.com/angular/angular/issues/23684)) ([553a680](https://github.com/angular/angular/commit/553a680))
|
||||||
|
* **service-worker:** add badge to NOTIFICATION_OPTION_NAMES ([#23241](https://github.com/angular/angular/issues/23241)) ([fb59b2d](https://github.com/angular/angular/commit/fb59b2d)), closes [#23196](https://github.com/angular/angular/issues/23196)
|
||||||
|
* **service-worker:** check platformBrowser before accessing navigator.serviceWorker ([#21231](https://github.com/angular/angular/issues/21231)) ([0bdd30e](https://github.com/angular/angular/commit/0bdd30e))
|
||||||
|
* **service-worker:** correctly handle requests with empty `clientId` ([#23625](https://github.com/angular/angular/issues/23625)) ([e0ed59e](https://github.com/angular/angular/commit/e0ed59e)), closes [#23526](https://github.com/angular/angular/issues/23526)
|
||||||
|
* **service-worker:** deprecate `versionedFiles` in asset-group resources ([#23584](https://github.com/angular/angular/issues/23584)) ([1d378e2](https://github.com/angular/angular/commit/1d378e2))
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **compiler:** support `// ...` and `// TODO` in mock compiler expectations ([#23441](https://github.com/angular/angular/issues/23441)) ([c6b206e](https://github.com/angular/angular/commit/c6b206e))
|
||||||
|
* **compiler-cli:** update `tsickle` to `0.29.x` ([#24233](https://github.com/angular/angular/issues/24233)) ([f69ac67](https://github.com/angular/angular/commit/f69ac67))
|
||||||
|
* **platform-browser:** add HammerJS lazy-loader symbols to public API ([#23943](https://github.com/angular/angular/issues/23943)) ([26fbf1d](https://github.com/angular/angular/commit/26fbf1d))
|
||||||
|
* **platform-browser:** allow lazy-loading HammerJS ([#23906](https://github.com/angular/angular/issues/23906)) ([313bdce](https://github.com/angular/angular/commit/313bdce))
|
||||||
|
* **platform-server:** use EventManagerPlugin on the server ([#24132](https://github.com/angular/angular/issues/24132)) ([d6595eb](https://github.com/angular/angular/commit/d6595eb))
|
||||||
|
* **router:** add navigation execution context info to activation hooks ([#24204](https://github.com/angular/angular/issues/24204)) ([20c463e](https://github.com/angular/angular/commit/20c463e)), closes [#24202](https://github.com/angular/angular/issues/24202)
|
||||||
|
|
||||||
|
|
||||||
<a name="6.0.4"></a>
|
<a name="6.0.4"></a>
|
||||||
## [6.0.4](https://github.com/angular/angular/compare/6.0.3...6.0.4) (2018-06-06)
|
## [6.0.4](https://github.com/angular/angular/compare/6.0.3...6.0.4) (2018-06-06)
|
||||||
|
|
||||||
|
14
WORKSPACE
14
WORKSPACE
@ -6,9 +6,9 @@ workspace(name = "angular")
|
|||||||
|
|
||||||
http_archive(
|
http_archive(
|
||||||
name = "build_bazel_rules_nodejs",
|
name = "build_bazel_rules_nodejs",
|
||||||
url = "https://github.com/bazelbuild/rules_nodejs/archive/0.10.1.zip",
|
url = "https://github.com/bazelbuild/rules_nodejs/archive/0.9.1.zip",
|
||||||
strip_prefix = "rules_nodejs-0.10.1",
|
strip_prefix = "rules_nodejs-0.9.1",
|
||||||
sha256 = "634206524d90dc03c52392fa3f19a16637d2bcf154910436fe1d669a0d9d7b9c",
|
sha256 = "6139762b62b37c1fd171d7f22aa39566cb7dc2916f0f801d505a9aaf118c117f",
|
||||||
)
|
)
|
||||||
|
|
||||||
http_archive(
|
http_archive(
|
||||||
@ -20,9 +20,9 @@ http_archive(
|
|||||||
|
|
||||||
http_archive(
|
http_archive(
|
||||||
name = "build_bazel_rules_typescript",
|
name = "build_bazel_rules_typescript",
|
||||||
url = "https://github.com/bazelbuild/rules_typescript/archive/0.15.3.zip",
|
url = "https://github.com/bazelbuild/rules_typescript/archive/0.15.0.zip",
|
||||||
strip_prefix = "rules_typescript-0.15.3",
|
strip_prefix = "rules_typescript-0.15.0",
|
||||||
sha256 = "a2b26ac3fc13036011196063db1bf7f1eae81334449201dc28087ebfa3708c99",
|
sha256 = "1aa75917330b820cb239b3c10a936a10f0a46fe215063d4492dd76dc6e1616f4",
|
||||||
)
|
)
|
||||||
|
|
||||||
http_archive(
|
http_archive(
|
||||||
@ -77,7 +77,7 @@ http_archive(
|
|||||||
|
|
||||||
load("@build_bazel_rules_nodejs//:defs.bzl", "check_bazel_version", "node_repositories", "yarn_install")
|
load("@build_bazel_rules_nodejs//:defs.bzl", "check_bazel_version", "node_repositories", "yarn_install")
|
||||||
|
|
||||||
check_bazel_version("0.15.0")
|
check_bazel_version("0.14.0")
|
||||||
node_repositories(package_json = ["//:package.json"])
|
node_repositories(package_json = ["//:package.json"])
|
||||||
|
|
||||||
load("@io_bazel_rules_go//go:def.bzl", "go_rules_dependencies", "go_register_toolchains")
|
load("@io_bazel_rules_go//go:def.bzl", "go_rules_dependencies", "go_register_toolchains")
|
||||||
|
2
aio/content/examples/.gitignore
vendored
2
aio/content/examples/.gitignore
vendored
@ -60,8 +60,6 @@ dist/
|
|||||||
!rollup-config.js
|
!rollup-config.js
|
||||||
aot-compiler/**/*.d.ts
|
aot-compiler/**/*.d.ts
|
||||||
aot-compiler/**/*.factory.d.ts
|
aot-compiler/**/*.factory.d.ts
|
||||||
upgrade-phonecat-2-hybrid/aot/**/*
|
|
||||||
!upgrade-phonecat-2-hybrid/aot/index.html
|
|
||||||
|
|
||||||
# i18n
|
# i18n
|
||||||
!i18n/src/systemjs-text-plugin.js
|
!i18n/src/systemjs-text-plugin.js
|
||||||
|
@ -40,7 +40,5 @@ export class HighlightDirective {
|
|||||||
// #docregion color-2
|
// #docregion color-2
|
||||||
@Input() appHighlight: string;
|
@Input() appHighlight: string;
|
||||||
// #enddocregion color-2
|
// #enddocregion color-2
|
||||||
|
|
||||||
// #docregion
|
|
||||||
}
|
}
|
||||||
// #enddocregion
|
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
import { BrowserModule } from '@angular/platform-browser';
|
import { BrowserModule } from '@angular/platform-browser';
|
||||||
import { NgModule } from '@angular/core';
|
import { NgModule } from '@angular/core';
|
||||||
import { FormsModule } from '@angular/forms';
|
import { FormsModule } from '@angular/forms';
|
||||||
import { HttpClientModule } from '@angular/common/http';
|
import { HttpModule } from '@angular/http';
|
||||||
|
|
||||||
import { AppComponent } from './app.component';
|
import { AppComponent } from './app.component';
|
||||||
// #docregion directive-import
|
// #docregion directive-import
|
||||||
@ -24,7 +24,7 @@ import { ItemDirective } from './item.directive';
|
|||||||
imports: [
|
imports: [
|
||||||
BrowserModule,
|
BrowserModule,
|
||||||
FormsModule,
|
FormsModule,
|
||||||
HttpClientModule
|
HttpModule
|
||||||
],
|
],
|
||||||
providers: [],
|
providers: [],
|
||||||
bootstrap: [AppComponent]
|
bootstrap: [AppComponent]
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
// #docregion
|
// #docregion
|
||||||
import { BrowserModule } from '@angular/platform-browser';
|
import { BrowserModule } from '@angular/platform-browser';
|
||||||
import { FormsModule } from '@angular/forms';
|
import { FormsModule } from '@angular/forms';
|
||||||
import { HttpClientModule } from '@angular/common/http';
|
import { HttpModule } from '@angular/http';
|
||||||
|
|
||||||
// import { AppRoutingModule } from './app-routing.module';
|
// import { AppRoutingModule } from './app-routing.module';
|
||||||
import { LocationStrategy,
|
import { LocationStrategy,
|
||||||
@ -54,7 +54,7 @@ const c_components = [
|
|||||||
imports: [
|
imports: [
|
||||||
BrowserModule,
|
BrowserModule,
|
||||||
FormsModule,
|
FormsModule,
|
||||||
HttpClientModule,
|
HttpModule,
|
||||||
InMemoryWebApiModule.forRoot(HeroData)
|
InMemoryWebApiModule.forRoot(HeroData)
|
||||||
// AppRoutingModule TODO: add routes
|
// AppRoutingModule TODO: add routes
|
||||||
],
|
],
|
||||||
|
@ -1,69 +0,0 @@
|
|||||||
'use strict'; // necessary for es6 output in node
|
|
||||||
|
|
||||||
import { browser, by, element } from 'protractor';
|
|
||||||
|
|
||||||
/* tslint:disable:quotemark */
|
|
||||||
describe('Elements', () => {
|
|
||||||
const messageInput = element(by.css('input'));
|
|
||||||
const popupButtons = element.all(by.css('button'));
|
|
||||||
|
|
||||||
beforeEach(() => browser.get(''));
|
|
||||||
|
|
||||||
describe('popup component', () => {
|
|
||||||
const popupComponentButton = popupButtons.get(0);
|
|
||||||
const popupComponent = element(by.css('popup-component'));
|
|
||||||
const closeButton = popupComponent.element(by.css('button'));
|
|
||||||
|
|
||||||
it('should be displayed on button click', () => {
|
|
||||||
expect(popupComponent.isPresent()).toBe(false);
|
|
||||||
|
|
||||||
popupComponentButton.click();
|
|
||||||
expect(popupComponent.isPresent()).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should display the specified message', () => {
|
|
||||||
messageInput.clear();
|
|
||||||
messageInput.sendKeys('Angular rocks!');
|
|
||||||
|
|
||||||
popupComponentButton.click();
|
|
||||||
expect(popupComponent.getText()).toContain('Popup: Angular rocks!');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should be closed on "close" button click', () => {
|
|
||||||
popupComponentButton.click();
|
|
||||||
expect(popupComponent.isPresent()).toBe(true);
|
|
||||||
|
|
||||||
closeButton.click();
|
|
||||||
expect(popupComponent.isPresent()).toBe(false);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('popup element', () => {
|
|
||||||
const popupElementButton = popupButtons.get(1);
|
|
||||||
const popupElement = element(by.css('popup-element'));
|
|
||||||
const closeButton = popupElement.element(by.css('button'));
|
|
||||||
|
|
||||||
it('should be displayed on button click', () => {
|
|
||||||
expect(popupElement.isPresent()).toBe(false);
|
|
||||||
|
|
||||||
popupElementButton.click();
|
|
||||||
expect(popupElement.isPresent()).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should display the specified message', () => {
|
|
||||||
messageInput.clear();
|
|
||||||
messageInput.sendKeys('Angular rocks!');
|
|
||||||
|
|
||||||
popupElementButton.click();
|
|
||||||
expect(popupElement.getText()).toContain('Popup: Angular rocks!');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should be closed on "close" button click', () => {
|
|
||||||
popupElementButton.click();
|
|
||||||
expect(popupElement.isPresent()).toBe(true);
|
|
||||||
|
|
||||||
closeButton.click();
|
|
||||||
expect(popupElement.isPresent()).toBe(false);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
@ -1,3 +0,0 @@
|
|||||||
{
|
|
||||||
"projectType": "elements"
|
|
||||||
}
|
|
@ -1,5 +1,6 @@
|
|||||||
|
// #docregion
|
||||||
import { Component, Injector } from '@angular/core';
|
import { Component, Injector } from '@angular/core';
|
||||||
import { createCustomElement } from '@angular/elements';
|
import { createNgElementConstructor } from '../elements-dist';
|
||||||
import { PopupService } from './popup.service';
|
import { PopupService } from './popup.service';
|
||||||
import { PopupComponent } from './popup.component';
|
import { PopupComponent } from './popup.component';
|
||||||
|
|
||||||
@ -7,15 +8,19 @@ import { PopupComponent } from './popup.component';
|
|||||||
selector: 'app-root',
|
selector: 'app-root',
|
||||||
template: `
|
template: `
|
||||||
<input #input value="Message">
|
<input #input value="Message">
|
||||||
<button (click)="popup.showAsComponent(input.value)">Show as component</button>
|
<button (click)="popup.showAsComponent(input.value)">
|
||||||
<button (click)="popup.showAsElement(input.value)">Show as element</button>
|
Show as component </button>
|
||||||
`,
|
<button (click)="popup.showAsElement(input.value)">
|
||||||
|
Show as element </button>
|
||||||
|
`
|
||||||
})
|
})
|
||||||
|
|
||||||
export class AppComponent {
|
export class AppComponent {
|
||||||
constructor(injector: Injector, public popup: PopupService) {
|
constructor(private injector: Injector, public popup: PopupService) {
|
||||||
// Convert `PopupComponent` to a custom element.
|
// on init, convert PopupComponent to a custom element
|
||||||
const PopupElement = createCustomElement(PopupComponent, {injector});
|
const PopupElement =
|
||||||
// Register the custom element with the browser.
|
createNgElementConstructor(PopupComponent, {injector: this.injector});
|
||||||
customElements.define('popup-element', PopupElement);
|
// register the custom element with the browser.
|
||||||
|
customElements.define('popup-element', PopupElement);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,21 +1,22 @@
|
|||||||
import { NgModule } from '@angular/core';
|
// #docregion
|
||||||
import { BrowserModule } from '@angular/platform-browser';
|
import { BrowserModule } from '@angular/platform-browser';
|
||||||
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
|
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
|
||||||
|
import { NgModule } from '@angular/core';
|
||||||
|
|
||||||
import { AppComponent } from './app.component';
|
import { AppComponent } from './app.component';
|
||||||
import { PopupComponent } from './popup.component';
|
|
||||||
import { PopupService } from './popup.service';
|
import { PopupService } from './popup.service';
|
||||||
|
import { PopupComponent } from './popup.component';
|
||||||
|
|
||||||
// Include the `PopupService` provider,
|
// include the PopupService provider,
|
||||||
// but exclude `PopupComponent` from compilation,
|
// but exclude PopupComponent from compilation,
|
||||||
// because it will be added dynamically.
|
// because it will be added dynamically
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
|
declarations: [AppComponent, PopupComponent],
|
||||||
imports: [BrowserModule, BrowserAnimationsModule],
|
imports: [BrowserModule, BrowserAnimationsModule],
|
||||||
providers: [PopupService],
|
providers: [PopupService],
|
||||||
declarations: [AppComponent, PopupComponent],
|
|
||||||
bootstrap: [AppComponent],
|
bootstrap: [AppComponent],
|
||||||
entryComponents: [PopupComponent],
|
entryComponents: [PopupComponent],
|
||||||
})
|
})
|
||||||
export class AppModule {
|
|
||||||
}
|
export class AppModule {}
|
||||||
|
@ -1,14 +1,14 @@
|
|||||||
|
// #docregion
|
||||||
import { Component, EventEmitter, Input, Output } from '@angular/core';
|
import { Component, EventEmitter, Input, Output } from '@angular/core';
|
||||||
|
import { AnimationEvent } from '@angular/animations';
|
||||||
import { animate, state, style, transition, trigger } from '@angular/animations';
|
import { animate, state, style, transition, trigger } from '@angular/animations';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'my-popup',
|
selector: 'my-popup',
|
||||||
template: `
|
template: 'Popup: {{message}}',
|
||||||
<span>Popup: {{message}}</span>
|
|
||||||
<button (click)="closed.next()">✖</button>
|
|
||||||
`,
|
|
||||||
host: {
|
host: {
|
||||||
'[@state]': 'state',
|
'[@state]': 'state',
|
||||||
|
'(@state.done)': 'onAnimationDone($event)',
|
||||||
},
|
},
|
||||||
animations: [
|
animations: [
|
||||||
trigger('state', [
|
trigger('state', [
|
||||||
@ -27,17 +27,13 @@ import { animate, state, style, transition, trigger } from '@angular/animations'
|
|||||||
height: 48px;
|
height: 48px;
|
||||||
padding: 16px;
|
padding: 16px;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
align-items: center;
|
||||||
border-top: 1px solid black;
|
border-top: 1px solid black;
|
||||||
font-size: 24px;
|
font-size: 24px;
|
||||||
}
|
}
|
||||||
|
|
||||||
button {
|
|
||||||
border-radius: 50%;
|
|
||||||
}
|
|
||||||
`]
|
`]
|
||||||
})
|
})
|
||||||
|
|
||||||
export class PopupComponent {
|
export class PopupComponent {
|
||||||
private state: 'opened' | 'closed' = 'closed';
|
private state: 'opened' | 'closed' = 'closed';
|
||||||
|
|
||||||
@ -45,10 +41,18 @@ export class PopupComponent {
|
|||||||
set message(message: string) {
|
set message(message: string) {
|
||||||
this._message = message;
|
this._message = message;
|
||||||
this.state = 'opened';
|
this.state = 'opened';
|
||||||
|
|
||||||
|
setTimeout(() => this.state = 'closed', 2000);
|
||||||
}
|
}
|
||||||
get message(): string { return this._message; }
|
get message(): string { return this._message; }
|
||||||
_message: string;
|
_message: string;
|
||||||
|
|
||||||
@Output()
|
@Output()
|
||||||
closed = new EventEmitter();
|
closed = new EventEmitter();
|
||||||
|
|
||||||
|
onAnimationDone(e: AnimationEvent) {
|
||||||
|
if (e.toState === 'closed') {
|
||||||
|
this.closed.next();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
|
|
||||||
|
// #docregion
|
||||||
import { ApplicationRef, ComponentFactoryResolver, Injectable, Injector } from '@angular/core';
|
import { ApplicationRef, ComponentFactoryResolver, Injectable, Injector } from '@angular/core';
|
||||||
import { NgElement, WithProperties } from '@angular/elements';
|
|
||||||
import { PopupComponent } from './popup.component';
|
|
||||||
|
|
||||||
|
import { PopupComponent } from './popup.component';
|
||||||
|
import { NgElementConstructor } from '../elements-dist';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class PopupService {
|
export class PopupService {
|
||||||
@ -39,7 +40,7 @@ export class PopupService {
|
|||||||
// This uses the new custom-element method to add the popup to the DOM.
|
// This uses the new custom-element method to add the popup to the DOM.
|
||||||
showAsElement(message: string) {
|
showAsElement(message: string) {
|
||||||
// Create element
|
// Create element
|
||||||
const popupEl: NgElement & WithProperties<PopupComponent> = document.createElement('popup-element') as any;
|
const popupEl = document.createElement('popup-element');
|
||||||
|
|
||||||
// Listen to the close event
|
// Listen to the close event
|
||||||
popupEl.addEventListener('closed', () => document.body.removeChild(popupEl));
|
popupEl.addEventListener('closed', () => document.body.removeChild(popupEl));
|
||||||
|
@ -1,12 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<base href="/">
|
|
||||||
<title>Elements</title>
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<app-root></app-root>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
@ -16,7 +16,6 @@ describe('Form Validation Tests', function () {
|
|||||||
|
|
||||||
tests('Template-Driven Form');
|
tests('Template-Driven Form');
|
||||||
bobTests();
|
bobTests();
|
||||||
crossValidationTests();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('Reactive form', () => {
|
describe('Reactive form', () => {
|
||||||
@ -26,7 +25,6 @@ describe('Form Validation Tests', function () {
|
|||||||
|
|
||||||
tests('Reactive Form');
|
tests('Reactive Form');
|
||||||
bobTests();
|
bobTests();
|
||||||
crossValidationTests();
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -44,8 +42,7 @@ let page: {
|
|||||||
powerOption: ElementFinder,
|
powerOption: ElementFinder,
|
||||||
errorMessages: ElementArrayFinder,
|
errorMessages: ElementArrayFinder,
|
||||||
heroFormButtons: ElementArrayFinder,
|
heroFormButtons: ElementArrayFinder,
|
||||||
heroSubmitted: ElementFinder,
|
heroSubmitted: ElementFinder
|
||||||
crossValidationErrorMessage: ElementFinder,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
function getPage(sectionTag: string) {
|
function getPage(sectionTag: string) {
|
||||||
@ -62,8 +59,7 @@ function getPage(sectionTag: string) {
|
|||||||
powerOption: section.element(by.css('#power option')),
|
powerOption: section.element(by.css('#power option')),
|
||||||
errorMessages: section.all(by.css('div.alert')),
|
errorMessages: section.all(by.css('div.alert')),
|
||||||
heroFormButtons: buttons,
|
heroFormButtons: buttons,
|
||||||
heroSubmitted: section.element(by.css('.submitted-message')),
|
heroSubmitted: section.element(by.css('.submitted-message'))
|
||||||
crossValidationErrorMessage: section.element(by.css('.cross-validation-error-message')),
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -176,29 +172,3 @@ function bobTests() {
|
|||||||
expectFormIsValid();
|
expectFormIsValid();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function crossValidationTests() {
|
|
||||||
const emsg = 'Name cannot match alter ego.';
|
|
||||||
|
|
||||||
it(`should produce "${emsg}" error after setting name and alter ego to the same value`, function () {
|
|
||||||
page.nameInput.clear();
|
|
||||||
page.nameInput.sendKeys('Batman');
|
|
||||||
|
|
||||||
page.alterEgoInput.clear();
|
|
||||||
page.alterEgoInput.sendKeys('Batman');
|
|
||||||
|
|
||||||
expectFormIsInvalid();
|
|
||||||
expect(page.crossValidationErrorMessage.getText()).toBe(emsg);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should be ok again with different values', function () {
|
|
||||||
page.nameInput.clear();
|
|
||||||
page.nameInput.sendKeys('Batman');
|
|
||||||
|
|
||||||
page.alterEgoInput.clear();
|
|
||||||
page.alterEgoInput.sendKeys('Superman');
|
|
||||||
|
|
||||||
expectFormIsValid();
|
|
||||||
expect(page.crossValidationErrorMessage.isPresent()).toBe(false);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
@ -7,7 +7,7 @@ import { AppComponent } from './app.component';
|
|||||||
import { HeroFormTemplateComponent } from './template/hero-form-template.component';
|
import { HeroFormTemplateComponent } from './template/hero-form-template.component';
|
||||||
import { HeroFormReactiveComponent } from './reactive/hero-form-reactive.component';
|
import { HeroFormReactiveComponent } from './reactive/hero-form-reactive.component';
|
||||||
import { ForbiddenValidatorDirective } from './shared/forbidden-name.directive';
|
import { ForbiddenValidatorDirective } from './shared/forbidden-name.directive';
|
||||||
import { IdentityRevealedValidatorDirective } from './shared/identity-revealed.directive';
|
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [
|
imports: [
|
||||||
@ -19,8 +19,7 @@ import { IdentityRevealedValidatorDirective } from './shared/identity-revealed.d
|
|||||||
AppComponent,
|
AppComponent,
|
||||||
HeroFormTemplateComponent,
|
HeroFormTemplateComponent,
|
||||||
HeroFormReactiveComponent,
|
HeroFormReactiveComponent,
|
||||||
ForbiddenValidatorDirective,
|
ForbiddenValidatorDirective
|
||||||
IdentityRevealedValidatorDirective
|
|
||||||
],
|
],
|
||||||
bootstrap: [ AppComponent ]
|
bootstrap: [ AppComponent ]
|
||||||
})
|
})
|
||||||
|
@ -1,42 +0,0 @@
|
|||||||
/* tslint:disable: member-ordering forin */
|
|
||||||
// #docplaster
|
|
||||||
// #docregion
|
|
||||||
import { Component, OnInit } from '@angular/core';
|
|
||||||
import { FormControl, FormGroup, Validators } from '@angular/forms';
|
|
||||||
import { forbiddenNameValidator } from '../shared/forbidden-name.directive';
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'app-hero-form-reactive',
|
|
||||||
templateUrl: './hero-form-reactive.component.html',
|
|
||||||
styleUrls: ['./hero-form-reactive.component.css'],
|
|
||||||
})
|
|
||||||
export class HeroFormReactiveComponent implements OnInit {
|
|
||||||
|
|
||||||
powers = ['Really Smart', 'Super Flexible', 'Weather Changer'];
|
|
||||||
|
|
||||||
hero = {name: 'Dr.', alterEgo: 'Dr. What', power: this.powers[0]};
|
|
||||||
|
|
||||||
heroForm: FormGroup;
|
|
||||||
|
|
||||||
// #docregion form-group
|
|
||||||
ngOnInit(): void {
|
|
||||||
// #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)
|
|
||||||
});
|
|
||||||
// #enddocregion custom-validator
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
get name() { return this.heroForm.get('name'); }
|
|
||||||
|
|
||||||
get power() { return this.heroForm.get('power'); }
|
|
||||||
// #enddocregion form-group
|
|
||||||
}
|
|
||||||
// #enddocregion
|
|
@ -1,5 +0,0 @@
|
|||||||
/* #docregion cross-validation-error-css */
|
|
||||||
.cross-validation-error input {
|
|
||||||
border-left: 5px solid red;
|
|
||||||
}
|
|
||||||
/* #enddocregion cross-validation-error-css */
|
|
@ -7,41 +7,33 @@
|
|||||||
|
|
||||||
<div [hidden]="formDir.submitted">
|
<div [hidden]="formDir.submitted">
|
||||||
|
|
||||||
<div class="cross-validation" [class.cross-validation-error]="heroForm.errors?.identityRevealed && (heroForm.touched || heroForm.dirty)">
|
<div class="form-group">
|
||||||
<div class="form-group">
|
|
||||||
|
|
||||||
<label for="name">Name</label>
|
<label for="name">Name</label>
|
||||||
<!-- #docregion name-with-error-msg -->
|
<!-- #docregion name-with-error-msg -->
|
||||||
<input id="name" class="form-control"
|
<input id="name" class="form-control"
|
||||||
formControlName="name" required >
|
formControlName="name" required >
|
||||||
|
|
||||||
<div *ngIf="name.invalid && (name.dirty || name.touched)"
|
<div *ngIf="name.invalid && (name.dirty || name.touched)"
|
||||||
class="alert alert-danger">
|
class="alert alert-danger">
|
||||||
|
|
||||||
<div *ngIf="name.errors.required">
|
<div *ngIf="name.errors.required">
|
||||||
Name is required.
|
Name is required.
|
||||||
</div>
|
</div>
|
||||||
<div *ngIf="name.errors.minlength">
|
<div *ngIf="name.errors.minlength">
|
||||||
Name must be at least 4 characters long.
|
Name must be at least 4 characters long.
|
||||||
</div>
|
</div>
|
||||||
<div *ngIf="name.errors.forbiddenName">
|
<div *ngIf="name.errors.forbiddenName">
|
||||||
Name cannot be Bob.
|
Name cannot be Bob.
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<!-- #enddocregion name-with-error-msg -->
|
|
||||||
</div>
|
</div>
|
||||||
|
<!-- #enddocregion name-with-error-msg -->
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="alterEgo">Alter Ego</label>
|
<label for="alterEgo">Alter Ego</label>
|
||||||
<input id="alterEgo" class="form-control"
|
<input id="alterEgo" class="form-control"
|
||||||
formControlName="alterEgo" >
|
formControlName="alterEgo" >
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- #docregion cross-validation-error-message -->
|
|
||||||
<div *ngIf="heroForm.errors?.identityRevealed && (heroForm.touched || heroForm.dirty)" class="cross-validation-error-message alert alert-danger">
|
|
||||||
Name cannot match alter ego.
|
|
||||||
</div>
|
|
||||||
<!-- #enddocregion cross-validation-error-message -->
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
|
@ -1,36 +1,40 @@
|
|||||||
/* tslint:disable: member-ordering forin */
|
/* tslint:disable: member-ordering forin */
|
||||||
|
// #docplaster
|
||||||
// #docregion
|
// #docregion
|
||||||
import { Component, OnInit } from '@angular/core';
|
import { Component, OnInit } from '@angular/core';
|
||||||
import { FormControl, FormGroup, Validators } from '@angular/forms';
|
import { FormControl, FormGroup, Validators } from '@angular/forms';
|
||||||
import { forbiddenNameValidator } from '../shared/forbidden-name.directive';
|
import { forbiddenNameValidator } from '../shared/forbidden-name.directive';
|
||||||
import { identityRevealedValidator } from '../shared/identity-revealed.directive';
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-hero-form-reactive',
|
selector: 'app-hero-form-reactive',
|
||||||
templateUrl: './hero-form-reactive.component.html',
|
templateUrl: './hero-form-reactive.component.html'
|
||||||
styleUrls: ['./hero-form-reactive.component.css'],
|
|
||||||
})
|
})
|
||||||
export class HeroFormReactiveComponent implements OnInit {
|
export class HeroFormReactiveComponent implements OnInit {
|
||||||
|
|
||||||
powers = ['Really Smart', 'Super Flexible', 'Weather Changer'];
|
powers = ['Really Smart', 'Super Flexible', 'Weather Changer'];
|
||||||
|
|
||||||
hero = { name: 'Dr.', alterEgo: 'Dr. What', power: this.powers[0] };
|
hero = {name: 'Dr.', alterEgo: 'Dr. What', power: this.powers[0]};
|
||||||
|
|
||||||
heroForm: FormGroup;
|
heroForm: FormGroup;
|
||||||
|
|
||||||
|
// #docregion form-group
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
|
// #docregion custom-validator
|
||||||
this.heroForm = new FormGroup({
|
this.heroForm = new FormGroup({
|
||||||
'name': new FormControl(this.hero.name, [
|
'name': new FormControl(this.hero.name, [
|
||||||
Validators.required,
|
Validators.required,
|
||||||
Validators.minLength(4),
|
Validators.minLength(4),
|
||||||
forbiddenNameValidator(/bob/i)
|
forbiddenNameValidator(/bob/i) // <-- Here's how you pass in the custom validator.
|
||||||
]),
|
]),
|
||||||
'alterEgo': new FormControl(this.hero.alterEgo),
|
'alterEgo': new FormControl(this.hero.alterEgo),
|
||||||
'power': new FormControl(this.hero.power, Validators.required)
|
'power': new FormControl(this.hero.power, Validators.required)
|
||||||
}, { validators: identityRevealedValidator }); // <-- add custom validator at the FormGroup level
|
});
|
||||||
|
// #enddocregion custom-validator
|
||||||
}
|
}
|
||||||
|
|
||||||
get name() { return this.heroForm.get('name'); }
|
get name() { return this.heroForm.get('name'); }
|
||||||
|
|
||||||
get power() { return this.heroForm.get('power'); }
|
get power() { return this.heroForm.get('power'); }
|
||||||
|
// #enddocregion form-group
|
||||||
}
|
}
|
||||||
|
// #enddocregion
|
||||||
|
@ -1,25 +0,0 @@
|
|||||||
// #docregion
|
|
||||||
import { Directive } from '@angular/core';
|
|
||||||
import { AbstractControl, FormGroup, NG_VALIDATORS, ValidationErrors, Validator, ValidatorFn } from '@angular/forms';
|
|
||||||
|
|
||||||
// #docregion cross-validation-validator
|
|
||||||
/** A hero's name can't match the hero's alter ego */
|
|
||||||
export const identityRevealedValidator: ValidatorFn = (control: FormGroup): ValidationErrors | null => {
|
|
||||||
const name = control.get('name');
|
|
||||||
const alterEgo = control.get('alterEgo');
|
|
||||||
|
|
||||||
return name && alterEgo && name.value === alterEgo.value ? { 'identityRevealed': true } : null;
|
|
||||||
};
|
|
||||||
// #enddocregion cross-validation-validator
|
|
||||||
|
|
||||||
// #docregion cross-validation-directive
|
|
||||||
@Directive({
|
|
||||||
selector: '[appIdentityRevealed]',
|
|
||||||
providers: [{ provide: NG_VALIDATORS, useExisting: IdentityRevealedValidatorDirective, multi: true }]
|
|
||||||
})
|
|
||||||
export class IdentityRevealedValidatorDirective implements Validator {
|
|
||||||
validate(control: AbstractControl): ValidationErrors {
|
|
||||||
return identityRevealedValidator(control)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// #enddocregion cross-validation-directive
|
|
@ -1,4 +0,0 @@
|
|||||||
/* #docregion */
|
|
||||||
.cross-validation-error input {
|
|
||||||
border-left: 5px solid red;
|
|
||||||
}
|
|
@ -2,48 +2,41 @@
|
|||||||
<div class="container">
|
<div class="container">
|
||||||
|
|
||||||
<h1>Template-Driven Form</h1>
|
<h1>Template-Driven Form</h1>
|
||||||
<!-- #docregion cross-validation-register-validator -->
|
<!-- #docregion form-tag-->
|
||||||
<form #heroForm="ngForm" appIdentityRevealed>
|
<form #heroForm="ngForm">
|
||||||
<!-- #enddocregion cross-validation-register-validator -->
|
<!-- #enddocregion form-tag-->
|
||||||
<div [hidden]="heroForm.submitted">
|
<div [hidden]="heroForm.submitted">
|
||||||
<div class="cross-validation" [class.cross-validation-error]="heroForm.errors?.identityRevealed && (heroForm.touched || heroForm.dirty)">
|
|
||||||
<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" appForbiddenName="bob"
|
|
||||||
[(ngModel)]="hero.name" #name="ngModel" >
|
|
||||||
<!-- #enddocregion name-input -->
|
|
||||||
|
|
||||||
<div *ngIf="name.invalid && (name.dirty || name.touched)"
|
<div class="form-group">
|
||||||
class="alert alert-danger">
|
<label for="name">Name</label>
|
||||||
|
<!-- #docregion name-with-error-msg -->
|
||||||
|
<!-- #docregion name-input -->
|
||||||
|
<input id="name" name="name" class="form-control"
|
||||||
|
required minlength="4" appForbiddenName="bob"
|
||||||
|
[(ngModel)]="hero.name" #name="ngModel" >
|
||||||
|
<!-- #enddocregion name-input -->
|
||||||
|
|
||||||
<div *ngIf="name.errors.required">
|
<div *ngIf="name.invalid && (name.dirty || name.touched)"
|
||||||
Name is required.
|
class="alert alert-danger">
|
||||||
</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 *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>
|
||||||
|
<!-- #enddocregion name-with-error-msg -->
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- #docregion cross-validation-error-message -->
|
<div class="form-group">
|
||||||
<div *ngIf="heroForm.errors?.identityRevealed && (heroForm.touched || heroForm.dirty)" class="cross-validation-error-message alert alert-danger">
|
<label for="alterEgo">Alter Ego</label>
|
||||||
Name cannot match alter ego.
|
<input id="alterEgo" class="form-control"
|
||||||
</div>
|
name="alterEgo" [(ngModel)]="hero.alterEgo" >
|
||||||
<!-- #enddocregion cross-validation-error-message -->
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
@ -69,4 +62,5 @@
|
|||||||
<button (click)="heroForm.resetForm({})">Add new hero</button>
|
<button (click)="heroForm.resetForm({})">Add new hero</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
@ -3,11 +3,9 @@
|
|||||||
// #docregion
|
// #docregion
|
||||||
import { Component } from '@angular/core';
|
import { Component } from '@angular/core';
|
||||||
|
|
||||||
// #docregion component
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-hero-form-template',
|
selector: 'app-hero-form-template',
|
||||||
templateUrl: './hero-form-template.component.html',
|
templateUrl: './hero-form-template.component.html'
|
||||||
styleUrls: ['./hero-form-template.component.css'],
|
|
||||||
})
|
})
|
||||||
export class HeroFormTemplateComponent {
|
export class HeroFormTemplateComponent {
|
||||||
|
|
||||||
@ -16,4 +14,3 @@ export class HeroFormTemplateComponent {
|
|||||||
hero = {name: 'Dr.', alterEgo: 'Dr. What', power: this.powers[0]};
|
hero = {name: 'Dr.', alterEgo: 'Dr. What', power: this.powers[0]};
|
||||||
|
|
||||||
}
|
}
|
||||||
// #enddocregion
|
|
||||||
|
@ -2,7 +2,6 @@
|
|||||||
"description": "Validation",
|
"description": "Validation",
|
||||||
"files":[
|
"files":[
|
||||||
"!**/*.d.ts",
|
"!**/*.d.ts",
|
||||||
"!**/*.js",
|
"!**/*.js"
|
||||||
"!**/*.[1].*"
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -4,8 +4,7 @@ button {
|
|||||||
font-size: 100%;
|
font-size: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
code,
|
code, .code {
|
||||||
.code {
|
|
||||||
background-color: #eee;
|
background-color: #eee;
|
||||||
color: black;
|
color: black;
|
||||||
font-family: Courier, sans-serif;
|
font-family: Courier, sans-serif;
|
||||||
@ -22,18 +21,14 @@ div.code {
|
|||||||
}
|
}
|
||||||
|
|
||||||
hr {
|
hr {
|
||||||
margin: 40px 0;
|
margin: 40px 0
|
||||||
}
|
}
|
||||||
|
|
||||||
td,
|
td, th {
|
||||||
th {
|
|
||||||
text-align: left;
|
text-align: left;
|
||||||
vertical-align: top;
|
vertical-align: top;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* #docregion p-span */
|
/* #docregion p-span */
|
||||||
p span {
|
p span { color: red; font-size: 70%; }
|
||||||
color: red;
|
|
||||||
font-size: 70%;
|
|
||||||
}
|
|
||||||
/* #enddocregion p-span */
|
/* #enddocregion p-span */
|
||||||
|
@ -132,7 +132,7 @@
|
|||||||
<!-- #docregion select-span -->
|
<!-- #docregion select-span -->
|
||||||
<select [(ngModel)]="hero">
|
<select [(ngModel)]="hero">
|
||||||
<span *ngFor="let h of heroes">
|
<span *ngFor="let h of heroes">
|
||||||
<span *ngIf="showSad || h?.emotion !== 'sad'">
|
<span *ngIf="showSad || h?.emotion != 'sad'">
|
||||||
<option [ngValue]="h">{{h.name}} ({{h?.emotion}})</option>
|
<option [ngValue]="h">{{h.name}} ({{h?.emotion}})</option>
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
@ -147,7 +147,7 @@
|
|||||||
<!-- #docregion select-ngcontainer -->
|
<!-- #docregion select-ngcontainer -->
|
||||||
<select [(ngModel)]="hero">
|
<select [(ngModel)]="hero">
|
||||||
<ng-container *ngFor="let h of heroes">
|
<ng-container *ngFor="let h of heroes">
|
||||||
<ng-container *ngIf="showSad || h?.emotion !== 'sad'">
|
<ng-container *ngIf="showSad || h?.emotion != 'sad'">
|
||||||
<option [ngValue]="h">{{h.name}} ({{h?.emotion}})</option>
|
<option [ngValue]="h">{{h.name}} ({{h?.emotion}})</option>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
@ -6,15 +6,14 @@ import { heroes } from './hero';
|
|||||||
@Component({
|
@Component({
|
||||||
selector: 'my-app',
|
selector: 'my-app',
|
||||||
templateUrl: './app.component.html',
|
templateUrl: './app.component.html',
|
||||||
styleUrls: ['./app.component.css']
|
styleUrls: [ './app.component.css' ]
|
||||||
})
|
})
|
||||||
export class AppComponent {
|
export class AppComponent {
|
||||||
heroes = heroes;
|
heroes = heroes;
|
||||||
hero = this.heroes[0];
|
hero = this.heroes[0];
|
||||||
heroTraits = ['honest', 'brave', 'considerate'];
|
heroTraits = [ 'honest', 'brave', 'considerate' ];
|
||||||
|
|
||||||
// flags for the table
|
// flags for the table
|
||||||
|
|
||||||
attrDirs = true;
|
attrDirs = true;
|
||||||
strucDirs = true;
|
strucDirs = true;
|
||||||
divNgIf = false;
|
divNgIf = false;
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
// #docregion
|
// #docregion
|
||||||
import { NgModule } from '@angular/core';
|
import { NgModule } from '@angular/core';
|
||||||
import { FormsModule } from '@angular/forms';
|
import { FormsModule } from '@angular/forms';
|
||||||
import { BrowserModule } from '@angular/platform-browser';
|
import { BrowserModule } from '@angular/platform-browser';
|
||||||
|
|
||||||
import { AppComponent } from './app.component';
|
import { AppComponent } from './app.component';
|
||||||
import { ContentComponent } from './content.component';
|
import { ContentComponent } from './content.component';
|
||||||
import { heroComponents } from './hero.components';
|
import { heroComponents } from './hero.components';
|
||||||
|
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
// #docregion
|
// #docregion
|
||||||
import { Component, Input } from '@angular/core';
|
import { Component, Input } from '@angular/core';
|
||||||
|
|
||||||
import { Hero } from './hero';
|
import { Hero } from './hero';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
@ -34,15 +33,11 @@ export class ConfusedHeroComponent {
|
|||||||
export class UnknownHeroComponent {
|
export class UnknownHeroComponent {
|
||||||
@Input() hero: Hero;
|
@Input() hero: Hero;
|
||||||
get message() {
|
get message() {
|
||||||
return this.hero && this.hero.name
|
return this.hero && this.hero.name ?
|
||||||
? `${this.hero.name} is strange and mysterious.`
|
`${this.hero.name} is strange and mysterious.` :
|
||||||
: 'Are you feeling indecisive?';
|
'Are you feeling indecisive?';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const heroComponents = [
|
export const heroComponents =
|
||||||
HappyHeroComponent,
|
[ HappyHeroComponent, SadHeroComponent, ConfusedHeroComponent, UnknownHeroComponent ];
|
||||||
SadHeroComponent,
|
|
||||||
ConfusedHeroComponent,
|
|
||||||
UnknownHeroComponent
|
|
||||||
];
|
|
||||||
|
@ -6,8 +6,8 @@ export class Hero {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const heroes: Hero[] = [
|
export const heroes: Hero[] = [
|
||||||
{ id: 1, name: 'Mr. Nice', emotion: 'happy' },
|
{ id: 1, name: 'Mr. Nice', emotion: 'happy'},
|
||||||
{ id: 2, name: 'Narco', emotion: 'sad' },
|
{ id: 2, name: 'Narco', emotion: 'sad' },
|
||||||
{ id: 3, name: 'Windstorm', emotion: 'confused' },
|
{ id: 3, name: 'Windstorm', emotion: 'confused' },
|
||||||
{ id: 4, name: 'Magneta' }
|
{ id: 4, name: 'Magneta'}
|
||||||
];
|
];
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
import { BrowserModule } from '@angular/platform-browser';
|
import { BrowserModule } from '@angular/platform-browser';
|
||||||
import { NgModule } from '@angular/core';
|
import { NgModule } from '@angular/core';
|
||||||
|
import { FormsModule } from '@angular/forms';
|
||||||
|
import { HttpModule } from '@angular/http';
|
||||||
|
|
||||||
/* App Root */
|
/* App Root */
|
||||||
import { AppComponent } from './app.component';
|
import { AppComponent } from './app.component';
|
||||||
|
@ -15,7 +15,7 @@ function sequenceSubscriber(observer) {
|
|||||||
if (idx === arr.length - 1) {
|
if (idx === arr.length - 1) {
|
||||||
observer.complete();
|
observer.complete();
|
||||||
} else {
|
} else {
|
||||||
doSequence(arr, ++idx);
|
doSequence(arr, idx++);
|
||||||
}
|
}
|
||||||
}, 1000);
|
}, 1000);
|
||||||
}
|
}
|
||||||
@ -95,7 +95,7 @@ function multicastSequenceSubscriber() {
|
|||||||
},
|
},
|
||||||
complete() {
|
complete() {
|
||||||
// Notify all complete callbacks
|
// Notify all complete callbacks
|
||||||
observers.slice(0).forEach(obs => obs.complete());
|
observers.forEach(obs => obs.complete());
|
||||||
}
|
}
|
||||||
}, seq, 0);
|
}, seq, 0);
|
||||||
}
|
}
|
||||||
@ -121,13 +121,13 @@ function doSequence(observer, arr, idx) {
|
|||||||
if (idx === arr.length - 1) {
|
if (idx === arr.length - 1) {
|
||||||
observer.complete();
|
observer.complete();
|
||||||
} else {
|
} else {
|
||||||
doSequence(observer, arr, ++idx);
|
doSequence(observer, arr, idx++);
|
||||||
}
|
}
|
||||||
}, 1000);
|
}, 1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create a new Observable that will deliver the above sequence
|
// Create a new Observable that will deliver the above sequence
|
||||||
const multicastSequence = new Observable(multicastSequenceSubscriber());
|
const multicastSequence = new Observable(multicastSequenceSubscriber);
|
||||||
|
|
||||||
// Subscribe starts the clock, and begins to emit after 1 second
|
// Subscribe starts the clock, and begins to emit after 1 second
|
||||||
multicastSequence.subscribe({
|
multicastSequence.subscribe({
|
||||||
|
3
aio/content/examples/reactive-forms/debug.log
Normal file
3
aio/content/examples/reactive-forms/debug.log
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
[1030/162525.401:ERROR:process_reader_win.cc(123)] NtOpenThread: {Acceso denegado} Un proceso ha solicitado acceso a un objeto, pero no se le han concedido esos derechos de acceso. (0xc0000022)
|
||||||
|
[1030/162525.402:ERROR:exception_snapshot_win.cc(87)] thread ID 26896 not found in process
|
||||||
|
[1030/162525.402:WARNING:crash_report_exception_handler.cc(62)] ProcessSnapshotWin::Initialize failed
|
File diff suppressed because it is too large
Load Diff
24
aio/content/examples/reactive-forms/final.stackblitz.json
Normal file
24
aio/content/examples/reactive-forms/final.stackblitz.json
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
{
|
||||||
|
"description": "Angular Reactive Forms (final)",
|
||||||
|
"files":[
|
||||||
|
"src/styles.css",
|
||||||
|
|
||||||
|
"src/app/app.component.ts",
|
||||||
|
"src/app/app.component.html",
|
||||||
|
"src/app/app.component.css",
|
||||||
|
"src/app/app.module.ts",
|
||||||
|
"src/app/data-model.ts",
|
||||||
|
"src/app/hero.service.ts",
|
||||||
|
"src/app/hero-detail/hero-detail.component.html",
|
||||||
|
"src/app/hero-detail/hero-detail.component.ts",
|
||||||
|
"src/app/hero-detail/hero-detail.component.css",
|
||||||
|
"src/app/hero-list/hero-list.component.html",
|
||||||
|
"src/app/hero-list/hero-list.component.ts",
|
||||||
|
"src/app/hero-list/hero-list.component.css",
|
||||||
|
|
||||||
|
"src/main-final.ts",
|
||||||
|
"src/index-final.html"
|
||||||
|
],
|
||||||
|
"main": "src/index-final.html",
|
||||||
|
"tags": ["reactive", "forms"]
|
||||||
|
}
|
@ -1,10 +1,4 @@
|
|||||||
<!-- #docplaster -->
|
<div class="container">
|
||||||
<h1>Reactive Forms</h1>
|
<h1>Reactive Forms</h1>
|
||||||
|
<app-hero-detail></app-hero-detail>
|
||||||
<!-- #docregion app-name-editor-->
|
</div>
|
||||||
<app-name-editor></app-name-editor>
|
|
||||||
<!-- #enddocregion app-name-editor-->
|
|
||||||
|
|
||||||
<!-- #docregion app-profile-editor -->
|
|
||||||
<app-profile-editor></app-profile-editor>
|
|
||||||
<!-- #enddocregion app-profile-editor -->
|
|
||||||
|
@ -1,17 +1,4 @@
|
|||||||
<!-- #docplaster -->
|
<div class="container">
|
||||||
<!-- #docregion app-name-editor -->
|
<h1>Reactive Forms</h1>
|
||||||
<h1>Reactive Forms</h1>
|
<app-hero-list></app-hero-list>
|
||||||
|
</div>
|
||||||
<!-- #enddocregion app-name-editor -->
|
|
||||||
<nav>
|
|
||||||
<a (click)="toggleEditor('name')">Name Editor</a>
|
|
||||||
<a (click)="toggleEditor('profile')">Profile Editor</a>
|
|
||||||
</nav>
|
|
||||||
|
|
||||||
<!-- #docregion app-name-editor -->
|
|
||||||
<app-name-editor *ngIf="showNameEditor"></app-name-editor>
|
|
||||||
<!-- #enddocregion app-name-editor -->
|
|
||||||
|
|
||||||
<!-- #docregion app-profile-editor -->
|
|
||||||
<app-profile-editor *ngIf="showProfileEditor"></app-profile-editor>
|
|
||||||
<!-- #enddocregion app-profile-editor -->
|
|
||||||
|
@ -1,27 +0,0 @@
|
|||||||
import { TestBed, async } from '@angular/core/testing';
|
|
||||||
import { AppComponent } from './app.component';
|
|
||||||
describe('AppComponent', () => {
|
|
||||||
beforeEach(async(() => {
|
|
||||||
TestBed.configureTestingModule({
|
|
||||||
declarations: [
|
|
||||||
AppComponent
|
|
||||||
],
|
|
||||||
}).compileComponents();
|
|
||||||
}));
|
|
||||||
it('should create the app', async(() => {
|
|
||||||
const fixture = TestBed.createComponent(AppComponent);
|
|
||||||
const app = fixture.debugElement.componentInstance;
|
|
||||||
expect(app).toBeTruthy();
|
|
||||||
}));
|
|
||||||
it(`should have as title 'app'`, async(() => {
|
|
||||||
const fixture = TestBed.createComponent(AppComponent);
|
|
||||||
const app = fixture.debugElement.componentInstance;
|
|
||||||
expect(app.title).toEqual('app');
|
|
||||||
}));
|
|
||||||
it('should render title in a h1 tag', async(() => {
|
|
||||||
const fixture = TestBed.createComponent(AppComponent);
|
|
||||||
fixture.detectChanges();
|
|
||||||
const compiled = fixture.debugElement.nativeElement;
|
|
||||||
expect(compiled.querySelector('h1').textContent).toContain('Welcome to reactive-forms!');
|
|
||||||
}));
|
|
||||||
});
|
|
@ -1,24 +1,9 @@
|
|||||||
|
// #docregion
|
||||||
import { Component } from '@angular/core';
|
import { Component } from '@angular/core';
|
||||||
|
|
||||||
export type EditorType = 'name' | 'profile';
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-root',
|
selector: 'app-root',
|
||||||
templateUrl: './app.component.html',
|
templateUrl: './app.component.html',
|
||||||
styleUrls: ['./app.component.css']
|
styleUrls: ['./app.component.css']
|
||||||
})
|
})
|
||||||
export class AppComponent {
|
export class AppComponent { }
|
||||||
editor: EditorType = 'name';
|
|
||||||
|
|
||||||
get showNameEditor() {
|
|
||||||
return this.editor === 'name';
|
|
||||||
}
|
|
||||||
|
|
||||||
get showProfileEditor() {
|
|
||||||
return this.editor === 'profile';
|
|
||||||
}
|
|
||||||
|
|
||||||
toggleEditor(type: EditorType) {
|
|
||||||
this.editor = type;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -1,34 +1,45 @@
|
|||||||
// #docplaster
|
// #docplaster
|
||||||
import { BrowserModule } from '@angular/platform-browser';
|
// #docregion
|
||||||
import { NgModule } from '@angular/core';
|
// #docregion v1
|
||||||
// #docregion imports
|
import { NgModule } from '@angular/core';
|
||||||
import { ReactiveFormsModule } from '@angular/forms';
|
import { BrowserModule } from '@angular/platform-browser';
|
||||||
|
import { ReactiveFormsModule } from '@angular/forms'; // <-- #1 import module
|
||||||
|
|
||||||
// #enddocregion imports
|
import { AppComponent } from './app.component';
|
||||||
import { AppComponent } from './app.component';
|
import { HeroDetailComponent } from './hero-detail/hero-detail.component';
|
||||||
import { NameEditorComponent } from './name-editor/name-editor.component';
|
// #enddocregion v1
|
||||||
import { ProfileEditorComponent } from './profile-editor/profile-editor.component';
|
// #docregion hero-service-list
|
||||||
|
// add JavaScript imports
|
||||||
|
import { HeroListComponent } from './hero-list/hero-list.component';
|
||||||
|
import { HeroService } from './hero.service';
|
||||||
|
// #docregion v1
|
||||||
|
|
||||||
// #docregion imports
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
// #enddocregion imports
|
|
||||||
declarations: [
|
declarations: [
|
||||||
AppComponent,
|
AppComponent,
|
||||||
NameEditorComponent,
|
HeroDetailComponent,
|
||||||
ProfileEditorComponent
|
// #enddocregion v1
|
||||||
|
HeroListComponent // <--declare HeroListComponent
|
||||||
|
// #docregion v1
|
||||||
],
|
],
|
||||||
// #docregion imports
|
// #enddocregion hero-service-list
|
||||||
imports: [
|
imports: [
|
||||||
// #enddocregion imports
|
|
||||||
BrowserModule,
|
BrowserModule,
|
||||||
// #docregion imports
|
ReactiveFormsModule // <-- #2 add to @NgModule imports
|
||||||
// other imports ...
|
|
||||||
ReactiveFormsModule
|
|
||||||
],
|
],
|
||||||
// #enddocregion imports
|
// #enddocregion v1
|
||||||
providers: [],
|
// export for the DemoModule
|
||||||
bootstrap: [AppComponent]
|
// #docregion hero-service-list
|
||||||
// #docregion imports
|
// ...
|
||||||
|
exports: [
|
||||||
|
AppComponent,
|
||||||
|
HeroDetailComponent,
|
||||||
|
HeroListComponent // <-- export HeroListComponent
|
||||||
|
],
|
||||||
|
providers: [ HeroService ], // <-- provide HeroService
|
||||||
|
// #enddocregion hero-service-list
|
||||||
|
// #docregion v1
|
||||||
|
bootstrap: [ AppComponent ]
|
||||||
})
|
})
|
||||||
export class AppModule { }
|
export class AppModule { }
|
||||||
// #enddocregion imports
|
// #enddocregion v1
|
||||||
|
40
aio/content/examples/reactive-forms/src/app/data-model.ts
Normal file
40
aio/content/examples/reactive-forms/src/app/data-model.ts
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
// #docregion
|
||||||
|
// #docregion model-classes
|
||||||
|
export class Hero {
|
||||||
|
id = 0;
|
||||||
|
name = '';
|
||||||
|
addresses: Address[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Address {
|
||||||
|
street = '';
|
||||||
|
city = '';
|
||||||
|
state = '';
|
||||||
|
zip = '';
|
||||||
|
}
|
||||||
|
// #enddocregion model-classes
|
||||||
|
|
||||||
|
export const heroes: Hero[] = [
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
name: 'Whirlwind',
|
||||||
|
addresses: [
|
||||||
|
{street: '123 Main', city: 'Anywhere', state: 'CA', zip: '94801'},
|
||||||
|
{street: '456 Maple', city: 'Somewhere', state: 'VA', zip: '23226'},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
name: 'Bombastic',
|
||||||
|
addresses: [
|
||||||
|
{street: '789 Elm', city: 'Smallville', state: 'OH', zip: '04501'},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 3,
|
||||||
|
name: 'Magneta',
|
||||||
|
addresses: [ ]
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export const states = ['CA', 'MD', 'OH', 'VA'];
|
@ -0,0 +1,40 @@
|
|||||||
|
<div class="container">
|
||||||
|
<h1>Reactive Forms</h1>
|
||||||
|
<h4><i>Pick a demo:</i>
|
||||||
|
<select [selectedIndex]="demo - 1" (change)="selectDemo($event.target.selectedIndex)">
|
||||||
|
<option *ngFor="let demo of demos">{{demo}}</option>
|
||||||
|
</select>
|
||||||
|
</h4>
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
|
||||||
|
<div class="demo">
|
||||||
|
<app-hero-list *ngIf="demo===final"></app-hero-list>
|
||||||
|
<app-hero-detail-1 *ngIf="demo===1"></app-hero-detail-1>
|
||||||
|
<app-hero-detail-2 *ngIf="demo===2"></app-hero-detail-2>
|
||||||
|
<app-hero-detail-3 *ngIf="demo===3"></app-hero-detail-3>
|
||||||
|
<app-hero-detail-4 *ngIf="demo===4"></app-hero-detail-4>
|
||||||
|
<app-hero-detail-5 *ngIf="demo===5"></app-hero-detail-5>
|
||||||
|
|
||||||
|
<div *ngIf="demo >= 6 && demo !== final" >
|
||||||
|
|
||||||
|
<h3 *ngIf="isLoading"><i>Loading heroes ... </i></h3>
|
||||||
|
<h3 *ngIf="!isLoading">Select a hero:</h3>
|
||||||
|
|
||||||
|
<nav>
|
||||||
|
<button (click)="getHeroes()" class="btn btn-primary">Refresh</button>
|
||||||
|
<a *ngFor="let hero of heroes | async" (click)="select(hero)">{{hero.name}}</a>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<div *ngIf="selectedHero">
|
||||||
|
<hr>
|
||||||
|
<h2>Hero Detail</h2>
|
||||||
|
<h3>Editing: {{selectedHero.name}}</h3>
|
||||||
|
<app-hero-detail-6 [hero]=selectedHero *ngIf="demo===6"></app-hero-detail-6>
|
||||||
|
<app-hero-detail-7 [hero]=selectedHero *ngIf="demo===7"></app-hero-detail-7>
|
||||||
|
<app-hero-detail-8 [hero]=selectedHero *ngIf="demo===8"></app-hero-detail-8>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
@ -0,0 +1,49 @@
|
|||||||
|
/* tslint:disable:member-ordering */
|
||||||
|
import { Component } from '@angular/core';
|
||||||
|
import { Observable } from 'rxjs';
|
||||||
|
import { finalize } from 'rxjs/operators';
|
||||||
|
|
||||||
|
import { Hero } from './data-model';
|
||||||
|
import { HeroService } from './hero.service';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-root',
|
||||||
|
templateUrl: './demo.component.html'
|
||||||
|
})
|
||||||
|
export class DemoComponent {
|
||||||
|
|
||||||
|
demos: string[] = [
|
||||||
|
'Just a FormControl',
|
||||||
|
'FormControl in a FormGroup',
|
||||||
|
'Simple FormBuilder group',
|
||||||
|
'Group with multiple controls',
|
||||||
|
'Nested FormBuilder group',
|
||||||
|
'PatchValue',
|
||||||
|
'SetValue',
|
||||||
|
'FormArray',
|
||||||
|
'Final'].map(n => n + ' Demo');
|
||||||
|
|
||||||
|
final = this.demos.length;
|
||||||
|
demo = this.final; // current demo
|
||||||
|
|
||||||
|
heroes: Observable<Hero[]>;
|
||||||
|
isLoading = false;
|
||||||
|
selectedHero: Hero;
|
||||||
|
|
||||||
|
constructor(private heroService: HeroService) { }
|
||||||
|
|
||||||
|
getHeroes() {
|
||||||
|
this.isLoading = true;
|
||||||
|
this.heroes = this.heroService.getHeroes().pipe(
|
||||||
|
finalize(() => this.isLoading = false)
|
||||||
|
);
|
||||||
|
this.selectedHero = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
select(hero: Hero) { this.selectedHero = hero; }
|
||||||
|
|
||||||
|
selectDemo(demo: number) {
|
||||||
|
this.demo = demo + 1;
|
||||||
|
this.getHeroes();
|
||||||
|
}
|
||||||
|
}
|
33
aio/content/examples/reactive-forms/src/app/demo.module.ts
Normal file
33
aio/content/examples/reactive-forms/src/app/demo.module.ts
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
import { NgModule } from '@angular/core';
|
||||||
|
import { BrowserModule } from '@angular/platform-browser';
|
||||||
|
import { ReactiveFormsModule } from '@angular/forms';
|
||||||
|
|
||||||
|
import { AppModule } from './app.module';
|
||||||
|
import { DemoComponent } from './demo.component';
|
||||||
|
import { HeroDetailComponent1 } from './hero-detail/hero-detail-1.component';
|
||||||
|
import { HeroDetailComponent2 } from './hero-detail/hero-detail-2.component';
|
||||||
|
import { HeroDetailComponent3 } from './hero-detail/hero-detail-3.component';
|
||||||
|
import { HeroDetailComponent4 } from './hero-detail/hero-detail-4.component';
|
||||||
|
import { HeroDetailComponent5 } from './hero-detail/hero-detail-5.component';
|
||||||
|
import { HeroDetailComponent6 } from './hero-detail/hero-detail-6.component';
|
||||||
|
import { HeroDetailComponent7 } from './hero-detail/hero-detail-7.component';
|
||||||
|
import { HeroDetailComponent8 } from './hero-detail/hero-detail-8.component';
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
imports: [
|
||||||
|
BrowserModule,
|
||||||
|
ReactiveFormsModule,
|
||||||
|
AppModule,
|
||||||
|
],
|
||||||
|
declarations: [ DemoComponent,
|
||||||
|
HeroDetailComponent1,
|
||||||
|
HeroDetailComponent2,
|
||||||
|
HeroDetailComponent3,
|
||||||
|
HeroDetailComponent4,
|
||||||
|
HeroDetailComponent5,
|
||||||
|
HeroDetailComponent6,
|
||||||
|
HeroDetailComponent7,
|
||||||
|
HeroDetailComponent8],
|
||||||
|
bootstrap: [ DemoComponent ]
|
||||||
|
})
|
||||||
|
export class DemoModule { }
|
@ -0,0 +1,8 @@
|
|||||||
|
<!-- #docregion simple-control-->
|
||||||
|
<h2>Hero Detail</h2>
|
||||||
|
<h3><i>Just a FormControl</i></h3>
|
||||||
|
<label class="center-block">Name:
|
||||||
|
<input class="form-control" [formControl]="name">
|
||||||
|
</label>
|
||||||
|
<!-- #enddocregion simple-control-->
|
||||||
|
|
@ -0,0 +1,15 @@
|
|||||||
|
/* tslint:disable:component-class-suffix */
|
||||||
|
|
||||||
|
import { Component } from '@angular/core';
|
||||||
|
// #docregion import
|
||||||
|
import { FormControl } from '@angular/forms';
|
||||||
|
// #enddocregion import
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-hero-detail-1',
|
||||||
|
templateUrl: './hero-detail-1.component.html'
|
||||||
|
})
|
||||||
|
// #docregion v1
|
||||||
|
export class HeroDetailComponent1 {
|
||||||
|
name = new FormControl();
|
||||||
|
}
|
@ -0,0 +1,18 @@
|
|||||||
|
<!-- #docregion basic-form-->
|
||||||
|
<h2>Hero Detail</h2>
|
||||||
|
<h3><i>FormControl in a FormGroup</i></h3>
|
||||||
|
<form [formGroup]="heroForm">
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="center-block">Name:
|
||||||
|
<input class="form-control" formControlName="name">
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
<!-- #enddocregion basic-form-->
|
||||||
|
|
||||||
|
<!-- #docregion form-value-json -->
|
||||||
|
<p>Form value: {{ heroForm.value | json }}</p>
|
||||||
|
<!-- #enddocregion form-value-json -->
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -0,0 +1,17 @@
|
|||||||
|
/* tslint:disable:component-class-suffix */
|
||||||
|
// #docregion imports
|
||||||
|
import { Component } from '@angular/core';
|
||||||
|
import { FormControl, FormGroup } from '@angular/forms';
|
||||||
|
// #enddocregion imports
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-hero-detail-2',
|
||||||
|
templateUrl: './hero-detail-2.component.html'
|
||||||
|
})
|
||||||
|
// #docregion v2
|
||||||
|
export class HeroDetailComponent2 {
|
||||||
|
heroForm = new FormGroup ({
|
||||||
|
name: new FormControl()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// #enddocregion v2
|
@ -0,0 +1,16 @@
|
|||||||
|
<!-- #docregion basic-form-->
|
||||||
|
<h2>Hero Detail</h2>
|
||||||
|
<h3><i>A FormGroup with a single FormControl using FormBuilder</i></h3>
|
||||||
|
<form [formGroup]="heroForm">
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="center-block">Name:
|
||||||
|
<input class="form-control" formControlName="name">
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
<!-- #enddocregion basic-form-->
|
||||||
|
|
||||||
|
<!-- #docregion form-value-json -->
|
||||||
|
<p>Form value: {{ heroForm.value | json }}</p>
|
||||||
|
<p>Form status: {{ heroForm.status | json }}</p>
|
||||||
|
<!-- #enddocregion form-value-json -->
|
@ -0,0 +1,27 @@
|
|||||||
|
/* tslint:disable:component-class-suffix */
|
||||||
|
// #docregion imports
|
||||||
|
import { Component } from '@angular/core';
|
||||||
|
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
|
||||||
|
// #enddocregion imports
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-hero-detail-3',
|
||||||
|
templateUrl: './hero-detail-3.component.html'
|
||||||
|
})
|
||||||
|
// #docregion v3
|
||||||
|
export class HeroDetailComponent3 {
|
||||||
|
heroForm: FormGroup; // <--- heroForm is of type FormGroup
|
||||||
|
|
||||||
|
constructor(private fb: FormBuilder) { // <--- inject FormBuilder
|
||||||
|
this.createForm();
|
||||||
|
}
|
||||||
|
|
||||||
|
createForm() {
|
||||||
|
// #docregion required
|
||||||
|
this.heroForm = this.fb.group({
|
||||||
|
name: ['', Validators.required ],
|
||||||
|
});
|
||||||
|
// #enddocregion required
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// #enddocregion v3
|
@ -0,0 +1,25 @@
|
|||||||
|
/* tslint:disable:component-class-suffix */
|
||||||
|
// #docregion imports
|
||||||
|
import { Component } from '@angular/core';
|
||||||
|
import { FormBuilder, FormGroup } from '@angular/forms';
|
||||||
|
// #enddocregion imports
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-hero-detail-3',
|
||||||
|
templateUrl: './hero-detail-3.component.html'
|
||||||
|
})
|
||||||
|
// #docregion v3a
|
||||||
|
export class HeroDetailComponent3 {
|
||||||
|
heroForm: FormGroup; // <--- heroForm is of type FormGroup
|
||||||
|
|
||||||
|
constructor(private fb: FormBuilder) { // <--- inject FormBuilder
|
||||||
|
this.createForm();
|
||||||
|
}
|
||||||
|
|
||||||
|
createForm() {
|
||||||
|
this.heroForm = this.fb.group({
|
||||||
|
name: '', // <--- the FormControl called "name"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// #enddocregion v3a
|
@ -0,0 +1,46 @@
|
|||||||
|
<!-- #docregion -->
|
||||||
|
<h2>Hero Detail</h2>
|
||||||
|
<h3><i>A FormGroup with multiple FormControls</i></h3>
|
||||||
|
<form [formGroup]="heroForm">
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="center-block">Name:
|
||||||
|
<input class="form-control" formControlName="name">
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="center-block">Street:
|
||||||
|
<input class="form-control" formControlName="street">
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="center-block">City:
|
||||||
|
<input class="form-control" formControlName="city">
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="center-block">State:
|
||||||
|
<select class="form-control" formControlName="state">
|
||||||
|
<option *ngFor="let state of states" [value]="state">{{state}}</option>
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="center-block">Zip Code:
|
||||||
|
<input class="form-control" formControlName="zip">
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="form-group radio">
|
||||||
|
<h4>Super power:</h4>
|
||||||
|
<label class="center-block"><input type="radio" formControlName="power" value="flight">Flight</label>
|
||||||
|
<label class="center-block"><input type="radio" formControlName="power" value="x-ray vision">X-ray vision</label>
|
||||||
|
<label class="center-block"><input type="radio" formControlName="power" value="strength">Strength</label>
|
||||||
|
</div>
|
||||||
|
<div class="checkbox">
|
||||||
|
<label class="center-block">
|
||||||
|
<input type="checkbox" formControlName="sidekick">I have a sidekick.
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
|
||||||
|
<p>Form value: {{ heroForm.value | json }}</p>
|
@ -0,0 +1,34 @@
|
|||||||
|
/* tslint:disable:component-class-suffix */
|
||||||
|
// #docregion imports
|
||||||
|
import { Component } from '@angular/core';
|
||||||
|
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
|
||||||
|
|
||||||
|
import { states } from '../data-model';
|
||||||
|
// #enddocregion imports
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-hero-detail-4',
|
||||||
|
templateUrl: './hero-detail-4.component.html'
|
||||||
|
})
|
||||||
|
// #docregion v4
|
||||||
|
export class HeroDetailComponent4 {
|
||||||
|
heroForm: FormGroup;
|
||||||
|
states = states;
|
||||||
|
|
||||||
|
constructor(private fb: FormBuilder) {
|
||||||
|
this.createForm();
|
||||||
|
}
|
||||||
|
|
||||||
|
createForm() {
|
||||||
|
this.heroForm = this.fb.group({
|
||||||
|
name: ['', Validators.required ],
|
||||||
|
street: '',
|
||||||
|
city: '',
|
||||||
|
state: '',
|
||||||
|
zip: '',
|
||||||
|
power: '',
|
||||||
|
sidekick: ''
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// #enddocregion v4
|
@ -0,0 +1,56 @@
|
|||||||
|
|
||||||
|
<form [formGroup]="heroForm">
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="center-block">Name:
|
||||||
|
<input class="form-control" formControlName="name">
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<!-- #docregion add-group-->
|
||||||
|
<div formGroupName="address" class="well well-lg">
|
||||||
|
<h4>Secret Lair</h4>
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="center-block">Street:
|
||||||
|
<input class="form-control" formControlName="street">
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="center-block">City:
|
||||||
|
<input class="form-control" formControlName="city">
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="center-block">State:
|
||||||
|
<select class="form-control" formControlName="state">
|
||||||
|
<option *ngFor="let state of states" [value]="state">{{state}}</option>
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="center-block">Zip Code:
|
||||||
|
<input class="form-control" formControlName="zip">
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- #enddocregion add-group-->
|
||||||
|
<div class="form-group radio">
|
||||||
|
<h4>Super power:</h4>
|
||||||
|
<label class="center-block"><input type="radio" formControlName="power" value="flight">Flight</label>
|
||||||
|
<label class="center-block"><input type="radio" formControlName="power" value="x-ray vision">X-ray vision</label>
|
||||||
|
<label class="center-block"><input type="radio" formControlName="power" value="strength">Strength</label>
|
||||||
|
</div>
|
||||||
|
<div class="checkbox">
|
||||||
|
<label class="center-block">
|
||||||
|
<input type="checkbox" formControlName="sidekick">I have a sidekick.
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<p>heroForm value: {{ heroForm.value | json}}</p>
|
||||||
|
<h4>Extra info for the curious:</h4>
|
||||||
|
<!-- #docregion inspect-value -->
|
||||||
|
<p>Name value: {{ heroForm.get('name').value }}</p>
|
||||||
|
<!-- #enddocregion inspect-value -->
|
||||||
|
|
||||||
|
<!-- #docregion inspect-child-control -->
|
||||||
|
<p>Street value: {{ heroForm.get('address.street').value}}</p>
|
||||||
|
<!-- #enddocregion inspect-child-control -->
|
@ -0,0 +1,35 @@
|
|||||||
|
/* tslint:disable:component-class-suffix */
|
||||||
|
import { Component } from '@angular/core';
|
||||||
|
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
|
||||||
|
|
||||||
|
import { states } from '../data-model';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-hero-detail-5',
|
||||||
|
templateUrl: './hero-detail-5.component.html'
|
||||||
|
})
|
||||||
|
// #docregion v5
|
||||||
|
export class HeroDetailComponent5 {
|
||||||
|
heroForm: FormGroup;
|
||||||
|
states = states;
|
||||||
|
|
||||||
|
constructor(private fb: FormBuilder) {
|
||||||
|
this.createForm();
|
||||||
|
}
|
||||||
|
|
||||||
|
createForm() {
|
||||||
|
this.heroForm = this.fb.group({ // <-- the parent FormGroup
|
||||||
|
name: ['', Validators.required ],
|
||||||
|
address: this.fb.group({ // <-- the child FormGroup
|
||||||
|
street: '',
|
||||||
|
city: '',
|
||||||
|
state: '',
|
||||||
|
zip: ''
|
||||||
|
}),
|
||||||
|
power: '',
|
||||||
|
sidekick: ''
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// #enddocregion v5
|
||||||
|
|
@ -0,0 +1,46 @@
|
|||||||
|
<!-- #docregion -->
|
||||||
|
<h2>Hero Detail</h2>
|
||||||
|
<h3><i>PatchValue to initialize a value</i></h3>
|
||||||
|
<form [formGroup]="heroForm">
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="center-block">Name:
|
||||||
|
<input class="form-control" formControlName="name">
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="center-block">Street:
|
||||||
|
<input class="form-control" formControlName="street">
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="center-block">City:
|
||||||
|
<input class="form-control" formControlName="city">
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="center-block">State:
|
||||||
|
<select class="form-control" formControlName="state">
|
||||||
|
<option *ngFor="let state of states" [value]="state">{{state}}</option>
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="center-block">Zip Code:
|
||||||
|
<input class="form-control" formControlName="zip">
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="form-group radio">
|
||||||
|
<h4>Super power:</h4>
|
||||||
|
<label class="center-block"><input type="radio" formControlName="power" value="flight">Flight</label>
|
||||||
|
<label class="center-block"><input type="radio" formControlName="power" value="x-ray vision">X-ray vision</label>
|
||||||
|
<label class="center-block"><input type="radio" formControlName="power" value="strength">Strength</label>
|
||||||
|
</div>
|
||||||
|
<div class="checkbox">
|
||||||
|
<label class="center-block">
|
||||||
|
<input type="checkbox" formControlName="sidekick">I have a sidekick.
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
|
||||||
|
<p>Form value: {{ heroForm.value | json }}</p>
|
@ -0,0 +1,66 @@
|
|||||||
|
/* tslint:disable:component-class-suffix */
|
||||||
|
// #docregion import-input
|
||||||
|
import { Component, Input, OnChanges } from '@angular/core';
|
||||||
|
// #enddocregion import-input
|
||||||
|
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
|
||||||
|
|
||||||
|
// #docregion import-hero
|
||||||
|
import { Hero, states } from '../data-model';
|
||||||
|
// #enddocregion import-hero
|
||||||
|
|
||||||
|
////////// 6 ////////////////////
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-hero-detail-6',
|
||||||
|
templateUrl: './hero-detail-5.component.html'
|
||||||
|
})
|
||||||
|
// #docregion v6
|
||||||
|
export class HeroDetailComponent6 implements OnChanges {
|
||||||
|
// #docregion hero
|
||||||
|
@Input() hero: Hero;
|
||||||
|
// #enddocregion hero
|
||||||
|
|
||||||
|
heroForm: FormGroup;
|
||||||
|
states = states;
|
||||||
|
|
||||||
|
constructor(private fb: FormBuilder) {
|
||||||
|
this.createForm();
|
||||||
|
}
|
||||||
|
|
||||||
|
createForm() {
|
||||||
|
// #docregion hero-form-model
|
||||||
|
this.heroForm = this.fb.group({
|
||||||
|
name: ['', Validators.required ],
|
||||||
|
address: this.fb.group({
|
||||||
|
street: '',
|
||||||
|
city: '',
|
||||||
|
state: '',
|
||||||
|
zip: ''
|
||||||
|
}),
|
||||||
|
power: '',
|
||||||
|
sidekick: ''
|
||||||
|
});
|
||||||
|
// #enddocregion hero-form-model
|
||||||
|
}
|
||||||
|
|
||||||
|
// #docregion patch-value-on-changes
|
||||||
|
ngOnChanges() { // <-- call rebuildForm in ngOnChanges
|
||||||
|
this.rebuildForm();
|
||||||
|
}
|
||||||
|
// #enddocregion patch-value-on-changes
|
||||||
|
|
||||||
|
// #docregion patch-value-rebuildform
|
||||||
|
rebuildForm() { // <-- wrap patchValue in rebuildForm
|
||||||
|
this.heroForm.reset();
|
||||||
|
// #docregion patch-value
|
||||||
|
this.heroForm.patchValue({
|
||||||
|
name: this.hero.name
|
||||||
|
});
|
||||||
|
// #enddocregion patch-value
|
||||||
|
}
|
||||||
|
// #enddocregion patch-value-rebuildform
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// #enddocregion v6
|
@ -0,0 +1,46 @@
|
|||||||
|
<!-- #docregion -->
|
||||||
|
<h2>Hero Detail</h2>
|
||||||
|
<h3><i>A FormGroup with multiple FormControls</i></h3>
|
||||||
|
<form [formGroup]="heroForm">
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="center-block">Name:
|
||||||
|
<input class="form-control" formControlName="name">
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="center-block">Street:
|
||||||
|
<input class="form-control" formControlName="street">
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="center-block">City:
|
||||||
|
<input class="form-control" formControlName="city">
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="center-block">State:
|
||||||
|
<select class="form-control" formControlName="state">
|
||||||
|
<option *ngFor="let state of states" [value]="state">{{state}}</option>
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="center-block">Zip Code:
|
||||||
|
<input class="form-control" formControlName="zip">
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="form-group radio">
|
||||||
|
<h4>Super power:</h4>
|
||||||
|
<label class="center-block"><input type="radio" formControlName="power" value="flight">Flight</label>
|
||||||
|
<label class="center-block"><input type="radio" formControlName="power" value="x-ray vision">X-ray vision</label>
|
||||||
|
<label class="center-block"><input type="radio" formControlName="power" value="strength">Strength</label>
|
||||||
|
</div>
|
||||||
|
<div class="checkbox">
|
||||||
|
<label class="center-block">
|
||||||
|
<input type="checkbox" formControlName="sidekick">I have a sidekick.
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
|
||||||
|
<p>Form value: {{ heroForm.value | json }}</p>
|
@ -0,0 +1,68 @@
|
|||||||
|
/* tslint:disable:component-class-suffix */
|
||||||
|
// #docplaster
|
||||||
|
// #docregion imports
|
||||||
|
import { Component, Input, OnChanges } from '@angular/core';
|
||||||
|
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
|
||||||
|
|
||||||
|
// #docregion import-address
|
||||||
|
import { Address, Hero, states } from '../data-model';
|
||||||
|
// #enddocregion import-address
|
||||||
|
|
||||||
|
// #enddocregion imports
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-hero-detail-7',
|
||||||
|
templateUrl: './hero-detail-5.component.html'
|
||||||
|
})
|
||||||
|
// #docregion v7
|
||||||
|
export class HeroDetailComponent7 implements OnChanges {
|
||||||
|
@Input() hero: Hero;
|
||||||
|
|
||||||
|
heroForm: FormGroup;
|
||||||
|
states = states;
|
||||||
|
|
||||||
|
constructor(private fb: FormBuilder) {
|
||||||
|
this.createForm();
|
||||||
|
}
|
||||||
|
|
||||||
|
createForm() {
|
||||||
|
// #docregion address-form-group
|
||||||
|
this.heroForm = this.fb.group({
|
||||||
|
name: ['', Validators.required ],
|
||||||
|
address: this.fb.group(new Address()), // <-- a FormGroup with a new address
|
||||||
|
power: '',
|
||||||
|
sidekick: ''
|
||||||
|
});
|
||||||
|
// #enddocregion address-form-group
|
||||||
|
}
|
||||||
|
|
||||||
|
// #docregion ngOnChanges
|
||||||
|
ngOnChanges() {
|
||||||
|
this.rebuildForm();
|
||||||
|
}
|
||||||
|
// #enddocregion ngOnChanges
|
||||||
|
|
||||||
|
// #docregion rebuildForm
|
||||||
|
rebuildForm() {
|
||||||
|
this.heroForm.reset({
|
||||||
|
name: this.hero.name,
|
||||||
|
// #docregion set-value-address
|
||||||
|
address: this.hero.addresses[0] || new Address()
|
||||||
|
// #enddocregion set-value-address
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// #enddocregion rebuildForm
|
||||||
|
|
||||||
|
/* First version of rebuildForm */
|
||||||
|
rebuildForm1() {
|
||||||
|
// #docregion reset
|
||||||
|
this.heroForm.reset();
|
||||||
|
// #enddocregion reset
|
||||||
|
// #docregion set-value
|
||||||
|
this.heroForm.setValue({
|
||||||
|
name: this.hero.name,
|
||||||
|
address: this.hero.addresses[0] || new Address()
|
||||||
|
});
|
||||||
|
// #enddocregion set-value
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,72 @@
|
|||||||
|
<!-- #docplaster-->
|
||||||
|
<h3><i>Using FormArray to add groups</i></h3>
|
||||||
|
|
||||||
|
<form [formGroup]="heroForm">
|
||||||
|
<p>Form Changed: {{ heroForm.dirty }}</p>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="center-block">Name:
|
||||||
|
<input class="form-control" formControlName="name">
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<!-- #docregion form-array-->
|
||||||
|
<!-- #docregion form-array-skeleton -->
|
||||||
|
<!-- #docregion form-array-name -->
|
||||||
|
<div formArrayName="secretLairs" class="well well-lg">
|
||||||
|
<!-- #enddocregion form-array-name -->
|
||||||
|
<div *ngFor="let address of secretLairs.controls; let i=index" [formGroupName]="i" >
|
||||||
|
<!-- The repeated address template -->
|
||||||
|
<!-- #enddocregion form-array-skeleton -->
|
||||||
|
<h4>Address #{{i + 1}}</h4>
|
||||||
|
<div style="margin-left: 1em;">
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="center-block">Street:
|
||||||
|
<input class="form-control" formControlName="street">
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="center-block">City:
|
||||||
|
<input class="form-control" formControlName="city">
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="center-block">State:
|
||||||
|
<select class="form-control" formControlName="state">
|
||||||
|
<option *ngFor="let state of states" [value]="state">{{state}}</option>
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="center-block">Zip Code:
|
||||||
|
<input class="form-control" formControlName="zip">
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<br>
|
||||||
|
<!-- End of the repeated address template -->
|
||||||
|
<!-- #docregion form-array-skeleton -->
|
||||||
|
</div>
|
||||||
|
<!-- #enddocregion form-array-skeleton -->
|
||||||
|
<!-- #enddocregion form-array-->
|
||||||
|
<!-- #docregion add-lair -->
|
||||||
|
<button (click)="addLair()" type="button">Add a Secret Lair</button>
|
||||||
|
<!-- #enddocregion add-lair -->
|
||||||
|
<!-- #docregion form-array-->
|
||||||
|
<!-- #docregion form-array-skeleton -->
|
||||||
|
</div>
|
||||||
|
<!-- #enddocregion form-array-skeleton -->
|
||||||
|
<!-- #enddocregion form-array-->
|
||||||
|
<div class="form-group radio">
|
||||||
|
<h4>Super power:</h4>
|
||||||
|
<label class="center-block"><input type="radio" formControlName="power" value="flight">Flight</label>
|
||||||
|
<label class="center-block"><input type="radio" formControlName="power" value="x-ray vision">X-ray vision</label>
|
||||||
|
<label class="center-block"><input type="radio" formControlName="power" value="strength">Strength</label>
|
||||||
|
</div>
|
||||||
|
<div class="checkbox">
|
||||||
|
<label class="center-block">
|
||||||
|
<input type="checkbox" formControlName="sidekick">I have a sidekick.
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<p>heroForm value: {{ heroForm.value | json}}</p>
|
@ -0,0 +1,74 @@
|
|||||||
|
/* tslint:disable:component-class-suffix */
|
||||||
|
// #docregion imports
|
||||||
|
import { Component, Input, OnChanges } from '@angular/core';
|
||||||
|
import { FormArray, FormBuilder, FormGroup, Validators } from '@angular/forms';
|
||||||
|
|
||||||
|
import { Address, Hero, states } from '../data-model';
|
||||||
|
// #enddocregion imports
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-hero-detail-8',
|
||||||
|
templateUrl: './hero-detail-8.component.html'
|
||||||
|
})
|
||||||
|
// #docregion v8
|
||||||
|
export class HeroDetailComponent8 implements OnChanges {
|
||||||
|
@Input() hero: Hero;
|
||||||
|
|
||||||
|
heroForm: FormGroup;
|
||||||
|
states = states;
|
||||||
|
|
||||||
|
// #docregion ctor
|
||||||
|
constructor(private fb: FormBuilder) {
|
||||||
|
this.createForm();
|
||||||
|
this.logNameChange();
|
||||||
|
}
|
||||||
|
// #enddocregion ctor
|
||||||
|
|
||||||
|
createForm() {
|
||||||
|
// #docregion secretLairs-form-array
|
||||||
|
this.heroForm = this.fb.group({
|
||||||
|
name: ['', Validators.required ],
|
||||||
|
secretLairs: this.fb.array([]), // <-- secretLairs as an empty FormArray
|
||||||
|
power: '',
|
||||||
|
sidekick: ''
|
||||||
|
});
|
||||||
|
// #enddocregion secretLairs-form-array
|
||||||
|
}
|
||||||
|
|
||||||
|
logNameChange() {/* Coming soon */}
|
||||||
|
|
||||||
|
// #docregion onchanges
|
||||||
|
ngOnChanges() {
|
||||||
|
this.rebuildForm();
|
||||||
|
}
|
||||||
|
// #enddocregion onchanges
|
||||||
|
|
||||||
|
// #docregion rebuildform
|
||||||
|
rebuildForm() {
|
||||||
|
this.heroForm.reset({
|
||||||
|
name: this.hero.name
|
||||||
|
});
|
||||||
|
this.setAddresses(this.hero.addresses);
|
||||||
|
}
|
||||||
|
// #enddocregion rebuildform
|
||||||
|
|
||||||
|
// #docregion get-secret-lairs
|
||||||
|
get secretLairs(): FormArray {
|
||||||
|
return this.heroForm.get('secretLairs') as FormArray;
|
||||||
|
};
|
||||||
|
// #enddocregion get-secret-lairs
|
||||||
|
|
||||||
|
// #docregion set-addresses
|
||||||
|
setAddresses(addresses: Address[]) {
|
||||||
|
const addressFGs = addresses.map(address => this.fb.group(address));
|
||||||
|
const addressFormArray = this.fb.array(addressFGs);
|
||||||
|
this.heroForm.setControl('secretLairs', addressFormArray);
|
||||||
|
}
|
||||||
|
// #enddocregion set-addresses
|
||||||
|
|
||||||
|
// #docregion add-lair
|
||||||
|
addLair() {
|
||||||
|
this.secretLairs.push(this.fb.group(new Address()));
|
||||||
|
}
|
||||||
|
// #enddocregion add-lair
|
||||||
|
}
|
@ -0,0 +1,73 @@
|
|||||||
|
<!-- #docplaster -->
|
||||||
|
<!-- #docregion -->
|
||||||
|
<!-- #docregion buttons -->
|
||||||
|
<form [formGroup]="heroForm" (ngSubmit)="onSubmit()">
|
||||||
|
<div style="margin-bottom: 1em">
|
||||||
|
<button type="submit"
|
||||||
|
[disabled]="heroForm.pristine" class="btn btn-success">Save</button>
|
||||||
|
<button type="button" (click)="revert()"
|
||||||
|
[disabled]="heroForm.pristine" class="btn btn-danger">Revert</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Hero Detail Controls -->
|
||||||
|
<!-- #enddocregion buttons -->
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="center-block">Name:
|
||||||
|
<input class="form-control" formControlName="name">
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div formArrayName="secretLairs" class="well well-lg">
|
||||||
|
<div *ngFor="let address of secretLairs.controls; let i=index" [formGroupName]="i" >
|
||||||
|
<!-- The repeated address template -->
|
||||||
|
<h4>Address #{{i + 1}}</h4>
|
||||||
|
<div style="margin-left: 1em;">
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="center-block">Street:
|
||||||
|
<input class="form-control" formControlName="street">
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="center-block">City:
|
||||||
|
<input class="form-control" formControlName="city">
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="center-block">State:
|
||||||
|
<select class="form-control" formControlName="state">
|
||||||
|
<option *ngFor="let state of states" [value]="state">{{state}}</option>
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="center-block">Zip Code:
|
||||||
|
<input class="form-control" formControlName="zip">
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<br>
|
||||||
|
<!-- End of the repeated address template -->
|
||||||
|
</div>
|
||||||
|
<button (click)="addLair()" type="button">Add a Secret Lair</button>
|
||||||
|
</div>
|
||||||
|
<!-- #docregion buttons -->
|
||||||
|
<div class="form-group radio">
|
||||||
|
<h4>Super power:</h4>
|
||||||
|
<label class="center-block"><input type="radio" formControlName="power" value="flight">Flight</label>
|
||||||
|
<label class="center-block"><input type="radio" formControlName="power" value="x-ray vision">X-ray vision</label>
|
||||||
|
<label class="center-block"><input type="radio" formControlName="power" value="strength">Strength</label>
|
||||||
|
</div>
|
||||||
|
<div class="checkbox">
|
||||||
|
<label class="center-block">
|
||||||
|
<input type="checkbox" formControlName="sidekick">I have a sidekick.
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
<!-- #enddocregion buttons -->
|
||||||
|
|
||||||
|
<p>heroForm value: {{ heroForm.value | json}}</p>
|
||||||
|
|
||||||
|
<!-- #docregion name-change-log -->
|
||||||
|
<h4>Name change log</h4>
|
||||||
|
<div *ngFor="let name of nameChangeLog">{{name}}</div>
|
||||||
|
<!-- #enddocregion name-change-log -->
|
@ -0,0 +1,113 @@
|
|||||||
|
// #docplaster
|
||||||
|
// #docregion
|
||||||
|
import { Component, Input, OnChanges } from '@angular/core';
|
||||||
|
import { FormArray, FormBuilder, FormGroup } from '@angular/forms';
|
||||||
|
|
||||||
|
import { Address, Hero, states } from '../data-model';
|
||||||
|
// #docregion import-service
|
||||||
|
import { HeroService } from '../hero.service';
|
||||||
|
// #enddocregion import-service
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-hero-detail',
|
||||||
|
templateUrl: './hero-detail.component.html',
|
||||||
|
styleUrls: ['./hero-detail.component.css']
|
||||||
|
})
|
||||||
|
|
||||||
|
// #docregion onchanges-implementation
|
||||||
|
export class HeroDetailComponent implements OnChanges {
|
||||||
|
// #enddocregion onchanges-implementation
|
||||||
|
@Input() hero: Hero;
|
||||||
|
|
||||||
|
heroForm: FormGroup;
|
||||||
|
// #docregion log-name-change
|
||||||
|
nameChangeLog: string[] = [];
|
||||||
|
// #enddocregion log-name-change
|
||||||
|
states = states;
|
||||||
|
|
||||||
|
// #docregion ctor
|
||||||
|
constructor(
|
||||||
|
private fb: FormBuilder,
|
||||||
|
private heroService: HeroService) {
|
||||||
|
|
||||||
|
this.createForm();
|
||||||
|
this.logNameChange();
|
||||||
|
}
|
||||||
|
// #enddocregion ctor
|
||||||
|
|
||||||
|
createForm() {
|
||||||
|
this.heroForm = this.fb.group({
|
||||||
|
name: '',
|
||||||
|
secretLairs: this.fb.array([]),
|
||||||
|
power: '',
|
||||||
|
sidekick: ''
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnChanges() {
|
||||||
|
this.rebuildForm();
|
||||||
|
}
|
||||||
|
|
||||||
|
rebuildForm() {
|
||||||
|
this.heroForm.reset({
|
||||||
|
name: this.hero.name
|
||||||
|
});
|
||||||
|
this.setAddresses(this.hero.addresses);
|
||||||
|
}
|
||||||
|
|
||||||
|
get secretLairs(): FormArray {
|
||||||
|
return this.heroForm.get('secretLairs') as FormArray;
|
||||||
|
};
|
||||||
|
|
||||||
|
setAddresses(addresses: Address[]) {
|
||||||
|
const addressFGs = addresses.map(address => this.fb.group(address));
|
||||||
|
const addressFormArray = this.fb.array(addressFGs);
|
||||||
|
this.heroForm.setControl('secretLairs', addressFormArray);
|
||||||
|
}
|
||||||
|
|
||||||
|
addLair() {
|
||||||
|
this.secretLairs.push(this.fb.group(new Address()));
|
||||||
|
}
|
||||||
|
|
||||||
|
// #docregion on-submit
|
||||||
|
onSubmit() {
|
||||||
|
this.hero = this.prepareSaveHero();
|
||||||
|
this.heroService.updateHero(this.hero).subscribe(/* error handling */);
|
||||||
|
this.rebuildForm();
|
||||||
|
}
|
||||||
|
// #enddocregion on-submit
|
||||||
|
|
||||||
|
// #docregion prepare-save-hero
|
||||||
|
prepareSaveHero(): Hero {
|
||||||
|
const formModel = this.heroForm.value;
|
||||||
|
|
||||||
|
// deep copy of form model lairs
|
||||||
|
const secretLairsDeepCopy: Address[] = formModel.secretLairs.map(
|
||||||
|
(address: Address) => Object.assign({}, address)
|
||||||
|
);
|
||||||
|
|
||||||
|
// return new `Hero` object containing a combination of original hero value(s)
|
||||||
|
// and deep copies of changed form model values
|
||||||
|
const saveHero: Hero = {
|
||||||
|
id: this.hero.id,
|
||||||
|
name: formModel.name as string,
|
||||||
|
// addresses: formModel.secretLairs // <-- bad!
|
||||||
|
addresses: secretLairsDeepCopy
|
||||||
|
};
|
||||||
|
return saveHero;
|
||||||
|
}
|
||||||
|
// #enddocregion prepare-save-hero
|
||||||
|
|
||||||
|
// #docregion revert
|
||||||
|
revert() { this.rebuildForm(); }
|
||||||
|
// #enddocregion revert
|
||||||
|
|
||||||
|
// #docregion log-name-change
|
||||||
|
logNameChange() {
|
||||||
|
const nameControl = this.heroForm.get('name');
|
||||||
|
nameControl.valueChanges.forEach(
|
||||||
|
(value: string) => this.nameChangeLog.push(value)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
// #enddocregion log-name-change
|
||||||
|
}
|
@ -0,0 +1,8 @@
|
|||||||
|
<!-- #docregion -->
|
||||||
|
<nav>
|
||||||
|
<a *ngFor="let hero of heroes | async" (click)="select(hero)">{{hero.name}}</a>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<div *ngIf="selectedHero">
|
||||||
|
<app-hero-detail [hero]="selectedHero"></app-hero-detail>
|
||||||
|
</div>
|
@ -0,0 +1,17 @@
|
|||||||
|
<!-- #docregion -->
|
||||||
|
<h3 *ngIf="isLoading"><i>Loading heroes ... </i></h3>
|
||||||
|
<h3 *ngIf="!isLoading">Select a hero:</h3>
|
||||||
|
|
||||||
|
<nav>
|
||||||
|
<button (click)="getHeroes()" class="btn btn-primary">Refresh</button>
|
||||||
|
<a *ngFor="let hero of heroes | async" (click)="select(hero)">{{hero.name}}</a>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<div *ngIf="selectedHero">
|
||||||
|
<hr>
|
||||||
|
<h2>Hero Detail</h2>
|
||||||
|
<h3>Editing: {{selectedHero.name}}</h3>
|
||||||
|
<!-- #docregion hero-binding -->
|
||||||
|
<app-hero-detail [hero]="selectedHero"></app-hero-detail>
|
||||||
|
<!-- #enddocregion hero-binding -->
|
||||||
|
</div>
|
@ -0,0 +1,32 @@
|
|||||||
|
// #docregion
|
||||||
|
import { Component, OnInit } from '@angular/core';
|
||||||
|
import { Observable } from 'rxjs';
|
||||||
|
import { finalize } from 'rxjs/operators';
|
||||||
|
|
||||||
|
import { Hero } from '../data-model';
|
||||||
|
import { HeroService } from '../hero.service';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-hero-list',
|
||||||
|
templateUrl: './hero-list.component.html',
|
||||||
|
styleUrls: ['./hero-list.component.css']
|
||||||
|
})
|
||||||
|
export class HeroListComponent implements OnInit {
|
||||||
|
heroes: Observable<Hero[]>;
|
||||||
|
isLoading = false;
|
||||||
|
selectedHero: Hero;
|
||||||
|
|
||||||
|
constructor(private heroService: HeroService) { }
|
||||||
|
|
||||||
|
ngOnInit() { this.getHeroes(); }
|
||||||
|
|
||||||
|
getHeroes() {
|
||||||
|
this.isLoading = true;
|
||||||
|
this.heroes = this.heroService.getHeroes()
|
||||||
|
// TODO: error handling
|
||||||
|
.pipe(finalize(() => this.isLoading = false));
|
||||||
|
this.selectedHero = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
select(hero: Hero) { this.selectedHero = hero; }
|
||||||
|
}
|
25
aio/content/examples/reactive-forms/src/app/hero.service.ts
Normal file
25
aio/content/examples/reactive-forms/src/app/hero.service.ts
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
// #docregion
|
||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
|
||||||
|
import { Observable, of } from 'rxjs';
|
||||||
|
import { delay } from 'rxjs/operators';
|
||||||
|
|
||||||
|
import { Hero, heroes } from './data-model';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class HeroService {
|
||||||
|
|
||||||
|
delayMs = 500;
|
||||||
|
|
||||||
|
// Fake server get; assume nothing can go wrong
|
||||||
|
getHeroes(): Observable<Hero[]> {
|
||||||
|
return of(heroes).pipe(delay(this.delayMs)); // simulate latency with delay
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fake server update; assume nothing can go wrong
|
||||||
|
updateHero(hero: Hero): Observable<Hero> {
|
||||||
|
const oldHero = heroes.find(h => h.id === hero.id);
|
||||||
|
const newHero = Object.assign(oldHero, hero); // Demo: mutate cached hero
|
||||||
|
return of(newHero).pipe(delay(this.delayMs)); // simulate latency with delay
|
||||||
|
}
|
||||||
|
}
|
@ -1,19 +0,0 @@
|
|||||||
:host {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
padding-top: 24px;
|
|
||||||
}
|
|
||||||
|
|
||||||
label {
|
|
||||||
display: block;
|
|
||||||
width: 6em;
|
|
||||||
margin: .5em 0;
|
|
||||||
color: #607D8B;
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
input {
|
|
||||||
height: 2em;
|
|
||||||
font-size: 1em;
|
|
||||||
padding-left: .4em;
|
|
||||||
}
|
|
@ -1,21 +0,0 @@
|
|||||||
<!-- #docregion control-binding -->
|
|
||||||
<label>
|
|
||||||
Name:
|
|
||||||
<input type="text" [formControl]="name">
|
|
||||||
</label>
|
|
||||||
|
|
||||||
<!-- #enddocregion control-binding -->
|
|
||||||
|
|
||||||
<!-- #docregion display-value -->
|
|
||||||
|
|
||||||
<p>
|
|
||||||
Value: {{ name.value }}
|
|
||||||
</p>
|
|
||||||
<!-- #enddocregion display-value -->
|
|
||||||
|
|
||||||
<!-- #docregion update-value -->
|
|
||||||
|
|
||||||
<p>
|
|
||||||
<button (click)="updateName()">Update Name</button>
|
|
||||||
</p>
|
|
||||||
<!-- #enddocregion update-value -->
|
|
@ -1,22 +0,0 @@
|
|||||||
// #docplaster
|
|
||||||
// #docregion create-control
|
|
||||||
import { Component } from '@angular/core';
|
|
||||||
import { FormControl } from '@angular/forms';
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'app-name-editor',
|
|
||||||
templateUrl: './name-editor.component.html',
|
|
||||||
styleUrls: ['./name-editor.component.css']
|
|
||||||
})
|
|
||||||
export class NameEditorComponent {
|
|
||||||
name = new FormControl('');
|
|
||||||
// #enddocregion create-control
|
|
||||||
|
|
||||||
// #docregion update-value
|
|
||||||
updateName() {
|
|
||||||
this.name.setValue('Nancy');
|
|
||||||
}
|
|
||||||
// #enddocregion update-value
|
|
||||||
// #docregion create-control
|
|
||||||
}
|
|
||||||
// #enddocregion create-control
|
|
@ -1,67 +0,0 @@
|
|||||||
<!-- #docplaster -->
|
|
||||||
<!-- #docregion formgroup -->
|
|
||||||
<form [formGroup]="profileForm">
|
|
||||||
|
|
||||||
<label>
|
|
||||||
First Name:
|
|
||||||
<input type="text" formControlName="firstName">
|
|
||||||
</label>
|
|
||||||
|
|
||||||
<label>
|
|
||||||
Last Name:
|
|
||||||
<input type="text" formControlName="lastName">
|
|
||||||
</label>
|
|
||||||
|
|
||||||
<!-- #enddocregion formgroup -->
|
|
||||||
<!-- #docregion formgroupname -->
|
|
||||||
<div formGroupName="address">
|
|
||||||
<h3>Address</h3>
|
|
||||||
|
|
||||||
<label>
|
|
||||||
Street:
|
|
||||||
<input type="text" formControlName="street">
|
|
||||||
</label>
|
|
||||||
|
|
||||||
<label>
|
|
||||||
City:
|
|
||||||
<input type="text" formControlName="city">
|
|
||||||
</label>
|
|
||||||
|
|
||||||
<label>
|
|
||||||
State:
|
|
||||||
<input type="text" formControlName="state">
|
|
||||||
</label>
|
|
||||||
|
|
||||||
<label>
|
|
||||||
Zip Code:
|
|
||||||
<input type="text" formControlName="zip">
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
<!-- #enddocregion formgroupname -->
|
|
||||||
|
|
||||||
<!-- #docregion formarrayname -->
|
|
||||||
<div formArrayName="aliases">
|
|
||||||
<h3>Aliases</h3> <button (click)="addAlias()">Add Alias</button>
|
|
||||||
|
|
||||||
<div *ngFor="let address of aliases.controls; let i=index">
|
|
||||||
<!-- The repeated alias template -->
|
|
||||||
<label>
|
|
||||||
Alias:
|
|
||||||
<input type="text" [formControlName]="i">
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<!-- #enddocregion formarrayname -->
|
|
||||||
<!-- #docregion formgroup -->
|
|
||||||
</form>
|
|
||||||
<!-- #enddocregion formgroup -->
|
|
||||||
|
|
||||||
<p>
|
|
||||||
Form Value: {{ profileForm.value | json }}
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<!-- #docregion patch-value -->
|
|
||||||
<p>
|
|
||||||
<button (click)="updateProfile()">Update Profile</button>
|
|
||||||
</p>
|
|
||||||
<!-- #enddocregion patch-value -->
|
|
@ -1,40 +0,0 @@
|
|||||||
// #docplaster
|
|
||||||
// #docregion formgroup, nested-formgroup
|
|
||||||
import { Component } from '@angular/core';
|
|
||||||
// #docregion imports
|
|
||||||
import { FormGroup, FormControl } from '@angular/forms';
|
|
||||||
// #enddocregion imports
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'app-profile-editor',
|
|
||||||
templateUrl: './profile-editor.component.html',
|
|
||||||
styleUrls: ['./profile-editor.component.css']
|
|
||||||
})
|
|
||||||
export class ProfileEditorComponent {
|
|
||||||
// #docregion formgroup-compare
|
|
||||||
profileForm = new FormGroup({
|
|
||||||
firstName: new FormControl(''),
|
|
||||||
lastName: new FormControl(''),
|
|
||||||
// #enddocregion formgroup
|
|
||||||
address: new FormGroup({
|
|
||||||
street: new FormControl(''),
|
|
||||||
city: new FormControl(''),
|
|
||||||
state: new FormControl(''),
|
|
||||||
zip: new FormControl('')
|
|
||||||
})
|
|
||||||
// #docregion formgroup
|
|
||||||
});
|
|
||||||
// #enddocregion formgroup, nested-formgroup, formgroup-compare
|
|
||||||
// #docregion patch-value
|
|
||||||
updateProfile() {
|
|
||||||
this.profileForm.patchValue({
|
|
||||||
firstName: 'Nancy',
|
|
||||||
address: {
|
|
||||||
street: '123 Drew Street'
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
// #enddocregion patch-value
|
|
||||||
// #docregion formgroup, nested-formgroup
|
|
||||||
}
|
|
||||||
// #enddocregion formgroup
|
|
@ -1,58 +0,0 @@
|
|||||||
// #docplaster
|
|
||||||
// #docregion form-builder
|
|
||||||
import { Component } from '@angular/core';
|
|
||||||
// #docregion form-builder-imports
|
|
||||||
import { FormBuilder } from '@angular/forms';
|
|
||||||
// #enddocregion form-builder-imports, form-builder
|
|
||||||
// #docregion form-array-imports
|
|
||||||
import { FormArray } from '@angular/forms';
|
|
||||||
// #docregion form-builder-imports, form-builder
|
|
||||||
// #enddocregion form-builder-imports, form-array-imports
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'app-profile-editor',
|
|
||||||
templateUrl: './profile-editor.component.html',
|
|
||||||
styleUrls: ['./profile-editor.component.css']
|
|
||||||
})
|
|
||||||
export class ProfileEditorComponent {
|
|
||||||
// #docregion formgroup-compare
|
|
||||||
profileForm = this.fb.group({
|
|
||||||
firstName: [''],
|
|
||||||
lastName: [''],
|
|
||||||
address: this.fb.group({
|
|
||||||
street: [''],
|
|
||||||
city: [''],
|
|
||||||
state: [''],
|
|
||||||
zip: ['']
|
|
||||||
}),
|
|
||||||
// #enddocregion form-builder, formgroup-compare
|
|
||||||
aliases: this.fb.array([
|
|
||||||
this.fb.control('')
|
|
||||||
])
|
|
||||||
// #docregion form-builder, formgroup-compare
|
|
||||||
});
|
|
||||||
// #enddocregion form-builder, formgroup-compare
|
|
||||||
get aliases() {
|
|
||||||
return this.profileForm.get('aliases') as FormArray;
|
|
||||||
}
|
|
||||||
|
|
||||||
// #docregion inject-form-builder, form-builder
|
|
||||||
|
|
||||||
constructor(private fb: FormBuilder) { }
|
|
||||||
// #enddocregion inject-form-builder, form-builder
|
|
||||||
|
|
||||||
updateProfile() {
|
|
||||||
this.profileForm.patchValue({
|
|
||||||
firstName: 'Nancy',
|
|
||||||
address: {
|
|
||||||
street: '123 Drew Street'
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
addAlias() {
|
|
||||||
this.aliases.push(this.fb.control(''));
|
|
||||||
}
|
|
||||||
// #docregion form-builder
|
|
||||||
}
|
|
||||||
// #enddocregion form-builder
|
|
@ -1,39 +0,0 @@
|
|||||||
/* ProfileEditorComponent's private CSS styles */
|
|
||||||
:host {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
padding-top: 24px;
|
|
||||||
}
|
|
||||||
|
|
||||||
label {
|
|
||||||
display: block;
|
|
||||||
width: 6em;
|
|
||||||
margin: .5em 0;
|
|
||||||
color: #607D8B;
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
input {
|
|
||||||
height: 2em;
|
|
||||||
font-size: 1em;
|
|
||||||
padding-left: .4em;
|
|
||||||
}
|
|
||||||
|
|
||||||
button {
|
|
||||||
font-family: Arial;
|
|
||||||
background-color: #eee;
|
|
||||||
border: none;
|
|
||||||
padding: 5px 10px;
|
|
||||||
border-radius: 4px;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
button:hover {
|
|
||||||
background-color: #cfd8dc;
|
|
||||||
}
|
|
||||||
|
|
||||||
button:disabled {
|
|
||||||
background-color: #eee;
|
|
||||||
color: #ccc;
|
|
||||||
cursor: auto;
|
|
||||||
}
|
|
@ -1,80 +0,0 @@
|
|||||||
<!-- #docplaster -->
|
|
||||||
<!-- #docregion ng-submit -->
|
|
||||||
<form [formGroup]="profileForm" (ngSubmit)="onSubmit()">
|
|
||||||
<!-- #enddocregion ng-submit -->
|
|
||||||
<label>
|
|
||||||
First Name:
|
|
||||||
<!-- #docregion required-attribute -->
|
|
||||||
<input type="text" formControlName="firstName" required>
|
|
||||||
<!-- #enddocregion required-attribute -->
|
|
||||||
</label>
|
|
||||||
|
|
||||||
<label>
|
|
||||||
Last Name:
|
|
||||||
<input type="text" formControlName="lastName">
|
|
||||||
</label>
|
|
||||||
|
|
||||||
<div formGroupName="address">
|
|
||||||
<h3>Address</h3>
|
|
||||||
|
|
||||||
<label>
|
|
||||||
Street:
|
|
||||||
<input type="text" formControlName="street">
|
|
||||||
</label>
|
|
||||||
|
|
||||||
<label>
|
|
||||||
City:
|
|
||||||
<input type="text" formControlName="city">
|
|
||||||
</label>
|
|
||||||
|
|
||||||
<label>
|
|
||||||
State:
|
|
||||||
<input type="text" formControlName="state">
|
|
||||||
</label>
|
|
||||||
|
|
||||||
<label>
|
|
||||||
Zip Code:
|
|
||||||
<input type="text" formControlName="zip">
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- #docregion formarrayname -->
|
|
||||||
<div formArrayName="aliases">
|
|
||||||
<h3>Aliases</h3> <button (click)="addAlias()">Add Alias</button>
|
|
||||||
|
|
||||||
<div *ngFor="let address of aliases.controls; let i=index">
|
|
||||||
<!-- The repeated alias template -->
|
|
||||||
<label>
|
|
||||||
Alias:
|
|
||||||
<input type="text" [formControlName]="i">
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<!-- #enddocregion formarrayname -->
|
|
||||||
|
|
||||||
<!-- #docregion submit-button -->
|
|
||||||
<button type="submit" [disabled]="!profileForm.valid">Submit</button>
|
|
||||||
<!-- #enddocregion submit-button -->
|
|
||||||
</form>
|
|
||||||
|
|
||||||
<hr>
|
|
||||||
|
|
||||||
<!-- #docregion display-value -->
|
|
||||||
|
|
||||||
<p>
|
|
||||||
Form Value: {{ profileForm.value | json }}
|
|
||||||
</p>
|
|
||||||
<!-- #enddocregion display-value -->
|
|
||||||
|
|
||||||
<!-- #docregion display-status -->
|
|
||||||
|
|
||||||
<p>
|
|
||||||
Form Status: {{ profileForm.status }}
|
|
||||||
</p>
|
|
||||||
<!-- #enddocregion display-status -->
|
|
||||||
|
|
||||||
<!-- #docregion patch-value -->
|
|
||||||
<p>
|
|
||||||
<button (click)="updateProfile()">Update Profile</button>
|
|
||||||
</p>
|
|
||||||
<!-- #enddocregion patch-value -->
|
|
@ -1,73 +0,0 @@
|
|||||||
// #docplaster
|
|
||||||
// #docregion form-builder
|
|
||||||
import { Component } from '@angular/core';
|
|
||||||
// #docregion form-builder-imports
|
|
||||||
import { FormBuilder } from '@angular/forms';
|
|
||||||
// #enddocregion form-builder-imports
|
|
||||||
// #docregion validator-imports
|
|
||||||
import { Validators } from '@angular/forms';
|
|
||||||
// #enddocregion validator-imports
|
|
||||||
// #docregion form-array-imports
|
|
||||||
import { FormArray } from '@angular/forms';
|
|
||||||
// #enddocregion form-array-imports
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'app-profile-editor',
|
|
||||||
templateUrl: './profile-editor.component.html',
|
|
||||||
styleUrls: ['./profile-editor.component.css']
|
|
||||||
})
|
|
||||||
export class ProfileEditorComponent {
|
|
||||||
// #docregion required-validator, aliases
|
|
||||||
profileForm = this.fb.group({
|
|
||||||
firstName: ['', Validators.required],
|
|
||||||
lastName: [''],
|
|
||||||
address: this.fb.group({
|
|
||||||
street: [''],
|
|
||||||
city: [''],
|
|
||||||
state: [''],
|
|
||||||
zip: ['']
|
|
||||||
}),
|
|
||||||
// #enddocregion form-builder, required-validator
|
|
||||||
aliases: this.fb.array([
|
|
||||||
this.fb.control('')
|
|
||||||
])
|
|
||||||
// #docregion form-builder, required-validator
|
|
||||||
});
|
|
||||||
// #enddocregion form-builder, required-validator, aliases
|
|
||||||
// #docregion aliases-getter
|
|
||||||
|
|
||||||
get aliases() {
|
|
||||||
return this.profileForm.get('aliases') as FormArray;
|
|
||||||
}
|
|
||||||
|
|
||||||
// #enddocregion aliases-getter
|
|
||||||
// #docregion inject-form-builder, form-builder
|
|
||||||
constructor(private fb: FormBuilder) { }
|
|
||||||
|
|
||||||
// #enddocregion inject-form-builder
|
|
||||||
|
|
||||||
updateProfile() {
|
|
||||||
this.profileForm.patchValue({
|
|
||||||
firstName: 'Nancy',
|
|
||||||
address: {
|
|
||||||
street: '123 Drew Street'
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
// #enddocregion form-builder
|
|
||||||
// #docregion add-alias
|
|
||||||
|
|
||||||
addAlias() {
|
|
||||||
this.aliases.push(this.fb.control(''));
|
|
||||||
}
|
|
||||||
// #enddocregion add-alias
|
|
||||||
// #docregion on-submit
|
|
||||||
|
|
||||||
onSubmit() {
|
|
||||||
// TODO: Use EventEmitter with form value
|
|
||||||
console.warn(this.profileForm.value);
|
|
||||||
}
|
|
||||||
// #enddocregion on-submit
|
|
||||||
// #docregion form-builder
|
|
||||||
}
|
|
||||||
// #enddocregion form-builder
|
|
17
aio/content/examples/reactive-forms/src/index-final.html
Normal file
17
aio/content/examples/reactive-forms/src/index-final.html
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<!-- #docregion -->
|
||||||
|
<html lang="en">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<title>Hero Form</title>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<!-- #docregion bootstrap -->
|
||||||
|
<link rel="stylesheet" href="https://unpkg.com/bootstrap@3.3.7/dist/css/bootstrap.min.css">
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<app-root></app-root>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
@ -2,9 +2,10 @@
|
|||||||
<!-- #docregion -->
|
<!-- #docregion -->
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<title>Angular Reactive Forms</title>
|
<title>Hero Form</title>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<link rel="stylesheet" href="https://unpkg.com/bootstrap@3.3.7/dist/css/bootstrap.min.css">
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
// tslint:disable:no-unused-variable
|
||||||
import { enableProdMode } from '@angular/core';
|
import { enableProdMode } from '@angular/core';
|
||||||
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
|
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
|
||||||
|
|
||||||
@ -9,3 +10,4 @@ if (environment.production) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
platformBrowserDynamic().bootstrapModule(AppModule);
|
platformBrowserDynamic().bootstrapModule(AppModule);
|
||||||
|
|
@ -2,11 +2,12 @@
|
|||||||
import { enableProdMode } from '@angular/core';
|
import { enableProdMode } from '@angular/core';
|
||||||
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
|
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
|
||||||
|
|
||||||
import { AppModule } from './app/app.module';
|
import { AppModule } from './app/app.module'; // just the final version
|
||||||
|
import { DemoModule } from './app/demo.module'; // demo picker
|
||||||
import { environment } from './environments/environment';
|
import { environment } from './environments/environment';
|
||||||
|
|
||||||
if (environment.production) {
|
if (environment.production) {
|
||||||
enableProdMode();
|
enableProdMode();
|
||||||
}
|
}
|
||||||
|
|
||||||
platformBrowserDynamic().bootstrapModule(AppModule);
|
platformBrowserDynamic().bootstrapModule(DemoModule);
|
||||||
|
1
aio/content/examples/reactive-forms/src/styles.1.css
Normal file
1
aio/content/examples/reactive-forms/src/styles.1.css
Normal file
@ -0,0 +1 @@
|
|||||||
|
@import url('https://unpkg.com/bootstrap@3.3.7/dist/css/bootstrap.min.css');
|
@ -3,7 +3,6 @@
|
|||||||
"files":[
|
"files":[
|
||||||
"!**/*.d.ts",
|
"!**/*.d.ts",
|
||||||
"!**/*.js",
|
"!**/*.js",
|
||||||
"!**/*.[0-9].*",
|
|
||||||
|
|
||||||
"!src/app/app.component.1.ts",
|
"!src/app/app.component.1.ts",
|
||||||
"!src/app/hero-list.component.1.html",
|
"!src/app/hero-list.component.1.html",
|
||||||
|
@ -8,7 +8,7 @@ const nums = of(1, 2, 3, 4, 5);
|
|||||||
|
|
||||||
// Create a function that accepts an Observable.
|
// Create a function that accepts an Observable.
|
||||||
const squareOddVals = pipe(
|
const squareOddVals = pipe(
|
||||||
filter((n: number) => n % 2 !== 0),
|
filter(n => n % 2),
|
||||||
map(n => n * n)
|
map(n => n * n)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -9,6 +9,6 @@ import { HeroService } from './heroes';
|
|||||||
<toh-heroes></toh-heroes>
|
<toh-heroes></toh-heroes>
|
||||||
`,
|
`,
|
||||||
styleUrls: ['./app.component.css'],
|
styleUrls: ['./app.component.css'],
|
||||||
providers: [HeroService]
|
providers: [ HeroService ]
|
||||||
})
|
})
|
||||||
export class AppComponent {}
|
export class AppComponent { }
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
// #docregion
|
// #docregion
|
||||||
/* avoid */
|
/* avoid */
|
||||||
import { Component, NgModule, OnInit } from '@angular/core';
|
|
||||||
import { BrowserModule } from '@angular/platform-browser';
|
|
||||||
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
|
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
|
||||||
|
import { BrowserModule } from '@angular/platform-browser';
|
||||||
|
import { NgModule, Component, OnInit } from '@angular/core';
|
||||||
|
|
||||||
class Hero {
|
class Hero {
|
||||||
id: number;
|
id: number;
|
||||||
@ -23,24 +24,24 @@ class AppComponent implements OnInit {
|
|||||||
heroes: Hero[] = [];
|
heroes: Hero[] = [];
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
getHeroes().then(heroes => (this.heroes = heroes));
|
getHeroes().then(heroes => this.heroes = heroes);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [BrowserModule],
|
imports: [ BrowserModule ],
|
||||||
declarations: [AppComponent],
|
declarations: [ AppComponent ],
|
||||||
exports: [AppComponent],
|
exports: [ AppComponent ],
|
||||||
bootstrap: [AppComponent]
|
bootstrap: [ AppComponent ]
|
||||||
})
|
})
|
||||||
export class AppModule {}
|
export class AppModule { }
|
||||||
|
|
||||||
platformBrowserDynamic().bootstrapModule(AppModule);
|
platformBrowserDynamic().bootstrapModule(AppModule);
|
||||||
|
|
||||||
const HEROES: Hero[] = [
|
const HEROES: Hero[] = [
|
||||||
{ id: 1, name: 'Bombasto' },
|
{id: 1, name: 'Bombasto'},
|
||||||
{ id: 2, name: 'Tornado' },
|
{id: 2, name: 'Tornado'},
|
||||||
{ id: 3, name: 'Magneta' }
|
{id: 3, name: 'Magneta'},
|
||||||
];
|
];
|
||||||
|
|
||||||
function getHeroes(): Promise<Hero[]> {
|
function getHeroes(): Promise<Hero[]> {
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
import { Hero } from './hero.model';
|
import { Hero } from './hero.model';
|
||||||
|
|
||||||
export const HEROES: Hero[] = [
|
export const HEROES: Hero[] = [
|
||||||
{ id: 1, name: 'Bombasto' },
|
{id: 1, name: 'Bombasto'},
|
||||||
{ id: 2, name: 'Tornado' },
|
{id: 2, name: 'Tornado'},
|
||||||
{ id: 3, name: 'Magneta' }
|
{id: 3, name: 'Magneta'},
|
||||||
];
|
];
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
// #docregion
|
// #docregion
|
||||||
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
|
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
|
||||||
|
|
||||||
import { AppModule } from './app/app.module';
|
import { AppModule } from './app/app.module';
|
||||||
|
|
||||||
platformBrowserDynamic().bootstrapModule(AppModule);
|
platformBrowserDynamic().bootstrapModule(AppModule);
|
||||||
|
@ -3,8 +3,9 @@
|
|||||||
/* avoid */
|
/* avoid */
|
||||||
|
|
||||||
import { ExceptionService, SpinnerService, ToastService } from '../../core';
|
import { ExceptionService, SpinnerService, ToastService } from '../../core';
|
||||||
import { HttpClient } from '@angular/common/http';
|
import { Http } from '@angular/http';
|
||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
|
import { map } from 'rxjs/operators';
|
||||||
import { Hero } from './hero.model';
|
import { Hero } from './hero.model';
|
||||||
// #enddocregion example
|
// #enddocregion example
|
||||||
|
|
||||||
@ -15,15 +16,18 @@ export class HeroService {
|
|||||||
private exceptionService: ExceptionService,
|
private exceptionService: ExceptionService,
|
||||||
private spinnerService: SpinnerService,
|
private spinnerService: SpinnerService,
|
||||||
private toastService: ToastService,
|
private toastService: ToastService,
|
||||||
private http: HttpClient
|
private http: Http
|
||||||
) { }
|
) { }
|
||||||
|
|
||||||
getHero(id: number) {
|
getHero(id: number) {
|
||||||
return this.http.get<Hero>(`api/heroes/${id}`);
|
return this.http.get(`api/heroes/${id}`).pipe(
|
||||||
|
map(response => response.json().data as Hero));
|
||||||
}
|
}
|
||||||
|
|
||||||
getHeroes() {
|
getHeroes() {
|
||||||
return this.http.get<Hero[]>(`api/heroes`);
|
return this.http.get(`api/heroes`).pipe(
|
||||||
|
map(response => response.json().data as Hero[]));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
// #docregion
|
// #docregion
|
||||||
// #docregion example
|
// #docregion example
|
||||||
import { HttpClient } from '@angular/common/http';
|
|
||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
|
import { Http } from '@angular/http';
|
||||||
|
import { map } from 'rxjs/operators';
|
||||||
|
|
||||||
import { ExceptionService, SpinnerService, ToastService } from '../../core';
|
|
||||||
import { Hero } from './hero.model';
|
import { Hero } from './hero.model';
|
||||||
|
import { ExceptionService, SpinnerService, ToastService } from '../../core';
|
||||||
// #enddocregion example
|
// #enddocregion example
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
@ -16,15 +16,18 @@ export class HeroService {
|
|||||||
private exceptionService: ExceptionService,
|
private exceptionService: ExceptionService,
|
||||||
private spinnerService: SpinnerService,
|
private spinnerService: SpinnerService,
|
||||||
private toastService: ToastService,
|
private toastService: ToastService,
|
||||||
private http: HttpClient
|
private http: Http
|
||||||
) { }
|
) { }
|
||||||
|
|
||||||
getHero(id: number) {
|
getHero(id: number) {
|
||||||
return this.http.get<Hero>(`api/heroes/${id}`);
|
return this.http.get(`api/heroes/${id}`).pipe(
|
||||||
|
map(response => response.json() as Hero));
|
||||||
}
|
}
|
||||||
|
|
||||||
getHeroes() {
|
getHeroes() {
|
||||||
return this.http.get<Hero[]>(`api/heroes`);
|
return this.http.get(`api/heroes`).pipe(
|
||||||
|
map(response => response.json() as Hero[]));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3,15 +3,15 @@
|
|||||||
<!-- #enddocregion show-hero-1 -->
|
<!-- #enddocregion show-hero-1 -->
|
||||||
|
|
||||||
<!-- #docregion show-hero-2 -->
|
<!-- #docregion show-hero-2 -->
|
||||||
<h2>{{hero.name}} Details</h2>
|
<h2>{{ hero.name }} Details</h2>
|
||||||
<div><span>id: </span>{{hero.id}}</div>
|
<div><span>id: </span>{{hero.id}}</div>
|
||||||
<div><span>name: </span>{{hero.name}}</div>
|
<div><span>name: </span>{{hero.name}}</div>
|
||||||
<!-- #enddocregion show-hero-2 -->
|
<!-- #enddocregion show-hero-2 -->
|
||||||
|
|
||||||
<!-- #docregion name-input -->
|
<!-- #docregion name-input -->
|
||||||
<div>
|
<div>
|
||||||
<label>name:
|
<label>name:
|
||||||
<input [(ngModel)]="hero.name" placeholder="name">
|
<input [(ngModel)]="hero.name" placeholder="name">
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<!-- #enddocregion name-input -->
|
<!-- #enddocregion name-input -->
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
<!-- #docregion -->
|
<!-- #docregion -->
|
||||||
<!-- #docregion pipe -->
|
<!-- #docregion pipe -->
|
||||||
<h2>{{hero.name | uppercase}} Details</h2>
|
<h2>{{ hero.name | uppercase }} Details</h2>
|
||||||
<!-- #enddocregion pipe -->
|
<!-- #enddocregion pipe -->
|
||||||
<div><span>id: </span>{{hero.id}}</div>
|
<div><span>id: </span>{{hero.id}}</div>
|
||||||
<div>
|
<div>
|
||||||
<label>name:
|
<label>name:
|
||||||
<input [(ngModel)]="hero.name" placeholder="name">
|
<input [(ngModel)]="hero.name" placeholder="name">
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
@ -14,7 +14,7 @@
|
|||||||
<div *ngIf="selectedHero">
|
<div *ngIf="selectedHero">
|
||||||
|
|
||||||
<!-- #docregion selectedHero-details -->
|
<!-- #docregion selectedHero-details -->
|
||||||
<h2>{{selectedHero.name | uppercase}} Details</h2>
|
<h2>{{ selectedHero.name | uppercase }} Details</h2>
|
||||||
<div><span>id: </span>{{selectedHero.id}}</div>
|
<div><span>id: </span>{{selectedHero.id}}</div>
|
||||||
<div>
|
<div>
|
||||||
<label>name:
|
<label>name:
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<div *ngIf="hero">
|
<div *ngIf="hero">
|
||||||
|
|
||||||
<h2>{{hero.name | uppercase}} Details</h2>
|
<h2>{{ hero.name | uppercase }} Details</h2>
|
||||||
<div><span>id: </span>{{hero.id}}</div>
|
<div><span>id: </span>{{hero.id}}</div>
|
||||||
<div>
|
<div>
|
||||||
<label>name:
|
<label>name:
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
<div *ngIf="hero">
|
<div *ngIf="hero">
|
||||||
<h2>{{hero.name | uppercase}} Details</h2>
|
<h2>{{ hero.name | uppercase }} Details</h2>
|
||||||
<div><span>id: </span>{{hero.id}}</div>
|
<div><span>id: </span>{{hero.id}}</div>
|
||||||
<div>
|
<div>
|
||||||
<label>name:
|
<label>name:
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
<div *ngIf="hero">
|
<div *ngIf="hero">
|
||||||
<h2>{{hero.name | uppercase}} Details</h2>
|
<h2>{{ hero.name | uppercase }} Details</h2>
|
||||||
<div><span>id: </span>{{hero.id}}</div>
|
<div><span>id: </span>{{hero.id}}</div>
|
||||||
<div>
|
<div>
|
||||||
<label>name:
|
<label>name:
|
||||||
|
@ -1,36 +1,16 @@
|
|||||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
import { DashboardComponent } from './dashboard.component';
|
import { DashboardComponent } from './dashboard.component';
|
||||||
import { HeroSearchComponent } from '../hero-search/hero-search.component';
|
|
||||||
|
|
||||||
import { RouterTestingModule } from '@angular/router/testing';
|
|
||||||
import { of } from 'rxjs';
|
|
||||||
import { HEROES } from '../mock-heroes';
|
|
||||||
import { HeroService } from '../hero.service';
|
|
||||||
|
|
||||||
describe('DashboardComponent', () => {
|
describe('DashboardComponent', () => {
|
||||||
let component: DashboardComponent;
|
let component: DashboardComponent;
|
||||||
let fixture: ComponentFixture<DashboardComponent>;
|
let fixture: ComponentFixture<DashboardComponent>;
|
||||||
let heroService;
|
|
||||||
let getHeroesSpy;
|
|
||||||
|
|
||||||
beforeEach(async(() => {
|
beforeEach(async(() => {
|
||||||
heroService = jasmine.createSpyObj('HeroService', ['getHeroes']);
|
|
||||||
getHeroesSpy = heroService.getHeroes.and.returnValue( of(HEROES) );
|
|
||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
declarations: [
|
declarations: [ DashboardComponent ]
|
||||||
DashboardComponent,
|
|
||||||
HeroSearchComponent
|
|
||||||
],
|
|
||||||
imports: [
|
|
||||||
RouterTestingModule.withRoutes([])
|
|
||||||
],
|
|
||||||
providers: [
|
|
||||||
{ provide: HeroService, useValue: heroService }
|
|
||||||
]
|
|
||||||
})
|
})
|
||||||
.compileComponents();
|
.compileComponents();
|
||||||
|
|
||||||
}));
|
}));
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
@ -42,17 +22,4 @@ describe('DashboardComponent', () => {
|
|||||||
it('should be created', () => {
|
it('should be created', () => {
|
||||||
expect(component).toBeTruthy();
|
expect(component).toBeTruthy();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should display "Top Heroes" as headline', () => {
|
|
||||||
expect(fixture.nativeElement.querySelector('h3').textContent).toEqual('Top Heroes');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should call heroService', async(() => {
|
|
||||||
expect(getHeroesSpy.calls.any()).toBe(true);
|
|
||||||
}));
|
|
||||||
|
|
||||||
it('should display 4 links', async(() => {
|
|
||||||
expect(fixture.nativeElement.querySelectorAll('a').length).toEqual(4);
|
|
||||||
}));
|
|
||||||
|
|
||||||
});
|
});
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user