Merge remote-tracking branch 'en/master' into aio

# Conflicts:
#	aio/content/guide/animations.md
#	aio/content/guide/aot-compiler.md
#	aio/content/guide/api-page-class.md
#	aio/content/guide/architecture-components.md
#	aio/content/guide/architecture-modules.md
#	aio/content/guide/architecture-services.md
#	aio/content/guide/architecture.md
#	aio/content/guide/attribute-directives.md
#	aio/content/guide/browser-support.md
#	aio/content/guide/component-interaction.md
#	aio/content/guide/component-styles.md
#	aio/content/guide/dependency-injection-in-action.md
#	aio/content/guide/dependency-injection-pattern.md
#	aio/content/guide/dependency-injection.md
#	aio/content/guide/deployment.md
#	aio/content/guide/displaying-data.md
#	aio/content/guide/elements.md
#	aio/content/guide/form-validation.md
#	aio/content/guide/forms.md
#	aio/content/guide/frequent-ngmodules.md
#	aio/content/guide/glossary.md
#	aio/content/guide/hierarchical-dependency-injection.md
#	aio/content/guide/http.md
#	aio/content/guide/i18n.md
#	aio/content/guide/language-service.md
#	aio/content/guide/lifecycle-hooks.md
#	aio/content/guide/ngmodule-api.md
#	aio/content/guide/ngmodule-faq.md
#	aio/content/guide/npm-packages.md
#	aio/content/guide/observables.md
#	aio/content/guide/pipes.md
#	aio/content/guide/providers.md
#	aio/content/guide/quickstart.md
#	aio/content/guide/reactive-forms.md
#	aio/content/guide/releases.md
#	aio/content/guide/router.md
#	aio/content/guide/rx-library.md
#	aio/content/guide/security.md
#	aio/content/guide/service-worker-config.md
#	aio/content/guide/service-worker-getting-started.md
#	aio/content/guide/set-document-title.md
#	aio/content/guide/setup.md
#	aio/content/guide/structural-directives.md
#	aio/content/guide/styleguide.md
#	aio/content/guide/template-syntax.md
#	aio/content/guide/testing-observables.md
#	aio/content/guide/testing.md
#	aio/content/guide/typescript-configuration.md
#	aio/content/guide/universal.md
#	aio/content/guide/upgrade.md
#	aio/content/guide/user-input.md
#	aio/content/guide/visual-studio-2015.md
#	aio/content/guide/webpack.md
#	aio/content/marketing/resources.json
#	aio/content/navigation.json
#	aio/content/tutorial/toh-pt0.md
#	aio/content/tutorial/toh-pt1.md
#	aio/content/tutorial/toh-pt4.md
#	aio/content/tutorial/toh-pt5.md
#	aio/content/tutorial/toh-pt6.md
#	aio/src/app/custom-elements/api/api-list.component.ts
#	aio/src/app/custom-elements/live-example/live-example.component.html
#	aio/src/app/custom-elements/live-example/live-example.component.ts
#	aio/src/app/layout/doc-viewer/doc-viewer.component.ts
#	aio/src/styles/main.scss
#	aio/tools/transforms/remark-package/services/renderMarkdown.js
This commit is contained in:
Zhicheng Wang 2018-07-27 10:51:18 +08:00
commit 2d485c6589
1273 changed files with 48328 additions and 23329 deletions

View File

@ -3,7 +3,10 @@
# See remote cache documentation in /docs/BAZEL.md # See remote cache documentation in /docs/BAZEL.md
# Don't be spammy in the logs # Don't be spammy in the logs
build --noshow_progress # TODO(gmagolan): Hide progress again once build performance improves
# Presently, CircleCI can timeout during bazel test ... with the following
# error: Too long with no output (exceeded 10m0s)
# build --noshow_progress
# Don't run manual tests # Don't run manual tests
test --test_tag_filters=-manual test --test_tag_filters=-manual

View File

@ -12,8 +12,8 @@
## IMPORTANT ## IMPORTANT
# If you change the `docker_image` version, also change the `cache_key` suffix and the version of # If you change the `docker_image` version, also change the `cache_key` suffix and the version of
# `com_github_bazelbuild_buildtools` in the `/WORKSPACE` file. # `com_github_bazelbuild_buildtools` in the `/WORKSPACE` file.
var_1: &docker_image angular/ngcontainer:0.2.0 var_1: &docker_image angular/ngcontainer:0.3.3
var_2: &cache_key v2-angular-{{ .Branch }}-{{ checksum "yarn.lock" }}-0.2.0 var_2: &cache_key v2-angular-{{ .Branch }}-{{ checksum "yarn.lock" }}-0.3.3
# Define common ENV vars # Define common ENV vars
var_3: &define_env_vars var_3: &define_env_vars
@ -80,33 +80,73 @@ jobs:
- run: ls /home/circleci/bazel_repository_cache || true - run: ls /home/circleci/bazel_repository_cache || true
- run: bazel info release - run: bazel info release
- run: bazel run @yarn//:yarn - run: bazel run @nodejs//:yarn
# Use bazel query so that we explicitly ask for all buildable targets to be built as well # Use bazel query so that we explicitly ask for all buildable targets to be built as well
# This avoids waiting for the slowest build target to finish before running the first test # This avoids waiting for the slowest build target to finish before running the first test
# See https://github.com/bazelbuild/bazel/issues/4257 # See https://github.com/bazelbuild/bazel/issues/4257
# NOTE: Angular developers should typically just bazel build //packages/... or bazel test //packages/... # NOTE: Angular developers should typically just bazel build //packages/... or bazel test //packages/...
- run: bazel query --output=label //... | xargs bazel test - run: bazel query --output=label //... | xargs bazel test --build_tag_filters=-ivy-only --test_tag_filters=-manual,-ivy-only
# CircleCI will allow us to go back and view/download these artifacts from past builds. # CircleCI will allow us to go back and view/download these artifacts from past builds.
# Also we can use a service like https://buildsize.org/ to automatically track binary size of these artifacts. # Also we can use a service like https://buildsize.org/ to automatically track binary size of these artifacts.
# The destination keys need be format {projectName}/{context}/{fileName} so that the github-robot can process them for size calculations
# projectName should remain consistant to group files
# context and fileName can be almost anything (within usual URI rules)
# There should only be exactly 2 forward slashes in the path
# This is so they're backwards compatiable with the existing data we have on bundle sizes
- store_artifacts: - store_artifacts:
path: dist/bin/packages/core/test/bundling/hello_world/bundle.min.js path: dist/bin/packages/core/test/bundling/hello_world/bundle.min.js
destination: packages/core/test/bundling/hello_world/bundle.min.js destination: core/hello_world/bundle
- store_artifacts: - store_artifacts:
path: dist/bin/packages/core/test/bundling/todo/bundle.min.js path: dist/bin/packages/core/test/bundling/todo/bundle.min.js
destination: packages/core/test/bundling/todo/bundle.min.js destination: core/todo/bundle
- store_artifacts: - store_artifacts:
path: dist/bin/packages/core/test/bundling/hello_world/bundle.min.js.br path: dist/bin/packages/core/test/bundling/hello_world/bundle.min.js.br
destination: packages/core/test/bundling/hello_world/bundle.min.js.br destination: core/hello_world/bundle.br
- store_artifacts: - store_artifacts:
path: dist/bin/packages/core/test/bundling/todo/bundle.min.js.br path: dist/bin/packages/core/test/bundling/todo/bundle.min.js.br
destination: packages/core/test/bundling/todo/bundle.min.js.br destination: core/todo/bundle.br
- save_cache: - save_cache:
key: *cache_key key: *cache_key
paths: paths:
- "node_modules" - "node_modules"
- "~/bazel_repository_cache" - "~/bazel_repository_cache"
# Temporary job to test what will happen when we flip the Ivy flag to true
test_ivy_jit:
<<: *job_defaults
resource_class: xlarge
steps:
- *define_env_vars
- checkout:
<<: *post_checkout
# See remote cache documentation in /docs/BAZEL.md
- run: .circleci/setup_cache.sh
- run: sudo cp .circleci/bazel.rc /etc/bazel.bazelrc
- *setup-bazel-remote-cache
- restore_cache:
key: *cache_key
- run: bazel run @yarn//:yarn
- run: bazel query --output=label //... | xargs bazel test --define=compile=jit --build_tag_filters=ivy-jit --test_tag_filters=-manual,ivy-jit
test_ivy_aot:
<<: *job_defaults
resource_class: xlarge
steps:
- *define_env_vars
- checkout:
<<: *post_checkout
# See remote cache documentation in /docs/BAZEL.md
- run: .circleci/setup_cache.sh
- run: sudo cp .circleci/bazel.rc /etc/bazel.bazelrc
- *setup-bazel-remote-cache
- restore_cache:
key: *cache_key
- run: bazel run @yarn//:yarn
- run: bazel query --output=label //... | xargs bazel test --define=compile=local --build_tag_filters=ivy-local --test_tag_filters=-manual,ivy-local
# This job exists only for backwards-compatibility with old scripts and tests # This job exists only for backwards-compatibility with old scripts and tests
# that rely on the pre-Bazel dist/packages-dist layout. # that rely on the pre-Bazel dist/packages-dist layout.
@ -127,7 +167,7 @@ jobs:
- run: sudo cp .circleci/bazel.rc /etc/bazel.bazelrc - run: sudo cp .circleci/bazel.rc /etc/bazel.bazelrc
- *setup-bazel-remote-cache - *setup-bazel-remote-cache
- run: bazel run @yarn//:yarn - run: bazel run @nodejs//:yarn
- run: scripts/build-packages-dist.sh - run: scripts/build-packages-dist.sh
# Save the npm packages from //packages/... for other workflow jobs to read # Save the npm packages from //packages/... for other workflow jobs to read
@ -136,6 +176,8 @@ jobs:
root: dist root: dist
paths: paths:
- packages-dist - packages-dist
- packages-dist-ivy-jit
- packages-dist-ivy-local
# We run the integration tests outside of Bazel for now. # We run the integration tests outside of Bazel for now.
# They are a separate workflow job so that they can be easily re-run. # They are a separate workflow job so that they can be easily re-run.
@ -145,6 +187,10 @@ jobs:
# See comments inside the integration/run_tests.sh script. # See comments inside the integration/run_tests.sh script.
integration_test: integration_test:
<<: *job_defaults <<: *job_defaults
# Note: we run Bazel in one of the integration tests, and it can consume >2G
# of memory. Together with the system under test, this can exhaust the RAM
# on a 4G worker so we use a larger machine here too.
resource_class: xlarge
steps: steps:
- *define_env_vars - *define_env_vars
- checkout: - checkout:
@ -196,6 +242,8 @@ workflows:
jobs: jobs:
- lint - lint
- test - test
- test_ivy_jit
- test_ivy_aot
- build-packages-dist - build-packages-dist
- integration_test: - integration_test:
requires: requires:
@ -208,6 +256,8 @@ workflows:
requires: requires:
# Only publish if tests and integration tests pass # Only publish if tests and integration tests pass
- test - test
- test_ivy_jit
- test_ivy_aot
- integration_test - integration_test
# Get the artifacts to publish from the build-packages-dist job # Get the artifacts to publish from the build-packages-dist job
# since the publishing script expects the legacy outputs layout. # since the publishing script expects the legacy outputs layout.

View File

@ -1,5 +1,14 @@
# Configuration for angular-robot # Configuration for angular-robot
#options for the size plugin
size:
disabled: false
maxSizeIncrease: 1000
circleCiStatusName: "ci/circleci: build-packages-dist"
status:
disabled: false
context: "ci/angular: size"
# options for the merge plugin # options for the merge plugin
merge: merge:
# the status will be added to your pull requests # the status will be added to your pull requests
@ -36,10 +45,12 @@ merge:
- "packages/language-service/**" - "packages/language-service/**"
- "**/.gitignore" - "**/.gitignore"
- "**/.gitkeep" - "**/.gitkeep"
- "**/package.json"
- "**/tsconfig-build.json" - "**/tsconfig-build.json"
- "**/tsconfig.json" - "**/tsconfig.json"
- "**/rollup.config.js" - "**/rollup.config.js"
- "**/BUILD.bazel" - "**/BUILD.bazel"
- "packages/**/integrationtest/**"
- "packages/**/test/**" - "packages/**/test/**"
# comment that will be added to a PR when there is a conflict, leave empty or set to false to disable # comment that will be added to a PR when there is a conflict, leave empty or set to false to disable

View File

@ -10,7 +10,6 @@
# andrewseguin - Andrew Seguin # andrewseguin - Andrew Seguin
# brandonroberts - Brandon Roberts # brandonroberts - Brandon Roberts
# brocco - Mike Brocchi # brocco - Mike Brocchi
# chuckjaz - Chuck Jazdzewski
# filipesilva - Filipe Silva # filipesilva - Filipe Silva
# gkalpak - George Kalpakas # gkalpak - George Kalpakas
# hansl - Hans Larsen # hansl - Hans Larsen
@ -18,12 +17,12 @@
# jasonaden - Jason Aden # jasonaden - Jason Aden
# kapunahelewong - Kapunahele Wong # kapunahelewong - Kapunahele Wong
# kara - Kara Erickson # kara - Kara Erickson
# kyliau - Keen Yee Liau
# matsko - Matias Niemelä # matsko - Matias Niemelä
# mhevery - Misko Hevery # mhevery - Misko Hevery
# petebacondarwin - Pete Bacon Darwin # petebacondarwin - Pete Bacon Darwin
# pkozlowski-opensource - Pawel Kozlowski # pkozlowski-opensource - Pawel Kozlowski
# robwormald - Rob Wormald # robwormald - Rob Wormald
# tinayuangao - Tina Gao
# vicb - Victor Berchet # vicb - Victor Berchet
# vikerman - Vikram Subramanian # vikerman - Vikram Subramanian
@ -94,7 +93,7 @@ groups:
- "tools/bazel.rc" - "tools/bazel.rc"
users: users:
- alexeagle #primary - alexeagle #primary
- chuckjaz - kyliau
- IgorMinar #fallback - IgorMinar #fallback
- mhevery - mhevery
- vikerman #fallback - vikerman #fallback
@ -133,7 +132,7 @@ groups:
- "packages/core/*" - "packages/core/*"
users: users:
- mhevery #primary - mhevery #primary
- chuckjaz - jasonaden
- kara - kara
- vicb - vicb
- IgorMinar #fallback - IgorMinar #fallback
@ -218,7 +217,6 @@ groups:
- "aio/content/examples/reactive-forms/*" - "aio/content/examples/reactive-forms/*"
users: users:
- kara #primary - kara #primary
- tinayuangao #secondary
- IgorMinar #fallback - IgorMinar #fallback
- mhevery #fallback - mhevery #fallback
@ -237,7 +235,7 @@ groups:
files: files:
- "packages/language-service/*" - "packages/language-service/*"
users: users:
- chuckjaz #primary - kyliau #primary
# needs secondary # needs secondary
- vicb - vicb
- IgorMinar #fallback - IgorMinar #fallback

View File

@ -5,53 +5,39 @@ load("@build_bazel_rules_nodejs//:defs.bzl", "node_modules_filegroup")
exports_files([ exports_files([
"tsconfig.json", "tsconfig.json",
"LICENSE", "LICENSE",
"protractor-perf.conf.js",
]) ])
# Developers should always run `bazel run :install` # Developers should always run `bazel run :install`
# This ensures that package.json in subdirectories get installed as well. # This ensures that package.json in subdirectories get installed as well.
alias( alias(
name = "install", name = "install",
actual = "@yarn//:yarn", actual = "@nodejs//:yarn",
) )
node_modules_filegroup( alias(
name = "node_modules", name = "node_modules",
packages = [ actual = "@angular_deps//:node_modules",
"bytebuffer",
"hammerjs",
"jasmine",
"minimist",
"protobufjs",
"reflect-metadata",
"source-map-support",
"tsickle",
"tslib",
"tsutils",
"typescript",
"zone.js",
"@angular-devkit/core",
"@angular-devkit/schematics",
"@types",
"@webcomponents/custom-elements",
],
) )
filegroup( filegroup(
name = "web_test_bootstrap_scripts", name = "web_test_bootstrap_scripts",
# do not sort # do not sort
srcs = [ srcs = [
"//:node_modules/reflect-metadata/Reflect.js", "@angular_deps//:node_modules/reflect-metadata/Reflect.js",
"//:node_modules/zone.js/dist/zone.js", "@angular_deps//:node_modules/zone.js/dist/zone.js",
"//:node_modules/zone.js/dist/zone-testing.js", "@angular_deps//:node_modules/zone.js/dist/zone-testing.js",
"//:node_modules/zone.js/dist/task-tracking.js", "@angular_deps//:node_modules/zone.js/dist/task-tracking.js",
"//:test-events.js",
], ],
) )
filegroup( filegroup(
name = "angularjs", name = "angularjs_scripts",
# do not sort
srcs = [ srcs = [
"//:node_modules/angular/angular.js", "@angular_deps//:node_modules/angular-1.5/angular.js",
"//:node_modules/angular-mocks/angular-mocks.js", "@angular_deps//:node_modules/angular-mocks-1.5/angular-mocks.js",
"@angular_deps//:node_modules/angular-mocks/angular-mocks.js",
"@angular_deps//:node_modules/angular/angular.js",
], ],
) )

View File

