Compare commits

..

3 Commits

Author SHA1 Message Date
b1d8784f79 feat(ivy): add element instruction
Adds a simplified element instruction that can be used if an element
has no children.
2018-05-30 15:13:39 -07:00
6a4ad11e87 feat(ivy): added namespaced attributes 2018-05-30 15:11:07 -07:00
a1a3af9e23 feat(ivy): add namespace instructions for SVG and others 2018-05-30 13:56:52 -07:00
1235 changed files with 24518 additions and 57935 deletions

View File

@ -3,10 +3,7 @@
# See remote cache documentation in /docs/BAZEL.md
# Don't be spammy in the logs
# 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
build --noshow_progress
# Don't run manual tests
test --test_tag_filters=-manual

View File

@ -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.3.3
var_2: &cache_key v2-angular-{{ .Branch }}-{{ checksum "yarn.lock" }}-0.3.3
var_1: &docker_image angular/ngcontainer:0.3.0
var_2: &cache_key v2-angular-{{ .Branch }}-{{ checksum "yarn.lock" }}-0.3.0
# Define common ENV vars
var_3: &define_env_vars
@ -48,7 +48,7 @@ jobs:
# Check BUILD.bazel formatting before we have a node_modules directory
# Then we don't need any exclude pattern to avoid checking those files
- run: 'buildifier -mode=check $(find . -type f \( -name "*.bzl" -or -name BUILD.bazel -or -name BUILD \)) ||
- run: 'buildifier -mode=check $(find . -type f \( -name BUILD.bazel -or -name BUILD \)) ||
(echo "BUILD files not formatted. Please run ''yarn buildifier''" ; exit 1)'
# Run the skylark linter to check our Bazel rules
# deprecated-api is disabled because we use actions.new_file(genfiles_dir)
@ -80,7 +80,7 @@ jobs:
- run: ls /home/circleci/bazel_repository_cache || true
- run: bazel info release
- run: bazel run @nodejs//:yarn
- run: bazel run @yarn//: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
@ -111,42 +111,6 @@ jobs:
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.
@ -167,7 +131,7 @@ jobs:
- run: sudo cp .circleci/bazel.rc /etc/bazel.bazelrc
- *setup-bazel-remote-cache
- run: bazel run @nodejs//:yarn
- run: bazel run @yarn//:yarn
- run: scripts/build-packages-dist.sh
# Save the npm packages from //packages/... for other workflow jobs to read
@ -176,8 +140,6 @@ 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.
@ -187,10 +149,6 @@ 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:
@ -242,8 +200,6 @@ workflows:
jobs:
- lint
- test
- test_ivy_jit
- test_ivy_aot
- build-packages-dist
- integration_test:
requires:
@ -256,8 +212,6 @@ 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.

View File

@ -45,12 +45,10 @@ 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
@ -62,13 +60,6 @@ merge:
# list of checks that will determine if the merge label can be added
checks:
# require that the PR has reviews from all requested reviewers
#
# This enables us to request reviews from both eng and tech writers, or multiple eng folks, and prevents accidental merges.
# Rather than merging PRs with pending reviews, if all PullApprove requirements are satisfied and additional reviews are not needed pending reviewers should be removed via GitHub UI (this also leaves an audit trail behind these decisions).
requireReviews: true,
# whether the PR shouldn't have a conflict with the base branch
noConflict: true
# list of labels that a PR needs to have, checked with a regexp (e.g. "PR target:" will work for the label "PR target: master")

View File

