Compare commits

..

130 Commits

Author SHA1 Message Date
1e1af7ffcb docs: changelog for 5.0.0-beta.2 release 2017-08-02 13:23:27 -07:00
a84b2bc945 release: cut the 5.0.0-beta.2 release 2017-08-02 13:21:07 -07:00
7abcb99d57 docs: add changelog for 4.3.3 2017-08-02 13:19:00 -07:00
49cd8513e4 feat(router): add events tracking activation of individual routes
* Adds `ChildActivationStart` and `ChildActivationEnd`
* Adds test to verify the PreActivation phase of routing
2017-08-01 10:44:00 -07:00
82b067fc40 build: ignore node_modules for tslint 2017-08-01 10:13:44 -07:00
9479a106bb build: enable TSLint on the packages folder 2017-07-31 15:47:57 -07:00
e64b54b67b fix(compiler): do not consider arguments when determining recursion
The static reflectory check for macro function recursion was too
agressive and disallowed calling a function with argument that also
calls the same function. For example, it disallowed nested animation
groups.

Fixes: #17467
2017-07-31 13:42:31 -07:00
cc2a4c41f9 build(aio): fix warning about missing <h1>
Fixes #17549
2017-07-31 13:40:07 -07:00
a11542a375 docs(aio): fixed list format in FormArray section 2017-07-31 11:31:05 -07:00
b6c4af6495 feat(compiler-cli): automatically lower lambda expressions in metadata 2017-07-31 11:30:44 -07:00
67dff7bd5d feat(tsc-wrapped): allow values to be substituted by collector clients
Also reenabled tests that were unintentionally disabled when they were
moved from tools/@angular.
2017-07-31 11:30:44 -07:00
381471d338 fix(compiler): fix for element needing implicit parent placed in top-level ng-container
fixes #18314
2017-07-31 11:30:19 -07:00
ebef5e697a feat(forms): add options arg to abstract controls
FormControls, FormGroups, and FormArrays now optionally accept an options
object as their second argument. Validators and async validators can be
passed in as part of this options object (though they can still be passed
in as the second and third arg as before).

```ts
const c = new FormControl(, {
   validators: [Validators.required],
   asyncValidators: [myAsyncValidator]
});
```

This commit also adds support for passing arrays of validators and async
validators to FormGroups and FormArrays, which formerly only accepted
individual functions.

```ts
const g = new FormGroup({
   one: new FormControl()
}, [myPasswordValidator, myOtherValidator]);
```

