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
|
||||
|
||||
# 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
|
||||
test --test_tag_filters=-manual
|
||||
|
|
|
@ -12,8 +12,8 @@
|
|||
## IMPORTANT
|
||||
# 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.
|
||||
var_1: &docker_image angular/ngcontainer:0.2.0
|
||||
var_2: &cache_key v2-angular-{{ .Branch }}-{{ checksum "yarn.lock" }}-0.2.0
|
||||
var_1: &docker_image angular/ngcontainer:0.3.3
|
||||
var_2: &cache_key v2-angular-{{ .Branch }}-{{ checksum "yarn.lock" }}-0.3.3
|
||||
|
||||
# Define common ENV vars
|
||||
var_3: &define_env_vars
|
||||
|
@ -80,33 +80,73 @@ jobs:
|
|||
|
||||
- run: ls /home/circleci/bazel_repository_cache || true
|
||||
- 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
|
||||
# This avoids waiting for the slowest build target to finish before running the first test
|
||||
# See https://github.com/bazelbuild/bazel/issues/4257
|
||||
# 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.
|
||||
# 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:
|
||||
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:
|
||||
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:
|
||||
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:
|
||||
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:
|
||||
key: *cache_key
|
||||
paths:
|
||||
- "node_modules"
|
||||
- "~/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
|
||||
# that rely on the pre-Bazel dist/packages-dist layout.
|
||||
|
@ -127,7 +167,7 @@ jobs:
|
|||
- run: sudo cp .circleci/bazel.rc /etc/bazel.bazelrc
|
||||
- *setup-bazel-remote-cache
|
||||
|
||||
- run: bazel run @yarn//:yarn
|
||||
- run: bazel run @nodejs//:yarn
|
||||
- run: scripts/build-packages-dist.sh
|
||||
|
||||
# Save the npm packages from //packages/... for other workflow jobs to read
|
||||
|
@ -136,6 +176,8 @@ jobs:
|
|||
root: dist
|
||||
paths:
|
||||
- packages-dist
|
||||
- packages-dist-ivy-jit
|
||||
- packages-dist-ivy-local
|
||||
|
||||
# We run the integration tests outside of Bazel for now.
|
||||
# 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.
|
||||
integration_test:
|
||||
<<: *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:
|
||||
- *define_env_vars
|
||||
- checkout:
|
||||
|
@ -196,6 +242,8 @@ workflows:
|
|||
jobs:
|
||||
- lint
|
||||
- test
|
||||
- test_ivy_jit
|
||||
- test_ivy_aot
|
||||
- build-packages-dist
|
||||
- integration_test:
|
||||
requires:
|
||||
|
@ -208,6 +256,8 @@ workflows:
|
|||
requires:
|
||||
# Only publish if tests and integration tests pass
|
||||
- test
|
||||
- test_ivy_jit
|
||||
- test_ivy_aot
|
||||
- integration_test
|
||||
# Get the artifacts to publish from the build-packages-dist job
|
||||
# since the publishing script expects the legacy outputs layout.
|
||||
|
|
|
@ -1,5 +1,14 @@
|
|||
# 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
|
||||
merge:
|
||||
# the status will be added to your pull requests
|
||||
|
@ -36,10 +45,12 @@ merge:
|
|||
- "packages/language-service/**"
|
||||
- "**/.gitignore"
|
||||
- "**/.gitkeep"
|
||||
- "**/package.json"
|
||||
- "**/tsconfig-build.json"
|
||||
- "**/tsconfig.json"
|
||||
- "**/rollup.config.js"
|
||||
- "**/BUILD.bazel"
|
||||
- "packages/**/integrationtest/**"
|
||||
- "packages/**/test/**"
|
||||
|
||||
# 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
|
||||
# brandonroberts - Brandon Roberts
|
||||
# brocco - Mike Brocchi
|
||||
# chuckjaz - Chuck Jazdzewski
|
||||
# filipesilva - Filipe Silva
|
||||
# gkalpak - George Kalpakas
|
||||
# hansl - Hans Larsen
|
||||
|
@ -18,12 +17,12 @@
|
|||
# jasonaden - Jason Aden
|
||||
# kapunahelewong - Kapunahele Wong
|
||||
# kara - Kara Erickson
|
||||
# kyliau - Keen Yee Liau
|
||||
# matsko - Matias Niemelä
|
||||
# mhevery - Misko Hevery
|
||||
# petebacondarwin - Pete Bacon Darwin
|
||||
# pkozlowski-opensource - Pawel Kozlowski
|
||||
# robwormald - Rob Wormald
|
||||
# tinayuangao - Tina Gao
|
||||
# vicb - Victor Berchet
|
||||
# vikerman - Vikram Subramanian
|
||||
|
||||
|
@ -94,7 +93,7 @@ groups:
|
|||
- "tools/bazel.rc"
|
||||
users:
|
||||
- alexeagle #primary
|
||||
- chuckjaz
|
||||
- kyliau
|
||||
- IgorMinar #fallback
|
||||
- mhevery
|
||||
- vikerman #fallback
|
||||
|
@ -133,7 +132,7 @@ groups:
|
|||
- "packages/core/*"
|
||||
users:
|
||||
- mhevery #primary
|
||||
- chuckjaz
|
||||
- jasonaden
|
||||
- kara
|
||||
- vicb
|
||||
- IgorMinar #fallback
|
||||
|
@ -218,7 +217,6 @@ groups:
|
|||
- "aio/content/examples/reactive-forms/*"
|
||||
users:
|
||||
- kara #primary
|
||||
- tinayuangao #secondary
|
||||
- IgorMinar #fallback
|
||||
- mhevery #fallback
|
||||
|
||||
|
@ -237,7 +235,7 @@ groups:
|
|||
files:
|
||||
- "packages/language-service/*"
|
||||
users:
|
||||
- chuckjaz #primary
|
||||
- kyliau #primary
|
||||
# needs secondary
|
||||
- vicb
|
||||
- IgorMinar #fallback
|
||||
|
|
42
BUILD.bazel
42
BUILD.bazel
|
@ -5,53 +5,39 @@ load("@build_bazel_rules_nodejs//:defs.bzl", "node_modules_filegroup")
|
|||
exports_files([
|
||||
"tsconfig.json",
|
||||
"LICENSE",
|
||||
"protractor-perf.conf.js",
|
||||
])
|
||||
|
||||
# Developers should always run `bazel run :install`
|
||||
# This ensures that package.json in subdirectories get installed as well.
|
||||
alias(
|
||||
name = "install",
|
||||
actual = "@yarn//:yarn",
|
||||
actual = "@nodejs//:yarn",
|
||||
)
|
||||
|
||||
node_modules_filegroup(
|
||||
alias(
|
||||
name = "node_modules",
|
||||
packages = [
|
||||
"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",
|
||||
],
|
||||
actual = "@angular_deps//:node_modules",
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "web_test_bootstrap_scripts",
|
||||
# do not sort
|
||||
srcs = [
|
||||
"//:node_modules/reflect-metadata/Reflect.js",
|
||||
"//:node_modules/zone.js/dist/zone.js",
|
||||
"//:node_modules/zone.js/dist/zone-testing.js",
|
||||
"//:node_modules/zone.js/dist/task-tracking.js",
|
||||
"@angular_deps//:node_modules/reflect-metadata/Reflect.js",
|
||||
"@angular_deps//:node_modules/zone.js/dist/zone.js",
|
||||
"@angular_deps//:node_modules/zone.js/dist/zone-testing.js",
|
||||
"@angular_deps//:node_modules/zone.js/dist/task-tracking.js",
|
||||
"//:test-events.js",
|
||||
],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "angularjs",
|
||||
# do not sort
|
||||
name = "angularjs_scripts",
|
||||
srcs = [
|
||||
"//:node_modules/angular/angular.js",
|
||||
"//:node_modules/angular-mocks/angular-mocks.js",
|
||||
"@angular_deps//:node_modules/angular-1.5/angular.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",
|
||||
],
|
||||
)
|
||||
|
|
251
CHANGELOG.md
251
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>
|
||||
# [6.0.0](https://github.com/angular/angular/compare/6.0.0-beta.0...6.0.0) (2018-05-03)
|
||||
|
||||
|
@ -5,7 +228,7 @@
|
|||
|
||||
Angular v6 is the first release of Angular that unifies the Framework, Material and CLI.
|
||||
|
||||
To learn about the release highlights and our new CLI-powered update workflow for your projects please check out the [v6 release announcement](https://blog.angular.io/version-6-0-0-of-angular-now-available-cc56b0efa7a4).
|
||||
To learn about the release highlights and our new CLI-powered update workflow for your projects please check out the [v6 release announcement](https://blog.angular.io/version-6-0-0-of-angular-now-available-cc56b0efa7a4).
|
||||
|
||||
|
||||
|
||||
|
@ -156,10 +379,10 @@ To learn about the release highlights and our new CLI-powered update workflow fo
|
|||
This change removes support for `<template>`. `<ng-template>` should be used instead.
|
||||
|
||||
BEFORE:
|
||||
|
||||
|
||||
<!-- html template -->
|
||||
<template>some template content</template>
|
||||
|
||||
|
||||
# tsconfig.json
|
||||
{
|
||||
# ...
|
||||
|
@ -169,12 +392,12 @@ To learn about the release highlights and our new CLI-powered update workflow fo
|
|||
"enableLegacyTemplate": [true|false]
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
AFTER:
|
||||
|
||||
|
||||
<!-- html template -->
|
||||
<ng-template>some template content</ng-template>
|
||||
|
||||
|
||||
* **core:** it is no longer possible to import animation-related functions from @angular/core. All animation symbols must now be imported from @angular/animations.
|
||||
|
||||
|
||||
|
@ -187,35 +410,35 @@ To learn about the release highlights and our new CLI-powered update workflow fo
|
|||
|
||||
Previously, ngModelChange was emitted before its underlying control was updated.
|
||||
This was fine if you passed through the value directly through the $event keyword, e.g.
|
||||
|
||||
|
||||
```
|
||||
<input [(ngModel)]="name" (ngModelChange)="onChange($event)">
|
||||
|
||||
|
||||
onChange(value) {
|
||||
console.log(value); // would log updated value
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
However, if you had a handler for the ngModelChange event that checked the value through the control,
|
||||
you would get the old value rather than the updated value. e.g:
|
||||
|
||||
|
||||
```
|
||||
<input #modelDir="ngModel" [(ngModel)]="name" (ngModelChange)="onChange(modelDir)">
|
||||
|
||||
|
||||
onChange(ngModel: NgModel) {
|
||||
console.log(ngModel.value); // would log old value, not updated value
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
Now the value and validity will be updated before the ngModelChange event is emitted,
|
||||
so the same setup will log the updated value.
|
||||
|
||||
|
||||
```
|
||||
onChange(ngModel: NgModel) {
|
||||
console.log(ngModel.value); // will log updated value
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
We think this order will be less confusing when the control is checked directly.
|
||||
You will only need to update your app if it has relied on this bug to keep track of the old control value.
|
||||
If that is the case, you should be able to track the old value directly by saving it on your component.
|
||||
|
|
|
@ -227,10 +227,15 @@ The following is the list of supported scopes:
|
|||
|
||||
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
|
||||
* **aio**: used for docs-app (angular.io) related changes within the /aio directory of the repo
|
||||
* none/empty string: useful for `style`, `test` and `refactor` changes that are done across all packages (e.g. `style: add missing semicolons`)
|
||||
* **docs-infra**: used for docs-app (angular.io) related changes within the /aio directory of the
|
||||
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
|
||||
The subject contains a succinct description of the change:
|
||||
|
@ -269,7 +274,7 @@ changes to be accepted, the CLA must be signed. It's a quick process, we promise
|
|||
* https://help.github.com/articles/about-commit-email-addresses/
|
||||
* https://help.github.com/articles/blocking-command-line-pushes-that-expose-your-personal-email-address/
|
||||
|
||||
Note that if you have more than one Git identity, it is important to verify that you are logged in with the same ID with which you signed the CLA, before you commit changes. If not, your PR will fail the CLA check.
|
||||
Note that if you have more than one Git identity, it is important to verify that you are logged in with the same ID with which you signed the CLA, before you commit changes. If not, your PR will fail the CLA check.
|
||||
|
||||
<hr>
|
||||
|
||||
|
|
159
WORKSPACE
159
WORKSPACE
|
@ -1,39 +1,79 @@
|
|||
workspace(name = "angular")
|
||||
|
||||
#
|
||||
# Download Bazel toolchain dependencies as needed by build actions
|
||||
#
|
||||
|
||||
http_archive(
|
||||
name = "build_bazel_rules_nodejs",
|
||||
url = "https://github.com/bazelbuild/rules_nodejs/archive/1931156c232a08356dfda02e9c8b0275c2e63c00.zip",
|
||||
strip_prefix = "rules_nodejs-1931156c232a08356dfda02e9c8b0275c2e63c00",
|
||||
sha256 = "9cfe33276a6ac0076ee9ee159c4a2576f9851c0f437435b5ac19b2e592493078",
|
||||
url = "https://github.com/bazelbuild/rules_nodejs/archive/20ff5892612f8359aec8aaf26dd3902a24976ada.zip",
|
||||
strip_prefix = "rules_nodejs-20ff5892612f8359aec8aaf26dd3902a24976ada",
|
||||
sha256 = "07da9d4c3e688a02745d0f50709a87744706d4f5d1959b799b0ac38e97acd622",
|
||||
)
|
||||
|
||||
load("@build_bazel_rules_nodejs//:defs.bzl", "check_bazel_version", "node_repositories", "yarn_install")
|
||||
|
||||
check_bazel_version("0.11.1")
|
||||
node_repositories(package_json = ["//:package.json"])
|
||||
|
||||
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(
|
||||
name = "io_bazel_rules_webtesting",
|
||||
url = "https://github.com/bazelbuild/rules_webtesting/archive/7ffe970bbf380891754487f66c3d680c087d67f2.zip",
|
||||
strip_prefix = "rules_webtesting-7ffe970bbf380891754487f66c3d680c087d67f2",
|
||||
sha256 = "4fb0dca8c9a90547891b7ef486592775a523330fc4555c88cd8f09270055c2ce",
|
||||
)
|
||||
|
||||
http_archive(
|
||||
name = "build_bazel_rules_typescript",
|
||||
url = "https://github.com/bazelbuild/rules_typescript/archive/0.12.1.zip",
|
||||
strip_prefix = "rules_typescript-0.12.1",
|
||||
sha256 = "24e2c36f60508c6d270ae4265b89b381e3f66d550e70c367ed3755ad8d7ce3b0",
|
||||
url = "https://github.com/bazelbuild/rules_typescript/archive/0.15.3.zip",
|
||||
strip_prefix = "rules_typescript-0.15.3",
|
||||
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(
|
||||
name = "rxjs",
|
||||
|
@ -47,54 +87,53 @@ local_repository(
|
|||
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
|
||||
# version in /.circleci/config.yml
|
||||
BAZEL_BUILDTOOLS_VERSION = "70bc7843bb9950fece2bc014ed16de03419e36e2"
|
||||
#
|
||||
# Load and install our dependencies downloaded above.
|
||||
#
|
||||
|
||||
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 = "367c23a5fe7fc2a7cb57863d3718b4149f0e57426c48c8ad54c45348a0b53cc1",
|
||||
)
|
||||
load("@build_bazel_rules_nodejs//:defs.bzl", "check_bazel_version", "node_repositories", "yarn_install")
|
||||
|
||||
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",
|
||||
check_bazel_version("0.15.0")
|
||||
node_repositories(
|
||||
package_json = ["//:package.json"],
|
||||
preserve_symlinks = True,
|
||||
)
|
||||
|
||||
load("@io_bazel_rules_go//go:def.bzl", "go_rules_dependencies", "go_register_toolchains")
|
||||
|
||||
go_rules_dependencies()
|
||||
|
||||
go_register_toolchains()
|
||||
|
||||
# Fetching the Bazel source code allows us to compile the Skylark linter
|
||||
http_archive(
|
||||
name = "io_bazel",
|
||||
url = "https://github.com/bazelbuild/bazel/archive/5a35e72f9e97c06540c479f8c31512fb4656202f.zip",
|
||||
strip_prefix = "bazel-5a35e72f9e97c06540c479f8c31512fb4656202f",
|
||||
sha256 = "ed33a52874c14e3b487fb50f390c541fab9c81a33d986d38fb01766a66dbcd21",
|
||||
load("@io_bazel_rules_webtesting//web:repositories.bzl", "browser_repositories", "web_test_repositories")
|
||||
|
||||
web_test_repositories()
|
||||
browser_repositories(
|
||||
chromium = True,
|
||||
firefox = True,
|
||||
)
|
||||
|
||||
# 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_devkit",
|
||||
url = "https://github.com/angular/devkit/archive/v0.3.1.zip",
|
||||
strip_prefix = "devkit-0.3.1",
|
||||
sha256 = "31d4b597fe9336650acf13df053c1c84dcbe9c29c6a833bcac3819cd3fd8cad3",
|
||||
load("@build_bazel_rules_typescript//:defs.bzl", "ts_setup_workspace")
|
||||
|
||||
ts_setup_workspace()
|
||||
|
||||
load("@angular//:index.bzl", "ng_setup_workspace")
|
||||
|
||||
ng_setup_workspace()
|
||||
|
||||
#
|
||||
# Ask Bazel to manage these toolchain dependencies for us.
|
||||
# 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(
|
||||
name = "org_brotli",
|
||||
url = "https://github.com/google/brotli/archive/c6333e1e79fb62ea088443f192293f964409b04e.zip",
|
||||
strip_prefix = "brotli-c6333e1e79fb62ea088443f192293f964409b04e",
|
||||
sha256 = "3f781988dee7dd3bcce2bf238294663cfaaf3b6433505bdb762e24d0a284d1dc",
|
||||
yarn_install(
|
||||
name = "http-server_runtime_deps",
|
||||
package_json = "//tools/http-server:package.json",
|
||||
yarn_lock = "//tools/http-server:yarn.lock",
|
||||
)
|
||||
|
|
|
@ -52,8 +52,7 @@ export class BuildCleaner {
|
|||
protected removeDir(dir: string) {
|
||||
try {
|
||||
if (shell.test('-d', dir)) {
|
||||
// Undocumented signature (see https://github.com/shelljs/shelljs/pull/663).
|
||||
(shell as any).chmod('-R', 'a+w', dir);
|
||||
shell.chmod('-R', 'a+w', dir);
|
||||
shell.rm('-rf', dir);
|
||||
}
|
||||
} catch (err) {
|
||||
|
|
|
@ -106,8 +106,7 @@ export class BuildCreator extends EventEmitter {
|
|||
}
|
||||
|
||||
try {
|
||||
// Undocumented signature (see https://github.com/shelljs/shelljs/pull/663).
|
||||
(shell as any).chmod('-R', 'a-w', outputDir);
|
||||
shell.chmod('-R', 'a-w', outputDir);
|
||||
shell.rm('-f', inputFile);
|
||||
resolve();
|
||||
} catch (err) {
|
||||
|
|
|
@ -98,8 +98,7 @@ class Helper {
|
|||
const prDir = this.getPrDir(pr, isPublic);
|
||||
|
||||
if (fs.existsSync(prDir)) {
|
||||
// Undocumented signature (see https://github.com/shelljs/shelljs/pull/663).
|
||||
(shell as any).chmod('-R', 'a+w', prDir);
|
||||
shell.chmod('-R', 'a+w', prDir);
|
||||
shell.rm('-rf', prDir);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
"scripts": {
|
||||
"prebuild": "yarn clean-dist",
|
||||
"build": "tsc",
|
||||
"build-watch": "yarn tsc --watch",
|
||||
"build-watch": "yarn build --watch",
|
||||
"clean-dist": "node --eval \"require('shelljs').rm('-rf', 'dist')\"",
|
||||
"dev": "concurrently --kill-others --raw --success first \"yarn build-watch\" \"yarn test-watch\"",
|
||||
"lint": "tslint --project tsconfig.json",
|
||||
|
@ -33,7 +33,7 @@
|
|||
"@types/jasmine": "^2.6.0",
|
||||
"@types/jsonwebtoken": "^7.2.3",
|
||||
"@types/node": "^8.0.30",
|
||||
"@types/shelljs": "^0.7.4",
|
||||
"@types/shelljs": "^0.8.0",
|
||||
"@types/supertest": "^2.0.3",
|
||||
"concurrently": "^3.5.0",
|
||||
"nodemon": "^1.12.1",
|
||||
|
|
|
@ -69,9 +69,9 @@
|
|||
"@types/express-serve-static-core" "*"
|
||||
"@types/mime" "*"
|
||||
|
||||
"@types/shelljs@^0.7.4":
|
||||
version "0.7.4"
|
||||
resolved "https://registry.yarnpkg.com/@types/shelljs/-/shelljs-0.7.4.tgz#137b5f31306eaff4de120ffe5b9d74b297809cfc"
|
||||
"@types/shelljs@^0.8.0":
|
||||
version "0.8.0"
|
||||
resolved "https://registry.yarnpkg.com/@types/shelljs/-/shelljs-0.8.0.tgz#0caa56b68baae4f68f44e0dd666ab30b098e3632"
|
||||
dependencies:
|
||||
"@types/glob" "*"
|
||||
"@types/node" "*"
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
set -eux -o pipefail
|
||||
exec 3>&1
|
||||
|
||||
echo "\n\n[`date`] - Updating the preview server..."
|
||||
echo -e "\n\n[`date`] - Updating the preview server..."
|
||||
|
||||
# Input
|
||||
readonly HOST_REPO_DIR=$1
|
||||
|
|
Binary file not shown.
|
@ -60,6 +60,8 @@ dist/
|
|||
!rollup-config.js
|
||||
aot-compiler/**/*.d.ts
|
||||
aot-compiler/**/*.factory.d.ts
|
||||
upgrade-phonecat-2-hybrid/aot/**/*
|
||||
!upgrade-phonecat-2-hybrid/aot/index.html
|
||||
|
||||
# i18n
|
||||
!i18n/src/systemjs-text-plugin.js
|
||||
|
|
|
@ -40,5 +40,7 @@ export class HighlightDirective {
|
|||
// #docregion color-2
|
||||
@Input() appHighlight: string;
|
||||
// #enddocregion color-2
|
||||
}
|
||||
|
||||
// #docregion
|
||||
}
|
||||
// #enddocregion
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
import { BrowserModule } from '@angular/platform-browser';
|
||||
import { NgModule } from '@angular/core';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { HttpModule } from '@angular/http';
|
||||
import { HttpClientModule } from '@angular/common/http';
|
||||
|
||||
import { AppComponent } from './app.component';
|
||||
// #docregion directive-import
|
||||
|
@ -24,7 +24,7 @@ import { ItemDirective } from './item.directive';
|
|||
imports: [
|
||||
BrowserModule,
|
||||
FormsModule,
|
||||
HttpModule
|
||||
HttpClientModule
|
||||
],
|
||||
providers: [],
|
||||
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',
|
||||
template: `
|
||||
<h4>{{name}}</h4>
|
||||
<button (click)="vote(true)" [disabled]="voted">Agree</button>
|
||||
<button (click)="vote(false)" [disabled]="voted">Disagree</button>
|
||||
<button (click)="vote(true)" [disabled]="didVote">Agree</button>
|
||||
<button (click)="vote(false)" [disabled]="didVote">Disagree</button>
|
||||
`
|
||||
})
|
||||
export class VoterComponent {
|
||||
@Input() name: string;
|
||||
@Output() onVoted = new EventEmitter<boolean>();
|
||||
voted = false;
|
||||
@Output() voted = new EventEmitter<boolean>();
|
||||
didVote = false;
|
||||
|
||||
vote(agreed: boolean) {
|
||||
this.onVoted.emit(agreed);
|
||||
this.voted = true;
|
||||
this.voted.emit(agreed);
|
||||
this.didVote = true;
|
||||
}
|
||||
}
|
||||
// #enddocregion
|
||||
|
|
|
@ -8,7 +8,7 @@ import { Component } from '@angular/core';
|
|||
<h3>Agree: {{agreed}}, Disagree: {{disagreed}}</h3>
|
||||
<app-voter *ngFor="let voter of voters"
|
||||
[name]="voter"
|
||||
(onVoted)="onVoted($event)">
|
||||
(voted)="onVoted($event)">
|
||||
</app-voter>
|
||||
`
|
||||
})
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
// #docregion
|
||||
import { BrowserModule } from '@angular/platform-browser';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { HttpModule } from '@angular/http';
|
||||
import { HttpClientModule } from '@angular/common/http';
|
||||
|
||||
// import { AppRoutingModule } from './app-routing.module';
|
||||
import { LocationStrategy,
|
||||
|
@ -54,7 +54,7 @@ const c_components = [
|
|||
imports: [
|
||||
BrowserModule,
|
||||
FormsModule,
|
||||
HttpModule,
|
||||
HttpClientModule,
|
||||
InMemoryWebApiModule.forRoot(HeroData)
|
||||
// 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 { createNgElementConstructor } from '../elements-dist';
|
||||
import { createCustomElement } from '@angular/elements';
|
||||
import { PopupService } from './popup.service';
|
||||
import { PopupComponent } from './popup.component';
|
||||
|
||||
|
@ -8,19 +7,15 @@ import { PopupComponent } from './popup.component';
|
|||
selector: 'app-root',
|
||||
template: `
|
||||
<input #input value="Message">
|
||||
<button (click)="popup.showAsComponent(input.value)">
|
||||
Show as component </button>
|
||||
<button (click)="popup.showAsElement(input.value)">
|
||||
Show as element </button>
|
||||
`
|
||||
<button (click)="popup.showAsComponent(input.value)">Show as component</button>
|
||||
<button (click)="popup.showAsElement(input.value)">Show as element</button>
|
||||
`,
|
||||
})
|
||||
|
||||
export class AppComponent {
|
||||
constructor(private injector: Injector, public popup: PopupService) {
|
||||
// on init, convert PopupComponent to a custom element
|
||||
const PopupElement =
|
||||
createNgElementConstructor(PopupComponent, {injector: this.injector});
|
||||
// register the custom element with the browser.
|
||||
customElements.define('popup-element', PopupElement);
|
||||
constructor(injector: Injector, public popup: PopupService) {
|
||||
// Convert `PopupComponent` to a custom element.
|
||||
const PopupElement = createCustomElement(PopupComponent, {injector});
|
||||
// Register the custom element with the browser.
|
||||
customElements.define('popup-element', PopupElement);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,22 +1,21 @@
|
|||
// #docregion
|
||||
import { NgModule } from '@angular/core';
|
||||
import { BrowserModule } from '@angular/platform-browser';
|
||||
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
|
||||
import { NgModule } from '@angular/core';
|
||||
|
||||
import { AppComponent } from './app.component';
|
||||
import { PopupService } from './popup.service';
|
||||
import { PopupComponent } from './popup.component';
|
||||
import { PopupService } from './popup.service';
|
||||
|
||||
// include the PopupService provider,
|
||||
// but exclude PopupComponent from compilation,
|
||||
// because it will be added dynamically
|
||||
// Include the `PopupService` provider,
|
||||
// but exclude `PopupComponent` from compilation,
|
||||
// because it will be added dynamically.
|
||||
|
||||
@NgModule({
|
||||
declarations: [AppComponent, PopupComponent],
|
||||
imports: [BrowserModule, BrowserAnimationsModule],
|
||||
providers: [PopupService],
|
||||
declarations: [AppComponent, PopupComponent],
|
||||
bootstrap: [AppComponent],
|
||||
entryComponents: [PopupComponent],
|
||||
})
|
||||
|
||||
export class AppModule {}
|
||||
export class AppModule {
|
||||
}
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
// #docregion
|
||||
import { Component, EventEmitter, Input, Output } from '@angular/core';
|
||||
import { AnimationEvent } from '@angular/animations';
|
||||
import { animate, state, style, transition, trigger } from '@angular/animations';
|
||||
|
||||
@Component({
|
||||
selector: 'my-popup',
|
||||
template: 'Popup: {{message}}',
|
||||
template: `
|
||||
<span>Popup: {{message}}</span>
|
||||
<button (click)="closed.next()">✖</button>
|
||||
`,
|
||||
host: {
|
||||
'[@state]': 'state',
|
||||
'(@state.done)': 'onAnimationDone($event)',
|
||||
},
|
||||
animations: [
|
||||
trigger('state', [
|
||||
|
@ -27,13 +27,17 @@ import { animate, state, style, transition, trigger } from '@angular/animations'
|
|||
height: 48px;
|
||||
padding: 16px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
border-top: 1px solid black;
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
button {
|
||||
border-radius: 50%;
|
||||
}
|
||||
`]
|
||||
})
|
||||
|
||||
export class PopupComponent {
|
||||
private state: 'opened' | 'closed' = 'closed';
|
||||
|
||||
|
@ -41,18 +45,10 @@ export class PopupComponent {
|
|||
set message(message: string) {
|
||||
this._message = message;
|
||||
this.state = 'opened';
|
||||
|
||||
setTimeout(() => this.state = 'closed', 2000);
|
||||
}
|
||||
get message(): string { return this._message; }
|
||||
_message: string;
|
||||
|
||||
@Output()
|
||||
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 { NgElement, WithProperties } from '@angular/elements';
|
||||
import { PopupComponent } from './popup.component';
|
||||
import { NgElementConstructor } from '../elements-dist';
|
||||
|
||||
|
||||
@Injectable()
|
||||
export class PopupService {
|
||||
|
@ -40,7 +39,7 @@ export class PopupService {
|
|||
// This uses the new custom-element method to add the popup to the DOM.
|
||||
showAsElement(message: string) {
|
||||
// Create element
|
||||
const popupEl = document.createElement('popup-element');
|
||||
const popupEl: NgElement & WithProperties<PopupComponent> = document.createElement('popup-element') as any;
|
||||
|
||||
// Listen to the close event
|
||||
popupEl.addEventListener('closed', () => document.body.removeChild(popupEl));
|
||||
|
|
|
@ -1,14 +1,12 @@
|
|||
<!-- #docregion -->
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<base href="/">
|
||||
<title>Angular With Webpack</title>
|
||||
<meta charset="UTF-8">
|
||||
<base href="/">
|
||||
<title>Elements</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
</head>
|
||||
<body>
|
||||
<my-app>Loading...</my-app>
|
||||
<app-root></app-root>
|
||||
</body>
|
||||
</html>
|
||||
<!-- #enddocregion -->
|
|
@ -1,4 +1,3 @@
|
|||
// tslint:disable:no-unused-variable
|
||||
import { enableProdMode } from '@angular/core';
|
||||
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
|
||||
|
||||
|
@ -10,4 +9,3 @@ if (environment.production) {
|
|||
}
|
||||
|
||||
platformBrowserDynamic().bootstrapModule(AppModule);
|
||||
|
|
@ -16,6 +16,7 @@ describe('Form Validation Tests', function () {
|
|||
|
||||
tests('Template-Driven Form');
|
||||
bobTests();
|
||||
crossValidationTests();
|
||||
});
|
||||
|
||||
describe('Reactive form', () => {
|
||||
|
@ -25,6 +26,7 @@ describe('Form Validation Tests', function () {
|
|||
|
||||
tests('Reactive Form');
|
||||
bobTests();
|
||||
crossValidationTests();
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -42,7 +44,8 @@ let page: {
|
|||
powerOption: ElementFinder,
|
||||
errorMessages: ElementArrayFinder,
|
||||
heroFormButtons: ElementArrayFinder,
|
||||
heroSubmitted: ElementFinder
|
||||
heroSubmitted: ElementFinder,
|
||||
crossValidationErrorMessage: ElementFinder,
|
||||
};
|
||||
|
||||
function getPage(sectionTag: string) {
|
||||
|
@ -59,7 +62,8 @@ function getPage(sectionTag: string) {
|
|||
powerOption: section.element(by.css('#power option')),
|
||||
errorMessages: section.all(by.css('div.alert')),
|
||||
heroFormButtons: buttons,
|
||||
heroSubmitted: section.element(by.css('.submitted-message'))
|
||||
heroSubmitted: section.element(by.css('.submitted-message')),
|
||||
crossValidationErrorMessage: section.element(by.css('.cross-validation-error-message')),
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -172,3 +176,29 @@ function bobTests() {
|
|||
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 { HeroFormReactiveComponent } from './reactive/hero-form-reactive.component';
|
||||
import { ForbiddenValidatorDirective } from './shared/forbidden-name.directive';
|
||||
|
||||
import { IdentityRevealedValidatorDirective } from './shared/identity-revealed.directive';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
|
@ -19,7 +19,8 @@ import { ForbiddenValidatorDirective } from './shared/forbidden-name.directive';
|
|||
AppComponent,
|
||||
HeroFormTemplateComponent,
|
||||
HeroFormReactiveComponent,
|
||||
ForbiddenValidatorDirective
|
||||
ForbiddenValidatorDirective,
|
||||
IdentityRevealedValidatorDirective
|
||||
],
|
||||
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 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>
|
||||
<!-- #docregion name-with-error-msg -->
|
||||
<input id="name" class="form-control"
|
||||
formControlName="name" required >
|
||||
<label for="name">Name</label>
|
||||
<!-- #docregion name-with-error-msg -->
|
||||
<input id="name" class="form-control"
|
||||
formControlName="name" required >
|
||||
|
||||
<div *ngIf="name.invalid && (name.dirty || name.touched)"
|
||||
class="alert alert-danger">
|
||||
<div *ngIf="name.invalid && (name.dirty || name.touched)"
|
||||
class="alert alert-danger">
|
||||
|
||||
<div *ngIf="name.errors.required">
|
||||
Name is required.
|
||||
</div>
|
||||
<div *ngIf="name.errors.minlength">
|
||||
Name must be at least 4 characters long.
|
||||
</div>
|
||||
<div *ngIf="name.errors.forbiddenName">
|
||||
Name cannot be Bob.
|
||||
<div *ngIf="name.errors.required">
|
||||
Name is required.
|
||||
</div>
|
||||
<div *ngIf="name.errors.minlength">
|
||||
Name must be at least 4 characters long.
|
||||
</div>
|
||||
<div *ngIf="name.errors.forbiddenName">
|
||||
Name cannot be Bob.
|
||||
</div>
|
||||
</div>
|
||||
<!-- #enddocregion name-with-error-msg -->
|
||||
</div>
|
||||
<!-- #enddocregion name-with-error-msg -->
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="alterEgo">Alter Ego</label>
|
||||
<input id="alterEgo" class="form-control"
|
||||
formControlName="alterEgo" >
|
||||
<div class="form-group">
|
||||
<label for="alterEgo">Alter Ego</label>
|
||||
<input id="alterEgo" class="form-control"
|
||||
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 class="form-group">
|
||||
|
|
|
@ -1,40 +1,36 @@
|
|||
/* tslint:disable: member-ordering forin */
|
||||
// #docplaster
|
||||
// #docregion
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { FormControl, FormGroup, Validators } from '@angular/forms';
|
||||
import { forbiddenNameValidator } from '../shared/forbidden-name.directive';
|
||||
import { identityRevealedValidator } from '../shared/identity-revealed.directive';
|
||||
|
||||
@Component({
|
||||
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 {
|
||||
|
||||
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;
|
||||
|
||||
// #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.
|
||||
forbiddenNameValidator(/bob/i)
|
||||
]),
|
||||
'alterEgo': new FormControl(this.hero.alterEgo),
|
||||
'power': new FormControl(this.hero.power, Validators.required)
|
||||
});
|
||||
// #enddocregion custom-validator
|
||||
}, { validators: identityRevealedValidator }); // <-- add custom validator at the FormGroup level
|
||||
}
|
||||
|
||||
get name() { return this.heroForm.get('name'); }
|
||||
|
||||
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
|
||||
/** A hero's name can't match the given regular expression */
|
||||
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);
|
||||
return forbidden ? {'forbiddenName': {value: control.value}} : null;
|
||||
};
|
||||
|
@ -22,7 +22,7 @@ export function forbiddenNameValidator(nameRe: RegExp): ValidatorFn {
|
|||
export class ForbiddenValidatorDirective implements Validator {
|
||||
@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)
|
||||
: 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">
|
||||
|
||||
<h1>Template-Driven Form</h1>
|
||||
<!-- #docregion form-tag-->
|
||||
<form #heroForm="ngForm">
|
||||
<!-- #enddocregion form-tag-->
|
||||
<!-- #docregion cross-validation-register-validator -->
|
||||
<form #heroForm="ngForm" appIdentityRevealed>
|
||||
<!-- #enddocregion cross-validation-register-validator -->
|
||||
<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">
|
||||
<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 *ngIf="name.invalid && (name.dirty || name.touched)"
|
||||
class="alert alert-danger">
|
||||
|
||||
<div *ngIf="name.invalid && (name.dirty || name.touched)"
|
||||
class="alert alert-danger">
|
||||
<div *ngIf="name.errors.required">
|
||||
Name is required.
|
||||
</div>
|
||||
<div *ngIf="name.errors.minlength">
|
||||
Name must be at least 4 characters long.
|
||||
</div>
|
||||
<div *ngIf="name.errors.forbiddenName">
|
||||
Name cannot be Bob.
|
||||
</div>
|
||||
|
||||
<div *ngIf="name.errors.required">
|
||||
Name is required.
|
||||
</div>
|
||||
<div *ngIf="name.errors.minlength">
|
||||
Name must be at least 4 characters long.
|
||||
</div>
|
||||
<div *ngIf="name.errors.forbiddenName">
|
||||
Name cannot be Bob.
|
||||
</div>
|
||||
|
||||
<!-- #enddocregion name-with-error-msg -->
|
||||
</div>
|
||||
<!-- #enddocregion name-with-error-msg -->
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="alterEgo">Alter Ego</label>
|
||||
<input id="alterEgo" class="form-control"
|
||||
name="alterEgo" [(ngModel)]="hero.alterEgo" >
|
||||
<div class="form-group">
|
||||
<label for="alterEgo">Alter Ego</label>
|
||||
<input id="alterEgo" class="form-control"
|
||||
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 class="form-group">
|
||||
|
@ -62,5 +69,4 @@
|
|||
<button (click)="heroForm.resetForm({})">Add new hero</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
</div>
|
||||
|
|
|
@ -3,9 +3,11 @@
|
|||
// #docregion
|
||||
import { Component } from '@angular/core';
|
||||
|
||||
// #docregion component
|
||||
@Component({
|
||||
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 {
|
||||
|
||||
|
@ -14,3 +16,4 @@ export class HeroFormTemplateComponent {
|
|||
hero = {name: 'Dr.', alterEgo: 'Dr. What', power: this.powers[0]};
|
||||
|
||||
}
|
||||
// #enddocregion
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
"description": "Validation",
|
||||
"files":[
|
||||
"!**/*.d.ts",
|
||||
"!**/*.js"
|
||||
"!**/*.js",
|
||||
"!**/*.[1].*"
|
||||
]
|
||||
}
|
||||
|
|
|
@ -38,8 +38,6 @@ export class MyCounterComponent implements OnChanges {
|
|||
}
|
||||
}
|
||||
|
||||
/***************************************/
|
||||
|
||||
@Component({
|
||||
selector: 'counter-parent',
|
||||
template: `
|
||||
|
|
|
@ -72,8 +72,6 @@ export class DoCheckComponent implements DoCheck {
|
|||
}
|
||||
}
|
||||
|
||||
/***************************************/
|
||||
|
||||
@Component({
|
||||
selector: 'do-check-parent',
|
||||
templateUrl: './do-check-parent.component.html',
|
||||
|
|
|
@ -46,8 +46,6 @@ export class OnChangesComponent implements OnChanges {
|
|||
reset() { this.changeLog = []; }
|
||||
}
|
||||
|
||||
/***************************************/
|
||||
|
||||
@Component({
|
||||
selector: 'on-changes-parent',
|
||||
templateUrl: './on-changes-parent.component.html',
|
||||
|
|
|
@ -4,7 +4,8 @@ button {
|
|||
font-size: 100%;
|
||||
}
|
||||
|
||||
code, .code {
|
||||
code,
|
||||
.code {
|
||||
background-color: #eee;
|
||||
color: black;
|
||||
font-family: Courier, sans-serif;
|
||||
|
@ -21,14 +22,18 @@ div.code {
|
|||
}
|
||||
|
||||
hr {
|
||||
margin: 40px 0
|
||||
margin: 40px 0;
|
||||
}
|
||||
|
||||
td, th {
|
||||
td,
|
||||
th {
|
||||
text-align: left;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
/* #docregion p-span */
|
||||
p span { color: red; font-size: 70%; }
|
||||
p span {
|
||||
color: red;
|
||||
font-size: 70%;
|
||||
}
|
||||
/* #enddocregion p-span */
|
||||
|
|
|
@ -132,7 +132,7 @@
|
|||
<!-- #docregion select-span -->
|
||||
<select [(ngModel)]="hero">
|
||||
<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>
|
||||
</span>
|
||||
</span>
|
||||
|
@ -147,7 +147,7 @@
|
|||
<!-- #docregion select-ngcontainer -->
|
||||
<select [(ngModel)]="hero">
|
||||
<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>
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
|
|
|
@ -6,14 +6,15 @@ import { heroes } from './hero';
|
|||
@Component({
|
||||
selector: 'my-app',
|
||||
templateUrl: './app.component.html',
|
||||
styleUrls: [ './app.component.css' ]
|
||||
styleUrls: ['./app.component.css']
|
||||
})
|
||||
export class AppComponent {
|
||||
heroes = heroes;
|
||||
hero = this.heroes[0];
|
||||
heroTraits = [ 'honest', 'brave', 'considerate' ];
|
||||
heroTraits = ['honest', 'brave', 'considerate'];
|
||||
|
||||
// flags for the table
|
||||
|
||||
attrDirs = true;
|
||||
strucDirs = true;
|
||||
divNgIf = false;
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
// #docregion
|
||||
import { NgModule } from '@angular/core';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { NgModule } from '@angular/core';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { BrowserModule } from '@angular/platform-browser';
|
||||
|
||||
import { AppComponent } from './app.component';
|
||||
import { AppComponent } from './app.component';
|
||||
import { ContentComponent } from './content.component';
|
||||
import { heroComponents } from './hero.components';
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
// #docregion
|
||||
import { Component, Input } from '@angular/core';
|
||||
|
||||
import { Hero } from './hero';
|
||||
|
||||
@Component({
|
||||
|
@ -33,11 +34,15 @@ export class ConfusedHeroComponent {
|
|||
export class UnknownHeroComponent {
|
||||
@Input() hero: Hero;
|
||||
get message() {
|
||||
return this.hero && this.hero.name ?
|
||||
`${this.hero.name} is strange and mysterious.` :
|
||||
'Are you feeling indecisive?';
|
||||
return this.hero && this.hero.name
|
||||
? `${this.hero.name} is strange and mysterious.`
|
||||
: 'Are you feeling indecisive?';
|
||||
}
|
||||
}
|
||||
|
||||
export const heroComponents =
|
||||
[ HappyHeroComponent, SadHeroComponent, ConfusedHeroComponent, UnknownHeroComponent ];
|
||||
export const heroComponents = [
|
||||
HappyHeroComponent,
|
||||
SadHeroComponent,
|
||||
ConfusedHeroComponent,
|
||||
UnknownHeroComponent
|
||||
];
|
||||
|
|
|
@ -6,8 +6,8 @@ export class Hero {
|
|||
}
|
||||
|
||||
export const heroes: Hero[] = [
|
||||
{ id: 1, name: 'Mr. Nice', emotion: 'happy'},
|
||||
{ id: 2, name: 'Narco', emotion: 'sad' },
|
||||
{ id: 1, name: 'Mr. Nice', emotion: 'happy' },
|
||||
{ id: 2, name: 'Narco', emotion: 'sad' },
|
||||
{ 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 { NgModule } from '@angular/core';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { HttpModule } from '@angular/http';
|
||||
|
||||
/* App Root */
|
||||
import { AppComponent } from './app.component';
|
||||
|
|
|
@ -15,7 +15,7 @@ function sequenceSubscriber(observer) {
|
|||
if (idx === arr.length - 1) {
|
||||
observer.complete();
|
||||
} else {
|
||||
doSequence(arr, idx++);
|
||||
doSequence(arr, ++idx);
|
||||
}
|
||||
}, 1000);
|
||||
}
|
||||
|
@ -95,7 +95,7 @@ function multicastSequenceSubscriber() {
|
|||
},
|
||||
complete() {
|
||||
// Notify all complete callbacks
|
||||
observers.forEach(obs => obs.complete());
|
||||
observers.slice(0).forEach(obs => obs.complete());
|
||||
}
|
||||
}, seq, 0);
|
||||
}
|
||||
|
@ -121,13 +121,13 @@ function doSequence(observer, arr, idx) {
|
|||
if (idx === arr.length - 1) {
|
||||
observer.complete();
|
||||
} else {
|
||||
doSequence(observer, arr, idx++);
|
||||
doSequence(observer, arr, ++idx);
|
||||
}
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
// 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
|
||||
multicastSequence.subscribe({
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
import { NgModule } from '@angular/core';
|
||||
import { BrowserModule } from '@angular/platform-browser';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { HttpClientModule } from '@angular//common/http';
|
||||
import { HttpClientModule } from '@angular/common/http';
|
||||
|
||||
import { AppComponent } from './app.component';
|
||||
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">
|
||||
<h1>Reactive Forms</h1>
|
||||
<app-hero-detail></app-hero-detail>
|
||||
</div>
|
||||
<!-- #docplaster -->
|
||||
<h1>Reactive Forms</h1>
|
||||
|
||||
<!-- #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">
|
||||
<h1>Reactive Forms</h1>
|
||||
<app-hero-list></app-hero-list>
|
||||
</div>
|
||||
<!-- #docplaster -->
|
||||
<!-- #docregion app-name-editor -->
|
||||
<h1>Reactive Forms</h1>
|
||||
|
||||
<!-- #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';
|
||||
|
||||
export type EditorType = 'name' | 'profile';
|
||||
|
||||
@Component({
|
||||
selector: 'app-root',
|
||||
templateUrl: './app.component.html',
|
||||
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
|
||||
// #docregion
|
||||
// #docregion v1
|
||||
import { NgModule } from '@angular/core';
|
||||
import { BrowserModule } from '@angular/platform-browser';
|
||||
import { ReactiveFormsModule } from '@angular/forms'; // <-- #1 import module
|
||||
import { BrowserModule } from '@angular/platform-browser';
|
||||
import { NgModule } from '@angular/core';
|
||||
// #docregion imports
|
||||
import { ReactiveFormsModule } from '@angular/forms';
|
||||
|
||||
import { AppComponent } from './app.component';
|
||||
import { HeroDetailComponent } from './hero-detail/hero-detail.component';
|
||||
// #enddocregion v1
|
||||
// #docregion hero-service-list
|
||||
// add JavaScript imports
|
||||
import { HeroListComponent } from './hero-list/hero-list.component';
|
||||
import { HeroService } from './hero.service';
|
||||
// #docregion v1
|
||||
// #enddocregion imports
|
||||
import { AppComponent } from './app.component';
|
||||
import { NameEditorComponent } from './name-editor/name-editor.component';
|
||||
import { ProfileEditorComponent } from './profile-editor/profile-editor.component';
|
||||
|
||||
// #docregion imports
|
||||
@NgModule({
|
||||
// #enddocregion imports
|
||||
declarations: [
|
||||
AppComponent,
|
||||
HeroDetailComponent,
|
||||
// #enddocregion v1
|
||||
HeroListComponent // <--declare HeroListComponent
|
||||
// #docregion v1
|
||||
NameEditorComponent,
|
||||
ProfileEditorComponent
|
||||
],
|
||||
// #enddocregion hero-service-list
|
||||
// #docregion imports
|
||||
imports: [
|
||||
// #enddocregion imports
|
||||
BrowserModule,
|
||||
ReactiveFormsModule // <-- #2 add to @NgModule imports
|
||||
// #docregion imports
|
||||
// other imports ...
|
||||
ReactiveFormsModule
|
||||
],
|
||||
// #enddocregion v1
|
||||
// export for the DemoModule
|
||||
// #docregion hero-service-list
|
||||
// ...
|
||||
exports: [
|
||||
AppComponent,
|
||||
HeroDetailComponent,
|
||||
HeroListComponent // <-- export HeroListComponent
|
||||
],
|
||||
providers: [ HeroService ], // <-- provide HeroService
|
||||
// #enddocregion hero-service-list
|
||||
// #docregion v1
|
||||
bootstrap: [ AppComponent ]
|
||||
// #enddocregion imports
|
||||
providers: [],
|
||||
bootstrap: [AppComponent]
|
||||
// #docregion imports
|
||||
})
|
||||
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