@ -8,7 +8,6 @@
# alexeagle - Alex Eagle
# alxhub - Alex Rickabaugh
# andrewseguin - Andrew Seguin
# benlesh - Ben Lesh
# brandonroberts - Brandon Roberts
# brocco - Mike Brocchi
# filipesilva - Filipe Silva
@ -16,7 +15,7 @@
# hansl - Hans Larsen
# IgorMinar - Igor Minar
# jasonaden - Jason Aden
# jenniferfell - Jennifer Fell
# kapunahelewong - Kapunahele Wong
# kara - Kara Erickson
# kyliau - Keen Yee Liau
# matsko - Matias Niemelä
@ -24,6 +23,7 @@
# petebacondarwin - Pete Bacon Darwin
# pkozlowski-opensource - Pawel Kozlowski
# robwormald - Rob Wormald
# tinayuangao - Tina Gao
# vicb - Victor Berchet
# vikerman - Vikram Subramanian
@ -92,7 +92,6 @@ groups:
- "*.bzl"
- "packages/bazel/*"
- "tools/bazel.rc"
- "/docs/BAZEL.md"
users:
- alexeagle #primary
- kyliau
@ -132,113 +131,42 @@ groups:
conditions:
files:
- "packages/core/*"
- "aio/content/guide/bootstrapping.md"
- "aio/content/examples/bootstrapping/*"
- "aio/content/guide/attribute-directives.md"
- "aio/content/examples/attribute-directives/*"
- "aio/content/images/guide/attribute-directives/*"
- "aio/content/guide/structural-directives.md"
- "aio/content/examples/structural-directives/*"
- "aio/content/images/guide/structural-directives/*"
- "aio/content/guide/dynamic-component-loader.md"
- "aio/content/examples/dynamic-component-loader/*"
- "aio/content/images/guide/dynamic-component-loader/*"
- "aio/content/guide/template-syntax.md"
- "aio/content/examples/template-syntax/*"
- "aio/content/images/guide/template-syntax/*"
- "aio/content/guide/dependency-injection.md"
- "aio/content/examples/dependency-injection/*"
- "aio/content/images/guide/dependency-injection/*"
- "aio/content/guide/dependency-injection-in-action.md"
- "aio/content/examples/dependency-injection-in-action/*"
- "aio/content/images/guide/dependency-injection-in-action/*"
- "aio/content/guide/hierarchical-dependency-injection.md"
- "aio/content/examples/hierarchical-dependency-injection/*"
- "aio/content/guide/singleton-services.md"
- "aio/content/guide/dependency-injection-pattern.md"
- "aio/content/guide/providers.md"
- "aio/content/examples/providers/*"
- "aio/content/guide/component-interaction.md"
- "aio/content/examples/component-interaction/*"
- "aio/content/images/guide/component-interaction/*"
- "aio/content/guide/component-styles.md"
- "aio/content/examples/component-styles/*"
- "aio/content/guide/lifecycle-hooks.md"
- "aio/content/examples/lifecycle-hooks/*"
- "aio/content/images/guide/lifecycle-hooks/*"
- "aio/content/examples/ngcontainer/*"
- "aio/content/images/guide/ngcontainer/*"
- "aio/content/guide/pipes.md"
- "aio/content/examples/pipes/*"
- "aio/content/images/guide/pipes/*"
- "aio/content/guide/entry-components.md"
- "aio/content/guide/set-document-title.md"
- "aio/content/examples/set-document-title/*"
- "aio/content/images/guide/set-document-title/*"
- "aio/content/guide/ngmodules.md"
- "aio/content/examples/ngmodules/*"
- "aio/content/examples/ngmodule/*"
- "aio/content/images/guide/ngmodule/*"
- "aio/content/guide/ngmodule-faq.md"
- "aio/content/examples/ngmodule-faq/*"
- "aio/content/guide/module-types.md"
- "aio/content/guide/sharing-ngmodules.md"
- "aio/content/guide/frequent-ngmodules.md"
- "aio/content/images/guide/frequent-ngmodules/*"
- "aio/content/guide/ngmodule-api.md"
- "aio/content/guide/ngmodule-vs-jsmodule.md"
- "aio/content/guide/feature-modules.md"
- "aio/content/examples/feature-modules/*"
- "aio/content/images/guide/feature-modules/*"
- "aio/content/guide/lazy-loading-ngmodules.md"
- "aio/content/examples/lazy-loading-ngmodules/*"
- "aio/content/images/guide/lazy-loading-ngmodules"
users:
- mhevery #primary
- jasonaden
- kara
- vicb
- IgorMinar
- jenniferfell #docs only
- IgorMinar #fallback
animations:
conditions:
files:
- "packages/animations/*"
- "packages/platform-browser/animations/*"
- "aio/content/guide/animations.md"
- "aio/content/examples/animations/*"
- "aio/content/images/guide/animations/*"
users:
- matsko #primary
- mhevery #fallback
- IgorMinar #fallback
- jenniferfell #docs only
compiler/i18n:
conditions:
files:
- "packages/compiler/src/i18n/*"
- "aio/content/guide/i18n.md"
- "aio/content/examples/i18n/*"
users:
- vicb #primary
- alxhub
- IgorMinar #fallback
- mhevery #fallback
- jenniferfell #docs only
compiler:
conditions:
files:
- "packages/compiler/*"
- "aio/content/guide/aot-compiler.md"
users:
- alxhub #primary
- vicb
- mhevery
- IgorMinar #fallback
- jenniferfell #docs only
compiler-cli/ngtools:
conditions:
@ -247,6 +175,7 @@ groups:
users:
- hansl
- filipesilva #fallback
- brocco #fallback
- IgorMinar #fallback
compiler-cli:
@ -282,97 +211,57 @@ groups:
files:
- "packages/forms/*"
- "aio/content/guide/forms.md"
- "aio/content/examples/forms/*"
- "aio/content/images/guide/forms/*"
- "aio/content/guide/form-validation.md"
- "aio/content/examples/form-validation/*"
- "aio/content/images/guide/form-validation/*"
- "aio/content/guide/dynamic-form.md"
- "aio/content/examples/dynamic-form/*"
- "aio/content/images/guide/dynamic-form/*"
- "aio/content/guide/reactive-forms.md"
- "aio/content/examples/forms/*"
- "aio/content/examples/form-validation/*"
- "aio/content/examples/reactive-forms/*"
- "aio/content/images/guide/reactive-forms/*"
users:
- kara #primary
- tinayuangao #secondary
- IgorMinar #fallback
- mhevery #fallback
- jenniferfell #docs only
http:
conditions:
files:
- "packages/common/http/*"
- "packages/http/*"
- "aio/content/guide/http.md"
- "aio/content/examples/http/*"
- "aio/content/images/guide/http/*"
users:
- alxhub #primary
- IgorMinar
- mhevery #fallback
- jenniferfell #docs only
language-service:
conditions:
files:
- "packages/language-service/*"
- "aio/content/guide/language-service.md"
- "aio/content/images/guide/language-service/*"
users:
- kyliau #primary
# needs secondary
- vicb
- IgorMinar #fallback
- mhevery #fallback
- jenniferfell #docs only
router:
conditions:
files:
- "packages/router/*"
- "aio/content/guide/router.md"
- "aio/content/examples/router/*"
- "aio/content/images/guide/router/*"
users:
- jasonaden #primary
- vicb
- IgorMinar #fallback
- mhevery #fallback
- jenniferfell #docs only
testing:
conditions:
files:
- "*/testing/*"
- "aio/content/guide/testing.md"
- "aio/content/examples/testing/*"
- "aio/content/images/guide/testing/*"
users:
- vikerman
- IgorMinar #fallback
- mhevery #fallback
- jenniferfell #docs only
upgrade:
conditions:
files:
- "packages/upgrade/*"
- "aio/content/guide/upgrade.md"
- "aio/content/examples/upgrade-module/*"
- "aio/content/images/guide/upgrade/*"
- "aio/content/examples/upgrade-phonecat-1-typescript/*"
- "aio/content/examples/upgrade-phonecat-2-hybrid/*"
- "aio/content/examples/upgrade-phonecat-3-final/*"
- "aio/content/guide/upgrade-performance.md"
- "aio/content/guide/ajs-quick-reference.md"
- "aio/content/examples/ajs-quick-reference/*"
users:
- petebacondarwin #primary
- gkalpak
- IgorMinar #fallback
- mhevery #fallback
- jenniferfell #docs only
platform-browser:
conditions:
@ -388,15 +277,12 @@ groups:
conditions:
files:
- "packages/platform-server/*"
- "aio/content/guide/universal.md"
- "aio/content/examples/universal/*"
users:
- vikerman #primary
- alxhub #secondary
- vicb
- IgorMinar #fallback
- mhevery #fallback
- jenniferfell #docs only
platform-webworker:
conditions:
@ -412,34 +298,22 @@ groups:
conditions:
files:
- "packages/service-worker/*"
- "aio/content/guide/service-worker-getting-started.md"
- "aio/content/examples/service-worker-getting-started/*"
- "aio/content/guide/service-worker-communications.md"
- "aio/content/guide/service-worker-config.md"
- "aio/content/guide/service-worker-devops.md"
- "aio/content/guide/service-worker-intro.md"
- "aio/content/images/guide/service-worker/*"
users:
- gkalpak #primary
- alxhub
- IgorMinar
- alxhub #primary
- gkalpak
- IgorMinar #fallback
- mhevery #fallback
- jenniferfell #docs only
elements:
conditions:
files:
- "packages/elements/*"
- "aio/content/examples/elements/*"
- "aio/content/images/guide/elements/*"
- "aio/content/guide/elements.md"
users:
- andrewseguin #primary
- gkalpak
- robwormald
- IgorMinar #fallback
- mhevery #fallback
- jenniferfell #docs only
benchpress:
conditions:
@ -451,7 +325,7 @@ groups:
- IgorMinar #fallback
- mhevery #fallback
docs-infra:
angular.io:
conditions:
files:
include:
@ -464,7 +338,7 @@ groups:
- gkalpak
- mhevery #fallback
docs/guide-and-tutorial:
angular.io-guide-and-tutorial:
conditions:
files:
include:
@ -474,20 +348,19 @@ groups:
- "aio/content/navigation.json"
- "aio/content/license.md"
users:
- kapunahelewong
- stephenfluin
- jenniferfell
- brandonroberts
- petebacondarwin
- gkalpak
- IgorMinar
- brandonroberts
- mhevery #fallback
docs/marketing:
angular.io-marketing:
conditions:
files:
include:
- "aio/content/marketing/*"
- "aio/content/images/marketing/*"
- "aio/content/navigation.json"
- "aio/content/license.md"
users:
@ -497,43 +370,3 @@ groups:
- IgorMinar
- robwormald
- mhevery #fallback
docs/observables:
conditions:
files:
- "aio/content/examples/observables/*"
- "aio/content/images/guide/observables/*"
- "aio/content/guide/observables.md"
- "aio/content/guide/comparing-observables.md"
- "aio/content/examples/observables-in-angular/*"
- "aio/content/images/guide/observables-in-angular/*"
- "aio/content/guide/observables-in-angular.md"
- "aio/content/examples/practical-observable-usage/*"
- "aio/content/guide/practical-observable-usage.md"
- "aio/content/examples/rx-library/*"
- "aio/content/guide/rx-library.md"
users:
- jasonaden
- benlesh
- IgorMinar
- mhevery
- jenniferfell #docs only
docs/packaging:
conditions:
files:
- "aio/content/guide/npm-packages.md"
- "aio/content/guide/browser-support.md"
- "aio/content/guide/typescript-configuration.md"
- "aio/content/guide/setup-systemjs-anatomy.md"
- "aio/content/examples/setup/*"
- "aio/content/guide/setup.md"
- "aio/content/guide/deployment.md"
- "aio/content/guide/releases.md"
- "aio/content/guide/updating.md"
users:
- IgorMinar #primary
- alexeagle
- hansl
- mhevery #fallback
- jenniferfell #docs only

