Compare commits

..

49 Commits

Author SHA1 Message Date
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
583 changed files with 8012 additions and 8810 deletions

View File

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

View File

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

View File

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

View File

@ -1,124 +1,29 @@
<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.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
* **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)
* **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
* **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))
<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="4.3.2"></a>
## [4.3.2](https://github.com/angular/angular/compare/4.3.1...4.3.2) (2017-07-26)
### Bug Fixes
* **animations:** export BrowserModule as apart of BrowserAnimationsModule ([#18263](https://github.com/angular/angular/issues/18263)) ([cbeb197](https://github.com/angular/angular/commit/cbeb197))
* **compiler:** add equiv & disp attributes to Xliff2 ICU placeholders ([#18283](https://github.com/angular/angular/issues/18283)) ([a084619](https://github.com/angular/angular/commit/a084619)), closes [#17344](https://github.com/angular/angular/issues/17344)
* **compiler:** allow numbers for ICU message cases in lexer ([#18095](https://github.com/angular/angular/issues/18095)) ([a8ac77b](https://github.com/angular/angular/commit/a8ac77b)), closes [#17799](https://github.com/angular/angular/issues/17799)
* **core:** invoke error handler outside of the Angular Zone ([#18269](https://github.com/angular/angular/issues/18269)) ([a1bb9c2](https://github.com/angular/angular/commit/a1bb9c2)), closes [#17073](https://github.com/angular/angular/issues/17073) [#7774](https://github.com/angular/angular/issues/7774)
* **platform-server:** don't clobber parse5 properties when setting ([#18237](https://github.com/angular/angular/issues/18237)) ([97135e8](https://github.com/angular/angular/commit/97135e8)), closes [#17050](https://github.com/angular/angular/issues/17050)
* **router:** child CanActivate guard should wait for parent to complete ([#18110](https://github.com/angular/angular/issues/18110)) ([b9e32c8](https://github.com/angular/angular/commit/b9e32c8)), closes [#15670](https://github.com/angular/angular/issues/15670)
* **router:** should throw when lazy loaded module doesn't define any routes ([#15001](https://github.com/angular/angular/issues/15001)) ([be49e0e](https://github.com/angular/angular/commit/be49e0e)), closes [#14596](https://github.com/angular/angular/issues/14596)
* **upgrade:** throw error if trying to get injector before setting ([#18209](https://github.com/angular/angular/issues/18209)) ([1f106d7](https://github.com/angular/angular/commit/1f106d7))
* **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))
@ -134,7 +39,7 @@
* **animations:** make sure @.disabled works in non-animation components ([a5c4bb5](https://github.com/angular/angular/commit/a5c4bb5))
* **common:** send flushed body as error instead of null ([17b7bc3](https://github.com/angular/angular/commit/17b7bc3)), closes [#18181](https://github.com/angular/angular/issues/18181)
* **compiler:** ensure jit external id arguments names are unique ([4671168](https://github.com/angular/angular/commit/4671168))
* **compiler-cli:** don't generate empty `<target/>` when extracting xliff ([f0476fc](https://github.com/angular/angular/commit/f0476fc)), closes [#15754](https://github.com/angular/angular/issues/15754)
* **compiler-cli:** don't generate empty <target/> when extracting xliff ([f0476fc](https://github.com/angular/angular/commit/f0476fc)), closes [#15754](https://github.com/angular/angular/issues/15754)
* **platform-server:** provide XhrFactory for HttpClient ([4ce29f3](https://github.com/angular/angular/commit/4ce29f3))
* **router:** canDeactivate guards should run from bottom to top ([1ac78bf](https://github.com/angular/angular/commit/1ac78bf)), closes [#15657](https://github.com/angular/angular/issues/15657)
* **router:** should navigate to the same url when config changes ([4340bea](https://github.com/angular/angular/commit/4340bea)), closes [#15535](https://github.com/angular/angular/issues/15535)
@ -1193,6 +1098,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

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

View File

@ -31,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

@ -43,9 +43,13 @@ dist/
**/app/**/*.ajs.js
# aot
*/aot/**/*
!*/aot/bs-config.json
!*/aot/index.html
**/*.ngfactory.ts
**/*.ngsummary.json
**/*.ngsummary.ts
**/*.shim.ngstyle.ts
**/*.metadata.json
!aot/bs-config.json
!aot/index.html
!rollup-config.js
# i18n
@ -55,6 +59,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

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

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

View File

@ -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>

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