This change paves the way for adding more options to AbstractControls,
such as more fine-grained control of validation timing.
2017-07-31 11:29:32 -07:00
d71ae278ef fix(aio): fix links to source for paths with symlinks
Fixes #18353
2017-07-28 15:28:59 -07:00
46207538ef ci: short-circuit npm install for aio builds that use yarn only 2017-07-28 15:28:28 -07:00
71eb7437b6 docs(aio): delay ngUpgrade e2e test to avoid flakes 2017-07-28 15:28:28 -07:00
b5ffbe342b build: short-circuit build for AIO tasks 2017-07-28 15:28:28 -07:00
0f79223008 docs(aio): fix deprecated protractor API usage
`browser.getLocationAbsUrl()` is deprecated.
We should use `browser.getCurrentUrl()` instead.
2017-07-28 15:28:28 -07:00
a085223331 ci(aio): test the example e2e files using local build of Angular 2017-07-28 15:28:28 -07:00
c383048259 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-28 15:28:28 -07:00
b18eb04b46 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-28 15:28:28 -07:00
c8c2ab012a build(aio): support overriding the Angular packages in examples with locally built ones 2017-07-28 15:28:28 -07:00
ecff8e6c93 build(aio): refactor and test the example-boilerplate tool 2017-07-28 15:28:28 -07:00
51f1da1b85 ci: shard the aio example e2e tests 2017-07-28 15:28:28 -07:00
a5e18c4cdf ci(aio): support sharding of example e2e tests 2017-07-28 15:28:28 -07:00
cf6284656f build(aio): upgrade @angular/material to 2.0.0-meta.8 2017-07-28 15:26:45 -07:00
3182ddaf3e build(aio): upgrade @angular/* to 4.3.1 2017-07-28 15:26:45 -07:00
416ed691e5 docs(aio): fix URLSearchParams interface link to MDN
Fixes #18367
2017-07-28 15:26:04 -07:00
0fb7484d51 refactor(aio): move content-specific images to content/images/
Fixes #17053
2017-07-28 15:06:49 -07:00
6a3454e81e refactor(aio): rename unused directories to _unused 2017-07-28 15:06:49 -07:00
c3fbe87012 fix(aio): fix link to logo in example 2017-07-28 15:06:49 -07:00
24117d7a49 refactor(aio): move unused images to unused directories
This prevents the ServiceWorker from prefetching unnecessary files.
2017-07-28 15:06:49 -07:00
5808153359 docs: add changelog for 5.0.0.-beta.1 2017-07-27 14:59:24 -07:00
9030c8a03e release: cut the 5.0.0-beta.1 release 2017-07-27 14:57:38 -07:00
b14fc06fa2 docs: add changelog for 4.3.2 2017-07-27 14:52:35 -07:00
a7f2468184 Revert "fix(router): should throw when lazy loaded module doesn't define any routes (#15001)"
This reverts commit 82923a381d.
2017-07-27 10:53:01 -07:00
fae47d86b3 refactor(forms): move value accessor tests into own spec (#18356)
PR Close #18356
2017-07-26 17:55:37 -05:00
d20ac14fe2 refactor(compiler-cli): allow custom error checking function in ngc (#18355)
PR Close #18355
2017-07-26 17:55:31 -05:00
cae3e6dca0 ci: give ownership of ngc-wrapped to compiler-cli maintainers (#18354)
PR Close #18354
2017-07-26 17:55:24 -05:00
086f4aa72c fix(router): child CanActivate guard should wait for parent to complete (#18110)
Closes #15670

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

PR Close #15001
2017-07-26 17:11:07 -05:00
5152abb037 docs(aio): add my details as a contributor (#18315)
PR Close #18315
2017-07-26 17:11:07 -05:00
67f7032321 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:07:26 -05:00
205abe8140 build: fix broken bazel build (#18335) 2017-07-26 09:40:33 -07:00
b582e2b311 docs(aio): update examples to 4.3 2017-07-25 15:32:38 -07:00
91ab39cc55 docs(aio) - Fixed link to the glossary dash-case term (#18311)
PR Close #18311
2017-07-25 15:59:28 -05:00
38ec05f533 fix(compiler): add equiv & disp attributes to Xliff2 ICU placeholders (#18283)
Fixes #17344

PR Close #18283
2017-07-25 15:58:53 -05:00
b3085e96c2 feat(compiler): add representation of placeholders to xliff & xmb
Closes #17345
2017-07-25 15:58:53 -05:00
4cea2bd612 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-25 15:58:13 -05:00
ce47546188 refactor(compiler-cli): add support for browser compiler bundle (#17979)
PR Close #17979
2017-07-25 15:51:46 -05:00
6279e50d78 perf(core): use native addEventListener for faster rendering. (#18107)
Angular can make many assumptions about its event handlers. As a result
the bookkeeping for native addEventListener is significantly cheaper
than Zone's addEventLister which can't make such assumptions.

This change bypasses the Zone's addEventListener if present and always
uses the native addEventHandler. As a result registering event listeners
is about 3 times faster.

PR Close #18107
2017-07-25 15:35:44 -05:00
8bcb268140 ci: use chrome stable (#18307) 2017-07-25 11:18:24 -07:00
6fc5940959 build: Bazel builds ngfactories for packages/core (#18289)
PR Close #18289
2017-07-21 18:09:47 -05:00
0317c4c478 build: update bazel rules to latest (#18289) 2017-07-21 18:09:44 -05:00
b7a6f52d59 perf: latest tsickle to tree shake: abstract class methods & interfaces (#18236)
In previous version of tsickle abstract class methods were materialized.
The change resulted in 6Kb savings in angular.io bundle.

This change also required the removal of `@private` and `@return` type
annotation as it is explicitly dissalowed by tsickle.

NOTE: removed casts in front of `makeDecorator` due to:
https://github.com/angular/devkit/issues/45

```
 14938 Jul 19 13:16 0.b19e913fbdd6507d346b.chunk.js
  1535 Jul 19 13:16 inline.d8e019ea3cfdd86c2bd0.bundle.js
589178 Jul 19 13:16 main.54c97bcb6f254776b678.bundle.js
 34333 Jul 19 13:16 polyfills.4a3c9ca9481d53803157.bundle.js

 14938 Jul 18 16:55 0.b19e913fbdd6507d346b.chunk.js
  1535 Jul 18 16:55 inline.0c83abb44fad9a2768a7.bundle.js
582786 Jul 18 16:55 main.ea290db71b051813e156.bundle.js
 34333 Jul 18 16:55 polyfills.4a3c9ca9481d53803157.bundle.js

main savings: 589178 - 582786 = 6,392
```

PR Close #18236
2017-07-21 16:35:37 -05:00
7ae7573bc8 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-21 16:35:23 -05:00
abee785821 refactor(tsc-wrapped): update tsc-wrapped to pass strictNullCheck (#18160)
PR Close #18160
2017-07-21 12:26:20 -05:00
619e625ee2 refactor(tsc-wrapped): move tsc-wrapped to the packages directory (#18160) 2017-07-21 12:26:16 -05:00
a6c635e69e ci: force precise on Travis (#18282)
PR Close #18282
2017-07-21 12:20:27 -05:00
e0a9625e46 test: fix bad merge (#18267)
PR Close #18267
2017-07-21 11:53:34 -05:00
fd0cc01eed fix(animations): export BrowserModule as apart of BrowserAnimationsModule (#18263)
PR Close #18263
2017-07-20 17:47:49 -05:00
1bfc77bf8c docs(aio): pngcrush all pngs (#18243)
PR Close #18243
2017-07-20 17:47:06 -05:00
a094769bca 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-20 17:46:37 -05:00
b4c98305da refactor(common): CleanUp HttpClient's imports (#18120)
PR Close #18120
2017-07-20 17:43:23 -05:00
a3a54299af fix(compiler): allow numbers for ICU message cases in lexer (#18095)
Closes #18095
Fixes #17799
2017-07-20 17:41:54 -05:00
15a3e2d307 ci(aio): fix aio payload script 2017-07-20 15:32:32 -07:00
54e0244954 Revert "docs: Remove unneeded file (#18106)"
This reverts commit 72fe45db2b.
2017-07-20 16:46:47 -05:00
43c33d5663 fix(upgrade): ensure downgraded components are created in the Angular zone (#18209)
PR Close #18209
2017-07-20 16:25:11 -05:00
6d7799fce9 test(upgrade): fail tests when there are AngularJS errors (#18209) 2017-07-20 16:25:07 -05:00
d31dc7b2b3 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-20 16:24:45 -05:00
4cd4f7a208 aio: debounce search and delay index building (#18134)
* feat(aio): debounce search requests

* feat(aio): delay loading search worker and index
2017-07-20 09:51:40 -07:00
72fe45db2b docs: Remove unneeded file (#18106) 2017-07-20 09:50:17 -07:00
8d2819121b fix(aio): invalid formatting in architecure.md (#18159)
Introduced in e110a80caf (diff-9ac9c6a9277eea9856d75249a7c0a40aL127)
2017-07-20 09:48:02 -07:00
073e8ba2f2 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-20 09:45:14 -07:00
5d1864fe68 docs: fixing invisible tag in README (#18245) 2017-07-20 09:43:42 -07:00
eaa843b55f ci: test bazel builds on travis (#18240) 2017-07-20 09:40:40 -07:00
c6cf678a07 docs(aio): Fixed typo with closing div 2017-07-20 09:38:28 -07:00
31cb418370 docs(aio): Fix http guide 2017-07-20 09:35:27 -07:00
8de44cf5e3 docs: fix wrong link in CONTRIBUTING.md (#18228)
The link to the CONTRIBUTING.md file was not correct
2017-07-20 09:33:15 -07:00
c67bad4f43 docs(router): minor typo (#18226)
Fix a minor typo in the description of a router spec.
2017-07-20 09:32:34 -07:00
410f21c75c 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-20 09:07:30 -07:00
54ea5b6ffd docs: add changelog for 5.0.0-beta.0 2017-07-19 13:16:43 -07:00
0af03beaed release: cut the 5.0.0-beta.0 release 2017-07-19 13:12:50 -07:00
d71fa734f5 docs: add changelog for 4.3.1 2017-07-19 12:55:08 -07:00
6f45519d6f feat(animations): support :increment and :decrement transition aliases 2017-07-19 11:24:00 -07:00
65c9e13105 fix(compiler-cli): don't generate empty <target/> when extracting xliff
Fixes #15754
2017-07-19 09:45:52 -07:00
9208f0beea docs(aio): fix typo in Router documentation
Fix title and link to RouteConfigLoadEnd documentation
2017-07-19 15:01:50 +01:00
5344be5182 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-18 16:37:04 -07:00
5db6f38b73 fix(animations): do not crash animations if a nested component fires CD during CD
Closes #18193
2017-07-18 15:22:43 -07:00
d22f8f54db fix(animations): always camelcase style property names that contain auto styles
Closes #17938
2017-07-18 15:22:30 -07:00
23146c9201 fix(animations): capture cancelled animation styles within grouped animations
Closes #17170
2017-07-18 15:22:10 -07:00
a5205c686e fix(upgrade): allow accessing AngularJS injector from downgraded module 2017-07-18 14:00:19 -07:00
807648251f fix(platform-server): provide XhrFactory for HttpClient 2017-07-18 13:59:26 -07:00
5c62e300e1 fix(common): send flushed body as error instead of null
fix #18181
2017-07-18 10:57:51 -07:00
256bc8acdd docs(http): Make name of injected HttpTestingController consistent 2017-07-18 10:35:56 -07:00
59c23c7bd7 feat(upgrade): propagate touched state of NgModelController 2017-07-18 10:35:35 -07:00
e03adb9edd docs(platform-server): add doc string for PlatformOptions 2017-07-18 10:34:57 -07:00
b399cb26d9 fix(router): terminal route in custom matcher 2017-07-18 10:25:18 -07:00
3b588fe2b0 docs: fix typo 2017-07-18 10:11:55 -07:00
95635c18c7 fix(compiler): ensure jit external id arguments names are unique
Fixes: #17558, #17378, #8676
2017-07-18 10:11:32 -07:00
e20cfe1bbc fix(router): canDeactivate guards should run from bottom to top
Closes #15657.
2017-07-18 10:04:39 -07:00
eb6fb5f87e fix(router): should navigate to the same url when config changes
Closes #15535
2017-07-18 10:04:11 -07:00
ad3029e786 fix(router): should run resolvers for the same route concurrently
Fixes #14279
2017-07-18 10:03:33 -07:00
2a2fe11e8d docs(aio): fix HttpClient setting new header sample 2017-07-18 10:02:27 -07:00
7d0f2cd51e 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:28 +01:00
36faba1aab fix(aio): add quote to module 2017-07-18 17:48:04 +01:00
92179bcc64 fix(aio): do not wrap <code-tabs> tab labels
Fixes #17751
2017-07-18 17:43:59 +01:00
cdb069ab0e 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:32:43 +01:00
c453b7bcfa build(aio): fail doc-gen if referenced images are missing 2017-07-18 11:45:05 +01:00
9d97163c64 docs(aio): fix broken image sources 2017-07-18 11:45:05 +01:00
f054c8360b docs(aio): fix up broken links 2017-07-18 11:45:05 +01:00
758848961e build(aio): abort doc-gen on dangling links 2017-07-18 11:45:05 +01:00
99b666614d build(aio): abort doc-gen if an example is missing
Closes #16936
2017-07-18 11:45:05 +01:00
3f331b53b2 docs(aio): update jelbourn photo 2017-07-17 14:51:11 -07:00
375d598a9f docs(aio): add George's fixes 2017-07-17 14:49:19 -07:00
cd67fced1c docs(aio): add npm install to Sublime instructions 2017-07-17 14:49:19 -07:00
a77cf7ee37 docs(aio): add Chuck's comments 2017-07-17 14:49:19 -07:00
2150b45954 docs(aio): add language service doc 2017-07-17 14:49:19 -07:00
9f99f4fae2 docs: fix HttpClient sample 2017-07-17 14:03:32 -07:00
c6ad212a98 docs(aio): fix HttpClient's interceptor sample 2017-07-17 14:03:16 -07:00
47b3ecd9a3 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:01:44 -07:00
8c81c62d46 docs: fix HttpClient logging's sample 2017-07-17 14:01:24 -07:00
7e72317059 docs(http): fixed syntax error in AuthInterceptor example 2017-07-17 14:00:59 -07:00
0bb8423df9 ci: add GK and PBD to aio content and marketing groups 2017-07-17 10:52:34 -07:00
95698d93ad fix(aio): remove title from callout 2017-07-15 15:35:15 +01:00
c649da9f0a 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:20 +01:00
0bf0c35bca build(aio): render type parameters of API function exports
Fixes #18123
2017-07-15 08:52:35 +01:00
97e6901ded 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:54:31 -07:00
30e76fcd80 feat(upgrade): support lazy-loading Angular module into AngularJS app 2017-07-14 14:10:30 -07:00
44b50427d9 refactor(upgrade): clean up some types 2017-07-14 14:10:30 -07:00
471 changed files with 11260 additions and 9181 deletions

View File

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

View File

@ -69,8 +69,8 @@ groups:
- "*.lock"
- "tools/*"
exclude:
- "tools/@angular/tsc-wrapped/*"
- "tools/public_api_guard/*"
- "tools/ngc-wrapped/*"
- "aio/*"
users:
- IgorMinar #primary
@ -136,8 +136,9 @@ groups:
compiler-cli:
conditions:
files:
- "tools/@angular/tsc-wrapped/*"
- "packages/tsc-wrapped/*"
- "packages/compiler-cli/*"
- "tools/ngc-wrapped/*"
users:
- alexeagle
- chuckjaz

View File

@ -53,7 +53,7 @@ env:
- CI_MODE=browserstack_required
- CI_MODE=saucelabs_optional
- CI_MODE=browserstack_optional
- CI_MODE=aio_tools_test
- CI_MODE=docs_test
- CI_MODE=aio
- CI_MODE=aio_e2e AIO_SHARD=0
- CI_MODE=aio_e2e AIO_SHARD=1

View File

@ -1,110 +1,59 @@
<a name="4.4.0"></a>
# [4.4.0](https://github.com/angular/angular/compare/4.4.0-RC.0...4.4.0) (2017-09-15)
<a name="5.0.0-beta.2"></a>
# [5.0.0-beta.2](https://github.com/angular/angular/compare/5.0.0-beta.1...5.0.0-beta.2) (2017-08-02)
### Bug Fixes
* **tsc-wrapped:** deduplicate metadata for re-exported modules ([48ae1a6](https://github.com/angular/angular/commit/48ae1a6))
* **tsc-wrapped:** fix metadata symbol reference ([f6a7183](https://github.com/angular/angular/commit/f6a7183))
* **upgrade:** remove code setting id attribute. ([#19182](https://github.com/angular/angular/issues/19182)) ([b20c5d2](https://github.com/angular/angular/commit/b20c5d2)), closes [#18446](https://github.com/angular/angular/issues/18446)
<a name="4.4.0-RC.0"></a>
# [4.4.0-RC.0](https://github.com/angular/angular/compare/4.3.6...4.4.0-RC.0) (2017-09-02)
### Bug Fixes
* **animations:** do not leak DOM nodes/styling for host triggered animations ([#18853](https://github.com/angular/angular/issues/18853)) ([1cc3fe2](https://github.com/angular/angular/commit/1cc3fe2)), closes [#18606](https://github.com/angular/angular/issues/18606)
* **common:** fix improper packaging for [@angular](https://github.com/angular)/common/http ([#18613](https://github.com/angular/angular/issues/18613)) ([a203a95](https://github.com/angular/angular/commit/a203a95))
* **common:** fix XSSI prefix stripping by using JSON.parse always ([#18466](https://github.com/angular/angular/issues/18466)) ([8821723](https://github.com/angular/angular/commit/8821723)), closes [#18396](https://github.com/angular/angular/issues/18396) [#18453](https://github.com/angular/angular/issues/18453)
* **compiler:** normalize the locale name ([#18963](https://github.com/angular/angular/issues/18963)) ([497e017](https://github.com/angular/angular/commit/497e017))
* **core:** complete EventEmitter in QueryList on component destroy ([#18902](https://github.com/angular/angular/issues/18902)) ([7d137d7](https://github.com/angular/angular/commit/7d137d7)), closes [#18741](https://github.com/angular/angular/issues/18741)
* **compiler:** do not consider arguments when determining recursion ([e64b54b](https://github.com/angular/angular/commit/e64b54b))
* **compiler:** fix for element needing implicit parent placed in top-level ng-container ([381471d](https://github.com/angular/angular/commit/381471d)), closes [#18314](https://github.com/angular/angular/issues/18314)
### Features
* **compiler:** allow multiple exportAs names ([#18723](https://github.com/angular/angular/issues/18723)) ([7ec28fe](https://github.com/angular/angular/commit/7ec28fe))
* **core:** add option to remove blank text nodes from compiled templates ([#18823](https://github.com/angular/angular/issues/18823)) ([b8b551c](https://github.com/angular/angular/commit/b8b551c))
* **forms:** add options arg to abstract controls ([ebef5e6](https://github.com/angular/angular/commit/ebef5e6))
* **router:** add events tracking activation of individual routes ([49cd851](https://github.com/angular/angular/commit/49cd851))
<a name="4.3.6"></a>
## [4.3.6](https://github.com/angular/angular/compare/4.3.5...4.3.6) (2017-08-23)
### Bug Fixes
* **animations:** ensure animations are disabled on the element containing the @.disabled flag ([#18714](https://github.com/angular/angular/issues/18714)) ([5d68c83](https://github.com/angular/angular/commit/5d68c83))
* **animations:** make sure @.disabled respects disabled parent/sub animation sequences ([#18715](https://github.com/angular/angular/issues/18715)) ([c3dcbf9](https://github.com/angular/angular/commit/c3dcbf9))
* **animations:** make sure animation cancellations respect AUTO style values ([#18787](https://github.com/angular/angular/issues/18787)) ([9a754f9](https://github.com/angular/angular/commit/9a754f9)), closes [#17450](https://github.com/angular/angular/issues/17450)
* **animations:** resolve error when using AnimationBuilder with platform-server ([#18642](https://github.com/angular/angular/issues/18642)) ([f9b2905](https://github.com/angular/angular/commit/f9b2905)), closes [#18635](https://github.com/angular/angular/issues/18635)
* **animations:** restore auto-style support for removed DOM nodes ([#18787](https://github.com/angular/angular/issues/18787)) ([e1f45a3](https://github.com/angular/angular/commit/e1f45a3))
* **core:** correct order in ContentChildren query result ([#18326](https://github.com/angular/angular/issues/18326)) ([fec3b1a](https://github.com/angular/angular/commit/fec3b1a)), closes [#16568](https://github.com/angular/angular/issues/16568)
* **core:** make sure onStable runs in the right zone ([#18706](https://github.com/angular/angular/issues/18706)) ([ee5591d](https://github.com/angular/angular/commit/ee5591d))
### Features
* **animations:** allow @.disabled property to work without an expression ([#18713](https://github.com/angular/angular/issues/18713)) ([ac58914](https://github.com/angular/angular/commit/ac58914))
* **common:** add an empty DeprecatedI18NPipesModule module ([793f31b](https://github.com/angular/angular/commit/793f31b))
<a name="5.0.0-beta.4"></a>
# [5.0.0-beta.4](https://github.com/angular/angular/compare/5.0.0-beta.3...5.0.0-beta.4) (2017-08-16)
### Bug Fixes
* **compiler:** Don't strip CSS source maps ([64b4be9](https://github.com/angular/angular/commit/64b4be9))
* **forms:** re-assigning options should not clear select ([32ff21c](https://github.com/angular/angular/commit/32ff21c)), closes [#18330](https://github.com/angular/angular/issues/18330)
* **language-service:** remove tsickle dependency ([bc22ff1](https://github.com/angular/angular/commit/bc22ff1))
### Features
* **common:** mark NgTemplateOutlet API as stable ([0a73e8d](https://github.com/angular/angular/commit/0a73e8d))
* **forms:** add status to `AbstractControlDirective` ([233ef93](https://github.com/angular/angular/commit/233ef93))
* **forms:** add updateOn support to ngModelOptions ([1cfa79c](https://github.com/angular/angular/commit/1cfa79c))
### Performance Improvements
* **core:** add option to remove blank text nodes from compiled templates ([d2c0d98](https://github.com/angular/angular/commit/d2c0d98))
* **core:** Remove decorator DSL which depends on Reflect ([cac130e](https://github.com/angular/angular/commit/cac130e))
<a name="4.3.5"></a>
## [4.3.5](https://github.com/angular/angular/compare/4.3.4...4.3.5) (2017-08-16)
### Bug Fixes
* **core:** forbid destroyed views to be inserted or moved in VC ([972538b](https://github.com/angular/angular/commit/972538b)), closes [#18615](https://github.com/angular/angular/issues/18615)
* **forms:** re-assigning options should not clear select ([a1624f2](https://github.com/angular/angular/commit/a1624f2)), closes [#18330](https://github.com/angular/angular/issues/18330)
<a name="4.3.4"></a>
## [4.3.4](https://github.com/angular/angular/compare/4.3.3...4.3.4) (2017-08-10)
### Bug Fixes
* **animations:** revert container/queried animations accordingly during cancel ([#18516](https://github.com/angular/angular/issues/18516)) ([5a165eb](https://github.com/angular/angular/commit/5a165eb))
* **animations:** support persisting dynamic styles within animation states ([#18468](https://github.com/angular/angular/issues/18468)) ([e0660b1](https://github.com/angular/angular/commit/e0660b1)), closes [#18423](https://github.com/angular/angular/issues/18423) [#17505](https://github.com/angular/angular/issues/17505)
* **benchpress:** compile cleanly with TS 2.4 ([#18455](https://github.com/angular/angular/issues/18455)) ([5afc7ab](https://github.com/angular/angular/commit/5afc7ab))
* **compiler:** cleanly compile with TypeScript 2.4 ([#18456](https://github.com/angular/angular/issues/18456)) ([5e4054b](https://github.com/angular/angular/commit/5e4054b))
* **compiler:** ignore [@import](https://github.com/import) in multi-line css ([#18452](https://github.com/angular/angular/issues/18452)) ([e7e7622](https://github.com/angular/angular/commit/e7e7622)), closes [#18038](https://github.com/angular/angular/issues/18038)
<a name="4.3.3"></a>
## [4.3.3](https://github.com/angular/angular/compare/4.3.2...4.3.3) (2017-08-02)
### Bug Fixes
* **compiler:** fix for element needing implicit parent placed in top-level ng-container ([f5cbc2e](https://github.com/angular/angular/commit/f5cbc2e)), closes [#18314](https://github.com/angular/angular/issues/18314)
<a name="5.0.0-beta.1"></a>
# [5.0.0-beta.1](https://github.com/angular/angular/compare/5.0.0-beta.0...5.0.0-beta.1) (2017-07-27)
### Bug Fixes
* **animations:** export BrowserModule as apart of BrowserAnimationsModule ([#18263](https://github.com/angular/angular/issues/18263)) ([fd0cc01](https://github.com/angular/angular/commit/fd0cc01))
* **compiler:** add equiv & disp attributes to Xliff2 ICU placeholders ([#18283](https://github.com/angular/angular/issues/18283)) ([38ec05f](https://github.com/angular/angular/commit/38ec05f)), 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)) ([a3a5429](https://github.com/angular/angular/commit/a3a5429)), 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)) ([7ae7573](https://github.com/angular/angular/commit/7ae7573)), 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)) ([a094769](https://github.com/angular/angular/commit/a094769)), 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)) ([086f4aa](https://github.com/angular/angular/commit/086f4aa)), 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)) ([82923a3](https://github.com/angular/angular/commit/82923a3)), closes [#14596](https://github.com/angular/angular/issues/14596)
* **upgrade:** ensure downgraded components are created in the Angular zone ([#18209](https://github.com/angular/angular/issues/18209)) ([43c33d5](https://github.com/angular/angular/commit/43c33d5))
* **upgrade:** throw error if trying to get injector before setting ([#18209](https://github.com/angular/angular/issues/18209)) ([d31dc7b](https://github.com/angular/angular/commit/d31dc7b))
### Features
* **compiler:** add representation of placeholders to xliff & xmb ([b3085e9](https://github.com/angular/angular/commit/b3085e9)), closes [#17345](https://github.com/angular/angular/issues/17345)
### Performance Improvements
* latest tsickle to tree shake: abstract class methods & interfaces ([#18236](https://github.com/angular/angular/issues/18236)) ([b7a6f52](https://github.com/angular/angular/commit/b7a6f52))
* **core:** use native addEventListener for faster rendering. ([#18107](https://github.com/angular/angular/issues/18107)) ([6279e50](https://github.com/angular/angular/commit/6279e50))
<a name="4.3.2"></a>
## [4.3.2](https://github.com/angular/angular/compare/4.3.1...4.3.2) (2017-07-26)
@ -122,6 +71,35 @@
<a name="5.0.0-beta.0"></a>
# [5.0.0-beta.0](https://github.com/angular/angular/compare/4.3.0...5.0.0-beta.0) (2017-07-19)
### Bug Fixes
* **animations:** always camelcase style property names that contain auto styles ([d22f8f5](https://github.com/angular/angular/commit/d22f8f5)), closes [#17938](https://github.com/angular/angular/issues/17938)
* **animations:** capture cancelled animation styles within grouped animations ([23146c9](https://github.com/angular/angular/commit/23146c9)), closes [#17170](https://github.com/angular/angular/issues/17170)
* **animations:** do not crash animations if a nested component fires CD during CD ([5db6f38](https://github.com/angular/angular/commit/5db6f38)), closes [#18193](https://github.com/angular/angular/issues/18193)
* **animations:** make sure @.disabled works in non-animation components ([5344be5](https://github.com/angular/angular/commit/5344be5))
* **common:** send flushed body as error instead of null ([5c62e30](https://github.com/angular/angular/commit/5c62e30)), closes [#18181](https://github.com/angular/angular/issues/18181)
* **compiler:** ensure jit external id arguments names are unique ([95635c1](https://github.com/angular/angular/commit/95635c1))
* **compiler-cli:** don't generate empty <target/> when extracting xliff ([65c9e13](https://github.com/angular/angular/commit/65c9e13)), closes [#15754](https://github.com/angular/angular/issues/15754)
* **platform-server:** provide XhrFactory for HttpClient ([8076482](https://github.com/angular/angular/commit/8076482))
* **router:** canDeactivate guards should run from bottom to top ([e20cfe1](https://github.com/angular/angular/commit/e20cfe1)), closes [#15657](https://github.com/angular/angular/issues/15657)
* **router:** should navigate to the same url when config changes ([eb6fb5f](https://github.com/angular/angular/commit/eb6fb5f)), closes [#15535](https://github.com/angular/angular/issues/15535)
* **router:** should run resolvers for the same route concurrently ([ad3029e](https://github.com/angular/angular/commit/ad3029e)), closes [#14279](https://github.com/angular/angular/issues/14279)
* **router:** terminal route in custom matcher ([b399cb2](https://github.com/angular/angular/commit/b399cb2))
* **upgrade:** allow accessing AngularJS injector from downgraded module ([a5205c6](https://github.com/angular/angular/commit/a5205c6))
### Features
* **animations:** support :increment and :decrement transition aliases ([6f45519](https://github.com/angular/angular/commit/6f45519))
* **upgrade:** propagate touched state of NgModelController ([59c23c7](https://github.com/angular/angular/commit/59c23c7))
* **upgrade:** support lazy-loading Angular module into AngularJS app ([30e76fc](https://github.com/angular/angular/commit/30e76fc))
<a name="4.3.1"></a>
## [4.3.1](https://github.com/angular/angular/compare/4.3.0...4.3.1) (2017-07-19)
@ -1193,6 +1171,7 @@ templates is unaffected. We expect no or little impact on apps from this change,
### Features
* **aio:** add initial angular-cli scaffold ([#14118](https://github.com/angular/angular/issues/14118)) ([e130bc1](https://github.com/angular/angular/commit/e130bc1))
* **common:** rename underlying `NgFor` class and add a type parameter ([#14104](https://github.com/angular/angular/issues/14104)) ([86b2b25](https://github.com/angular/angular/commit/86b2b25))
* **compiler:** allow missing translations ([#14113](https://github.com/angular/angular/issues/14113)) ([8775ab9](https://github.com/angular/angular/commit/8775ab9)), closes [#13861](https://github.com/angular/angular/issues/13861)
* **compiler:** do not parse xtb messages not needed by angular ([#14111](https://github.com/angular/angular/issues/14111)) ([f7fba74](https://github.com/angular/angular/commit/f7fba74)), closes [#14046](https://github.com/angular/angular/issues/14046)

View File

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

View File

@ -26,7 +26,7 @@ Here are the most important tasks you might need to use:
* `yarn docs-lint` - check that the doc gen code follows our style rules.
* `yarn docs-test` - run the unit tests for the doc generation code.
* `yarn boilerplate:add` - generate all the boilerplate code for the examples, so that they can be run locally. Add the option `-- --local` to use your local version of Angular contained in the "dist" folder.
* `yarn boilerplate:add` - generate all the boilerplate code for the examples, so that they can be run locally.
* `yarn boilerplate:remove` - remove all the boilerplate code that was added via `yarn boilerplate:add`.
* `yarn generate-plunkers` - generate the plunker files that are used by the `live-example` tags in the docs.
* `yarn generate-zips` - generate the zip files from the examples. Zip available via the `live-example` tags in the docs.
@ -34,7 +34,6 @@ Here are the most important tasks you might need to use:
* `yarn example-e2e` - run all e2e tests for examples
- `yarn example-e2e -- --setup` - force webdriver update & other setup, then run tests
- `yarn example-e2e -- --filter=foo` - limit e2e tests to those containing the word "foo"
- `yarn example-e2e -- --setup --local` - run e2e tests with the local version of Angular contained in the "dist" folder
* `yarn build-ie-polyfills` - generates a js file of polyfills that can be loaded in Internet Explorer.

View File

@ -32,8 +32,7 @@ export class BuildCreator extends EventEmitter {
then(() => Promise.all([this.exists(prDir), this.exists(shaDir)])).
then(([prDirExisted, shaDirExisted]) => {
if (shaDirExisted) {
const publicOrNot = isPublic ? 'public' : 'non-public';
throw new UploadError(409, `Request to overwrite existing ${publicOrNot} directory: ${shaDir}`);
throw new UploadError(409, `Request to overwrite existing directory: ${shaDir}`);
}
dirToRemoveOnError = prDirExisted ? shaDir : prDir;

View File

@ -110,7 +110,6 @@ describe('upload-server (on HTTP)', () => {
const authorizationHeader2 = isPublic ?
authorizationHeader : `--header "Authorization: ${c.BV_verify_verifiedNotTrusted}"`;
const cmdPrefix = curl('', `${authorizationHeader2} ${xFileHeader}`);
const overwriteRe = RegExp(`^Request to overwrite existing ${isPublic ? 'public' : 'non-public'} directory`);
it('should not overwrite existing builds', done => {
@ -121,7 +120,7 @@ describe('upload-server (on HTTP)', () => {
expect(h.readBuildFile(pr, sha9, 'index.html', isPublic)).toBe('My content');
h.runCmd(`${cmdPrefix} http://${host}/create-build/${pr}/${sha9}`).
then(h.verifyResponse(409, overwriteRe)).
then(h.verifyResponse(409, /^Request to overwrite existing directory/)).
then(() => expect(h.readBuildFile(pr, sha9, 'index.html', isPublic)).toBe('My content')).
then(done);
});
@ -142,7 +141,7 @@ describe('upload-server (on HTTP)', () => {
expect(h.readBuildFile(pr, sha9, 'index.html', isPublic)).toBe('My content');
h.runCmd(`${cmdPrefix} http://${host}/create-build/${pr}/${sha9Almost}`).
then(h.verifyResponse(409, overwriteRe)).
then(h.verifyResponse(409, /^Request to overwrite existing directory/)).
then(() => expect(h.readBuildFile(pr, sha9, 'index.html', isPublic)).toBe('My content')).
then(done);
});
@ -311,7 +310,7 @@ describe('upload-server (on HTTP)', () => {
expect(h.buildExists(pr, sha0, isPublic)).toBe(false);
uploadBuild(sha0).
then(h.verifyResponse(409, overwriteRe)).
then(h.verifyResponse(409, /^Request to overwrite existing directory/)).
then(() => {
checkPrVisibility(isPublic);
expect(h.readBuildFile(pr, sha0, 'index.html', isPublic)).toContain(pr);

View File

@ -153,8 +153,7 @@ describe('BuildCreator', () => {
it('should abort and skip further operations if the build does already exist', done => {
existsValues[shaDir] = true;
bc.create(pr, sha, archive, isPublic).catch(err => {
const publicOrNot = isPublic ? 'public' : 'non-public';
expectToBeUploadError(err, 409, `Request to overwrite existing ${publicOrNot} directory: ${shaDir}`);
expectToBeUploadError(err, 409, `Request to overwrite existing directory: ${shaDir}`);
expect(shellMkdirSpy).not.toHaveBeenCalled();
expect(bcExtractArchiveSpy).not.toHaveBeenCalled();
expect(bcEmitSpy).not.toHaveBeenCalled();
@ -170,8 +169,7 @@ describe('BuildCreator', () => {
expect(bcExistsSpy(shaDir)).toBe(false);
bc.create(pr, sha, archive, isPublic).catch(err => {
const publicOrNot = isPublic ? 'public' : 'non-public';
expectToBeUploadError(err, 409, `Request to overwrite existing ${publicOrNot} directory: ${shaDir}`);
expectToBeUploadError(err, 409, `Request to overwrite existing directory: ${shaDir}`);
expect(shellMkdirSpy).not.toHaveBeenCalled();
expect(bcExtractArchiveSpy).not.toHaveBeenCalled();
expect(bcEmitSpy).not.toHaveBeenCalled();

View File

@ -6,7 +6,7 @@
"interface-name": [true, "never-prefix"],
"max-classes-per-file": [true, 4],
"no-consecutive-blank-lines": [true, 2],
"no-console": [false],
"no-console": false,
"no-namespace": [true, "allow-declarations"],
"no-string-literal": false,
"quotemark": [true, "single"],

View File

@ -46,8 +46,8 @@ with a bried explanation of what they mean:
Request method other than POST.
- **409 (Conflict)**:
Request to overwrite existing (public or non-public) directory (e.g. deploy existing build or
change PR visibility when the destination directory does already exist).
Request to overwrite existing directory (e.g. deploy existing build or change PR visibility when
the destination directory does already exist).
- **413 (Payload Too Large)**:
Payload larger than size specified in `AIO_UPLOAD_MAX_SIZE`.
@ -71,8 +71,7 @@ with a bried explanation of what they mean:
Request method other than POST.
- **409 (Conflict)**:
Request to overwrite existing (public or non-public) directory (i.e. directories for both
visibilities exist).
Request to overwrite existing directory (i.e. directories for both visibilities exist).
(Normally, this should not happen.)

View File

@ -55,6 +55,10 @@ dist/
!testing/src/browser-test-shim.js
!testing/karma*.js
# TS to JS
!ts-to-js/js*/**/*.js
ts-to-js/js*/**/system*.js
# webpack
!webpack/**/config/*.js
!webpack/**/*webpack*.js