View File

@ -5,41 +5,53 @@ 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 = "@nodejs//:yarn",
actual = "@yarn//:yarn",
)
alias(
node_modules_filegroup(
name = "node_modules",
actual = "@angular_deps//: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",
],
)
filegroup(
name = "web_test_bootstrap_scripts",
# do not sort
srcs = [
"@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",
"//: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",
],
)
filegroup(
name = "angularjs_scripts",
name = "angularjs",
# do not sort
srcs = [
"@angular_deps//:node_modules/angular-1.5/angular.js",
"@angular_deps//:node_modules/angular-1.6/angular.js",
"@angular_deps//:node_modules/angular-mocks-1.5/angular-mocks.js",
"@angular_deps//:node_modules/angular-mocks-1.6/angular-mocks.js",
"@angular_deps//:node_modules/angular-mocks/angular-mocks.js",
"@angular_deps//:node_modules/angular/angular.js",
"//:node_modules/angular/angular.js",
"//:node_modules/angular-mocks/angular-mocks.js",
],
)

View File

@ -1,236 +1,3 @@
<a name="7.0.0-beta.2"></a>
# [7.0.0-beta.2](https://github.com/angular/angular/compare/7.0.0-beta.1...7.0.0-beta.2) (2018-08-15)
### Bug Fixes
* **bazel:** correct type concatenated to devmode_js ([#25467](https://github.com/angular/angular/issues/25467)) ([fb2c524](https://github.com/angular/angular/commit/fb2c524))
* **service-worker:** `Cache-Control: no-cache` on assets breaks service worker ([#25408](https://github.com/angular/angular/issues/25408)) ([01ec5fd](https://github.com/angular/angular/commit/01ec5fd)), closes [#25442](https://github.com/angular/angular/issues/25442)
<a name="7.0.0-beta.1"></a>
# [7.0.0-beta.1](https://github.com/angular/angular/compare/7.0.0-beta.0...7.0.0-beta.1) (2018-08-08)
### Bug Fixes
* **compiler-cli:** use the oldProgram option in watch mode ([#21364](https://github.com/angular/angular/issues/21364)) ([c6e5b97](https://github.com/angular/angular/commit/c6e5b97)), closes [#21361](https://github.com/angular/angular/issues/21361)
* **core:** In Testability.whenStable update callback, pass more complete ([#25010](https://github.com/angular/angular/issues/25010)) ([16c03c0](https://github.com/angular/angular/commit/16c03c0))
* add mappings for ngfactory & ngsummary files to their module names in aot summary resolver ([#25335](https://github.com/angular/angular/issues/25335)) ([02e201a](https://github.com/angular/angular/commit/02e201a))
* **router:** take base uri into account in `setUpLocationSync()` ([#20244](https://github.com/angular/angular/issues/20244)) ([ba1e25f](https://github.com/angular/angular/commit/ba1e25f)), closes [#20061](https://github.com/angular/angular/issues/20061)
### Features
* **core:** add DoBootstrap interface. ([#24558](https://github.com/angular/angular/issues/24558)) ([732026c](https://github.com/angular/angular/commit/732026c)), closes [#24557](https://github.com/angular/angular/issues/24557)
<a name="6.1.2"></a>
## [6.1.2](https://github.com/angular/angular/compare/6.1.1...6.1.2) (2018-08-08)
### Bug Fixes
* **router:** take base uri into account in `setUpLocationSync()` ([#20244](https://github.com/angular/angular/issues/20244)) ([ae9b4e6](https://github.com/angular/angular/commit/ae9b4e6)), closes [#20061](https://github.com/angular/angular/issues/20061)
* add mappings for ngfactory & ngsummary files to their module names in aot summary resolver ([#25335](https://github.com/angular/angular/issues/25335)) ([054fbbe](https://github.com/angular/angular/commit/054fbbe))
<a name="7.0.0-beta.0"></a>
# [7.0.0-beta.0](https://github.com/angular/angular/compare/6.1.0...7.0.0-beta.0) (2018-08-02)
### Bug Fixes
* **bazel:** allow compile_strategy to be (privately) imported ([#25080](https://github.com/angular/angular/issues/25080)) ([0d1d589](https://github.com/angular/angular/commit/0d1d589))
* **compiler:** update compiler to flatten nested template fns ([#24943](https://github.com/angular/angular/issues/24943)) ([fe14f18](https://github.com/angular/angular/commit/fe14f18))
* **compiler-cli:** correct realPath to realpath. ([#25023](https://github.com/angular/angular/issues/25023)) ([01e6dab](https://github.com/angular/angular/commit/01e6dab))
* **core:** throw error message when @Output not initialized ([#19116](https://github.com/angular/angular/issues/19116)) ([adf510f](https://github.com/angular/angular/commit/adf510f)), closes [#3664](https://github.com/angular/angular/issues/3664)
### Features
* **compiler:** add "original" placeholder value on extracted XMB ([#25079](https://github.com/angular/angular/issues/25079)) ([e99d860](https://github.com/angular/angular/commit/e99d860))
<a name="6.1.1"></a>
## [6.1.1](https://github.com/angular/angular/compare/6.1.0...6.1.1) (2018-08-02)
* **compiler-cli:** correct tsickle dependency version to fix typescript 2.9 compatibility ([fec29fa](https://github.com/angular/angular/commit/317c7087c56b72aa74cd6d6a8f719e6e7fec29fa))
<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:** 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:** 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:** 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:** 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)
* **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:** 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:** 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))
(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))
### Angular Labs (experimental feature) breaking change
* **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)
@ -275,7 +42,7 @@ For example:
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).
@ -294,6 +61,7 @@ To learn about the release highlights and our new CLI-powered update workflow fo
* **animations:** only use the WA-polyfill alongside AnimationBuilder ([#22143](https://github.com/angular/angular/issues/22143)) ([b2f366b](https://github.com/angular/angular/commit/b2f366b)), closes [#17496](https://github.com/angular/angular/issues/17496)
* **animations:** expose `element` and `params` within transition matchers ([#22693](https://github.com/angular/angular/issues/22693)) ([58b94e6](https://github.com/angular/angular/commit/58b94e6))
* **common:** better error message when non-template element used in NgIf ([#22274](https://github.com/angular/angular/issues/22274)) ([67cf11d](https://github.com/angular/angular/commit/67cf11d)), closes [#16410](https://github.com/angular/angular/issues/16410)
* **common:** better error message when non-template element used in NgIf ([#22274](https://github.com/angular/angular/issues/22274)) ([67cf11d](https://github.com/angular/angular/commit/67cf11d)), closes [#16410](https://github.com/angular/angular/issues/16410)
* **common:** export functions to format numbers, percents, currencies & dates ([#22423](https://github.com/angular/angular/issues/22423)) ([4180912](https://github.com/angular/angular/commit/4180912)), closes [#20536](https://github.com/angular/angular/issues/20536)
* **compiler:** lower @NgModule ids if needed ([#23031](https://github.com/angular/angular/issues/23031)) ([bd024c0](https://github.com/angular/angular/commit/bd024c0))
* **compiler:** implement "enableIvy" compiler option ([#21427](https://github.com/angular/angular/issues/21427)) ([64d16de](https://github.com/angular/angular/commit/64d16de))
@ -333,6 +101,7 @@ To learn about the release highlights and our new CLI-powered update workflow fo
* **animations:** report correct totalTime value even during noOp animations ([#22225](https://github.com/angular/angular/issues/22225)) ([e1bf067](https://github.com/angular/angular/commit/e1bf067))
* **animations:** avoid animation insertions during router back/refresh ([#21977](https://github.com/angular/angular/issues/21977)) ([f88fba0](https://github.com/angular/angular/commit/f88fba0)), closes [#19712](https://github.com/angular/angular/issues/19712)
* **animations:** treat numeric state name values as strings ([#22923](https://github.com/angular/angular/issues/22923)) ([e5e1b0d](https://github.com/angular/angular/commit/e5e1b0d))
* **animations:** report correct totalTime value even during noOp animations ([#22225](https://github.com/angular/angular/issues/22225)) ([e1bf067](https://github.com/angular/angular/commit/e1bf067))
* **animations:** fix increment/decrement aliases example ([#18323](https://github.com/angular/angular/issues/18323)) ([d2aa8ac](https://github.com/angular/angular/commit/d2aa8ac))
* **common:** NgClass should properly take className changes into account ([#21937](https://github.com/angular/angular/issues/21937)) ([4a42669](https://github.com/angular/angular/commit/4a42669)), closes [#21932](https://github.com/angular/angular/issues/21932)
* **common:** fix the titlecase pipe ([#22600](https://github.com/angular/angular/issues/22600)) ([7966744](https://github.com/angular/angular/commit/7966744))
@ -424,10 +193,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
{
# ...
@ -437,12 +206,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.
@ -455,35 +224,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.

View File

@ -227,15 +227,10 @@ 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
* **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`).
* **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`)
### Subject
The subject contains a succinct description of the change:
@ -274,7 +269,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>

100
WORKSPACE
View File

@ -6,30 +6,23 @@ workspace(name = "angular")
http_archive(
name = "build_bazel_rules_nodejs",
urls = ["https://github.com/bazelbuild/rules_nodejs/archive/0.11.4.zip"],
strip_prefix = "rules_nodejs-0.11.4",
sha256 = "c31c4ead696944a50fad2b3ee9dfbbeffe31a8dcca0b21b9bf5b3e6c6b069801",
)
http_archive(
name = "bazel_skylib",
urls = ["https://github.com/bazelbuild/bazel-skylib/archive/0.3.1.zip"],
strip_prefix = "bazel-skylib-0.3.1",
sha256 = "95518adafc9a2b656667bbf517a952e54ce7f350779d0dd95133db4eb5c27fb1",
url = "https://github.com/bazelbuild/rules_nodejs/archive/0.8.0.zip",
strip_prefix = "rules_nodejs-0.8.0",
sha256 = "4e40dd49ae7668d245c3107645f2a138660fcfd975b9310b91eda13f0c973953",
)
http_archive(
name = "io_bazel_rules_webtesting",
url = "https://github.com/bazelbuild/rules_webtesting/archive/0.2.1.zip",
strip_prefix = "rules_webtesting-0.2.1",
sha256 = "7d490aadff9b5262e5251fa69427ab2ffd1548422467cb9f9e1d110e2c36f0fa",
url = "https://github.com/bazelbuild/rules_webtesting/archive/v0.2.0.zip",
strip_prefix = "rules_webtesting-0.2.0",
sha256 = "cecc12f07e95740750a40d38e8b14b76fefa1551bef9332cb432d564d693723c",
)
http_archive(
name = "build_bazel_rules_typescript",
url = "https://github.com/bazelbuild/rules_typescript/archive/0.16.0.zip",
strip_prefix = "rules_typescript-0.16.0",
sha256 = "e65c5639a42e2f6d3f9d2bda62487d6b42734830dda45be1620c3e2b1115070c",
url = "https://github.com/bazelbuild/rules_typescript/archive/3b86d6d46269fb52d4c6f1416868869e847feac2.zip",
strip_prefix = "rules_typescript-3b86d6d46269fb52d4c6f1416868869e847feac2",
sha256 = "f67e5fbe4a2b34b3ead9fe56f22b713540c23b501bd24d661d3fb047071dc2c1",
)
http_archive(
@ -38,23 +31,16 @@ http_archive(
sha256 = "feba3278c13cde8d67e341a837f69a029f698d7a27ddbb2a202be7a10b22142a",
)
http_archive(
name = "io_bazel_rules_sass",
url = "https://github.com/bazelbuild/rules_sass/archive/0.1.0.zip",
strip_prefix = "rules_sass-0.1.0",
sha256 = "b243c4d64f054c174051785862ab079050d90b37a1cef7da93821c6981cb9ad4",
)
# 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"
BAZEL_BUILDTOOLS_VERSION = "fd9878fd5de921e0bbab3dcdcb932c2627812ee1"
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",
sha256 = "27bb461ade23fd44ba98723ad98f84ee9c83cd3540b773b186a1bc5037f3d862",
)
# Fetching the Bazel source code allows us to compile the Skylark linter
@ -65,14 +51,6 @@ http_archive(
sha256 = "e373d2ae24955c1254c495c9c421c009d88966565c35e4e8444c082cb1f0f48f",
)
http_archive(
name = "io_bazel_skydoc",
# TODO: switch to upstream when https://github.com/bazelbuild/skydoc/pull/103 is merged
url = "https://github.com/alexeagle/skydoc/archive/fe2e9f888d28e567fef62ec9d4a93c425526d701.zip",
strip_prefix = "skydoc-fe2e9f888d28e567fef62ec9d4a93c425526d701",
sha256 = "7bfb5545f59792a2745f2523b9eef363f9c3e7274791c030885e7069f8116016",
)
# 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
@ -80,10 +58,10 @@ http_archive(
# 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",
name = "angular_devkit",
url = "https://github.com/angular/devkit/archive/v0.3.1.zip",
strip_prefix = "devkit-0.3.1",
sha256 = "31d4b597fe9336650acf13df053c1c84dcbe9c29c6a833bcac3819cd3fd8cad3",
)
http_archive(
@ -93,33 +71,14 @@ http_archive(
sha256 = "8a517806d2b7c8505ba5c53934e7d7c70d341b68ffd268e9044d35b564a48828",
)
#
# Point Bazel to WORKSPACEs that live in subdirectories
#
local_repository(
name = "rxjs",
path = "node_modules/rxjs/src",
)
# Point to the integration test workspace just so that Bazel doesn't descend into it
# when expanding the //... pattern
local_repository(
name = "bazel_integration_test",
path = "integration/bazel",
)
#
# Load and install our dependencies downloaded above.
#
load("@build_bazel_rules_nodejs//:defs.bzl", "check_bazel_version", "node_repositories", "yarn_install")
check_bazel_version("0.15.0")
node_repositories(
package_json = ["//:package.json"],
preserve_symlinks = True,
)
check_bazel_version("0.13.0")
node_repositories(package_json = ["//:package.json"])
load("@io_bazel_rules_go//go:def.bzl", "go_rules_dependencies", "go_register_toolchains")
@ -138,9 +97,21 @@ load("@build_bazel_rules_typescript//:defs.bzl", "ts_setup_workspace")
ts_setup_workspace()
load("@angular//:index.bzl", "ng_setup_workspace")
#
# Point Bazel to WORKSPACEs that live in subdirectories
#
ng_setup_workspace()
local_repository(
name = "rxjs",
path = "node_modules/rxjs/src",
)
# Point to the integration test workspace just so that Bazel doesn't descend into it
# when expanding the //... pattern
local_repository(
name = "bazel_integration_test",
path = "integration/bazel",
)
#
# Ask Bazel to manage these toolchain dependencies for us.
@ -159,12 +130,3 @@ yarn_install(
package_json = "//tools/http-server:package.json",
yarn_lock = "//tools/http-server:yarn.lock",
)
##################################
# Skylark documentation generation
load("@io_bazel_rules_sass//sass:sass_repositories.bzl", "sass_repositories")
sass_repositories()
load("@io_bazel_skydoc//skylark:skylark.bzl", "skydoc_repositories")
skydoc_repositories()

View File

@ -8,7 +8,7 @@ Everything in this folder is part of the documentation project. This includes
## Developer tasks
We use [Yarn](https://yarnpkg.com) to manage the dependencies and to run build tasks.
We use `yarn` to manage the dependencies and to run build tasks.
You should run all these tasks from the `angular/aio` folder.
Here are the most important tasks you might need to use:
@ -43,17 +43,6 @@ Here are the most important tasks you might need to use:
* `yarn build-ie-polyfills` - generates a js file of polyfills that can be loaded in Internet Explorer.
## Developing on Windows
The `packages/` directory may contain Linux-specific symlinks, which are not recognized by Windows.
These unresolved links cause the docs generation process to fail because it cannot locate certain files.
> Hint: The following steps require administration rights or [Windows Developer Mode](https://docs.microsoft.com/en-us/windows/uwp/get-started/enable-your-device-for-development) enabled!
To fix this problem, run `scripts/windows/create-symlinks.sh`. This command creates temporary files where the symlinks used to be. Make sure not to commit those files with your documentation changes.
When you are done making and testing your documentation changes, you can restore the original symlinks and delete the temporary files by running `scripts/windows/remove-symlinks.sh`.
It's necessary to remove the temporary files, because otherwise they're displayed as local changes in your git working copy and certain operations are blocked.
## Using ServiceWorker locally
Since abb36e3cb, running `yarn start --prod` will no longer set up the ServiceWorker, which

View File

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

View File

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

View File

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

View File

@ -8,7 +8,7 @@
"scripts": {
"prebuild": "yarn clean-dist",
"build": "tsc",
"build-watch": "yarn build --watch",
"build-watch": "yarn tsc --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.8.0",
"@types/shelljs": "^0.7.4",
"@types/supertest": "^2.0.3",
"concurrently": "^3.5.0",
"nodemon": "^1.12.1",

View File

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

View File

@ -60,8 +60,6 @@ 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

View File

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

View File

@ -1,38 +1,26 @@
import { Injector } from '@angular/core';
import { ReflectiveInjector } from '@angular/core';
import { Car, Engine, Tires } from './car';
import { Logger } from '../logger.service';
// #docregion injector
export function useInjector() {
let injector: Injector;
let injector: ReflectiveInjector;
// #enddocregion injector
/*
// #docregion injector-no-new
// Cannot instantiate an Injector like this!
let injector = new Injector([
{ provide: Car, deps: [Engine, Tires] },
{ provide: Engine, deps: [] },
{ provide: Tires, deps: [] }
]);
// Cannot instantiate an ReflectiveInjector like this!
let injector = new ReflectiveInjector([Car, Engine, Tires]);
// #enddocregion injector-no-new
*/
// #docregion injector, injector-create-and-call
injector = Injector.create({
providers: [
{ provide: Car, deps: [Engine, Tires] },
{ provide: Engine, deps: [] },
{ provide: Tires, deps: [] }
]
});
injector = ReflectiveInjector.resolveAndCreate([Car, Engine, Tires]);
// #docregion injector-call
let car = injector.get(Car);
// #enddocregion injector-call, injector-create-and-call
car.description = 'Injector';
injector = Injector.create({
providers: [{ provide: Logger, deps: [] }]
});
injector = ReflectiveInjector.resolveAndCreate([Logger]);
let logger = injector.get(Logger);
logger.log('Injector car.drive() said: ' + car.drive());
return car;

View File

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

View File

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

View File

@ -1,5 +1,6 @@
// #docregion
import { Component, Injector } from '@angular/core';
import { createCustomElement } from '@angular/elements';
import { createNgElementConstructor } from '../elements-dist';
import { PopupService } from './popup.service';
import { PopupComponent } from './popup.component';
@ -7,15 +8,19 @@ 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(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);
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);
}
}

View File

@ -1,21 +1,22 @@
import { NgModule } from '@angular/core';
// #docregion
import { BrowserModule } from '@angular/platform-browser';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { NgModule } from '@angular/core';
import { AppComponent } from './app.component';
import { PopupComponent } from './popup.component';
import { PopupService } from './popup.service';
import { PopupComponent } from './popup.component';
// 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 {}

View File

@ -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: `
<span>Popup: {{message}}</span>
<button (click)="closed.next()">&#x2716;</button>
`,
template: 'Popup: {{message}}',
host: {
'[@state]': 'state',
'(@state.done)': 'onAnimationDone($event)',
},
animations: [
trigger('state', [
@ -27,17 +27,13 @@ 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';
@ -45,10 +41,18 @@ 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();
}
}
}

View File

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

View File

@ -1,9 +0,0 @@
{
"description": "Angular Elements",
"files":[
"!**/*.d.ts",
"!**/*.js",
"!**/*.[1].*"
],
"tags":["cookbook"]
}

View File

@ -1,14 +1,15 @@
// #docplaster
// #docregion app-module
import { HttpClientModule } from '@angular/common/http';
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { BrowserModule } from '@angular/platform-browser';
import { HttpClientModule } from '@angular/common/http';
import { AppComponent } from './app.component';
// import the feature module here so you can add it to the imports array below
import { CustomerDashboardModule } from './customer-dashboard/customer-dashboard.module';
@NgModule({
declarations: [
AppComponent

View File

@ -16,8 +16,6 @@ describe('Form Validation Tests', function () {
tests('Template-Driven Form');
bobTests();
asyncValidationTests();
crossValidationTests();
});
describe('Reactive form', () => {
@ -27,8 +25,6 @@ describe('Form Validation Tests', function () {
tests('Reactive Form');
bobTests();
asyncValidationTests();
crossValidationTests();
});
});
@ -46,9 +42,7 @@ let page: {
powerOption: ElementFinder,
errorMessages: ElementArrayFinder,
heroFormButtons: ElementArrayFinder,
heroSubmitted: ElementFinder,
alterEgoErrors: ElementFinder,
crossValidationErrorMessage: ElementFinder,
heroSubmitted: ElementFinder
};
function getPage(sectionTag: string) {
@ -65,9 +59,7 @@ 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')),
alterEgoErrors: section.element(by.css('.alter-ego-errors')),
crossValidationErrorMessage: section.element(by.css('.cross-validation-error-message')),
heroSubmitted: section.element(by.css('.submitted-message'))
};
}
@ -160,16 +152,6 @@ function expectFormIsInvalid() {
expect(page.form.getAttribute('class')).toMatch('ng-invalid');
}
function triggerAlterEgoValidation() {
// alterEgo has updateOn set to 'blur', click outside of the input to trigger the blur event
element(by.css('app-root')).click()
}
function waitForAlterEgoValidation() {
// alterEgo async validation will be performed in 400ms
browser.sleep(400);
}
function bobTests() {
const emsg = 'Name cannot be Bob.';
@ -190,61 +172,3 @@ function bobTests() {
expectFormIsValid();
});
}
function asyncValidationTests() {
const emsg = 'Alter ego is already taken.';
it(`should produce "${emsg}" error after setting alterEgo to Eric`, function () {
page.alterEgoInput.clear();
page.alterEgoInput.sendKeys('Eric');
triggerAlterEgoValidation();
waitForAlterEgoValidation();
expectFormIsInvalid();
expect(page.alterEgoErrors.getText()).toBe(emsg);
});
it('should be ok again with different values', function () {
page.alterEgoInput.clear();
page.alterEgoInput.sendKeys('John');
triggerAlterEgoValidation();
waitForAlterEgoValidation();
expectFormIsValid();
expect(page.alterEgoErrors.isPresent()).toBe(false);
});
}
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');
triggerAlterEgoValidation();
waitForAlterEgoValidation();
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');
triggerAlterEgoValidation();
waitForAlterEgoValidation();
expectFormIsValid();
expect(page.crossValidationErrorMessage.isPresent()).toBe(false);
});
}

View File

@ -7,8 +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';
import { UniqueAlterEgoValidatorDirective } from './shared/alter-ego.directive';
@NgModule({
imports: [
@ -20,9 +19,7 @@ import { UniqueAlterEgoValidatorDirective } from './shared/alter-ego.directive';
AppComponent,
HeroFormTemplateComponent,
HeroFormReactiveComponent,
ForbiddenValidatorDirective,
IdentityRevealedValidatorDirective,
UniqueAlterEgoValidatorDirective
ForbiddenValidatorDirective
],
bootstrap: [ AppComponent ]
})

View File

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

View File

@ -1,48 +0,0 @@
/* 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';
import { UniqueAlterEgoValidator } from '../shared/alter-ego.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;
ngOnInit(): void {
// #docregion async-validation
this.heroForm = new FormGroup({
'name': new FormControl(this.hero.name, [
Validators.required,
Validators.minLength(4),
forbiddenNameValidator(/bob/i)
]),
'alterEgo': new FormControl(this.hero.alterEgo, {
asyncValidators: [this.alterEgoValidator.validate.bind(this.alterEgoValidator)],
updateOn: 'blur'
}),
'power': new FormControl(this.hero.power, Validators.required)
});
// #enddocregion async-validation
}
get name() { return this.heroForm.get('name'); }
get power() { return this.heroForm.get('power'); }
get alterEgo() { return this.heroForm.get('alterEgo'); }
// #docregion async-validation
constructor(private alterEgoValidator: UniqueAlterEgoValidator) {}
// #enddocregion async-validation
}

View File

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

View File

@ -7,48 +7,33 @@
<div [hidden]="formDir.submitted">
<div class="cross-validation" [class.cross-validation-error]="heroForm.errors?.identityRevealed && (heroForm.touched || heroForm.dirty)">
<div class="form-group">
<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>
<div *ngIf="name.errors.required">
Name is required.
</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 *ngIf="alterEgo.pending">Validating...</div>
<div *ngIf="alterEgo.invalid" class="alert alert-danger alter-ego-errors">
<div *ngIf="alterEgo.errors?.uniqueAlterEgo">
Alter ego is already taken.
</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>
<!-- #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 class="form-group">
<label for="alterEgo">Alter Ego</label>
<input id="alterEgo" class="form-control"
formControlName="alterEgo" >
</div>
<div class="form-group">

View File

@ -1,44 +1,40 @@
/* 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';
import { UniqueAlterEgoValidator } from '../shared/alter-ego.directive';
@Component({
selector: 'app-hero-form-reactive',
templateUrl: './hero-form-reactive.component.html',
styleUrls: ['./hero-form-reactive.component.css'],
templateUrl: './hero-form-reactive.component.html'
})
export class HeroFormReactiveComponent implements OnInit {
powers = ['Really Smart', 'Super Flexible', 'Weather Changer'];
hero = { name: 'Dr.', alterEgo: 'Dr. What', power: this.powers[0] };
hero = {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)
forbiddenNameValidator(/bob/i) // <-- Here's how you pass in the custom validator.
]),
'alterEgo': new FormControl(this.hero.alterEgo, {
asyncValidators: [this.alterEgoValidator.validate.bind(this.alterEgoValidator)],
updateOn: 'blur'
}),
'alterEgo': new FormControl(this.hero.alterEgo),
'power': new FormControl(this.hero.power, Validators.required)
}, { validators: identityRevealedValidator }); // <-- add custom validator at the FormGroup level
});
// #enddocregion custom-validator
}
get name() { return this.heroForm.get('name'); }
get power() { return this.heroForm.get('power'); }
get alterEgo() { return this.heroForm.get('alterEgo'); }
constructor(private alterEgoValidator: UniqueAlterEgoValidator) { }
// #enddocregion form-group
}
// #enddocregion

View File

@ -1,46 +0,0 @@
import { Directive, forwardRef, Injectable } from '@angular/core';
import {
AsyncValidator,
AbstractControl,
NG_ASYNC_VALIDATORS,
ValidationErrors
} from '@angular/forms';
import { catchError, map } from 'rxjs/operators';
import { HeroesService } from './heroes.service';
import { Observable } from 'rxjs';
// #docregion async-validator
@Injectable({ providedIn: 'root' })
export class UniqueAlterEgoValidator implements AsyncValidator {
constructor(private heroesService: HeroesService) {}
validate(
ctrl: AbstractControl
): Promise<ValidationErrors | null> | Observable<ValidationErrors | null> {
return this.heroesService.isAlterEgoTaken(ctrl.value).pipe(
map(isTaken => (isTaken ? { uniqueAlterEgo: true } : null)),
catchError(() => null)
);
}
}
// #enddocregion async-validator
// #docregion async-validator-directive
@Directive({
selector: '[appUniqueAlterEgo]',
providers: [
{
provide: NG_ASYNC_VALIDATORS,
useExisting: forwardRef(() => UniqueAlterEgoValidator),
multi: true
}
]
})
export class UniqueAlterEgoValidatorDirective {
constructor(private validator: UniqueAlterEgoValidator) {}
validate(control: AbstractControl) {
this.validator.validate(control);
}
}
// #enddocregion async-validator-directive

View File

@ -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} | null => {
return (control: AbstractControl): {[key: string]: any} => {
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} | null {
validate(control: AbstractControl): {[key: string]: any} {
return this.forbiddenName ? forbiddenNameValidator(new RegExp(this.forbiddenName, 'i'))(control)
: null;
}

View File

@ -1,14 +0,0 @@
import { Injectable } from '@angular/core';
import { Observable, of } from 'rxjs';
import { delay } from 'rxjs/operators';
const ALTER_EGOS = ['Eric'];
@Injectable({ providedIn: 'root' })
export class HeroesService {
isAlterEgoTaken(alterEgo: string): Observable<boolean> {
const isTaken = ALTER_EGOS.includes(alterEgo);
return of(isTaken).pipe(delay(400));
}
}

View File

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

View File

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

View File

@ -2,60 +2,41 @@
<div class="container">
<h1>Template-Driven Form</h1>
<!-- #docregion cross-validation-register-validator -->
<form #heroForm="ngForm" appIdentityRevealed>
<!-- #enddocregion cross-validation-register-validator -->
<!-- #docregion form-tag-->
<form #heroForm="ngForm">
<!-- #enddocregion form-tag-->
<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 *ngIf="name.invalid && (name.dirty || name.touched)"
class="alert alert-danger">
<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.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.invalid && (name.dirty || name.touched)"
class="alert alert-danger">
<div *ngIf="name.errors.required">
Name is required.
</div>
<!-- #enddocregion name-with-error-msg -->
</div>
<div class="form-group">
<label for="alterEgo">Alter Ego</label>
<!-- #docregion async-validation -->
<input id="alterEgo" class="form-control" name="alterEgo"
#alterEgo="ngModel"
[(ngModel)]="hero.alterEgo"
[ngModelOptions]="{ updateOn: 'blur' }"
appUniqueAlterEgo>
<!-- #enddocregion async-validation -->
<div *ngIf="alterEgo.pending">Validating...</div>
<div *ngIf="alterEgo.invalid" class="alert alert-danger alter-ego-errors">
<div *ngIf="alterEgo.errors?.uniqueAlterEgo">
Alter ego is already taken.
</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>
<!-- #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 -->
<!-- #enddocregion name-with-error-msg -->
</div>
<div class="form-group">
<label for="alterEgo">Alter Ego</label>
<input id="alterEgo" class="form-control"
name="alterEgo" [(ngModel)]="hero.alterEgo" >
</div>
<div class="form-group">
@ -81,4 +62,5 @@
<button (click)="heroForm.resetForm({})">Add new hero</button>
</div>
</form>
</div>

View File

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

View File

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

View File

@ -1,9 +1,9 @@
import { HttpClientModule } from '@angular/common/http';
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { BrowserModule } from '@angular/platform-browser';
import { HttpClientModule } from '@angular/common/http';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
@NgModule({

View File

@ -4,8 +4,7 @@ button {
font-size: 100%;
}
code,
.code {
code, .code {
background-color: #eee;
color: black;
font-family: Courier, sans-serif;
@ -22,18 +21,14 @@ 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 */

View File

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

View File

@ -6,15 +6,14 @@ 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;

View File

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

View File

@ -1,6 +1,5 @@
// #docregion
import { Component, Input } from '@angular/core';
import { Hero } from './hero';
@Component({
@ -34,15 +33,11 @@ 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 ];

View File

@ -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'}
];

View File

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

View File

@ -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.slice(0).forEach(obs => obs.complete());
observers.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({

View File

@ -1,21 +1,26 @@
// #docregion
import { HttpClientModule } from '@angular/common/http';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { BrowserModule } from '@angular/platform-browser';
import { FormsModule } from '@angular/forms';
import { HttpClientModule } from '@angular/common/http';
import { AppComponent } from './app.component';
import { ExponentialStrengthPipe } from './exponential-strength.pipe';
import { FetchJsonPipe } from './fetch-json.pipe';
import { FlyingHeroesComponent, FlyingHeroesImpureComponent } from './flying-heroes.component';
import { FlyingHeroesImpurePipe, FlyingHeroesPipe } from './flying-heroes.pipe';
import {
FlyingHeroesComponent,
FlyingHeroesImpureComponent
} from './flying-heroes.component';
import { HeroAsyncMessageComponent } from './hero-async-message.component';
import { HeroBirthdayComponent } from './hero-birthday1.component';
import { HeroBirthday2Component } from './hero-birthday2.component';
import { HeroListComponent } from './hero-list.component';
import { PowerBoostCalculatorComponent } from './power-boost-calculator.component';
import { PowerBoosterComponent } from './power-booster.component';
import { PowerBoostCalculatorComponent } from './power-boost-calculator.component';
import {
FlyingHeroesPipe,
FlyingHeroesImpurePipe
} from './flying-heroes.pipe';
import { FetchJsonPipe } from './fetch-json.pipe';
import { ExponentialStrengthPipe } from './exponential-strength.pipe';
@NgModule({
imports: [
@ -38,6 +43,6 @@ import { PowerBoosterComponent } from './power-booster.component';
FetchJsonPipe,
ExponentialStrengthPipe
],
bootstrap: [AppComponent]
bootstrap: [ AppComponent ]
})
export class AppModule { }

View File

@ -1,14 +1,13 @@
// #docregion
import { HttpClient } from '@angular/common/http';
import { Pipe, PipeTransform } from '@angular/core';
import { HttpClient } from '@angular/common/http';
// #docregion pipe-metadata
@Pipe({
name: 'fetch',
pure: false
})
// #enddocregion pipe-metadata
export class FetchJsonPipe implements PipeTransform {
export class FetchJsonPipe implements PipeTransform {
private cachedData: any = null;
private cachedUrl = '';
@ -18,7 +17,7 @@ export class FetchJsonPipe implements PipeTransform {
if (url !== this.cachedUrl) {
this.cachedData = null;
this.cachedUrl = url;
this.http.get(url).subscribe(result => this.cachedData = result);
this.http.get(url).subscribe( result => this.cachedData = result );
}
return this.cachedData;

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

@ -1,10 +1,4 @@
<!-- #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 -->
<div class="container">
<h1>Reactive Forms</h1>
<app-hero-detail></app-hero-detail>
</div>

View File

@ -1,17 +1,4 @@
<!-- #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 -->
<div class="container">
<h1>Reactive Forms</h1>
<app-hero-list></app-hero-list>
</div>

View File

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

View File

@ -1,24 +1,9 @@
// #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 {
editor: EditorType = 'name';
get showNameEditor() {
return this.editor === 'name';
}
get showProfileEditor() {
return this.editor === 'profile';
}
toggleEditor(type: EditorType) {
this.editor = type;
}
}
export class AppComponent { }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,73 +0,0 @@
// #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

View File

@ -0,0 +1,17 @@
<!DOCTYPE html>
<!-- #docregion -->
<html lang="en">
<head>
<title>Hero Form</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- #docregion bootstrap -->
<link rel="stylesheet" href="https://unpkg.com/bootstrap@3.3.7/dist/css/bootstrap.min.css">
</head>
<body>
<app-root></app-root>
</body>
</html>

View File

@ -2,9 +2,10 @@
<!-- #docregion -->
<html lang="en">
<head>
<title>Angular Reactive Forms</title>
<title>Hero Form</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="https://unpkg.com/bootstrap@3.3.7/dist/css/bootstrap.min.css">
</head>
<body>

View File

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

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