Compare commits

...

142 Commits
5.0.x ... 4.3.4

Author SHA1 Message Date
06e479ff66 docs: add changelog for 4.3.4 2017-08-10 09:40:01 -07:00
0065868f37 release: cut the 4.3.4 release 2017-08-10 09:38:13 -07:00
77fa3c3e48 test(aio): fix tests for example-boilerplate (#18619)
PR #18520 was accidentally merged into 4.3.x, which uses a different
location for building `tsc-wrapped`. This commit reverts the changes
from #18520 that were not compatible with 4.3.x.
2017-08-09 20:28:43 -07:00
f4cb45345d test(aio): fix running docs examples against local builds (#18520)
This commit also updates the version of `@angular/cli` used for docs examples.
The previous (transient) dependency `@ngtools/webpack` was not compatible with
`@angular/compiler-cli@>=5` and was breaking when running against the local
builds (currently at 5.0.0-beta.2). The version of `@ngtools/webpack` used by
the latest `@angular/cli` version is compatible with `@angular/compiler-cli@5`.
2017-08-09 14:21:39 -07:00
9329bfb86a fix(aio): add missing code snippet (#18547)
The snippet got lost some time during the migration from the old version (it is
[present in v2][1]).

[1]: https://v2.angular.io/docs/ts/latest/cookbook/aot-compiler.html#!#running-the-application

Fixes #18544
2017-08-09 14:20:50 -07:00
3efc88fb81 test(aio): fix the deploy-to-firebase tests
This commit also ensures that if the tests fail, the script exits with an error.

closes #18595
2017-08-09 14:18:07 -07:00
954b09022a ci(aio): fix deploying the stable branch to Firebase
The `deploy-to-firebase.sh` always expects there to be a
`src/extra-files/<mode>` directory and breaks if it doesn't exist.
2017-08-09 14:17:47 -07:00
71f5e78bcb test(animations): disable buggy test in Chrome 39 (#18483)
Fixes #15793
2017-08-09 14:16:03 -07:00
f0c3ed0f14 ci(aio): fix deploying to firebase (#18590) 2017-08-08 14:00:59 -07:00
c8fd3f5237 docs(common): fix the DatePipe API docs (#18548) 2017-08-07 11:47:38 -07:00
e0660b1b72 fix(animations): support persisting dynamic styles within animation states (#18468)
Closes #18423
Closes #17505
2017-08-07 11:40:34 -07:00
5a165ebcef fix(animations): revert container/queried animations accordingly during cancel (#18516) 2017-08-07 11:39:04 -07:00
3212f8c826 docs(aio): typo & update the bio (#18559) 2017-08-07 10:20:52 -07:00
c421ccaae9 ci(aio): compute AIO deployment mode
There are now 3 modes for deployment: next, stable, archive.
We compute which mode (and other deployment properties)
from the `TRAVIS_BRANCH` and the `STABLE_BRANCH`.

If the TRAVIS_BRANCH is master we deploy as "next".
If the `TRAVIS_BRANCH` matches the `STABLE_BRANCH` we deploy as "stable".

Otherwise if the branch has a major version lower than the stable version
and its minor version is highest of similar branches we deploy as "archive".

For "archive" deployments we compute the firebase project and deployment
url based on the major version of the `TRAVIS_BRANCH`.

As well as choosing where to deploy the build, we also use this
to select the environment file for the AIO Angular app.
This will enable the app to change its rendering and behaviour
based on its mode.

See #18287
2017-08-04 09:15:38 -07:00
bbec7db7ba feat(aio): add "archive" and "next" color themes 2017-08-04 09:15:38 -07:00
00134ae4e0 feat(aio): redirect marketing pages to docs if deploy mode is archive
See #18287
2017-08-04 09:15:38 -07:00
07bd459baa feat(aio): add deploy mode to version picker
See #18287
2017-08-04 09:15:38 -07:00
302adf1081 feat(aio): enable deployment mode to be set via URL query
The deployment mode set from the environment provided at build time;
or overridden by the `mode` query parameter: e.g. `...?mode=archive`

See #18287
2017-08-04 09:15:38 -07:00
1a6a13425b feat(aio): update UI based on deployment mode
* Add a banner if the mode is "archive"
* Add a `mode-...` class to the `aio-shell` element to enable
mode based theming.

See #18287
2017-08-04 09:15:38 -07:00
072a772ca6 ci(aio): include extra files in AIO deployment based on mode
Any files that are inside the `extra-files/{mode}` folder
will be copied over to the `dist` folder before deployment
to Firebase.

See #18287
2017-08-04 09:15:38 -07:00
5f0e0a46fd ci(aio): compute AIO deployment mode
There are now 3 modes for deployment: next, stable, archive.
We compute which mode (and other deployment properties)
from the `TRAVIS_BRANCH` and the `STABLE_BRANCH`.

If the TRAVIS_BRANCH is master we deploy as "next".
Otherwise if the branch is the highest of its minor versions
we deploy as "stable" if the `TRAVIS_BRANCH` matches the `STABLE_BRANCH` or
else "archive".

For "archive" deployments we compute the firebase project and deployment
url based on the major version of the `TRAVIS_BRANCH`.

As well as choosing where to deploy the build, we also use this
to select the environment file for the AIO Angular app.
This will enable the app to change its rendering and behaviour
based on its mode.

See #18287
2017-08-04 09:15:38 -07:00
c7b72aa575 docs(aio): tech edits to form validation (PR #18495) 2017-08-03 13:58:22 -07:00
732eb61957 docs(forms): update and re-organize validation guide 2017-08-03 13:57:58 -07:00
e7e7622971 fix(compiler): ignore @import in multi-line css (#18452)
Fixes #18038
2017-08-03 11:01:24 -07:00
4176832266 refactor(router): compile router cleanly with TypeScript 2.4 (#18465) 2017-08-02 17:32:27 -07:00
71de92a189 docs(router): fix typo (#18479) 2017-08-02 17:31:33 -07:00
e0021d4cf5 refactor(platform-browser): compiler platform-browser packages cleanly (#18464) 2017-08-02 16:31:14 -07:00
4e44102e31 refactor(forms): compile forms cleanly with TypeScript 2.4 (#18462) 2017-08-02 16:29:57 -07:00
111b70d108 refactor(upgrade): compile upgrade cleanly with TypeScript 2.4 (#18461) 2017-08-02 16:28:38 -07:00
5e4054b8f3 fix(compiler): cleanly compile with TypeScript 2.4 (#18456) 2017-08-02 16:27:14 -07:00
5afc7abcb0 fix(benchpress): compile cleanly with TS 2.4 (#18455) 2017-08-02 16:24:33 -07:00
65d0888708 fix(aio): fix layout of the webpack guide (#18493)
This is possibly a temporary fix for the layout, until we decide whether we want
to remove the guide or properly add it to the SideNav menu.

Fixes #17912
2017-08-02 16:01:02 -07:00
adfd2373b8 ci(aio): Add commit message to payload data (#18137) 2017-08-02 15:59:54 -07:00
3a82af3bde docs(aio): fix missing anchor-open in i18n documentation (#18476)
The Internationalisation documentation, "Translate text nodes" section, has an incomplete
markdown anchor, and leaks markdown into the page. Fix the anchor by adding the opening bracket.
2017-08-02 15:56:25 -07:00
3af62306b4 fix(aio): correctly redirect cookbook/a1-a2-quick-reference.html (#18418)
Fixes #18415
2017-08-02 15:54:49 -07:00
afe339396f test(common): skip some DatePipe tests in old Chrome where Intl is buggy (#15784) 2017-08-02 15:52:27 -07:00
c4b51bf689 docs(aio): replace old blog link in footer (#18448)
Fixes #18233
2017-08-02 15:46:25 -07:00
b65fe3e44e ci: remove chromium fold reference (#18445) 2017-08-02 15:44:26 -07:00
116ee334fb build(aio): use cli 1.3.0-rc (#18290) 2017-08-02 15:37:39 -07:00
dbc5c5817a docs: improve github labels by introducing "PR target" labels (#18436)
I also renamed all "pr_*" lables to "PR *" lables, removed obsolete
"chore" label, and added docs label.
2017-08-02 15:31:56 -07:00
baf4ce0dd0 build: enable TSLint on the packages folder (#18459)
porting PRs #18392 and #18441 to 4.x
2017-08-02 15:23:33 -07:00
24db1ed938 docs: add changelog for 4.3.3 2017-08-02 13:00:35 -07:00
82798e9d04 release: cut the 4.3.3 release 2017-08-02 12:57:34 -07:00
da8bb1b45b docs(aio): fixed list format in FormArray section 2017-07-31 11:40:27 -07:00
f5cbc2ee25 fix(compiler): fix for element needing implicit parent placed in top-level ng-container
fixes #18314
2017-07-31 11:40:20 -07:00
cbc1986c6f fix(aio): fix links to source for paths with symlinks
Fixes #18353
2017-07-31 11:39:47 -07:00
0982f993cb ci: short-circuit npm install for aio builds that use yarn only 2017-07-31 11:39:41 -07:00
a5a29b0591 docs(aio): delay ngUpgrade e2e test to avoid flakes 2017-07-31 11:39:36 -07:00
a8f3197f24 build: short-circuit build for AIO tasks 2017-07-31 11:39:31 -07:00
e6f37120fe docs(aio): fix deprecated protractor API usage
`browser.getLocationAbsUrl()` is deprecated.
We should use `browser.getCurrentUrl()` instead.
2017-07-31 11:39:25 -07:00
6840b7bda9 ci(aio): test the example e2e files using local build of Angular 2017-07-31 11:39:21 -07:00
68f458909a build(aio): ignore generated aot files
Assets such as images and data which are generated
by the aot build were not being ignored.
2017-07-31 11:39:14 -07:00
12acecf756 docs(aio): remove generated styles.css file
This file should have been ignored as it is created
during the build of the example
2017-07-31 11:39:10 -07:00
cfbed40ab6 build(aio): support overriding the Angular packages in examples with locally built ones 2017-07-31 11:39:03 -07:00
fe1a6b8e42 build(aio): refactor and test the example-boilerplate tool 2017-07-31 11:38:59 -07:00
13e29c4e89 ci: shard the aio example e2e tests 2017-07-31 11:38:53 -07:00
fd52b178ed ci(aio): support sharding of example e2e tests 2017-07-31 11:38:48 -07:00
ca1f071b2e build(aio): upgrade @angular/material to 2.0.0-meta.8 2017-07-31 11:38:43 -07:00
296adbbb72 build(aio): upgrade @angular/* to 4.3.1 2017-07-31 11:38:37 -07:00
c795ee1176 docs(aio): fix URLSearchParams interface link to MDN
Fixes #18367
2017-07-31 11:38:33 -07:00
b550618afd refactor(aio): move content-specific images to content/images/
Fixes #17053
2017-07-31 11:38:28 -07:00
d08d6eebff refactor(aio): rename unused directories to _unused 2017-07-31 11:38:23 -07:00
e9789abd05 fix(aio): fix link to logo in example 2017-07-31 11:38:17 -07:00
f2ec2cbb99 refactor(aio): move unused images to unused directories
This prevents the ServiceWorker from prefetching unnecessary files.
2017-07-31 11:38:09 -07:00
8de2ace80a docs: add changelog for 4.3.2 2017-07-27 15:51:46 -05:00
c977994864 release: cut the 4.3.2 release 2017-07-27 15:51:46 -05:00
12b8e1af55 Revert "fix(router): should throw when lazy loaded module doesn't define any routes (#15001)"
This reverts commit be49e0ee93.
2017-07-27 13:05:36 -07:00
9a188485f5 ci: correct bad bazel merge 2017-07-27 12:43:29 -07:00
45a10419bc Revert "build: Bazel builds ngfactories for packages/core (#18289)"
This reverts commit bcea196530.
2017-07-27 10:51:41 -07:00
2245748c14 test(compiler): fix xliff2 integration test for the 4.3 branch (#18363)
Closes #18363
2017-07-27 10:20:34 -07:00
bcea196530 build: Bazel builds ngfactories for packages/core (#18289)
PR Close #18289
2017-07-26 16:31:26 -07:00
b9e32c833a fix(router): child CanActivate guard should wait for parent to complete (#18110)
Closes #15670

PR Close #18110
2017-07-26 17:52:47 -05:00
be49e0ee93 fix(router): should throw when lazy loaded module doesn't define any routes (#15001)
Closes #14596

PR Close #15001
2017-07-26 16:49:50 -05:00
bf95655a1a docs(aio): add my details as a contributor (#18315)
PR Close #18315
2017-07-26 16:49:50 -05:00
6bf5b84fa4 fix(aio): correctly process markdown link in "Browser Support" (#18349)
The markdown processor expects an empty line between an opening tag and the
markdown content. (If there is no empty line, the content is interpreted as
plain HTML.)
Previously, the line between the opening `<td>` and the content contained
whitespace, which caused the content to be interpreted as HTML and not markdown.

Fixes #18312

PR Close #18349
2017-07-26 16:11:11 -05:00
4836565ca7 docs(aio): update examples to 4.3 2017-07-26 12:08:29 -05:00
750e4e8156 docs(aio) - Fixed link to the glossary dash-case term (#18311)
PR Close #18311
2017-07-26 12:08:29 -05:00
a0846194b7 fix(compiler): add equiv & disp attributes to Xliff2 ICU placeholders (#18283)
Fixes #17344

PR Close #18283
2017-07-26 12:08:29 -05:00
bcf6b90c95 docs(platform-server): inline PlatformOptions and add doc strings (#18264)
Fix documentation for the options passed into renderModule and
renderModuleFactory.

PR Close #18264
2017-07-26 12:08:29 -05:00
3ca2a0aa37 refactor(compiler-cli): add support for browser compiler bundle (#17979)
PR Close #17979
2017-07-26 12:08:29 -05:00
b4be96c65d ci: use chrome stable (#18307) 2017-07-26 12:08:29 -05:00
434ff5fecb build: update bazel rules to latest (#18289) 2017-07-26 12:08:24 -05:00
a1bb9c2d42 fix(core): invoke error handler outside of the Angular Zone (#18269)
In Node.JS console.log/error/warn functions actually resuls in a socket
write which in turn is considered by Zone.js as an async task.

This means that if there is any exception during change detection in a platform-server
application the error handler will make the Angular Zone unstable which
in turn will cause change detection to run on next tick and cause an
infinite loop.

It is also better to run the error handler outside of the Angular Zone
in general on all platforms so that an error in the error handler itself doesn't cause an
infinite loop.

Fixes #17073, #7774.

PR Close #18269
2017-07-26 12:04:47 -05:00
7e626bef0a ci: force precise on Travis (#18282)
PR Close #18282
2017-07-26 12:04:47 -05:00
a1e83a8ed2 test: fix bad merge (#18267)
PR Close #18267
2017-07-26 12:04:47 -05:00
cbeb197aa5 fix(animations): export BrowserModule as apart of BrowserAnimationsModule (#18263)
PR Close #18263
2017-07-26 12:04:47 -05:00
0330fa6b82 docs(aio): pngcrush all pngs (#18243)
PR Close #18243
2017-07-26 12:04:47 -05:00
97135e8fd5 fix(platform-server): don't clobber parse5 properties when setting (#18237)
element properties.

Fixes #17050.

We now store all element properties in a separate 'properties' bag.

PR Close #18237
2017-07-26 12:04:47 -05:00
35bd07fc7b refactor(common): CleanUp HttpClient's imports (#18120)
PR Close #18120
2017-07-26 12:04:47 -05:00
a8ac77b645 fix(compiler): allow numbers for ICU message cases in lexer (#18095)
Closes #18095
Fixes #17799
2017-07-26 12:04:47 -05:00
9ecd377a51 ci(aio): fix aio payload script 2017-07-26 12:04:47 -05:00
76171bd8b4 test(upgrade): fail tests when there are AngularJS errors (#18209) 2017-07-26 12:04:47 -05:00
1f106d75bc fix(upgrade): throw error if trying to get injector before setting (#18209)
Previously, `undefined` would be returned.
This change makes it easier to identify incorrect uses/bugs.
(Discussed in https://github.com/angular/angular/pull/18213#issuecomment-316191308.)
2017-07-26 12:04:47 -05:00
a4fae8c405 aio: debounce search and delay index building (#18134)
* feat(aio): debounce search requests

* feat(aio): delay loading search worker and index
2017-07-26 12:04:46 -05:00
33c07b3394 fix(aio): invalid formatting in architecure.md (#18159)
Introduced in e110a80caf (diff-9ac9c6a9277eea9856d75249a7c0a40aL127)
2017-07-26 12:00:01 -05:00
c9d06e676f docs(aio): replace old blog link (#18252)
Fixes #18233
* Docs(aio): Replaced old blog link Now with the Link to the new Angular.io Blog
* Removed double braces
2017-07-26 12:00:01 -05:00
c7c65d9fda docs: fixing invisible tag in README (#18245) 2017-07-26 12:00:01 -05:00
257a9e3e6f ci: test bazel builds on travis (#18240) 2017-07-26 12:00:01 -05:00
c7c0a1688e docs(aio): Fixed typo with closing div 2017-07-26 12:00:00 -05:00
7e95e2b0ba docs(aio): Fix http guide 2017-07-26 12:00:00 -05:00
ddc286f4b5 docs: fix wrong link in CONTRIBUTING.md (#18228)
The link to the CONTRIBUTING.md file was not correct
2017-07-26 12:00:00 -05:00
3d17a3672e docs(router): minor typo (#18226)
Fix a minor typo in the description of a router spec.
2017-07-26 12:00:00 -05:00
61d253f5fd docs(aio): fix typo in NgModule FAQs (#18211)
Fix #18133. Fix typo 'added' to 'add' in the 'Why does lazy loading create a child injector' section.
2017-07-26 12:00:00 -05:00
54be25a7a1 docs: add changelog for 4.3.1 2017-07-19 12:54:45 -07:00
b1757037fb release: cut the 4.3.1 release 2017-07-19 12:53:17 -07:00
f0476fcff0 fix(compiler-cli): don't generate empty <target/> when extracting xliff
Fixes #15754
2017-07-19 09:50:09 -07:00
a5c4bb5b96 fix(animations): make sure @.disabled works in non-animation components
Note 4.3 only!

Prior to this fix when [@.disabled] was used in a component that
contained zero animation code it wouldn't register properly because the
renderer associated with that component was not an animation renderer.
This patch ensures that it gets registered even when there are no
animations set.
2017-07-19 09:50:03 -07:00
4c1f32b0db fix(animations): do not crash animations if a nested component fires CD during CD
Closes #18193
2017-07-19 09:49:57 -07:00
383d8969ab fix(animations): always camelcase style property names that contain auto styles
Closes #17938
2017-07-19 09:49:47 -07:00
333ffd8d32 fix(animations): capture cancelled animation styles within grouped animations
Closes #17170
2017-07-19 09:49:42 -07:00
d4679a0bc2 docs(aio): fix typo in Router documentation
Fix title and link to RouteConfigLoadEnd documentation
2017-07-19 15:02:14 +01:00
4ce29f3a5b fix(platform-server): provide XhrFactory for HttpClient 2017-07-18 14:19:18 -07:00
17b7bc3e06 fix(common): send flushed body as error instead of null
fix #18181
2017-07-18 10:58:27 -07:00
f19bd5f4f3 docs(http): Make name of injected HttpTestingController consistent 2017-07-18 10:54:42 -07:00
d503d25f29 docs(platform-server): add doc string for PlatformOptions 2017-07-18 10:54:37 -07:00
5d275e994a fix(router): terminal route in custom matcher 2017-07-18 10:54:32 -07:00
d8c8b13bb8 docs: fix typo 2017-07-18 10:54:26 -07:00
4671168635 fix(compiler): ensure jit external id arguments names are unique
Fixes: #17558, #17378, #8676
2017-07-18 10:54:21 -07:00
1ac78bfd5d fix(router): canDeactivate guards should run from bottom to top
Closes #15657.
2017-07-18 10:54:15 -07:00
4340beacea fix(router): should navigate to the same url when config changes
Closes #15535
2017-07-18 10:54:09 -07:00
ec89f378fc fix(router): should run resolvers for the same route concurrently
Fixes #14279
2017-07-18 10:54:04 -07:00
4dd6863bc2 docs(aio): fix HttpClient setting new header sample 2017-07-18 10:53:58 -07:00
37c626e673 fix(aio): remove title attribute from CodeExampleComponent
This was causing browser to add an unwanted tooltip that appeared
when the user hovers over the code.

See #17524
2017-07-18 17:55:54 +01:00
f0a110928b fix(aio): add quote to module 2017-07-18 17:54:51 +01:00
c39e7d1eb2 fix(aio): do not wrap <code-tabs> tab labels
Fixes #17751
2017-07-18 17:45:20 +01:00
799bffb431 docs(aio): fix cheatsheet layout for narrow screens
* Tell the app that this will have no Table of Contents, since we have no
h2 headings anyway.
* Remove all the `nbsp;` from the code since that doesn't help with layout
* Remove side padding from sidenav-content when screen is narrow
* Restyle the cheatsheet table when the screen is narrow
2017-07-18 17:33:36 +01:00
fda607cc2f build(aio): fail doc-gen if referenced images are missing 2017-07-18 11:46:15 +01:00
cc3aa68123 docs(aio): fix broken image sources 2017-07-18 11:46:15 +01:00
306621d2d6 docs(aio): fix up broken links 2017-07-18 11:46:15 +01:00
d204f7aa2a build(aio): abort doc-gen on dangling links 2017-07-18 11:46:14 +01:00
a94f5e8cbb build(aio): abort doc-gen if an example is missing
Closes #16936
2017-07-18 11:46:14 +01:00
1390afef23 docs: fix HttpClient sample 2017-07-17 14:18:41 -07:00
b0346a6e45 docs(aio): fix HttpClient's interceptor sample 2017-07-17 14:18:41 -07:00
e5da059994 docs(http): fix "Expecting and answering requests" example mistake
Possibly overlooked testing documentation mistake fixed:
- `http.get('/data')` probably ought to be paired with `httpMock.expectOne('/data')` instead of `httpMock.expectOne('/data')`.
2017-07-17 14:18:41 -07:00
ac92c3bb26 docs: fix HttpClient logging's sample 2017-07-17 14:18:41 -07:00
87157d7089 docs(http): fixed syntax error in AuthInterceptor example 2017-07-17 14:18:41 -07:00
611dd12f0f ci: add GK and PBD to aio content and marketing groups 2017-07-17 14:18:41 -07:00
969ce9dc2b fix(aio): remove title from callout 2017-07-15 15:39:40 +01:00
34834a9e79 fix(aio): remove unused news.html file
Although outdated and not used, the file would be picked up and showed in search
results.
2017-07-15 15:29:59 +01:00
6e2ddccc2c build(aio): render type parameters of API function exports
Fixes #18123
2017-07-15 08:54:34 +01:00
55742e4737 Revert "revert: revert: ci(aio): exclude changes in aio/content folder"
This reverts commit 3d85f72652.

Still causing repeated flakes on master.
2017-07-14 14:55:56 -07:00
0091b1e8db refactor(upgrade): clean up some types 2017-07-14 14:55:46 -07:00
378 changed files with 5013 additions and 3057 deletions

View File

@ -41,7 +41,7 @@ jobs:
- restore_cache:
key: angular-{{ .Branch }}-{{ checksum "npm-shrinkwrap.json" }}
- run: bazel run @io_bazel_rules_typescript_node//:bin/npm install
- run: bazel run @build_bazel_rules_typescript_node//:bin/npm install
- run: bazel build ...
- save_cache:
key: angular-{{ .Branch }}-{{ checksum "npm-shrinkwrap.json" }}

View File

@ -256,6 +256,8 @@ groups:
files:
include:
- "aio/*"
exclude:
- "aio/content/*"
users:
- petebacondarwin #primary
- IgorMinar
@ -276,6 +278,8 @@ groups:
- Foxandxss
- stephenfluin
- wardbell
- petebacondarwin
- gkalpak
- IgorMinar #fallback
- mhevery #fallback
@ -289,5 +293,7 @@ groups:
users:
- juleskremer #primary
- stephenfluin
- petebacondarwin
- gkalpak
- IgorMinar #fallback
- mhevery #fallback

View File

@ -1,9 +1,12 @@
language: node_js
sudo: false
# force trusty as Google Chrome addon is not supported on Precise
dist: trusty
node_js:
- '6.9.5'
addons:
chrome: stable
# firefox: "38.0"
apt:
sources:
@ -50,16 +53,17 @@ env:
- CI_MODE=browserstack_required
- CI_MODE=saucelabs_optional
- CI_MODE=browserstack_optional
- CI_MODE=docs_test
- CI_MODE=aio_tools_test
- CI_MODE=aio
- CI_MODE=aio_e2e
- CI_MODE=aio_e2e AIO_SHARD=0
- CI_MODE=aio_e2e AIO_SHARD=1
- CI_MODE=bazel
matrix:
fast_finish: true
allow_failures:
- env: "CI_MODE=saucelabs_optional"
- env: "CI_MODE=browserstack_optional"
- env: "CI_MODE=aio_e2e"
before_install:
# source the env.sh script so that the exported variables are available to other scripts later on

View File

@ -11,8 +11,15 @@ filegroup(
# This won't scale in the general case.
# TODO(alexeagle): figure out what to do
"node_modules/typescript/**",
"node_modules/zone.js/**/*.d.ts",
"node_modules/zone.js/**",
"node_modules/rxjs/**/*.d.ts",
"node_modules/rxjs/**/*.js",
"node_modules/@types/**/*.d.ts",
"node_modules/tsickle/**",
"node_modules/hammerjs/**/*.d.ts",
"node_modules/protobufjs/**",
"node_modules/bytebuffer/**",
"node_modules/reflect-metadata/**",
"node_modules/minimist/**/*.js",
]),
)

View File

@ -1,3 +1,59 @@
<a name="4.3.4"></a>
## [4.3.4](https://github.com/angular/angular/compare/4.3.3...4.3.4) (2017-08-10)
### Bug Fixes
* **animations:** revert container/queried animations accordingly during cancel ([#18516](https://github.com/angular/angular/issues/18516)) ([5a165eb](https://github.com/angular/angular/commit/5a165eb))
* **animations:** support persisting dynamic styles within animation states ([#18468](https://github.com/angular/angular/issues/18468)) ([e0660b1](https://github.com/angular/angular/commit/e0660b1)), closes [#18423](https://github.com/angular/angular/issues/18423) [#17505](https://github.com/angular/angular/issues/17505)
* **benchpress:** compile cleanly with TS 2.4 ([#18455](https://github.com/angular/angular/issues/18455)) ([5afc7ab](https://github.com/angular/angular/commit/5afc7ab))
* **compiler:** cleanly compile with TypeScript 2.4 ([#18456](https://github.com/angular/angular/issues/18456)) ([5e4054b](https://github.com/angular/angular/commit/5e4054b))
* **compiler:** ignore [@import](https://github.com/import) in multi-line css ([#18452](https://github.com/angular/angular/issues/18452)) ([e7e7622](https://github.com/angular/angular/commit/e7e7622)), closes [#18038](https://github.com/angular/angular/issues/18038)
<a name="4.3.3"></a>
## [4.3.3](https://github.com/angular/angular/compare/4.3.2...4.3.3) (2017-08-02)
### Bug Fixes
* **compiler:** fix for element needing implicit parent placed in top-level ng-container ([f5cbc2e](https://github.com/angular/angular/commit/f5cbc2e)), closes [#18314](https://github.com/angular/angular/issues/18314)
<a name="4.3.2"></a>
## [4.3.2](https://github.com/angular/angular/compare/4.3.1...4.3.2) (2017-07-26)
### Bug Fixes
* **animations:** export BrowserModule as apart of BrowserAnimationsModule ([#18263](https://github.com/angular/angular/issues/18263)) ([cbeb197](https://github.com/angular/angular/commit/cbeb197))
* **compiler:** add equiv & disp attributes to Xliff2 ICU placeholders ([#18283](https://github.com/angular/angular/issues/18283)) ([a084619](https://github.com/angular/angular/commit/a084619)), closes [#17344](https://github.com/angular/angular/issues/17344)
* **compiler:** allow numbers for ICU message cases in lexer ([#18095](https://github.com/angular/angular/issues/18095)) ([a8ac77b](https://github.com/angular/angular/commit/a8ac77b)), closes [#17799](https://github.com/angular/angular/issues/17799)
* **core:** invoke error handler outside of the Angular Zone ([#18269](https://github.com/angular/angular/issues/18269)) ([a1bb9c2](https://github.com/angular/angular/commit/a1bb9c2)), closes [#17073](https://github.com/angular/angular/issues/17073) [#7774](https://github.com/angular/angular/issues/7774)
* **platform-server:** don't clobber parse5 properties when setting ([#18237](https://github.com/angular/angular/issues/18237)) ([97135e8](https://github.com/angular/angular/commit/97135e8)), closes [#17050](https://github.com/angular/angular/issues/17050)
* **router:** child CanActivate guard should wait for parent to complete ([#18110](https://github.com/angular/angular/issues/18110)) ([b9e32c8](https://github.com/angular/angular/commit/b9e32c8)), closes [#15670](https://github.com/angular/angular/issues/15670)
* **router:** should throw when lazy loaded module doesn't define any routes ([#15001](https://github.com/angular/angular/issues/15001)) ([be49e0e](https://github.com/angular/angular/commit/be49e0e)), closes [#14596](https://github.com/angular/angular/issues/14596)
* **upgrade:** throw error if trying to get injector before setting ([#18209](https://github.com/angular/angular/issues/18209)) ([1f106d7](https://github.com/angular/angular/commit/1f106d7))
<a name="4.3.1"></a>
## [4.3.1](https://github.com/angular/angular/compare/4.3.0...4.3.1) (2017-07-19)
### Bug Fixes
* **animations:** always camelcase style property names that contain auto styles ([383d896](https://github.com/angular/angular/commit/383d896)), closes [#17938](https://github.com/angular/angular/issues/17938)
* **animations:** capture cancelled animation styles within grouped animations ([333ffd8](https://github.com/angular/angular/commit/333ffd8)), closes [#17170](https://github.com/angular/angular/issues/17170)
* **animations:** do not crash animations if a nested component fires CD during CD ([4c1f32b](https://github.com/angular/angular/commit/4c1f32b)), closes [#18193](https://github.com/angular/angular/issues/18193)
* **animations:** make sure @.disabled works in non-animation components ([a5c4bb5](https://github.com/angular/angular/commit/a5c4bb5))
* **common:** send flushed body as error instead of null ([17b7bc3](https://github.com/angular/angular/commit/17b7bc3)), closes [#18181](https://github.com/angular/angular/issues/18181)
* **compiler:** ensure jit external id arguments names are unique ([4671168](https://github.com/angular/angular/commit/4671168))
* **compiler-cli:** don't generate empty `<target/>` when extracting xliff ([f0476fc](https://github.com/angular/angular/commit/f0476fc)), closes [#15754](https://github.com/angular/angular/issues/15754)
* **platform-server:** provide XhrFactory for HttpClient ([4ce29f3](https://github.com/angular/angular/commit/4ce29f3))
* **router:** canDeactivate guards should run from bottom to top ([1ac78bf](https://github.com/angular/angular/commit/1ac78bf)), closes [#15657](https://github.com/angular/angular/issues/15657)
* **router:** should navigate to the same url when config changes ([4340bea](https://github.com/angular/angular/commit/4340bea)), closes [#15535](https://github.com/angular/angular/issues/15535)
* **router:** should run resolvers for the same route concurrently ([ec89f37](https://github.com/angular/angular/commit/ec89f37)), closes [#14279](https://github.com/angular/angular/issues/14279)
* **router:** terminal route in custom matcher ([5d275e9](https://github.com/angular/angular/commit/5d275e9))
<a name="4.3.0"></a>
# [4.3.0](https://github.com/angular/angular/compare/4.3.0-rc.0...4.3.0) (2017-07-14)

View File

@ -1,11 +1,17 @@
load("@bazel_tools//tools/build_defs/repo:git.bzl", "git_repository")
git_repository(
name = "io_bazel_rules_typescript",
name = "build_bazel_rules_typescript",
remote = "https://github.com/bazelbuild/rules_typescript.git",
commit = "3a8404d",
tag = "0.0.5",
)
load("@io_bazel_rules_typescript//:defs.bzl", "node_repositories")
load("@build_bazel_rules_typescript//:defs.bzl", "node_repositories")
node_repositories(package_json = "//:package.json")
git_repository(
name = "build_bazel_rules_angular",
remote = "https://github.com/bazelbuild/rules_angular.git",
tag = "0.0.1",
)

View File

@ -31,8 +31,9 @@
"environmentSource": "environments/environment.ts",
"environments": {
"dev": "environments/environment.ts",
"stage": "environments/environment.stage.ts",
"prod": "environments/environment.prod.ts"
"next": "environments/environment.next.ts",
"stable": "environments/environment.stable.ts",
"archive": "environments/environment.archive.ts"
}
}
],

View File

@ -43,13 +43,9 @@ dist/
**/app/**/*.ajs.js
# aot
**/*.ngfactory.ts
**/*.ngsummary.json
**/*.ngsummary.ts
**/*.shim.ngstyle.ts
**/*.metadata.json
!aot/bs-config.json
!aot/index.html
*/aot/**/*
!*/aot/bs-config.json
!*/aot/index.html
!rollup-config.js
# i18n

View File

@ -9,30 +9,20 @@ describe('Form Validation Tests', function () {
browser.get('');
});
describe('Hero Form 1', () => {
describe('Template-driven form', () => {
beforeAll(() => {
getPage('hero-form-template1');
getPage('hero-form-template');
});
tests();
tests('Template-Driven Form');
});
describe('Hero Form 2', () => {
describe('Reactive form', () => {
beforeAll(() => {
getPage('hero-form-template2');
getPage('hero-form-reactive');
});
tests();
bobTests();
});
describe('Hero Form 3 (Reactive)', () => {
beforeAll(() => {
getPage('hero-form-reactive3');
makeNameTooLong();
});
tests();
tests('Reactive Form');
bobTests();
});
});
@ -48,6 +38,7 @@ let page: {
nameInput: ElementFinder,
alterEgoInput: ElementFinder,
powerSelect: ElementFinder,
powerOption: ElementFinder,
errorMessages: ElementArrayFinder,
heroFormButtons: ElementArrayFinder,
heroSubmitted: ElementFinder
@ -64,19 +55,21 @@ function getPage(sectionTag: string) {
nameInput: section.element(by.css('#name')),
alterEgoInput: section.element(by.css('#alterEgo')),
powerSelect: section.element(by.css('#power')),
powerOption: section.element(by.css('#power option')),
errorMessages: section.all(by.css('div.alert')),
heroFormButtons: buttons,
heroSubmitted: section.element(by.css('hero-submitted > div'))
heroSubmitted: section.element(by.css('.submitted-message'))
};
}
function tests() {
function tests(title: string) {
it('should display correct title', function () {
expect(page.title.getText()).toContain('Hero Form');
expect(page.title.getText()).toContain(title);
});
it('should not display submitted message before submit', function () {
expect(page.heroSubmitted.isElementPresent(by.css('h2'))).toBe(false);
expect(page.heroSubmitted.isElementPresent(by.css('p'))).toBe(false);
});
it('should have form buttons', function () {
@ -130,11 +123,11 @@ function tests() {
it('should hide form after submit', function () {
page.heroFormButtons.get(0).click();
expect(page.title.isDisplayed()).toBe(false);
expect(page.heroFormButtons.get(0).isDisplayed()).toBe(false);
});
it('submitted form should be displayed', function () {
expect(page.heroSubmitted.isElementPresent(by.css('h2'))).toBe(true);
expect(page.heroSubmitted.isElementPresent(by.css('p'))).toBe(true);
});
it('submitted form should have new hero name', function () {
@ -142,9 +135,9 @@ function tests() {
});
it('clicking edit button should reveal form again', function () {
const editBtn = page.heroSubmitted.element(by.css('button'));
editBtn.click();
expect(page.heroSubmitted.isElementPresent(by.css('h2')))
const newFormBtn = page.heroSubmitted.element(by.css('button'));
newFormBtn.click();
expect(page.heroSubmitted.isElementPresent(by.css('p')))
.toBe(false, 'submitted hidden again');
expect(page.title.isDisplayed()).toBe(true, 'can see form title');
});
@ -159,9 +152,13 @@ function expectFormIsInvalid() {
}
function bobTests() {
const emsg = 'Someone named "Bob" cannot be a hero.';
const emsg = 'Name cannot be Bob.';
it('should produce "no bob" error after setting name to "Bobby"', function () {
// Re-populate select element
page.powerSelect.click();
page.powerOption.click();
page.nameInput.clear();
page.nameInput.sendKeys('Bobby');
expectFormIsInvalid();
@ -174,8 +171,3 @@ function bobTests() {
expectFormIsValid();
});
}
function makeNameTooLong() {
// make the first name invalid
page.nameInput.sendKeys('ThisHeroNameHasWayWayTooManyLetters');
}

View File

@ -3,10 +3,8 @@ import { Component } from '@angular/core';
@Component({
selector: 'my-app',
template: `<hero-form-template1></hero-form-template1>
template: `<hero-form-template></hero-form-template>
<hr>
<hero-form-template2></hero-form-template2>
<hr>
<hero-form-reactive3></hero-form-reactive3>`
<hero-form-reactive></hero-form-reactive>`
})
export class AppComponent { }

View File

@ -1,18 +1,26 @@
// #docregion
import { NgModule } from '@angular/core';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { BrowserModule } from '@angular/platform-browser';
import { AppComponent } from './app.component';
import { HeroFormTemplateModule } from './template/hero-form-template.module';
import { HeroFormReactiveModule } from './reactive/hero-form-reactive.module';
import { HeroFormTemplateComponent } from './template/hero-form-template.component';
import { HeroFormReactiveComponent } from './reactive/hero-form-reactive.component';
import { ForbiddenValidatorDirective } from './shared/forbidden-name.directive';
@NgModule({
imports: [
BrowserModule,
HeroFormTemplateModule,
HeroFormReactiveModule
FormsModule,
ReactiveFormsModule
],
declarations: [
AppComponent,
HeroFormTemplateComponent,
HeroFormReactiveComponent,
ForbiddenValidatorDirective
],
declarations: [ AppComponent ],
bootstrap: [ AppComponent ]
})
export class AppModule { }

View File

@ -1,26 +1,38 @@
<!-- #docregion -->
<div class="container">
<div [hidden]="submitted">
<h1>Hero Form 3 (Reactive)</h1>
<!-- #docregion form-tag-->
<form [formGroup]="heroForm" *ngIf="active" (ngSubmit)="onSubmit()">
<!-- #enddocregion form-tag-->
<div class="form-group">
<!-- #docregion name-with-error-msg -->
<label for="name">Name</label>
<input type="text" id="name" class="form-control"
<h1>Reactive Form</h1>
<form [formGroup]="heroForm" #formDir="ngForm">
<div [hidden]="formDir.submitted">
<div class="form-group">
<label for="name">Name</label>
<!-- #docregion name-with-error-msg -->
<input id="name" class="form-control"
formControlName="name" required >
<div *ngIf="formErrors.name" class="alert alert-danger">
{{ formErrors.name }}
<div *ngIf="name.invalid && (name.dirty || name.touched)"
class="alert alert-danger">
<div *ngIf="name.errors.required">
Name is required.
</div>
<div *ngIf="name.errors.minlength">
Name must be at least 4 characters long.
</div>
<div *ngIf="name.errors.forbiddenName">
Name cannot be Bob.
</div>
</div>
<!-- #enddocregion name-with-error-msg -->
</div>
<div class="form-group">
<label for="alterEgo">Alter Ego</label>
<input type="text" id="alterEgo" class="form-control"
<input id="alterEgo" class="form-control"
formControlName="alterEgo" >
</div>
@ -31,17 +43,20 @@
<option *ngFor="let p of powers" [value]="p">{{p}}</option>
</select>
<div *ngIf="formErrors.power" class="alert alert-danger">
{{ formErrors.power }}
<div *ngIf="power.invalid && power.touched" class="alert alert-danger">
<div *ngIf="power.errors.required">Power is required.</div>
</div>
</div>
<button type="submit" class="btn btn-default"
[disabled]="!heroForm.valid">Submit</button>
[disabled]="heroForm.invalid">Submit</button>
<button type="button" class="btn btn-default"
(click)="addHero()">New Hero</button>
</form>
</div>
(click)="formDir.resetForm({})">Reset</button>
</div>
</form>
<hero-submitted [hero]="hero" [(submitted)]="submitted"></hero-submitted>
<div class="submitted-message" *ngIf="formDir.submitted">
<p>You've submitted your hero, {{ heroForm.value.name }}!</p>
<button (click)="formDir.resetForm({})">Add new hero</button>
</div>
</div>

View File

@ -2,115 +2,39 @@
// #docplaster
// #docregion
import { Component, OnInit } from '@angular/core';
import { FormGroup, FormBuilder, Validators } from '@angular/forms';
import { Hero } from '../shared/hero';
import { FormControl, FormGroup, Validators } from '@angular/forms';
import { forbiddenNameValidator } from '../shared/forbidden-name.directive';
@Component({
selector: 'hero-form-reactive3',
selector: 'hero-form-reactive',
templateUrl: './hero-form-reactive.component.html'
})
export class HeroFormReactiveComponent implements OnInit {
powers = ['Really Smart', 'Super Flexible', 'Weather Changer'];
hero = new Hero(18, 'Dr. WhatIsHisName', this.powers[0], 'Dr. What');
hero = {name: 'Dr.', alterEgo: 'Dr. What', power: this.powers[0]};
submitted = false;
// #docregion on-submit
onSubmit() {
this.submitted = true;
this.hero = this.heroForm.value;
}
// #enddocregion on-submit
// #enddocregion
// Reset the form with a new hero AND restore 'pristine' class state
// by toggling 'active' flag which causes the form
// to be removed/re-added in a tick via NgIf
// TODO: Workaround until NgForm has a reset method (#6822)
active = true;
// #docregion class
// #docregion add-hero
addHero() {
this.hero = new Hero(42, '', '');
this.buildForm();
// #enddocregion add-hero
// #enddocregion class
this.active = false;
setTimeout(() => this.active = true, 0);
// #docregion
// #docregion add-hero
}
// #enddocregion add-hero
// #docregion form-builder
heroForm: FormGroup;
constructor(private fb: FormBuilder) { }
// #docregion form-group
ngOnInit(): void {
this.buildForm();
}
buildForm(): void {
this.heroForm = this.fb.group({
// #docregion name-validators
'name': [this.hero.name, [
Validators.required,
Validators.minLength(4),
Validators.maxLength(24),
forbiddenNameValidator(/bob/i)
]
],
// #enddocregion name-validators
'alterEgo': [this.hero.alterEgo],
'power': [this.hero.power, Validators.required]
// #docregion custom-validator
this.heroForm = new FormGroup({
'name': new FormControl(this.hero.name, [
Validators.required,
Validators.minLength(4),
forbiddenNameValidator(/bob/i) // <-- Here's how you pass in the custom validator.
]),
'alterEgo': new FormControl(this.hero.alterEgo),
'power': new FormControl(this.hero.power, Validators.required)
});
this.heroForm.valueChanges
.subscribe(data => this.onValueChanged(data));
this.onValueChanged(); // (re)set validation messages now
// #enddocregion custom-validator
}
// #enddocregion form-builder
get name() { return this.heroForm.get('name'); }
onValueChanged(data?: any) {
if (!this.heroForm) { return; }
const form = this.heroForm;
for (const field in this.formErrors) {
// clear previous error message (if any)
this.formErrors[field] = '';
const control = form.get(field);
if (control && control.dirty && !control.valid) {
const messages = this.validationMessages[field];
for (const key in control.errors) {
this.formErrors[field] += messages[key] + ' ';
}
}
}
}
formErrors = {
'name': '',
'power': ''
};
validationMessages = {
'name': {
'required': 'Name is required.',
'minlength': 'Name must be at least 4 characters long.',
'maxlength': 'Name cannot be more than 24 characters long.',
'forbiddenName': 'Someone named "Bob" cannot be a hero.'
},
'power': {
'required': 'Power is required.'
}
};
get power() { return this.heroForm.get('power'); }
// #enddocregion form-group
}
// #enddocregion

View File

@ -1,13 +0,0 @@
// #docregion
import { NgModule } from '@angular/core';
import { ReactiveFormsModule } from '@angular/forms';
import { SharedModule } from '../shared/shared.module';
import { HeroFormReactiveComponent } from './hero-form-reactive.component';
@NgModule({
imports: [ SharedModule, ReactiveFormsModule ],
declarations: [ HeroFormReactiveComponent ],
exports: [ HeroFormReactiveComponent ]
})
export class HeroFormReactiveModule { }

View File

@ -6,9 +6,8 @@ import { AbstractControl, NG_VALIDATORS, Validator, ValidatorFn, Validators } fr
/** A hero's name can't match the given regular expression */
export function forbiddenNameValidator(nameRe: RegExp): ValidatorFn {
return (control: AbstractControl): {[key: string]: any} => {
const name = control.value;
const no = nameRe.test(name);
return no ? {'forbiddenName': {name}} : null;
const forbidden = nameRe.test(control.value);
return forbidden ? {'forbiddenName': {value: control.value}} : null;
};
}
// #enddocregion custom-validator
@ -20,23 +19,12 @@ export function forbiddenNameValidator(nameRe: RegExp): ValidatorFn {
providers: [{provide: NG_VALIDATORS, useExisting: ForbiddenValidatorDirective, multi: true}]
// #enddocregion directive-providers
})
export class ForbiddenValidatorDirective implements Validator, OnChanges {
export class ForbiddenValidatorDirective implements Validator {
@Input() forbiddenName: string;
private valFn = Validators.nullValidator;
ngOnChanges(changes: SimpleChanges): void {
const change = changes['forbiddenName'];
if (change) {
const val: string | RegExp = change.currentValue;
const re = val instanceof RegExp ? val : new RegExp(val, 'i');
this.valFn = forbiddenNameValidator(re);
} else {
this.valFn = Validators.nullValidator;
}
}
validate(control: AbstractControl): {[key: string]: any} {
return this.valFn(control);
return this.forbiddenName ? forbiddenNameValidator(new RegExp(this.forbiddenName, 'i'))(control)
: null;
}
}
// #enddocregion directive

View File

@ -1,9 +0,0 @@
// #docregion
export class Hero {
constructor(
public id: number,
public name: string,
public power: string,
public alterEgo?: string
) { }
}

View File

@ -1,14 +0,0 @@
// #docregion
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { ForbiddenValidatorDirective } from './forbidden-name.directive';
import { SubmittedComponent } from './submitted.component';
@NgModule({
imports: [ CommonModule],
declarations: [ ForbiddenValidatorDirective, SubmittedComponent ],
exports: [ ForbiddenValidatorDirective, SubmittedComponent,
CommonModule ]
})
export class SharedModule { }

View File

@ -1,32 +0,0 @@
// #docregion
import { Component, EventEmitter, Input, Output } from '@angular/core';
import { Hero } from './hero';
@Component({
selector: 'hero-submitted',
template: `
<div *ngIf="submitted">
<h2>You submitted the following:</h2>
<div class="row">
<div class="col-xs-3">Name</div>
<div class="col-xs-9 pull-left">{{ hero.name }}</div>
</div>
<div class="row">
<div class="col-xs-3">Alter Ego</div>
<div class="col-xs-9 pull-left">{{ hero.alterEgo }}</div>
</div>
<div class="row">
<div class="col-xs-3">Power</div>
<div class="col-xs-9 pull-left">{{ hero.power }}</div>
</div>
<br>
<button class="btn btn-default" (click)="onClick()">Edit</button>
</div>`
})
export class SubmittedComponent {
@Input() hero: Hero;
@Input() submitted = false;
@Output() submittedChange = new EventEmitter<boolean>();
onClick() { this.submittedChange.emit(false); }
}

View File

@ -0,0 +1,66 @@
<!-- #docregion -->
<div class="container">
<h1>Template-Driven Form</h1>
<!-- #docregion form-tag-->
<form #heroForm="ngForm">
<!-- #enddocregion form-tag-->
<div [hidden]="heroForm.submitted">
<div class="form-group">
<label for="name">Name</label>
<!-- #docregion name-with-error-msg -->
<!-- #docregion name-input -->
<input id="name" name="name" class="form-control"
required minlength="4" forbiddenName="bob"
[(ngModel)]="hero.name" #name="ngModel" >
<!-- #enddocregion name-input -->
<div *ngIf="name.invalid && (name.dirty || name.touched)"
class="alert alert-danger">
<div *ngIf="name.errors.required">
Name is required.
</div>
<div *ngIf="name.errors.minlength">
Name must be at least 4 characters long.
</div>
<div *ngIf="name.errors.forbiddenName">
Name cannot be Bob.
</div>
</div>
<!-- #enddocregion name-with-error-msg -->
</div>
<div class="form-group">
<label for="alterEgo">Alter Ego</label>
<input id="alterEgo" class="form-control"
name="alterEgo" [(ngModel)]="hero.alterEgo" >
</div>
<div class="form-group">
<label for="power">Hero Power</label>
<select id="power" name="power" class="form-control"
required [(ngModel)]="hero.power" #power="ngModel" >
<option *ngFor="let p of powers" [value]="p">{{p}}</option>
</select>
<div *ngIf="power.errors && power.touched" class="alert alert-danger">
<div *ngIf="power.errors.required">Power is required.</div>
</div>
</div>
<button type="submit" class="btn btn-default"
[disabled]="heroForm.invalid">Submit</button>
<button type="button" class="btn btn-default"
(click)="heroForm.resetForm({})">Reset</button>
</div>
<div class="submitted-message" *ngIf="heroForm.submitted">
<p>You've submitted your hero, {{ heroForm.value.name }}!</p>
<button (click)="heroForm.resetForm({})">Add new hero</button>
</div>
</form>
</div>

View File

@ -0,0 +1,16 @@
/* tslint:disable: member-ordering */
// #docplaster
// #docregion
import { Component } from '@angular/core';
@Component({
selector: 'hero-form-template',
templateUrl: './hero-form-template.component.html'
})
export class HeroFormTemplateComponent {
powers = ['Really Smart', 'Super Flexible', 'Weather Changer'];
hero = {name: 'Dr.', alterEgo: 'Dr. What', power: this.powers[0]};
}

View File

@ -1,14 +0,0 @@
// #docregion
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { SharedModule } from '../shared/shared.module';
import { HeroFormTemplate1Component } from './hero-form-template1.component';
import { HeroFormTemplate2Component } from './hero-form-template2.component';
@NgModule({
imports: [ SharedModule, FormsModule ],
declarations: [ HeroFormTemplate1Component, HeroFormTemplate2Component ],
exports: [ HeroFormTemplate1Component, HeroFormTemplate2Component ]
})
export class HeroFormTemplateModule { }

View File

@ -1,61 +0,0 @@
<!-- #docregion -->
<div class="container">
<div [hidden]="submitted">
<h1>Hero Form 1 (Template)</h1>
<!-- #docregion form-tag-->
<form #heroForm="ngForm" *ngIf="active" (ngSubmit)="onSubmit()">
<!-- #enddocregion form-tag-->
<div class="form-group">
<!-- #docregion name-with-error-msg -->
<label for="name">Name</label>
<input type="text" id="name" class="form-control"
required minlength="4" maxlength="24"
name="name" [(ngModel)]="hero.name"
#name="ngModel" >
<div *ngIf="name.errors && (name.dirty || name.touched)"
class="alert alert-danger">
<div [hidden]="!name.errors.required">
Name is required
</div>
<div [hidden]="!name.errors.minlength">
Name must be at least 4 characters long.
</div>
<div [hidden]="!name.errors.maxlength">
Name cannot be more than 24 characters long.
</div>
</div>
<!-- #enddocregion name-with-error-msg -->
</div>
<div class="form-group">
<label for="alterEgo">Alter Ego</label>
<input type="text" id="alterEgo" class="form-control"
name="alterEgo"
[(ngModel)]="hero.alterEgo" >
</div>
<div class="form-group">
<label for="power">Hero Power</label>
<select id="power" class="form-control"
name="power"
[(ngModel)]="hero.power" required
#power="ngModel" >
<option *ngFor="let p of powers" [value]="p">{{p}}</option>
</select>
<div *ngIf="power.errors && power.touched" class="alert alert-danger">
<div [hidden]="!power.errors.required">Power is required</div>
</div>
</div>
<button type="submit" class="btn btn-default"
[disabled]="!heroForm.form.valid">Submit</button>
<button type="button" class="btn btn-default"
(click)="addHero()">New Hero</button>
</form>
</div>
<hero-submitted [hero]="hero" [(submitted)]="submitted"></hero-submitted>
</div>

View File

@ -1,47 +0,0 @@
/* tslint:disable: member-ordering */
// #docplaster
// #docregion
import { Component } from '@angular/core';
import { Hero } from '../shared/hero';
@Component({
selector: 'hero-form-template1',
templateUrl: './hero-form-template1.component.html'
})
// #docregion class
export class HeroFormTemplate1Component {
powers = ['Really Smart', 'Super Flexible', 'Weather Changer'];
hero = new Hero(18, 'Dr. WhatIsHisWayTooLongName', this.powers[0], 'Dr. What');
submitted = false;
onSubmit() {
this.submitted = true;
}
// #enddocregion class
// #enddocregion
// Reset the form with a new hero AND restore 'pristine' class state
// by toggling 'active' flag which causes the form
// to be removed/re-added in a tick via NgIf
// TODO: Workaround until NgForm has a reset method (#6822)
active = true;
// #docregion
// #docregion class
addHero() {
this.hero = new Hero(42, '', '');
// #enddocregion class
// #enddocregion
this.active = false;
setTimeout(() => this.active = true, 0);
// #docregion
// #docregion class
}
}
// #enddocregion class
// #enddocregion

View File

@ -1,52 +0,0 @@
<!-- #docregion -->
<div class="container">
<div [hidden]="submitted">
<h1>Hero Form 2 (Template & Messages)</h1>
<!-- #docregion form-tag-->
<form #heroForm="ngForm" *ngIf="active" (ngSubmit)="onSubmit()">
<!-- #enddocregion form-tag-->
<div class="form-group">
<!-- #docregion name-with-error-msg -->
<label for="name">Name</label>
<!-- #docregion name-input -->
<input type="text" id="name" class="form-control"
required minlength="4" maxlength="24" forbiddenName="bob"
name="name" [(ngModel)]="hero.name" >
<!-- #enddocregion name-input -->
<div *ngIf="formErrors.name" class="alert alert-danger">
{{ formErrors.name }}
</div>
<!-- #enddocregion name-with-error-msg -->
</div>
<div class="form-group">
<label for="alterEgo">Alter Ego</label>
<input type="text" id="alterEgo" class="form-control"
name="alterEgo"
[(ngModel)]="hero.alterEgo" >
</div>
<div class="form-group">
<label for="power">Hero Power</label>
<select id="power" class="form-control"
name="power"
[(ngModel)]="hero.power" required >
<option *ngFor="let p of powers" [value]="p">{{p}}</option>
</select>
<div *ngIf="formErrors.power" class="alert alert-danger">
{{ formErrors.power }}
</div>
</div>
<button type="submit" class="btn btn-default"
[disabled]="!heroForm.form.valid">Submit</button>
<button type="button" class="btn btn-default"
(click)="addHero()">New Hero</button>
</form>
</div>
<hero-submitted [hero]="hero" [(submitted)]="submitted"></hero-submitted>
</div>

View File

@ -1,99 +0,0 @@
/* tslint:disable: member-ordering forin */
// #docplaster
// #docregion
import { Component, AfterViewChecked, ViewChild } from '@angular/core';
import { NgForm } from '@angular/forms';
import { Hero } from '../shared/hero';
@Component({
selector: 'hero-form-template2',
templateUrl: './hero-form-template2.component.html'
})
export class HeroFormTemplate2Component implements AfterViewChecked {
powers = ['Really Smart', 'Super Flexible', 'Weather Changer'];
hero = new Hero(18, 'Dr. WhatIsHisWayTooLongName', this.powers[0], 'Dr. What');
submitted = false;
onSubmit() {
this.submitted = true;
}
// #enddocregion
// Reset the form with a new hero AND restore 'pristine' class state
// by toggling 'active' flag which causes the form
// to be removed/re-added in a tick via NgIf
// TODO: Workaround until NgForm has a reset method (#6822)
active = true;
// #docregion
addHero() {
this.hero = new Hero(42, '', '');
// #enddocregion
this.active = false;
setTimeout(() => this.active = true, 0);
// #docregion
}
// #docregion view-child
heroForm: NgForm;
@ViewChild('heroForm') currentForm: NgForm;
ngAfterViewChecked() {
this.formChanged();
}
formChanged() {
if (this.currentForm === this.heroForm) { return; }
this.heroForm = this.currentForm;
if (this.heroForm) {
this.heroForm.valueChanges
.subscribe(data => this.onValueChanged(data));
}
}
// #enddocregion view-child
// #docregion handler
onValueChanged(data?: any) {
if (!this.heroForm) { return; }
const form = this.heroForm.form;
for (const field in this.formErrors) {
// clear previous error message (if any)
this.formErrors[field] = '';
const control = form.get(field);
if (control && control.dirty && !control.valid) {
const messages = this.validationMessages[field];
for (const key in control.errors) {
this.formErrors[field] += messages[key] + ' ';
}
}
}
}
formErrors = {
'name': '',
'power': ''
};
// #enddocregion handler
// #docregion messages
validationMessages = {
'name': {
'required': 'Name is required.',
'minlength': 'Name must be at least 4 characters long.',
'maxlength': 'Name cannot be more than 24 characters long.',
'forbiddenName': 'Someone named "Bob" cannot be a hero.'
},
'power': {
'required': 'Power is required.'
}
};
// #enddocregion messages
}
// #enddocregion

View File

@ -1,3 +1,4 @@
.ng-valid[required], .ng-valid.required {
border-left: 5px solid #42A948; /* green */
}

View File

@ -9,7 +9,7 @@ export class AppComponent {
wolves = 0;
gender = 'f';
fly = true;
logo = 'https://angular.io/resources/images/logos/angular/angular.png';
logo = 'https://angular.io/assets/images/logos/angular/angular.png';
count = 3;
heroes: string[] = ['Magneta', 'Celeritas', 'Dynama'];
inc(i: number) {

View File

@ -1,116 +0,0 @@
/* #docregion , quickstart, toh */
/* Master Styles */
h1 {
color: #369;
font-family: Arial, Helvetica, sans-serif;
font-size: 250%;
}
h2, h3 {
color: #444;
font-family: Arial, Helvetica, sans-serif;
font-weight: lighter;
}
body {
margin: 2em;
}
/* #enddocregion quickstart */
body, input[text], button {
color: #888;
font-family: Cambria, Georgia;
}
/* #enddocregion toh */
a {
cursor: pointer;
cursor: hand;
}
button {
font-family: Arial;
background-color: #eee;
border: none;
padding: 5px 10px;
border-radius: 4px;
cursor: pointer;
cursor: hand;
}
button:hover {
background-color: #cfd8dc;
}
button:disabled {
background-color: #eee;
color: #aaa;
cursor: auto;
}
/* Navigation link styles */
nav a {
padding: 5px 10px;
text-decoration: none;
margin-right: 10px;
margin-top: 10px;
display: inline-block;
background-color: #eee;
border-radius: 4px;
}
nav a:visited, a:link {
color: #607D8B;
}
nav a:hover {
color: #039be5;
background-color: #CFD8DC;
}
nav a.active {
color: #039be5;
}
/* items class */
.items {
margin: 0 0 2em 0;
list-style-type: none;
padding: 0;
width: 24em;
}
.items li {
cursor: pointer;
position: relative;
left: 0;
background-color: #EEE;
margin: .5em;
padding: .3em 0;
height: 1.6em;
border-radius: 4px;
}
.items li:hover {
color: #607D8B;
background-color: #DDD;
left: .1em;
}
.items li.selected {
background-color: #CFD8DC;
color: white;
}
.items li.selected:hover {
background-color: #BBD8DC;
}
.items .text {
position: relative;
top: -3px;
}
.items .badge {
display: inline-block;
font-size: small;
color: white;
padding: 0.8em 0.7em 0 0.7em;
background-color: #607D8B;
line-height: 1em;
position: relative;
left: -1px;
top: -4px;
height: 1.8em;
margin-right: .8em;
border-radius: 4px 0 0 4px;
}
/* #docregion toh */
/* everywhere else */
* {
font-family: Arial, Helvetica, sans-serif;
}

View File

@ -9,7 +9,8 @@ describe('PhoneCat Application', function() {
it('should redirect `index.html` to `index.html#!/phones', function() {
browser.get('index.html');
expect(browser.getLocationAbsUrl()).toBe('/phones');
browser.sleep(1000); // Not sure why this is needed but it is. The route change works fine.
expect(browser.getCurrentUrl()).toMatch(/\/phones$/);
});
describe('View: Phone list', function() {
@ -65,7 +66,7 @@ describe('PhoneCat Application', function() {
element.all(by.css('.phones li a')).first().click();
browser.sleep(1000); // Not sure why this is needed but it is. The route change works fine.
expect(browser.getLocationAbsUrl()).toBe('/phones/nexus-s');
expect(browser.getCurrentUrl()).toMatch(/\/phones\/nexus-s$/);
});
});

View File

@ -528,6 +528,11 @@ Compiling with AOT presupposes certain supporting files, most of them discussed
Extend the `scripts` section of the `package.json` with these npm scripts:
<code-example language="json">
"build:aot": "ngc -p tsconfig-aot.json && rollup -c rollup-config.js",
"serve:aot": "lite-server -c bs-config.aot.json",
</code-example>
Copy the AOT distribution files into the `/aot` folder with the node script:
<code-example language="none" class="code-shell">

View File

@ -124,7 +124,7 @@ For example, import Angular's `Component` decorator from the `@angular/core` lib
<code-example path="architecture/src/app/app.component.ts" region="import" linenums="false"></code-example>
You also import NgModules_ from Angular _libraries_ using JavaScript import statements:
You also import NgModules from Angular _libraries_ using JavaScript import statements:
<code-example path="architecture/src/app/mini-app.ts" region="import-browser-module" linenums="false"></code-example>

View File

@ -1,8 +1,8 @@
# Bootstrapping
An NgModule class describes how the application parts fit together.
Every application has at least one NgModule, the _root_ module
that you [bootstrap](guide/appmodule#main) to launch the application.
Every application has at least one NgModule, the _root_ module
that you [bootstrap](#main) to launch the application.
You can call it anything you want. The conventional name is `AppModule`.
The [setup](guide/setup) instructions produce a new project with the following minimal `AppModule`.
@ -25,7 +25,7 @@ The `@NgModule` decorator identifies `AppModule` as an `NgModule` class.
* **_bootstrap_** &mdash; the _root_ component that Angular creates and inserts into the `index.html` host web page.
The [NgModules](guide/ngmodule) guide dives deeply into the details of NgModules.
All you need to know at the moment is a few basics about these three properties.
All you need to know at the moment is a few basics about these three properties.
{@a imports}
@ -34,7 +34,7 @@ All you need to know at the moment is a few basics about these three properties.
### The _imports_ array
NgModules are a way to consolidate features that belong together into discrete units.
Many features of Angular itself are organized as NgModules.
Many features of Angular itself are organized as NgModules.
HTTP services are in the `HttpModule`. The router is in the `RouterModule`.
Eventually you may create a feature module.
@ -70,7 +70,7 @@ You add `import` statements to almost every application file.
They have nothing to do with Angular and Angular knows nothing about them.
The _module's_ `imports` array appears _exclusively_ in the `@NgModule` metadata object.
It tells Angular about specific _other_ NgModules&mdash;all of them classes decorated
It tells Angular about specific _other_ NgModules&mdash;all of them classes decorated
with `@NgModule`&mdash;that the application needs to function properly.
</div>
@ -110,7 +110,7 @@ Do not put any other kind of class in `declarations`; _not_ `NgModule` classes,
### The _bootstrap_ array
You launch the application by [_bootstrapping_](guide/bootstrapping#main) the root `AppModule`.
You launch the application by [_bootstrapping_](#main) the root `AppModule`.
Among other things, the _bootstrapping_ process creates the component(s) listed in the `bootstrap` array
and inserts each one into the browser DOM.
@ -127,13 +127,6 @@ Which brings us to the _bootstrapping_ process itself.
{@a main}
<l-main-section>
</l-main-section>
## Bootstrap in _main.ts_
There are many ways to bootstrap an application.

View File

@ -193,8 +193,8 @@ Angular supports most recent browsers. This includes the following specific vers
Angular's continuous integration process runs unit tests of the framework on all of these browsers for every pull request,
using <a href="https://saucelabs.com/">SauceLabs</a> and
Angular's continuous integration process runs unit tests of the framework on all of these browsers for every pull request,
using <a href="https://saucelabs.com/">SauceLabs</a> and
<a href="https://www.browserstack.com">Browserstack</a>.
@ -215,7 +215,7 @@ that implement missing features in JavaScript.
A particular browser may require at least one polyfill to run _any_ Angular application.
A particular browser may require at least one polyfill to run _any_ Angular application.
You may need additional polyfills for specific features.
The tables below can help you determine which polyfills to load, depending on the browsers you target and the features you use.
@ -241,7 +241,7 @@ These are the polyfills required to run an Angular application on each supported
<table>
<tr style="vertical-align: top">
<th>
Browsers (Desktop & Mobile)
</th>
@ -253,7 +253,7 @@ These are the polyfills required to run an Angular application on each supported
</tr>
<tr style="vertical-align: top">
<td>
Chrome, Firefox, Edge, Safari 9+
</td>
@ -265,7 +265,7 @@ These are the polyfills required to run an Angular application on each supported
</tr>
<tr style="vertical-align: top">
<td>
Safari 7 & 8, IE10 & 11, Android 4.1+
</td>
@ -279,7 +279,7 @@ These are the polyfills required to run an Angular application on each supported
</tr>
<tr style="vertical-align: top">
<td>
IE9
</td>
@ -309,7 +309,7 @@ Here are the features which may require additional polyfills:
<table>
<tr style="vertical-align: top">
<th>
Feature
</th>
@ -325,7 +325,7 @@ Here are the features which may require additional polyfills:
</tr>
<tr style="vertical-align: top">
<td>
[Animations](guide/animations)
@ -363,14 +363,14 @@ Here are the features which may require additional polyfills:
</tr>
<tr style="vertical-align: top">
<td>
[NgClass](api/common/NgClass) on SVG elements
</td>
<td>
[classList](guide/browser-support#classlist)
</td>
@ -382,16 +382,17 @@ Here are the features which may require additional polyfills:
</tr>
<tr style="vertical-align: top">
<td>
[Http](guide/http) when sending and receiving binary data
</td>
<td>
[Typed&nbsp;Array](guide/browser-support#typedarray) <br>[Blob](guide/browser-support#blob)<br>[FormData](guide/browser-support#formdata)
[Typed&nbsp;Array](guide/browser-support#typedarray)<br>
[Blob](guide/browser-support#blob)<br>
[FormData](guide/browser-support#formdata)
</td>
<td>
@ -405,7 +406,7 @@ Here are the features which may require additional polyfills:
### Suggested polyfills ##
Below are the polyfills which are used to test the framework itself. They are a good starting point for an application.
Below are the polyfills which are used to test the framework itself. They are a good starting point for an application.
<table>
@ -542,5 +543,5 @@ Below are the polyfills which are used to test the framework itself. They are a
\* Figures are for minified and gzipped code,
\* Figures are for minified and gzipped code,
computed with the <a href="http://closure-compiler.appspot.com/home">closure compiler</a>.

View File

@ -27,12 +27,12 @@ Revised samples are more clear and cover all topics discussed.
## NEW: Samples re-structured with `src/` folder (2017-02-02)
All documentation samples have been realigned with the default folder structure of the Angular CLI.
That's a step along the road to basing the sample in the Angular CLI.
But it's also good in its own right.
But it's also good in its own right.
It helps clearly separate app code from setup and configuration files.
All samples now have a `src/` folder at the project root.
The former `app/` folder moves under `src/`.
Read about moving your existing project to this structure in
The former `app/` folder moves under `src/`.
Read about moving your existing project to this structure in
<a href="https://github.com/angular/quickstart#updating-to-a-newer-version-of-the-quickstart-repo" target="Migrating samples/quickstart app to the src folder">
the QuickStart repo update instructions</a>.
@ -48,8 +48,8 @@ Notably:
The new [**Reactive Forms**](guide/reactive-forms) guide explains how and why to build a "reactive form".
"Reactive Forms" are the code-based counterpart to the declarative "Template Driven" forms approach
introduced in the [Forms](guide/forms) guide.
Check it out before you decide how to add forms to your app.
Remember also that you can use both techniques in the same app,
Check it out before you decide how to add forms to your app.
Remember also that you can use both techniques in the same app,
choosing the approach that best fits each scenario.
## NEW: Deployment guide (2017-01-30)
@ -65,25 +65,25 @@ Revised samples are clearer and cover all topics discussed.
## Miscellaneous (2017-01-05)
* [Setup](guide/setup) guide:
added (optional) instructions on how to remove _non-essential_ files.
* [Setup](guide/setup) guide:
added (optional) instructions on how to remove _non-essential_ files.
* No longer consolidate RxJS operator imports in `rxjs-extensions` file; each file should import what it needs.
* All samples prepend template/style URLs with `./` as a best practice.
* [Style Guide](guide/styleguide): copy edits and revised rules.
## Router: more detail (2016-12-21)
Added more information to the [Router](guide/router) guide
Added more information to the [Router](guide/router) guide
including sections named outlets, wildcard routes, and preload strategies.
## HTTP: how to set default request headers (and other request options) (2016-12-14)
Added section on how to set default request headers (and other request options) to
[HTTP](guide/http#override-default-request-options) guide.
Added section on how to set default request headers (and other request options) to
HTTP guide.
## Testing: added component test plunkers (2016-12-02)
Added two plunkers that each test _one simple component_ so you can write a component test plunker of your own: <live-example name="setup" plnkr="quickstart-specs">one</live-example> for the QuickStart seed's `AppComponent` and <live-example name="testing" plnkr="banner-specs">another</live-example> for the Testing guide's `BannerComponent`.
Added two plunkers that each test _one simple component_ so you can write a component test plunker of your own: <live-example name="setup" plnkr="quickstart-specs">one</live-example> for the QuickStart seed's `AppComponent` and <live-example name="testing" plnkr="banner-specs">another</live-example> for the Testing guide's `BannerComponent`.
Linked to these plunkers in [Testing](guide/testing#live-examples) and [Setup anatomy](guide/setup-systemjs-anatomy) guides.
## Internationalization: pluralization and _select_ (2016-11-30)
@ -112,15 +112,15 @@ Docs and code samples updated and tested with Angular v.2.2.0.
## UPDATE: NgUpgrade Guide for the AOT friendly _upgrade/static_ module (2016-11-14)
The updated [NgUpgrade Guide](guide/upgrade) guide covers the
new AOT friendly `upgrade/static` module
The updated [NgUpgrade Guide](guide/upgrade) guide covers the
new AOT friendly `upgrade/static` module
released in v.2.2.0, which is the recommended
facility for migrating from AngularJS to Angular.
The documentation for the version prior to v.2.2.0 has been removed.
## ES6 described in "TypeScript to JavaScript" (2016-11-14)
The updated [TypeScript to JavaScript](guide/ts-to-js) guide
The updated [TypeScript to JavaScript](guide/ts-to-js) guide
now explains how to write apps in ES6/7
by translating the common idioms in the TypeScript documentation examples
(and elsewhere on the web) to ES6/7 and ES5.
@ -156,7 +156,7 @@ in the `in-memory-web-api` repo.
The router can lazily _preload_ modules _after_ the app starts and
_before_ the user navigates to them for improved perceived performance.
New `:enter` and `:leave` aliases make animation more natural.
New `:enter` and `:leave` aliases make animation more natural.
## Sync with Angular v.2.1.0 (2016-10-12)
@ -176,11 +176,11 @@ Docs and code samples updated and tested with Angular v.2.0.2.
## "Routing and Navigation" guide with the _Router Module_ (2016-10-5)
The [Routing and Navigation](guide/router) guide now locates route configuration
in a _Routing Module_.
in a _Routing Module_.
The _Routing Module_ replaces the previous _routing object_ involving the `ModuleWithProviders`.
All guided samples with routing use the _Routing Module_ and prose content has been updated,
most conspicuously in the
most conspicuously in the
[NgModule](guide/ngmodule) guide and [NgModule FAQ](guide/ngmodule-faq) guide.
## New "Internationalization" guide (2016-09-30)
@ -194,7 +194,7 @@ Many samples use the `angular-in-memory-web-api` to simulate a remote server.
This library is also useful to you during early development before you have a server to talk to.
The package name was changed from "angular2-in-memory-web-api" which is still frozen-in-time on npm.
The new "angular-in-memory-web-api" has new features.
The new "angular-in-memory-web-api" has new features.
<a href="https://github.com/angular/in-memory-web-api/blob/master/README.md">Read about them on github</a>.
## "Style Guide" with _NgModules_ (2016-09-27)
@ -215,5 +215,5 @@ modules with SystemJS as the samples currently do.
## "Lifecycle Hooks" guide simplified (2016-09-24)
The [Lifecycle Hooks](guide/lifecycle-hooks) guide is shorter, simpler, and
The [Lifecycle Hooks](guide/lifecycle-hooks) guide is shorter, simpler, and
draws more attention to the order in which Angular calls the hooks.

View File

@ -1,4 +1,4 @@
# Cheat Sheet
<h1 class="no-toc">Cheat Sheet</h1>
<div id="cheatsheet">
<table class="is-full-width is-fixed-layout">
@ -23,28 +23,28 @@
</th>
</tr>
<tr>
<td><code>@<b>NgModule</b>({&nbsp;declarations:&nbsp;...,&nbsp;imports:&nbsp;...,<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;exports:&nbsp;...,&nbsp;providers:&nbsp;...,&nbsp;bootstrap:&nbsp;...})<br>class&nbsp;MyModule&nbsp;{}</code></td>
<td><code>@<b>NgModule</b>({ declarations: ..., imports: ...,<br> exports: ..., providers: ..., bootstrap: ...})<br>class MyModule {}</code></td>
<td><p>Defines a module that contains components, directives, pipes, and providers.</p>
</td>
</tr><tr>
<td><code><b>declarations:</b>&nbsp;[MyRedComponent,&nbsp;MyBlueComponent,&nbsp;MyDatePipe]</code></td>
<td><code><b>declarations:</b> [MyRedComponent, MyBlueComponent, MyDatePipe]</code></td>
<td><p>List of components, directives, and pipes that belong to this module.</p>
</td>
</tr><tr>
<td><code><b>imports:</b>&nbsp;[BrowserModule,&nbsp;SomeOtherModule]</code></td>
<td><code><b>imports:</b> [BrowserModule, SomeOtherModule]</code></td>
<td><p>List of modules to import into this module. Everything from the imported modules
is available to <code>declarations</code> of this module.</p>
</td>
</tr><tr>
<td><code><b>exports:</b>&nbsp;[MyRedComponent,&nbsp;MyDatePipe]</code></td>
<td><code><b>exports:</b> [MyRedComponent, MyDatePipe]</code></td>
<td><p>List of components, directives, and pipes visible to modules that import this module.</p>
</td>
</tr><tr>
<td><code><b>providers:</b>&nbsp;[MyService,&nbsp;{&nbsp;provide:&nbsp;...&nbsp;}]</code></td>
<td><code><b>providers:</b> [MyService, { provide: ... }]</code></td>
<td><p>List of dependency injection providers visible both to the contents of this module and to importers of this module.</p>
</td>
</tr><tr>
<td><code><b>bootstrap:</b>&nbsp;[MyAppComponent]</code></td>
<td><code><b>bootstrap:</b> [MyAppComponent]</code></td>
<td><p>List of components to bootstrap when this module is bootstrapped.</p>
</td>
</tr>
@ -56,61 +56,61 @@ is available to <code>declarations</code> of this module.</p>
<th></th>
</tr>
<tr>
<td><code>&lt;input&nbsp;<b>[value]</b>="firstName"&gt;</code></td>
<td><code>&lt;input <b>[value]</b>="firstName"&gt;</code></td>
<td><p>Binds property <code>value</code> to the result of expression <code>firstName</code>.</p>
</td>
</tr><tr>
<td><code>&lt;div&nbsp;<b>[attr.role]</b>="myAriaRole"&gt;</code></td>
<td><code>&lt;div <b>[attr.role]</b>="myAriaRole"&gt;</code></td>
<td><p>Binds attribute <code>role</code> to the result of expression <code>myAriaRole</code>.</p>
</td>
</tr><tr>
<td><code>&lt;div&nbsp;<b>[class.extra-sparkle]</b>="isDelightful"&gt;</code></td>
<td><code>&lt;div <b>[class.extra-sparkle]</b>="isDelightful"&gt;</code></td>
<td><p>Binds the presence of the CSS class <code>extra-sparkle</code> on the element to the truthiness of the expression <code>isDelightful</code>.</p>
</td>
</tr><tr>
<td><code>&lt;div&nbsp;<b>[style.width.px]</b>="mySize"&gt;</code></td>
<td><code>&lt;div <b>[style.width.px]</b>="mySize"&gt;</code></td>
<td><p>Binds style property <code>width</code> to the result of expression <code>mySize</code> in pixels. Units are optional.</p>
</td>
</tr><tr>
<td><code>&lt;button&nbsp;<b>(click)</b>="readRainbow($event)"&gt;</code></td>
<td><code>&lt;button <b>(click)</b>="readRainbow($event)"&gt;</code></td>
<td><p>Calls method <code>readRainbow</code> when a click event is triggered on this button element (or its children) and passes in the event object.</p>
</td>
</tr><tr>
<td><code>&lt;div&nbsp;title="Hello&nbsp;<b>{{ponyName}}</b>"&gt;</code></td>
<td><code>&lt;div title="Hello <b>{{ponyName}}</b>"&gt;</code></td>
<td><p>Binds a property to an interpolated string, for example, "Hello Seabiscuit". Equivalent to:
<code>&lt;div [title]="'Hello ' + ponyName"&gt;</code></p>
</td>
</tr><tr>
<td><code>&lt;p&gt;Hello&nbsp;<b>{{ponyName}}</b>&lt;/p&gt;</code></td>
<td><code>&lt;p&gt;Hello <b>{{ponyName}}</b>&lt;/p&gt;</code></td>
<td><p>Binds text content to an interpolated string, for example, "Hello Seabiscuit".</p>
</td>
</tr><tr>
<td><code>&lt;my-cmp&nbsp;<b>[(title)]</b>="name"&gt;</code></td>
<td><code>&lt;my-cmp <b>[(title)]</b>="name"&gt;</code></td>
<td><p>Sets up two-way data binding. Equivalent to: <code>&lt;my-cmp [title]="name" (titleChange)="name=$event"&gt;</code></p>
</td>
</tr><tr>
<td><code>&lt;video&nbsp;<b>#movieplayer</b>&nbsp;...&gt;<br>&nbsp;&nbsp;&lt;button&nbsp;<b>(click)</b>="movieplayer.play()"&gt;<br>&lt;/video&gt;</code></td>
<td><code>&lt;video <b>#movieplayer</b> ...&gt;<br> &lt;button <b>(click)</b>="movieplayer.play()"&gt;<br>&lt;/video&gt;</code></td>
<td><p>Creates a local variable <code>movieplayer</code> that provides access to the <code>video</code> element instance in data-binding and event-binding expressions in the current template.</p>
</td>
</tr><tr>
<td><code>&lt;p&nbsp;<b>*myUnless</b>="myExpression"&gt;...&lt;/p&gt;</code></td>
<td><code>&lt;p <b>*myUnless</b>="myExpression"&gt;...&lt;/p&gt;</code></td>
<td><p>The <code>*</code> symbol turns the current element into an embedded template. Equivalent to:
<code>&lt;ng-template [myUnless]="myExpression"&gt;&lt;p&gt;...&lt;/p&gt;&lt;/ng-template&gt;</code></p>
</td>
</tr><tr>
<td><code>&lt;p&gt;Card&nbsp;No.:&nbsp;<b>{{cardNumber&nbsp;|&nbsp;myCardNumberFormatter}}</b>&lt;/p&gt;</code></td>
<td><code>&lt;p&gt;Card No.: <b>{{cardNumber | myCardNumberFormatter}}</b>&lt;/p&gt;</code></td>
<td><p>Transforms the current value of expression <code>cardNumber</code> via the pipe called <code>myCardNumberFormatter</code>.</p>
</td>
</tr><tr>
<td><code>&lt;p&gt;Employer:&nbsp;<b>{{employer?.companyName}}</b>&lt;/p&gt;</code></td>
<td><code>&lt;p&gt;Employer: <b>{{employer?.companyName}}</b>&lt;/p&gt;</code></td>
<td><p>The safe navigation operator (<code>?</code>) means that the <code>employer</code> field is optional and if <code>undefined</code>, the rest of the expression should be ignored.</p>
</td>
</tr><tr>
<td><code>&lt;<b>svg:</b>rect&nbsp;x="0"&nbsp;y="0"&nbsp;width="100"&nbsp;height="100"/&gt;</code></td>
<td><code>&lt;<b>svg:</b>rect x="0" y="0" width="100" height="100"/&gt;</code></td>
<td><p>An SVG snippet template needs an <code>svg:</code> prefix on its root element to disambiguate the SVG element from an HTML component.</p>
</td>
</tr><tr>
<td><code>&lt;<b>svg</b>&gt;<br>&nbsp;&nbsp;&lt;rect&nbsp;x="0"&nbsp;y="0"&nbsp;width="100"&nbsp;height="100"/&gt;<br>&lt;/<b>svg</b>&gt;</code></td>
<td><code>&lt;<b>svg</b>&gt;<br> &lt;rect x="0" y="0" width="100" height="100"/&gt;<br>&lt;/<b>svg</b>&gt;</code></td>
<td><p>An <code>&lt;svg&gt;</code> root element is detected as an SVG element automatically, without the prefix.</p>
</td>
</tr>
@ -124,19 +124,19 @@ is available to <code>declarations</code> of this module.</p>
</th>
</tr>
<tr>
<td><code>&lt;section&nbsp;<b>*ngIf</b>="showSection"&gt;</code></td>
<td><code>&lt;section <b>*ngIf</b>="showSection"&gt;</code></td>
<td><p>Removes or recreates a portion of the DOM tree based on the <code>showSection</code> expression.</p>
</td>
</tr><tr>
<td><code>&lt;li&nbsp;<b>*ngFor</b>="let&nbsp;item&nbsp;of&nbsp;list"&gt;</code></td>
<td><code>&lt;li <b>*ngFor</b>="let item of list"&gt;</code></td>
<td><p>Turns the li element and its contents into a template, and uses that to instantiate a view for each item in list.</p>
</td>
</tr><tr>
<td><code>&lt;div&nbsp;<b>[ngSwitch]</b>="conditionExpression"&gt;<br>&nbsp;&nbsp;&lt;ng-template&nbsp;<b>[<b>ngSwitchCase</b>]</b>="case1Exp"&gt;...&lt;/ng-template&gt;<br>&nbsp;&nbsp;&lt;ng-template&nbsp;<b>ngSwitchCase</b>="case2LiteralString"&gt;...&lt;/ng-template&gt;<br>&nbsp;&nbsp;&lt;ng-template&nbsp;<b>ngSwitchDefault</b>&gt;...&lt;/ng-template&gt;<br>&lt;/div&gt;</code></td>
<td><code>&lt;div <b>[ngSwitch]</b>="conditionExpression"&gt;<br> &lt;ng-template <b>[<b>ngSwitchCase</b>]</b>="case1Exp"&gt;...&lt;/ng-template&gt;<br> &lt;ng-template <b>ngSwitchCase</b>="case2LiteralString"&gt;...&lt;/ng-template&gt;<br> &lt;ng-template <b>ngSwitchDefault</b>&gt;...&lt;/ng-template&gt;<br>&lt;/div&gt;</code></td>
<td><p>Conditionally swaps the contents of the div by selecting one of the embedded templates based on the current value of <code>conditionExpression</code>.</p>
</td>
</tr><tr>
<td><code>&lt;div&nbsp;<b>[ngClass]</b>="{'active':&nbsp;isActive,&nbsp;'disabled':&nbsp;isDisabled}"&gt;</code></td>
<td><code>&lt;div <b>[ngClass]</b>="{'active': isActive, 'disabled': isDisabled}"&gt;</code></td>
<td><p>Binds the presence of CSS classes on the element to the truthiness of the associated map values. The right-hand expression should return {class-name: true/false} map.</p>
</td>
</tr>
@ -150,7 +150,7 @@ is available to <code>declarations</code> of this module.</p>
</th>
</tr>
<tr>
<td><code>&lt;input&nbsp;<b>[(ngModel)]</b>="userName"&gt;</code></td>
<td><code>&lt;input <b>[(ngModel)]</b>="userName"&gt;</code></td>
<td><p>Provides two-way data-binding, parsing, and validation for form controls.</p>
</td>
</tr>
@ -164,19 +164,19 @@ is available to <code>declarations</code> of this module.</p>
</th>
</tr>
<tr>
<td><code><b>@Component({...})</b><br>class&nbsp;MyComponent()&nbsp;{}</code></td>
<td><code><b>@Component({...})</b><br>class MyComponent() {}</code></td>
<td><p>Declares that a class is a component and provides metadata about the component.</p>
</td>
</tr><tr>
<td><code><b>@Directive({...})</b><br>class&nbsp;MyDirective()&nbsp;{}</code></td>
<td><code><b>@Directive({...})</b><br>class MyDirective() {}</code></td>
<td><p>Declares that a class is a directive and provides metadata about the directive.</p>
</td>
</tr><tr>
<td><code><b>@Pipe({...})</b><br>class&nbsp;MyPipe()&nbsp;{}</code></td>
<td><code><b>@Pipe({...})</b><br>class MyPipe() {}</code></td>
<td><p>Declares that a class is a pipe and provides metadata about the pipe.</p>
</td>
</tr><tr>
<td><code><b>@Injectable()</b><br>class&nbsp;MyService()&nbsp;{}</code></td>
<td><code><b>@Injectable()</b><br>class MyService() {}</code></td>
<td><p>Declares that a class has dependencies that should be injected into the constructor when the dependency injector is creating an instance of this class.
</p>
</td>
@ -191,13 +191,13 @@ is available to <code>declarations</code> of this module.</p>
</th>
</tr>
<tr>
<td><code><b>selector:</b>&nbsp;'.cool-button:not(a)'</code></td>
<td><code><b>selector:</b> '.cool-button:not(a)'</code></td>
<td><p>Specifies a CSS selector that identifies this directive within a template. Supported selectors include <code>element</code>,
<code>[attribute]</code>, <code>.class</code>, and <code>:not()</code>.</p>
<p>Does not support parent-child relationship selectors.</p>
</td>
</tr><tr>
<td><code><b>providers:</b>&nbsp;[MyService,&nbsp;{&nbsp;provide:&nbsp;...&nbsp;}]</code></td>
<td><code><b>providers:</b> [MyService, { provide: ... }]</code></td>
<td><p>List of dependency injection providers for this directive and its children.</p>
</td>
</tr>
@ -212,19 +212,19 @@ so the <code>@Directive</code> configuration applies to components as well</p>
</th>
</tr>
<tr>
<td><code><b>moduleId:</b>&nbsp;module.id</code></td>
<td><code><b>moduleId:</b> module.id</code></td>
<td><p>If set, the <code>templateUrl</code> and <code>styleUrl</code> are resolved relative to the component.</p>
</td>
</tr><tr>
<td><code><b>viewProviders:</b>&nbsp;[MyService,&nbsp;{&nbsp;provide:&nbsp;...&nbsp;}]</code></td>
<td><code><b>viewProviders:</b> [MyService, { provide: ... }]</code></td>
<td><p>List of dependency injection providers scoped to this component's view.</p>
</td>
</tr><tr>
<td><code><b>template:</b>&nbsp;'Hello&nbsp;{{name}}'<br><b>templateUrl:</b>&nbsp;'my-component.html'</code></td>
<td><code><b>template:</b> 'Hello {{name}}'<br><b>templateUrl:</b> 'my-component.html'</code></td>
<td><p>Inline template or external template URL of the component's view.</p>
</td>
</tr><tr>
<td><code><b>styles:</b>&nbsp;['.primary&nbsp;{color:&nbsp;red}']<br><b>styleUrls:</b>&nbsp;['my-component.css']</code></td>
<td><code><b>styles:</b> ['.primary {color: red}']<br><b>styleUrls:</b> ['my-component.css']</code></td>
<td><p>List of inline CSS styles or external stylesheet URLs for styling the components view.</p>
</td>
</tr>
@ -238,36 +238,36 @@ so the <code>@Directive</code> configuration applies to components as well</p>
</th>
</tr>
<tr>
<td><code><b>@Input()</b>&nbsp;myProperty;</code></td>
<td><code><b>@Input()</b> myProperty;</code></td>
<td><p>Declares an input property that you can update via property binding (example:
<code>&lt;my-cmp [myProperty]="someExpression"&gt;</code>).</p>
</td>
</tr><tr>
<td><code><b>@Output()</b>&nbsp;myEvent&nbsp;=&nbsp;new&nbsp;EventEmitter();</code></td>
<td><code><b>@Output()</b> myEvent = new EventEmitter();</code></td>
<td><p>Declares an output property that fires events that you can subscribe to with an event binding (example: <code>&lt;my-cmp (myEvent)="doSomething()"&gt;</code>).</p>
</td>
</tr><tr>
<td><code><b>@HostBinding('class.valid')</b>&nbsp;isValid;</code></td>
<td><code><b>@HostBinding('class.valid')</b> isValid;</code></td>
<td><p>Binds a host element property (here, the CSS class <code>valid</code>) to a directive/component property (<code>isValid</code>).</p>
</td>
</tr><tr>
<td><code><b>@HostListener('click',&nbsp;['$event'])</b>&nbsp;onClick(e)&nbsp;{...}</code></td>
<td><code><b>@HostListener('click', ['$event'])</b> onClick(e) {...}</code></td>
<td><p>Subscribes to a host element event (<code>click</code>) with a directive/component method (<code>onClick</code>), optionally passing an argument (<code>$event</code>).</p>
</td>
</tr><tr>
<td><code><b>@ContentChild(myPredicate)</b>&nbsp;myChildComponent;</code></td>
<td><code><b>@ContentChild(myPredicate)</b> myChildComponent;</code></td>
<td><p>Binds the first result of the component content query (<code>myPredicate</code>) to a property (<code>myChildComponent</code>) of the class.</p>
</td>
</tr><tr>
<td><code><b>@ContentChildren(myPredicate)</b>&nbsp;myChildComponents;</code></td>
<td><code><b>@ContentChildren(myPredicate)</b> myChildComponents;</code></td>
<td><p>Binds the results of the component content query (<code>myPredicate</code>) to a property (<code>myChildComponents</code>) of the class.</p>
</td>
</tr><tr>
<td><code><b>@ViewChild(myPredicate)</b>&nbsp;myChildComponent;</code></td>
<td><code><b>@ViewChild(myPredicate)</b> myChildComponent;</code></td>
<td><p>Binds the first result of the component view query (<code>myPredicate</code>) to a property (<code>myChildComponent</code>) of the class. Not available for directives.</p>
</td>
</tr><tr>
<td><code><b>@ViewChildren(myPredicate)</b>&nbsp;myChildComponents;</code></td>
<td><code><b>@ViewChildren(myPredicate)</b> myChildComponents;</code></td>
<td><p>Binds the results of the component view query (<code>myPredicate</code>) to a property (<code>myChildComponents</code>) of the class. Not available for directives.</p>
</td>
</tr>
@ -281,39 +281,39 @@ so the <code>@Directive</code> configuration applies to components as well</p>
</th>
</tr>
<tr>
<td><code><b>constructor(myService:&nbsp;MyService,&nbsp;...)</b>&nbsp;{&nbsp;...&nbsp;}</code></td>
<td><code><b>constructor(myService: MyService, ...)</b> { ... }</code></td>
<td><p>Called before any other lifecycle hook. Use it to inject dependencies, but avoid any serious work here.</p>
</td>
</tr><tr>
<td><code><b>ngOnChanges(changeRecord)</b>&nbsp;{&nbsp;...&nbsp;}</code></td>
<td><code><b>ngOnChanges(changeRecord)</b> { ... }</code></td>
<td><p>Called after every change to input properties and before processing content or child views.</p>
</td>
</tr><tr>
<td><code><b>ngOnInit()</b>&nbsp;{&nbsp;...&nbsp;}</code></td>
<td><code><b>ngOnInit()</b> { ... }</code></td>
<td><p>Called after the constructor, initializing input properties, and the first call to <code>ngOnChanges</code>.</p>
</td>
</tr><tr>
<td><code><b>ngDoCheck()</b>&nbsp;{&nbsp;...&nbsp;}</code></td>
<td><code><b>ngDoCheck()</b> { ... }</code></td>
<td><p>Called every time that the input properties of a component or a directive are checked. Use it to extend change detection by performing a custom check.</p>
</td>
</tr><tr>
<td><code><b>ngAfterContentInit()</b>&nbsp;{&nbsp;...&nbsp;}</code></td>
<td><code><b>ngAfterContentInit()</b> { ... }</code></td>
<td><p>Called after <code>ngOnInit</code> when the component's or directive's content has been initialized.</p>
</td>
</tr><tr>
<td><code><b>ngAfterContentChecked()</b>&nbsp;{&nbsp;...&nbsp;}</code></td>
<td><code><b>ngAfterContentChecked()</b> { ... }</code></td>
<td><p>Called after every check of the component's or directive's content.</p>
</td>
</tr><tr>
<td><code><b>ngAfterViewInit()</b>&nbsp;{&nbsp;...&nbsp;}</code></td>
<td><code><b>ngAfterViewInit()</b> { ... }</code></td>
<td><p>Called after <code>ngAfterContentInit</code> when the component's view has been initialized. Applies to components only.</p>
</td>
</tr><tr>
<td><code><b>ngAfterViewChecked()</b>&nbsp;{&nbsp;...&nbsp;}</code></td>
<td><code><b>ngAfterViewChecked()</b> { ... }</code></td>
<td><p>Called after every check of the component's view. Applies to components only.</p>
</td>
</tr><tr>
<td><code><b>ngOnDestroy()</b>&nbsp;{&nbsp;...&nbsp;}</code></td>
<td><code><b>ngOnDestroy()</b> { ... }</code></td>
<td><p>Called once, before the instance is destroyed.</p>
</td>
</tr>
@ -325,15 +325,15 @@ so the <code>@Directive</code> configuration applies to components as well</p>
<th></th>
</tr>
<tr>
<td><code>{&nbsp;<b>provide</b>:&nbsp;MyService,&nbsp;<b>useClass</b>:&nbsp;MyMockService&nbsp;}</code></td>
<td><code>{ <b>provide</b>: MyService, <b>useClass</b>: MyMockService }</code></td>
<td><p>Sets or overrides the provider for <code>MyService</code> to the <code>MyMockService</code> class.</p>
</td>
</tr><tr>
<td><code>{&nbsp;<b>provide</b>:&nbsp;MyService,&nbsp;<b>useFactory</b>:&nbsp;myFactory&nbsp;}</code></td>
<td><code>{ <b>provide</b>: MyService, <b>useFactory</b>: myFactory }</code></td>
<td><p>Sets or overrides the provider for <code>MyService</code> to the <code>myFactory</code> factory function.</p>
</td>
</tr><tr>
<td><code>{&nbsp;<b>provide</b>:&nbsp;MyValue,&nbsp;<b>useValue</b>:&nbsp;41&nbsp;}</code></td>
<td><code>{ <b>provide</b>: MyValue, <b>useValue</b>: 41 }</code></td>
<td><p>Sets or overrides the provider for <code>MyValue</code> to the value <code>41</code>.</p>
</td>
</tr>
@ -347,39 +347,39 @@ so the <code>@Directive</code> configuration applies to components as well</p>
</th>
</tr>
<tr>
<td><code>const&nbsp;routes:&nbsp;<b>Routes</b>&nbsp;=&nbsp;[<br>&nbsp;&nbsp;{&nbsp;path:&nbsp;'',&nbsp;component:&nbsp;HomeComponent&nbsp;},<br>&nbsp;&nbsp;{&nbsp;path:&nbsp;'path/:routeParam',&nbsp;component:&nbsp;MyComponent&nbsp;},<br>&nbsp;&nbsp;{&nbsp;path:&nbsp;'staticPath',&nbsp;component:&nbsp;...&nbsp;},<br>&nbsp;&nbsp;{&nbsp;path:&nbsp;'**',&nbsp;component:&nbsp;...&nbsp;},<br>&nbsp;&nbsp;{&nbsp;path:&nbsp;'oldPath',&nbsp;redirectTo:&nbsp;'/staticPath'&nbsp;},<br>&nbsp;&nbsp;{&nbsp;path:&nbsp;...,&nbsp;component:&nbsp;...,&nbsp;data:&nbsp;{&nbsp;message:&nbsp;'Custom'&nbsp;}&nbsp;}<br>]);<br><br>const&nbsp;routing&nbsp;=&nbsp;RouterModule.forRoot(routes);</code></td>
<td><code>const routes: <b>Routes</b> = [<br> { path: '', component: HomeComponent },<br> { path: 'path/:routeParam', component: MyComponent },<br> { path: 'staticPath', component: ... },<br> { path: '**', component: ... },<br> { path: 'oldPath', redirectTo: '/staticPath' },<br> { path: ..., component: ..., data: { message: 'Custom' } }<br>]);<br><br>const routing = RouterModule.forRoot(routes);</code></td>
<td><p>Configures routes for the application. Supports static, parameterized, redirect, and wildcard routes. Also supports custom route data and resolve.</p>
</td>
</tr><tr>
<td><code><br>&lt;<b>router-outlet</b>&gt;&lt;/<b>router-outlet</b>&gt;<br>&lt;<b>router-outlet</b>&nbsp;name="aux"&gt;&lt;/<b>router-outlet</b>&gt;<br></code></td>
<td><code><br>&lt;<b>router-outlet</b>&gt;&lt;/<b>router-outlet</b>&gt;<br>&lt;<b>router-outlet</b> name="aux"&gt;&lt;/<b>router-outlet</b>&gt;<br></code></td>
<td><p>Marks the location to load the component of the active route.</p>
</td>
</tr><tr>
<td><code><br>&lt;a&nbsp;routerLink="/path"&gt;<br>&lt;a&nbsp;<b>[routerLink]</b>="[&nbsp;'/path',&nbsp;routeParam&nbsp;]"&gt;<br>&lt;a&nbsp;<b>[routerLink]</b>="[&nbsp;'/path',&nbsp;{&nbsp;matrixParam:&nbsp;'value'&nbsp;}&nbsp;]"&gt;<br>&lt;a&nbsp;<b>[routerLink]</b>="[&nbsp;'/path'&nbsp;]"&nbsp;[queryParams]="{&nbsp;page:&nbsp;1&nbsp;}"&gt;<br>&lt;a&nbsp;<b>[routerLink]</b>="[&nbsp;'/path'&nbsp;]"&nbsp;fragment="anchor"&gt;<br></code></td>
<td><code><br>&lt;a routerLink="/path"&gt;<br>&lt;a <b>[routerLink]</b>="[ '/path', routeParam ]"&gt;<br>&lt;a <b>[routerLink]</b>="[ '/path', { matrixParam: 'value' } ]"&gt;<br>&lt;a <b>[routerLink]</b>="[ '/path' ]" [queryParams]="{ page: 1 }"&gt;<br>&lt;a <b>[routerLink]</b>="[ '/path' ]" fragment="anchor"&gt;<br></code></td>
<td><p>Creates a link to a different view based on a route instruction consisting of a route path, required and optional parameters, query parameters, and a fragment. To navigate to a root route, use the <code>/</code> prefix; for a child route, use the <code>./</code>prefix; for a sibling or parent, use the <code>../</code> prefix.</p>
</td>
</tr><tr>
<td><code>&lt;a&nbsp;[routerLink]="[&nbsp;'/path'&nbsp;]"&nbsp;routerLinkActive="active"&gt;</code></td>
<td><code>&lt;a [routerLink]="[ '/path' ]" routerLinkActive="active"&gt;</code></td>
<td><p>The provided classes are added to the element when the <code>routerLink</code> becomes the current active route.</p>
</td>
</tr><tr>
<td><code>class&nbsp;<b>CanActivate</b>Guard&nbsp;implements&nbsp;<b>CanActivate</b>&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp;canActivate(<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;route:&nbsp;ActivatedRouteSnapshot,<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;state:&nbsp;RouterStateSnapshot<br>&nbsp;&nbsp;&nbsp;&nbsp;):&nbsp;Observable&lt;boolean&gt;|Promise&lt;boolean&gt;|boolean&nbsp;{&nbsp;...&nbsp;}<br>}<br><br>{&nbsp;path:&nbsp;...,&nbsp;canActivate:&nbsp;[<b>CanActivate</b>Guard]&nbsp;}</code></td>
<td><code>class <b>CanActivate</b>Guard implements <b>CanActivate</b> {<br> canActivate(<br> route: ActivatedRouteSnapshot,<br> state: RouterStateSnapshot<br> ): Observable&lt;boolean&gt;|Promise&lt;boolean&gt;|boolean { ... }<br>}<br><br>{ path: ..., canActivate: [<b>CanActivate</b>Guard] }</code></td>
<td><p>An interface for defining a class that the router should call first to determine if it should activate this component. Should return a boolean or an Observable/Promise that resolves to a boolean.</p>
</td>
</tr><tr>
<td><code>class&nbsp;<b>CanDeactivate</b>Guard&nbsp;implements&nbsp;<b>CanDeactivate</b>&lt;T&gt;&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp;canDeactivate(<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;component:&nbsp;T,<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;route:&nbsp;ActivatedRouteSnapshot,<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;state:&nbsp;RouterStateSnapshot<br>&nbsp;&nbsp;&nbsp;&nbsp;):&nbsp;Observable&lt;boolean&gt;|Promise&lt;boolean&gt;|boolean&nbsp;{&nbsp;...&nbsp;}<br>}<br><br>{&nbsp;path:&nbsp;...,&nbsp;canDeactivate:&nbsp;[<b>CanDeactivate</b>Guard]&nbsp;}</code></td>
<td><code>class <b>CanDeactivate</b>Guard implements <b>CanDeactivate</b>&lt;T&gt; {<br> canDeactivate(<br> component: T,<br> route: ActivatedRouteSnapshot,<br> state: RouterStateSnapshot<br> ): Observable&lt;boolean&gt;|Promise&lt;boolean&gt;|boolean { ... }<br>}<br><br>{ path: ..., canDeactivate: [<b>CanDeactivate</b>Guard] }</code></td>
<td><p>An interface for defining a class that the router should call first to determine if it should deactivate this component after a navigation. Should return a boolean or an Observable/Promise that resolves to a boolean.</p>
</td>
</tr><tr>
<td><code>class&nbsp;<b>CanActivateChild</b>Guard&nbsp;implements&nbsp;<b>CanActivateChild</b>&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp;canActivateChild(<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;route:&nbsp;ActivatedRouteSnapshot,<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;state:&nbsp;RouterStateSnapshot<br>&nbsp;&nbsp;&nbsp;&nbsp;):&nbsp;Observable&lt;boolean&gt;|Promise&lt;boolean&gt;|boolean&nbsp;{&nbsp;...&nbsp;}<br>}<br><br>{&nbsp;path:&nbsp;...,&nbsp;canActivateChild:&nbsp;[CanActivateGuard],<br>&nbsp;&nbsp;&nbsp;&nbsp;children:&nbsp;...&nbsp;}</code></td>
<td><code>class <b>CanActivateChild</b>Guard implements <b>CanActivateChild</b> {<br> canActivateChild(<br> route: ActivatedRouteSnapshot,<br> state: RouterStateSnapshot<br> ): Observable&lt;boolean&gt;|Promise&lt;boolean&gt;|boolean { ... }<br>}<br><br>{ path: ..., canActivateChild: [CanActivateGuard],<br> children: ... }</code></td>
<td><p>An interface for defining a class that the router should call first to determine if it should activate the child route. Should return a boolean or an Observable/Promise that resolves to a boolean.</p>
</td>
</tr><tr>
<td><code>class&nbsp;<b>Resolve</b>Guard&nbsp;implements&nbsp;<b>Resolve</b>&lt;T&gt;&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp;resolve(<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;route:&nbsp;ActivatedRouteSnapshot,<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;state:&nbsp;RouterStateSnapshot<br>&nbsp;&nbsp;&nbsp;&nbsp;):&nbsp;Observable&lt;any&gt;|Promise&lt;any&gt;|any&nbsp;{&nbsp;...&nbsp;}<br>}<br><br>{&nbsp;path:&nbsp;...,&nbsp;resolve:&nbsp;[<b>Resolve</b>Guard]&nbsp;}</code></td>
<td><code>class <b>Resolve</b>Guard implements <b>Resolve</b>&lt;T&gt; {<br> resolve(<br> route: ActivatedRouteSnapshot,<br> state: RouterStateSnapshot<br> ): Observable&lt;any&gt;|Promise&lt;any&gt;|any { ... }<br>}<br><br>{ path: ..., resolve: [<b>Resolve</b>Guard] }</code></td>
<td><p>An interface for defining a class that the router should call first to resolve route data before rendering the route. Should return a value or an Observable/Promise that resolves to a value.</p>
</td>
</tr><tr>
<td><code>class&nbsp;<b>CanLoad</b>Guard&nbsp;implements&nbsp;<b>CanLoad</b>&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp;canLoad(<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;route:&nbsp;Route<br>&nbsp;&nbsp;&nbsp;&nbsp;):&nbsp;Observable&lt;boolean&gt;|Promise&lt;boolean&gt;|boolean&nbsp;{&nbsp;...&nbsp;}<br>}<br><br>{&nbsp;path:&nbsp;...,&nbsp;canLoad:&nbsp;[<b>CanLoad</b>Guard],&nbsp;loadChildren:&nbsp;...&nbsp;}</code></td>
<td><code>class <b>CanLoad</b>Guard implements <b>CanLoad</b> {<br> canLoad(<br> route: Route<br> ): Observable&lt;boolean&gt;|Promise&lt;boolean&gt;|boolean { ... }<br>}<br><br>{ path: ..., canLoad: [<b>CanLoad</b>Guard], loadChildren: ... }</code></td>
<td><p>An interface for defining a class that the router should call first to check if the lazy loaded module should be loaded. Should return a boolean or an Observable/Promise that resolves to a boolean.</p>
</td>
</tr>

View File

@ -6,75 +6,50 @@
Improve overall data quality by validating user input for accuracy and completeness.
This page shows how to validate user input in the UI and display useful validation messages
using first the Template Driven Forms and then the Reactive Forms approach.
using both reactive and template-driven forms. It assumes some basic knowledge of the two
forms modules.
<div class="l-sub-section">
Read more about these choices in the [Forms](guide/forms)
and the [Reactive Forms](guide/reactive-forms) guides.
If you're new to forms, start by reviewing the [Forms](guide/forms) and
[Reactive Forms](guide/reactive-forms) guides.
</div>
{@a live-example}
## Template-driven validation
To add validation to a template-driven form, you add the same validation attributes as you
would with [native HTML form validation](https://developer.mozilla.org/en-US/docs/Web/Guide/HTML/HTML5/Constraint_validation).
Angular uses directives to match these attributes with validator functions in the framework.
**Try the live example to see and download the full cookbook source code.**
Every time the value of a form control changes, Angular runs validation and generates
either a list of validation errors, which results in an INVALID status, or null, which results in a VALID status.
<live-example name="form-validation" embedded=true img="guide/form-validation/plunker.png">
You can then inspect the control's state by exporting `ngModel` to a local template variable.
The following example exports `NgModel` into a variable called `name`:
</live-example>
## Simple Template Driven Forms
In the Template Driven approach, you arrange
[form elements](https://developer.mozilla.org/en-US/docs/Web/Guide/HTML/Forms_in_HTML) in the component's template.
You add Angular form directives (mostly directives beginning `ng...`) to help
Angular construct a corresponding internal control model that implements form functionality.
In Template Driven forms, the control model is _implicit_ in the template.
To validate user input, you add [HTML validation attributes](https://developer.mozilla.org/en-US/docs/Web/Guide/HTML/HTML5/Constraint_validation)
to the elements. Angular interprets those as well, adding validator functions to the control model.
Angular exposes information about the state of the controls including
whether the user has "touched" the control or made changes and if the control values are valid.
In this first template validation example,
notice the HTML that reads the control state and updates the display appropriately.
Here's an excerpt from the template HTML for a single input control bound to the hero name:
<code-example path="form-validation/src/app/template/hero-form-template1.component.html" region="name-with-error-msg" title="template/hero-form-template1.component.html (Hero name)" linenums="false">
<code-example path="form-validation/src/app/template/hero-form-template.component.html" region="name-with-error-msg" title="template/hero-form-template.component.html (name)" linenums="false">
</code-example>
Note the following:
* The `<input>` element carries the HTML validation attributes: `required`, `minlength`, and `maxlength`.
* The `<input>` element carries the HTML validation attributes: `required` and `minlength`. It
also carries a custom validator directive, `forbiddenName`. For more
information, see [Custom validators](guide/form-validation#custom-validators) section.
* The `name` attribute of the input is set to `"name"` so Angular can track this input element and associate it
with an Angular form control called `name` in its internal control model.
* The `[(ngModel)]` directive allows two-way data binding between the input box to the `hero.name` property.
* The template variable (`#name`) has the value `"ngModel"` (always `ngModel`).
This gives you a reference to the Angular `NgModel` directive
associated with this control that you can use _in the template_
to check for control states such as `valid` and `dirty`.
* `#name="ngModel"` exports `NgModel` into a local variable callled `name`. `NgModel` mirrors many of the properties of its underlying
`FormControl` instance, so you can use this in the template to check for control states such as `valid` and `dirty`. For a full list of control properties, see the [AbstractControl](api/forms/AbstractControl)
API reference.
* The `*ngIf` on the `<div>` element reveals a set of nested message `divs`
but only if there are `name` errors and
the control is either `dirty` or `touched`.
but only if the `name` is invalid and the control is either `dirty` or `touched`.
* Each nested `<div>` can present a custom message for one of the possible validation errors.
There are messages for `required`, `minlength`, and `maxlength`.
The full template repeats this kind of layout for each data entry control on the form.
{@a why-check}
There are messages for `required`, `minlength`, and `forbiddenName`.
<div class="l-sub-section">
@ -82,567 +57,152 @@ The full template repeats this kind of layout for each data entry control on the
#### Why check _dirty_ and _touched_?
The app shouldn't show errors for a new hero before the user has had a chance to edit the value.
The checks for `dirty` and `touched` prevent premature display of errors.
Learn about `dirty` and `touched` in the [Forms](guide/forms) guide.
You may not want your application to display errors before the user has a chance to edit the form.
The checks for `dirty` and `touched` prevent errors from showing until the user
does one of two things: changes the value,
turning the control dirty; or blurs the form control element, setting the control to touched.
</div>
## Reactive form validation
In a reactive form, the source of truth is the component class. Instead of adding validators through attributes in the template, you add validator functions directly to the form control model in the component class. Angular then calls these functions whenever the value of the control changes.
The component class manages the hero model used in the data binding
as well as other code to support the view.
### Validator functions
There are two types of validator functions: sync validators and async validators.
<code-example path="form-validation/src/app/template/hero-form-template1.component.ts" region="class" title="template/hero-form-template1.component.ts (class)">
* **Sync validators**: functions that take a control instance and immediately return either a set of validation errors or `null`. You can pass these in as the second argument when you instantiate a `FormControl`.
</code-example>
* **Async validators**: functions that take a control instance and return a Promise
or Observable that later emits a set of validation errors or `null`. You can
pass these in as the third argument when you instantiate a `FormControl`.
Note: for performance reasons, Angular only runs async validators if all sync validators pass. Each must complete before errors are set.
### Built-in validators
Use this Template Driven validation technique when working with static forms with simple, standard validation rules.
Here are the complete files for the first version of `HeroFormTemplateCompononent` in the Template Driven approach:
<code-tabs>
<code-pane title="template/hero-form-template1.component.html" path="form-validation/src/app/template/hero-form-template1.component.html">
</code-pane>
<code-pane title="template/hero-form-template1.component.ts" path="form-validation/src/app/template/hero-form-template1.component.ts">
</code-pane>
</code-tabs>
## Template Driven Forms with validation messages in code
While the layout is straightforward,
there are obvious shortcomings with the way it's handling validation messages:
* It takes a lot of HTML to represent all possible error conditions.
This gets out of hand when there are many controls and many validation rules.
* There's a lot of JavaScript logic in the HTML.
* The messages are static strings, hard-coded into the template.
It's easier to maintain _dynamic_ messages in the component class.
In this example, you can move the logic and the messages into the component with a few changes to
the template and component.
Here's the hero name again, excerpted from the revised template
(template 2), next to the original version:
<code-tabs>
<code-pane title="hero-form-template2.component.html (name #2)" path="form-validation/src/app/template/hero-form-template2.component.html" region="name-with-error-msg">
</code-pane>
<code-pane title="hero-form-template1.component.html (name #1)" path="form-validation/src/app/template/hero-form-template1.component.html" region="name-with-error-msg">
</code-pane>
</code-tabs>
The `<input>` element HTML is almost the same. There are noteworthy differences:
* The hard-code error message `<divs>` are gone.
* There's a new attribute, `forbiddenName`, that is actually a custom validation directive.
It invalidates the control if the user enters "bob" in the name `<input>`([try it](guide/form-validation#live-example)).
See the [custom validation](guide/form-validation#custom-validation) section later in this page for more information
on custom validation directives.
* The `#name` template variable is gone because the app no longer refers to the Angular control for this element.
* Binding to the new `formErrors.name` property is sufficient to display all name validation error messages.
{@a component-class}
### Component class
The original component code for Template 1 stayed the same; however,
Template 2 requires some changes in the component. This section covers the code
necessary in Template 2's component class to acquire the Angular
form control and compose error messages.
The first step is to acquire the form control that Angular created from the template by querying for it.
Look back at the top of the component template at the
`#heroForm` template variable in the `<form>` element:
<code-example path="form-validation/src/app/template/hero-form-template1.component.html" region="form-tag" title="template/hero-form-template1.component.html (form tag)" linenums="false">
</code-example>
The `heroForm` variable is a reference to the control model that Angular derived from the template.
Tell Angular to inject that model into the component class's `currentForm` property using a `@ViewChild` query:
<code-example path="form-validation/src/app/template/hero-form-template2.component.ts" region="view-child" title="template/hero-form-template2.component.ts (heroForm)" linenums="false">
</code-example>
Some observations:
* Angular `@ViewChild` queries for a template variable when you pass it
the name of that variable as a string (`'heroForm'` in this case).
* The `heroForm` object changes several times during the life of the component, most notably when you add a new hero.
Periodically inspecting it reveals these changes.
* Angular calls the `ngAfterViewChecked()` [lifecycle hook method](guide/lifecycle-hooks#afterview)
when anything changes in the view.
That's the right time to see if there's a new `heroForm` object.
* When there _is_ a new `heroForm` model, `formChanged()` subscribes to its `valueChanges` _Observable_ property.
The `onValueChanged` handler looks for validation errors after every keystroke.
<code-example path="form-validation/src/app/template/hero-form-template2.component.ts" region="handler" title="template/hero-form-template2.component.ts (handler)" linenums="false">
</code-example>
The `onValueChanged` handler interprets user data entry.
The `data` object passed into the handler contains the current element values.
The handler ignores them. Instead, it iterates over the fields of the component's `formErrors` object.
The `formErrors` is a dictionary of the hero fields that have validation rules and their current error messages.
Only two hero properties have validation rules, `name` and `power`.
The messages are empty strings when the hero data are valid.
For each field, the `onValueChanged` handler does the following:
* Clears the prior error message, if any.
* Acquires the field's corresponding Angular form control.
* If such a control exists _and_ it's been changed ("dirty")
_and_ it's invalid, the handler composes a consolidated error message for all of the control's errors.
Next, the component needs some error messages&mdash;a set for each validated property with
one message per validation rule:
<code-example path="form-validation/src/app/template/hero-form-template2.component.ts" region="messages" title="template/hero-form-template2.component.ts (messages)" linenums="false">
</code-example>
Now every time the user makes a change, the `onValueChanged` handler checks for validation errors and produces messages accordingly.
{@a improvement}
### The benefits of messages in code
Clearly the template got substantially smaller while the component code got substantially larger.
It's not easy to see the benefit when there are just three fields and only two of them have validation rules.
Consider what happens as the number of validated
fields and rules increases.
In general, HTML is harder to read and maintain than code.
The initial template was already large and threatening to get rapidly worse
with the addition of more validation message `<div>` elements.
After moving the validation messaging to the component,
the template grows more slowly and proportionally.
Each field has approximately the same number of lines no matter its number of validation rules.
The component also grows proportionally, at the rate of one line per validated field
and one line per validation message.
Now that the messages are in code, you have more flexibility and can compose messages more efficiently.
You can refactor the messages out of the component, perhaps to a service class that retrieves them from the server.
In short, there are more opportunities to improve message handling now that text and logic have moved from template to code.
{@a formmodule}
### _FormModule_ and Template Driven forms
Angular has two different forms modules&mdash;`FormsModule` and
`ReactiveFormsModule`&mdash;that correspond with the
two approaches to form development. Both modules come
from the same `@angular/forms` library package.
You've been reviewing the Template Driven approach which requires the `FormsModule`.
Here's how you imported it in the `HeroFormTemplateModule`.
<code-example path="form-validation/src/app/template/hero-form-template.module.ts" title="template/hero-form-template.module.ts" linenums="false">
</code-example>
<div class="l-sub-section">
This guide hasn't talked about the `SharedModule` or its `SubmittedComponent` which appears at the bottom of every
form template in this cookbook.
They're not germane to the validation story. Look at the [live example](guide/form-validation#live-example) if you're interested.
</div>
{@a reactive}
## Reactive Forms with validation in code
In the Template Driven approach, you mark up the template with form elements, validation attributes,
and `ng...` directives from the Angular `FormsModule`.
At runtime, Angular interprets the template and derives its _form control model_.
**Reactive Forms** takes a different approach.
You create the form control model in code. You write the template with form elements
and `form...` directives from the Angular `ReactiveFormsModule`.
At runtime, Angular binds the template elements to your control model based on your instructions.
This allows you to do the following:
* Add, change, and remove validation functions on the fly.
* Manipulate the control model dynamically from within the component.
* [Test](guide/form-validation#testing-considerations) validation and control logic with isolated unit tests.
The following sample re-writes the hero form in Reactive Forms style.
{@a reactive-forms-module}
### Switch to the _ReactiveFormsModule_
The Reactive Forms classes and directives come from the Angular `ReactiveFormsModule`, not the `FormsModule`.
The application module for the Reactive Forms feature in this sample looks like this:
<code-example path="form-validation/src/app/reactive/hero-form-reactive.module.ts" title="src/app/reactive/hero-form-reactive.module.ts" linenums="false">
</code-example>
The Reactive Forms feature module and component are in the `src/app/reactive` folder.
Focus on the `HeroFormReactiveComponent` there, starting with its template.
{@a reactive-component-template}
### Component template
Begin by changing the `<form>` tag so that it binds the Angular `formGroup` directive in the template
to the `heroForm` property in the component class.
The `heroForm` is the control model that the component class builds and maintains.
<code-example path="form-validation/src/app/reactive/hero-form-reactive.component.html" region="form-tag" title="form-validation/src/app/reactive/hero-form-reactive.component.html" linenums="false">
</code-example>
Next, modify the template HTML elements to match the Reactive Forms style.
Here is the "name" portion of the template again, revised for Reactive Forms and compared with the Template Driven version:
<code-tabs>
<code-pane title="hero-form-reactive.component.html (name #3)" path="form-validation/src/app/reactive/hero-form-reactive.component.html" region="name-with-error-msg">
</code-pane>
<code-pane title="hero-form-template1.component.html (name #2)" path="form-validation/src/app/template/hero-form-template2.component.html" region="name-with-error-msg">
</code-pane>
</code-tabs>
Key changes are:
* The validation attributes are gone (except `required`) because
validating happens in code.
* `required` remains, not for validation purposes (that's in the code),
but rather for css styling and accessibility.
<div class="l-sub-section">
Currently, Reactive Forms doesn't add the `required` or `aria-required`
HTML validation attribute to the DOM element
when the control has the `required` validator function.
Until then, apply the `required` attribute _and_ add the `Validator.required` function
to the control model, as you'll see below.
</div>
* The `formControlName` replaces the `name` attribute; it serves the same
purpose of correlating the input with the Angular form control.
* The two-way `[(ngModel)]` binding is gone.
The reactive approach does not use data binding to move data into and out of the form controls.
That's all in code.
You can choose to [write your own validator functions](guide/form-validation#custom-validators), or you can use some of
Angular's built-in validators.
The same built-in validators that are available as attributes in template-driven forms, such as `required` and `minlength`, are all available to use as functions from the `Validators` class. For a full list of built-in validators, see the [Validators](api/forms/Validators) API reference.
To update the hero form to be a reactive form, you can use some of the same
built-in validators&mdash;this time, in function form. See below:
{@a reactive-component-class}
### Component class
The component class is now responsible for defining and managing the form control model.
Angular no longer derives the control model from the template so you can no longer query for it.
You can create the Angular form control model explicitly with
the help of the `FormBuilder` class.
Here's the section of code devoted to that process, paired with the Template Driven code it replaces:
<code-tabs>
<code-pane title="reactive/hero-form-reactive.component.ts (FormBuilder)" path="form-validation/src/app/reactive/hero-form-reactive.component.ts" region="form-builder">
</code-pane>
<code-pane title="template/hero-form-template2.component.ts (ViewChild)" path="form-validation/src/app/template/hero-form-template2.component.ts" region="view-child">
</code-pane>
</code-tabs>
* Inject `FormBuilder` in a constructor.
* Call a `buildForm` method in the `ngOnInit` [lifecycle hook method](guide/lifecycle-hooks#hooks-overview)
because that's when you'll have the hero data. Call it again in the `addHero` method.
<div class="l-sub-section">
A real app would retrieve the hero asynchronously from a data service, a task best performed in the `ngOnInit` hook.
</div>
* The `buildForm` method uses the `FormBuilder`, `fb`, to declare the form control model.
Then it attaches the same `onValueChanged` handler (there's a one line difference)
to the form's `valueChanges` event and calls it immediately
to set error messages for the new control model.
## Built-in validators
Angular forms include a number of built-in validator functions, which are functions
that help you check common user input in forms. In addition to the built-in
validators covered here of `minlength`, `maxlength`,
and `required`, there are others such as `email` and `pattern`
for Reactive Forms.
For a full list of built-in validators,
see the [Validators](api/forms/Validators) API reference.
#### _FormBuilder_ declaration
The `FormBuilder` declaration object specifies the three controls of the sample's hero form.
Each control spec is a control name with an array value.
The first array element is the current value of the corresponding hero field.
The optional second value is a validator function or an array of validator functions.
Most of the validator functions are stock validators provided by Angular as static methods of the `Validators` class.
Angular has stock validators that correspond to the standard HTML validation attributes.
The `forbiddenName` validator on the `"name"` control is a custom validator,
discussed in a separate [section below](guide/form-validation#custom-validation).
<div class="l-sub-section">
Learn more about `FormBuilder` in the [Introduction to FormBuilder](guide/reactive-forms#formbuilder) section of Reactive Forms guide.
</div>
#### Committing hero value changes
In two-way data binding, the user's changes flow automatically from the controls back to the data model properties.
A Reactive Forms component should not use data binding to
automatically update data model properties.
The developer decides _when and how_ to update the data model from control values.
This sample updates the model twice:
1. When the user submits the form.
1. When the user adds a new hero.
The `onSubmit()` method simply replaces the `hero` object with the combined values of the form:
<code-example path="form-validation/src/app/reactive/hero-form-reactive.component.ts" region="on-submit" title="form-validation/src/app/reactive/hero-form-reactive.component.ts" linenums="false">
<code-example path="form-validation/src/app/reactive/hero-form-reactive.component.ts" region="form-group" title="reactive/hero-form-reactive.component.ts (validator functions)" linenums="false">
</code-example>
Note that:
The `addHero()` method discards pending changes and creates a brand new `hero` model object.
* The name control sets up two built-in validators&mdash;`Validators.required` and `Validators.minLength(4)`&mdash;and one custom validator, `forbiddenNameValidator`. For more details see the [Custom validators](guide/form-validation#custom-validators) section in this guide.
* As these validators are all sync validators, you pass them in as the second argument.
* Support multiple validators by passing the functions in as an array.
* This example adds a few getter methods. In a reactive form, you can always access any form control through the `get` method on its parent group, but sometimes it's useful to define getters as shorthands
for the template.
<code-example path="form-validation/src/app/reactive/hero-form-reactive.component.ts" region="add-hero" title="form-validation/src/app/reactive/hero-form-reactive.component.ts" linenums="false">
If you look at the template for the name input again, it is fairly similar to the template-driven example.
<code-example path="form-validation/src/app/reactive/hero-form-reactive.component.html" region="name-with-error-msg" title="reactive/hero-form-reactive.component.html (name with error msg)" linenums="false">
</code-example>
Key takeaways:
* The form no longer exports any directives, and instead uses the `name` getter defined in
the component class.
* The `required` attribute is still present. While it's not necessary for validation purposes,
you may want to keep it in your template for CSS styling or accessibility reasons.
Then it calls `buildForm()` again which replaces the previous `heroForm` control model with a new one.
The `<form>` tag's `[formGroup]` binding refreshes the page with the new control model.
## Custom validators
Here's the complete reactive component file, compared to the two Template Driven component files.
Since the built-in validators won't always match the exact use case of your application, sometimes you'll want to create a custom validator.
<code-tabs>
<code-pane title="reactive/hero-form-reactive.component.ts (#3)" path="form-validation/src/app/reactive/hero-form-reactive.component.ts">
</code-pane>
<code-pane title="template/hero-form-template2.component.ts (#2)" path="form-validation/src/app/template/hero-form-template2.component.ts">
</code-pane>
<code-pane title="template/hero-form-template1.component.ts (#1)" path="form-validation/src/app/template/hero-form-template1.component.ts">
</code-pane>
</code-tabs>
<div class="l-sub-section">
Run the [live example](guide/form-validation#live-example) to see how the reactive form behaves,
and to compare all of the files in this sample.
</div>
## Custom validation
This cookbook sample has a custom `forbiddenNameValidator()` function that's applied to both the
Template Driven and the reactive form controls. It's in the `src/app/shared` folder
and declared in the `SharedModule`.
Here's the `forbiddenNameValidator()` function:
Consider the `forbiddenNameValidator` function from previous
[examples](guide/form-validation#reactive-component-class) in
this guide. Here's what the definition of that function looks like:
<code-example path="form-validation/src/app/shared/forbidden-name.directive.ts" region="custom-validator" title="shared/forbidden-name.directive.ts (forbiddenNameValidator)" linenums="false">
</code-example>
The function is actually a factory that takes a regular expression to detect a _specific_ forbidden name and returns a validator function.
The function is actually a factory that takes a regular expression to detect a _specific_ forbidden name
and returns a validator function.
In this sample, the forbidden name is "bob";
the validator rejects any hero name containing "bob".
In this sample, the forbidden name is "bob", so the validator will reject any hero name containing "bob".
Elsewhere it could reject "alice" or any name that the configuring regular expression matches.
The `forbiddenNameValidator` factory returns the configured validator function.
That function takes an Angular control object and returns _either_
null if the control value is valid _or_ a validation error object.
The validation error object typically has a property whose name is the validation key, `'forbiddenName'`,
and whose value is an arbitrary dictionary of values that you could insert into an error message (`{name}`).
and whose value is an arbitrary dictionary of values that you could insert into an error message, `{name}`.
### Adding to reactive forms
In reactive forms, custom validators are fairly simple to add. All you have to do is pass the function directly
to the `FormControl`.
### Custom validation directive
In the Reactive Forms component, the `'name'` control's validator function list
has a `forbiddenNameValidator` at the bottom.
<code-example path="form-validation/src/app/reactive/hero-form-reactive.component.ts" region="name-validators" title="reactive/hero-form-reactive.component.ts (name validators)" linenums="false">
<code-example path="form-validation/src/app/reactive/hero-form-reactive.component.ts" region="custom-validator" title="reactive/hero-form-reactive.component.ts (validator functions)" linenums="false">
</code-example>
### Adding to template-driven forms
In template-driven forms, you don't have direct access to the `FormControl` instance, so you can't pass the
validator in like you can for reactive forms. Instead, you need to add a directive to the template.
In the Template Driven example, the `<input>` has the selector (`forbiddenName`)
of a custom _attribute directive_, which rejects "bob".
The corresponding `ForbiddenValidatorDirective` serves as a wrapper around the `forbiddenNameValidator`.
<code-example path="form-validation/src/app/template/hero-form-template2.component.html" region="name-input" title="template/hero-form-template2.component.html (name input)" linenums="false">
</code-example>
The corresponding `ForbiddenValidatorDirective` is a wrapper around the `forbiddenNameValidator`.
Angular `forms` recognizes the directive's role in the validation process because the directive registers itself
with the `NG_VALIDATORS` provider, a provider with an extensible collection of validation directives.
Angular recognizes the directive's role in the validation process because the directive registers itself
with the `NG_VALIDATORS` provider, a provider with an extensible collection of validators.
<code-example path="form-validation/src/app/shared/forbidden-name.directive.ts" region="directive-providers" title="shared/forbidden-name.directive.ts (providers)" linenums="false">
</code-example>
Here is the rest of the directive to help you get an idea of how it all comes together:
The directive class then implements the `Validator` interface, so that it can easily integrate
with Angular forms. Here is the rest of the directive to help you get an idea of how it all
comes together:
<code-example path="form-validation/src/app/shared/forbidden-name.directive.ts" region="directive" title="shared/forbidden-name.directive.ts (directive)">
</code-example>
Once the `ForbiddenValidatorDirective` is ready, you can simply add its selector, `forbiddenName`, to any input element to activate it. For example:
<code-example path="form-validation/src/app/template/hero-form-template.component.html" region="name-input" title="template/hero-form-template.component.html (forbidden-name-input)" linenums="false">
</code-example>
<div class="l-sub-section">
If you are familiar with Angular validations, you may have noticed
that the custom validation directive is instantiated with `useExisting`
You may have noticed that the custom validation directive is instantiated with `useExisting`
rather than `useClass`. The registered validator must be _this instance_ of
the `ForbiddenValidatorDirective`&mdash;the instance in the form with
its `forbiddenName` property bound to “bob". If you were to replace
`useExisting` with `useClass`, then youd be registering a new class instance, one that
doesnt have a `forbiddenName`.
To see this in action, run the example and then type “bob” in the name of Hero Form 2.
Notice that you get a validation error. Now change from `useExisting` to `useClass` and try again.
This time, when you type “bob”, there's no "bob" error message.
</div>
## Control status CSS classes
<div class="l-sub-section">
Like in AngularJS, Angular automatically mirrors many control properties onto the form control element as CSS classes. You can use these classes to style form control elements according to the state of the form. The following classes are currently supported:
For more information on attaching behavior to elements,
see [Attribute Directives](guide/attribute-directives).
* `.ng-valid`
* `.ng-invalid`
* `.ng-pending`
* `.ng-pristine`
* `.ng-dirty`
* `.ng-untouched`
* `.ng-touched`
</div>
The hero form uses the `.ng-valid` and `.ng-invalid` classes to
set the color of each form control's border.
<code-example path="form-validation/src/forms.css" title="forms.css (status classes)">
</code-example>
## Testing Considerations
You can write _isolated unit tests_ of validation and control logic in Reactive Forms.
_Isolated unit tests_ probe the component class directly, independent of its
interactions with its template, the DOM, other dependencies, or Angular itself.
Such tests have minimal setup, are quick to write, and easy to maintain.
They do not require the `Angular TestBed` or asynchronous testing practices.
That's not possible with Template Driven forms.
The Template Driven approach relies on Angular to produce the control model and
to derive validation rules from the HTML validation attributes.
You must use the `Angular TestBed` to create component test instances,
write asynchronous tests, and interact with the DOM.
While not difficult, this takes more time, work and
skill&mdash;factors that tend to diminish test code
coverage and quality.
**You can run the <live-example></live-example> to see the complete reactive and template-driven example code.**

View File

@ -29,7 +29,7 @@ import {HttpClientModule} from '@angular/common/http';
export class MyAppModule {}
```
Once you import `HttpClientModule` into your app module, you can inject `HttpClient`
Once you import `HttpClientModule` into your app module, you can inject `HttpClient`
into your components and services.
## Making a request for JSON data
@ -50,10 +50,13 @@ The `get()` method on `HttpClient` makes accessing this data straightforward.
```javascript
@Component(...)
export class MyComponent implements NgOnInit {
export class MyComponent implements OnInit {
results: string[];
// Inject HttpClient into your component or service.
constructor(private http: HttpClient) {}
ngOnInit(): void {
// Make the HTTP request:
this.http.get('/api/items').subscribe(data => {
@ -69,7 +72,7 @@ export class MyComponent implements NgOnInit {
In the above example, the `data['results']` field access stands out because you use bracket notation to access the results field. If you tried to write `data.results`, TypeScript would correctly complain that the `Object` coming back from HTTP does not have a `results` property. That's because while `HttpClient` parsed the JSON response into an `Object`, it doesn't know what shape that object is.
You can, however, tell `HttpClient` what type the response will be, which is recommended.
You can, however, tell `HttpClient` what type the response will be, which is recommended.
To do so, first you define an interface with the correct shape:
```javascript
@ -160,7 +163,7 @@ RxJS has a useful operator called `.retry()`, which automatically resubscribes t
First, import it:
```js
```js
import 'rxjs/add/operator/retry';
```
@ -194,7 +197,7 @@ In addition to fetching data from the server, `HttpClient` supports mutating req
### Making a POST request
One common operation is to POST data to a server; for example when submitting a form. The code for
One common operation is to POST data to a server; for example when submitting a form. The code for
sending a POST request is very similar to the code for GET:
```javascript
@ -258,22 +261,22 @@ The above sections detail how to use the basic HTTP functionality in `@angular/c
### Intercepting all requests or responses
A major feature of `@angular/common/http` is _interception_, the ability to declare interceptors which sit in between your application and the backend. When your application makes a request, interceptors transform it
A major feature of `@angular/common/http` is _interception_, the ability to declare interceptors which sit in between your application and the backend. When your application makes a request, interceptors transform it
before sending it to the server, and the interceptors can transform the response on its way back before your application sees it. This is useful for everything from authentication to logging.
#### Writing an interceptor
To implement an interceptor, you declare a class that implements `HttpInterceptor`, which
To implement an interceptor, you declare a class that implements `HttpInterceptor`, which
has a single `intercept()` method. Here is a simple interceptor which does nothing but forward the request through without altering it:
```javascript
import {Injectable} from '@angular/core';
import {HttpEvent, HttpInterceptor, HttpHandler, HttpRequest) from '@angular/common/http';
import {HttpEvent, HttpInterceptor, HttpHandler, HttpRequest} from '@angular/common/http';
@Injectable()
export class NoopInterceptor implements HttpInterceptor {
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
return next.handle(Req);
return next.handle(req);
}
}
```
@ -316,7 +319,7 @@ An interceptor must pass through all events that it does not understand or inten
##### Ordering
When you provide multiple interceptors in an application, Angular applies them in the order that you
When you provide multiple interceptors in an application, Angular applies them in the order that you
provided them.
##### Immutability
@ -332,10 +335,10 @@ If you have a need to mutate the request body, you need to copy the request body
Since requests are immutable, they cannot be modified directly. To mutate them, use `clone()`:
```javascript
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpError<any>> {
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
// This is a duplicate. It is exactly the same as the original.
const dupReq = req.clone();
// Change the URL and replace 'http://' with 'https://'
const secureReq = req.clone({url: req.url.replace('http://', 'https://')});
}
@ -349,15 +352,15 @@ A common use of interceptors is to set default headers on outgoing responses. Fo
```javascript
import {Injectable} from '@angular/core';
import {HttpEvent, HttpInterceptor, HttpHandler, HttpRequest) from '@angular/common/http';
import {HttpEvent, HttpInterceptor, HttpHandler, HttpRequest} from '@angular/common/http';
@Injectable()
export class AuthInterceptor implements HttpInterceptor {
constructor(private auth: AuthService) {}
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
// Get the auth header from the service.
const authHeader: this.auth.getAuthorizationHeader();
const authHeader = this.auth.getAuthorizationHeader();
// Clone the request to add the new header.
const authReq = req.clone({headers: req.headers.set('Authorization', authHeader)});
// Pass on the cloned request instead of the original request.
@ -387,15 +390,15 @@ import 'rxjs/add/operator/do';
export class TimingInterceptor implements HttpInterceptor {
constructor(private auth: AuthService) {}
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
const elapsed = Date.now();
const started = Date.now();
return next
.handle(req)
.do(event => {
if (event instanceof HttpResponse) {
const time = Date.now() - started;
console.log(`Request for ${req.urlWithParams} took ${elapsed} ms.`);
const elapsed = Date.now() - started;
console.log(`Request for ${req.urlWithParams} took ${elapsed} ms.`);
}
});
}
@ -413,7 +416,7 @@ abstract class HttpCache {
* Returns a cached response, if any, or null if not present.
*/
abstract get(req: HttpRequest<any>): HttpResponse<any>|null;
/**
* Adds or updates the response in the cache.
*/
@ -427,14 +430,14 @@ An interceptor can apply this cache to outgoing requests.
@Injectable()
export class CachingInterceptor implements HttpInterceptor {
constructor(private cache: HttpCache) {}
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
// Before doing anything, it's important to only cache GET requests.
// Skip this interceptor if the request method isn't GET.
if (req.method !== 'GET') {
return next.handle(req);
}
// First, check the cache to see if this request exists.
const cachedResponse = this.cache.get(req);
if (cachedResponse) {
@ -442,7 +445,7 @@ export class CachingInterceptor implements HttpInterceptor {
// the request to the next handler.
return Observable.of(cachedResponse);
}
// No cached response exists. Go to the network, and cache
// the response when it arrives.
return next.handle(req).do(event => {
@ -466,17 +469,17 @@ intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>>
if (req.method !== 'GET') {
return next.handle(req);
}
// This will be an Observable of the cached value if there is one,
// or an empty Observable otherwise. It starts out empty.
let maybeCachedResponse: Observable<HttpEvent<any>> = Observable.empty();
// Check the cache.
const cachedResponse = this.cache.get(req);
if (cachedResponse) {
maybeCachedResponse = Observable.of(cachedResponse);
}
// Create an Observable (but don't subscribe) that represents making
// the network request and caching the value.
const networkResponse = next.handle(req).do(event => {
@ -485,7 +488,7 @@ intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>>
this.cache.put(req, event);
}
});
// Now, combine the two and send the cached response first (if there is
// one), and the network response second.
return Observable.concat(maybeCachedResponse, networkResponse);
@ -506,7 +509,7 @@ const req = new HttpRequest('POST', '/upload/file', file, {
});
```
This option enables tracking of progress events. Remember, every progress event triggers
This option enables tracking of progress events. Remember, every progress event triggers
change detection, so only turn them on if you intend to actually update the UI on each event.
Next, make the request through the `request()` method of `HttpClient`. The result will be an Observable of events, just like with interceptors:
@ -529,11 +532,11 @@ http.request(req).subscribe(event => {
[Cross-Site Request Forgery (XSRF)](https://en.wikipedia.org/wiki/Cross-site_request_forgery) is an attack technique by which the attacker can trick an authenticated user into unknowingly executing actions on your website. `HttpClient` supports a [common mechanism](https://en.wikipedia.org/wiki/Cross-site_request_forgery#Cookie-to-Header_Token) used to prevent XSRF attacks. When performing HTTP requests, an interceptor reads a token from a cookie, by default `XSRF-TOKEN`, and sets it as an HTTP header, `X-XSRF-TOKEN`. Since only code that runs on your domain could read the cookie, the backend can be certain that the HTTP request came from your client application and not an attacker.
By default, an interceptor sends this cookie on all mutating requests (POST, etc.)
to relative URLs but not on GET/HEAD requests or
By default, an interceptor sends this cookie on all mutating requests (POST, etc.)
to relative URLs but not on GET/HEAD requests or
on requests with an absolute URL.
To take advantage of this, your server needs to set a token in a JavaScript readable session cookie called `XSRF-TOKEN` on either the page load or the first GET request. On subsequent requests the server can verify that the cookie matches the `X-XSRF-TOKEN` HTTP header, and therefore be sure that only code running on your domain could have sent the request. The token must be unique for each user and must be verifiable by the server; this prevents the client from making up its own tokens. Set the token to a digest of your site's authentication
To take advantage of this, your server needs to set a token in a JavaScript readable session cookie called `XSRF-TOKEN` on either the page load or the first GET request. On subsequent requests the server can verify that the cookie matches the `X-XSRF-TOKEN` HTTP header, and therefore be sure that only code running on your domain could have sent the request. The token must be unique for each user and must be verifiable by the server; this prevents the client from making up its own tokens. Set the token to a digest of your site's authentication
cookie with a salt for added security.
In order to prevent collisions in environments where multiple Angular apps share the same domain or subdomain, give each application a unique cookie name.
@ -595,23 +598,23 @@ it('expects a GET request', inject([HttpClient, HttpTestingController], (http: H
http
.get('/data')
.subscribe(data => expect(data['name']).toEqual('Test Data'));
// At this point, the request is pending, and no response has been
// sent. The next step is to expect that the request happened.
const req = httpMock.expectOne('/test');
const req = httpMock.expectOne('/data');
// If no request with that URL was made, or if multiple requests match,
// expectOne() would throw. However this test makes only one request to
// this URL, so it will match and return a mock request. The mock request
// can be used to deliver a response or make assertions against the
// request. In this case, the test asserts that the request is a GET.
expect(req.request.method).toEqual('GET');
// Next, fulfill the request by transmitting a response.
req.flush({name: 'Test Data'});
// Finally, assert that there are no outstanding requests.
mockHttp.verify();
httpMock.verify();
}));
```
@ -619,7 +622,7 @@ The last step, verifying that no requests remain outstanding, is common enough f
```javascript
afterEach(inject([HttpTestingController], (httpMock: HttpTestingController) => {
mockHttp.verify();
httpMock.verify();
}));
```
@ -628,7 +631,7 @@ afterEach(inject([HttpTestingController], (httpMock: HttpTestingController) => {
If matching by URL isn't sufficient, it's possible to implement your own matching function. For example, you could look for an outgoing request that has an Authorization header:
```javascript
const req = mockHttp.expectOne((req) => req.headers.has('Authorization'));
const req = httpMock.expectOne((req) => req.headers.has('Authorization'));
```
Just as with the `expectOne()` by URL in the test above, if 0 or 2+ requests match this expectation, it will throw.
@ -639,7 +642,7 @@ If you need to respond to duplicate requests in your test, use the `match()` API
```javascript
// Expect that 5 pings have been made and flush them.
const reqs = mockHttp.match('/ping');
const reqs = httpMock.match('/ping');
expect(reqs.length).toBe(5);
reqs.forEach(req => req.flush());
```

View File

@ -408,7 +408,7 @@ This XML element represents the translation of the `<h1>` greeting tag you marke
<div class="l-sub-section">
Note that the translation unit `id=introductionHeader` is derived from the _custom_ `id`](#custom-id "Set a custom id") that you set earlier, but **without the `@@` prefix** required in the source HTML.
Note that the translation unit `id=introductionHeader` is derived from the [_custom_ `id`](#custom-id "Set a custom id") that you set earlier, but **without the `@@` prefix** required in the source HTML.
</div>

View File

@ -397,8 +397,7 @@ created under test or before you decide to display it.
Constructors should do no more than set the initial local variables to simple values.
An `ngOnInit()` is a good place for a component to fetch its initial data. The
[Tour of Heroes Tutorial](tutorial/toh-pt4#oninit) and [HTTP Client](guide/http#oninit)
guides show how.
[Tour of Heroes Tutorial](tutorial/toh-pt4#oninit) guide shows how.
Remember also that a directive's data-bound input properties are not set until _after construction_.

View File

@ -615,7 +615,7 @@ Once the application begins, the app root injector is closed to new providers.
Time passes and application logic triggers lazy loading of a module.
Angular must add the lazy-loaded module's providers to an injector somewhere.
It can't added them to the app root injector because that injector is closed to new providers.
It can't add them to the app root injector because that injector is closed to new providers.
So Angular creates a new child injector for the lazy-loaded module context.

View File

@ -163,7 +163,6 @@ without waiting for Angular updates.
***angular-in-memory-web-api***: An Angular-supported library that simulates a remote server's web api
without requiring an actual server or real HTTP calls.
Good for demos, samples, and early stage development (before you even have a server).
Read about it in the [HTTP Client](guide/http#in-mem-web-api) page.
***bootstrap***: [Bootstrap](http://getbootstrap.com/) is a popular HTML and CSS framework for designing responsive web apps.
Some of the samples improve their appearance with *bootstrap*.

View File

@ -1080,8 +1080,11 @@ To get access to the `FormArray` class, import it into `hero-detail.component.ts
To _work_ with a `FormArray` you do the following:
1. Define the items (`FormControls` or `FormGroups`) in the array.
1. Initialize the array with items created from data in the _data model_.
1. Add and remove items as the user requires.
In this guide, you define a `FormArray` for `Hero.addresses` and

View File

@ -255,11 +255,11 @@ During each navigation, the `Router` emits navigation events through the `Router
<tr>
<td>
<code>RouteConfigLoadStart</code>
<code>RouteConfigLoadEnd</code>
</td>
<td>
An [event](api/router/RouteConfigLoadStart) triggered after a route has been lazy loaded.
An [event](api/router/RouteConfigLoadEnd) triggered after a route has been lazy loaded.
</td>
</tr>
@ -1830,7 +1830,7 @@ Finally, you activate the observable with `subscribe` method and (re)set the com
#### _ParamMap_ API
The `ParamMap` API is inspired by the [URLSearchParams interface](https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParamsOPut). It provides methods
The `ParamMap` API is inspired by the [URLSearchParams interface](https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParams). It provides methods
to handle parameter access for both route parameters (`paramMap`) and query parameters (`queryParamMap`).
<table>

View File

@ -178,7 +178,7 @@ For a discussion of the unit testing setup files, [see below](guide/testing#setu
{@a isolated-v-testing-utilities}
### Isolated unit tests vs. the Angular testing utilites
### Isolated unit tests vs. the Angular testing utilities
[Isolated unit tests](guide/testing#isolated-unit-tests "Unit testing without the Angular testing utilities")
examine an instance of a class all by itself without any dependence on Angular or any injected values.

View File

@ -247,12 +247,10 @@ next to the original _ES5_ version for comparison:
</code-pane>
</code-tabs>
{@a name-constructor}
<div class="callout is-helpful">
{@a name-constructor}
### Name the constructor
A **named** constructor displays clearly in the console log
if the component throws a runtime error.
An **unnamed** constructor displays as an anonymous function, for example, `class0`,

Binary file not shown.

After

Width:  |  Height:  |  Size: 89 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 77 KiB

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 135 KiB

After

Width:  |  Height:  |  Size: 101 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 119 KiB

After

Width:  |  Height:  |  Size: 96 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 320 KiB

After

Width:  |  Height:  |  Size: 200 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 90 KiB

After

Width:  |  Height:  |  Size: 83 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 9.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.9 KiB

After

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.1 KiB

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.8 KiB

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.8 KiB

After

Width:  |  Height:  |  Size: 8.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.9 KiB

After

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.9 KiB

After

Width:  |  Height:  |  Size: 9.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.9 KiB

After

Width:  |  Height:  |  Size: 8.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.8 KiB

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.7 KiB

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.9 KiB

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.7 KiB

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.8 KiB

View File

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.0 KiB

View File

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

View File

Before

Width:  |  Height:  |  Size: 85 KiB

After

Width:  |  Height:  |  Size: 85 KiB

View File

Before

Width:  |  Height:  |  Size: 257 KiB

After

Width:  |  Height:  |  Size: 257 KiB

View File

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 17 KiB

View File

Before

Width:  |  Height:  |  Size: 27 KiB

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.9 KiB

After

Width:  |  Height:  |  Size: 4.1 KiB

View File

@ -342,7 +342,7 @@
"name": "Ralph Wang",
"picture": "ralph.jpg",
"twitter": "ralph_wang_gde",
"bio": "Ralph(Zhicheng Wang) is a senior consultant at ThoughWorks and also a GDE. He is a technology enthusiast and he is a passionate advocate of 'Simplicity, Professionalism and Sharing'. In his eighteen years of R&D career, he worked as tester, R&D engineer, project manager, product manager and CTO. He is looking forward to the birth of his baby.",
"bio": "Ralph(Zhicheng Wang) is a senior consultant at ThoughtWorks and also a GDE. He is a technology enthusiast and he is a passionate advocate of 'Simplicity, Professionalism and Sharing'. In his eighteen years of R&D career, he worked as tester, R&D engineer, project manager, product manager and CTO. He is immersed in the excitement of the arrival of the baby.",
"group": "GDE"
},
@ -573,5 +573,14 @@
"website": "http://www.methotic.com",
"bio": "Thierry is a senior consultant and trainer, specialized on Angular, and a Google Developer Expert.",
"group": "GDE"
},
"gerardsans": {
"name": "Gerard Sans",
"picture": "gerardsans.jpg",
"twitter": "gerardsans",
"website": "https://medium.com/@gerard.sans",
"bio": "Gerard is very excited about the future of the Web and JavaScript. Always happy Computer Science Engineer and humble Google Developer Expert. He loves to share his learnings by giving talks, trainings and writing about cool technologies. He loves running AngularZone and GraphQL London, mentoring students and giving back to the community.",
"group": "GDE"
}
}

View File

@ -8,7 +8,7 @@
<div class="feature-section">
<div class="feature-header">
<div class="text-headline">Cross Platform</div>
<img src="../assets/images/icons/feature-icon.svg" height="70px">
<img src="generated/images/marketing/features/feature-icon.svg" height="70px">
</div>
<div class="feature-row">
@ -34,7 +34,7 @@
<div class="feature-section">
<div class="feature-header">
<div class="text-headline">Speed and Performance</div>
<img src="../assets/images/icons/feature-icon.svg" height="70px">
<img src="generated/images/marketing/features/feature-icon.svg" height="70px">
</div>
<div class="feature-row">
@ -59,7 +59,7 @@
<div class="feature-section">
<div class="feature-header">
<div class="text-headline">Productivity</div>
<img src="../assets/images/icons/feature-icon.svg" height="70px">
<img src="generated/images/marketing/features/feature-icon.svg" height="70px">
</div>
<div class="feature-row">
@ -84,7 +84,7 @@
<div class="feature-section">
<div class="feature-header">
<div class="text-headline">Full Development Story</div>
<img src="../assets/images/icons/feature-icon.svg" height="70px">
<img src="generated/images/marketing/features/feature-icon.svg" height="70px">
</div>
<div class="feature-row">

View File

@ -30,7 +30,7 @@
<!--Announcement Bar-->
<div class="homepage-container">
<div class="announcement-bar">
<img src="generated/images/marketing/angular-mix.png" height="40" width="151">
<img src="generated/images/marketing/home/angular-mix.png" height="40" width="151">
<p>Join us at our newest event, October 2017</p>
<a class="button" href="https://angularmix.com/">Learn More</a>
</div>
@ -40,7 +40,7 @@
<div layout="row" layout-xs="column" class="home-row homepage-container">
<div class="promo-img-container promo-1">
<div>
<img height="222" width="340" src="assets/images/home/responsive-framework.svg" alt="responsive framework">
<img height="222" width="340" src="generated/images/marketing/home/responsive-framework.svg" alt="responsive framework">
</div>
</div>
@ -65,7 +65,7 @@
<div class="promo-img-container promo-2">
<div>
<img height="222" width="323" src="assets/images/home/speed-performance.svg" alt="speed and performance">
<img height="222" width="323" src="generated/images/marketing/home/speed-performance.svg" alt="speed and performance">
</div>
</div>
</div>
@ -74,7 +74,7 @@
<!-- Group 3-->
<div layout="row" layout-xs="column" class="home-row">
<div class="promo-img-container promo-3">
<div><img src="assets/images/home/joyful-development.png" alt="IDE example"></div>
<div><img src="generated/images/marketing/home/joyful-development.svg" alt="IDE example"></div>
</div>
<div class="text-container">
@ -100,7 +100,7 @@
<div class="promo-img-container promo-4">
<div>
<img src="assets/images/home/loved-by-millions.png" alt="angular on the map" width="455" height="228">
<img src="generated/images/marketing/home/loved-by-millions.svg" alt="angular on the map" width="455" height="228">
</div>
</div>
</div>
@ -110,7 +110,7 @@
<a href="guide/quickstart">
<div class="card">
<img src="../assets/images/icons/code-icon.svg" height="70px">
<img src="generated/images/marketing/home/code-icon.svg" height="70px">
<div class="card-text-container">
<div class="text-headline">Get Started</div>
<p>Start building your Angular application.</p>

Some files were not shown because too many files have changed in this diff Show More