View File

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

View File

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

View File

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

View File

@ -1,38 +1,26 @@
<!-- #docregion -->
<div class="container">
<h1>Reactive Form</h1>
<form [formGroup]="heroForm" #formDir="ngForm">
<div [hidden]="formDir.submitted">
<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">
<label for="name">Name</label>
<!-- #docregion name-with-error-msg -->
<input id="name" class="form-control"
<label for="name">Name</label>
<input type="text" id="name" class="form-control"
formControlName="name" required >
<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 *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 id="alterEgo" class="form-control"
<input type="text" id="alterEgo" class="form-control"
formControlName="alterEgo" >
</div>
@ -43,20 +31,17 @@
<option *ngFor="let p of powers" [value]="p">{{p}}</option>
</select>
<div *ngIf="power.invalid && power.touched" class="alert alert-danger">
<div *ngIf="power.errors.required">Power is required.</div>
<div *ngIf="formErrors.power" class="alert alert-danger">
{{ formErrors.power }}
</div>
</div>
<button type="submit" class="btn btn-default"
[disabled]="heroForm.invalid">Submit</button>
[disabled]="!heroForm.valid">Submit</button>
<button type="button" class="btn btn-default"
(click)="formDir.resetForm({})">Reset</button>
</div>
</form>
<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>
(click)="addHero()">New Hero</button>
</form>
</div>
<hero-submitted [hero]="hero" [(submitted)]="submitted"></hero-submitted>
</div>

