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)

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:

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