@ -1,3 +1,226 @@
<a name="6.1.0"></a>
# [6.1.0](https://github.com/angular/angular/compare/6.0.0-rc.5...6.1.0) (2018-07-25)
### Bug Fixes
* **animations:** always render end-state styles for orphaned DOM nodes ([#24236](https://github.com/angular/angular/issues/24236)) ([dc4a3d0](https://github.com/angular/angular/commit/dc4a3d0))
* **animations:** set animations styles properly on platform-server ([#24624](https://github.com/angular/angular/issues/24624)) ([0b356d4](https://github.com/angular/angular/commit/0b356d4))
* **animations:** do not throw errors when a destroyed component is animated ([#23836](https://github.com/angular/angular/issues/23836)) ([d2a8687](https://github.com/angular/angular/commit/d2a8687))
* **animations:** Fix browser detection logic ([#24188](https://github.com/angular/angular/issues/24188)) ([b492b9e](https://github.com/angular/angular/commit/b492b9e))
* **animations:** properly clean up queried element styles in safari/edge ([#23633](https://github.com/angular/angular/issues/23633)) ([da9ff25](https://github.com/angular/angular/commit/da9ff25))
* **animations:** retain state styling for nodes that are moved around ([#23534](https://github.com/angular/angular/issues/23534)) ([65211f4](https://github.com/angular/angular/commit/65211f4))
* **animations:** retain trigger-state for nodes that are moved around ([#24238](https://github.com/angular/angular/issues/24238)) ([8db928d](https://github.com/angular/angular/commit/8db928d))
* **bazel:** Allow ng_module to depend on targets w no deps ([#24446](https://github.com/angular/angular/issues/24446)) ([282d351](https://github.com/angular/angular/commit/282d351))
* **benchpress:** Fix promise chain in chrome_driver_extension. ([#23458](https://github.com/angular/angular/issues/23458)) ([d4b6c41](https://github.com/angular/angular/commit/d4b6c41))
* **common:** do not round factional seconds ([#24831](https://github.com/angular/angular/issues/24831)) ([a527c69](https://github.com/angular/angular/commit/a527c69)), closes [#24384](https://github.com/angular/angular/issues/24384)
* **common:** format fractional seconds ([#24844](https://github.com/angular/angular/issues/24844)) ([0b4d85e](https://github.com/angular/angular/commit/0b4d85e)), closes [#24831](https://github.com/angular/angular/issues/24831)
* **common:** properly update collection reference in NgForOf ([#24684](https://github.com/angular/angular/issues/24684)) ([ff84c5c](https://github.com/angular/angular/commit/ff84c5c)), closes [#24155](https://github.com/angular/angular/issues/24155)
* **common:** use correct currency format for locale de-AT ([#24658](https://github.com/angular/angular/issues/24658)) ([dcabb05](https://github.com/angular/angular/commit/dcabb05)), closes [#24609](https://github.com/angular/angular/issues/24609)
* **common:** do not round factional seconds ([#24831](https://github.com/angular/angular/issues/24831)) ([a527c69](https://github.com/angular/angular/commit/a527c69)), closes [#24384](https://github.com/angular/angular/issues/24384)
* **common:** properly update collection reference in NgForOf ([#24684](https://github.com/angular/angular/issues/24684)) ([ff84c5c](https://github.com/angular/angular/commit/ff84c5c)), closes [#24155](https://github.com/angular/angular/issues/24155)
* **common:** use correct currency format for locale de-AT ([#24658](https://github.com/angular/angular/issues/24658)) ([dcabb05](https://github.com/angular/angular/commit/dcabb05)), closes [#24609](https://github.com/angular/angular/issues/24609)
* **common:** use correct ICU plural for locale mk ([#24659](https://github.com/angular/angular/issues/24659)) ([64a8584](https://github.com/angular/angular/commit/64a8584))
* **compiler:** fix a few non-tree-shakeable code patterns ([#24677](https://github.com/angular/angular/issues/24677)) ([50d4a4f](https://github.com/angular/angular/commit/50d4a4f))
* **compiler:** i18n_extractor now outputs the correct source file name ([#24885](https://github.com/angular/angular/issues/24885)) ([c8ad965](https://github.com/angular/angular/commit/c8ad965)), closes [#24884](https://github.com/angular/angular/issues/24884)
* **compiler:** fix a few non-tree-shakeable code patterns ([#24677](https://github.com/angular/angular/issues/24677)) ([50d4a4f](https://github.com/angular/angular/commit/50d4a4f))
* **compiler:** support `.` in import statements. ([#20634](https://github.com/angular/angular/issues/20634)) ([d8f7b29](https://github.com/angular/angular/commit/d8f7b29)), closes [#20363](https://github.com/angular/angular/issues/20363)
* **compiler:** avoid a crash in ngc-wrapped. ([#23468](https://github.com/angular/angular/issues/23468)) ([e1c4930](https://github.com/angular/angular/commit/e1c4930))
* **compiler:** generate constant array for i18n attributes ([#23837](https://github.com/angular/angular/issues/23837)) ([cfde36d](https://github.com/angular/angular/commit/cfde36d))
* **compiler:** generate core-compliant hostBindings property ([#24087](https://github.com/angular/angular/issues/24087)) ([01b5acd](https://github.com/angular/angular/commit/01b5acd)), closes [#24013](https://github.com/angular/angular/issues/24013)
* **compiler:** handle undefined annotation metadata ([#23349](https://github.com/angular/angular/issues/23349)) ([ca776c5](https://github.com/angular/angular/commit/ca776c5))
* **compiler-cli:** Use typescript to resolve modules for metadata ([#22856](https://github.com/angular/angular/issues/22856)) ([0d5f2d3](https://github.com/angular/angular/commit/0d5f2d3))
* **compiler-cli:** Use typescript to resolve modules for metadata ([#22856](https://github.com/angular/angular/issues/22856)) ([0d5f2d3](https://github.com/angular/angular/commit/0d5f2d3))
* **compiler-cli:** don't rely on incompatible TS method ([#23550](https://github.com/angular/angular/issues/23550)) ([b1f040f](https://github.com/angular/angular/commit/b1f040f))
* **core:** stop reusing provider definitions across NgModuleRef instances ([#25022](https://github.com/angular/angular/issues/25022)) ([6b859da](https://github.com/angular/angular/commit/6b859da)), closes [#25018](https://github.com/angular/angular/issues/25018)
* **core:** mark NgModule as not the root if APP_ROOT is set to false ([#24814](https://github.com/angular/angular/issues/24814)) ([1089261](https://github.com/angular/angular/commit/1089261))
* **core:** use addCustomEqualityTester instead of overriding toEqual ([#22983](https://github.com/angular/angular/issues/22983)) ([0922228](https://github.com/angular/angular/commit/0922228)), closes [#22939](https://github.com/angular/angular/issues/22939)
* **core:** mark NgModule as not the root if APP_ROOT is set to false ([#24814](https://github.com/angular/angular/issues/24814)) ([1089261](https://github.com/angular/angular/commit/1089261))
* **core:** use addCustomEqualityTester instead of overriding toEqual ([#22983](https://github.com/angular/angular/issues/22983)) ([0922228](https://github.com/angular/angular/commit/0922228)), closes [#22939](https://github.com/angular/angular/issues/22939)
* **core:** Injector correctly honors the @Self flag ([#24520](https://github.com/angular/angular/issues/24520)) ([ccbda9d](https://github.com/angular/angular/commit/ccbda9d))
* **core:** avoid eager providers re-initialization ([#23559](https://github.com/angular/angular/issues/23559)) ([0c6dc45](https://github.com/angular/angular/commit/0c6dc45))
* **core:** call ngOnDestroy on all services that have it ([#23755](https://github.com/angular/angular/issues/23755)) ([fc03427](https://github.com/angular/angular/commit/fc03427)), closes [#22466](https://github.com/angular/angular/issues/22466) [#22240](https://github.com/angular/angular/issues/22240) [#14818](https://github.com/angular/angular/issues/14818)
* **docs-infra:** fix table header layout in API pages ([#24919](https://github.com/angular/angular/issues/24919)) ([3cd9645](https://github.com/angular/angular/commit/3cd9645))
* **elements:** always check to create strategy ([#23825](https://github.com/angular/angular/issues/23825)) ([b1cda36](https://github.com/angular/angular/commit/b1cda36))
* **elements:** prevent closure renaming of platform properties ([#23843](https://github.com/angular/angular/issues/23843)) ([d4b8b24](https://github.com/angular/angular/commit/d4b8b24))
* **forms:** properly handle special properties in FormGroup.get ([#22249](https://github.com/angular/angular/issues/22249)) ([9367e91](https://github.com/angular/angular/commit/9367e91)), closes [#17195](https://github.com/angular/angular/issues/17195)
* **language-service:** do not overwrite native `Reflect` ([#24299](https://github.com/angular/angular/issues/24299)) ([6881404](https://github.com/angular/angular/commit/6881404)), closes [#21420](https://github.com/angular/angular/issues/21420)
* **language-service:** do not overwrite native `Reflect` ([#24299](https://github.com/angular/angular/issues/24299)) ([6881404](https://github.com/angular/angular/commit/6881404)), closes [#21420](https://github.com/angular/angular/issues/21420)
* **platform-browser:** add missing deps for HammerGesturesPlugin ([#24682](https://github.com/angular/angular/issues/24682)) ([13d60ea](https://github.com/angular/angular/commit/13d60ea))
* **platform-browser:** mark Meta and Title services as tree shakable providers ([#24815](https://github.com/angular/angular/issues/24815)) ([197387d](https://github.com/angular/angular/commit/197387d))
* **platform-browser:** workaround wrong import path generated by ngc for DOCUMENT ([#24830](https://github.com/angular/angular/issues/24830)) ([7d27ecc](https://github.com/angular/angular/commit/7d27ecc))
* **platform-browser:** add missing deps for HammerGesturesPlugin ([#24682](https://github.com/angular/angular/issues/24682)) ([13d60ea](https://github.com/angular/angular/commit/13d60ea))
* **platform-browser:** mark Meta and Title services as tree shakable providers ([#24815](https://github.com/angular/angular/issues/24815)) ([197387d](https://github.com/angular/angular/commit/197387d))
* **platform-browser:** workaround wrong import path generated by ngc for DOCUMENT ([#24830](https://github.com/angular/angular/issues/24830)) ([7d27ecc](https://github.com/angular/angular/commit/7d27ecc))
* **platform-server:** avoid clash between server and client style encapsulation attributes ([#24158](https://github.com/angular/angular/issues/24158)) ([b96a3c8](https://github.com/angular/angular/commit/b96a3c8))
* **platform-server:** avoid dependency cycle when using http interceptor ([#24229](https://github.com/angular/angular/issues/24229)) ([60aa943](https://github.com/angular/angular/commit/60aa943)), closes [#23023](https://github.com/angular/angular/issues/23023)
* **platform-server:** don't reflect innerHTML property to attribute ([#24213](https://github.com/angular/angular/issues/24213)) ([6a663a4](https://github.com/angular/angular/commit/6a663a4)), closes [#19278](https://github.com/angular/angular/issues/19278)
* **platform-server:** provide Domino DOM types globally ([#24116](https://github.com/angular/angular/issues/24116)) ([c73196e](https://github.com/angular/angular/commit/c73196e)), closes [#23280](https://github.com/angular/angular/issues/23280) [#23133](https://github.com/angular/angular/issues/23133)
* **router:** Fix _lastPathIndex in deeply nested empty paths ([#22394](https://github.com/angular/angular/issues/22394)) ([968f153](https://github.com/angular/angular/commit/968f153))
* **router:** add ability to recover from malformed url ([#23283](https://github.com/angular/angular/issues/23283)) ([86d254d](https://github.com/angular/angular/commit/86d254d)), closes [#21468](https://github.com/angular/angular/issues/21468)
* **router:** add ability to recover from malformed url ([#23283](https://github.com/angular/angular/issues/23283)) ([86d254d](https://github.com/angular/angular/commit/86d254d)), closes [#21468](https://github.com/angular/angular/issues/21468)
* **router:** fix lazy loading of aux routes ([#23459](https://github.com/angular/angular/issues/23459)) ([5731d07](https://github.com/angular/angular/commit/5731d07)), closes [#10981](https://github.com/angular/angular/issues/10981)
* **router:** avoid freezing queryParams in-place ([#22663](https://github.com/angular/angular/issues/22663)) ([89f64e5](https://github.com/angular/angular/commit/89f64e5)), closes [#22617](https://github.com/angular/angular/issues/22617)
* **router:** cache route handle if found ([#22475](https://github.com/angular/angular/issues/22475)) ([4cfa571](https://github.com/angular/angular/commit/4cfa571)), closes [#22474](https://github.com/angular/angular/issues/22474)
* **router:** correct the segment parsing so it won't break on ampersand ([#23684](https://github.com/angular/angular/issues/23684)) ([553a680](https://github.com/angular/angular/commit/553a680))
* **service-worker:** don't include sourceMappingURL in ngsw-worker ([#24877](https://github.com/angular/angular/issues/24877)) ([8620373](https://github.com/angular/angular/commit/8620373)), closes [#23596](https://github.com/angular/angular/issues/23596)
* **service-worker:** avoid network requests when looking up hashed resources in cache ([#24127](https://github.com/angular/angular/issues/24127)) ([52d43a9](https://github.com/angular/angular/commit/52d43a9))
* **service-worker:** avoid network requests when looking up hashed resources in cache ([#24127](https://github.com/angular/angular/issues/24127)) ([52d43a9](https://github.com/angular/angular/commit/52d43a9))
* **service-worker:** fix `SwPush.unsubscribe()` ([#24162](https://github.com/angular/angular/issues/24162)) ([3ed2d75](https://github.com/angular/angular/commit/3ed2d75)), closes [#24095](https://github.com/angular/angular/issues/24095)
* **service-worker:** add badge to NOTIFICATION_OPTION_NAMES ([#23241](https://github.com/angular/angular/issues/23241)) ([fb59b2d](https://github.com/angular/angular/commit/fb59b2d)), closes [#23196](https://github.com/angular/angular/issues/23196)
* **service-worker:** check platformBrowser before accessing navigator.serviceWorker ([#21231](https://github.com/angular/angular/issues/21231)) ([0bdd30e](https://github.com/angular/angular/commit/0bdd30e))
* **service-worker:** correctly handle requests with empty `clientId` ([#23625](https://github.com/angular/angular/issues/23625)) ([e0ed59e](https://github.com/angular/angular/commit/e0ed59e)), closes [#23526](https://github.com/angular/angular/issues/23526)
* **service-worker:** deprecate `versionedFiles` in asset-group resources ([#23584](https://github.com/angular/angular/issues/23584)) ([1d378e2](https://github.com/angular/angular/commit/1d378e2))
### Features
* **bazel:** Initial commit of protractor_web_test_suite ([#24787](https://github.com/angular/angular/issues/24787)) ([71e0df0](https://github.com/angular/angular/commit/71e0df0))
* **bazel:** protractor_web_test_suite for release ([#24787](https://github.com/angular/angular/issues/24787)) ([161ff5c](https://github.com/angular/angular/commit/161ff5c))
* **common:** introduce KeyValuePipe ([#24319](https://github.com/angular/angular/issues/24319)) ([2b49bf7](https://github.com/angular/angular/commit/2b49bf7))
* **compiler:** support `// ...` and `// TODO` in mock compiler expectations ([#23441](https://github.com/angular/angular/issues/23441)) ([c6b206e](https://github.com/angular/angular/commit/c6b206e))
* **compiler-cli:** update `tsickle` to `0.29.x` ([#24233](https://github.com/angular/angular/issues/24233)) ([f69ac67](https://github.com/angular/angular/commit/f69ac67))
* **core:** export defaultKeyValueDiffers to private api ([#24319](https://github.com/angular/angular/issues/24319)) ([92b278c](https://github.com/angular/angular/commit/92b278c))
* **core:** expose a Compiler API for accessing module ids from NgModule types ([#24258](https://github.com/angular/angular/issues/24258)) ([bd02b27](https://github.com/angular/angular/commit/bd02b27))
* **core:** KeyValueDiffer#diff allows null values ([#24319](https://github.com/angular/angular/issues/24319)) ([52ce9d5](https://github.com/angular/angular/commit/52ce9d5))
* **core:** add support for ShadowDOM v1 ([#24718](https://github.com/angular/angular/issues/24718)) ([3553977](https://github.com/angular/angular/commit/3553977))
* **core:** add support for using async/await with Jasmine ([#24637](https://github.com/angular/angular/issues/24637)) ([71100e6](https://github.com/angular/angular/commit/71100e6))
* **core:** add support for ShadowDOM v1 ([#24718](https://github.com/angular/angular/issues/24718)) ([3553977](https://github.com/angular/angular/commit/3553977))
* **core:** add support for using async/await with Jasmine ([#24637](https://github.com/angular/angular/issues/24637)) ([71100e6](https://github.com/angular/angular/commit/71100e6))
(https://github.com/angular/angular/commit/328971f)), closes [#24616](https://github.com/angular/angular/issues/24616)
* **platform-browser:** add HammerJS lazy-loader symbols to public API ([#23943](https://github.com/angular/angular/issues/23943)) ([26fbf1d](https://github.com/angular/angular/commit/26fbf1d))
* **platform-browser:** allow lazy-loading HammerJS ([#23906](https://github.com/angular/angular/issues/23906)) ([313bdce](https://github.com/angular/angular/commit/313bdce))
* **platform-server:** use EventManagerPlugin on the server ([#24132](https://github.com/angular/angular/issues/24132)) ([d6595eb](https://github.com/angular/angular/commit/d6595eb))
* **router:** add urlUpdateStrategy allow updating the browser URL at the beginning of navigation ([#24820](https://github.com/angular/angular/issues/24820)) ([328971f]
* **router:** add navigation execution context info to activation hooks ([#24204](https://github.com/angular/angular/issues/24204)) ([20c463e](https://github.com/angular/angular/commit/20c463e)), closes [#24202](https://github.com/angular/angular/issues/24202)
* **router:** implement scrolling restoration service ([#20030](https://github.com/angular/angular/issues/20030)) ([49c5234](https://github.com/angular/angular/commit/49c5234)), closes [#13636](https://github.com/angular/angular/issues/13636) [#10929](https://github.com/angular/angular/issues/10929) [#7791](https://github.com/angular/angular/issues/7791) [#6595](https://github.com/angular/angular/issues/6595)
* **service-worker:** add support for `?` in SW config globbing ([#24105](https://github.com/angular/angular/issues/24105)) ([250527c](https://github.com/angular/angular/commit/250527c))
* typescript 2.9 support ([#24652](https://github.com/angular/angular/issues/24652)) ([e3064d5](https://github.com/angular/angular/commit/e3064d5))
### build
* **bazel:** turn on preserve-symlinks ([#24881](https://github.com/angular/angular/issues/24881)) ([c438b5e](https://github.com/angular/angular/commit/c438b5e))
### BREAKING CHANGES
* **bazel:** Use of @angular/bazel rules now requires calling ng_setup_workspace() in your WORKSPACE file.
For example:
local_repository(
name = "angular",
path = "node_modules/@angular/bazel",
)
load("@angular//:index.bzl", "ng_setup_workspace")
ng_setup_workspace()
<a name="6.0.9"></a>
## [6.0.9](https://github.com/angular/angular/compare/6.0.8...6.0.9) (2018-07-11)
### Bug Fixes
* **common:** format fractional seconds ([#24844](https://github.com/angular/angular/issues/24844)) ([3c93d07](https://github.com/angular/angular/commit/3c93d07)), closes [#24831](https://github.com/angular/angular/issues/24831)
<a name="6.0.8"></a>
## [6.0.8](https://github.com/angular/angular/compare/6.0.7...6.0.8) (2018-07-11)
### Bug Fixes
* **common:** do not round factional seconds ([#24831](https://github.com/angular/angular/issues/24831)) ([0746485](https://github.com/angular/angular/commit/0746485)), closes [#24384](https://github.com/angular/angular/issues/24384)
* **common:** properly update collection reference in NgForOf ([#24684](https://github.com/angular/angular/issues/24684)) ([9a98de9](https://github.com/angular/angular/commit/9a98de9)), closes [#24155](https://github.com/angular/angular/issues/24155)
* **common:** use correct currency format for locale de-AT ([#24658](https://github.com/angular/angular/issues/24658)) ([a92f111](https://github.com/angular/angular/commit/a92f111)), closes [#24609](https://github.com/angular/angular/issues/24609)
* **compiler-cli:** Use typescript to resolve modules for metadata ([#22856](https://github.com/angular/angular/issues/22856)) ([7717ff1](https://github.com/angular/angular/commit/7717ff1))
* **core:** use addCustomEqualityTester instead of overriding toEqual ([#22983](https://github.com/angular/angular/issues/22983)) ([b8975a9](https://github.com/angular/angular/commit/b8975a9)), closes [#22939](https://github.com/angular/angular/issues/22939)
* **language-service:** do not overwrite native `Reflect` ([#24299](https://github.com/angular/angular/issues/24299)) ([de1c44f](https://github.com/angular/angular/commit/de1c44f)), closes [#21420](https://github.com/angular/angular/issues/21420)
* **router:** add ability to recover from malformed url ([#23283](https://github.com/angular/angular/issues/23283)) ([2d4f4b5](https://github.com/angular/angular/commit/2d4f4b5)), closes [#21468](https://github.com/angular/angular/issues/21468)
* **service-worker:** avoid network requests when looking up hashed resources in cache ([#24127](https://github.com/angular/angular/issues/24127)) ([183b079](https://github.com/angular/angular/commit/183b079))
### Features
* **core:** add support for ShadowDOM v1 ([#24718](https://github.com/angular/angular/issues/24718)) ([6c55a13](https://github.com/angular/angular/commit/6c55a13))
<a name="6.0.7"></a>
## [6.0.7](https://github.com/angular/angular/compare/6.0.6...6.0.7) (2018-06-27)
### Bug Fixes
* **animations:** set animations styles properly on platform-server ([#24624](https://github.com/angular/angular/issues/24624)) ([0b356d4](https://github.com/angular/angular/commit/0b356d4))
* **common:** use correct ICU plural for locale mk ([#24659](https://github.com/angular/angular/issues/24659)) ([64a8584](https://github.com/angular/angular/commit/64a8584))
<a name="6.0.6"></a>
## [6.0.6](https://github.com/angular/angular/compare/6.0.5...6.0.6) (2018-06-20)
### Bug Fixes
* **compiler:** support `.` in import statements. ([#20634](https://github.com/angular/angular/issues/20634)) ([e543c73](https://github.com/angular/angular/commit/e543c73)), closes [#20363](https://github.com/angular/angular/issues/20363)
* **core:** Injector correctly honors the @Self flag ([#24520](https://github.com/angular/angular/issues/24520)) ([f5b3661](https://github.com/angular/angular/commit/f5b3661))
<a name="6.0.5"></a>
## [6.0.5](https://github.com/angular/angular/compare/6.0.4...6.0.5) (2018-06-13)
* **animations:** always render end-state styles for orphaned DOM nodes ([#24236](https://github.com/angular/angular/issues/24236)) ([0139173](https://github.com/angular/angular/commit/0139173))
* **bazel:** Allow ng_module to depend on targets w no deps ([#24446](https://github.com/angular/angular/issues/24446)) ([ea3669e](https://github.com/angular/angular/commit/ea3669e))
* **docs-infra:** use script nomodule to load IE polyfills, skip other polyfills ([#24317](https://github.com/angular/angular/issues/24317)) ([e876535](https://github.com/angular/angular/commit/e876535)), closes [#23647](https://github.com/angular/angular/issues/23647)
* **router:** fix lazy loading of aux routes ([#23459](https://github.com/angular/angular/issues/23459)) ([d20877b](https://github.com/angular/angular/commit/d20877b)), closes [#10981](https://github.com/angular/angular/issues/10981)
* **service-worker:** fix `SwPush.unsubscribe()` ([#24162](https://github.com/angular/angular/issues/24162)) ([ea2987c](https://github.com/angular/angular/commit/ea2987c)), closes [#24095](https://github.com/angular/angular/issues/24095)
<a name="6.0.4"></a>
## [6.0.4](https://github.com/angular/angular/compare/6.0.3...6.0.4) (2018-06-06)
### Bug Fixes
* **animations:** Fix browser detection logic ([#24188](https://github.com/angular/angular/issues/24188)) ([c9eb491](https://github.com/angular/angular/commit/c9eb491))
* **animations:** retain trigger-state for nodes that are moved around ([#24238](https://github.com/angular/angular/issues/24238)) ([19deca1](https://github.com/angular/angular/commit/19deca1))
* **forms:** properly handle special properties in FormGroup.get ([#22249](https://github.com/angular/angular/issues/22249)) ([dc3e8aa](https://github.com/angular/angular/commit/dc3e8aa)), closes [#17195](https://github.com/angular/angular/issues/17195)
* **platform-server:** avoid clash between server and client style encapsulation attributes ([#24158](https://github.com/angular/angular/issues/24158)) ([e9f2203](https://github.com/angular/angular/commit/e9f2203))
* **platform-server:** avoid dependency cycle when using http interceptor ([#24229](https://github.com/angular/angular/issues/24229)) ([2991b1b](https://github.com/angular/angular/commit/2991b1b)), closes [#23023](https://github.com/angular/angular/issues/23023)
* **platform-server:** don't reflect innerHTML property to attibute ([#24213](https://github.com/angular/angular/issues/24213)) ([c17098d](https://github.com/angular/angular/commit/c17098d)), closes [#19278](https://github.com/angular/angular/issues/19278)
* **platform-server:** provide Domino DOM types globally ([#24116](https://github.com/angular/angular/issues/24116)) ([906b3ec](https://github.com/angular/angular/commit/906b3ec)), closes [#23280](https://github.com/angular/angular/issues/23280) [#23133](https://github.com/angular/angular/issues/23133)
<a name="6.0.3"></a>
## [6.0.3](https://github.com/angular/angular/compare/6.0.2...6.0.3) (2018-05-22)
### Bug Fixes
* **service-worker:** check platformBrowser before accessing navigator.serviceWorker ([#21231](https://github.com/angular/angular/issues/21231)) ([0ee5b7e](https://github.com/angular/angular/commit/0ee5b7e))
<a name="6.0.2"></a>
## [6.0.2](https://github.com/angular/angular/compare/6.0.1...6.0.2) (2018-05-15)
### Bug Fixes
* **animations:** do not throw errors when a destroyed component is animated ([#23836](https://github.com/angular/angular/issues/23836)) ([752b83a](https://github.com/angular/angular/commit/752b83a))
* **service-worker:** deprecate `versionedFiles` in asset-group resources ([#23584](https://github.com/angular/angular/issues/23584)) ([c6b618d](https://github.com/angular/angular/commit/c6b618d))
<a name="6.0.1"></a>
# [6.0.1](https://github.com/angular/angular/compare/6.0.0...6.0.1) (2018-05-11)
### Bug Fixes
* **animations:** properly clean up queried element styles in safari/edge ([#23686](https://github.com/angular/angular/issues/23686)) ([3824e3f](https://github.com/angular/angular/commit/3824e3f))
* **animations:** retain state styling for nodes that are moved around ([#23686](https://github.com/angular/angular/issues/23686)) ([05aa5e0](https://github.com/angular/angular/commit/05aa5e0))
* **core:** call ngOnDestroy on all services that have it ([#23755](https://github.com/angular/angular/issues/23755)) ([5581e97](https://github.com/angular/angular/commit/5581e97)), closes [#22466](https://github.com/angular/angular/issues/22466) [#22240](https://github.com/angular/angular/issues/22240) [#14818](https://github.com/angular/angular/issues/14818)
* **elements:** always check to create strategy ([#23825](https://github.com/angular/angular/issues/23825)) ([d280077](https://github.com/angular/angular/commit/d280077))
* **router:** avoid freezing queryParams in-place ([#22663](https://github.com/angular/angular/issues/22663)) ([3d8799b](https://github.com/angular/angular/commit/3d8799b)), closes [#22617](https://github.com/angular/angular/issues/22617)
* **router:** correct the segment parsing so it won't break on ampersand ([#23684](https://github.com/angular/angular/issues/23684)) ([8733843](https://github.com/angular/angular/commit/8733843))
* **service-worker:** correctly handle requests with empty `clientId` ([#23625](https://github.com/angular/angular/issues/23625)) ([2254ac2](https://github.com/angular/angular/commit/2254ac2)), closes [#23526](https://github.com/angular/angular/issues/23526)
<a name="6.0.0"></a> <a name="6.0.0"></a>
# [6.0.0](https://github.com/angular/angular/compare/6.0.0-beta.0...6.0.0) (2018-05-03) # [6.0.0](https://github.com/angular/angular/compare/6.0.0-beta.0...6.0.0) (2018-05-03)
@ -5,7 +228,7 @@
Angular v6 is the first release of Angular that unifies the Framework, Material and CLI. Angular v6 is the first release of Angular that unifies the Framework, Material and CLI.
To learn about the release highlights and our new CLI-powered update workflow for your projects please check out the [v6 release announcement](https://blog.angular.io/version-6-0-0-of-angular-now-available-cc56b0efa7a4). To learn about the release highlights and our new CLI-powered update workflow for your projects please check out the [v6 release announcement](https://blog.angular.io/version-6-0-0-of-angular-now-available-cc56b0efa7a4).
@ -156,10 +379,10 @@ To learn about the release highlights and our new CLI-powered update workflow fo
This change removes support for `<template>`. `<ng-template>` should be used instead. This change removes support for `<template>`. `<ng-template>` should be used instead.
BEFORE: BEFORE:
<!-- html template --> <!-- html template -->
<template>some template content</template> <template>some template content</template>
# tsconfig.json # tsconfig.json
{ {
# ... # ...
@ -169,12 +392,12 @@ To learn about the release highlights and our new CLI-powered update workflow fo
"enableLegacyTemplate": [true|false] "enableLegacyTemplate": [true|false]
} }
} }
AFTER: AFTER:
<!-- html template --> <!-- html template -->
<ng-template>some template content</ng-template> <ng-template>some template content</ng-template>
* **core:** it is no longer possible to import animation-related functions from @angular/core. All animation symbols must now be imported from @angular/animations. * **core:** it is no longer possible to import animation-related functions from @angular/core. All animation symbols must now be imported from @angular/animations.
@ -187,35 +410,35 @@ To learn about the release highlights and our new CLI-powered update workflow fo
Previously, ngModelChange was emitted before its underlying control was updated. Previously, ngModelChange was emitted before its underlying control was updated.
This was fine if you passed through the value directly through the $event keyword, e.g. This was fine if you passed through the value directly through the $event keyword, e.g.
``` ```
<input [(ngModel)]="name" (ngModelChange)="onChange($event)"> <input [(ngModel)]="name" (ngModelChange)="onChange($event)">
onChange(value) { onChange(value) {
console.log(value); // would log updated value console.log(value); // would log updated value
} }
``` ```
However, if you had a handler for the ngModelChange event that checked the value through the control, However, if you had a handler for the ngModelChange event that checked the value through the control,
you would get the old value rather than the updated value. e.g: you would get the old value rather than the updated value. e.g:
``` ```
<input #modelDir="ngModel" [(ngModel)]="name" (ngModelChange)="onChange(modelDir)"> <input #modelDir="ngModel" [(ngModel)]="name" (ngModelChange)="onChange(modelDir)">
onChange(ngModel: NgModel) { onChange(ngModel: NgModel) {
console.log(ngModel.value); // would log old value, not updated value console.log(ngModel.value); // would log old value, not updated value
} }
``` ```
Now the value and validity will be updated before the ngModelChange event is emitted, Now the value and validity will be updated before the ngModelChange event is emitted,
so the same setup will log the updated value. so the same setup will log the updated value.
``` ```
onChange(ngModel: NgModel) { onChange(ngModel: NgModel) {
console.log(ngModel.value); // will log updated value console.log(ngModel.value); // will log updated value
} }
``` ```
We think this order will be less confusing when the control is checked directly. We think this order will be less confusing when the control is checked directly.
You will only need to update your app if it has relied on this bug to keep track of the old control value. You will only need to update your app if it has relied on this bug to keep track of the old control value.
If that is the case, you should be able to track the old value directly by saving it on your component. If that is the case, you should be able to track the old value directly by saving it on your component.

View File

@ -227,10 +227,15 @@ The following is the list of supported scopes:
There are currently a few exceptions to the "use package name" rule: There are currently a few exceptions to the "use package name" rule:
* **packaging**: used for changes that change the npm package layout in all of our packages, e.g. public path changes, package.json changes done to all packages, d.ts file/format changes, changes to bundles, etc. * **packaging**: used for changes that change the npm package layout in all of our packages, e.g.
public path changes, package.json changes done to all packages, d.ts file/format changes, changes
to bundles, etc.
* **changelog**: used for updating the release notes in CHANGELOG.md * **changelog**: used for updating the release notes in CHANGELOG.md
* **aio**: used for docs-app (angular.io) related changes within the /aio directory of the repo * **docs-infra**: used for docs-app (angular.io) related changes within the /aio directory of the
* none/empty string: useful for `style`, `test` and `refactor` changes that are done across all packages (e.g. `style: add missing semicolons`) repo
* none/empty string: useful for `style`, `test` and `refactor` changes that are done across all
packages (e.g. `style: add missing semicolons`) and for docs changes that are not related to a
specific package (e.g. `docs: fix typo in tutorial`).
### Subject ### Subject
The subject contains a succinct description of the change: The subject contains a succinct description of the change:
@ -269,7 +274,7 @@ changes to be accepted, the CLA must be signed. It's a quick process, we promise
* https://help.github.com/articles/about-commit-email-addresses/ * https://help.github.com/articles/about-commit-email-addresses/
* https://help.github.com/articles/blocking-command-line-pushes-that-expose-your-personal-email-address/ * https://help.github.com/articles/blocking-command-line-pushes-that-expose-your-personal-email-address/
Note that if you have more than one Git identity, it is important to verify that you are logged in with the same ID with which you signed the CLA, before you commit changes. If not, your PR will fail the CLA check. Note that if you have more than one Git identity, it is important to verify that you are logged in with the same ID with which you signed the CLA, before you commit changes. If not, your PR will fail the CLA check.
<hr> <hr>

159
WORKSPACE
View File

@ -1,39 +1,79 @@
workspace(name = "angular") workspace(name = "angular")
#
# Download Bazel toolchain dependencies as needed by build actions
#
http_archive( http_archive(
name = "build_bazel_rules_nodejs", name = "build_bazel_rules_nodejs",
url = "https://github.com/bazelbuild/rules_nodejs/archive/1931156c232a08356dfda02e9c8b0275c2e63c00.zip", url = "https://github.com/bazelbuild/rules_nodejs/archive/20ff5892612f8359aec8aaf26dd3902a24976ada.zip",
strip_prefix = "rules_nodejs-1931156c232a08356dfda02e9c8b0275c2e63c00", strip_prefix = "rules_nodejs-20ff5892612f8359aec8aaf26dd3902a24976ada",
sha256 = "9cfe33276a6ac0076ee9ee159c4a2576f9851c0f437435b5ac19b2e592493078", sha256 = "07da9d4c3e688a02745d0f50709a87744706d4f5d1959b799b0ac38e97acd622",
) )
load("@build_bazel_rules_nodejs//:defs.bzl", "check_bazel_version", "node_repositories", "yarn_install") http_archive(
name = "io_bazel_rules_webtesting",
check_bazel_version("0.11.1") url = "https://github.com/bazelbuild/rules_webtesting/archive/7ffe970bbf380891754487f66c3d680c087d67f2.zip",
node_repositories(package_json = ["//:package.json"]) strip_prefix = "rules_webtesting-7ffe970bbf380891754487f66c3d680c087d67f2",
sha256 = "4fb0dca8c9a90547891b7ef486592775a523330fc4555c88cd8f09270055c2ce",
yarn_install(
name = "ts-api-guardian_runtime_deps",
package_json = "//tools/ts-api-guardian:package.json",
yarn_lock = "//tools/ts-api-guardian:yarn.lock",
)
yarn_install(
name = "http-server_runtime_deps",
package_json = "//tools/http-server:package.json",
yarn_lock = "//tools/http-server:yarn.lock",
) )
http_archive( http_archive(
name = "build_bazel_rules_typescript", name = "build_bazel_rules_typescript",
url = "https://github.com/bazelbuild/rules_typescript/archive/0.12.1.zip", url = "https://github.com/bazelbuild/rules_typescript/archive/0.15.3.zip",
strip_prefix = "rules_typescript-0.12.1", strip_prefix = "rules_typescript-0.15.3",
sha256 = "24e2c36f60508c6d270ae4265b89b381e3f66d550e70c367ed3755ad8d7ce3b0", sha256 = "a2b26ac3fc13036011196063db1bf7f1eae81334449201dc28087ebfa3708c99",
) )
load("@build_bazel_rules_typescript//:defs.bzl", "ts_setup_workspace") http_archive(
name = "io_bazel_rules_go",
url = "https://github.com/bazelbuild/rules_go/releases/download/0.10.3/rules_go-0.10.3.tar.gz",
sha256 = "feba3278c13cde8d67e341a837f69a029f698d7a27ddbb2a202be7a10b22142a",
)
ts_setup_workspace() # This commit matches the version of buildifier in angular/ngcontainer
# If you change this, also check if it matches the version in the angular/ngcontainer
# version in /.circleci/config.yml
BAZEL_BUILDTOOLS_VERSION = "82b21607e00913b16fe1c51bec80232d9d6de31c"
http_archive(
name = "com_github_bazelbuild_buildtools",
url = "https://github.com/bazelbuild/buildtools/archive/%s.zip" % BAZEL_BUILDTOOLS_VERSION,
strip_prefix = "buildtools-%s" % BAZEL_BUILDTOOLS_VERSION,
sha256 = "edb24c2f9c55b10a820ec74db0564415c0cf553fa55e9fc709a6332fb6685eff",
)
# Fetching the Bazel source code allows us to compile the Skylark linter
http_archive(
name = "io_bazel",
url = "https://github.com/bazelbuild/bazel/archive/968f87900dce45a7af749a965b72dbac51b176b3.zip",
strip_prefix = "bazel-968f87900dce45a7af749a965b72dbac51b176b3",
sha256 = "e373d2ae24955c1254c495c9c421c009d88966565c35e4e8444c082cb1f0f48f",
)
# We have a source dependency on the Devkit repository, because it's built with
# Bazel.
# This allows us to edit sources and have the effect appear immediately without
# re-packaging or "npm link"ing.
# Even better, things like aspects will visit the entire graph including
# ts_library rules in the devkit repository.
http_archive(
name = "angular_cli",
url = "https://github.com/angular/angular-cli/archive/v6.1.0-rc.0.zip",
strip_prefix = "angular-cli-6.1.0-rc.0",
sha256 = "8cf320ea58c321e103f39087376feea502f20eaf79c61a4fdb05c7286c8684fd",
)
http_archive(
name = "org_brotli",
url = "https://github.com/google/brotli/archive/f9b8c02673c576a3e807edbf3a9328e9e7af6d7c.zip",
strip_prefix = "brotli-f9b8c02673c576a3e807edbf3a9328e9e7af6d7c",
sha256 = "8a517806d2b7c8505ba5c53934e7d7c70d341b68ffd268e9044d35b564a48828",
)
#
# Point Bazel to WORKSPACEs that live in subdirectories
#
local_repository( local_repository(
name = "rxjs", name = "rxjs",
@ -47,54 +87,53 @@ local_repository(
path = "integration/bazel", path = "integration/bazel",
) )
# This commit matches the version of buildifier in angular/ngcontainer #
# If you change this, also check if it matches the version in the angular/ngcontainer # Load and install our dependencies downloaded above.
# version in /.circleci/config.yml #
BAZEL_BUILDTOOLS_VERSION = "70bc7843bb9950fece2bc014ed16de03419e36e2"
http_archive( load("@build_bazel_rules_nodejs//:defs.bzl", "check_bazel_version", "node_repositories", "yarn_install")
name = "com_github_bazelbuild_buildtools",
url = "https://github.com/bazelbuild/buildtools/archive/%s.zip" % BAZEL_BUILDTOOLS_VERSION,
strip_prefix = "buildtools-%s" % BAZEL_BUILDTOOLS_VERSION,
sha256 = "367c23a5fe7fc2a7cb57863d3718b4149f0e57426c48c8ad54c45348a0b53cc1",
)
http_archive( check_bazel_version("0.15.0")
name = "io_bazel_rules_go", node_repositories(
url = "https://github.com/bazelbuild/rules_go/releases/download/0.10.3/rules_go-0.10.3.tar.gz", package_json = ["//:package.json"],
sha256 = "feba3278c13cde8d67e341a837f69a029f698d7a27ddbb2a202be7a10b22142a", preserve_symlinks = True,
) )
load("@io_bazel_rules_go//go:def.bzl", "go_rules_dependencies", "go_register_toolchains") load("@io_bazel_rules_go//go:def.bzl", "go_rules_dependencies", "go_register_toolchains")
go_rules_dependencies() go_rules_dependencies()
go_register_toolchains() go_register_toolchains()
# Fetching the Bazel source code allows us to compile the Skylark linter load("@io_bazel_rules_webtesting//web:repositories.bzl", "browser_repositories", "web_test_repositories")
http_archive(
name = "io_bazel", web_test_repositories()
url = "https://github.com/bazelbuild/bazel/archive/5a35e72f9e97c06540c479f8c31512fb4656202f.zip", browser_repositories(
strip_prefix = "bazel-5a35e72f9e97c06540c479f8c31512fb4656202f", chromium = True,
sha256 = "ed33a52874c14e3b487fb50f390c541fab9c81a33d986d38fb01766a66dbcd21", firefox = True,
) )
# We have a source dependency on the Devkit repository, because it's built with load("@build_bazel_rules_typescript//:defs.bzl", "ts_setup_workspace")
# Bazel.
# This allows us to edit sources and have the effect appear immediately without ts_setup_workspace()
# re-packaging or "npm link"ing.
# Even better, things like aspects will visit the entire graph including load("@angular//:index.bzl", "ng_setup_workspace")
# ts_library rules in the devkit repository.
http_archive( ng_setup_workspace()
name = "angular_devkit",
url = "https://github.com/angular/devkit/archive/v0.3.1.zip", #
strip_prefix = "devkit-0.3.1", # Ask Bazel to manage these toolchain dependencies for us.
sha256 = "31d4b597fe9336650acf13df053c1c84dcbe9c29c6a833bcac3819cd3fd8cad3", # Bazel will run `yarn install` when one of these toolchains is requested during
# a build.
#
yarn_install(
name = "ts-api-guardian_runtime_deps",
package_json = "//tools/ts-api-guardian:package.json",
yarn_lock = "//tools/ts-api-guardian:yarn.lock",
) )
http_archive( yarn_install(
name = "org_brotli", name = "http-server_runtime_deps",
url = "https://github.com/google/brotli/archive/c6333e1e79fb62ea088443f192293f964409b04e.zip", package_json = "//tools/http-server:package.json",
strip_prefix = "brotli-c6333e1e79fb62ea088443f192293f964409b04e", yarn_lock = "//tools/http-server:yarn.lock",
sha256 = "3f781988dee7dd3bcce2bf238294663cfaaf3b6433505bdb762e24d0a284d1dc",
) )

View File

@ -52,8 +52,7 @@ export class BuildCleaner {
protected removeDir(dir: string) { protected removeDir(dir: string) {
try { try {
if (shell.test('-d', dir)) { if (shell.test('-d', dir)) {
// Undocumented signature (see https://github.com/shelljs/shelljs/pull/663). shell.chmod('-R', 'a+w', dir);
(shell as any).chmod('-R', 'a+w', dir);
shell.rm('-rf', dir); shell.rm('-rf', dir);
} }
} catch (err) { } catch (err) {

View File

@ -106,8 +106,7 @@ export class BuildCreator extends EventEmitter {
} }
try { try {
// Undocumented signature (see https://github.com/shelljs/shelljs/pull/663). shell.chmod('-R', 'a-w', outputDir);
(shell as any).chmod('-R', 'a-w', outputDir);
shell.rm('-f', inputFile); shell.rm('-f', inputFile);
resolve(); resolve();
} catch (err) { } catch (err) {

View File

@ -98,8 +98,7 @@ class Helper {
const prDir = this.getPrDir(pr, isPublic); const prDir = this.getPrDir(pr, isPublic);
if (fs.existsSync(prDir)) { if (fs.existsSync(prDir)) {
// Undocumented signature (see https://github.com/shelljs/shelljs/pull/663). shell.chmod('-R', 'a+w', prDir);
(shell as any).chmod('-R', 'a+w', prDir);
shell.rm('-rf', prDir); shell.rm('-rf', prDir);
} }
} }

View File

@ -8,7 +8,7 @@
"scripts": { "scripts": {
"prebuild": "yarn clean-dist", "prebuild": "yarn clean-dist",
"build": "tsc", "build": "tsc",
"build-watch": "yarn tsc --watch", "build-watch": "yarn build --watch",
"clean-dist": "node --eval \"require('shelljs').rm('-rf', 'dist')\"", "clean-dist": "node --eval \"require('shelljs').rm('-rf', 'dist')\"",
"dev": "concurrently --kill-others --raw --success first \"yarn build-watch\" \"yarn test-watch\"", "dev": "concurrently --kill-others --raw --success first \"yarn build-watch\" \"yarn test-watch\"",
"lint": "tslint --project tsconfig.json", "lint": "tslint --project tsconfig.json",
@ -33,7 +33,7 @@
"@types/jasmine": "^2.6.0", "@types/jasmine": "^2.6.0",
"@types/jsonwebtoken": "^7.2.3", "@types/jsonwebtoken": "^7.2.3",
"@types/node": "^8.0.30", "@types/node": "^8.0.30",
"@types/shelljs": "^0.7.4", "@types/shelljs": "^0.8.0",
"@types/supertest": "^2.0.3", "@types/supertest": "^2.0.3",
"concurrently": "^3.5.0", "concurrently": "^3.5.0",
"nodemon": "^1.12.1", "nodemon": "^1.12.1",

View File

@ -69,9 +69,9 @@
"@types/express-serve-static-core" "*" "@types/express-serve-static-core" "*"
"@types/mime" "*" "@types/mime" "*"
"@types/shelljs@^0.7.4": "@types/shelljs@^0.8.0":
version "0.7.4" version "0.8.0"
resolved "https://registry.yarnpkg.com/@types/shelljs/-/shelljs-0.7.4.tgz#137b5f31306eaff4de120ffe5b9d74b297809cfc" resolved "https://registry.yarnpkg.com/@types/shelljs/-/shelljs-0.8.0.tgz#0caa56b68baae4f68f44e0dd666ab30b098e3632"
dependencies: dependencies:
"@types/glob" "*" "@types/glob" "*"
"@types/node" "*" "@types/node" "*"

View File

@ -3,7 +3,7 @@
set -eux -o pipefail set -eux -o pipefail
exec 3>&1 exec 3>&1
echo "\n\n[`date`] - Updating the preview server..." echo -e "\n\n[`date`] - Updating the preview server..."
# Input # Input
readonly HOST_REPO_DIR=$1 readonly HOST_REPO_DIR=$1

Binary file not shown.

View File

@ -60,6 +60,8 @@ dist/
!rollup-config.js !rollup-config.js
aot-compiler/**/*.d.ts aot-compiler/**/*.d.ts
aot-compiler/**/*.factory.d.ts aot-compiler/**/*.factory.d.ts
upgrade-phonecat-2-hybrid/aot/**/*
!upgrade-phonecat-2-hybrid/aot/index.html
# i18n # i18n
!i18n/src/systemjs-text-plugin.js !i18n/src/systemjs-text-plugin.js

View File

@ -40,5 +40,7 @@ export class HighlightDirective {
// #docregion color-2 // #docregion color-2
@Input() appHighlight: string; @Input() appHighlight: string;
// #enddocregion color-2 // #enddocregion color-2
}
// #docregion
}
// #enddocregion

View File

@ -5,7 +5,7 @@
import { BrowserModule } from '@angular/platform-browser'; import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core'; import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms'; import { FormsModule } from '@angular/forms';
import { HttpModule } from '@angular/http'; import { HttpClientModule } from '@angular/common/http';
import { AppComponent } from './app.component'; import { AppComponent } from './app.component';
// #docregion directive-import // #docregion directive-import
@ -24,7 +24,7 @@ import { ItemDirective } from './item.directive';
imports: [ imports: [
BrowserModule, BrowserModule,
FormsModule, FormsModule,
HttpModule HttpClientModule
], ],
providers: [], providers: [],
bootstrap: [AppComponent] bootstrap: [AppComponent]

View File

@ -1,27 +0,0 @@
# MasterProject
This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 1.0.0-rc.0.
## Development server
Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The app will automatically reload if you change any of the source files.
## Code scaffolding
Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive/pipe/service/class/module`.
## Build
Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory. Use the `-prod` flag for a production build.
## Running unit tests
Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io).
## Running end-to-end tests
Run `ng e2e` to execute the end-to-end tests via [Protractor](http://www.protractortest.org/).
Before running the tests make sure you are serving the app via `ng serve`.
## Further help
To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI README](https://github.com/angular/angular-cli/blob/master/README.md).

View File

@ -5,18 +5,18 @@ import { Component, EventEmitter, Input, Output } from '@angular/core';
selector: 'app-voter', selector: 'app-voter',
template: ` template: `
<h4>{{name}}</h4> <h4>{{name}}</h4>
<button (click)="vote(true)" [disabled]="voted">Agree</button> <button (click)="vote(true)" [disabled]="didVote">Agree</button>
<button (click)="vote(false)" [disabled]="voted">Disagree</button> <button (click)="vote(false)" [disabled]="didVote">Disagree</button>
` `
}) })
export class VoterComponent { export class VoterComponent {
@Input() name: string; @Input() name: string;
@Output() onVoted = new EventEmitter<boolean>(); @Output() voted = new EventEmitter<boolean>();
voted = false; didVote = false;
vote(agreed: boolean) { vote(agreed: boolean) {
this.onVoted.emit(agreed); this.voted.emit(agreed);
this.voted = true; this.didVote = true;
} }
} }
// #enddocregion // #enddocregion

View File

@ -8,7 +8,7 @@ import { Component } from '@angular/core';
<h3>Agree: {{agreed}}, Disagree: {{disagreed}}</h3> <h3>Agree: {{agreed}}, Disagree: {{disagreed}}</h3>
<app-voter *ngFor="let voter of voters" <app-voter *ngFor="let voter of voters"
[name]="voter" [name]="voter"
(onVoted)="onVoted($event)"> (voted)="onVoted($event)">
</app-voter> </app-voter>
` `
}) })

View File

@ -1,7 +1,7 @@
// #docregion // #docregion
import { BrowserModule } from '@angular/platform-browser'; import { BrowserModule } from '@angular/platform-browser';
import { FormsModule } from '@angular/forms'; import { FormsModule } from '@angular/forms';
import { HttpModule } from '@angular/http'; import { HttpClientModule } from '@angular/common/http';
// import { AppRoutingModule } from './app-routing.module'; // import { AppRoutingModule } from './app-routing.module';
import { LocationStrategy, import { LocationStrategy,
@ -54,7 +54,7 @@ const c_components = [
imports: [ imports: [
BrowserModule, BrowserModule,
FormsModule, FormsModule,
HttpModule, HttpClientModule,
InMemoryWebApiModule.forRoot(HeroData) InMemoryWebApiModule.forRoot(HeroData)
// AppRoutingModule TODO: add routes // AppRoutingModule TODO: add routes
], ],

View File

@ -0,0 +1,69 @@
'use strict'; // necessary for es6 output in node
import { browser, by, element } from 'protractor';
/* tslint:disable:quotemark */
describe('Elements', () => {
const messageInput = element(by.css('input'));
const popupButtons = element.all(by.css('button'));
beforeEach(() => browser.get(''));
describe('popup component', () => {
const popupComponentButton = popupButtons.get(0);
const popupComponent = element(by.css('popup-component'));
const closeButton = popupComponent.element(by.css('button'));
it('should be displayed on button click', () => {
expect(popupComponent.isPresent()).toBe(false);
popupComponentButton.click();
expect(popupComponent.isPresent()).toBe(true);
});
it('should display the specified message', () => {
messageInput.clear();
messageInput.sendKeys('Angular rocks!');
popupComponentButton.click();
expect(popupComponent.getText()).toContain('Popup: Angular rocks!');
});
it('should be closed on "close" button click', () => {
popupComponentButton.click();
expect(popupComponent.isPresent()).toBe(true);
closeButton.click();
expect(popupComponent.isPresent()).toBe(false);
});
});
describe('popup element', () => {
const popupElementButton = popupButtons.get(1);
const popupElement = element(by.css('popup-element'));
const closeButton = popupElement.element(by.css('button'));
it('should be displayed on button click', () => {
expect(popupElement.isPresent()).toBe(false);
popupElementButton.click();
expect(popupElement.isPresent()).toBe(true);
});
it('should display the specified message', () => {
messageInput.clear();
messageInput.sendKeys('Angular rocks!');
popupElementButton.click();
expect(popupElement.getText()).toContain('Popup: Angular rocks!');
});
it('should be closed on "close" button click', () => {
popupElementButton.click();
expect(popupElement.isPresent()).toBe(true);
closeButton.click();
expect(popupElement.isPresent()).toBe(false);
});
});
});

View File

@ -0,0 +1,3 @@
{
"projectType": "elements"
}

View File

@ -1,6 +1,5 @@
// #docregion
import { Component, Injector } from '@angular/core'; import { Component, Injector } from '@angular/core';
import { createNgElementConstructor } from '../elements-dist'; import { createCustomElement } from '@angular/elements';
import { PopupService } from './popup.service'; import { PopupService } from './popup.service';
import { PopupComponent } from './popup.component'; import { PopupComponent } from './popup.component';
@ -8,19 +7,15 @@ import { PopupComponent } from './popup.component';
selector: 'app-root', selector: 'app-root',
template: ` template: `
<input #input value="Message"> <input #input value="Message">
<button (click)="popup.showAsComponent(input.value)"> <button (click)="popup.showAsComponent(input.value)">Show as component</button>
Show as component </button> <button (click)="popup.showAsElement(input.value)">Show as element</button>
<button (click)="popup.showAsElement(input.value)"> `,
Show as element </button>
`
}) })
export class AppComponent { export class AppComponent {
constructor(private injector: Injector, public popup: PopupService) { constructor(injector: Injector, public popup: PopupService) {
// on init, convert PopupComponent to a custom element // Convert `PopupComponent` to a custom element.
const PopupElement = const PopupElement = createCustomElement(PopupComponent, {injector});
createNgElementConstructor(PopupComponent, {injector: this.injector}); // Register the custom element with the browser.
// register the custom element with the browser. customElements.define('popup-element', PopupElement);
customElements.define('popup-element', PopupElement);
} }
} }

View File

@ -1,22 +1,21 @@
// #docregion import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser'; import { BrowserModule } from '@angular/platform-browser';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { NgModule } from '@angular/core';
import { AppComponent } from './app.component'; import { AppComponent } from './app.component';
import { PopupService } from './popup.service';
import { PopupComponent } from './popup.component'; import { PopupComponent } from './popup.component';
import { PopupService } from './popup.service';
// include the PopupService provider, // Include the `PopupService` provider,
// but exclude PopupComponent from compilation, // but exclude `PopupComponent` from compilation,
// because it will be added dynamically // because it will be added dynamically.
@NgModule({ @NgModule({
declarations: [AppComponent, PopupComponent],
imports: [BrowserModule, BrowserAnimationsModule], imports: [BrowserModule, BrowserAnimationsModule],
providers: [PopupService], providers: [PopupService],
declarations: [AppComponent, PopupComponent],
bootstrap: [AppComponent], bootstrap: [AppComponent],
entryComponents: [PopupComponent], entryComponents: [PopupComponent],
}) })
export class AppModule {
export class AppModule {} }

View File

@ -1,14 +1,14 @@
// #docregion
import { Component, EventEmitter, Input, Output } from '@angular/core'; import { Component, EventEmitter, Input, Output } from '@angular/core';
import { AnimationEvent } from '@angular/animations';
import { animate, state, style, transition, trigger } from '@angular/animations'; import { animate, state, style, transition, trigger } from '@angular/animations';
@Component({ @Component({
selector: 'my-popup', selector: 'my-popup',
template: 'Popup: {{message}}', template: `
<span>Popup: {{message}}</span>
<button (click)="closed.next()">&#x2716;</button>
`,
host: { host: {
'[@state]': 'state', '[@state]': 'state',
'(@state.done)': 'onAnimationDone($event)',
}, },
animations: [ animations: [
trigger('state', [ trigger('state', [
@ -27,13 +27,17 @@ import { animate, state, style, transition, trigger } from '@angular/animations'
height: 48px; height: 48px;
padding: 16px; padding: 16px;
display: flex; display: flex;
justify-content: space-between;
align-items: center; align-items: center;
border-top: 1px solid black; border-top: 1px solid black;
font-size: 24px; font-size: 24px;
} }
button {
border-radius: 50%;
}
`] `]
}) })
export class PopupComponent { export class PopupComponent {
private state: 'opened' | 'closed' = 'closed'; private state: 'opened' | 'closed' = 'closed';
@ -41,18 +45,10 @@ export class PopupComponent {
set message(message: string) { set message(message: string) {
this._message = message; this._message = message;
this.state = 'opened'; this.state = 'opened';
setTimeout(() => this.state = 'closed', 2000);
} }
get message(): string { return this._message; } get message(): string { return this._message; }
_message: string; _message: string;
@Output() @Output()
closed = new EventEmitter(); closed = new EventEmitter();
onAnimationDone(e: AnimationEvent) {
if (e.toState === 'closed') {
this.closed.next();
}
}
} }

View File

@ -1,9 +1,8 @@
// #docregion
import { ApplicationRef, ComponentFactoryResolver, Injectable, Injector } from '@angular/core'; import { ApplicationRef, ComponentFactoryResolver, Injectable, Injector } from '@angular/core';
import { NgElement, WithProperties } from '@angular/elements';
import { PopupComponent } from './popup.component'; import { PopupComponent } from './popup.component';
import { NgElementConstructor } from '../elements-dist';
@Injectable() @Injectable()
export class PopupService { export class PopupService {
@ -40,7 +39,7 @@ export class PopupService {
// This uses the new custom-element method to add the popup to the DOM. // This uses the new custom-element method to add the popup to the DOM.
showAsElement(message: string) { showAsElement(message: string) {
// Create element // Create element
const popupEl = document.createElement('popup-element'); const popupEl: NgElement & WithProperties<PopupComponent> = document.createElement('popup-element') as any;
// Listen to the close event // Listen to the close event
popupEl.addEventListener('closed', () => document.body.removeChild(popupEl)); popupEl.addEventListener('closed', () => document.body.removeChild(popupEl));

View File

@ -1,14 +1,12 @@
<!-- #docregion -->
<!DOCTYPE html> <!DOCTYPE html>
<html> <html lang="en">
<head> <head>
<base href="/">
<title>Angular With Webpack</title>
<meta charset="UTF-8"> <meta charset="UTF-8">
<base href="/">
<title>Elements</title>
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
</head> </head>
<body> <body>
<my-app>Loading...</my-app> <app-root></app-root>
</body> </body>
</html> </html>
<!-- #enddocregion -->

View File

@ -1,4 +1,3 @@
// tslint:disable:no-unused-variable
import { enableProdMode } from '@angular/core'; import { enableProdMode } from '@angular/core';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
@ -10,4 +9,3 @@ if (environment.production) {
} }
platformBrowserDynamic().bootstrapModule(AppModule); platformBrowserDynamic().bootstrapModule(AppModule);

View File

@ -16,6 +16,7 @@ describe('Form Validation Tests', function () {
tests('Template-Driven Form'); tests('Template-Driven Form');
bobTests(); bobTests();
crossValidationTests();
}); });
describe('Reactive form', () => { describe('Reactive form', () => {
@ -25,6 +26,7 @@ describe('Form Validation Tests', function () {
tests('Reactive Form'); tests('Reactive Form');
bobTests(); bobTests();
crossValidationTests();
}); });
}); });
@ -42,7 +44,8 @@ let page: {
powerOption: ElementFinder, powerOption: ElementFinder,
errorMessages: ElementArrayFinder, errorMessages: ElementArrayFinder,
heroFormButtons: ElementArrayFinder, heroFormButtons: ElementArrayFinder,
heroSubmitted: ElementFinder heroSubmitted: ElementFinder,
crossValidationErrorMessage: ElementFinder,
}; };
function getPage(sectionTag: string) { function getPage(sectionTag: string) {
@ -59,7 +62,8 @@ function getPage(sectionTag: string) {
powerOption: section.element(by.css('#power option')), powerOption: section.element(by.css('#power option')),
errorMessages: section.all(by.css('div.alert')), errorMessages: section.all(by.css('div.alert')),
heroFormButtons: buttons, heroFormButtons: buttons,
heroSubmitted: section.element(by.css('.submitted-message')) heroSubmitted: section.element(by.css('.submitted-message')),
crossValidationErrorMessage: section.element(by.css('.cross-validation-error-message')),
}; };
} }
@ -172,3 +176,29 @@ function bobTests() {
expectFormIsValid(); expectFormIsValid();
}); });
} }
function crossValidationTests() {
const emsg = 'Name cannot match alter ego.';
it(`should produce "${emsg}" error after setting name and alter ego to the same value`, function () {
page.nameInput.clear();
page.nameInput.sendKeys('Batman');
page.alterEgoInput.clear();
page.alterEgoInput.sendKeys('Batman');
expectFormIsInvalid();
expect(page.crossValidationErrorMessage.getText()).toBe(emsg);
});
it('should be ok again with different values', function () {
page.nameInput.clear();
page.nameInput.sendKeys('Batman');
page.alterEgoInput.clear();
page.alterEgoInput.sendKeys('Superman');
expectFormIsValid();
expect(page.crossValidationErrorMessage.isPresent()).toBe(false);
});
}

View File

@ -7,7 +7,7 @@ import { AppComponent } from './app.component';
import { HeroFormTemplateComponent } from './template/hero-form-template.component'; import { HeroFormTemplateComponent } from './template/hero-form-template.component';
import { HeroFormReactiveComponent } from './reactive/hero-form-reactive.component'; import { HeroFormReactiveComponent } from './reactive/hero-form-reactive.component';
import { ForbiddenValidatorDirective } from './shared/forbidden-name.directive'; import { ForbiddenValidatorDirective } from './shared/forbidden-name.directive';
import { IdentityRevealedValidatorDirective } from './shared/identity-revealed.directive';
@NgModule({ @NgModule({
imports: [ imports: [
@ -19,7 +19,8 @@ import { ForbiddenValidatorDirective } from './shared/forbidden-name.directive';
AppComponent, AppComponent,
HeroFormTemplateComponent, HeroFormTemplateComponent,
HeroFormReactiveComponent, HeroFormReactiveComponent,
ForbiddenValidatorDirective ForbiddenValidatorDirective,
IdentityRevealedValidatorDirective
], ],
bootstrap: [ AppComponent ] bootstrap: [ AppComponent ]
}) })

View File

@ -0,0 +1,42 @@
/* tslint:disable: member-ordering forin */
// #docplaster
// #docregion
import { Component, OnInit } from '@angular/core';
import { FormControl, FormGroup, Validators } from '@angular/forms';
import { forbiddenNameValidator } from '../shared/forbidden-name.directive';
@Component({
selector: 'app-hero-form-reactive',
templateUrl: './hero-form-reactive.component.html',
styleUrls: ['./hero-form-reactive.component.css'],
})
export class HeroFormReactiveComponent implements OnInit {
powers = ['Really Smart', 'Super Flexible', 'Weather Changer'];
hero = {name: 'Dr.', alterEgo: 'Dr. What', power: this.powers[0]};
heroForm: FormGroup;
// #docregion form-group
ngOnInit(): void {
// #docregion custom-validator
this.heroForm = new FormGroup({
'name': new FormControl(this.hero.name, [
Validators.required,
Validators.minLength(4),
forbiddenNameValidator(/bob/i) // <-- Here's how you pass in the custom validator.
]),
'alterEgo': new FormControl(this.hero.alterEgo),
'power': new FormControl(this.hero.power, Validators.required)
});
// #enddocregion custom-validator
}
get name() { return this.heroForm.get('name'); }
get power() { return this.heroForm.get('power'); }
// #enddocregion form-group
}
// #enddocregion

View File

@ -0,0 +1,5 @@
/* #docregion cross-validation-error-css */
.cross-validation-error input {
border-left: 5px solid red;
}
/* #enddocregion cross-validation-error-css */

View File

@ -7,33 +7,41 @@
<div [hidden]="formDir.submitted"> <div [hidden]="formDir.submitted">
<div class="form-group"> <div class="cross-validation" [class.cross-validation-error]="heroForm.errors?.identityRevealed && (heroForm.touched || heroForm.dirty)">
<div class="form-group">
<label for="name">Name</label> <label for="name">Name</label>
<!-- #docregion name-with-error-msg --> <!-- #docregion name-with-error-msg -->
<input id="name" class="form-control" <input id="name" class="form-control"
formControlName="name" required > formControlName="name" required >
<div *ngIf="name.invalid && (name.dirty || name.touched)" <div *ngIf="name.invalid && (name.dirty || name.touched)"
class="alert alert-danger"> class="alert alert-danger">
<div *ngIf="name.errors.required"> <div *ngIf="name.errors.required">
Name is required. Name is required.
</div> </div>
<div *ngIf="name.errors.minlength"> <div *ngIf="name.errors.minlength">
Name must be at least 4 characters long. Name must be at least 4 characters long.
</div> </div>
<div *ngIf="name.errors.forbiddenName"> <div *ngIf="name.errors.forbiddenName">
Name cannot be Bob. Name cannot be Bob.
</div>
</div> </div>
<!-- #enddocregion name-with-error-msg -->
</div> </div>
<!-- #enddocregion name-with-error-msg -->
</div>
<div class="form-group"> <div class="form-group">
<label for="alterEgo">Alter Ego</label> <label for="alterEgo">Alter Ego</label>
<input id="alterEgo" class="form-control" <input id="alterEgo" class="form-control"
formControlName="alterEgo" > formControlName="alterEgo" >
</div>
<!-- #docregion cross-validation-error-message -->
<div *ngIf="heroForm.errors?.identityRevealed && (heroForm.touched || heroForm.dirty)" class="cross-validation-error-message alert alert-danger">
Name cannot match alter ego.
</div>
<!-- #enddocregion cross-validation-error-message -->
</div> </div>
<div class="form-group"> <div class="form-group">

View File

@ -1,40 +1,36 @@
/* tslint:disable: member-ordering forin */ /* tslint:disable: member-ordering forin */
// #docplaster
// #docregion // #docregion
import { Component, OnInit } from '@angular/core'; import { Component, OnInit } from '@angular/core';
import { FormControl, FormGroup, Validators } from '@angular/forms'; import { FormControl, FormGroup, Validators } from '@angular/forms';
import { forbiddenNameValidator } from '../shared/forbidden-name.directive'; import { forbiddenNameValidator } from '../shared/forbidden-name.directive';
import { identityRevealedValidator } from '../shared/identity-revealed.directive';
@Component({ @Component({
selector: 'app-hero-form-reactive', selector: 'app-hero-form-reactive',
templateUrl: './hero-form-reactive.component.html' templateUrl: './hero-form-reactive.component.html',
styleUrls: ['./hero-form-reactive.component.css'],
}) })
export class HeroFormReactiveComponent implements OnInit { export class HeroFormReactiveComponent implements OnInit {
powers = ['Really Smart', 'Super Flexible', 'Weather Changer']; powers = ['Really Smart', 'Super Flexible', 'Weather Changer'];
hero = {name: 'Dr.', alterEgo: 'Dr. What', power: this.powers[0]}; hero = { name: 'Dr.', alterEgo: 'Dr. What', power: this.powers[0] };
heroForm: FormGroup; heroForm: FormGroup;
// #docregion form-group
ngOnInit(): void { ngOnInit(): void {
// #docregion custom-validator
this.heroForm = new FormGroup({ this.heroForm = new FormGroup({
'name': new FormControl(this.hero.name, [ 'name': new FormControl(this.hero.name, [
Validators.required, Validators.required,
Validators.minLength(4), Validators.minLength(4),
forbiddenNameValidator(/bob/i) // <-- Here's how you pass in the custom validator. forbiddenNameValidator(/bob/i)
]), ]),
'alterEgo': new FormControl(this.hero.alterEgo), 'alterEgo': new FormControl(this.hero.alterEgo),
'power': new FormControl(this.hero.power, Validators.required) 'power': new FormControl(this.hero.power, Validators.required)
}); }, { validators: identityRevealedValidator }); // <-- add custom validator at the FormGroup level
// #enddocregion custom-validator
} }
get name() { return this.heroForm.get('name'); } get name() { return this.heroForm.get('name'); }
get power() { return this.heroForm.get('power'); } get power() { return this.heroForm.get('power'); }
// #enddocregion form-group
} }
// #enddocregion

View File

@ -5,7 +5,7 @@ import { AbstractControl, NG_VALIDATORS, Validator, ValidatorFn, Validators } fr
// #docregion custom-validator // #docregion custom-validator
/** A hero's name can't match the given regular expression */ /** A hero's name can't match the given regular expression */
export function forbiddenNameValidator(nameRe: RegExp): ValidatorFn { export function forbiddenNameValidator(nameRe: RegExp): ValidatorFn {
return (control: AbstractControl): {[key: string]: any} => { return (control: AbstractControl): {[key: string]: any} | null => {
const forbidden = nameRe.test(control.value); const forbidden = nameRe.test(control.value);
return forbidden ? {'forbiddenName': {value: control.value}} : null; return forbidden ? {'forbiddenName': {value: control.value}} : null;
}; };
@ -22,7 +22,7 @@ export function forbiddenNameValidator(nameRe: RegExp): ValidatorFn {
export class ForbiddenValidatorDirective implements Validator { export class ForbiddenValidatorDirective implements Validator {
@Input('appForbiddenName') forbiddenName: string; @Input('appForbiddenName') forbiddenName: string;
validate(control: AbstractControl): {[key: string]: any} { validate(control: AbstractControl): {[key: string]: any} | null {
return this.forbiddenName ? forbiddenNameValidator(new RegExp(this.forbiddenName, 'i'))(control) return this.forbiddenName ? forbiddenNameValidator(new RegExp(this.forbiddenName, 'i'))(control)
: null; : null;
} }

View File

@ -0,0 +1,25 @@
// #docregion
import { Directive } from '@angular/core';
import { AbstractControl, FormGroup, NG_VALIDATORS, ValidationErrors, Validator, ValidatorFn } from '@angular/forms';
// #docregion cross-validation-validator
/** A hero's name can't match the hero's alter ego */
export const identityRevealedValidator: ValidatorFn = (control: FormGroup): ValidationErrors | null => {
const name = control.get('name');
const alterEgo = control.get('alterEgo');
return name && alterEgo && name.value === alterEgo.value ? { 'identityRevealed': true } : null;
};
// #enddocregion cross-validation-validator
// #docregion cross-validation-directive
@Directive({
selector: '[appIdentityRevealed]',
providers: [{ provide: NG_VALIDATORS, useExisting: IdentityRevealedValidatorDirective, multi: true }]
})
export class IdentityRevealedValidatorDirective implements Validator {
validate(control: AbstractControl): ValidationErrors {
return identityRevealedValidator(control)
}
}
// #enddocregion cross-validation-directive

View File

@ -0,0 +1,4 @@
/* #docregion */
.cross-validation-error input {
border-left: 5px solid red;
}

View File

@ -2,41 +2,48 @@
<div class="container"> <div class="container">
<h1>Template-Driven Form</h1> <h1>Template-Driven Form</h1>
<!-- #docregion form-tag--> <!-- #docregion cross-validation-register-validator -->
<form #heroForm="ngForm"> <form #heroForm="ngForm" appIdentityRevealed>
<!-- #enddocregion form-tag--> <!-- #enddocregion cross-validation-register-validator -->
<div [hidden]="heroForm.submitted"> <div [hidden]="heroForm.submitted">
<div class="cross-validation" [class.cross-validation-error]="heroForm.errors?.identityRevealed && (heroForm.touched || heroForm.dirty)">
<div class="form-group">
<label for="name">Name</label>
<!-- #docregion name-with-error-msg -->
<!-- #docregion name-input -->
<input id="name" name="name" class="form-control"
required minlength="4" appForbiddenName="bob"
[(ngModel)]="hero.name" #name="ngModel" >
<!-- #enddocregion name-input -->
<div class="form-group"> <div *ngIf="name.invalid && (name.dirty || name.touched)"
<label for="name">Name</label> class="alert alert-danger">
<!-- #docregion name-with-error-msg -->
<!-- #docregion name-input -->
<input id="name" name="name" class="form-control"
required minlength="4" appForbiddenName="bob"
[(ngModel)]="hero.name" #name="ngModel" >
<!-- #enddocregion name-input -->
<div *ngIf="name.invalid && (name.dirty || name.touched)" <div *ngIf="name.errors.required">
class="alert alert-danger"> 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="name.errors.required">
Name is required.
</div> </div>
<div *ngIf="name.errors.minlength"> <!-- #enddocregion name-with-error-msg -->
Name must be at least 4 characters long.
</div>
<div *ngIf="name.errors.forbiddenName">
Name cannot be Bob.
</div>
</div> </div>
<!-- #enddocregion name-with-error-msg -->
</div>
<div class="form-group"> <div class="form-group">
<label for="alterEgo">Alter Ego</label> <label for="alterEgo">Alter Ego</label>
<input id="alterEgo" class="form-control" <input id="alterEgo" class="form-control"
name="alterEgo" [(ngModel)]="hero.alterEgo" > name="alterEgo" [(ngModel)]="hero.alterEgo" >
</div>
<!-- #docregion cross-validation-error-message -->
<div *ngIf="heroForm.errors?.identityRevealed && (heroForm.touched || heroForm.dirty)" class="cross-validation-error-message alert alert-danger">
Name cannot match alter ego.
</div>
<!-- #enddocregion cross-validation-error-message -->
</div> </div>
<div class="form-group"> <div class="form-group">
@ -62,5 +69,4 @@
<button (click)="heroForm.resetForm({})">Add new hero</button> <button (click)="heroForm.resetForm({})">Add new hero</button>
</div> </div>
</form> </form>
</div> </div>

View File

@ -3,9 +3,11 @@
// #docregion // #docregion
import { Component } from '@angular/core'; import { Component } from '@angular/core';
// #docregion component
@Component({ @Component({
selector: 'app-hero-form-template', selector: 'app-hero-form-template',
templateUrl: './hero-form-template.component.html' templateUrl: './hero-form-template.component.html',
styleUrls: ['./hero-form-template.component.css'],
}) })
export class HeroFormTemplateComponent { export class HeroFormTemplateComponent {
@ -14,3 +16,4 @@ export class HeroFormTemplateComponent {
hero = {name: 'Dr.', alterEgo: 'Dr. What', power: this.powers[0]}; hero = {name: 'Dr.', alterEgo: 'Dr. What', power: this.powers[0]};
} }
// #enddocregion

View File

@ -2,6 +2,7 @@
"description": "Validation", "description": "Validation",
"files":[ "files":[
"!**/*.d.ts", "!**/*.d.ts",
"!**/*.js" "!**/*.js",
"!**/*.[1].*"
] ]
} }

View File

@ -38,8 +38,6 @@ export class MyCounterComponent implements OnChanges {
} }
} }
/***************************************/
@Component({ @Component({
selector: 'counter-parent', selector: 'counter-parent',
template: ` template: `

View File

@ -72,8 +72,6 @@ export class DoCheckComponent implements DoCheck {
} }
} }
/***************************************/
@Component({ @Component({
selector: 'do-check-parent', selector: 'do-check-parent',
templateUrl: './do-check-parent.component.html', templateUrl: './do-check-parent.component.html',

View File

@ -46,8 +46,6 @@ export class OnChangesComponent implements OnChanges {
reset() { this.changeLog = []; } reset() { this.changeLog = []; }
} }
/***************************************/
@Component({ @Component({
selector: 'on-changes-parent', selector: 'on-changes-parent',
templateUrl: './on-changes-parent.component.html', templateUrl: './on-changes-parent.component.html',

View File

@ -4,7 +4,8 @@ button {
font-size: 100%; font-size: 100%;
} }
code, .code { code,
.code {
background-color: #eee; background-color: #eee;
color: black; color: black;
font-family: Courier, sans-serif; font-family: Courier, sans-serif;
@ -21,14 +22,18 @@ div.code {
} }
hr { hr {
margin: 40px 0 margin: 40px 0;
} }
td, th { td,
th {
text-align: left; text-align: left;
vertical-align: top; vertical-align: top;
} }
/* #docregion p-span */ /* #docregion p-span */
p span { color: red; font-size: 70%; } p span {
color: red;
font-size: 70%;
}
/* #enddocregion p-span */ /* #enddocregion p-span */

View File

@ -132,7 +132,7 @@
<!-- #docregion select-span --> <!-- #docregion select-span -->
<select [(ngModel)]="hero"> <select [(ngModel)]="hero">
<span *ngFor="let h of heroes"> <span *ngFor="let h of heroes">
<span *ngIf="showSad || h?.emotion != 'sad'"> <span *ngIf="showSad || h?.emotion !== 'sad'">
<option [ngValue]="h">{{h.name}} ({{h?.emotion}})</option> <option [ngValue]="h">{{h.name}} ({{h?.emotion}})</option>
</span> </span>
</span> </span>
@ -147,7 +147,7 @@
<!-- #docregion select-ngcontainer --> <!-- #docregion select-ngcontainer -->
<select [(ngModel)]="hero"> <select [(ngModel)]="hero">
<ng-container *ngFor="let h of heroes"> <ng-container *ngFor="let h of heroes">
<ng-container *ngIf="showSad || h?.emotion != 'sad'"> <ng-container *ngIf="showSad || h?.emotion !== 'sad'">
<option [ngValue]="h">{{h.name}} ({{h?.emotion}})</option> <option [ngValue]="h">{{h.name}} ({{h?.emotion}})</option>
</ng-container> </ng-container>
</ng-container> </ng-container>

View File

@ -6,14 +6,15 @@ import { heroes } from './hero';
@Component({ @Component({
selector: 'my-app', selector: 'my-app',
templateUrl: './app.component.html', templateUrl: './app.component.html',
styleUrls: [ './app.component.css' ] styleUrls: ['./app.component.css']
}) })
export class AppComponent { export class AppComponent {
heroes = heroes; heroes = heroes;
hero = this.heroes[0]; hero = this.heroes[0];
heroTraits = [ 'honest', 'brave', 'considerate' ]; heroTraits = ['honest', 'brave', 'considerate'];
// flags for the table // flags for the table
attrDirs = true; attrDirs = true;
strucDirs = true; strucDirs = true;
divNgIf = false; divNgIf = false;

View File

@ -1,9 +1,9 @@
// #docregion // #docregion
import { NgModule } from '@angular/core'; import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms'; import { FormsModule } from '@angular/forms';
import { BrowserModule } from '@angular/platform-browser'; import { BrowserModule } from '@angular/platform-browser';
import { AppComponent } from './app.component'; import { AppComponent } from './app.component';
import { ContentComponent } from './content.component'; import { ContentComponent } from './content.component';
import { heroComponents } from './hero.components'; import { heroComponents } from './hero.components';

View File

@ -1,5 +1,6 @@
// #docregion // #docregion
import { Component, Input } from '@angular/core'; import { Component, Input } from '@angular/core';
import { Hero } from './hero'; import { Hero } from './hero';
@Component({ @Component({
@ -33,11 +34,15 @@ export class ConfusedHeroComponent {
export class UnknownHeroComponent { export class UnknownHeroComponent {
@Input() hero: Hero; @Input() hero: Hero;
get message() { get message() {
return this.hero && this.hero.name ? return this.hero && this.hero.name
`${this.hero.name} is strange and mysterious.` : ? `${this.hero.name} is strange and mysterious.`
'Are you feeling indecisive?'; : 'Are you feeling indecisive?';
} }
} }
export const heroComponents = export const heroComponents = [
[ HappyHeroComponent, SadHeroComponent, ConfusedHeroComponent, UnknownHeroComponent ]; HappyHeroComponent,
SadHeroComponent,
ConfusedHeroComponent,
UnknownHeroComponent
];

View File

@ -6,8 +6,8 @@ export class Hero {
} }
export const heroes: Hero[] = [ export const heroes: Hero[] = [
{ id: 1, name: 'Mr. Nice', emotion: 'happy'}, { id: 1, name: 'Mr. Nice', emotion: 'happy' },
{ id: 2, name: 'Narco', emotion: 'sad' }, { id: 2, name: 'Narco', emotion: 'sad' },
{ id: 3, name: 'Windstorm', emotion: 'confused' }, { id: 3, name: 'Windstorm', emotion: 'confused' },
{ id: 4, name: 'Magneta'} { id: 4, name: 'Magneta' }
]; ];

View File

@ -1,7 +1,5 @@
import { BrowserModule } from '@angular/platform-browser'; import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core'; import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { HttpModule } from '@angular/http';
/* App Root */ /* App Root */
import { AppComponent } from './app.component'; import { AppComponent } from './app.component';

View File

@ -15,7 +15,7 @@ function sequenceSubscriber(observer) {
if (idx === arr.length - 1) { if (idx === arr.length - 1) {
observer.complete(); observer.complete();
} else { } else {
doSequence(arr, idx++); doSequence(arr, ++idx);
} }
}, 1000); }, 1000);
} }
@ -95,7 +95,7 @@ function multicastSequenceSubscriber() {
}, },
complete() { complete() {
// Notify all complete callbacks // Notify all complete callbacks
observers.forEach(obs => obs.complete()); observers.slice(0).forEach(obs => obs.complete());
} }
}, seq, 0); }, seq, 0);
} }
@ -121,13 +121,13 @@ function doSequence(observer, arr, idx) {
if (idx === arr.length - 1) { if (idx === arr.length - 1) {
observer.complete(); observer.complete();
} else { } else {
doSequence(observer, arr, idx++); doSequence(observer, arr, ++idx);
} }
}, 1000); }, 1000);
} }
// Create a new Observable that will deliver the above sequence // Create a new Observable that will deliver the above sequence
const multicastSequence = new Observable(multicastSequenceSubscriber); const multicastSequence = new Observable(multicastSequenceSubscriber());
// Subscribe starts the clock, and begins to emit after 1 second // Subscribe starts the clock, and begins to emit after 1 second
multicastSequence.subscribe({ multicastSequence.subscribe({

View File

@ -2,7 +2,7 @@
import { NgModule } from '@angular/core'; import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser'; import { BrowserModule } from '@angular/platform-browser';
import { FormsModule } from '@angular/forms'; import { FormsModule } from '@angular/forms';
import { HttpClientModule } from '@angular//common/http'; import { HttpClientModule } from '@angular/common/http';
import { AppComponent } from './app.component'; import { AppComponent } from './app.component';
import { import {

View File

@ -1,3 +0,0 @@
[1030/162525.401:ERROR:process_reader_win.cc(123)] NtOpenThread: {Acceso denegado} Un proceso ha solicitado acceso a un objeto, pero no se le han concedido esos derechos de acceso. (0xc0000022)
[1030/162525.402:ERROR:exception_snapshot_win.cc(87)] thread ID 26896 not found in process
[1030/162525.402:WARNING:crash_report_exception_handler.cc(62)] ProcessSnapshotWin::Initialize failed

File diff suppressed because it is too large Load Diff

View File

@ -1,24 +0,0 @@
{
"description": "Angular Reactive Forms (final)",
"files":[
"src/styles.css",
"src/app/app.component.ts",
"src/app/app.component.html",
"src/app/app.component.css",
"src/app/app.module.ts",
"src/app/data-model.ts",
"src/app/hero.service.ts",
"src/app/hero-detail/hero-detail.component.html",
"src/app/hero-detail/hero-detail.component.ts",
"src/app/hero-detail/hero-detail.component.css",
"src/app/hero-list/hero-list.component.html",
"src/app/hero-list/hero-list.component.ts",
"src/app/hero-list/hero-list.component.css",
"src/main-final.ts",
"src/index-final.html"
],
"main": "src/index-final.html",
"tags": ["reactive", "forms"]
}

View File

@ -1,4 +1,10 @@
<div class="container"> <!-- #docplaster -->
<h1>Reactive Forms</h1> <h1>Reactive Forms</h1>
<app-hero-detail></app-hero-detail>
</div> <!-- #docregion app-name-editor-->
<app-name-editor></app-name-editor>
<!-- #enddocregion app-name-editor-->
<!-- #docregion app-profile-editor -->
<app-profile-editor></app-profile-editor>
<!-- #enddocregion app-profile-editor -->

View File

@ -1,4 +1,17 @@
<div class="container"> <!-- #docplaster -->
<h1>Reactive Forms</h1> <!-- #docregion app-name-editor -->
<app-hero-list></app-hero-list> <h1>Reactive Forms</h1>
</div>
<!-- #enddocregion app-name-editor -->
<nav>
<a (click)="toggleEditor('name')">Name Editor</a>
<a (click)="toggleEditor('profile')">Profile Editor</a>
</nav>
<!-- #docregion app-name-editor -->
<app-name-editor *ngIf="showNameEditor"></app-name-editor>
<!-- #enddocregion app-name-editor -->
<!-- #docregion app-profile-editor -->
<app-profile-editor *ngIf="showProfileEditor"></app-profile-editor>
<!-- #enddocregion app-profile-editor -->

View File

@ -0,0 +1,27 @@
import { TestBed, async } from '@angular/core/testing';
import { AppComponent } from './app.component';
describe('AppComponent', () => {
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [
AppComponent
],
}).compileComponents();
}));
it('should create the app', async(() => {
const fixture = TestBed.createComponent(AppComponent);
const app = fixture.debugElement.componentInstance;
expect(app).toBeTruthy();
}));
it(`should have as title 'app'`, async(() => {
const fixture = TestBed.createComponent(AppComponent);
const app = fixture.debugElement.componentInstance;
expect(app.title).toEqual('app');
}));
it('should render title in a h1 tag', async(() => {
const fixture = TestBed.createComponent(AppComponent);
fixture.detectChanges();
const compiled = fixture.debugElement.nativeElement;
expect(compiled.querySelector('h1').textContent).toContain('Welcome to reactive-forms!');
}));
});

View File

@ -1,9 +1,24 @@
// #docregion
import { Component } from '@angular/core'; import { Component } from '@angular/core';
export type EditorType = 'name' | 'profile';
@Component({ @Component({
selector: 'app-root', selector: 'app-root',
templateUrl: './app.component.html', templateUrl: './app.component.html',
styleUrls: ['./app.component.css'] styleUrls: ['./app.component.css']
}) })
export class AppComponent { } export class AppComponent {
editor: EditorType = 'name';
get showNameEditor() {
return this.editor === 'name';
}
get showProfileEditor() {
return this.editor === 'profile';
}
toggleEditor(type: EditorType) {
this.editor = type;
}
}

View File

@ -1,45 +1,34 @@
// #docplaster // #docplaster
// #docregion import { BrowserModule } from '@angular/platform-browser';
// #docregion v1 import { NgModule } from '@angular/core';
import { NgModule } from '@angular/core'; // #docregion imports
import { BrowserModule } from '@angular/platform-browser'; import { ReactiveFormsModule } from '@angular/forms';
import { ReactiveFormsModule } from '@angular/forms'; // <-- #1 import module
import { AppComponent } from './app.component'; // #enddocregion imports
import { HeroDetailComponent } from './hero-detail/hero-detail.component'; import { AppComponent } from './app.component';
// #enddocregion v1 import { NameEditorComponent } from './name-editor/name-editor.component';
// #docregion hero-service-list import { ProfileEditorComponent } from './profile-editor/profile-editor.component';
// add JavaScript imports
import { HeroListComponent } from './hero-list/hero-list.component';
import { HeroService } from './hero.service';
// #docregion v1
// #docregion imports
@NgModule({ @NgModule({
// #enddocregion imports
declarations: [ declarations: [
AppComponent, AppComponent,
HeroDetailComponent, NameEditorComponent,
// #enddocregion v1 ProfileEditorComponent
HeroListComponent // <--declare HeroListComponent
// #docregion v1
], ],
// #enddocregion hero-service-list // #docregion imports
imports: [ imports: [
// #enddocregion imports
BrowserModule, BrowserModule,
ReactiveFormsModule // <-- #2 add to @NgModule imports // #docregion imports
// other imports ...
ReactiveFormsModule
], ],
// #enddocregion v1 // #enddocregion imports
// export for the DemoModule providers: [],
// #docregion hero-service-list bootstrap: [AppComponent]
// ... // #docregion imports
exports: [
AppComponent,
HeroDetailComponent,
HeroListComponent // <-- export HeroListComponent
],
providers: [ HeroService ], // <-- provide HeroService
// #enddocregion hero-service-list
// #docregion v1
bootstrap: [ AppComponent ]
}) })
export class AppModule { } export class AppModule { }
// #enddocregion v1 // #enddocregion imports

View File

@ -1,40 +0,0 @@
// #docregion
// #docregion model-classes
export class Hero {
id = 0;
name = '';
addresses: Address[];
}
export class Address {
street = '';
city = '';
state = '';
zip = '';
}
// #enddocregion model-classes
export const heroes: Hero[] = [
{
id: 1,
name: 'Whirlwind',
addresses: [
{street: '123 Main', city: 'Anywhere', state: 'CA', zip: '94801'},
{street: '456 Maple', city: 'Somewhere', state: 'VA', zip: '23226'},
]
},
{
id: 2,
name: 'Bombastic',
addresses: [
{street: '789 Elm', city: 'Smallville', state: 'OH', zip: '04501'},
]
},
{
id: 3,
name: 'Magneta',
addresses: [ ]
},
];
export const states = ['CA', 'MD', 'OH', 'VA'];

View File

@ -1,40 +0,0 @@
<div class="container">
<h1>Reactive Forms</h1>
<h4><i>Pick a demo:</i>
<select [selectedIndex]="demo - 1" (change)="selectDemo($event.target.selectedIndex)">
<option *ngFor="let demo of demos">{{demo}}</option>
</select>
</h4>
<hr>
<div class="demo">
<app-hero-list *ngIf="demo===final"></app-hero-list>
<app-hero-detail-1 *ngIf="demo===1"></app-hero-detail-1>
<app-hero-detail-2 *ngIf="demo===2"></app-hero-detail-2>
<app-hero-detail-3 *ngIf="demo===3"></app-hero-detail-3>
<app-hero-detail-4 *ngIf="demo===4"></app-hero-detail-4>
<app-hero-detail-5 *ngIf="demo===5"></app-hero-detail-5>
<div *ngIf="demo >= 6 && demo !== final" >
<h3 *ngIf="isLoading"><i>Loading heroes ... </i></h3>
<h3 *ngIf="!isLoading">Select a hero:</h3>
<nav>
<button (click)="getHeroes()" class="btn btn-primary">Refresh</button>
<a *ngFor="let hero of heroes | async" (click)="select(hero)">{{hero.name}}</a>
</nav>
<div *ngIf="selectedHero">
<hr>
<h2>Hero Detail</h2>
<h3>Editing: {{selectedHero.name}}</h3>
<app-hero-detail-6 [hero]=selectedHero *ngIf="demo===6"></app-hero-detail-6>
<app-hero-detail-7 [hero]=selectedHero *ngIf="demo===7"></app-hero-detail-7>
<app-hero-detail-8 [hero]=selectedHero *ngIf="demo===8"></app-hero-detail-8>
</div>
</div>
</div>
</div>

View File

@ -1,49 +0,0 @@
/* tslint:disable:member-ordering */
import { Component } from '@angular/core';
import { Observable } from 'rxjs';
import { finalize } from 'rxjs/operators';
import { Hero } from './data-model';
import { HeroService } from './hero.service';
@Component({
selector: 'app-root',
templateUrl: './demo.component.html'
})
export class DemoComponent {
demos: string[] = [
'Just a FormControl',
'FormControl in a FormGroup',
'Simple FormBuilder group',
'Group with multiple controls',
'Nested FormBuilder group',
'PatchValue',
'SetValue',
'FormArray',
'Final'].map(n => n + ' Demo');
final = this.demos.length;
demo = this.final; // current demo
heroes: Observable<Hero[]>;
isLoading = false;
selectedHero: Hero;
constructor(private heroService: HeroService) { }
getHeroes() {
this.isLoading = true;
this.heroes = this.heroService.getHeroes().pipe(
finalize(() => this.isLoading = false)
);
this.selectedHero = undefined;
}
select(hero: Hero) { this.selectedHero = hero; }
selectDemo(demo: number) {
this.demo = demo + 1;
this.getHeroes();
}
}

View File

@ -1,33 +0,0 @@
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { ReactiveFormsModule } from '@angular/forms';
import { AppModule } from './app.module';
import { DemoComponent } from './demo.component';
import { HeroDetailComponent1 } from './hero-detail/hero-detail-1.component';
import { HeroDetailComponent2 } from './hero-detail/hero-detail-2.component';
import { HeroDetailComponent3 } from './hero-detail/hero-detail-3.component';
import { HeroDetailComponent4 } from './hero-detail/hero-detail-4.component';
import { HeroDetailComponent5 } from './hero-detail/hero-detail-5.component';
import { HeroDetailComponent6 } from './hero-detail/hero-detail-6.component';
import { HeroDetailComponent7 } from './hero-detail/hero-detail-7.component';
import { HeroDetailComponent8 } from './hero-detail/hero-detail-8.component';
@NgModule({
imports: [
BrowserModule,
ReactiveFormsModule,
AppModule,
],
declarations: [ DemoComponent,
HeroDetailComponent1,
HeroDetailComponent2,
HeroDetailComponent3,
HeroDetailComponent4,
HeroDetailComponent5,
HeroDetailComponent6,
HeroDetailComponent7,
HeroDetailComponent8],
bootstrap: [ DemoComponent ]
})
export class DemoModule { }

View File

@ -1,8 +0,0 @@
<!-- #docregion simple-control-->
<h2>Hero Detail</h2>
<h3><i>Just a FormControl</i></h3>
<label class="center-block">Name:
<input class="form-control" [formControl]="name">
</label>
<!-- #enddocregion simple-control-->

View File

@ -1,15 +0,0 @@
/* tslint:disable:component-class-suffix */
import { Component } from '@angular/core';
// #docregion import
import { FormControl } from '@angular/forms';
// #enddocregion import
@Component({
selector: 'app-hero-detail-1',
templateUrl: './hero-detail-1.component.html'
})
// #docregion v1
export class HeroDetailComponent1 {
name = new FormControl();
}

View File

@ -1,18 +0,0 @@
<!-- #docregion basic-form-->
<h2>Hero Detail</h2>
<h3><i>FormControl in a FormGroup</i></h3>
<form [formGroup]="heroForm">
<div class="form-group">
<label class="center-block">Name:
<input class="form-control" formControlName="name">
</label>
</div>
</form>
<!-- #enddocregion basic-form-->
<!-- #docregion form-value-json -->
<p>Form value: {{ heroForm.value | json }}</p>
<!-- #enddocregion form-value-json -->

View File

@ -1,17 +0,0 @@
/* tslint:disable:component-class-suffix */
// #docregion imports
import { Component } from '@angular/core';
import { FormControl, FormGroup } from '@angular/forms';
// #enddocregion imports
@Component({
selector: 'app-hero-detail-2',
templateUrl: './hero-detail-2.component.html'
})
// #docregion v2
export class HeroDetailComponent2 {
heroForm = new FormGroup ({
name: new FormControl()
});
}
// #enddocregion v2

View File

@ -1,16 +0,0 @@
<!-- #docregion basic-form-->
<h2>Hero Detail</h2>
<h3><i>A FormGroup with a single FormControl using FormBuilder</i></h3>
<form [formGroup]="heroForm">
<div class="form-group">
<label class="center-block">Name:
<input class="form-control" formControlName="name">
</label>
</div>
</form>
<!-- #enddocregion basic-form-->
<!-- #docregion form-value-json -->
<p>Form value: {{ heroForm.value | json }}</p>
<p>Form status: {{ heroForm.status | json }}</p>
<!-- #enddocregion form-value-json -->

View File

@ -1,27 +0,0 @@
/* tslint:disable:component-class-suffix */
// #docregion imports
import { Component } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
// #enddocregion imports
@Component({
selector: 'app-hero-detail-3',
templateUrl: './hero-detail-3.component.html'
})
// #docregion v3
export class HeroDetailComponent3 {
heroForm: FormGroup; // <--- heroForm is of type FormGroup
constructor(private fb: FormBuilder) { // <--- inject FormBuilder
this.createForm();
}
createForm() {
// #docregion required
this.heroForm = this.fb.group({
name: ['', Validators.required ],
});
// #enddocregion required
}
}
// #enddocregion v3

View File

@ -1,25 +0,0 @@
/* tslint:disable:component-class-suffix */
// #docregion imports
import { Component } from '@angular/core';
import { FormBuilder, FormGroup } from '@angular/forms';
// #enddocregion imports
@Component({
selector: 'app-hero-detail-3',
templateUrl: './hero-detail-3.component.html'
})
// #docregion v3a
export class HeroDetailComponent3 {
heroForm: FormGroup; // <--- heroForm is of type FormGroup
constructor(private fb: FormBuilder) { // <--- inject FormBuilder
this.createForm();
}
createForm() {
this.heroForm = this.fb.group({
name: '', // <--- the FormControl called "name"
});
}
}
// #enddocregion v3a

View File

@ -1,46 +0,0 @@
<!-- #docregion -->
<h2>Hero Detail</h2>
<h3><i>A FormGroup with multiple FormControls</i></h3>
<form [formGroup]="heroForm">
<div class="form-group">
<label class="center-block">Name:
<input class="form-control" formControlName="name">
</label>
</div>
<div class="form-group">
<label class="center-block">Street:
<input class="form-control" formControlName="street">
</label>
</div>
<div class="form-group">
<label class="center-block">City:
<input class="form-control" formControlName="city">
</label>
</div>
<div class="form-group">
<label class="center-block">State:
<select class="form-control" formControlName="state">
<option *ngFor="let state of states" [value]="state">{{state}}</option>
</select>
</label>
</div>
<div class="form-group">
<label class="center-block">Zip Code:
<input class="form-control" formControlName="zip">
</label>
</div>
<div class="form-group radio">
<h4>Super power:</h4>
<label class="center-block"><input type="radio" formControlName="power" value="flight">Flight</label>
<label class="center-block"><input type="radio" formControlName="power" value="x-ray vision">X-ray vision</label>
<label class="center-block"><input type="radio" formControlName="power" value="strength">Strength</label>
</div>
<div class="checkbox">
<label class="center-block">
<input type="checkbox" formControlName="sidekick">I have a sidekick.
</label>
</div>
</form>
<p>Form value: {{ heroForm.value | json }}</p>

View File

@ -1,34 +0,0 @@
/* tslint:disable:component-class-suffix */
// #docregion imports
import { Component } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { states } from '../data-model';
// #enddocregion imports
@Component({
selector: 'app-hero-detail-4',
templateUrl: './hero-detail-4.component.html'
})
// #docregion v4
export class HeroDetailComponent4 {
heroForm: FormGroup;
states = states;
constructor(private fb: FormBuilder) {
this.createForm();
}
createForm() {
this.heroForm = this.fb.group({
name: ['', Validators.required ],
street: '',
city: '',
state: '',
zip: '',
power: '',
sidekick: ''
});
}
}
// #enddocregion v4

View File

@ -1,56 +0,0 @@
<form [formGroup]="heroForm">
<div class="form-group">
<label class="center-block">Name:
<input class="form-control" formControlName="name">
</label>
</div>
<!-- #docregion add-group-->
<div formGroupName="address" class="well well-lg">
<h4>Secret Lair</h4>
<div class="form-group">
<label class="center-block">Street:
<input class="form-control" formControlName="street">
</label>
</div>
<div class="form-group">
<label class="center-block">City:
<input class="form-control" formControlName="city">
</label>
</div>
<div class="form-group">
<label class="center-block">State:
<select class="form-control" formControlName="state">
<option *ngFor="let state of states" [value]="state">{{state}}</option>
</select>
</label>
</div>
<div class="form-group">
<label class="center-block">Zip Code:
<input class="form-control" formControlName="zip">
</label>
</div>
</div>
<!-- #enddocregion add-group-->
<div class="form-group radio">
<h4>Super power:</h4>
<label class="center-block"><input type="radio" formControlName="power" value="flight">Flight</label>
<label class="center-block"><input type="radio" formControlName="power" value="x-ray vision">X-ray vision</label>
<label class="center-block"><input type="radio" formControlName="power" value="strength">Strength</label>
</div>
<div class="checkbox">
<label class="center-block">
<input type="checkbox" formControlName="sidekick">I have a sidekick.
</label>
</div>
</form>
<p>heroForm value: {{ heroForm.value | json}}</p>
<h4>Extra info for the curious:</h4>
<!-- #docregion inspect-value -->
<p>Name value: {{ heroForm.get('name').value }}</p>
<!-- #enddocregion inspect-value -->
<!-- #docregion inspect-child-control -->
<p>Street value: {{ heroForm.get('address.street').value}}</p>
<!-- #enddocregion inspect-child-control -->

View File

@ -1,35 +0,0 @@
/* tslint:disable:component-class-suffix */
import { Component } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { states } from '../data-model';
@Component({
selector: 'app-hero-detail-5',
templateUrl: './hero-detail-5.component.html'
})
// #docregion v5
export class HeroDetailComponent5 {
heroForm: FormGroup;
states = states;
constructor(private fb: FormBuilder) {
this.createForm();
}
createForm() {
this.heroForm = this.fb.group({ // <-- the parent FormGroup
name: ['', Validators.required ],
address: this.fb.group({ // <-- the child FormGroup
street: '',
city: '',
state: '',
zip: ''
}),
power: '',
sidekick: ''
});
}
}
// #enddocregion v5

View File

@ -1,46 +0,0 @@
<!-- #docregion -->
<h2>Hero Detail</h2>
<h3><i>PatchValue to initialize a value</i></h3>
<form [formGroup]="heroForm">
<div class="form-group">
<label class="center-block">Name:
<input class="form-control" formControlName="name">
</label>
</div>
<div class="form-group">
<label class="center-block">Street:
<input class="form-control" formControlName="street">
</label>
</div>
<div class="form-group">
<label class="center-block">City:
<input class="form-control" formControlName="city">
</label>
</div>
<div class="form-group">
<label class="center-block">State:
<select class="form-control" formControlName="state">
<option *ngFor="let state of states" [value]="state">{{state}}</option>
</select>
</label>
</div>
<div class="form-group">
<label class="center-block">Zip Code:
<input class="form-control" formControlName="zip">
</label>
</div>
<div class="form-group radio">
<h4>Super power:</h4>
<label class="center-block"><input type="radio" formControlName="power" value="flight">Flight</label>
<label class="center-block"><input type="radio" formControlName="power" value="x-ray vision">X-ray vision</label>
<label class="center-block"><input type="radio" formControlName="power" value="strength">Strength</label>
</div>
<div class="checkbox">
<label class="center-block">
<input type="checkbox" formControlName="sidekick">I have a sidekick.
</label>
</div>
</form>
<p>Form value: {{ heroForm.value | json }}</p>

View File

@ -1,66 +0,0 @@
/* tslint:disable:component-class-suffix */
// #docregion import-input
import { Component, Input, OnChanges } from '@angular/core';
// #enddocregion import-input
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
// #docregion import-hero
import { Hero, states } from '../data-model';
// #enddocregion import-hero
////////// 6 ////////////////////
@Component({
selector: 'app-hero-detail-6',
templateUrl: './hero-detail-5.component.html'
})
// #docregion v6
export class HeroDetailComponent6 implements OnChanges {
// #docregion hero
@Input() hero: Hero;
// #enddocregion hero
heroForm: FormGroup;
states = states;
constructor(private fb: FormBuilder) {
this.createForm();
}
createForm() {
// #docregion hero-form-model
this.heroForm = this.fb.group({
name: ['', Validators.required ],
address: this.fb.group({
street: '',
city: '',
state: '',
zip: ''
}),
power: '',
sidekick: ''
});
// #enddocregion hero-form-model
}
// #docregion patch-value-on-changes
ngOnChanges() { // <-- call rebuildForm in ngOnChanges
this.rebuildForm();
}
// #enddocregion patch-value-on-changes
// #docregion patch-value-rebuildform
rebuildForm() { // <-- wrap patchValue in rebuildForm
this.heroForm.reset();
// #docregion patch-value
this.heroForm.patchValue({
name: this.hero.name
});
// #enddocregion patch-value
}
// #enddocregion patch-value-rebuildform
}
// #enddocregion v6

View File

@ -1,46 +0,0 @@
<!-- #docregion -->
<h2>Hero Detail</h2>
<h3><i>A FormGroup with multiple FormControls</i></h3>
<form [formGroup]="heroForm">
<div class="form-group">
<label class="center-block">Name:
<input class="form-control" formControlName="name">
</label>
</div>
<div class="form-group">
<label class="center-block">Street:
<input class="form-control" formControlName="street">
</label>
</div>
<div class="form-group">
<label class="center-block">City:
<input class="form-control" formControlName="city">
</label>
</div>
<div class="form-group">
<label class="center-block">State:
<select class="form-control" formControlName="state">
<option *ngFor="let state of states" [value]="state">{{state}}</option>
</select>
</label>
</div>
<div class="form-group">
<label class="center-block">Zip Code:
<input class="form-control" formControlName="zip">
</label>
</div>
<div class="form-group radio">
<h4>Super power:</h4>
<label class="center-block"><input type="radio" formControlName="power" value="flight">Flight</label>
<label class="center-block"><input type="radio" formControlName="power" value="x-ray vision">X-ray vision</label>
<label class="center-block"><input type="radio" formControlName="power" value="strength">Strength</label>
</div>
<div class="checkbox">
<label class="center-block">
<input type="checkbox" formControlName="sidekick">I have a sidekick.
</label>
</div>
</form>
<p>Form value: {{ heroForm.value | json }}</p>

View File

@ -1,68 +0,0 @@
/* tslint:disable:component-class-suffix */
// #docplaster
// #docregion imports
import { Component, Input, OnChanges } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
// #docregion import-address
import { Address, Hero, states } from '../data-model';
// #enddocregion import-address
// #enddocregion imports
@Component({
selector: 'app-hero-detail-7',
templateUrl: './hero-detail-5.component.html'
})
// #docregion v7
export class HeroDetailComponent7 implements OnChanges {
@Input() hero: Hero;
heroForm: FormGroup;
states = states;
constructor(private fb: FormBuilder) {
this.createForm();
}
createForm() {
// #docregion address-form-group
this.heroForm = this.fb.group({
name: ['', Validators.required ],
address: this.fb.group(new Address()), // <-- a FormGroup with a new address
power: '',
sidekick: ''
});
// #enddocregion address-form-group
}
// #docregion ngOnChanges
ngOnChanges() {
this.rebuildForm();
}
// #enddocregion ngOnChanges
// #docregion rebuildForm
rebuildForm() {
this.heroForm.reset({
name: this.hero.name,
// #docregion set-value-address
address: this.hero.addresses[0] || new Address()
// #enddocregion set-value-address
});
}
// #enddocregion rebuildForm
/* First version of rebuildForm */
rebuildForm1() {
// #docregion reset
this.heroForm.reset();
// #enddocregion reset
// #docregion set-value
this.heroForm.setValue({
name: this.hero.name,
address: this.hero.addresses[0] || new Address()
});
// #enddocregion set-value
}
}

View File

@ -1,72 +0,0 @@
<!-- #docplaster-->
<h3><i>Using FormArray to add groups</i></h3>
<form [formGroup]="heroForm">
<p>Form Changed: {{ heroForm.dirty }}</p>
<div class="form-group">
<label class="center-block">Name:
<input class="form-control" formControlName="name">
</label>
</div>
<!-- #docregion form-array-->
<!-- #docregion form-array-skeleton -->
<!-- #docregion form-array-name -->
<div formArrayName="secretLairs" class="well well-lg">
<!-- #enddocregion form-array-name -->
<div *ngFor="let address of secretLairs.controls; let i=index" [formGroupName]="i" >
<!-- The repeated address template -->
<!-- #enddocregion form-array-skeleton -->
<h4>Address #{{i + 1}}</h4>
<div style="margin-left: 1em;">
<div class="form-group">
<label class="center-block">Street:
<input class="form-control" formControlName="street">
</label>
</div>
<div class="form-group">
<label class="center-block">City:
<input class="form-control" formControlName="city">
</label>
</div>
<div class="form-group">
<label class="center-block">State:
<select class="form-control" formControlName="state">
<option *ngFor="let state of states" [value]="state">{{state}}</option>
</select>
</label>
</div>
<div class="form-group">
<label class="center-block">Zip Code:
<input class="form-control" formControlName="zip">
</label>
</div>
</div>
<br>
<!-- End of the repeated address template -->
<!-- #docregion form-array-skeleton -->
</div>
<!-- #enddocregion form-array-skeleton -->
<!-- #enddocregion form-array-->
<!-- #docregion add-lair -->
<button (click)="addLair()" type="button">Add a Secret Lair</button>
<!-- #enddocregion add-lair -->
<!-- #docregion form-array-->
<!-- #docregion form-array-skeleton -->
</div>
<!-- #enddocregion form-array-skeleton -->
<!-- #enddocregion form-array-->
<div class="form-group radio">
<h4>Super power:</h4>
<label class="center-block"><input type="radio" formControlName="power" value="flight">Flight</label>
<label class="center-block"><input type="radio" formControlName="power" value="x-ray vision">X-ray vision</label>
<label class="center-block"><input type="radio" formControlName="power" value="strength">Strength</label>
</div>
<div class="checkbox">
<label class="center-block">
<input type="checkbox" formControlName="sidekick">I have a sidekick.
</label>
</div>
</form>
<p>heroForm value: {{ heroForm.value | json}}</p>

View File

@ -1,74 +0,0 @@
/* tslint:disable:component-class-suffix */
// #docregion imports
import { Component, Input, OnChanges } from '@angular/core';
import { FormArray, FormBuilder, FormGroup, Validators } from '@angular/forms';
import { Address, Hero, states } from '../data-model';
// #enddocregion imports
@Component({
selector: 'app-hero-detail-8',
templateUrl: './hero-detail-8.component.html'
})
// #docregion v8
export class HeroDetailComponent8 implements OnChanges {
@Input() hero: Hero;
heroForm: FormGroup;
states = states;
// #docregion ctor
constructor(private fb: FormBuilder) {
this.createForm();
this.logNameChange();
}
// #enddocregion ctor
createForm() {
// #docregion secretLairs-form-array
this.heroForm = this.fb.group({
name: ['', Validators.required ],
secretLairs: this.fb.array([]), // <-- secretLairs as an empty FormArray
power: '',
sidekick: ''
});
// #enddocregion secretLairs-form-array
}
logNameChange() {/* Coming soon */}
// #docregion onchanges
ngOnChanges() {
this.rebuildForm();
}
// #enddocregion onchanges
// #docregion rebuildform
rebuildForm() {
this.heroForm.reset({
name: this.hero.name
});
this.setAddresses(this.hero.addresses);
}
// #enddocregion rebuildform
// #docregion get-secret-lairs
get secretLairs(): FormArray {
return this.heroForm.get('secretLairs') as FormArray;
};
// #enddocregion get-secret-lairs
// #docregion set-addresses
setAddresses(addresses: Address[]) {
const addressFGs = addresses.map(address => this.fb.group(address));
const addressFormArray = this.fb.array(addressFGs);
this.heroForm.setControl('secretLairs', addressFormArray);
}
// #enddocregion set-addresses
// #docregion add-lair
addLair() {
this.secretLairs.push(this.fb.group(new Address()));
}
// #enddocregion add-lair
}

View File

@ -1,73 +0,0 @@
<!-- #docplaster -->
<!-- #docregion -->
<!-- #docregion buttons -->
<form [formGroup]="heroForm" (ngSubmit)="onSubmit()">
<div style="margin-bottom: 1em">
<button type="submit"
[disabled]="heroForm.pristine" class="btn btn-success">Save</button> &nbsp;
<button type="button" (click)="revert()"
[disabled]="heroForm.pristine" class="btn btn-danger">Revert</button>
</div>
<!-- Hero Detail Controls -->
<!-- #enddocregion buttons -->
<div class="form-group">
<label class="center-block">Name:
<input class="form-control" formControlName="name">
</label>
</div>
<div formArrayName="secretLairs" class="well well-lg">
<div *ngFor="let address of secretLairs.controls; let i=index" [formGroupName]="i" >
<!-- The repeated address template -->
<h4>Address #{{i + 1}}</h4>
<div style="margin-left: 1em;">
<div class="form-group">
<label class="center-block">Street:
<input class="form-control" formControlName="street">
</label>
</div>
<div class="form-group">
<label class="center-block">City:
<input class="form-control" formControlName="city">
</label>
</div>
<div class="form-group">
<label class="center-block">State:
<select class="form-control" formControlName="state">
<option *ngFor="let state of states" [value]="state">{{state}}</option>
</select>
</label>
</div>
<div class="form-group">
<label class="center-block">Zip Code:
<input class="form-control" formControlName="zip">
</label>
</div>
</div>
<br>
<!-- End of the repeated address template -->
</div>
<button (click)="addLair()" type="button">Add a Secret Lair</button>
</div>
<!-- #docregion buttons -->
<div class="form-group radio">
<h4>Super power:</h4>
<label class="center-block"><input type="radio" formControlName="power" value="flight">Flight</label>
<label class="center-block"><input type="radio" formControlName="power" value="x-ray vision">X-ray vision</label>
<label class="center-block"><input type="radio" formControlName="power" value="strength">Strength</label>
</div>
<div class="checkbox">
<label class="center-block">
<input type="checkbox" formControlName="sidekick">I have a sidekick.
</label>
</div>
</form>
<!-- #enddocregion buttons -->
<p>heroForm value: {{ heroForm.value | json}}</p>
<!-- #docregion name-change-log -->
<h4>Name change log</h4>
<div *ngFor="let name of nameChangeLog">{{name}}</div>
<!-- #enddocregion name-change-log -->

View File

@ -1,113 +0,0 @@
// #docplaster
// #docregion
import { Component, Input, OnChanges } from '@angular/core';
import { FormArray, FormBuilder, FormGroup } from '@angular/forms';
import { Address, Hero, states } from '../data-model';
// #docregion import-service
import { HeroService } from '../hero.service';
// #enddocregion import-service
@Component({
selector: 'app-hero-detail',
templateUrl: './hero-detail.component.html',
styleUrls: ['./hero-detail.component.css']
})
// #docregion onchanges-implementation
export class HeroDetailComponent implements OnChanges {
// #enddocregion onchanges-implementation
@Input() hero: Hero;
heroForm: FormGroup;
// #docregion log-name-change
nameChangeLog: string[] = [];
// #enddocregion log-name-change
states = states;
// #docregion ctor
constructor(
private fb: FormBuilder,
private heroService: HeroService) {
this.createForm();
this.logNameChange();
}
// #enddocregion ctor
createForm() {
this.heroForm = this.fb.group({
name: '',
secretLairs: this.fb.array([]),
power: '',
sidekick: ''
});
}
ngOnChanges() {
this.rebuildForm();
}
rebuildForm() {
this.heroForm.reset({
name: this.hero.name
});
this.setAddresses(this.hero.addresses);
}
get secretLairs(): FormArray {
return this.heroForm.get('secretLairs') as FormArray;
};
setAddresses(addresses: Address[]) {
const addressFGs = addresses.map(address => this.fb.group(address));
const addressFormArray = this.fb.array(addressFGs);
this.heroForm.setControl('secretLairs', addressFormArray);
}
addLair() {
this.secretLairs.push(this.fb.group(new Address()));
}
// #docregion on-submit
onSubmit() {
this.hero = this.prepareSaveHero();
this.heroService.updateHero(this.hero).subscribe(/* error handling */);
this.rebuildForm();
}
// #enddocregion on-submit
// #docregion prepare-save-hero
prepareSaveHero(): Hero {
const formModel = this.heroForm.value;
// deep copy of form model lairs
const secretLairsDeepCopy: Address[] = formModel.secretLairs.map(
(address: Address) => Object.assign({}, address)
);
// return new `Hero` object containing a combination of original hero value(s)
// and deep copies of changed form model values
const saveHero: Hero = {
id: this.hero.id,
name: formModel.name as string,
// addresses: formModel.secretLairs // <-- bad!
addresses: secretLairsDeepCopy
};
return saveHero;
}
// #enddocregion prepare-save-hero
// #docregion revert
revert() { this.rebuildForm(); }
// #enddocregion revert
// #docregion log-name-change
logNameChange() {
const nameControl = this.heroForm.get('name');
nameControl.valueChanges.forEach(
(value: string) => this.nameChangeLog.push(value)
);
}
// #enddocregion log-name-change
}

View File

@ -1,8 +0,0 @@
<!-- #docregion -->
<nav>
<a *ngFor="let hero of heroes | async" (click)="select(hero)">{{hero.name}}</a>
</nav>
<div *ngIf="selectedHero">
<app-hero-detail [hero]="selectedHero"></app-hero-detail>
</div>

View File

@ -1,17 +0,0 @@
<!-- #docregion -->
<h3 *ngIf="isLoading"><i>Loading heroes ... </i></h3>
<h3 *ngIf="!isLoading">Select a hero:</h3>
<nav>
<button (click)="getHeroes()" class="btn btn-primary">Refresh</button>
<a *ngFor="let hero of heroes | async" (click)="select(hero)">{{hero.name}}</a>
</nav>
<div *ngIf="selectedHero">
<hr>
<h2>Hero Detail</h2>
<h3>Editing: {{selectedHero.name}}</h3>
<!-- #docregion hero-binding -->
<app-hero-detail [hero]="selectedHero"></app-hero-detail>
<!-- #enddocregion hero-binding -->
</div>

View File

@ -1,32 +0,0 @@
// #docregion
import { Component, OnInit } from '@angular/core';
import { Observable } from 'rxjs';
import { finalize } from 'rxjs/operators';
import { Hero } from '../data-model';
import { HeroService } from '../hero.service';
@Component({
selector: 'app-hero-list',
templateUrl: './hero-list.component.html',
styleUrls: ['./hero-list.component.css']
})
export class HeroListComponent implements OnInit {
heroes: Observable<Hero[]>;
isLoading = false;
selectedHero: Hero;
constructor(private heroService: HeroService) { }
ngOnInit() { this.getHeroes(); }
getHeroes() {
this.isLoading = true;
this.heroes = this.heroService.getHeroes()
// TODO: error handling
.pipe(finalize(() => this.isLoading = false));
this.selectedHero = undefined;
}
select(hero: Hero) { this.selectedHero = hero; }
}

View File

@ -1,25 +0,0 @@
// #docregion
import { Injectable } from '@angular/core';
import { Observable, of } from 'rxjs';
import { delay } from 'rxjs/operators';
import { Hero, heroes } from './data-model';
@Injectable()
export class HeroService {
delayMs = 500;
// Fake server get; assume nothing can go wrong
getHeroes(): Observable<Hero[]> {
return of(heroes).pipe(delay(this.delayMs)); // simulate latency with delay
}
// Fake server update; assume nothing can go wrong
updateHero(hero: Hero): Observable<Hero> {
const oldHero = heroes.find(h => h.id === hero.id);
const newHero = Object.assign(oldHero, hero); // Demo: mutate cached hero
return of(newHero).pipe(delay(this.delayMs)); // simulate latency with delay
}
}

View File

@ -0,0 +1,19 @@
:host {
display: flex;
flex-direction: column;
padding-top: 24px;
}
label {
display: block;
width: 6em;
margin: .5em 0;
color: #607D8B;
font-weight: bold;
}
input {
height: 2em;
font-size: 1em;
padding-left: .4em;
}

View File

@ -0,0 +1,21 @@
<!-- #docregion control-binding -->
<label>
Name:
<input type="text" [formControl]="name">
</label>
<!-- #enddocregion control-binding -->
<!-- #docregion display-value -->
<p>
Value: {{ name.value }}
</p>
<!-- #enddocregion display-value -->
<!-- #docregion update-value -->
<p>
<button (click)="updateName()">Update Name</button>
</p>
<!-- #enddocregion update-value -->

View File

@ -0,0 +1,22 @@
// #docplaster
// #docregion create-control
import { Component } from '@angular/core';
import { FormControl } from '@angular/forms';
@Component({
selector: 'app-name-editor',
templateUrl: './name-editor.component.html',
styleUrls: ['./name-editor.component.css']
})
export class NameEditorComponent {
name = new FormControl('');
// #enddocregion create-control
// #docregion update-value
updateName() {
this.name.setValue('Nancy');
}
// #enddocregion update-value
// #docregion create-control
}
// #enddocregion create-control

View File

@ -0,0 +1,67 @@
<!-- #docplaster -->
<!-- #docregion formgroup -->
<form [formGroup]="profileForm">
<label>
First Name:
<input type="text" formControlName="firstName">
</label>
<label>
Last Name:
<input type="text" formControlName="lastName">
</label>
<!-- #enddocregion formgroup -->
<!-- #docregion formgroupname -->
<div formGroupName="address">
<h3>Address</h3>
<label>
Street:
<input type="text" formControlName="street">
</label>
<label>
City:
<input type="text" formControlName="city">
</label>
<label>
State:
<input type="text" formControlName="state">
</label>
<label>
Zip Code:
<input type="text" formControlName="zip">
</label>
</div>
<!-- #enddocregion formgroupname -->
<!-- #docregion formarrayname -->
<div formArrayName="aliases">
<h3>Aliases</h3> <button (click)="addAlias()">Add Alias</button>
<div *ngFor="let address of aliases.controls; let i=index">
<!-- The repeated alias template -->
<label>
Alias:
<input type="text" [formControlName]="i">
</label>
</div>
</div>
<!-- #enddocregion formarrayname -->
<!-- #docregion formgroup -->
</form>
<!-- #enddocregion formgroup -->
<p>
Form Value: {{ profileForm.value | json }}
</p>
<!-- #docregion patch-value -->
<p>
<button (click)="updateProfile()">Update Profile</button>
</p>
<!-- #enddocregion patch-value -->

View File

@ -0,0 +1,40 @@
// #docplaster
// #docregion formgroup, nested-formgroup
import { Component } from '@angular/core';
// #docregion imports
import { FormGroup, FormControl } from '@angular/forms';
// #enddocregion imports
@Component({
selector: 'app-profile-editor',
templateUrl: './profile-editor.component.html',
styleUrls: ['./profile-editor.component.css']
})
export class ProfileEditorComponent {
// #docregion formgroup-compare
profileForm = new FormGroup({
firstName: new FormControl(''),
lastName: new FormControl(''),
// #enddocregion formgroup
address: new FormGroup({
street: new FormControl(''),
city: new FormControl(''),
state: new FormControl(''),
zip: new FormControl('')
})
// #docregion formgroup
});
// #enddocregion formgroup, nested-formgroup, formgroup-compare
// #docregion patch-value
updateProfile() {
this.profileForm.patchValue({
firstName: 'Nancy',
address: {
street: '123 Drew Street'
}
});
}
// #enddocregion patch-value
// #docregion formgroup, nested-formgroup
}
// #enddocregion formgroup

View File

@ -0,0 +1,58 @@
// #docplaster
// #docregion form-builder
import { Component } from '@angular/core';
// #docregion form-builder-imports
import { FormBuilder } from '@angular/forms';
// #enddocregion form-builder-imports, form-builder
// #docregion form-array-imports
import { FormArray } from '@angular/forms';
// #docregion form-builder-imports, form-builder
// #enddocregion form-builder-imports, form-array-imports
@Component({
selector: 'app-profile-editor',
templateUrl: './profile-editor.component.html',
styleUrls: ['./profile-editor.component.css']
})
export class ProfileEditorComponent {
// #docregion formgroup-compare
profileForm = this.fb.group({
firstName: [''],
lastName: [''],
address: this.fb.group({
street: [''],
city: [''],
state: [''],
zip: ['']
}),
// #enddocregion form-builder, formgroup-compare
aliases: this.fb.array([
this.fb.control('')
])
// #docregion form-builder, formgroup-compare
});
// #enddocregion form-builder, formgroup-compare
get aliases() {
return this.profileForm.get('aliases') as FormArray;
}
// #docregion inject-form-builder, form-builder
constructor(private fb: FormBuilder) { }
// #enddocregion inject-form-builder, form-builder
updateProfile() {
this.profileForm.patchValue({
firstName: 'Nancy',
address: {
street: '123 Drew Street'
}
});
}
addAlias() {
this.aliases.push(this.fb.control(''));
}
// #docregion form-builder
}
// #enddocregion form-builder

View File

@ -0,0 +1,39 @@
/* ProfileEditorComponent's private CSS styles */
:host {
display: flex;
flex-direction: column;
padding-top: 24px;
}
label {
display: block;
width: 6em;
margin: .5em 0;
color: #607D8B;
font-weight: bold;
}
input {
height: 2em;
font-size: 1em;
padding-left: .4em;
}
button {
font-family: Arial;
background-color: #eee;
border: none;
padding: 5px 10px;
border-radius: 4px;
cursor: pointer;
}
button:hover {
background-color: #cfd8dc;
}
button:disabled {
background-color: #eee;
color: #ccc;
cursor: auto;
}

View File

@ -0,0 +1,80 @@
<!-- #docplaster -->
<!-- #docregion ng-submit -->
<form [formGroup]="profileForm" (ngSubmit)="onSubmit()">
<!-- #enddocregion ng-submit -->
<label>
First Name:
<!-- #docregion required-attribute -->
<input type="text" formControlName="firstName" required>
<!-- #enddocregion required-attribute -->
</label>
<label>
Last Name:
<input type="text" formControlName="lastName">
</label>
<div formGroupName="address">
<h3>Address</h3>
<label>
Street:
<input type="text" formControlName="street">
</label>
<label>
City:
<input type="text" formControlName="city">
</label>
<label>
State:
<input type="text" formControlName="state">
</label>
<label>
Zip Code:
<input type="text" formControlName="zip">
</label>
</div>
<!-- #docregion formarrayname -->
<div formArrayName="aliases">
<h3>Aliases</h3> <button (click)="addAlias()">Add Alias</button>
<div *ngFor="let address of aliases.controls; let i=index">
<!-- The repeated alias template -->
<label>
Alias:
<input type="text" [formControlName]="i">
</label>
</div>
</div>
<!-- #enddocregion formarrayname -->
<!-- #docregion submit-button -->
<button type="submit" [disabled]="!profileForm.valid">Submit</button>
<!-- #enddocregion submit-button -->
</form>
<hr>
<!-- #docregion display-value -->
<p>
Form Value: {{ profileForm.value | json }}
</p>
<!-- #enddocregion display-value -->
<!-- #docregion display-status -->
<p>
Form Status: {{ profileForm.status }}
</p>
<!-- #enddocregion display-status -->
<!-- #docregion patch-value -->
<p>
<button (click)="updateProfile()">Update Profile</button>
</p>
<!-- #enddocregion patch-value -->

View File

@ -0,0 +1,73 @@
// #docplaster
// #docregion form-builder
import { Component } from '@angular/core';
// #docregion form-builder-imports
import { FormBuilder } from '@angular/forms';
// #enddocregion form-builder-imports
// #docregion validator-imports
import { Validators } from '@angular/forms';
// #enddocregion validator-imports
// #docregion form-array-imports
import { FormArray } from '@angular/forms';
// #enddocregion form-array-imports
@Component({
selector: 'app-profile-editor',
templateUrl: './profile-editor.component.html',
styleUrls: ['./profile-editor.component.css']
})
export class ProfileEditorComponent {
// #docregion required-validator, aliases
profileForm = this.fb.group({
firstName: ['', Validators.required],
lastName: [''],
address: this.fb.group({
street: [''],
city: [''],
state: [''],
zip: ['']
}),
// #enddocregion form-builder, required-validator
aliases: this.fb.array([
this.fb.control('')
])
// #docregion form-builder, required-validator
});
// #enddocregion form-builder, required-validator, aliases
// #docregion aliases-getter
get aliases() {
return this.profileForm.get('aliases') as FormArray;
}
// #enddocregion aliases-getter
// #docregion inject-form-builder, form-builder
constructor(private fb: FormBuilder) { }
// #enddocregion inject-form-builder
updateProfile() {
this.profileForm.patchValue({
firstName: 'Nancy',
address: {
street: '123 Drew Street'
}
});
}
// #enddocregion form-builder
// #docregion add-alias
addAlias() {
this.aliases.push(this.fb.control(''));
}
// #enddocregion add-alias
// #docregion on-submit
onSubmit() {
// TODO: Use EventEmitter with form value
console.warn(this.profileForm.value);
}
// #enddocregion on-submit
// #docregion form-builder
}
// #enddocregion form-builder

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