View File

@ -2,39 +2,115 @@
// #docplaster
// #docregion
import { Component, OnInit } from '@angular/core';
import { FormControl, FormGroup, Validators } from '@angular/forms';
import { FormGroup, FormBuilder, Validators } from '@angular/forms';
import { Hero } from '../shared/hero';
import { forbiddenNameValidator } from '../shared/forbidden-name.directive';
@Component({
selector: 'hero-form-reactive',
selector: 'hero-form-reactive3',
templateUrl: './hero-form-reactive.component.html'
})
export class HeroFormReactiveComponent implements OnInit {
powers = ['Really Smart', 'Super Flexible', 'Weather Changer'];
hero = {name: 'Dr.', alterEgo: 'Dr. What', power: this.powers[0]};
hero = new Hero(18, 'Dr. WhatIsHisName', this.powers[0], 'Dr. What');
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 {
// #docregion custom-validator
this.heroForm = new FormGroup({
'name': new FormControl(this.hero.name, [
Validators.required,
Validators.minLength(4),
forbiddenNameValidator(/bob/i) // <-- Here's how you pass in the custom validator.
]),
'alterEgo': new FormControl(this.hero.alterEgo),
'power': new FormControl(this.hero.power, Validators.required)
});
// #enddocregion custom-validator
this.buildForm();
}
get name() { return this.heroForm.get('name'); }
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]
});
get power() { return this.heroForm.get('power'); }
// #enddocregion form-group
this.heroForm.valueChanges
.subscribe(data => this.onValueChanged(data));
this.onValueChanged(); // (re)set validation messages now
}
// #enddocregion form-builder
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.'
}
};
}
// #enddocregion

View File

@ -0,0 +1,13 @@
// #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,8 +6,9 @@ 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 forbidden = nameRe.test(control.value);
return forbidden ? {'forbiddenName': {value: control.value}} : null;
const name = control.value;
const no = nameRe.test(name);
return no ? {'forbiddenName': {name}} : null;
};
}
// #enddocregion custom-validator
@ -19,12 +20,23 @@ export function forbiddenNameValidator(nameRe: RegExp): ValidatorFn {
providers: [{provide: NG_VALIDATORS, useExisting: ForbiddenValidatorDirective, multi: true}]
// #enddocregion directive-providers
})
export class ForbiddenValidatorDirective implements Validator {
export class ForbiddenValidatorDirective implements Validator, OnChanges {
@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.forbiddenName ? forbiddenNameValidator(new RegExp(this.forbiddenName, 'i'))(control)
: null;
return this.valFn(control);
}
}
// #enddocregion directive

View File

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

View File

@ -0,0 +1,14 @@
// #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

@ -0,0 +1,32 @@
// #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

@ -1,66 +0,0 @@
<!-- #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

@ -1,16 +0,0 @@
/* 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

@ -0,0 +1,14 @@
// #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

@ -0,0 +1,61 @@
<!-- #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

@ -0,0 +1,47 @@
/* 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

@ -0,0 +1,52 @@
<!-- #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

@ -0,0 +1,99 @@
/* 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,4 +1,3 @@
.ng-valid[required], .ng-valid.required {
border-left: 5px solid #42A948; /* green */
}

View File

@ -12,13 +12,13 @@ describe('Router', () => {
beforeAll(() => browser.get(''));
function getPageStruct() {
const hrefEles = element.all(by.css('my-app > nav a'));
const hrefEles = element.all(by.css('my-app a'));
const crisisDetail = element.all(by.css('my-app > ng-component > ng-component > ng-component > div')).first();
const heroDetail = element(by.css('my-app > ng-component > div'));
return {
hrefs: hrefEles,
activeHref: element(by.css('my-app > nav a.active')),
activeHref: element(by.css('my-app a.active')),
crisisHref: hrefEles.get(0),
crisisList: element.all(by.css('my-app > ng-component > ng-component li')),

View File

@ -15,10 +15,6 @@
height: 1.6em;
border-radius: 4px;
}
.items li a {
display: block;
text-decoration: none;
}
.items li:hover {
color: #607D8B;
background-color: #DDD;

View File

@ -28,7 +28,7 @@ const appRoutes: Routes = [
data: { preload: true }
},
// #enddocregion preload-v2
{ path: '', redirectTo: '/superheroes', pathMatch: 'full' },
{ path: '', redirectTo: '/heroes', pathMatch: 'full' },
{ path: '**', component: PageNotFoundComponent }
];

View File

@ -1,23 +0,0 @@
// #docplaster
// #docregion
import { Component } from '@angular/core';
@Component({
selector: 'my-app',
// #docregion template
template: `
<h1 class="title">Angular Router</h1>
<nav>
<a routerLink="/crisis-center" routerLinkActive="active">Crisis Center</a>
<a routerLink="/heroes" routerLinkActive="active">Heroes</a>
<a routerLink="/admin" routerLinkActive="active">Admin</a>
<a routerLink="/login" routerLinkActive="active">Login</a>
<a [routerLink]="[{ outlets: { popup: ['compose'] } }]">Contact</a>
</nav>
<router-outlet></router-outlet>
<router-outlet name="popup"></router-outlet>
`
// #enddocregion template
})
export class AppComponent {
}

View File

@ -9,7 +9,7 @@ import { Component } from '@angular/core';
<h1 class="title">Angular Router</h1>
<nav>
<a routerLink="/crisis-center" routerLinkActive="active">Crisis Center</a>
<a routerLink="/superheroes" routerLinkActive="active">Heroes</a>
<a routerLink="/heroes" routerLinkActive="active">Heroes</a>
<a routerLink="/admin" routerLinkActive="active">Admin</a>
<a routerLink="/login" routerLinkActive="active">Login</a>
<a [routerLink]="[{ outlets: { popup: ['compose'] } }]">Contact</a>

View File

@ -1,6 +1,5 @@
// #docregion
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs/Observable';
import { CanDeactivate,
ActivatedRouteSnapshot,
RouterStateSnapshot } from '@angular/router';
@ -14,7 +13,7 @@ export class CanDeactivateGuard implements CanDeactivate<CrisisDetailComponent>
component: CrisisDetailComponent,
route: ActivatedRouteSnapshot,
state: RouterStateSnapshot
): Observable<boolean> | boolean {
): Promise<boolean> | boolean {
// Get the Crisis Center ID
console.log(route.paramMap.get('id'));
@ -26,7 +25,7 @@ export class CanDeactivateGuard implements CanDeactivate<CrisisDetailComponent>
return true;
}
// Otherwise ask the user with the dialog service and return its
// observable which resolves to true or false when the user decides
// promise which resolves to true or false when the user decides
return component.dialogService.confirm('Discard changes?');
}
}

