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:
commit
2d485c6589
|
@ -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
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
42
BUILD.bazel
42
BUILD.bazel
|
@ -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",
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
223
CHANGELOG.md
223
CHANGELOG.md
|
@ -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)
|
||||||
|
|
||||||
|
|
|
@ -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
159
WORKSPACE
|
@ -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",
|
|
||||||
)
|
)
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -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" "*"
|
||||||
|
|
|
@ -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.
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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]
|
||||||
|
|
|
@ -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).
|
|
|
@ -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
|
||||||
|
|
|
@ -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>
|
||||||
`
|
`
|
||||||
})
|
})
|
||||||
|
|
|
@ -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
|
||||||
],
|
],
|
||||||
|
|
|
@ -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);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,3 @@
|
||||||
|
{
|
||||||
|
"projectType": "elements"
|
||||||
|
}
|
|
@ -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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 {}
|
}
|
||||||
|
|
|
@ -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()">✖</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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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));
|
||||||
|
|
|
@ -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 -->
|
|
|
@ -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);
|
||||||
|
|
|
@ -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);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
|
@ -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 ]
|
||||||
})
|
})
|
||||||
|
|
|
@ -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
|
|
@ -0,0 +1,5 @@
|
||||||
|
/* #docregion cross-validation-error-css */
|
||||||
|
.cross-validation-error input {
|
||||||
|
border-left: 5px solid red;
|
||||||
|
}
|
||||||
|
/* #enddocregion cross-validation-error-css */
|
|
@ -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">
|
||||||
|
|
|
@ -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
|
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
|
@ -0,0 +1,4 @@
|
||||||
|
/* #docregion */
|
||||||
|
.cross-validation-error input {
|
||||||
|
border-left: 5px solid red;
|
||||||
|
}
|
|
@ -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>
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
"description": "Validation",
|
"description": "Validation",
|
||||||
"files":[
|
"files":[
|
||||||
"!**/*.d.ts",
|
"!**/*.d.ts",
|
||||||
"!**/*.js"
|
"!**/*.js",
|
||||||
|
"!**/*.[1].*"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,8 +38,6 @@ export class MyCounterComponent implements OnChanges {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/***************************************/
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'counter-parent',
|
selector: 'counter-parent',
|
||||||
template: `
|
template: `
|
||||||
|
|
|
@ -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',
|
||||||
|
|
|
@ -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',
|
||||||
|
|
|
@ -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 */
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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';
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
];
|
||||||
|
|
|
@ -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' }
|
||||||
];
|
];
|
||||||
|
|
|
@ -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';
|
||||||
|
|
|
@ -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({
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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
|
@ -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"]
|
|
||||||
}
|
|
|
@ -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 -->
|
|
@ -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 -->
|
||||||
|
|
|
@ -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!');
|
||||||
|
}));
|
||||||
|
});
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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'];
|
|
|
@ -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>
|
|
|
@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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 { }
|
|
|
@ -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-->
|
|
||||||
|
|
|
@ -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();
|
|
||||||
}
|
|
|
@ -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 -->
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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
|
|
|
@ -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 -->
|
|
|
@ -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
|
|
|
@ -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
|
|
|
@ -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>
|
|
|
@ -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
|
|
|
@ -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 -->
|
|
|
@ -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
|
|
||||||
|
|
|
@ -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>
|
|
|
@ -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
|
|
|
@ -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>
|
|
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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>
|
|
|
@ -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
|
|
||||||
}
|
|
|
@ -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>
|
|
||||||
<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 -->
|
|
|
@ -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
|
|
||||||
}
|
|
|
@ -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>
|
|
|
@ -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>
|
|
|
@ -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; }
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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;
|
||||||
|
}
|
|
@ -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 -->
|
|
@ -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
|
|
@ -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 -->
|
|
@ -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
|
|
@ -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
|
|
@ -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;
|
||||||
|
}
|
|
@ -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 -->
|
|
@ -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
Loading…
Reference in New Issue