View File

@ -1,21 +1,18 @@
// #docregion
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/take';
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs/Observable';
import { Router, Resolve, RouterStateSnapshot,
ActivatedRouteSnapshot } from '@angular/router';
import { Crisis, CrisisService } from './crisis.service';
import { Crisis, CrisisService } from './crisis.service';
@Injectable()
export class CrisisDetailResolver implements Resolve<Crisis> {
constructor(private cs: CrisisService, private router: Router) {}
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<Crisis> {
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Promise<Crisis> {
let id = route.paramMap.get('id');
return this.cs.getCrisis(id).take(1).map(crisis => {
return this.cs.getCrisis(id).then(crisis => {
if (crisis) {
return crisis;
} else { // id not found

View File

@ -1,9 +1,8 @@
// #docplaster
// #docregion
import 'rxjs/add/operator/switchMap';
import { Component, OnInit, HostBinding } from '@angular/core';
import { Component, OnInit, HostBinding } from '@angular/core';
import { ActivatedRoute, Router, ParamMap } from '@angular/router';
import { Observable } from 'rxjs/Observable';
import { slideInDownAnimation } from '../animations';
import { Crisis, CrisisService } from './crisis.service';
@ -46,8 +45,7 @@ export class CrisisDetailComponent implements OnInit {
// #docregion ngOnInit
ngOnInit() {
this.route.paramMap
.switchMap((params: ParamMap) =>
this.service.getCrisis(params.get('id')))
.switchMap((params: ParamMap) => this.service.getCrisis(params.get('id')))
.subscribe((crisis: Crisis) => {
if (crisis) {
this.editName = crisis.name;
@ -68,13 +66,13 @@ export class CrisisDetailComponent implements OnInit {
this.gotoCrises();
}
canDeactivate(): Observable<boolean> | boolean {
canDeactivate(): Promise<boolean> | boolean {
// Allow synchronous navigation (`true`) if no crisis or the crisis is unchanged
if (!this.crisis || this.crisis.name === this.editName) {
return true;
}
// Otherwise ask the user with the dialog service and return its
// observable which resolves to true or false when the user decides
// promise which resolves to true or false when the user decides
return this.dialogService.confirm('Discard changes?');
}

View File

@ -2,7 +2,6 @@
// #docregion
import { Component, OnInit, HostBinding } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { Observable } from 'rxjs/Observable';
import { slideInDownAnimation } from '../animations';
import { Crisis } from './crisis.service';
@ -63,13 +62,13 @@ export class CrisisDetailComponent implements OnInit {
// #enddocregion cancel-save
// #docregion canDeactivate
canDeactivate(): Observable<boolean> | boolean {
canDeactivate(): Promise<boolean> | boolean {
// Allow synchronous navigation (`true`) if no crisis or the crisis is unchanged
if (!this.crisis || this.crisis.name === this.editName) {
return true;
}
// Otherwise ask the user with the dialog service and return its
// observable which resolves to true or false when the user decides
// promise which resolves to true or false when the user decides
return this.dialogService.confirm('Discard changes?');
}
// #enddocregion canDeactivate

View File

@ -1,7 +1,7 @@
import 'rxjs/add/operator/do';
import 'rxjs/add/operator/switchMap';
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute, ParamMap } from '@angular/router';
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute, Router, ParamMap } from '@angular/router';
import { Crisis, CrisisService } from './crisis.service';
import { Observable } from 'rxjs/Observable';
@ -10,34 +10,35 @@ import { Observable } from 'rxjs/Observable';
// #docregion relative-navigation-router-link
template: `
<ul class="items">
<li *ngFor="let crisis of crises$ | async"
[class.selected]="crisis.id === selectedId">
<a [routerLink]="[crisis.id]">
<span class="badge">{{ crisis.id }}</span>{{ crisis.name }}
<li *ngFor="let crisis of crises | async">
<a [routerLink]="[crisis.id]"
[class.selected]="isSelected(crisis)">
<span class="badge">{{ crisis.id }}</span>
{{ crisis.name }}
</a>
</li>
</ul>
<router-outlet></router-outlet>
`
</ul>`
// #enddocregion relative-navigation-router-link
})
export class CrisisListComponent implements OnInit {
crises$: Observable<Crisis[]>;
crises: Observable<Crisis[]>;
selectedId: number;
constructor(
private service: CrisisService,
private route: ActivatedRoute
private route: ActivatedRoute,
private router: Router
) {}
ngOnInit() {
this.crises$ = this.route.paramMap
this.crises = this.route.paramMap
.switchMap((params: ParamMap) => {
this.selectedId = +params.get('id');
return this.service.getCrises();
});
}
isSelected(crisis: Crisis) {
return crisis.id === this.selectedId;
}
}

View File

@ -1,19 +1,20 @@
// #docregion
import 'rxjs/add/operator/switchMap';
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute, ParamMap } from '@angular/router';
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute, Router, ParamMap } from '@angular/router';
import { Observable } from 'rxjs/Observable';
import { Crisis, CrisisService } from './crisis.service';
import { Observable } from 'rxjs/Observable';
@Component({
template: `
<ul class="items">
<li *ngFor="let crisis of crises$ | async"
[class.selected]="crisis.id === selectedId">
<a [routerLink]="[crisis.id]">
<span class="badge">{{ crisis.id }}</span>{{ crisis.name }}
</a>
<li *ngFor="let crisis of crises | async"
(click)="onSelect(crisis)"
[class.selected]="isSelected(crisis)">
<span class="badge">{{ crisis.id }}</span>
{{ crisis.name }}
</li>
</ul>
@ -21,21 +22,35 @@ import { Observable } from 'rxjs/Observable';
`
})
export class CrisisListComponent implements OnInit {
crises$: Observable<Crisis[]>;
crises: Observable<Crisis[]>;
selectedId: number;
// #docregion ctor
constructor(
private service: CrisisService,
private route: ActivatedRoute
private route: ActivatedRoute,
private router: Router
) {}
// #enddocregion ctor
isSelected(crisis: Crisis) {
return crisis.id === this.selectedId;
}
ngOnInit() {
this.crises$ = this.route.paramMap
this.crises = this.route.paramMap
.switchMap((params: ParamMap) => {
this.selectedId = +params.get('id');
return this.service.getCrises();
});
}
// #docregion onSelect
onSelect(crisis: Crisis) {
this.selectedId = crisis.id;
// Navigate with relative link
this.router.navigate([crisis.id], { relativeTo: this.route });
}
// #enddocregion onSelect
}

View File

@ -1,9 +1,5 @@
// #docplaster
// #docregion , mock-crises
import 'rxjs/add/observable/of';
import 'rxjs/add/operator/map';
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
export class Crisis {
constructor(public id: number, public name: string) { }
}
@ -16,18 +12,20 @@ const CRISES = [
];
// #enddocregion mock-crises
let crisesPromise = Promise.resolve(CRISES);
import { Injectable } from '@angular/core';
@Injectable()
export class CrisisService {
static nextCrisisId = 100;
private crises$: BehaviorSubject<Crisis[]> = new BehaviorSubject<Crisis[]>(CRISES);
getCrises() { return this.crises$; }
static nextCrisisId = 100;
getCrises() { return crisesPromise; }
getCrisis(id: number | string) {
return this.getCrises()
.map(crises => crises.find(crisis => crisis.id === +id));
return crisesPromise
.then(crises => crises.find(crisis => crisis.id === +id));
}
// #enddocregion
@ -35,8 +33,7 @@ export class CrisisService {
name = name.trim();
if (name) {
let crisis = new Crisis(CrisisService.nextCrisisId++, name);
CRISES.push(crisis);
this.crises$.next(CRISES);
crisesPromise.then(crises => crises.push(crisis));
}
}
// #docregion

View File

@ -1,8 +1,5 @@
// #docregion
import 'rxjs/add/observable/of';
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs/Observable';
/**
* Async modal dialog service
* DialogService makes this app easier to test by faking this service.
@ -12,11 +9,11 @@ import { Observable } from 'rxjs/Observable';
export class DialogService {
/**
* Ask user to confirm an action. `message` explains the action and choices.
* Returns observable resolving to `true`=confirm or `false`=cancel
* Returns promise resolving to `true`=confirm or `false`=cancel
*/
confirm(message?: string): Observable<boolean> {
const confirmation = window.confirm(message || 'Is it OK?');
return Observable.of(confirmation);
confirm(message?: string) {
return new Promise<boolean>(resolve => {
return resolve(window.confirm(message || 'Is it OK?'));
});
};
}

View File

@ -4,7 +4,6 @@
import 'rxjs/add/operator/switchMap';
// #enddocregion rxjs-operator-import
import { Component, OnInit } from '@angular/core';
import { Observable } from 'rxjs/Observable';
// #docregion imports
import { Router, ActivatedRoute, ParamMap } from '@angular/router';
// #enddocregion imports
@ -14,7 +13,7 @@ import { Hero, HeroService } from './hero.service';
@Component({
template: `
<h2>HEROES</h2>
<div *ngIf="hero$ | async as hero">
<div *ngIf="hero">
<h3>"{{ hero.name }}"</h3>
<div>
<label>Id: </label>{{ hero.id }}</div>
@ -29,7 +28,7 @@ import { Hero, HeroService } from './hero.service';
`
})
export class HeroDetailComponent implements OnInit {
hero$: Observable<Hero>;
hero: Hero;
// #docregion ctor
constructor(
@ -41,9 +40,10 @@ export class HeroDetailComponent implements OnInit {
// #docregion ngOnInit
ngOnInit() {
this.hero$ = this.route.paramMap
this.route.paramMap
.switchMap((params: ParamMap) =>
this.service.getHero(params.get('id')));
this.service.getHero(params.get('id')))
.subscribe((hero: Hero) => this.hero = hero);
}
// #enddocregion ngOnInit

View File

@ -2,14 +2,13 @@
// #docregion
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { Observable } from 'rxjs/Observable';
import { Hero, HeroService } from './hero.service';
@Component({
template: `
<h2>HEROES</h2>
<div *ngIf="hero$ | async as hero">
<div *ngIf="hero">
<h3>"{{ hero.name }}"</h3>
<div>
<label>Id: </label>{{ hero.id }}</div>
@ -24,7 +23,7 @@ import { Hero, HeroService } from './hero.service';
`
})
export class HeroDetailComponent implements OnInit {
hero$: Observable<Hero>;
hero: Hero;
constructor(
private route: ActivatedRoute,
@ -36,7 +35,8 @@ export class HeroDetailComponent implements OnInit {
ngOnInit() {
let id = this.route.snapshot.paramMap.get('id');
this.hero$ = this.service.getHero(id);
this.service.getHero(id)
.then((hero: Hero) => this.hero = hero);
}
// #enddocregion snapshot

View File

@ -4,7 +4,6 @@
import 'rxjs/add/operator/switchMap';
// #enddocregion rxjs-operator-import
import { Component, OnInit, HostBinding } from '@angular/core';
import { Observable } from 'rxjs/Observable';
import { Router, ActivatedRoute, ParamMap } from '@angular/router';
import { slideInDownAnimation } from '../animations';
@ -14,7 +13,7 @@ import { Hero, HeroService } from './hero.service';
@Component({
template: `
<h2>HEROES</h2>
<div *ngIf="hero$ | async as hero">
<div *ngIf="hero">
<h3>"{{ hero.name }}"</h3>
<div>
<label>Id: </label>{{ hero.id }}</div>
@ -23,7 +22,7 @@ import { Hero, HeroService } from './hero.service';
<input [(ngModel)]="hero.name" placeholder="name"/>
</div>
<p>
<button (click)="gotoHeroes(hero)">Back</button>
<button (click)="gotoHeroes()">Back</button>
</p>
</div>
`,
@ -36,7 +35,7 @@ export class HeroDetailComponent implements OnInit {
@HostBinding('style.position') position = 'absolute';
// #enddocregion host-bindings
hero$: Observable<Hero>;
hero: Hero;
// #docregion ctor
constructor(
@ -48,15 +47,16 @@ export class HeroDetailComponent implements OnInit {
// #docregion ngOnInit
ngOnInit() {
this.hero$ = this.route.paramMap
this.route.paramMap
.switchMap((params: ParamMap) =>
this.service.getHero(params.get('id')));
this.service.getHero(params.get('id')))
.subscribe((hero: Hero) => this.hero = hero);
}
// #enddocregion ngOnInit
// #docregion gotoHeroes
gotoHeroes(hero: Hero) {
let heroId = hero ? hero.id : null;
gotoHeroes() {
let heroId = this.hero ? this.hero.id : null;
// Pass along the hero id if available
// so that the HeroList component can select that hero.
// Include a junk 'foo' property for fun.

View File

@ -3,7 +3,6 @@
// TODO SOMEDAY: Feature Componetized like HeroCenter
import { Component, OnInit } from '@angular/core';
import { Router } from '@angular/router';
import { Observable } from 'rxjs/Observable';
import { Hero, HeroService } from './hero.service';
@ -12,12 +11,9 @@ import { Hero, HeroService } from './hero.service';
template: `
<h2>HEROES</h2>
<ul class="items">
<li *ngFor="let hero of heroes$ | async">
// #docregion nav-to-detail
<a [routerLink]="['/hero', hero.id]">
<span class="badge">{{ hero.id }}</span>{{ hero.name }}
</a>
// #enddocregion nav-to-detail
<li *ngFor="let hero of heroes | async"
(click)="onSelect(hero)">
<span class="badge">{{ hero.id }}</span> {{ hero.name }}
</li>
</ul>
@ -26,7 +22,7 @@ import { Hero, HeroService } from './hero.service';
// #enddocregion template
})
export class HeroListComponent implements OnInit {
heroes$: Observable<Hero[]>;
heroes: Promise<Hero[]>;
// #docregion ctor
constructor(
@ -36,8 +32,16 @@ export class HeroListComponent implements OnInit {
// #enddocregion ctor
ngOnInit() {
this.heroes$ = this.service.getHeroes();
this.heroes = this.service.getHeroes();
}
// #docregion select
onSelect(hero: Hero) {
// #docregion nav-to-detail
this.router.navigate(['/hero', hero.id]);
// #enddocregion nav-to-detail
}
// #enddocregion select
}
// #enddocregion

View File

@ -7,7 +7,7 @@ import { Observable } from 'rxjs/Observable';
// #enddocregion rxjs-imports
import { Component, OnInit } from '@angular/core';
// #docregion import-router
import { ActivatedRoute, ParamMap } from '@angular/router';
import { Router, ActivatedRoute, ParamMap } from '@angular/router';
// #enddocregion import-router
import { Hero, HeroService } from './hero.service';
@ -17,11 +17,10 @@ import { Hero, HeroService } from './hero.service';
template: `
<h2>HEROES</h2>
<ul class="items">
<li *ngFor="let hero of heroes$ | async"
[class.selected]="hero.id === selectedId">
<a [routerLink]="['/hero', hero.id]">
<span class="badge">{{ hero.id }}</span>{{ hero.name }}
</a>
<li *ngFor="let hero of heroes | async"
[class.selected]="isSelected(hero)"
(click)="onSelect(hero)">
<span class="badge">{{ hero.id }}</span> {{ hero.name }}
</li>
</ul>
@ -31,17 +30,18 @@ import { Hero, HeroService } from './hero.service';
})
// #docregion ctor
export class HeroListComponent implements OnInit {
heroes$: Observable<Hero[]>;
heroes: Observable<Hero[]>;
private selectedId: number;
constructor(
private service: HeroService,
private route: ActivatedRoute
private route: ActivatedRoute,
private router: Router
) {}
ngOnInit() {
this.heroes$ = this.route.paramMap
this.heroes = this.route.paramMap
.switchMap((params: ParamMap) => {
// (+) before `params.get()` turns the string into a number
this.selectedId = +params.get('id');
@ -49,6 +49,16 @@ export class HeroListComponent implements OnInit {
});
}
// #enddocregion ctor
// #docregion isSelected
isSelected(hero: Hero) { return hero.id === this.selectedId; }
// #enddocregion isSelected
// #docregion select
onSelect(hero: Hero) {
this.router.navigate(['/hero', hero.id]);
}
// #enddocregion select
// #docregion ctor
}
// #enddocregion

View File

@ -1,14 +1,11 @@
// #docregion
import 'rxjs/add/observable/of';
import 'rxjs/add/operator/map';
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs/Observable';
export class Hero {
constructor(public id: number, public name: string) { }
}
const HEROES = [
let HEROES = [
new Hero(11, 'Mr. Nice'),
new Hero(12, 'Narco'),
new Hero(13, 'Bombasto'),
@ -17,13 +14,15 @@ const HEROES = [
new Hero(16, 'RubberMan')
];
let heroesPromise = Promise.resolve(HEROES);
@Injectable()
export class HeroService {
getHeroes() { return Observable.of(HEROES); }
getHeroes() { return heroesPromise; }
getHero(id: number | string) {
return this.getHeroes()
return heroesPromise
// (+) before `id` turns the string into a number
.map(heroes => heroes.find(hero => hero.id === +id));
.then(heroes => heroes.find(hero => hero.id === +id));
}
}

View File

@ -1,24 +0,0 @@
// #docregion
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { HeroListComponent } from './hero-list.component';
import { HeroDetailComponent } from './hero-detail.component';
const heroesRoutes: Routes = [
{ path: 'heroes', component: HeroListComponent },
// #docregion hero-detail-route
{ path: 'hero/:id', component: HeroDetailComponent }
// #enddocregion hero-detail-route
];
@NgModule({
imports: [
RouterModule.forChild(heroesRoutes)
],
exports: [
RouterModule
]
})
export class HeroRoutingModule { }
// #enddocregion

View File

@ -6,10 +6,10 @@ import { HeroListComponent } from './hero-list.component';
import { HeroDetailComponent } from './hero-detail.component';
const heroesRoutes: Routes = [
{ path: 'heroes', redirectTo: '/superheroes' },
{ path: 'hero/:id', redirectTo: '/superhero/:id' },
{ path: 'superheroes', component: HeroListComponent },
{ path: 'superhero/:id', component: HeroDetailComponent }
{ path: 'heroes', component: HeroListComponent },
// #docregion hero-detail-route
{ path: 'hero/:id', component: HeroDetailComponent }
// #enddocregion hero-detail-route
];
@NgModule({

View File

@ -35,7 +35,7 @@ export class HeroDetailComponent {
@Input() prefix = '';
// #docregion deleteRequest
// This component makes a request but it can't actually delete a hero.
// This component make a request but it can't actually delete a hero.
deleteRequest = new EventEmitter<Hero>();
delete() {

View File

@ -44,7 +44,6 @@ System.config({
map: {
'@angular/core/testing': 'npm:@angular/core/bundles/core-testing.umd.js',
'@angular/common/testing': 'npm:@angular/common/bundles/common-testing.umd.js',
'@angular/common/http/testing': 'npm:@angular/common/bundles/common-http-testing.umd.js',
'@angular/compiler/testing': 'npm:@angular/compiler/bundles/compiler-testing.umd.js',
'@angular/platform-browser/testing': 'npm:@angular/platform-browser/bundles/platform-browser-testing.umd.js',
'@angular/platform-browser-dynamic/testing': 'npm:@angular/platform-browser-dynamic/bundles/platform-browser-dynamic-testing.umd.js',

View File

@ -52,10 +52,6 @@ module.exports = function(config) {
{ pattern: 'node_modules/rxjs/**/*.js', included: false, watched: false },
{ pattern: 'node_modules/rxjs/**/*.js.map', included: false, watched: false },
// tslib (TS helper fns such as `__extends`)
{ pattern: 'node_modules/tslib/**/*.js', included: false, watched: false },
{ pattern: 'node_modules/tslib/**/*.js.map', included: false, watched: false },
// Paths loaded via module imports:
// Angular itself
{ pattern: 'node_modules/@angular/**/*.js', included: false, watched: false },

View File

@ -0,0 +1,77 @@
'use strict'; // necessary for es6 output in node
import { browser, element, by } from 'protractor';
describe('TypeScript to Javascript tests', function () {
beforeAll(function () {
browser.get('');
});
it('should display the basic component example', function () {
testTag('hero-view', 'Hero Detail: Windstorm');
});
it('should display the component example with lifecycle methods', function () {
testTag('hero-lifecycle', 'Hero: Windstorm');
});
it('should display component with DI example', function () {
testTag('hero-di', 'Hero: Windstorm');
});
it('should display component with DI using @Inject example', function () {
testTag('hero-di-inject', 'Hero: Windstorm');
});
it('should support optional, attribute, and query injections', function () {
let app = element(by.css('hero-di-inject-additional'));
let h1 = app.element(by.css('h1'));
let okMsg = app.element(by.css('p'));
expect(h1.getText()).toBe('Tour of Heroes');
app.element(by.buttonText('OK')).click();
expect(okMsg.getText()).toBe('OK!');
});
it('should support component with inputs and outputs', function () {
let app = element(by.css('hero-io'));
let confirmComponent = app.element(by.css('app-confirm'));
confirmComponent.element(by.buttonText('OK')).click();
expect(app.element(by.cssContainingText('span', 'OK clicked')).isPresent()).toBe(true);
confirmComponent.element(by.buttonText('Cancel')).click();
expect(app.element(by.cssContainingText('span', 'Cancel clicked')).isPresent()).toBe(true);
});
it('should support host bindings and host listeners', function() {
let app = element(by.css('hero-host'));
let h1 = app.element(by.css('h1'));
expect(app.getAttribute('class')).toBe('heading');
expect(app.getAttribute('title')).toContain('Tooltip');
h1.click();
expect(h1.getAttribute('class')).toBe('active');
h1.click();
browser.actions().doubleClick(h1.getWebElement()).perform();
expect(h1.getAttribute('class')).toBe('active');
});
it('should support content and view queries', function() {
let app = element(by.css('hero-queries'));
let windstorm = app.element(by.css('view-child:first-child'));
app.element(by.css('button')).click();
expect(windstorm.element(by.css('h2')).getAttribute('class')).toBe('active');
expect(windstorm.element(by.css('content-child')).getText()).toBe('Active');
});
function testTag(selector: string, expectedText: string) {
let component = element(by.css(selector));
expect(component.getText()).toBe(expectedText);
}
});

View File

@ -0,0 +1,3 @@
{
"build": "build:babel"
}

View File

@ -0,0 +1,9 @@
{
"description": "TypeScript to JavaScript",
"basePath": "src/",
"files":[
"!**/*.d.ts",
"!**/*.js"
],
"tags":["cookbook"]
}

View File

@ -0,0 +1,6 @@
{
"presets": [
"es2015",
"angular2"
]
}

View File

@ -0,0 +1,14 @@
import { Component } from '@angular/core';
@Component({
selector: 'my-app',
templateUrl: './app.component.html',
styles: [
// See hero-di-inject-additional.component
'hero-host, hero-host-meta { border: 1px dashed black; display: block; padding: 4px;}',
'.heading {font-style: italic}'
]
})
export class AppComponent {
title = 'ES6 JavaScript with Decorators';
}

View File

@ -0,0 +1,31 @@
<a id="toc"></a>
<h1>{{title}}</h1>
<a href="#class-metadata">Classes and Class Metadata</a><br>
<a href="#io-metadata">Input and Output Decorators</a><br>
<a href="#dependency-injection">Dependency Injection</a><br>
<a href="#host-metadata">Host Metadata</a><br>
<a href="#view-child-metadata">View and Child Metadata</a><br>
<hr>
<h4 id="class-metadata">Classes and Class Metadata</h4>
<hero-view></hero-view>
<hero-lifecycle></hero-lifecycle>
<hr>
<h4 id="io-metadata">Input and Output Metadata</h4>
<hero-io></hero-io>
<hr>
<h4 id="dependency-injection">Dependency Injection</h4>
<hero-di></hero-di>
<hero-di-inject></hero-di-inject>
<hero-di-inject-additional></hero-di-inject-additional>
<hr>
<h4 id="host-metadata">Host Metadata</h4>
<hero-host></hero-host>
<hero-host-meta></hero-host-meta>
<hr>
<h4 id="view-child-metadata">View and Child Metadata</h4>
<hero-queries></hero-queries>

View File

@ -0,0 +1,55 @@
import { NgModule, NO_ERRORS_SCHEMA } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AppComponent } from './app.component';
import { ConfirmComponent } from './confirm.component';
// #docregion appimport
import { HeroComponent } from './hero.component';
// #enddocregion appimport
import { HeroComponent as HeroDIComponent } from './hero-di.component';
import { HeroComponent as HeroDIInjectComponent } from './hero-di-inject.component';
import { HeroComponent as HeroDIInjectAdditionalComponent } from './hero-di-inject-additional.component';
import { HeroHostComponent } from './hero-host.component';
import { HeroHostMetaComponent } from './hero-host-meta.component';
import { HeroIOComponent } from './hero-io.component';
import { HeroComponent as HeroLifecycleComponent } from './hero-lifecycle.component';
import { HeroQueriesComponent, ViewChildComponent, ContentChildComponent } from './hero-queries.component';
import { HeroTitleComponent } from './hero-title.component';
import { DataService } from './data.service';
@NgModule({
imports: [
BrowserModule
],
declarations: [
AppComponent,
ConfirmComponent,
HeroComponent,
HeroDIComponent,
HeroDIInjectComponent,
HeroDIInjectAdditionalComponent,
HeroHostComponent, HeroHostMetaComponent,
HeroIOComponent,
HeroLifecycleComponent,
HeroQueriesComponent, ViewChildComponent, ContentChildComponent,
HeroTitleComponent
],
providers: [
DataService,
{ provide: 'heroName', useValue: 'Windstorm' }
],
bootstrap: [ AppComponent ],
// schemas: [ NO_ERRORS_SCHEMA ] // helpful for debugging
})
export class AppModule { }
/* tslint:disable no-unused-variable */
// #docregion ng2import
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import {
LocationStrategy,
HashLocationStrategy
} from '@angular/common';
// #enddocregion ng2import

View File

@ -0,0 +1,21 @@
import { Component, EventEmitter, Input, Output } from '@angular/core';
// #docregion
@Component({
selector: 'app-confirm',
templateUrl: './confirm.component.html'
})
export class ConfirmComponent {
@Input() okMsg = '';
@Input('cancelMsg') notOkMsg = '';
@Output() ok = new EventEmitter();
@Output('cancel') notOk = new EventEmitter();
onOkClick() {
this.ok.emit(true);
}
onNotOkClick() {
this.notOk.emit(true);
}
}
// #enddocregion

View File

@ -0,0 +1,6 @@
<button (click)="onOkClick()">
{{okMsg}}
</button>
<button (click)="onNotOkClick()">
{{notOkMsg}}
</button>

View File

@ -0,0 +1,10 @@
import { Injectable } from '@angular/core';
@Injectable()
export class DataService {
constructor() { }
getHeroName() {
return 'Windstorm';
}
}

View File

@ -0,0 +1,7 @@
import { Component } from '@angular/core';
@Component({
selector: 'hero-di-inject-additional',
template: `<hero-title title="Tour of Heroes"></hero-title>`
})
export class HeroComponent { }

View File

@ -0,0 +1,13 @@
import { Component, Inject } from '@angular/core';
// #docregion
@Component({
selector: 'hero-di-inject',
template: `<h1>Hero: {{name}}</h1>`
})
export class HeroComponent {
constructor(@Inject('heroName') name) {
this.name = name;
}
}
// #enddocregion

View File

@ -0,0 +1,15 @@
// #docregion
import { Component } from '@angular/core';
import { DataService } from './data.service';
@Component({
selector: 'hero-di',
template: `<h1>Hero: {{name}}</h1>`
})
export class HeroComponent {
name = '';
constructor(dataService: DataService) {
this.name = dataService.getHeroName();
}
}
// #enddocregion

View File

@ -0,0 +1,44 @@
import { Component } from '@angular/core';
// #docregion
@Component({
selector: 'hero-host-meta',
template: `
<h1 [class.active]="active">Hero Host in Metadata</h1>
<div>Heading clicks: {{clicks}}</div>
`,
host: {
// HostBindings to the <hero-host-meta> element
'[title]': 'title',
'[class.heading]': 'headingClass',
// HostListeners on the entire <hero-host-meta> element
'(click)': 'clicked()',
'(mouseenter)': 'enter($event)',
'(mouseleave)': 'leave($event)'
},
// Styles within (but excluding) the <hero-host-meta> element
styles: ['.active {background-color: coral;}']
})
export class HeroHostMetaComponent {
title = 'Hero Host in Metadata Tooltip';
headingClass = true;
active = false;
clicks = 0;
clicked() {
this.clicks += 1;
}
enter(event: Event) {
this.active = true;
this.headingClass = false;
}
leave(event: Event) {
this.active = false;
this.headingClass = true;
}
}
// #enddocregion

View File

@ -0,0 +1,39 @@
import { Component, HostBinding, HostListener } from '@angular/core';
// #docregion
@Component({
selector: 'hero-host',
template: `
<h1 [class.active]="active">Hero Host in Decorators</h1>
<div>Heading clicks: {{clicks}}</div>
`,
// Styles within (but excluding) the <hero-host> element
styles: ['.active {background-color: yellow;}']
})
export class HeroHostComponent {
// HostBindings to the <hero-host> element
@HostBinding() title = 'Hero Host in Decorators Tooltip';
@HostBinding('class.heading') headingClass = true;
active = false;
clicks = 0;
// HostListeners on the entire <hero-host> element
@HostListener('click')
clicked() {
this.clicks += 1;
}
@HostListener('mouseenter', ['$event'])
enter(event: Event) {
this.active = true;
this.headingClass = false;
}
@HostListener('mouseleave', ['$event'])
leave(event: Event) {
this.active = false;
this.headingClass = true;
}
}
// #enddocregion

View File

@ -0,0 +1,26 @@
import { Component } from '@angular/core';
@Component({
selector: 'hero-io',
template: `
<app-confirm [okMsg]="'OK'"
[cancelMsg]="'Cancel'"
(ok)="onOk()"
(cancel)="onCancel()">
</app-confirm>
<span *ngIf="okClicked">OK clicked</span>
<span *ngIf="cancelClicked">Cancel clicked</span>
`
})
export class HeroIOComponent {
okClicked = false;
cancelClicked = false;
onOk() {
this.okClicked = true;
}
onCancel() {
this.cancelClicked = true;
}
}

View File

@ -0,0 +1,14 @@
// #docregion
import { Component } from '@angular/core';
@Component({
selector: 'hero-lifecycle',
template: `<h1>Hero: {{name}}</h1>`
})
export class HeroComponent {
name = '';
ngOnInit() {
// todo: fetch from server async
setTimeout(() => this.name = 'Windstorm', 0);
}
}

View File

@ -0,0 +1,81 @@
import {
Component,
ContentChild,
Input,
QueryList,
ViewChildren
} from '@angular/core';
@Component({
selector: 'content-child',
template: `
<span class="content-child" *ngIf="active">
Active
</span>`
})
export class ContentChildComponent {
active = false;
activate() {
this.active = true;
}
}
////////////////////
// #docregion content
@Component({
selector: 'view-child',
template: `
<h2 [class.active]=active>
{{hero.name}}
<ng-content></ng-content>
</h2>`,
styles: ['.active {font-weight: bold; background-color: skyblue;}']
})
export class ViewChildComponent {
@Input() hero;
active = false;
@ContentChild(ContentChildComponent) content;
activate() {
this.active = !this.active;
this.content.activate();
}
}
// #enddocregion content
////////////////////
// #docregion view
@Component({
selector: 'hero-queries',
template: `
<view-child *ngFor="let hero of heroData" [hero]="hero">
<content-child></content-child>
</view-child>
<button (click)="activate()">{{buttonLabel}} All</button>
`
})
export class HeroQueriesComponent {
active = false;
heroData = [
{id: 1, name: 'Windstorm'},
{id: 2, name: 'LaughingGas'}
];
@ViewChildren(ViewChildComponent) views;
activate() {
this.active = !this.active;
this.views.forEach(
view => view.activate()
);
}
get buttonLabel() {
return this.active ? 'Deactivate' : 'Activate';
}
}
// #enddocregion view

View File

@ -0,0 +1,25 @@
import { Attribute, Component, Inject, Optional } from '@angular/core';
// #docregion
// #docregion templateUrl
@Component({
selector: 'hero-title',
templateUrl: './hero-title.component.html'
})
// #enddocregion templateUrl
export class HeroTitleComponent {
msg = '';
constructor(
@Inject('titlePrefix') @Optional() titlePrefix,
@Attribute('title') title
) {
this.titlePrefix = titlePrefix;
this.title = title;
}
ok() {
this.msg = 'OK!';
}
}
// #enddocregion

View File

@ -0,0 +1,4 @@
<!-- #docregion -->
<h1>{{titlePrefix}} {{title}}</h1>
<button (click)="ok()">OK</button>
<p>{{ msg }}</p>

View File

@ -0,0 +1,15 @@
// #docregion
// #docregion metadata
import { Component } from '@angular/core';
@Component({
selector: 'hero-view',
template: '<h1>{{title}}: {{getName()}}</h1>'
})
// #docregion appexport, class
export class HeroComponent {
title = 'Hero Detail';
getName() {return 'Windstorm'; }
}
// #enddocregion appexport, class
// #enddocregion metadata

View File

@ -0,0 +1,26 @@
<!DOCTYPE html>
<html>
<head>
<base href="/">
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="styles.css">
<title>TypeScript to JavaScript</title>
<!-- Polyfill(s) for older browsers -->
<script src="node_modules/core-js/client/shim.min.js"></script>
<script src="node_modules/zone.js/dist/zone.js"></script>
<script src="node_modules/systemjs/dist/system.src.js"></script>
<script src="systemjs.config.js"></script>
<script>
System.import('main.js').catch(function(err){ console.error(err); });
</script>
</head>
<body>
<my-app>Loading...</my-app>
</body>
</html>

View File

@ -0,0 +1,4 @@
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppModule } from './app/app.module';
platformBrowserDynamic().bootstrapModule(AppModule);

View File

@ -0,0 +1,3 @@
{
"build": "build:babel"
}

View File

@ -0,0 +1,9 @@
{
"description": "TypeScript to JavaScript",
"basePath": "src/",
"files":[
"!**/*.d.ts",
"!**/*.js"
],
"tags":["cookbook"]
}

View File

@ -0,0 +1,5 @@
{
"presets": [
"es2015"
]
}

View File

@ -0,0 +1,19 @@
import { Component } from '@angular/core';
export class AppComponent {
constructor() {
this.title = 'Plain ES6 JavaScript';
}
}
AppComponent.annotations = [
new Component({
selector: 'my-app',
templateUrl: './app.component.html',
styles: [
// See hero-di-inject-additional.component
'hero-host { border: 1px dashed black; display: block; padding: 4px;}',
'.heading {font-style: italic}'
]
})
];

View File

@ -0,0 +1,30 @@
<a id="toc"></a>
<h1>{{title}}</h1>
<a href="#class-metadata">Classes and Class Metadata</a><br>
<a href="#io-metadata">Input and Output Metadata</a><br>
<a href="#dependency-injection">Dependency Injection</a><br>
<a href="#host-metadata">Host Metadata</a><br>
<a href="#view-child-metadata">View and Child Metadata</a><br>
<hr>
<h4 id="class-metadata">Classes and Class Metadata</h4>
<hero-view></hero-view>
<hero-lifecycle></hero-lifecycle>
<hr>
<h4 id="io-metadata">Input and Output Metadata</h4>
<hero-io></hero-io>
<hr>
<h4 id="dependency-injection">Dependency Injection</h4>
<hero-di></hero-di>
<hero-di-inject></hero-di-inject>
<hero-di-inject-additional></hero-di-inject-additional>
<hr>
<h4 id="host-metadata">Host Metadata</h4>
<hero-host></hero-host>
<hr>
<h4 id="view-child-metadata">View and Child Metadata</h4>
<hero-queries></hero-queries>

View File

@ -0,0 +1,56 @@
import { NgModule, NO_ERRORS_SCHEMA } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AppComponent } from './app.component';
import { ConfirmComponent } from './confirm.component';
// #docregion appimport
import { HeroComponent } from './hero.component';
// #enddocregion appimport
import { HeroComponent as HeroDIComponent } from './hero-di.component';
import { HeroComponent as HeroDIInjectComponent } from './hero-di-inject.component';
import { HeroComponent as HeroDIInjectAdditionalComponent } from './hero-di-inject-additional.component';
import { HeroHostComponent } from './hero-host.component';
import { HeroIOComponent } from './hero-io.component';
import { HeroComponent as HeroLifecycleComponent } from './hero-lifecycle.component';
import { HeroQueriesComponent, ViewChildComponent, ContentChildComponent } from './hero-queries.component';
import { HeroTitleComponent } from './hero-title.component';
import { DataService } from './data.service';
export class AppModule { }
AppModule.annotations = [
new NgModule({
imports: [ BrowserModule],
declarations: [
AppComponent,
ConfirmComponent,
HeroComponent,
HeroDIComponent,
HeroDIInjectComponent,
HeroDIInjectAdditionalComponent,
HeroHostComponent,
HeroIOComponent,
HeroLifecycleComponent,
HeroQueriesComponent, ViewChildComponent, ContentChildComponent,
HeroTitleComponent
],
providers: [
DataService,
{ provide: 'heroName', useValue: 'Windstorm' }
],
bootstrap: [ AppComponent ],
// schemas: [ NO_ERRORS_SCHEMA ] // helpful for debugging
})
]
/* tslint:disable no-unused-variable */
// #docregion ng2import
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import {
LocationStrategy,
HashLocationStrategy
} from '@angular/common';
// #enddocregion ng2import

View File

@ -0,0 +1,31 @@
import { Component, EventEmitter } from '@angular/core';
// #docregion
export class ConfirmComponent {
constructor(){
this.ok = new EventEmitter();
this.notOk = new EventEmitter();
}
onOkClick() {
this.ok.emit(true);
}
onNotOkClick() {
this.notOk.emit(true);
}
}
ConfirmComponent.annotations = [
new Component({
selector: 'app-confirm',
templateUrl: './confirm.component.html',
inputs: [
'okMsg',
'notOkMsg: cancelMsg'
],
outputs: [
'ok',
'notOk: cancel'
]
})
];
// #enddocregion

View File

@ -0,0 +1,6 @@
<button (click)="onOkClick()">
{{okMsg}}
</button>
<button (click)="onNotOkClick()">
{{notOkMsg}}
</button>

View File

@ -0,0 +1,13 @@
import { Injectable } from '@angular/core';
export class DataService {
constructor() {
}
getHeroName() {
return 'Windstorm';
}
}
DataService.annotations = [
new Injectable()
];

View File

@ -0,0 +1,10 @@
import { Component } from '@angular/core';
export class HeroComponent { }
HeroComponent.annotations = [
new Component({
selector: 'hero-di-inject-additional',
template: `<hero-title title="Tour of Heroes"></hero-title>`
})
];

View File

@ -0,0 +1,20 @@
import { Component, Inject } from '@angular/core';
// #docregion
export class HeroComponent {
constructor(name) {
this.name = name;
}
}
HeroComponent.annotations = [
new Component({
selector: 'hero-di-inject',
template: `<h1>Hero: {{name}}</h1>`
})
];
HeroComponent.parameters = [
[new Inject('heroName')]
];
// #enddocregion

View File

@ -0,0 +1,21 @@
// #docregion
import { Component } from '@angular/core';
import { DataService } from './data.service';
export class HeroComponent {
constructor(dataService) {
this.name = dataService.getHeroName();
}
}
HeroComponent.annotations = [
new Component({
selector: 'hero-di',
template: `<h1>Hero: {{name}}</h1>`
})
];
HeroComponent.parameters = [
[DataService]
];
// #enddocregion

View File

@ -0,0 +1,50 @@
import { Component } from '@angular/core';
// #docregion
export class HeroHostComponent {
constructor() {
this.active = false;
this.clicks = 0;
this.headingClass = true;
this.title = 'Hero Host Tooltip';
}
clicked() {
this.clicks += 1;
}
enter(event) {
this.active = true;
this.headingClass = false;
}
leave(event) {
this.active = false;
this.headingClass = true;
}
}
// #docregion metadata
HeroHostComponent.annotations = [
new Component({
selector: 'hero-host',
template: `
<h1 [class.active]="active">Hero Host</h1>
<div>Heading clicks: {{clicks}}</div>
`,
host: {
// HostBindings to the <hero-host> element
'[title]': 'title',
'[class.heading]': 'headingClass',
'(click)': 'clicked()',
// HostListeners on the entire <hero-host> element
'(mouseenter)': 'enter($event)',
'(mouseleave)': 'leave($event)'
},
// Styles within (but excluding) the <hero-host> element
styles: ['.active {background-color: yellow;}']
})
];
// #enddocregion metadata
// #enddocregion

View File

@ -0,0 +1,31 @@
import { Component } from '@angular/core';
export class HeroIOComponent {
constructor() {
this.okClicked = false;
this.cancelClicked = false;
}
onOk() {
this.okClicked = true;
}
onCancel() {
this.cancelClicked = true;
}
}
HeroIOComponent.annotations = [
new Component({
selector: 'hero-io',
template: `
<app-confirm [okMsg]="'OK'"
[cancelMsg]="'Cancel'"
(ok)="onOk()"
(cancel)="onCancel()">
</app-confirm>
<span *ngIf="okClicked">OK clicked</span>
<span *ngIf="cancelClicked">Cancel clicked</span>
`
})
];

View File

@ -0,0 +1,15 @@
// #docregion
import { Component } from '@angular/core';
export class HeroComponent {
ngOnInit() {
// todo: fetch from server async
setTimeout(() => this.name = 'Windstorm', 0);
}
}
HeroComponent.annotations = [
new Component({
selector: 'hero-lifecycle',
template: `<h1>Hero: {{name}}</h1>`
})
];

View File

@ -0,0 +1,97 @@
import {
Component,
ContentChild,
Input,
QueryList,
ViewChildren
} from '@angular/core';
export class ContentChildComponent {
constructor() {
this.active = false;
}
activate() {
this.active = !this.active;
}
}
ContentChildComponent.annotations = [
new Component({
selector: 'content-child',
template: `
<span class="content-child" *ngIf="active">
Active
</span>`
})
];
////////////////////
// #docregion content
export class ViewChildComponent {
constructor() {
this.active = false;
}
activate() {
this.active = !this.active;
this.content.activate();
}
}
ViewChildComponent.annotations = [
new Component({
selector: 'view-child',
template: `<h2 [class.active]=active>
{{hero.name}}
<ng-content></ng-content>
</h2>`,
styles: ['.active {font-weight: bold; background-color: skyblue;}'],
inputs: ['hero'],
queries: {
content: new ContentChild(ContentChildComponent)
}
})
];
// #enddocregion content
////////////////////
// #docregion view
export class HeroQueriesComponent {
constructor(){
this.active = false;
this.heroData = [
{id: 1, name: 'Windstorm'},
{id: 2, name: 'LaughingGas'}
];
}
activate() {
this.active = !this.active;
this.views.forEach(
view => view.activate()
);
}
get buttonLabel() {
return this.active ? 'Deactivate' : 'Activate';
}
}
HeroQueriesComponent.annotations = [
new Component({
selector: 'hero-queries',
template: `
<view-child *ngFor="let hero of heroData" [hero]="hero">
<content-child></content-child>
</view-child>
<button (click)="activate()">{{buttonLabel}} All</button>
`,
queries: {
views: new ViewChildren(ViewChildComponent)
}
})
];
// #enddocregion view

View File

@ -0,0 +1,28 @@
import { Attribute, Component, Inject, Optional } from '@angular/core';
// #docregion
export class HeroTitleComponent {
constructor(titlePrefix, title) {
this.titlePrefix = titlePrefix;
this.title = title;
this.msg = '';
}
ok() {
this.msg = 'OK!';
}
}
// #docregion templateUrl
HeroTitleComponent.annotations = [
new Component({
selector: 'hero-title',
templateUrl: './hero-title.component.html'
})
];
// #enddocregion templateUrl
HeroTitleComponent.parameters = [
[new Optional(), new Inject('titlePrefix')],
[new Attribute('title')]
];

View File

@ -0,0 +1,4 @@
<!-- #docregion -->
<h1>{{titlePrefix}} {{title}}</h1>
<button (click)="ok()">OK</button>
<p>{{ msg }}</p>

View File

@ -0,0 +1,21 @@
// #docplaster
// #docregion
// #docregion metadata
import { Component } from '@angular/core';
// #docregion appexport, class
export class HeroComponent {
constructor() {
this.title = 'Hero Detail';
}
getName() {return 'Windstorm'; }
}
// #enddocregion appexport, class
HeroComponent.annotations = [
new Component({
selector: 'hero-view',
template: '<h1>{{title}}: {{getName()}}</h1>'
})
];
// #enddocregion metadata

View File

@ -0,0 +1,26 @@
<!DOCTYPE html>
<html>
<head>
<base href="/">
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="styles.css">
<title>TypeScript to JavaScript</title>
<!-- Polyfill(s) for older browsers -->
<script src="node_modules/core-js/client/shim.min.js"></script>
<script src="node_modules/zone.js/dist/zone.js"></script>
<script src="node_modules/systemjs/dist/system.src.js"></script>
<script src="systemjs.config.js"></script>
<script>
System.import('main.js').catch(function(err){ console.error(err); });
</script>
</head>
<body>
<my-app>Loading...</my-app>
</body>
</html>

View File

@ -0,0 +1,4 @@
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppModule } from './app/app.module';
platformBrowserDynamic().bootstrapModule(AppModule);

View File

@ -0,0 +1,3 @@
{
"build": "build:babel"
}

View File

@ -0,0 +1,8 @@
{
"description": "TypeScript to JavaScript",
"basePath": "src/",
"files":[
"!**/karma*.*"
],
"tags":["cookbook"]
}

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