Compare commits
114 Commits
10.1.4
...
10.0.0-rc.
Author | SHA1 | Date | |
---|---|---|---|
9f698b4de0 | |||
742f3d6787 | |||
323651bd38 | |||
9d397eb5a1 | |||
6114cd2bd4 | |||
d494f7bd5e | |||
ec6a7ab721 | |||
ad6d2b4619 | |||
c093390010 | |||
acd69f2be2 | |||
5d2f341653 | |||
420d1c35f5 | |||
08647267bb | |||
215d50d2f6 | |||
bf2cb6fa48 | |||
e97a2d4123 | |||
585e3f6adc | |||
7f77ce1a48 | |||
a1616ce181 | |||
1c22dff714 | |||
8d1d6e8f70 | |||
e7f4aba5a3 | |||
fdbe9f5d9f | |||
8bead6bfdd | |||
52dda73dbb | |||
31b3888a2f | |||
6f938470c2 | |||
776c4afc03 | |||
536dd647c6 | |||
51d581ab27 | |||
75294e7dad | |||
04bada7a9d | |||
2349143477 | |||
e9bff5fe9f | |||
411cb0cb92 | |||
53e1fb3554 | |||
2cb3b66640 | |||
5af3144330 | |||
e4043cbb3a | |||
fff424a35f | |||
b5d1c8b05a | |||
d713e33cc4 | |||
3d327d25f0 | |||
077283bf0f | |||
9ec25ea036 | |||
878cfe669c | |||
5f0be3cb2e | |||
9e28e14c08 | |||
954d002884 | |||
0a48591e53 | |||
d37c723951 | |||
9078ca557e | |||
2be1ef6ba0 | |||
47c02efccb | |||
d7ecfb432a | |||
59abf4a33f | |||
d6e715e726 | |||
fcfcd1037c | |||
f3ccd29e7b | |||
5c0bdae809 | |||
838902556b | |||
c6872c02d8 | |||
819982ea20 | |||
f9daa136c3 | |||
6a0d2ed6c8 | |||
2c1f35e794 | |||
5345e8da45 | |||
e35269dd87 | |||
60a03b7ef7 | |||
305b5a3887 | |||
bc549361d3 | |||
084b627f2e | |||
6755d00601 | |||
cba1da3e44 | |||
7be8bb1489 | |||
c7c0c1f626 | |||
3aa4629f92 | |||
2d86dbb090 | |||
91767ff0f9 | |||
078b004ecc | |||
930d204d83 | |||
8d82cdfc77 | |||
cb6996b5c3 | |||
a4f7740332 | |||
ba0faa2f77 | |||
3e68029522 | |||
b4e26b5828 | |||
15cf7fcac2 | |||
24ff0eb13b | |||
cf86f72eb7 | |||
61486f14f1 | |||
d16a7f3ecc | |||
82761ec50e | |||
235bfa77a9 | |||
299ae1bb1c | |||
80f7522dab | |||
028921e369 | |||
a4e11bb524 | |||
a4131752d2 | |||
060dcfbba1 | |||
4be7008f80 | |||
4a0d05515e | |||
83ab99c746 | |||
270da1f69f | |||
6b0e46e36c | |||
3642707145 | |||
0ea76edfd8 | |||
d493a83b2b | |||
f1721d5cef | |||
5b3fd6aa82 | |||
6f829180f7 | |||
27b95ba64a | |||
ef405b1e90 | |||
441073bad5 |
@ -1,3 +1,3 @@
|
||||
2.1.1
|
||||
3.2.0
|
||||
# [NB: this comment has to be after the first line, see https://github.com/bazelbuild/bazelisk/issues/117]
|
||||
# When updating the Bazel version you also need to update the RBE toolchains version in package.bzl
|
||||
|
@ -67,9 +67,6 @@ var_10: &only_on_master
|
||||
# **NOTE 1**: Pin to exact images using an ID (SHA). See https://circleci.com/docs/2.0/circleci-images/#using-a-docker-image-id-to-pin-an-image-to-a-fixed-version.
|
||||
# (Using the tag in not necessary when pinning by ID, but include it anyway for documentation purposes.)
|
||||
# **NOTE 2**: If you change the version of the docker images, also change the `cache_key` suffix.
|
||||
# **NOTE 3**: If you change the version of the `*-browsers` docker image, make sure the
|
||||
# `--versions.chrome` arg in `integration/bazel-schematics/test.sh` specifies a
|
||||
# ChromeDriver version that is compatible with the Chrome version in the image.
|
||||
executors:
|
||||
default-executor:
|
||||
parameters:
|
||||
@ -120,7 +117,7 @@ commands:
|
||||
sudo apt-get update
|
||||
# Install GTK+ graphical user interface (libgtk-3-0), advanced linux sound architecture (libasound2)
|
||||
# and network security service libraries (libnss3) & X11 Screen Saver extension library (libssx1)
|
||||
# which are dependendies of chrome & needed for karma & protractor headless chrome tests.
|
||||
# which are dependencies of chrome & needed for karma & protractor headless chrome tests.
|
||||
# This is a very small install which takes around 7s in comparing to using the full
|
||||
# circleci/node:x.x.x-browsers image.
|
||||
sudo apt-get -y install libgtk-3-0 libasound2 libnss3 libxss1
|
||||
@ -163,7 +160,7 @@ commands:
|
||||
description: Sets up a domain that resolves to the local host.
|
||||
steps:
|
||||
- run:
|
||||
name: Preparing environment for running tests on Saucelabs.
|
||||
name: Preparing environment for running tests on Sauce Labs.
|
||||
command: |
|
||||
# For SauceLabs jobs, we set up a domain which resolves to the machine which launched
|
||||
# the tunnel. We do this because devices are sometimes not able to properly resolve
|
||||
@ -175,13 +172,13 @@ commands:
|
||||
setSecretVar SAUCE_ACCESS_KEY $(echo $SAUCE_ACCESS_KEY | rev)
|
||||
- run:
|
||||
# Sets up a local domain in the machine's host file that resolves to the local
|
||||
# host. This domain is helpful in Saucelabs tests where devices are not able to
|
||||
# host. This domain is helpful in Sauce Labs tests where devices are not able to
|
||||
# properly resolve `localhost` or `127.0.0.1` through the sauce-connect tunnel.
|
||||
name: Setting up alias domain for local host.
|
||||
command: echo "127.0.0.1 $SAUCE_LOCALHOST_ALIAS_DOMAIN" | sudo tee -a /etc/hosts
|
||||
|
||||
# Normally this would be an individual job instead of a command.
|
||||
# But startup and setup time for each invidual windows job are high enough to discourage
|
||||
# But startup and setup time for each individual windows job are high enough to discourage
|
||||
# many small jobs, so instead we use a command for setup unless the gain becomes significant.
|
||||
setup_win:
|
||||
description: Setup windows node environment
|
||||
@ -599,8 +596,8 @@ jobs:
|
||||
- run:
|
||||
name: Decrypt github credentials
|
||||
# We need ensure that the same default digest is used for encoding and decoding with
|
||||
# openssl. Openssl versions might have different default digests which can cause
|
||||
# decryption failures based on the installed openssl version. https://stackoverflow.com/a/39641378/4317734
|
||||
# OpenSSL. OpenSSL versions might have different default digests which can cause
|
||||
# decryption failures based on the installed OpenSSL version. https://stackoverflow.com/a/39641378/4317734
|
||||
command: 'openssl aes-256-cbc -d -in .circleci/github_token -md md5 -k "${KEY}" -out ~/.git_credentials'
|
||||
- run: ./scripts/ci/publish-build-artifacts.sh
|
||||
|
||||
|
4
.github/ISSUE_TEMPLATE/1-bug-report.md
vendored
@ -32,13 +32,13 @@ Existing issues often contain information about workarounds, resolution, or prog
|
||||
|
||||
## 🔬 Minimal Reproduction
|
||||
<!--
|
||||
Please create and share minimal reproduction of the issue starting with this template: https://stackblitz.com/fork/angular-issue-repro2
|
||||
Please create and share minimal reproduction of the issue starting with this template: https://stackblitz.com/fork/angular-ivy
|
||||
-->
|
||||
<!-- ✍️--> https://stackblitz.com/...
|
||||
|
||||
<!--
|
||||
If StackBlitz is not suitable for reproduction of your issue, please create a minimal GitHub repository with the reproduction of the issue.
|
||||
A good way to make a minimal reproduction is to create a new app via `ng new repro-app` and add the minimum possible code to show the problem.
|
||||
A good way to make a minimal reproduction is to create a new app via `ng new repro-app` and add the minimum possible code to show the problem.
|
||||
Share the link to the repo below along with step-by-step instructions to reproduce the problem, as well as expected and actual behavior.
|
||||
|
||||
Issues that don't have enough info and can't be reproduced will be closed.
|
||||
|
@ -1,6 +1,4 @@
|
||||
import {exec} from 'shelljs';
|
||||
|
||||
import {MergeConfig} from './dev-infra/pr/merge/config';
|
||||
import {MergeConfig} from '../dev-infra/pr/merge/config';
|
||||
|
||||
// The configuration for `ng-dev commit-message` commands.
|
||||
const commitMessage = {
|
||||
@ -82,33 +80,11 @@ const github = {
|
||||
name: 'angular',
|
||||
};
|
||||
|
||||
/**
|
||||
* Gets the name of the current patch branch. The patch branch is determined by
|
||||
* looking for upstream branches that follow the format of `{major}.{minor}.x`.
|
||||
*/
|
||||
const getPatchBranchName = (): string => {
|
||||
const branches =
|
||||
exec(
|
||||
`git ls-remote --heads https://github.com/${github.owner}/${github.name}.git`,
|
||||
{silent: true})
|
||||
.trim()
|
||||
.split('\n');
|
||||
|
||||
for (let i = branches.length - 1; i >= 0; i--) {
|
||||
const branchName = branches[i];
|
||||
const matches = branchName.match(/refs\/heads\/([0-9]+\.[0-9]+\.x)/);
|
||||
if (matches !== null) {
|
||||
return matches[1];
|
||||
}
|
||||
}
|
||||
|
||||
throw Error('Could not determine patch branch name.');
|
||||
};
|
||||
|
||||
// Configuration for the `ng-dev pr merge` command. The command can be used
|
||||
// for merging upstream pull requests into branches based on a PR target label.
|
||||
const merge = () => {
|
||||
const patchBranch = getPatchBranchName();
|
||||
// TODO: resume dynamically determining patch branch
|
||||
const patch = '10.0.x';
|
||||
const config: MergeConfig = {
|
||||
githubApiMerge: false,
|
||||
claSignedLabel: 'cla: yes',
|
||||
@ -121,18 +97,19 @@ const merge = () => {
|
||||
},
|
||||
{
|
||||
pattern: 'PR target: patch-only',
|
||||
branches: [patchBranch],
|
||||
branches: [patch],
|
||||
},
|
||||
{
|
||||
pattern: 'PR target: master & patch',
|
||||
branches: ['master', patchBranch],
|
||||
branches: ['master', patch],
|
||||
},
|
||||
],
|
||||
requiredBaseCommits: {
|
||||
// PRs that target either `master` or the patch branch, need to be rebased
|
||||
// on top of the latest commit message validation fix.
|
||||
'master': '4341743b4a6d7e23c6f944aa9e34166b701369a1',
|
||||
[patchBranch]: '2a53f471592f424538802907aca1f60f1177a86d'
|
||||
// These SHAs are the commits that update the required license text in the header.
|
||||
'master': '5aeb9a4124922d8ac08eb73b8f322905a32b0b3a',
|
||||
[patch]: '27b95ba64a5d99757f4042073fd1860e20e3ed24'
|
||||
},
|
||||
};
|
||||
return config;
|
109
.pullapprove.yml
@ -80,8 +80,8 @@
|
||||
# Used for approving minor changes, large-scale refactorings, and in emergency situations.
|
||||
#
|
||||
# IgorMinar
|
||||
# jelbourn
|
||||
# josephperrott
|
||||
# kara
|
||||
# mhevery
|
||||
#
|
||||
# =========================================================
|
||||
@ -203,7 +203,6 @@ groups:
|
||||
- alxhub
|
||||
- AndrewKushnir
|
||||
- JoostK
|
||||
- kara
|
||||
|
||||
|
||||
# =========================================================
|
||||
@ -235,7 +234,6 @@ groups:
|
||||
- alxhub
|
||||
- crisbeto
|
||||
- devversion
|
||||
- kara
|
||||
|
||||
|
||||
# =========================================================
|
||||
@ -352,7 +350,7 @@ groups:
|
||||
- alxhub
|
||||
- AndrewKushnir
|
||||
- atscott
|
||||
- kara
|
||||
- ~kara # do not request reviews from Kara, but allow her to approve PRs
|
||||
- mhevery
|
||||
- pkozlowski-opensource
|
||||
|
||||
@ -579,7 +577,6 @@ groups:
|
||||
users:
|
||||
- AndrewKushnir
|
||||
- IgorMinar
|
||||
- kara
|
||||
- pkozlowski-opensource
|
||||
|
||||
|
||||
@ -596,7 +593,6 @@ groups:
|
||||
reviewers:
|
||||
users:
|
||||
- IgorMinar
|
||||
- kara
|
||||
- pkozlowski-opensource
|
||||
|
||||
|
||||
@ -613,7 +609,8 @@ groups:
|
||||
reviewers:
|
||||
users:
|
||||
- IgorMinar
|
||||
- kara
|
||||
- jelbourn
|
||||
- pkozlowski-opensource
|
||||
|
||||
|
||||
# =========================================================
|
||||
@ -637,6 +634,13 @@ groups:
|
||||
users:
|
||||
- IgorMinar
|
||||
- mhevery
|
||||
- jelbourn
|
||||
- pkozlowski-opensource
|
||||
reviews:
|
||||
request: -1 # request reviews from everyone
|
||||
required: 2 # require at least 2 approvals
|
||||
reviewed_for: required
|
||||
|
||||
|
||||
# =========================================================
|
||||
# Bazel
|
||||
@ -648,7 +652,6 @@ groups:
|
||||
- >
|
||||
contains_any_globs(files, [
|
||||
'packages/bazel/**',
|
||||
'aio/content/guide/bazel.md'
|
||||
])
|
||||
reviewers:
|
||||
users:
|
||||
@ -708,6 +711,7 @@ groups:
|
||||
reviewers:
|
||||
users:
|
||||
- alxhub
|
||||
- josephperrott
|
||||
|
||||
|
||||
# =========================================================
|
||||
@ -724,7 +728,6 @@ groups:
|
||||
users:
|
||||
- IgorMinar
|
||||
- josephperrott
|
||||
- kara
|
||||
- mhevery
|
||||
|
||||
|
||||
@ -837,7 +840,33 @@ groups:
|
||||
reviewers:
|
||||
users:
|
||||
- IgorMinar
|
||||
- kara
|
||||
- jelbourn
|
||||
|
||||
|
||||
# =========================================================
|
||||
# Tooling: Compiler API shared with Angular CLI
|
||||
#
|
||||
# Changing this API might break Angular CLI, so we require
|
||||
# the CLI team to approve changes here.
|
||||
# =========================================================
|
||||
tooling-cli-shared-api:
|
||||
conditions:
|
||||
- *can-be-global-approved
|
||||
- *can-be-global-docs-approved
|
||||
- >
|
||||
contains_any_globs(files, [
|
||||
'packages/compiler-cli/src/tooling.ts'
|
||||
])
|
||||
reviewers:
|
||||
users:
|
||||
- alan-agius4
|
||||
- clydin
|
||||
- kyliau
|
||||
- IgorMinar
|
||||
reviews:
|
||||
request: -1 # request reviews from everyone
|
||||
required: 2 # require at least 2 approvals
|
||||
reviewed_for: required
|
||||
|
||||
|
||||
# =========================================================
|
||||
@ -953,6 +982,7 @@ groups:
|
||||
'.circleci/**',
|
||||
'.devcontainer/**',
|
||||
'.github/**',
|
||||
'.ng-dev/**',
|
||||
'.vscode/**',
|
||||
'.yarn/**',
|
||||
'dev-infra/**',
|
||||
@ -968,8 +998,6 @@ groups:
|
||||
'docs/TOOLS.md',
|
||||
'docs/TRIAGE_AND_LABELS.md',
|
||||
'goldens/*',
|
||||
'modules/e2e_util/e2e_util.ts',
|
||||
'modules/e2e_util/perf_util.ts',
|
||||
'modules/*',
|
||||
'packages/*',
|
||||
'packages/examples/test-utils/**',
|
||||
@ -977,14 +1005,10 @@ groups:
|
||||
'packages/examples/*',
|
||||
'scripts/**',
|
||||
'third_party/**',
|
||||
'tools/brotli-cli/**',
|
||||
'tools/browsers/**',
|
||||
'tools/build/**',
|
||||
'tools/circular_dependency_test/**',
|
||||
'tools/contributing-stats/**',
|
||||
'tools/components/**',
|
||||
'tools/gulp-tasks/**',
|
||||
'tools/ng_rollup_bundle/**',
|
||||
'tools/ngcontainer/**',
|
||||
'tools/npm/**',
|
||||
'tools/npm_integration_test/**',
|
||||
@ -1029,8 +1053,14 @@ groups:
|
||||
])
|
||||
reviewers:
|
||||
users:
|
||||
- alxhub
|
||||
- IgorMinar
|
||||
- kara
|
||||
- jelbourn
|
||||
- pkozlowski-opensource
|
||||
reviews:
|
||||
request: -1 # request reviews from everyone
|
||||
required: 3 # require at least 3 approvals
|
||||
reviewed_for: required
|
||||
|
||||
|
||||
# ================================================
|
||||
@ -1045,8 +1075,14 @@ groups:
|
||||
])
|
||||
reviewers:
|
||||
users:
|
||||
- alxhub
|
||||
- IgorMinar
|
||||
- kara
|
||||
- jelbourn
|
||||
- pkozlowski-opensource
|
||||
reviews:
|
||||
request: -1 # request reviews from everyone
|
||||
required: 2 # require at least 2 approvals
|
||||
reviewed_for: required
|
||||
|
||||
|
||||
# ================================================
|
||||
@ -1062,8 +1098,9 @@ groups:
|
||||
reviewers:
|
||||
users:
|
||||
- IgorMinar
|
||||
- jelbourn
|
||||
- josephperrott
|
||||
- kara
|
||||
- pkozlowski-opensource
|
||||
|
||||
|
||||
####################################################################################
|
||||
@ -1089,12 +1126,32 @@ groups:
|
||||
# Catch all for if no groups match the code change
|
||||
# ====================================================
|
||||
fallback:
|
||||
# A group is considered to be `active` for a PR if at least one of group's
|
||||
# conditions matches the PR.
|
||||
#
|
||||
# The PullApprove CI check should fail if a PR has no `active` groups, as
|
||||
# this indicates the PR is modifying a file that has no owner.
|
||||
#
|
||||
# This is enforced through the pullapprove verification check done
|
||||
# as part of the CircleCI lint job. Failures in this lint job should be
|
||||
# fixed as part of the PR. This can be done by updating the
|
||||
# `.pullapprove.yml` file cover the unmatched path.
|
||||
# The pullapprove verification script is part of the ng-dev tool and can be
|
||||
# run locally with the command: `yarn -s ng-dev pullapprove verify`
|
||||
#
|
||||
# For cases in which the verification check fails to ensure coverage, this
|
||||
# group will be active. The expectation is that this should be remedied
|
||||
# before merging the PR as described above. In an emergency situation
|
||||
# `global-approvers` can still approve PRs that match this `fallback` rule,
|
||||
# but that should be an exception and not an expectation.
|
||||
conditions:
|
||||
- *can-be-global-approved
|
||||
# Groups which are found to have matching conditions are `active`
|
||||
# according to PullApprove. If no groups are matched and considered
|
||||
# active, we still want to have a review occur.
|
||||
- len(groups.active) == 0
|
||||
reviewers:
|
||||
users:
|
||||
- IgorMinar
|
||||
# The following groups have no conditions and will be `active` on all PRs
|
||||
# - `global-approvers`
|
||||
# - `global-docs-approvers`
|
||||
#
|
||||
# Since this means the minimum number of active groups a PR can have is 2, this
|
||||
# `fallback` group should be matched anytime the number of active groups is at or
|
||||
# below this minimum. This work as a protection to ensure that pullapprove does
|
||||
# not incidently mark a PR as passing without meeting the review criteria.
|
||||
- len(groups.active) <= 2
|
||||
|
@ -2,7 +2,6 @@ package(default_visibility = ["//visibility:public"])
|
||||
|
||||
exports_files([
|
||||
"LICENSE",
|
||||
"protractor-perf.conf.js",
|
||||
"karma-js.conf.js",
|
||||
"browser-providers.conf.js",
|
||||
"scripts/ci/track-payload-size.sh",
|
||||
|
64
CHANGELOG.md
@ -1,3 +1,67 @@
|
||||
<a name="10.0.0-rc.4"></a>
|
||||
# [10.0.0-rc.4](https://github.com/angular/angular/compare/10.0.0-rc.3...10.0.0-rc.4) (2020-06-10)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **common:** prevent duplicate URL change notifications ([#37459](https://github.com/angular/angular/issues/37459)) ([0864726](https://github.com/angular/angular/commit/0864726))
|
||||
* **compiler-cli:** downlevel angular decorators to static properties ([#37382](https://github.com/angular/angular/issues/37382)) ([323651b](https://github.com/angular/angular/commit/323651b)), closes [#30586](https://github.com/angular/angular/issues/30586) [#30106](https://github.com/angular/angular/issues/30106) [#30586](https://github.com/angular/angular/issues/30586) [#30141](https://github.com/angular/angular/issues/30141)
|
||||
* **language-service:** Improve signature selection by finding exact match ([#37494](https://github.com/angular/angular/issues/37494)) ([e97a2d4](https://github.com/angular/angular/commit/e97a2d4))
|
||||
* **platform-server:** correctly handle absolute relative URLs ([#37341](https://github.com/angular/angular/issues/37341)) ([420d1c3](https://github.com/angular/angular/commit/420d1c3)), closes [#37314](https://github.com/angular/angular/issues/37314)
|
||||
* **router:** Fix relative link generation from empty path components ([#37446](https://github.com/angular/angular/issues/37446)) ([585e3f6](https://github.com/angular/angular/commit/585e3f6)), closes [#26243](https://github.com/angular/angular/issues/26243) [#13011](https://github.com/angular/angular/issues/13011) [#35687](https://github.com/angular/angular/issues/35687)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **language-service:** TS references from template items ([#37437](https://github.com/angular/angular/issues/37437)) ([bf2cb6f](https://github.com/angular/angular/commit/bf2cb6f))
|
||||
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
* **core:** avoid pulling in jit-specific code in aot bundles ([#37372](https://github.com/angular/angular/issues/37372)) ([#37514](https://github.com/angular/angular/issues/37514)) ([6114cd2](https://github.com/angular/angular/commit/6114cd2)), closes [#29083](https://github.com/angular/angular/issues/29083)
|
||||
|
||||
|
||||
|
||||
<a name="10.0.0-rc.3"></a>
|
||||
# [10.0.0-rc.3](https://github.com/angular/angular/compare/10.0.0-rc.2...10.0.0-rc.3) (2020-06-08)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **common:** prevent duplicate URL change notifications ([#37404](https://github.com/angular/angular/issues/37404)) ([fff424a](https://github.com/angular/angular/commit/fff424a))
|
||||
* **compiler-cli:** use ModuleWithProviders type if static eval fails ([#37126](https://github.com/angular/angular/issues/37126)) ([305b5a3](https://github.com/angular/angular/commit/305b5a3))
|
||||
* **core:** infinite loop if injectable using inheritance has a custom decorator ([#37022](https://github.com/angular/angular/issues/37022)) ([bc54936](https://github.com/angular/angular/commit/bc54936)), closes [#35733](https://github.com/angular/angular/issues/35733)
|
||||
* **elements:** fire custom element output events during component initialization ([#36161](https://github.com/angular/angular/issues/36161)) ([e9bff5f](https://github.com/angular/angular/commit/e9bff5f)), closes [/github.com/angular/angular/blob/c0143cb2abdd172de1b95fd1d2c4cfc738640e28/packages/elements/src/create-custom-element.ts#L167-L170](https://github.com//github.com/angular/angular/blob/c0143cb2abdd172de1b95fd1d2c4cfc738640e28/packages/elements/src/create-custom-element.ts/issues/L167-L170) [/github.com/angular/angular/blob/c0143cb2abdd172de1b95fd1d2c4cfc738640e28/packages/elements/src/create-custom-element.ts#L164](https://github.com//github.com/angular/angular/blob/c0143cb2abdd172de1b95fd1d2c4cfc738640e28/packages/elements/src/create-custom-element.ts/issues/L164) [/github.com/angular/angular/blob/c0143cb2abdd172de1b95fd1d2c4cfc738640e28/packages/elements/src/component-factory-strategy.ts#L158](https://github.com//github.com/angular/angular/blob/c0143cb2abdd172de1b95fd1d2c4cfc738640e28/packages/elements/src/component-factory-strategy.ts/issues/L158) [#36141](https://github.com/angular/angular/issues/36141)
|
||||
* **language-service:** Recover from error in analyzing Ng Modules ([#37108](https://github.com/angular/angular/issues/37108)) ([2c1f35e](https://github.com/angular/angular/commit/2c1f35e))
|
||||
* **ngcc:** capture dynamic import expressions as well as declarations ([#37075](https://github.com/angular/angular/issues/37075)) ([5c0bdae](https://github.com/angular/angular/commit/5c0bdae))
|
||||
* **ngcc:** do not inline source-maps for non-inline typings source-maps ([#37363](https://github.com/angular/angular/issues/37363)) ([b4e26b5](https://github.com/angular/angular/commit/b4e26b5)), closes [#37324](https://github.com/angular/angular/issues/37324)
|
||||
* **ngcc:** ensure that more dependencies are found by `EsmDependencyHost` ([#37075](https://github.com/angular/angular/issues/37075)) ([c6872c0](https://github.com/angular/angular/commit/c6872c0))
|
||||
* **ngcc:** find decorated constructor params on IIFE wrapped classes ([#37436](https://github.com/angular/angular/issues/37436)) ([2cb3b66](https://github.com/angular/angular/commit/2cb3b66)), closes [#37330](https://github.com/angular/angular/issues/37330)
|
||||
* **service-worker:** Don't stay locked in EXISTING_CLIENTS_ONLY if corrupted data ([#37453](https://github.com/angular/angular/issues/37453)) ([6f93847](https://github.com/angular/angular/commit/6f93847)), closes [#31109](https://github.com/angular/angular/issues/31109) [#31865](https://github.com/angular/angular/issues/31865) [/github.com/angular/angular/blob/3569fdf/packages/service-worker/worker/src/driver.ts#L559-L563](https://github.com//github.com/angular/angular/blob/3569fdf/packages/service-worker/worker/src/driver.ts/issues/L559-L563) [/github.com/angular/angular/blob/3569fdf/packages/service-worker/worker/src/driver.ts#L505-L519](https://github.com//github.com/angular/angular/blob/3569fdf/packages/service-worker/worker/src/driver.ts/issues/L505-L519)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **ngcc:** implement a program-based entry-point finder ([#37075](https://github.com/angular/angular/issues/37075)) ([f3ccd29](https://github.com/angular/angular/commit/f3ccd29))
|
||||
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
* **ngcc:** allow immediately reporting a stale lock file ([#37250](https://github.com/angular/angular/issues/37250)) ([930d204](https://github.com/angular/angular/commit/930d204))
|
||||
* **ngcc:** cache parsed tsconfig between runs ([#37417](https://github.com/angular/angular/issues/37417)) ([f9daa13](https://github.com/angular/angular/commit/f9daa13)), closes [#36882](https://github.com/angular/angular/issues/36882)
|
||||
|
||||
|
||||
<a name="10.0.0-rc.2"></a>
|
||||
# [10.0.0-rc.2](https://github.com/angular/angular/compare/10.0.0-rc.0...10.0.0-rc.2) (2020-06-01)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **core:** reenable decorator downleveling for Angular npm packages ([#37317](https://github.com/angular/angular/issues/37317)) ([d16a7f3](https://github.com/angular/angular/commit/d16a7f3)), closes [#37221](https://github.com/angular/angular/issues/37221) [#37221](https://github.com/angular/angular/issues/37221)
|
||||
|
||||
|
||||
Note: the 10.0.0-rc.1 release on npm accidentally glitched-out midway, so we cut 10.0.0-rc.2 instead. oops :-)
|
||||
|
||||
<a name="10.0.0-rc.0"></a>
|
||||
# [10.0.0-rc.0](https://github.com/angular/angular/compare/10.0.0-next.9...10.0.0-rc.0) (2020-05-21)
|
||||
|
||||
|
15
WORKSPACE
@ -8,8 +8,8 @@ load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
|
||||
# Fetch rules_nodejs so we can install our npm dependencies
|
||||
http_archive(
|
||||
name = "build_bazel_rules_nodejs",
|
||||
sha256 = "f9e7b9f42ae202cc2d2ce6d698ccb49a9f7f7ea572a78fd451696d03ef2ee116",
|
||||
urls = ["https://github.com/bazelbuild/rules_nodejs/releases/download/1.6.0/rules_nodejs-1.6.0.tar.gz"],
|
||||
sha256 = "84abf7ac4234a70924628baa9a73a5a5cbad944c4358cf9abdb4aab29c9a5b77",
|
||||
urls = ["https://github.com/bazelbuild/rules_nodejs/releases/download/1.7.0/rules_nodejs-1.7.0.tar.gz"],
|
||||
)
|
||||
|
||||
# Check the rules_nodejs version and download npm dependencies
|
||||
@ -17,7 +17,7 @@ http_archive(
|
||||
# assert on that.
|
||||
load("@build_bazel_rules_nodejs//:index.bzl", "check_rules_nodejs_version", "node_repositories", "yarn_install")
|
||||
|
||||
check_rules_nodejs_version(minimum_version_string = "1.6.0")
|
||||
check_rules_nodejs_version(minimum_version_string = "1.7.0")
|
||||
|
||||
# Setup the Node.js toolchain
|
||||
node_repositories(
|
||||
@ -64,7 +64,7 @@ load("@io_bazel_rules_webtesting//web:repositories.bzl", "web_test_repositories"
|
||||
|
||||
web_test_repositories()
|
||||
|
||||
load("//tools/browsers:browser_repositories.bzl", "browser_repositories")
|
||||
load("//dev-infra/browsers:browser_repositories.bzl", "browser_repositories")
|
||||
|
||||
browser_repositories()
|
||||
|
||||
@ -91,17 +91,18 @@ rbe_autoconfig(
|
||||
# Need to specify a base container digest in order to ensure that we can use the checked-in
|
||||
# platform configurations for the "ubuntu16_04" image. Otherwise the autoconfig rule would
|
||||
# need to pull the image and run it in order determine the toolchain configuration. See:
|
||||
# https://github.com/bazelbuild/bazel-toolchains/blob/1.1.2/configs/ubuntu16_04_clang/versions.bzl
|
||||
base_container_digest = "sha256:1ab40405810effefa0b2f45824d6d608634ccddbf06366760c341ef6fbead011",
|
||||
# https://github.com/bazelbuild/bazel-toolchains/blob/3.2.0/configs/ubuntu16_04_clang/versions.bzl
|
||||
base_container_digest = "sha256:5e750dd878df9fcf4e185c6f52b9826090f6e532b097f286913a428290622332",
|
||||
# Note that if you change the `digest`, you might also need to update the
|
||||
# `base_container_digest` to make sure marketplace.gcr.io/google/rbe-ubuntu16-04-webtest:<digest>
|
||||
# and marketplace.gcr.io/google/rbe-ubuntu16-04:<base_container_digest> have
|
||||
# the same Clang and JDK installed. Clang is needed because of the dependency on
|
||||
# @com_google_protobuf. Java is needed for the Bazel's test executor Java tool.
|
||||
digest = "sha256:0b8fa87db4b8e5366717a7164342a029d1348d2feea7ecc4b18c780bc2507059",
|
||||
digest = "sha256:f743114235a43355bf8324e2ba0fa6a597236fe06f7bc99aaa9ac703631c306b",
|
||||
env = clang_env(),
|
||||
registry = "marketplace.gcr.io",
|
||||
# We can't use the default "ubuntu16_04" RBE image provided by the autoconfig because we need
|
||||
# a specific Linux kernel that comes with "libx11" in order to run headless browser tests.
|
||||
repository = "google/rbe-ubuntu16-04-webtest",
|
||||
use_checked_in_confs = "Force",
|
||||
)
|
||||
|
@ -109,9 +109,3 @@ Options that specify files can be given as absolute paths, or as paths relative
|
||||
The [ng generate](cli/generate) and [ng add](cli/add) commands take as an argument the artifact or library to be generated or added to the current project.
|
||||
In addition to any general options, each artifact or library defines its own options in a *schematic*.
|
||||
Schematic options are supplied to the command in the same format as immediate command options.
|
||||
|
||||
|
||||
### Building with Bazel
|
||||
|
||||
Optionally, you can configure the Angular CLI to use [Bazel](https://docs.bazel.build) as the build tool. For more information, see [Building with Bazel](guide/bazel).
|
||||
|
||||
|
@ -3,7 +3,7 @@ import { NgModule } from '@angular/core';
|
||||
import { BrowserModule } from '@angular/platform-browser';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
|
||||
import { AppComponent } from './app.component';
|
||||
import { AppComponent } from './app.component';
|
||||
import { HeroFormComponent } from './hero-form/hero-form.component';
|
||||
|
||||
@NgModule({
|
||||
|
@ -200,13 +200,4 @@
|
||||
(ngModelChange)="model.name = $event">
|
||||
TODO: remove this: {{model.name}}
|
||||
<!-- #enddocregion ngModel-3-->
|
||||
<hr>
|
||||
<!-- #docregion ngModelName-2 -->
|
||||
<input type="text" class="form-control" id="name"
|
||||
required
|
||||
[(ngModel)]="model.name" name="name"
|
||||
#spy>
|
||||
<br>TODO: remove this: {{spy.className}}
|
||||
<!-- #enddocregion ngModelName-2 -->
|
||||
|
||||
</div>
|
||||
|
@ -2,7 +2,7 @@
|
||||
// #docregion , v1, final
|
||||
import { Component } from '@angular/core';
|
||||
|
||||
import { Hero } from '../hero';
|
||||
import { Hero } from '../hero';
|
||||
|
||||
@Component({
|
||||
selector: 'app-hero-form',
|
||||
|
@ -13,13 +13,13 @@ import { searchUrl } from '../package-search/package-search.service';
|
||||
|
||||
|
||||
/**
|
||||
* If request is cachable (e.g., package search) and
|
||||
* If request is cacheable (e.g., package search) and
|
||||
* response is in cache return the cached response as observable.
|
||||
* If has 'x-refresh' header that is true,
|
||||
* then also re-run the package search, using response from next(),
|
||||
* returning an observable that emits the cached response first.
|
||||
*
|
||||
* If not in cache or not cachable,
|
||||
* If not in cache or not cacheable,
|
||||
* pass request through to next()
|
||||
*/
|
||||
// #docregion v1
|
||||
@ -28,8 +28,8 @@ export class CachingInterceptor implements HttpInterceptor {
|
||||
constructor(private cache: RequestCache) {}
|
||||
|
||||
intercept(req: HttpRequest<any>, next: HttpHandler) {
|
||||
// continue if not cachable.
|
||||
if (!isCachable(req)) { return next.handle(req); }
|
||||
// continue if not cacheable.
|
||||
if (!isCacheable(req)) { return next.handle(req); }
|
||||
|
||||
const cachedResponse = this.cache.get(req);
|
||||
// #enddocregion v1
|
||||
@ -51,11 +51,11 @@ export class CachingInterceptor implements HttpInterceptor {
|
||||
// #enddocregion v1
|
||||
|
||||
|
||||
/** Is this request cachable? */
|
||||
function isCachable(req: HttpRequest<any>) {
|
||||
// Only GET requests are cachable
|
||||
/** Is this request cacheable? */
|
||||
function isCacheable(req: HttpRequest<any>) {
|
||||
// Only GET requests are cacheable
|
||||
return req.method === 'GET' &&
|
||||
// Only npm package search is cachable in this app
|
||||
// Only npm package search is cacheable in this app
|
||||
-1 < req.url.indexOf(searchUrl);
|
||||
}
|
||||
|
||||
|
@ -1,122 +0,0 @@
|
||||
# Building with Bazel
|
||||
|
||||
This guide explains how to build and test Angular apps with Bazel.
|
||||
|
||||
|
||||
<div class="alert is-helpful">
|
||||
|
||||
This guide assumes you are already familiar with developing and building Angular applications using the [CLI](cli).
|
||||
|
||||
It describes features which are part of Angular Labs, and are not considered a stable, supported API.
|
||||
|
||||
</div>
|
||||
|
||||
## Using Bazel with the Angular CLI
|
||||
|
||||
The `@angular/bazel` package provides a builder that allows Angular CLI to use Bazel as the build tool.
|
||||
|
||||
To opt-in an existing application, run
|
||||
|
||||
```sh
|
||||
ng add @angular/bazel
|
||||
```
|
||||
|
||||
To use Bazel in a new application, first install `@angular/bazel` globally
|
||||
|
||||
```sh
|
||||
npm install -g @angular/bazel
|
||||
```
|
||||
|
||||
then create the new application with
|
||||
|
||||
```sh
|
||||
ng new --collection=@angular/bazel
|
||||
```
|
||||
|
||||
Now when you use Angular CLI build commands such as `ng build` and `ng serve`,
|
||||
Bazel is used behind the scenes.
|
||||
Outputs from Bazel appear in the `dist/bin` folder.
|
||||
|
||||
> The command-line output includes extra logging from Bazel.
|
||||
> We plan to reduce this in the future.
|
||||
|
||||
### Removing Bazel
|
||||
|
||||
If you need to opt-out from using Bazel, you can restore the backup files:
|
||||
|
||||
- `/angular.json.bak` replaces `/angular.json`
|
||||
|
||||
## Advanced configuration
|
||||
|
||||
<div class="alert is-helpful">
|
||||
|
||||
Editing the Bazel configuration may prevent you opting out of Bazel.
|
||||
Custom behaviors driven by Bazel won't be available in other Builders.
|
||||
|
||||
This section assumes you are familiar with [Bazel](https://docs.bazel.build).
|
||||
|
||||
</div>
|
||||
|
||||
You can manually adjust the Bazel configuration to:
|
||||
|
||||
* customize the build steps
|
||||
* parallellize the build for scale and incrementality
|
||||
|
||||
Create the initial Bazel configuration files by running the following command:
|
||||
|
||||
```sh
|
||||
ng build --leaveBazelFilesOnDisk
|
||||
```
|
||||
|
||||
Now you'll find new files in the Angular workspace:
|
||||
|
||||
* `/WORKSPACE` tells Bazel how to download external dependencies.
|
||||
* `/BUILD.bazel` and `/src/BUILD.bazel` tell Bazel about your source code.
|
||||
|
||||
You can find a full-featured example with custom Bazel configurations at https://github.com/bazelbuild/rules_nodejs/tree/master/examples/angular.
|
||||
|
||||
Documentation for using Bazel for frontend projects is linked from https://docs.bazel.build/versions/master/bazel-and-javascript.html.
|
||||
|
||||
|
||||
|
||||
## Running Bazel directly
|
||||
|
||||
In some cases you'll want to bypass the Angular CLI builder, and run the Bazel CLI directly.
|
||||
The Bazel tool is managed by the `@bazel/bazelisk` package (similar to how Node.js can be managed by `nvm`).
|
||||
You can install it globally to get the `bazelisk` command in your path, or use `$(npm bin)/bazelisk` in place of bazelisk below.
|
||||
|
||||
The common commands in Bazel are:
|
||||
|
||||
* `bazelisk build [targets]`: Compile the default output artifacts of the given targets.
|
||||
* `bazelisk test [targets]`: For whichever `*_test` targets are found in the patterns, run the tests.
|
||||
* `bazelisk run [target]`: Compile the program represented by target, and then run it.
|
||||
|
||||
To repeat the command any time the inputs change (watch mode), replace `bazelisk` with `ibazel` in these commands.
|
||||
|
||||
The output locations are printed in the output.
|
||||
|
||||
Full documentation for the Bazel CLI is at https://docs.bazel.build/versions/master/command-line-reference.html.
|
||||
|
||||
|
||||
## Querying the build graph
|
||||
|
||||
Because Bazel constructs a graph out of your targets, you can find lots of useful information.
|
||||
|
||||
Using the graphviz optional dependency, you'll have a program `dot`, which you can use with `bazel query`:
|
||||
|
||||
```bash
|
||||
$ bazel query --output=graph ... | dot -Tpng > graph.png
|
||||
```
|
||||
|
||||
See https://docs.bazel.build/versions/master/query-how-to.html for more details on `bazel query`.
|
||||
|
||||
|
||||
## Customizing `BUILD.bazel` files
|
||||
|
||||
"Rules" are like plugins for Bazel. Many rule sets are available. This guide documents the ones maintained by the Angular team at Google.
|
||||
|
||||
Rules are used in `BUILD.bazel` files, which are markers for the packages in your workspace. Each `BUILD.bazel` file declares a separate package to Bazel, though you can have more coarse-grained distributions so that the packages you publish (for example, to `npm`) can be made up of many Bazel packages.
|
||||
|
||||
In the `BUILD.bazel` file, each rule must first be imported, using the `load` statement. Then the rule is called with some attributes, and the result of calling the rule is that you've declared to Bazel how it can derive some outputs given some inputs and dependencies. Then later, when you run a `bazel` command line, Bazel loads all the rules you've declared to determine an absolute ordering of what needs to be run. Note that only the rules needed to produce the requested output will actually be executed.
|
||||
|
||||
A list of common rules for frontend development is documented in the README at https://github.com/bazelbuild/rules_nodejs/.
|
@ -54,16 +54,17 @@ Angular supports most recent browsers. This includes the following specific vers
|
||||
</td>
|
||||
<td>
|
||||
<div> 11, 10*, 9* ("compatibility view" mode not supported) </div>
|
||||
<div>*deprecated in v10, see the <a href="/guide/deprecations#ie-9-10">deprecations guide</a>.</div>
|
||||
<div>*deprecated in v10, see the {@link guide/deprecations#ie-9-10-and-mobile deprecations guide}.</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<tr>
|
||||
<td>
|
||||
IE Mobile
|
||||
IE Mobile*
|
||||
</td>
|
||||
<td>
|
||||
11
|
||||
<div>*deprecated in v10, see the {@link guide/deprecations#ie-9-10-and-mobile deprecations guide}.</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
|
@ -109,7 +109,9 @@ To learn more, see [Schematics Overview](guide/schematics) and [Schematics for
|
||||
|
||||
## Publishing your library
|
||||
|
||||
Use the Angular CLI and the npm package manager to build and publish your library as an npm package. It is not recommended to publish Ivy libraries to NPM repositories. Before publishing a library to NPM, build it using the `--prod` flag which will use the older compiler and runtime known as View Engine instead of Ivy.
|
||||
Use the Angular CLI and the npm package manager to build and publish your library as an npm package.
|
||||
|
||||
Before publishing a library to NPM, build it using the `--prod` flag which will use the older compiler and runtime known as View Engine instead of Ivy.
|
||||
|
||||
<code-example language="bash">
|
||||
ng build my-lib --prod
|
||||
@ -119,6 +121,14 @@ npm publish
|
||||
|
||||
If you've never published a package in npm before, you must create a user account. Read more in [Publishing npm Packages](https://docs.npmjs.com/getting-started/publishing-npm-packages).
|
||||
|
||||
<div class="alert is-important">
|
||||
|
||||
For now, it is not recommended to publish Ivy libraries to NPM because Ivy generated code is not backward compatible with View Engine, so apps using View Engine will not be able to consume them. Furthermore, the internal Ivy instructions are not yet stable, which can potentially break consumers using a different Angular version from the one used to build the library.
|
||||
|
||||
When a published library is used in an Ivy app, the Angular CLI will automatically convert it to Ivy using a tool known as the Angular compatibility compiler (`ngcc`). Thus, by publishing your libraries using the View Engine compiler ensures that they can be transparently consumed by both View Engine and Ivy apps.
|
||||
|
||||
</div>
|
||||
|
||||
{@a lib-assets}
|
||||
|
||||
## Managing assets in a library
|
||||
|
@ -35,6 +35,7 @@ v9 - v12
|
||||
|
||||
| Area | API or Feature | May be removed in |
|
||||
| ----------------------------- | --------------------------------------------------------------------------- | ----------------- |
|
||||
| `@angular/bazel` | [`Bazel builder and schematics`](#bazelbuilder) | v10 |
|
||||
| `@angular/common` | [`ReflectiveInjector`](#reflectiveinjector) | <!--v8--> v11 |
|
||||
| `@angular/common` | [`CurrencyPipe` - `DEFAULT_CURRENCY_CODE`](api/common/CurrencyPipe#currency-code-deprecation) | <!--v9--> v11 |
|
||||
| `@angular/core` | [`CollectionChangeRecord`](#core) | <!--v7--> v11 |
|
||||
@ -59,7 +60,7 @@ v9 - v12
|
||||
| `@angular/core/testing` | [`TestBed.get`](#testing) | <!--v9--> v12 |
|
||||
| `@angular/router` | [`ActivatedRoute` params and `queryParams` properties](#activatedroute-props) | unspecified |
|
||||
| template syntax | [`/deep/`, `>>>`, and `::ng-deep`](#deep-component-style-selector) | <!--v7--> unspecified |
|
||||
| browser support | [`IE 9 and 10`](#ie-9-10) | <!--v10--> v11 |
|
||||
| browser support | [`IE 9 and 10, IE mobile`](#ie-9-10-and-mobile) | <!--v10--> v11 |
|
||||
|
||||
|
||||
|
||||
@ -160,7 +161,11 @@ Tip: In the [API reference section](api) of this doc site, deprecated APIs are i
|
||||
|
||||
This section lists all of the currently-deprecated features, which includes template syntax, configuration options, and any other deprecations not listed in the [Deprecated APIs](#deprecated-apis) section above. It also includes deprecated API usage scenarios or API combinations, to augment the information above.
|
||||
|
||||
{@a bazelbuilder}
|
||||
### Bazel builder and schematics
|
||||
|
||||
Bazel builder and schematics were introduced in Angular Labs to let users try out Bazel without having to manage Bazel version and BUILD files.
|
||||
This feature has been deprecated. For more information, please refer to the [migration doc](https://github.com/angular/angular/blob/master/packages/bazel/src/schematics/README.md).
|
||||
|
||||
{@a wtf}
|
||||
### Web Tracing Framework integration
|
||||
@ -459,17 +464,17 @@ export class MyModule {
|
||||
```
|
||||
|
||||
|
||||
{@a ie-9-10}
|
||||
### IE 9 and 10 support
|
||||
{@a ie-9-10-and-mobile}
|
||||
### IE 9, 10, and IE mobile support
|
||||
|
||||
Support for IE 9 and 10 has been deprecated and will be removed in a future version.
|
||||
Support for IE 9 and 10 has been deprecated, as well as support for IE Mobile. These will be dropped in a future version.
|
||||
Supporting outdated browsers like these increases bundle size, code complexity, and test load, and also requires time and effort that could be spent on improvements to the framework.
|
||||
For example, fixing issues can be more difficult, as a straightforward fix for modern browsers could break old ones that have quirks due to not receiving updates from vendors.
|
||||
|
||||
The final decision was made on three key points:
|
||||
* __Vendor support__: Microsoft dropped support of IE 9 and 10 on 1/12/16, meaning they no longer provide security updates or technical support.
|
||||
* __Usage statistics__: We looked at usage trends for IE 9 and 10 from various sources and all indicated that usage percentages were extremely small (fractions of 1%).
|
||||
* __Feedback from partners__: We also reached out to some of our Angular customers and none expressed concern about dropping IE 9 and 10 support.
|
||||
* __Vendor support__: Microsoft dropped support of IE 9 and 10 on 1/12/16, meaning they no longer provide security updates or technical support. Additionally, Microsoft dropped support for Windows 10 Mobile in December 2019.
|
||||
* __Usage statistics__: We looked at usage trends for IE 9 and 10 (as well as IE Mobile) from various sources and all indicated that usage percentages were extremely small (fractions of 1%).
|
||||
* __Feedback from partners__: We also reached out to some of our Angular customers and none expressed concern about dropping IE 9, 10, nor IE Mobile support.
|
||||
|
||||
|
||||
{@a wrapped-value}
|
||||
|
@ -931,7 +931,7 @@ If you do, be sure to set the `id` attribute - not the `name` attribute! The doc
|
||||
|
||||
</div>
|
||||
|
||||
## Alerts and Calllouts
|
||||
## Alerts and Callouts
|
||||
|
||||
Alerts and callouts present warnings, extra detail or references to other pages. They can also be used to provide commentary that _enriches_ the reader's understanding of the content being presented.
|
||||
|
||||
|
@ -4,22 +4,27 @@ Handling user input with forms is the cornerstone of many common applications. A
|
||||
|
||||
Angular provides two different approaches to handling user input through forms: reactive and template-driven. Both capture user input events from the view, validate the user input, create a form model and data model to update, and provide a way to track changes.
|
||||
|
||||
Reactive and template-driven forms process and manage form data differently. Each offers different advantages.
|
||||
|
||||
**In general:**
|
||||
|
||||
* **Reactive forms** are more robust: they're more scalable, reusable, and testable. If forms are a key part of your application, or you're already using reactive patterns for building your application, use reactive forms.
|
||||
* **Template-driven forms** are useful for adding a simple form to an app, such as an email list signup form. They're easy to add to an app, but they don't scale as well as reactive forms. If you have very basic form requirements and logic that can be managed solely in the template, use template-driven forms.
|
||||
|
||||
This guide provides information to help you decide which type of form works best for your situation. It introduces the common building blocks used by both approaches. It also summarizes the key differences between the two approaches, and demonstrates those differences in the context of setup, data flow, and testing.
|
||||
|
||||
<div class="alert is-helpful">
|
||||
## Prerequisites
|
||||
|
||||
**Note:** For complete information about each kind of form, see [Reactive Forms](guide/reactive-forms) and [Template-driven Forms](guide/forms).
|
||||
This guide assumes that you have a basic understanding of the following.
|
||||
|
||||
</div>
|
||||
* [TypeScript](https://www.typescriptlang.org/docs/home.html "The TypeScript language") and HTML5 programming.
|
||||
|
||||
## Key differences
|
||||
* Angular app-design fundamentals, as described in [Angular Concepts](guide/architecture "Introduction to Angular concepts.").
|
||||
|
||||
* The basics of [Angular template syntax](guide/architecture-components#template-syntax "Template syntax intro").
|
||||
|
||||
## Choosing an approach
|
||||
|
||||
Reactive forms and template-driven forms process and manage form data differently. Each approach offers different advantages.
|
||||
|
||||
* **Reactive forms** provide direct, explicit access to the underlying forms object model. Compared to template-driven forms, they are more robust: they're more scalable, reusable, and testable. If forms are a key part of your application, or you're already using reactive patterns for building your application, use reactive forms.
|
||||
|
||||
* **Template-driven forms** rely on directives in the template to create and manipulate the underlying object model. They are useful for adding a simple form to an app, such as an email list signup form. They're easy to add to an app, but they don't scale as well as reactive forms. If you have very basic form requirements and logic that can be managed solely in the template, template-driven forms could be a good fit.
|
||||
|
||||
### Key differences
|
||||
|
||||
The table below summarizes the key differences between reactive and template-driven forms.
|
||||
|
||||
@ -30,17 +35,33 @@ The table below summarizes the key differences between reactive and template-dri
|
||||
|
||||
||Reactive|Template-driven|
|
||||
|--- |--- |--- |
|
||||
|Setup (form model)|More explicit, created in component class|Less explicit, created by directives|
|
||||
|Data model|Structured|Unstructured|
|
||||
|Predictability|Synchronous|Asynchronous|
|
||||
|Form validation|Functions|Directives|
|
||||
|Mutability|Immutable|Mutable|
|
||||
|Scalability|Low-level API access|Abstraction on top of APIs|
|
||||
|[Setup of form model](#setup) | Explicit, created in component class | Implicit, created by directives |
|
||||
|[Data model](#data-flow-in-forms) | Structured and immutable | Unstructured and mutable |
|
||||
|Predictability | Synchronous | Asynchronous |
|
||||
|[Form validation](#validation) | Functions | Directives |
|
||||
|
||||
## Common foundation
|
||||
### Scalability
|
||||
|
||||
Both reactive and template-driven forms share underlying building blocks.
|
||||
If forms are a central part of your application, scalability is very important. Being able to reuse form models across components is critical.
|
||||
|
||||
Reactive forms are more scalable than template-driven forms. They provide direct access to the underlying form API, and synchronous access to the form data model, making creating large-scale forms easier.
|
||||
Reactive forms require less setup for testing, and testing does not require deep understanding of change detection to properly test form updates and validation.
|
||||
|
||||
Template-driven forms focus on simple scenarios and are not as reusable.
|
||||
They abstract away the underlying form API, and provide only asynchronous access to the form data model.
|
||||
The abstraction of template-driven forms also affects testing.
|
||||
Tests are deeply reliant on manual change detection execution to run properly, and require more setup.
|
||||
|
||||
{@a setup}
|
||||
|
||||
## Setting up the form model
|
||||
|
||||
Both reactive and template-driven forms track value changes between the form input elements that users interact with and the form data in your component model.
|
||||
The two approaches share underlying building blocks, but differ in how you create and manage the common form-control instances.
|
||||
|
||||
### Common form foundation classes
|
||||
|
||||
Both reactive and template-driven forms are built on the following base classes.
|
||||
|
||||
* `FormControl` tracks the value and validation status of an individual form control.
|
||||
|
||||
@ -50,59 +71,59 @@ Both reactive and template-driven forms share underlying building blocks.
|
||||
|
||||
* `ControlValueAccessor` creates a bridge between Angular `FormControl` instances and native DOM elements.
|
||||
|
||||
See the [Form model setup](#setup-the-form-model) section below for an introduction to how these control instances are created and managed with reactive and template-driven forms. Further details are provided in the [data flow section](#data-flow-in-forms) of this guide.
|
||||
|
||||
{@a setup-the-form-model}
|
||||
|
||||
## Form model setup
|
||||
|
||||
Reactive and template-driven forms both use a form model to track value changes between Angular forms and form input elements. The examples below show how the form model is defined and created.
|
||||
|
||||
### Setup in reactive forms
|
||||
|
||||
Here's a component with an input field for a single control implemented using reactive forms.
|
||||
With reactive forms, you define the form model directly in the component class.
|
||||
The `[formControl]` directive links the explicitly created `FormControl` instance to a specific form element in the view, using an internal value accessor.
|
||||
|
||||
The following component implements an input field for a single control, using reactive forms. In this example, the form model is the `FormControl` instance.
|
||||
|
||||
<code-example path="forms-overview/src/app/reactive/favorite-color/favorite-color.component.ts">
|
||||
</code-example>
|
||||
|
||||
The source of truth provides the value and status of the form element at a given point in time. In reactive forms, the form model is the source of truth. In the example above, the form model is the `FormControl` instance.
|
||||
Figure 1 shows how, in reactive forms, the form model is the source of truth; it provides the value and status of the form element at any given point in time, through the `[formControl]` directive on the input element.
|
||||
|
||||
**Figure 1.** *Direct access to forms model in a reactive form.*
|
||||
|
||||
<div class="lightbox">
|
||||
<img src="generated/images/guide/forms-overview/key-diff-reactive-forms.png" alt="Reactive forms key differences">
|
||||
</div>
|
||||
|
||||
With reactive forms, the form model is explicitly defined in the component class. The reactive form directive (in this case, `FormControlDirective`) then links the existing `FormControl` instance to a specific form element in the view using a value accessor (`ControlValueAccessor` instance).
|
||||
|
||||
### Setup in template-driven forms
|
||||
|
||||
Here's the same component with an input field for a single control implemented using template-driven forms.
|
||||
In template-driven forms, the form model is implicit, rather than explicit. The directive `NgModel` creates and manages a `FormControl` instance for a given form element.
|
||||
|
||||
The following component implements the same input field for a single control, using template-driven forms.
|
||||
|
||||
<code-example path="forms-overview/src/app/template/favorite-color/favorite-color.component.ts">
|
||||
</code-example>
|
||||
|
||||
In template-driven forms, the source of truth is the template.
|
||||
In a template-driven form the source of truth is the template. You do not have direct programmatic access to the `FormControl` instance, as shown in Figure 2.
|
||||
|
||||
**Figure 2.** *Indirect access to forms model in a template-driven form.*
|
||||
|
||||
<div class="lightbox">
|
||||
<img src="generated/images/guide/forms-overview/key-diff-td-forms.png" alt="Template-driven forms key differences">
|
||||
</div>
|
||||
|
||||
The abstraction of the form model promotes simplicity over structure. The template-driven form directive `NgModel` is responsible for creating and managing the `FormControl` instance for a given form element. It's less explicit, but you no longer have direct control over the form model.
|
||||
|
||||
{@a data-flow-in-forms}
|
||||
|
||||
## Data flow in forms
|
||||
|
||||
When building forms in Angular, it's important to understand how the framework handles data flowing from the user or from programmatic changes. Reactive and template-driven forms follow two different strategies when handling form input. The data flow examples below begin with the favorite color input field example from above, and then show how changes to favorite color are handled in reactive forms compared to template-driven forms.
|
||||
When an application contains a form, Angular must keep the view in sync with the component model and the component model in sync with the view.
|
||||
As users change values and make selections through the view, the new values must be reflected in the data model.
|
||||
Similarly, when the program logic changes values in the data model, those values must be reflected in the view.
|
||||
|
||||
Reactive and template-driven forms differ in how they handle data flowing from the user or from programmatic changes.
|
||||
The following diagrams illustrate both kinds of data flow for each type of form, using the a favorite-color input field defined above.
|
||||
|
||||
### Data flow in reactive forms
|
||||
|
||||
As described above, in reactive forms each form element in the view is directly linked to a form model (`FormControl` instance). Updates from the view to the model and from the model to the view are synchronous and aren't dependent on the UI rendered. The diagrams below use the same favorite color example to demonstrate how data flows when an input field's value is changed from the view and then from the model.
|
||||
In reactive forms each form element in the view is directly linked to the form model (a `FormControl` instance). Updates from the view to the model and from the model to the view are synchronous and do not depend on how the UI is rendered.
|
||||
|
||||
<div class="lightbox">
|
||||
<img src="generated/images/guide/forms-overview/dataflow-reactive-forms-vtm.png" alt="Reactive forms data flow - view to model" width="100%">
|
||||
</div>
|
||||
|
||||
The steps below outline the data flow from view to model.
|
||||
The view-to-model diagram shows how data flows when an input field's value is changed from the view through the following steps.
|
||||
|
||||
1. The user types a value into the input element, in this case the favorite color *Blue*.
|
||||
1. The form input element emits an "input" event with the latest value.
|
||||
@ -111,25 +132,25 @@ The steps below outline the data flow from view to model.
|
||||
1. Any subscribers to the `valueChanges` observable receive the new value.
|
||||
|
||||
<div class="lightbox">
|
||||
<img src="generated/images/guide/forms-overview/dataflow-reactive-forms-mtv.png" alt="Reactive forms data flow - model to view" width="100%">
|
||||
<img src="generated/images/guide/forms-overview/dataflow-reactive-forms-vtm.png" alt="Reactive forms data flow - view to model">
|
||||
</div>
|
||||
|
||||
The steps below outline the data flow from model to view.
|
||||
The model-to-view diagram shows how a programmatic change to the model is propagated to the view through the following steps.
|
||||
|
||||
1. The user calls the `favoriteColorControl.setValue()` method, which updates the `FormControl` value.
|
||||
1. The `FormControl` instance emits the new value through the `valueChanges` observable.
|
||||
1. Any subscribers to the `valueChanges` observable receive the new value.
|
||||
1. The control value accessor on the form input element updates the element with the new value.
|
||||
|
||||
### Data flow in template-driven forms
|
||||
|
||||
In template-driven forms, each form element is linked to a directive that manages the form model internally. The diagrams below use the same favorite color example to demonstrate how data flows when an input field's value is changed from the view and then from the model.
|
||||
|
||||
<div class="lightbox">
|
||||
<img src="generated/images/guide/forms-overview/dataflow-td-forms-vtm.png" alt="Template-driven forms data flow - view to model" width="100%">
|
||||
<img src="generated/images/guide/forms-overview/dataflow-reactive-forms-mtv.png" alt="Reactive forms data flow - model to view">
|
||||
</div>
|
||||
|
||||
The steps below outline the data flow from view to model when the input value changes from *Red* to *Blue*.
|
||||
### Data flow in template-driven forms
|
||||
|
||||
In template-driven forms, each form element is linked to a directive that manages the form model internally.
|
||||
|
||||
The view-to-model diagram shows how data flows when an input field's value is changed from the view through the following steps.
|
||||
|
||||
1. The user types *Blue* into the input element.
|
||||
1. The input element emits an "input" event with the value *Blue*.
|
||||
@ -141,10 +162,10 @@ The steps below outline the data flow from view to model when the input value ch
|
||||
is updated to the value emitted by the `ngModelChange` event (*Blue*).
|
||||
|
||||
<div class="lightbox">
|
||||
<img src="generated/images/guide/forms-overview/dataflow-td-forms-mtv.png" alt="Template-driven forms data flow - model to view" width="100%">
|
||||
<img src="generated/images/guide/forms-overview/dataflow-td-forms-vtm.png" alt="Template-driven forms data flow - view to model" width="100%">
|
||||
</div>
|
||||
|
||||
The steps below outline the data flow from model to view when the `favoriteColor` changes from *Blue* to *Red*.
|
||||
The model-to-view diagram shows how data flows from model to view when the `favoriteColor` changes from *Blue* to *Red*, through the following steps
|
||||
|
||||
1. The `favoriteColor` value is updated in the component.
|
||||
1. Change detection begins.
|
||||
@ -156,6 +177,30 @@ The steps below outline the data flow from model to view when the `favoriteColor
|
||||
1. Any subscribers to the `valueChanges` observable receive the new value.
|
||||
1. The control value accessor updates the form input element in the view with the latest `favoriteColor` value.
|
||||
|
||||
<div class="lightbox">
|
||||
<img src="generated/images/guide/forms-overview/dataflow-td-forms-mtv.png" alt="Template-driven forms data flow - model to view" width="100%">
|
||||
</div>
|
||||
|
||||
### Mutability of the data model
|
||||
|
||||
The change-tracking method plays a role in the efficiency of your application.
|
||||
|
||||
* **Reactive forms** keep the data model pure by providing it as an immutable data structure.
|
||||
Each time a change is triggered on the data model, the `FormControl` instance returns a new data model rather than updating the existing data model.
|
||||
This gives you the ability to track unique changes to the data model through the control's observable.
|
||||
Change detection is more efficient because it only needs to update on unique changes.
|
||||
Because data updates follow reactive patterns, you can integrate with observable operators to transform data.
|
||||
|
||||
* **Template-driven** forms rely on mutability with two-way data binding to update the data model in the component as changes are made in the template.
|
||||
Because there are no unique changes to track on the data model when using two-way data binding, change detection is less efficient at determining when updates are required.
|
||||
|
||||
The difference is demonstrated in the previous examples that use the favorite-color input element.
|
||||
|
||||
* With reactive forms, the **`FormControl` instance** always returns a new value when the control's value is updated.
|
||||
|
||||
* With template-driven forms, the **favorite color property** is always modified to its new value.
|
||||
|
||||
{@a validation}
|
||||
## Form validation
|
||||
|
||||
Validation is an integral part of managing any set of forms. Whether you're checking for required fields or querying an external API for an existing username, Angular provides a set of built-in validators as well as the ability to create custom validators.
|
||||
@ -167,36 +212,37 @@ For more information, see [Form Validation](guide/form-validation).
|
||||
|
||||
## Testing
|
||||
|
||||
Testing plays a large part in complex applications and a simpler testing strategy is useful when validating that your forms function correctly. Reactive forms and template-driven forms have different levels of reliance on rendering the UI to perform assertions based on form control and form field changes. The following examples demonstrate the process of testing forms with reactive and template-driven forms.
|
||||
Testing plays a large part in complex applications. A simpler testing strategy is useful when validating that your forms function correctly.
|
||||
Reactive forms and template-driven forms have different levels of reliance on rendering the UI to perform assertions based on form control and form field changes.
|
||||
The following examples demonstrate the process of testing forms with reactive and template-driven forms.
|
||||
|
||||
### Testing reactive forms
|
||||
|
||||
Reactive forms provide a relatively easy testing strategy because they provide synchronous access to the form and data models, and they can be tested without rendering the UI. In these tests, status and data are queried and manipulated through the control without interacting with the change detection cycle.
|
||||
Reactive forms provide a relatively easy testing strategy because they provide synchronous access to the form and data models, and they can be tested without rendering the UI.
|
||||
In these tests, status and data are queried and manipulated through the control without interacting with the change detection cycle.
|
||||
|
||||
The following tests use the favorite color components mentioned earlier to verify the data flows from view to model and model to view for a reactive form.
|
||||
The following tests use the favorite-color components from previous examples to verify the view-to-model and model-to-view data flows for a reactive form.
|
||||
|
||||
The following test verifies the data flow from view to model.
|
||||
**Verifying view-to-model data flow**
|
||||
|
||||
<code-example path="forms-overview/src/app/reactive/favorite-color/favorite-color.component.spec.ts" region="view-to-model" header="Favorite color test - view to model">
|
||||
</code-example>
|
||||
|
||||
Here are the steps performed in the view to model test.
|
||||
The first example performs the following steps to verify the view-to-model data flow.
|
||||
|
||||
1. Query the view for the form input element, and create a custom "input" event for the test.
|
||||
1. Set the new value for the input to *Red*, and dispatch the "input" event on the form input element.
|
||||
1. Assert that the component's `favoriteColorControl` value matches the value from the input.
|
||||
|
||||
The following test verifies the data flow from model to view.
|
||||
|
||||
<code-example path="forms-overview/src/app/reactive/favorite-color/favorite-color.component.spec.ts" region="model-to-view" header="Favorite color test - model to view">
|
||||
<code-example path="forms-overview/src/app/reactive/favorite-color/favorite-color.component.spec.ts" region="view-to-model" header="Favorite color test - view to model">
|
||||
</code-example>
|
||||
|
||||
Here are the steps performed in the model to view test.
|
||||
The next example performs the following steps to verify the model-to-view data flow.
|
||||
|
||||
1. Use the `favoriteColorControl`, a `FormControl` instance, to set the new value.
|
||||
1. Query the view for the form input element.
|
||||
1. Assert that the new value set on the control matches the value in the input.
|
||||
|
||||
<code-example path="forms-overview/src/app/reactive/favorite-color/favorite-color.component.spec.ts" region="model-to-view" header="Favorite color test - model to view">
|
||||
</code-example>
|
||||
|
||||
### Testing template-driven forms
|
||||
|
||||
Writing tests with template-driven forms requires a detailed knowledge of the change detection process and an understanding of how directives run on each cycle to ensure that elements are queried, tested, or changed at the correct time.
|
||||
@ -228,46 +274,17 @@ Here are the steps performed in the model to view test.
|
||||
1. Query the view for the form input element.
|
||||
1. Assert that the input value matches the value of the `favoriteColor` property in the component instance.
|
||||
|
||||
## Mutability
|
||||
|
||||
The change tracking method plays a role in the efficiency of your application.
|
||||
|
||||
|
||||
* **Reactive forms** keep the data model pure by providing it as an immutable data structure. Each time a change is triggered on the data model, the `FormControl` instance returns a new data model rather than updating the existing data model. This gives you the ability to track unique changes to the data model through the control's observable. This provides one way for change detection to be more efficient because it only needs to update on unique changes. It also follows reactive patterns that integrate with observable operators to transform data.
|
||||
|
||||
* **Template-driven** forms rely on mutability with two-way data binding to update the data model in the component as changes are made in the template. Because there are no unique changes to track on the data model when using two-way data binding, change detection is less efficient at determining when updates are required.
|
||||
|
||||
The difference is demonstrated in the examples above using the **favorite color** input element.
|
||||
|
||||
|
||||
* With reactive forms, the **`FormControl` instance** always returns a new value when the control's value is updated.
|
||||
|
||||
* With template-driven forms, the **favorite color property** is always modified to its new value.
|
||||
|
||||
## Scalability
|
||||
|
||||
If forms are a central part of your application, scalability is very important. Being able to reuse form models across components is critical.
|
||||
|
||||
|
||||
* **Reactive forms** provide access to low-level APIs and synchronous access to the form model, making creating large-scale forms easier.
|
||||
|
||||
* **Template-driven** forms focus on simple scenarios, are not as reusable, abstract away the low-level APIs, and provide asynchronous access to the form model. The abstraction with template-driven forms also surfaces in testing, where testing reactive forms requires less setup and no dependence on the change detection cycle when updating and validating the form and data models during testing.
|
||||
|
||||
## Final thoughts
|
||||
|
||||
Choosing a strategy begins with understanding the strengths and weaknesses of the options presented. Low-level API and form model access, predictability, mutability, straightforward validation and testing strategies, and scalability are all important considerations in choosing the infrastructure you use to build your forms in Angular. Template-driven forms are similar to patterns in AngularJS, but they have limitations given the criteria of many modern, large-scale Angular apps. Reactive forms minimize these limitations. Reactive forms integrate with reactive patterns already present in other areas of the Angular architecture, and complement those requirements well.
|
||||
|
||||
## Next steps
|
||||
|
||||
|
||||
|
||||
To learn more about reactive forms, see the following guides:
|
||||
|
||||
* [Reactive Forms](guide/reactive-forms)
|
||||
* [Form Validation](guide/form-validation#reactive-form-validation)
|
||||
* [Dynamic Forms](guide/dynamic-form)
|
||||
* [Reactive forms](guide/reactive-forms)
|
||||
* [Form validation](guide/form-validation#reactive-form-validation)
|
||||
* [Dynamic forms](guide/dynamic-form)
|
||||
|
||||
To learn more about template-driven forms, see the following guides:
|
||||
|
||||
* [Template-driven Forms](guide/forms#template-driven-forms)
|
||||
* [Form Validation](guide/form-validation#template-driven-validation)
|
||||
* [Building a template-driven form](guide/forms) tutorial
|
||||
* [Form validation](guide/form-validation#template-driven-validation)
|
||||
* `NgForm` directive API reference
|
||||
|
@ -1,389 +1,234 @@
|
||||
# Template-driven forms
|
||||
|
||||
Forms are the mainstay of business applications.
|
||||
You use forms to log in, submit a help request, place an order, book a flight,
|
||||
schedule a meeting, and perform countless other data-entry tasks.
|
||||
|
||||
In developing a form, it's important to create a data-entry experience that guides the
|
||||
user efficiently and effectively through the workflow.
|
||||
|
||||
<div class="alert is-helpful">
|
||||
|
||||
For the sample app that this page describes, see the <live-example></live-example>.
|
||||
|
||||
</div>
|
||||
|
||||
## Introduction to Template-driven forms
|
||||
|
||||
Developing forms requires design skills (which are out of scope for this page), as well as framework support for
|
||||
*two-way data binding, change tracking, validation, and error handling*,
|
||||
which you'll learn about on this page.
|
||||
|
||||
This page shows you how to build a simple form from scratch. Along the way you'll learn how to:
|
||||
|
||||
* Build an Angular form with a component and template.
|
||||
* Use `ngModel` to create two-way data bindings for reading and writing input-control values.
|
||||
* Track state changes and the validity of form controls.
|
||||
* Provide visual feedback using special CSS classes that track the state of the controls.
|
||||
* Display validation errors to users and enable/disable form controls.
|
||||
* Share information across HTML elements using template reference variables.
|
||||
# Building a template-driven form
|
||||
|
||||
{@a template-driven}
|
||||
|
||||
You can build forms by writing templates in the Angular [template syntax](guide/template-syntax) with
|
||||
the form-specific directives and techniques described in this page.
|
||||
This tutorial shows you how to create a template-driven form whose control elements are bound to data properties, with input validation to maintain data integrity and styling to improve the user experience.
|
||||
|
||||
Template-driven forms use [two-way data binding](guide/architecture-components#data-binding "Intro to 2-way data binding") to update the data model in the component as changes are made in the template and vice versa.
|
||||
|
||||
<div class="alert is-helpful">
|
||||
|
||||
You can also use a reactive (or model-driven) approach to build forms.
|
||||
However, this page focuses on template-driven forms.
|
||||
Angular supports two design approaches for interactive forms. You can build forms by writing templates using Angular [template syntax and directives](guide/glossary#template "Definition of template terms") with the form-specific directives and techniques described in this tutorial, or you can use a reactive (or model-driven) approach to build forms.
|
||||
|
||||
Template-driven forms are suitable for small or simple forms, while reactive forms are more scalable and suitable for complex forms.
|
||||
For a comparison of the two approaches, see [Introduction to Forms](guide/forms-overview "Overview of Angular forms.")
|
||||
|
||||
</div>
|
||||
|
||||
You can build almost any form with an Angular template—login forms, contact forms, and pretty much any business form.
|
||||
You can lay out the controls creatively, bind them to data, specify validation rules and display validation errors,
|
||||
You can build almost any kind of form with an Angular template—login forms, contact forms, and pretty much any business form.
|
||||
You can lay out the controls creatively and bind them to the data in your object model.
|
||||
You can specify validation rules and display validation errors,
|
||||
conditionally enable or disable specific controls, trigger built-in visual feedback, and much more.
|
||||
|
||||
Angular makes the process easy by handling many of the repetitive, boilerplate tasks you'd
|
||||
otherwise wrestle with yourself.
|
||||
This tutorial shows you how to build a form from scratch, using a simplified sample form like the one from the [Tour of Heroes tutorial](tutorial "Tour of Heroes") to illustrate the techniques.
|
||||
|
||||
You'll learn to build a template-driven form that looks like this:
|
||||
<div class="alert is-helpful">
|
||||
|
||||
Run or download the example app: <live-example></live-example>.
|
||||
|
||||
</div>
|
||||
|
||||
## Objectives
|
||||
|
||||
This tutorial teaches you how to do the following:
|
||||
|
||||
* Build an Angular form with a component and template.
|
||||
* Use `ngModel` to create two-way data bindings for reading and writing input-control values.
|
||||
* Provide visual feedback using special CSS classes that track the state of the controls.
|
||||
* Display validation errors to users and enable or disable form controls based on the form status.
|
||||
* Share information across HTML elements using [template reference variables](guide/template-syntax#template-reference-variables-var).
|
||||
|
||||
## Prerequisites
|
||||
|
||||
Before going further into template-driven forms, you should have a basic understanding of the following.
|
||||
|
||||
* TypeScript and HTML5 programming.
|
||||
* Angular app-design fundamentals, as described in [Angular Concepts](guide/architecture "Introduction to Angular concepts.").
|
||||
* The basics of [Angular template syntax](guide/template-syntax "Template syntax guide").
|
||||
* The form-design concepts that are presented in [Introduction to Forms](guide/forms-overview "Overview of Angular forms.").
|
||||
|
||||
{@a intro}
|
||||
|
||||
## Build a template-driven form
|
||||
|
||||
Template-driven forms rely on directives defined in the `FormsModule`.
|
||||
|
||||
* The `NgModel` directive reconciles value changes in the attached form element with changes in the data model, allowing you to respond to user input with input validation and error handling.
|
||||
|
||||
* The `NgForm` directive creates a top-level `FormGroup` instance and binds it to a `<form>` element to track aggregated form value and validation status.
|
||||
As soon as you import `FormsModule`, this directive becomes active by default on all `<form>` tags. You don't need to add a special selector.
|
||||
|
||||
* The `NgModelGroup` directive creates and binds a `FormGroup` instance to a DOM element.
|
||||
|
||||
### The sample application
|
||||
|
||||
The sample form in this guide is used by the *Hero Employment Agency* to maintain personal information about heroes.
|
||||
Every hero needs a job. This form helps the agency match the right hero with the right crisis.
|
||||
|
||||
<div class="lightbox">
|
||||
<img src="generated/images/guide/forms/hero-form-1.png" alt="Clean Form">
|
||||
</div>
|
||||
|
||||
The *Hero Employment Agency* uses this form to maintain personal information about heroes.
|
||||
Every hero needs a job. It's the company mission to match the right hero with the right crisis.
|
||||
The form highlights some design features that make it easier to use. For instance, the two required fields have a green bar on the left to make them easy to spot. These fields have initial values, so the form is valid and the **Submit** button is enabled.
|
||||
|
||||
Two of the three fields on this form are required. Required fields have a green bar on the left to make them easy to spot.
|
||||
|
||||
If you delete the hero name, the form displays a validation error in an attention-grabbing style:
|
||||
As you work with this form, you will learn how to include validation logic, how to customize the presentation with standard CSS, and how to handle error conditions to ensure valid input.
|
||||
If the user deletes the hero name, for example, the form becomes invalid. The app detects the changed status, and displays a validation error in an attention-grabbing style.
|
||||
In addition, the **Submit** button is disabled, and the "required" bar to the left of the input control changes from green to red.
|
||||
|
||||
<div class="lightbox">
|
||||
<img src="generated/images/guide/forms/hero-form-2.png" alt="Invalid, Name Required">
|
||||
</div>
|
||||
|
||||
Note that the *Submit* button is disabled, and the "required" bar to the left of the input control changes from green to red.
|
||||
### Step overview
|
||||
|
||||
<div class="alert is-helpful">
|
||||
In the course of this tutorial, you bind a sample form to data and handle user input using the following steps.
|
||||
|
||||
You can customize the colors and location of the "required" bar with standard CSS.
|
||||
1. Build the basic form.
|
||||
* Define a sample data model.
|
||||
* Include required infrastructure such as the `FormsModule`.
|
||||
2. Bind form controls to data properties using the `ngModel` directive and two-way data-binding syntax.
|
||||
* Examine how `ngModel` reports control states using CSS classes.
|
||||
* Name controls to make them accessible to `ngModel`.
|
||||
3. Track input validity and control status using `ngModel`.
|
||||
* Add custom CSS to provide visual feedback on the status.
|
||||
* Show and hide validation-error messages.
|
||||
4. Respond to a native HTML button-click event by adding to the model data.
|
||||
5. Handle form submission using the [`ngSubmit`(api/forms/NgForm#properties)] output property of the form.
|
||||
* Disable the **Submit** button until the form is valid.
|
||||
* After submit, swap out the finished form for different content on the page.
|
||||
|
||||
</div>
|
||||
{@a step1}
|
||||
|
||||
You'll build this form in small steps:
|
||||
## Build the form
|
||||
|
||||
1. Create the `Hero` model class.
|
||||
1. Create the component that controls the form.
|
||||
1. Create a template with the initial form layout.
|
||||
1. Bind data properties to each form control using the `ngModel` two-way data-binding syntax.
|
||||
1. Add a `name` attribute to each form-input control.
|
||||
1. Add custom CSS to provide visual feedback.
|
||||
1. Show and hide validation-error messages.
|
||||
1. Handle form submission with *ngSubmit*.
|
||||
1. Disable the form’s *Submit* button until the form is valid.
|
||||
You can recreate the sample application from the code provided here, or you can examine or download the <live-example></live-example>.
|
||||
|
||||
## Setup
|
||||
1. The provided sample application creates the `Hero` class which defines the data model reflected in the form.
|
||||
|
||||
Create a new project named <code>angular-forms</code>:
|
||||
<code-example path="forms/src/app/hero.ts" header="src/app/hero.ts"></code-example>
|
||||
|
||||
<code-example language="sh" class="code-shell">
|
||||
2. The form layout and details are defined in the `HeroFormComponent` class.
|
||||
|
||||
ng new angular-forms
|
||||
<code-example path="forms/src/app/hero-form/hero-form.component.ts" header="src/app/hero-form/hero-form.component.ts (v1)" region="v1"></code-example>
|
||||
|
||||
</code-example>
|
||||
The component's `selector` value of "app-hero-form" means you can drop this form in a parent
|
||||
template using the `<app-hero-form>` tag.
|
||||
|
||||
## Create the Hero model class
|
||||
3. The following code creates a new hero instance, so that the initial form can show an example hero.
|
||||
|
||||
As users enter form data, you'll capture their changes and update an instance of a model.
|
||||
You can't lay out the form until you know what the model looks like.
|
||||
<code-example path="forms/src/app/hero-form/hero-form.component.ts" region="SkyDog"></code-example>
|
||||
|
||||
A model can be as simple as a "property bag" that holds facts about a thing of application importance.
|
||||
That describes well the `Hero` class with its three required fields (`id`, `name`, `power`)
|
||||
and one optional field (`alterEgo`).
|
||||
This demo uses dummy data for `model` and `powers`. In a real app, you would inject a data service to get and save real data, or expose these properties as inputs and outputs.
|
||||
|
||||
Using the Angular CLI command [`ng generate class`](cli/generate), generate a new class named `Hero`:
|
||||
4. The application enables the Forms feature and registers the created form component.
|
||||
|
||||
<code-example language="sh" class="code-shell">
|
||||
<code-example path="forms/src/app/app.module.ts" header="src/app/app.module.ts"></code-example>
|
||||
|
||||
ng generate class Hero
|
||||
5. The form is displayed in the application layout defined by the root component's template.
|
||||
|
||||
</code-example>
|
||||
<code-example path="forms/src/app/app.component.html" header="src/app/app.component.html"></code-example>
|
||||
|
||||
With this content:
|
||||
The initial template defines the layout for a form with two form groups and a submit button.
|
||||
The form groups correspond to two properties of the Hero data model, name and alterEgo. Each group has a label and a box for user input.
|
||||
|
||||
<code-example path="forms/src/app/hero.ts" header="src/app/hero.ts"></code-example>
|
||||
* The **Name** `<input>` control element has the HTML5 `required` attribute.
|
||||
* The **Alter Ego** `<input>` control element does not because `alterEgo` is optional.
|
||||
|
||||
It's an anemic model with few requirements and no behavior. Perfect for the demo.
|
||||
The **Submit** button has some classes on it for styling.
|
||||
At this point, the form layout is all plain HTML5, with no bindings or directives.
|
||||
|
||||
The TypeScript compiler generates a public field for each `public` constructor parameter and
|
||||
automatically assigns the parameter’s value to that field when you create heroes.
|
||||
6. The sample form uses some style classes from [Twitter Bootstrap](http://getbootstrap.com/css/): `container`, `form-group`, `form-control`, and `btn`.
|
||||
To use these styles, the app's style sheet imports the library.
|
||||
|
||||
The `alterEgo` is optional, so the constructor lets you omit it; note the question mark (?) in `alterEgo?`.
|
||||
<code-example path="forms/src/styles.1.css" header="src/styles.css"></code-example>
|
||||
|
||||
You can create a new hero like this:
|
||||
7. The form makes the hero applicant choose one superpower from a fixed list of agency-approved powers.
|
||||
The predefined list of `powers` is part of the data model, maintained internally in `HeroFormComponent`.
|
||||
The Angular [NgForOf directive](api/common/NgForOf "API reference") iterates over the data values to populate the `<select>` element.
|
||||
|
||||
<code-example path="forms/src/app/hero-form/hero-form.component.ts" region="SkyDog"></code-example>
|
||||
<code-example path="forms/src/app/hero-form/hero-form.component.html" header="src/app/hero-form/hero-form.component.html (powers)" region="powers"></code-example>
|
||||
|
||||
## Create a form component
|
||||
|
||||
An Angular form has two parts: an HTML-based _template_ and a component _class_
|
||||
to handle data and user interactions programmatically.
|
||||
Begin with the class because it states, in brief, what the hero editor can do.
|
||||
|
||||
Using the Angular CLI command [`ng generate component`](cli/generate), generate a new component named `HeroForm`:
|
||||
|
||||
<code-example language="sh" class="code-shell">
|
||||
|
||||
ng generate component HeroForm
|
||||
|
||||
</code-example>
|
||||
|
||||
With this content:
|
||||
|
||||
<code-example path="forms/src/app/hero-form/hero-form.component.ts" header="src/app/hero-form/hero-form.component.ts (v1)" region="v1"></code-example>
|
||||
|
||||
There’s nothing special about this component, nothing form-specific,
|
||||
nothing to distinguish it from any component you've written before.
|
||||
|
||||
Understanding this component requires only the Angular concepts covered in previous pages.
|
||||
|
||||
* The code imports the Angular core library and the `Hero` model you just created.
|
||||
* The `@Component` selector value of "app-hero-form" means you can drop this form in a parent
|
||||
template with a `<app-hero-form>` tag.
|
||||
* The `templateUrl` property points to a separate file for the template HTML.
|
||||
* You defined dummy data for `model` and `powers`, as befits a demo.
|
||||
|
||||
Down the road, you can inject a data service to get and save real data
|
||||
or perhaps expose these properties as inputs and outputs
|
||||
(see [Input and output properties](guide/template-syntax#inputs-outputs) on the
|
||||
[Template Syntax](guide/template-syntax) page) for binding to a
|
||||
parent component. This is not a concern now and these future changes won't affect the form.
|
||||
|
||||
* You added a `diagnostic` property to return a JSON representation of the model.
|
||||
It'll help you see what you're doing during development; you've left yourself a cleanup note to discard it later.
|
||||
|
||||
## Revise *app.module.ts*
|
||||
|
||||
`app.module.ts` defines the application's root module. In it you identify the external modules you'll use in the application
|
||||
and declare the components that belong to this module, such as the `HeroFormComponent`.
|
||||
|
||||
Because template-driven forms are in their own module, you need to add the `FormsModule` to the array of
|
||||
`imports` for the application module before you can use forms.
|
||||
|
||||
Update it with the following:
|
||||
|
||||
<code-example path="forms/src/app/app.module.ts" header="src/app/app.module.ts"></code-example>
|
||||
|
||||
<div class="alert is-helpful">
|
||||
|
||||
There are two changes:
|
||||
|
||||
1. You import `FormsModule`.
|
||||
|
||||
1. You add the `FormsModule` to the list of `imports` defined in the `@NgModule` decorator. This gives the application
|
||||
access to all of the template-driven forms features, including `ngModel`.
|
||||
|
||||
</div>
|
||||
|
||||
<div class="alert is-important">
|
||||
|
||||
If a component, directive, or pipe belongs to a module in the `imports` array, _don't_ re-declare it in the `declarations` array.
|
||||
If you wrote it and it should belong to this module, _do_ declare it in the `declarations` array.
|
||||
|
||||
</div>
|
||||
|
||||
## Revise *app.component.html*
|
||||
|
||||
`AppComponent` is the application's root component. It will host the new `HeroFormComponent`.
|
||||
|
||||
Replace the contents of its template with the following:
|
||||
|
||||
<code-example path="forms/src/app/app.component.html" header="src/app/app.component.html"></code-example>
|
||||
|
||||
<div class="alert is-helpful">
|
||||
|
||||
There are only two changes.
|
||||
The `template` is simply the new element tag identified by the component's `selector` property.
|
||||
This displays the hero form when the application component is loaded.
|
||||
Don't forget to remove the `name` field from the class body as well.
|
||||
|
||||
</div>
|
||||
|
||||
## Create an initial HTML form template
|
||||
|
||||
Update the template file with the following contents:
|
||||
|
||||
<code-example path="forms/src/app/hero-form/hero-form.component.html" region="start" header="src/app/hero-form/hero-form.component.html"></code-example>
|
||||
|
||||
The language is simply HTML5. You're presenting two of the `Hero` fields, `name` and `alterEgo`, and
|
||||
opening them up for user input in input boxes.
|
||||
|
||||
The *Name* `<input>` control has the HTML5 `required` attribute;
|
||||
the *Alter Ego* `<input>` control does not because `alterEgo` is optional.
|
||||
|
||||
You added a *Submit* button at the bottom with some classes on it for styling.
|
||||
|
||||
*You're not using Angular yet*. There are no bindings or extra directives, just layout.
|
||||
|
||||
<div class="alert is-helpful">
|
||||
|
||||
In template driven forms, if you've imported `FormsModule`, you don't have to do anything
|
||||
to the `<form>` tag in order to make use of `FormsModule`. Continue on to see how this works.
|
||||
|
||||
</div>
|
||||
|
||||
The `container`, `form-group`, `form-control`, and `btn` classes
|
||||
come from [Twitter Bootstrap](http://getbootstrap.com/css/). These classes are purely cosmetic.
|
||||
Bootstrap gives the form a little style.
|
||||
|
||||
<div class="callout is-important">
|
||||
|
||||
<header>
|
||||
Angular forms don't require a style library
|
||||
</header>
|
||||
|
||||
Angular makes no use of the `container`, `form-group`, `form-control`, and `btn` classes or
|
||||
the styles of any external library. Angular apps can use any CSS library or none at all.
|
||||
|
||||
</div>
|
||||
|
||||
To add the stylesheet, open `styles.css` and add the following import line at the top:
|
||||
|
||||
<code-example path="forms/src/styles.1.css" header="src/styles.css"></code-example>
|
||||
|
||||
## Add powers with _*ngFor_
|
||||
|
||||
The hero must choose one superpower from a fixed list of agency-approved powers.
|
||||
You maintain that list internally (in `HeroFormComponent`).
|
||||
|
||||
You'll add a `select` to the
|
||||
form and bind the options to the `powers` list using `ngFor`,
|
||||
a technique seen previously in the [Displaying Data](guide/displaying-data) page.
|
||||
|
||||
Add the following HTML *immediately below* the *Alter Ego* group:
|
||||
|
||||
<code-example path="forms/src/app/hero-form/hero-form.component.html" header="src/app/hero-form/hero-form.component.html (powers)" region="powers"></code-example>
|
||||
|
||||
This code repeats the `<option>` tag for each power in the list of powers.
|
||||
The `pow` template input variable is a different power in each iteration;
|
||||
you display its name using the interpolation syntax.
|
||||
|
||||
{@a ngModel}
|
||||
|
||||
## Two-way data binding with _ngModel_
|
||||
|
||||
Running the app right now would be disappointing.
|
||||
If you run the app right now, you see the list of powers in the selection control. The input elements are not yet bound to data values or events, so they are still blank and have no behavior.
|
||||
|
||||
<div class="lightbox">
|
||||
<img src="generated/images/guide/forms/hero-form-3.png" alt="Early form with no binding">
|
||||
</div>
|
||||
|
||||
{@a ngModel}
|
||||
|
||||
You don't see hero data because you're not binding to the `Hero` yet.
|
||||
You know how to do that from earlier pages.
|
||||
[Displaying Data](guide/displaying-data) teaches property binding.
|
||||
[User Input](guide/user-input) shows how to listen for DOM events with an
|
||||
event binding and how to update a component property with the displayed value.
|
||||
## Bind input controls to data properties
|
||||
|
||||
Now you need to display, listen, and extract at the same time.
|
||||
The next step is to bind the input controls to the corresponding `Hero` properties with two-way data binding, so that they respond to user input by updating the data model, and also respond to programmatic changes in the data by updating the display.
|
||||
|
||||
You could use the techniques you already know, but
|
||||
instead you'll use the new `[(ngModel)]` syntax, which
|
||||
makes binding the form to the model easy.
|
||||
The `ngModel` directive declared in the `FormsModule` lets you bind controls in your template-driven form to properties in your data model.
|
||||
When you include the directive using the syntax for two-way data binding, `[(ngModel)]`, Angular can track the value and user interaction of the control and keep the view synced with the model.
|
||||
|
||||
Find the `<input>` tag for *Name* and update it like this:
|
||||
1. Edit the template file `hero-form.component.html`.
|
||||
|
||||
2. Find the `<input>` tag next to the **Name** label.
|
||||
|
||||
3. Add the `ngModel` directive, using two-way data binding syntax `[(ngModel)]="..."`.
|
||||
|
||||
<code-example path="forms/src/app/hero-form/hero-form.component.html" header="src/app/hero-form/hero-form.component.html (excerpt)" region="ngModelName-1"></code-example>
|
||||
|
||||
<div class="alert is-helpful">
|
||||
|
||||
You added a diagnostic interpolation after the input tag
|
||||
so you can see what you're doing.
|
||||
You left yourself a note to throw it away when you're done.
|
||||
This example has a temporary diagnostic interpolation after each input tag, `{{model.name}}`, to show the current data value of the corresponding property.
|
||||
The note reminds you to remove the diagnostic lines when you have finished observing the two-way data binding at work.
|
||||
|
||||
</div>
|
||||
|
||||
Focus on the binding syntax: `[(ngModel)]="..."`.
|
||||
{@a ngForm}
|
||||
|
||||
You need one more addition to display the data. Declare
|
||||
a template variable for the form. Update the `<form>` tag with
|
||||
`#heroForm="ngForm"` as follows:
|
||||
### Access the overall form status
|
||||
|
||||
<code-example path="forms/src/app/hero-form/hero-form.component.html" header="src/app/hero-form/hero-form.component.html (excerpt)" region="template-variable"></code-example>
|
||||
When you imported the `FormsModule` in your component, Angular automatically created and attached an [NgForm](api/forms/NgForm "API reference for NgForm") directive to the `<form>` tag in the template (because `NgForm` has the selector `form` that matches `<form>` elements).
|
||||
|
||||
The variable `heroForm` is now a reference to the `NgForm` directive that governs the form as a whole.
|
||||
To get access to the `NgForm` and the overall form status, declare a [template reference variable](guide/template-syntax#template-reference-variables-var).
|
||||
|
||||
<div class="alert is-helpful">
|
||||
1. Edit the template file `hero-form.component.html`.
|
||||
|
||||
{@a ngForm}
|
||||
2. Update the `<form>` tag with a template reference variable, `#heroForm`, and set its value as follows.
|
||||
|
||||
### The _NgForm_ directive
|
||||
<code-example path="forms/src/app/hero-form/hero-form.component.html" header="src/app/hero-form/hero-form.component.html (excerpt)" region="template-variable"></code-example>
|
||||
|
||||
What `NgForm` directive?
|
||||
You didn't add an [NgForm](api/forms/NgForm) directive.
|
||||
The `heroForm` template variable is now a reference to the `NgForm` directive instance that governs the form as a whole.
|
||||
|
||||
Angular did. Angular automatically creates and attaches an `NgForm` directive to the `<form>` tag.
|
||||
3. Run the app.
|
||||
|
||||
The `NgForm` directive supplements the `form` element with additional features.
|
||||
It holds the controls you created for the elements with an `ngModel` directive
|
||||
and `name` attribute, and monitors their properties, including their validity.
|
||||
It also has its own `valid` property which is true only *if every contained
|
||||
control* is valid.
|
||||
4. Start typing in the **Name** input box.
|
||||
|
||||
</div>
|
||||
As you add and delete characters, you can see them appear and disappear from the data model.
|
||||
For example:
|
||||
|
||||
If you ran the app now and started typing in the *Name* input box,
|
||||
adding and deleting characters, you'd see them appear and disappear
|
||||
from the interpolated text.
|
||||
At some point it might look like this:
|
||||
<div class="lightbox">
|
||||
<img src="generated/images/guide/forms/ng-model-in-action.png" alt="ngModel in action">
|
||||
</div>
|
||||
|
||||
<div class="lightbox">
|
||||
<img src="generated/images/guide/forms/ng-model-in-action.png" alt="ngModel in action">
|
||||
</div>
|
||||
The diagnostic line that shows interpolated values demonstrates that values are really flowing from the input box to the model and back again.
|
||||
|
||||
The diagnostic is evidence that values really are flowing from the input box to the model and
|
||||
back again.
|
||||
### Naming control elements
|
||||
|
||||
<div class="alert is-helpful">
|
||||
When you use `[(ngModel)]` on an element, you must define a `name` attribute for that element.
|
||||
Angular uses the assigned name to register the element with the `NgForm` directive attached to the parent `<form>` element.
|
||||
|
||||
That's *two-way data binding*.
|
||||
For more information, see
|
||||
[Two-way binding with NgModel](guide/template-syntax#ngModel) on the
|
||||
the [Template Syntax](guide/template-syntax) page.
|
||||
The example added a `name` attribute to the `<input>` element and set it to "name",
|
||||
which makes sense for the hero's name.
|
||||
Any unique value will do, but using a descriptive name is helpful.
|
||||
|
||||
</div>
|
||||
1. Add similar `[(ngModel)]` bindings and `name` attributes to **Alter Ego** and **Hero Power**.
|
||||
|
||||
Notice that you also added a `name` attribute to the `<input>` tag and set it to "name",
|
||||
which makes sense for the hero's name. Any unique value will do, but using a descriptive name is helpful.
|
||||
Defining a `name` attribute is a requirement when using `[(ngModel)]` in combination with a form.
|
||||
2. You can now remove the diagnostic messages that show interpolated values.
|
||||
|
||||
<div class="alert is-helpful">
|
||||
3. To confirm that two-way data binding works for the entire hero model, add a new binding at the top to the component's `diagnostic` property.
|
||||
|
||||
Internally, Angular creates `FormControl` instances and
|
||||
registers them with an `NgForm` directive that Angular attached to the `<form>` tag.
|
||||
Each `FormControl` is registered under the name you assigned to the `name` attribute.
|
||||
Read more in the previous section, [The NgForm directive](guide/forms#ngForm).
|
||||
|
||||
</div>
|
||||
|
||||
Add similar `[(ngModel)]` bindings and `name` attributes to *Alter Ego* and *Hero Power*.
|
||||
You'll ditch the input box binding message
|
||||
and add a new binding (at the top) to the component's `diagnostic` property.
|
||||
Then you can confirm that two-way data binding works *for the entire hero model*.
|
||||
|
||||
After revision, the core of the form should look like this:
|
||||
After these revisions, the form template should look like the following:
|
||||
|
||||
<code-example path="forms/src/app/hero-form/hero-form.component.html" header="src/app/hero-form/hero-form.component.html (excerpt)" region="ngModel-2"></code-example>
|
||||
|
||||
<div class="alert is-helpful">
|
||||
* Notice that each `<input>` element has an `id` property. This is used by the `<label>` element's `for` attribute to match the label to its input control. This is a [standard HTML feature](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/label).
|
||||
|
||||
* Each input element has an `id` property that is used by the `label` element's `for` attribute
|
||||
to match the label to its input control.
|
||||
* Each input element has a `name` property that is required by Angular forms to register the control with the form.
|
||||
|
||||
</div>
|
||||
* Each `<input>` element also has the required `name` property that Angular uses to register the control with the form.
|
||||
|
||||
If you run the app now and change every hero model property, the form might display like this:
|
||||
|
||||
@ -391,18 +236,15 @@ If you run the app now and change every hero model property, the form might disp
|
||||
<img src="generated/images/guide/forms/ng-model-in-action-2.png" alt="ngModel in action">
|
||||
</div>
|
||||
|
||||
The diagnostic near the top of the form
|
||||
confirms that all of your changes are reflected in the model.
|
||||
The diagnostic near the top of the form confirms that all of your changes are reflected in the model.
|
||||
|
||||
*Delete* the `{{diagnostic}}` binding at the top as it has served its purpose.
|
||||
4. When you have observed the effects, you can delete the `{{diagnostic}}` binding.
|
||||
|
||||
## Track control state and validity with _ngModel_
|
||||
## Track control states
|
||||
|
||||
Using `ngModel` in a form gives you more than just two-way data binding. It also tells
|
||||
you if the user touched the control, if the value changed, or if the value became invalid.
|
||||
|
||||
The *NgModel* directive doesn't just track state; it updates the control with special Angular CSS classes that reflect the state.
|
||||
You can leverage those class names to change the appearance of the control.
|
||||
The `NgModel` directive on a control tracks the state of that control.
|
||||
It tells you if the user touched the control, if the value changed, or if the value became invalid.
|
||||
Angular sets special CSS classes on the control element to reflect the state, as shown in the following table.
|
||||
|
||||
<table>
|
||||
|
||||
@ -472,38 +314,32 @@ You can leverage those class names to change the appearance of the control.
|
||||
|
||||
</table>
|
||||
|
||||
Temporarily add a [template reference variable](guide/template-syntax#ref-vars) named `spy`
|
||||
to the _Name_ `<input>` tag and use it to display the input's CSS classes.
|
||||
You use these CSS classes to define the styles for your control based on its status.
|
||||
|
||||
<code-example path="forms/src/app/hero-form/hero-form.component.html" header="src/app/hero-form/hero-form.component.html (excerpt)" region="ngModelName-2"></code-example>
|
||||
### Observe control states
|
||||
|
||||
Now run the app and look at the _Name_ input box.
|
||||
Follow these steps *precisely*:
|
||||
To see how the classes are added and removed by the framework, open the browser's developer tools and inspect the `<input>` element that represents the hero name.
|
||||
|
||||
1. Look but don't touch.
|
||||
1. Click inside the name box, then click outside it.
|
||||
1. Add slashes to the end of the name.
|
||||
1. Erase the name.
|
||||
1. Using your browser's developer tools, find the `<input>` element that corresponds to the **Name** input box.
|
||||
You can see that the element has multiple CSS classes in addition to "form-control".
|
||||
|
||||
The actions and effects are as follows:
|
||||
2. When you first bring it up, the classes indicate that it has a valid value, that the value has not been changed since initialization or reset, and that the control has not been visited since initialization or reset.
|
||||
|
||||
<div class="lightbox">
|
||||
<img src="generated/images/guide/forms/control-state-transitions-anim.gif" alt="Control State Transition">
|
||||
</div>
|
||||
```
|
||||
<input ... class="form-control ng-untouched ng-pristine ng-valid" ...>
|
||||
```
|
||||
|
||||
You should see the following transitions and class names:
|
||||
3. Take the following actions on the **Name** `<input>` box, and observe which classes appear.
|
||||
* Look but don't touch. The classes indicate that it is untouched, pristine, and valid.
|
||||
* Click inside the name box, then click outside it. The control has now been visited, and the element has the `ng-touched` class instead of the `ng-untouched` class.
|
||||
* Add slashes to the end of the name. It is now touched and dirty.
|
||||
* Erase the name. This makes the value invalid, so the `ng-invalid` class replaces the `ng-valid` class.
|
||||
|
||||
<div class="lightbox">
|
||||
<img src="generated/images/guide/forms/ng-control-class-changes.png" alt="Control state transitions">
|
||||
</div>
|
||||
### Create visual feedback for states
|
||||
|
||||
The `ng-valid`/`ng-invalid` pair is the most interesting, because you want to send a
|
||||
strong visual signal when the values are invalid. You also want to mark required fields.
|
||||
To create such visual feedback, add definitions for the `ng-*` CSS classes.
|
||||
|
||||
*Delete* the `#spy` template reference variable and the `TODO` as they have served their purpose.
|
||||
|
||||
## Add custom CSS for visual feedback
|
||||
The `ng-valid`/`ng-invalid` pair is particularly interesting, because you want to send a
|
||||
strong visual signal when the values are invalid.
|
||||
You also want to mark required fields.
|
||||
|
||||
You can mark required fields and invalid data at the same time with a colored bar
|
||||
on the left of the input box:
|
||||
@ -512,20 +348,25 @@ on the left of the input box:
|
||||
<img src="generated/images/guide/forms/validity-required-indicator.png" alt="Invalid Form">
|
||||
</div>
|
||||
|
||||
You achieve this effect by adding these class definitions to a new `forms.css` file
|
||||
that you add to the project as a sibling to `index.html`:
|
||||
To change the appearance in this way, take the following steps.
|
||||
|
||||
<code-example path="forms/src/assets/forms.css" header="src/assets/forms.css"></code-example>
|
||||
1. Add definitions for the `ng-*` CSS classes.
|
||||
|
||||
Update the `<head>` of `index.html` to include this style sheet:
|
||||
2. Add these class definitions to a new `forms.css` file.
|
||||
|
||||
<code-example path="forms/src/index.html" header="src/index.html (styles)" region="styles"></code-example>
|
||||
3. Add the new file to the project as a sibling to `index.html`:
|
||||
|
||||
## Show and hide validation error messages
|
||||
<code-example path="forms/src/assets/forms.css" header="src/assets/forms.css"></code-example>
|
||||
|
||||
You can improve the form. The _Name_ input box is required and clearing it turns the bar red.
|
||||
That says something is wrong but the user doesn't know *what* is wrong or what to do about it.
|
||||
Leverage the control's state to reveal a helpful message.
|
||||
4. In the `index.html` file, update the `<head>` tag to include the new style sheet.
|
||||
|
||||
<code-example path="forms/src/index.html" header="src/index.html (styles)" region="styles"></code-example>
|
||||
|
||||
### Show and hide validation error messages
|
||||
|
||||
The **Name** input box is required and clearing it turns the bar red.
|
||||
That indicates that something is wrong, but the user doesn't know what is wrong or what to do about it.
|
||||
You can provide a helpful message by checking for and responding to the control's state.
|
||||
|
||||
When the user deletes the name, the form should look like this:
|
||||
|
||||
@ -533,166 +374,135 @@ When the user deletes the name, the form should look like this:
|
||||
<img src="generated/images/guide/forms/name-required-error.png" alt="Name required">
|
||||
</div>
|
||||
|
||||
To achieve this effect, extend the `<input>` tag with the following:
|
||||
The **Hero Power** select box is also required, but it doesn't need this kind of error handling because the selection box already constrains the selection to valid values.
|
||||
|
||||
* A [template reference variable](guide/template-syntax#ref-vars).
|
||||
* The "*is required*" message in a nearby `<div>`, which you'll display only if the control is invalid.
|
||||
To define and show an error message when appropriate, take the following steps.
|
||||
|
||||
Here's an example of an error message added to the _name_ input box:
|
||||
1. Extend the `<input>` tag with a template reference variable that you can use to access the input box's Angular control from within the template. In the example, the variable is `#name="ngModel"`.
|
||||
|
||||
<code-example path="forms/src/app/hero-form/hero-form.component.html" header="src/app/hero-form/hero-form.component.html (excerpt)" region="name-with-error-msg"></code-example>
|
||||
<div class="alert is-helpful">
|
||||
|
||||
You need a template reference variable to access the input box's Angular control from within the template.
|
||||
Here you created a variable called `name` and gave it the value "ngModel".
|
||||
The template reference variable (`#name`) is set to `"ngModel"` because that is the value of the [`NgModel.exportAs`](api/core/Directive#exportAs) property. This property tells Angular how to link a reference variable to a directive.
|
||||
|
||||
<div class="alert is-helpful">
|
||||
</div>
|
||||
|
||||
Why "ngModel"?
|
||||
A directive's [exportAs](api/core/Directive) property
|
||||
tells Angular how to link the reference variable to the directive.
|
||||
You set `name` to `ngModel` because the `ngModel` directive's `exportAs` property happens to be "ngModel".
|
||||
|
||||
</div>
|
||||
|
||||
You control visibility of the name error message by binding properties of the `name`
|
||||
2. Add a `<div>` that contains a suitable error message.
|
||||
3. Show or hide the error message by binding properties of the `name`
|
||||
control to the message `<div>` element's `hidden` property.
|
||||
|
||||
<code-example path="forms/src/app/hero-form/hero-form.component.html" header="src/app/hero-form/hero-form.component.html (hidden-error-msg)" region="hidden-error-msg"></code-example>
|
||||
<code-example path="forms/src/app/hero-form/hero-form.component.html" header="src/app/hero-form/hero-form.component.html (hidden-error-msg)" region="hidden-error-msg"></code-example>
|
||||
|
||||
In this example, you hide the message when the control is valid or pristine;
|
||||
"pristine" means the user hasn't changed the value since it was displayed in this form.
|
||||
4. Add a conditional error message to the _name_ input box, as in the following example.
|
||||
|
||||
This user experience is the developer's choice. Some developers want the message to display at all times.
|
||||
<code-example path="forms/src/app/hero-form/hero-form.component.html" header="src/app/hero-form/hero-form.component.html (excerpt)" region="name-with-error-msg"></code-example>
|
||||
|
||||
<div class="callout is-helpful">
|
||||
|
||||
<header>Illustrating the "pristine" state</header>
|
||||
|
||||
In this example, you hide the message when the control is either valid or *pristine*.
|
||||
Pristine means the user hasn't changed the value since it was displayed in this form.
|
||||
If you ignore the `pristine` state, you would hide the message only when the value is valid.
|
||||
If you arrive in this component with a new (blank) hero or an invalid hero,
|
||||
you'll see the error message immediately, before you've done anything.
|
||||
|
||||
Some developers want the message to display only when the user makes an invalid change.
|
||||
Hiding the message while the control is "pristine" achieves that goal.
|
||||
You'll see the significance of this choice when you add a new hero to the form.
|
||||
You might want the message to display only when the user makes an invalid change.
|
||||
Hiding the message while the control is in the `pristine` state achieves that goal.
|
||||
You'll see the significance of this choice when you add a new hero to the form in the next step.
|
||||
|
||||
The hero *Alter Ego* is optional so you can leave that be.
|
||||
</div>
|
||||
|
||||
Hero *Power* selection is required.
|
||||
You can add the same kind of error handling to the `<select>` if you want,
|
||||
but it's not imperative because the selection box already constrains the
|
||||
power to valid values.
|
||||
## Add a new hero
|
||||
|
||||
Now you'll add a new hero in this form.
|
||||
Place a *New Hero* button at the bottom of the form and bind its click event to a `newHero` component method.
|
||||
This exercise shows how you can respond to a native HTML button-click event by adding to the model data.
|
||||
To let form users add a new hero, you will add a **New Hero** button that responds to a click event.
|
||||
|
||||
<code-example path="forms/src/app/hero-form/hero-form.component.html" region="new-hero-button-no-reset" header="src/app/hero-form/hero-form.component.html (New Hero button)"></code-example>
|
||||
1. In the template, place a "New Hero" `<button>` element at the bottom of the form.
|
||||
2. In the component file, add the hero-creation method to the hero data model.
|
||||
|
||||
<code-example path="forms/src/app/hero-form/hero-form.component.ts" region="new-hero" header="src/app/hero-form/hero-form.component.ts (New Hero method)"></code-example>
|
||||
<code-example path="forms/src/app/hero-form/hero-form.component.ts" region="new-hero" header="src/app/hero-form/hero-form.component.ts (New Hero method)"></code-example>
|
||||
|
||||
Run the application again, click the *New Hero* button, and the form clears.
|
||||
The *required* bars to the left of the input box are red, indicating invalid `name` and `power` properties.
|
||||
That's understandable as these are required fields.
|
||||
The error messages are hidden because the form is pristine; you haven't changed anything yet.
|
||||
3. Bind the button's click event to a hero-creation method, `newHero()`.
|
||||
|
||||
Enter a name and click *New Hero* again.
|
||||
The app displays a _Name is required_ error message.
|
||||
You don't want error messages when you create a new (empty) hero.
|
||||
Why are you getting one now?
|
||||
<code-example path="forms/src/app/hero-form/hero-form.component.html" region="new-hero-button-no-reset" header="src/app/hero-form/hero-form.component.html (New Hero button)"></code-example>
|
||||
|
||||
Inspecting the element in the browser tools reveals that the *name* input box is _no longer pristine_.
|
||||
The form remembers that you entered a name before clicking *New Hero*.
|
||||
Replacing the hero object *did not restore the pristine state* of the form controls.
|
||||
4. Run the application again and click the **New Hero** button.
|
||||
|
||||
You have to clear all of the flags imperatively, which you can do
|
||||
by calling the form's `reset()` method after calling the `newHero()` method.
|
||||
The form clears, and the *required* bars to the left of the input box are red, indicating invalid `name` and `power` properties.
|
||||
Notice that the error messages are hidden. This is because the form is pristine; you haven't changed anything yet.
|
||||
|
||||
<code-example path="forms/src/app/hero-form/hero-form.component.html" region="new-hero-button-form-reset" header="src/app/hero-form/hero-form.component.html (Reset the form)"></code-example>
|
||||
5. Enter a name and click **New Hero** again.
|
||||
|
||||
Now clicking "New Hero" resets both the form and its control flags.
|
||||
Now the app displays a _Name is required_ error message, because the input box is no longer pristine.
|
||||
The form remembers that you entered a name before clicking **New Hero**.
|
||||
|
||||
6. To restore the pristine state of the form controls, clear all of the flags imperatively by calling the form's `reset()` method after calling the `newHero()` method.
|
||||
|
||||
<code-example path="forms/src/app/hero-form/hero-form.component.html" region="new-hero-button-form-reset" header="src/app/hero-form/hero-form.component.html (Reset the form)"></code-example>
|
||||
|
||||
Now clicking **New Hero** resets both the form and its control flags.
|
||||
|
||||
<div class="alert is-helpful">
|
||||
|
||||
See the [User Input](guide/user-input) guide for more information about listening for DOM events with an event binding and updating a corresponding component property.
|
||||
|
||||
</div>
|
||||
|
||||
## Submit the form with _ngSubmit_
|
||||
|
||||
The user should be able to submit this form after filling it in.
|
||||
The *Submit* button at the bottom of the form
|
||||
does nothing on its own, but it will
|
||||
trigger a form submit because of its type (`type="submit"`).
|
||||
The **Submit** button at the bottom of the form does nothing on its own, but it does
|
||||
trigger a form-submit event because of its type (`type="submit"`).
|
||||
To respond to this event, take the following steps.
|
||||
|
||||
A "form submit" is useless at the moment.
|
||||
To make it useful, bind the form's `ngSubmit` event property
|
||||
to the hero form component's `onSubmit()` method:
|
||||
1. Bind the form's [`ngSubmit`](api/forms/NgForm#properties) event property to the hero-form component's `onSubmit()` method.
|
||||
|
||||
<code-example path="forms/src/app/hero-form/hero-form.component.html" header="src/app/hero-form/hero-form.component.html (ngSubmit)" region="ngSubmit"></code-example>
|
||||
<code-example path="forms/src/app/hero-form/hero-form.component.html" header="src/app/hero-form/hero-form.component.html (ngSubmit)" region="ngSubmit"></code-example>
|
||||
|
||||
You'd already defined a template reference variable,
|
||||
`#heroForm`, and initialized it with the value "ngForm".
|
||||
Now, use that variable to access the form with the Submit button.
|
||||
2. Use the template reference variable, `#heroForm` to access the form that contains the **Submit** button and create an event binding.
|
||||
You will bind the form property that indicates its overall validity to the **Submit** button's `disabled` property.
|
||||
|
||||
<code-example path="forms/src/app/hero-form/hero-form.component.html" header="src/app/hero-form/hero-form.component.html (submit-button)" region="submit-button"></code-example>
|
||||
|
||||
You'll bind the form's overall validity via
|
||||
the `heroForm` variable to the button's `disabled` property
|
||||
using an event binding. Here's the code:
|
||||
|
||||
<code-example path="forms/src/app/hero-form/hero-form.component.html" header="src/app/hero-form/hero-form.component.html (submit-button)" region="submit-button"></code-example>
|
||||
|
||||
If you run the application now, you find that the button is enabled—although
|
||||
3. Run the application now. Notice that the button is enabled—although
|
||||
it doesn't do anything useful yet.
|
||||
|
||||
Now if you delete the Name, you violate the "required" rule, which
|
||||
is duly noted in the error message.
|
||||
The *Submit* button is also disabled.
|
||||
4. Delete the **Name** value. This violates the "required" rule, so it displays the error message&emdash;and notice that it also disables the **Submit** button.
|
||||
|
||||
Not impressed? Think about it for a moment. What would you have to do to
|
||||
wire the button's enable/disabled state to the form's validity without Angular's help?
|
||||
|
||||
For you, it was as simple as this:
|
||||
You didn't have to explicitly wire the button's enabled state to the form's validity.
|
||||
The `FormsModule` did this automatically when you defined a template reference variable on the enhanced form element, then referred to that variable in the button control.
|
||||
|
||||
1. Define a template reference variable on the (enhanced) form element.
|
||||
2. Refer to that variable in a button many lines away.
|
||||
### Respond to form submission
|
||||
|
||||
## Toggle two form regions (extra credit)
|
||||
To show a response to form submission, you can hide the data entry area and display something else in its place.
|
||||
|
||||
Submitting the form isn't terribly dramatic at the moment.
|
||||
|
||||
<div class="alert is-helpful">
|
||||
|
||||
An unsurprising observation for a demo. To be honest,
|
||||
jazzing it up won't teach you anything new about forms.
|
||||
But this is an opportunity to exercise some of your newly won
|
||||
binding skills.
|
||||
If you aren't interested, skip to this page's conclusion.
|
||||
|
||||
</div>
|
||||
|
||||
For a more strikingly visual effect,
|
||||
hide the data entry area and display something else.
|
||||
|
||||
Wrap the form in a `<div>` and bind
|
||||
1. Wrap the entire form in a `<div>` and bind
|
||||
its `hidden` property to the `HeroFormComponent.submitted` property.
|
||||
|
||||
<code-example path="forms/src/app/hero-form/hero-form.component.html" header="src/app/hero-form/hero-form.component.html (excerpt)" region="edit-div"></code-example>
|
||||
<code-example path="forms/src/app/hero-form/hero-form.component.html" header="src/app/hero-form/hero-form.component.html (excerpt)" region="edit-div"></code-example>
|
||||
|
||||
The main form is visible from the start because the
|
||||
`submitted` property is false until you submit the form,
|
||||
as this fragment from the `HeroFormComponent` shows:
|
||||
* The main form is visible from the start because the `submitted` property is false until you submit the form, as this fragment from the `HeroFormComponent` shows:
|
||||
|
||||
<code-example path="forms/src/app/hero-form/hero-form.component.ts" header="src/app/hero-form/hero-form.component.ts (submitted)" region="submitted"></code-example>
|
||||
<code-example path="forms/src/app/hero-form/hero-form.component.ts" header="src/app/hero-form/hero-form.component.ts (submitted)" region="submitted"></code-example>
|
||||
|
||||
When you click the *Submit* button, the `submitted` flag becomes true and the form disappears
|
||||
as planned.
|
||||
* When you click the **Submit** button, the `submitted` flag becomes true and the form disappears.
|
||||
|
||||
Now the app needs to show something else while the form is in the submitted state.
|
||||
Add the following HTML below the `<div>` wrapper you just wrote:
|
||||
2. To show something else while the form is in the submitted state, add the following HTML below the new `<div>` wrapper.
|
||||
|
||||
<code-example path="forms/src/app/hero-form/hero-form.component.html" header="src/app/hero-form/hero-form.component.html (excerpt)" region="submitted"></code-example>
|
||||
<code-example path="forms/src/app/hero-form/hero-form.component.html" header="src/app/hero-form/hero-form.component.html (excerpt)" region="submitted"></code-example>
|
||||
|
||||
There's the hero again, displayed read-only with interpolation bindings.
|
||||
This `<div>` appears only while the component is in the submitted state.
|
||||
This `<div>`, which shows a read-only hero with interpolation bindings, appears only while the component is in the submitted state.
|
||||
|
||||
The HTML includes an *Edit* button whose click event is bound to an expression
|
||||
The alternative display includes an *Edit* button whose click event is bound to an expression
|
||||
that clears the `submitted` flag.
|
||||
|
||||
When you click the *Edit* button, this block disappears and the editable form reappears.
|
||||
3. Click the *Edit* button to switch the display back to the editable form.
|
||||
|
||||
## Summary
|
||||
|
||||
The Angular form discussed in this page takes advantage of the following
|
||||
framework features to provide support for data modification, validation, and more:
|
||||
framework features to provide support for data modification, validation, and more.
|
||||
|
||||
* An Angular HTML form template.
|
||||
* A form component class with a `@Component` decorator.
|
||||
@ -700,8 +510,8 @@ framework features to provide support for data modification, validation, and mor
|
||||
* Template-reference variables such as `#heroForm` and `#name`.
|
||||
* `[(ngModel)]` syntax for two-way data binding.
|
||||
* The use of `name` attributes for validation and form-element change tracking.
|
||||
* The reference variable’s `valid` property on input controls to check if a control is valid and show/hide error messages.
|
||||
* Controlling the *Submit* button's enabled state by binding to `NgForm` validity.
|
||||
* The reference variable’s `valid` property on input controls to check if a control is valid and show or hide error messages.
|
||||
* Controlling the **Submit** button's enabled state by binding to `NgForm` validity.
|
||||
* Custom CSS classes that provide visual feedback to users about invalid controls.
|
||||
|
||||
Here’s the code for the final version of the application:
|
||||
@ -741,4 +551,3 @@ Here’s the code for the final version of the application:
|
||||
</code-pane>
|
||||
|
||||
</code-tabs>
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
# Communicating with backend services using HTTP
|
||||
|
||||
Most front-end applications need to communicate with a server over the HTTP protocol, in order to download or upload data and accesss other back-end services.
|
||||
Most front-end applications need to communicate with a server over the HTTP protocol, in order to download or upload data and access other back-end services.
|
||||
Angular provides a simplified client HTTP API for Angular applications, the `HttpClient` service class in `@angular/common/http`.
|
||||
|
||||
The HTTP client service offers the following major features.
|
||||
@ -63,7 +63,7 @@ Look at the `AppModule` _imports_ to see how it is configured.
|
||||
## Requesting data from a server
|
||||
|
||||
Use the [`HTTPClient.get()`](api/common/http/HttpClient#get) method to fetch data from a server.
|
||||
The aynchronous method sends an HTTP request, and returns an Observable that emits the requested data when the response is received.
|
||||
The asynchronous method sends an HTTP request, and returns an Observable that emits the requested data when the response is received.
|
||||
The return type varies based on the `observe` and `responseType` values that you pass to the call.
|
||||
|
||||
The `get()` method takes two arguments; the endpoint URL from which to fetch, and an *options* object that you can use to configure the request.
|
||||
@ -805,16 +805,16 @@ The `CachingInterceptor` in the following example demonstrates this approach.
|
||||
header="app/http-interceptors/caching-interceptor.ts)">
|
||||
</code-example>
|
||||
|
||||
* The `isCachable()` function determines if the request is cachable.
|
||||
In this sample, only GET requests to the npm package search api are cachable.
|
||||
* The `isCacheable()` function determines if the request is cacheable.
|
||||
In this sample, only GET requests to the npm package search api are cacheable.
|
||||
|
||||
* If the request is not cachable, the interceptor simply forwards the request
|
||||
* If the request is not cacheable, the interceptor simply forwards the request
|
||||
to the next handler in the chain.
|
||||
|
||||
* If a cachable request is found in the cache, the interceptor returns an `of()` _observable_ with
|
||||
* If a cacheable request is found in the cache, the interceptor returns an `of()` _observable_ with
|
||||
the cached response, by-passing the `next` handler (and all other interceptors downstream).
|
||||
|
||||
* If a cachable request is not in cache, the code calls `sendRequest()`.
|
||||
* If a cacheable request is not in cache, the code calls `sendRequest()`.
|
||||
This function creates a [request clone](#immutability) without headers, because the npm API forbids them.
|
||||
The function then forwards the clone of the request to `next.handle()` which ultimately calls the server and returns the server's response.
|
||||
|
||||
|
@ -193,6 +193,21 @@ The Angular service worker can use either of two caching strategies for data res
|
||||
|
||||
* `freshness` optimizes for currency of data, preferentially fetching requested data from the network. Only if the network times out, according to `timeout`, does the request fall back to the cache. This is useful for resources that change frequently; for example, account balances.
|
||||
|
||||
|
||||
<div class="alert is-helpful">
|
||||
|
||||
You can also emulate a third strategy, [staleWhileRevalidate](https://developers.google.com/web/fundamentals/instant-and-offline/offline-cookbook/#stale-while-revalidate), which returns cached data (if available), but also fetches fresh data from the network in the background for next time.
|
||||
To use this strategy set `strategy` to `freshness` and `timeout` to `0u` in `cacheConfig`.
|
||||
|
||||
This will essentially do the following:
|
||||
|
||||
1. Try to fetch from the network first.
|
||||
2. If the network request does not complete after 0ms (i.e. immediately), fall back to the cache (ignoring cache age).
|
||||
3. Once the network request completes, update the cache for future requests.
|
||||
4. If the resource does not exist in the cache, wait for the network request anyway.
|
||||
|
||||
</div>
|
||||
|
||||
### `cacheQueryOptions`
|
||||
|
||||
See [assetGroups](#assetgroups) for details.
|
||||
|
@ -107,7 +107,7 @@ Notice that all of the files the browser needs to render this application are ca
|
||||
<div class="alert is-helpful">
|
||||
Pay attention to two key points:
|
||||
|
||||
1. The generated `ngsw-config.json` includes a limited list of cachable fonts and images extentions. In some cases, you might want to modify the glob pattern to suit your needs.
|
||||
1. The generated `ngsw-config.json` includes a limited list of cacheable fonts and images extentions. In some cases, you might want to modify the glob pattern to suit your needs.
|
||||
|
||||
1. If `resourcesOutputPath` or `assets` paths are modified after the generation of configuration file, you need to change the paths manually in `ngsw-config.json`.
|
||||
</div>
|
||||
|
@ -251,7 +251,7 @@ You enable these features in the string assigned to `ngFor`, which you write in
|
||||
|
||||
Everything _outside_ the `ngFor` string stays with the host element
|
||||
(the `<div>`) as it moves inside the `<ng-template>`.
|
||||
In this example, the `[ngClass]="odd"` stays on the `<div>`.
|
||||
In this example, the `[class.odd]="odd"` stays on the `<div>`.
|
||||
|
||||
|
||||
</div>
|
||||
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 56 KiB |
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 57 KiB |
Before Width: | Height: | Size: 26 KiB After Width: | Height: | Size: 44 KiB |
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 39 KiB |
Before Width: | Height: | Size: 221 KiB |
Before Width: | Height: | Size: 41 KiB |
@ -254,11 +254,6 @@
|
||||
"title": "Reactive Forms",
|
||||
"tooltip": "Create a reactive form using FormBuilder, groups, and arrays."
|
||||
},
|
||||
{
|
||||
"url": "guide/forms",
|
||||
"title": "Template-driven Forms",
|
||||
"tooltip": "Create a template-driven form using directives and Angular template syntax."
|
||||
},
|
||||
{
|
||||
"url": "guide/form-validation",
|
||||
"title": "Validate form input",
|
||||
@ -628,11 +623,6 @@
|
||||
"title": "Building & Serving",
|
||||
"tooltip": "Building and serving Angular apps."
|
||||
},
|
||||
{
|
||||
"url": "guide/bazel",
|
||||
"title": "Building with Bazel",
|
||||
"tooltip": "How to set up your environment to build and test with Bazel."
|
||||
},
|
||||
{
|
||||
"url": "guide/testing",
|
||||
"title": "Testing",
|
||||
@ -754,6 +744,11 @@
|
||||
"url": "guide/router-tutorial",
|
||||
"title": "Using Angular Routes in a Single-page Application",
|
||||
"tooltip": "A tutorial that covers many patterns associated with Angular routing."
|
||||
},
|
||||
{
|
||||
"url": "guide/forms",
|
||||
"title": "Building a Template-driven Form",
|
||||
"tooltip": "Create a template-driven form using directives and Angular template syntax."
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -9,6 +9,11 @@ In this part of the tutorial, you'll do the following:
|
||||
3. Serve the application.
|
||||
4. Make changes to the application.
|
||||
|
||||
<div class="alert is-helpful">
|
||||
|
||||
For the sample app that this page describes, see the <live-example></live-example>.
|
||||
|
||||
</div>
|
||||
|
||||
## Set up your environment
|
||||
|
||||
@ -113,9 +118,6 @@ Open `src/styles.css` and add the code below to the file.
|
||||
|
||||
## Final code review
|
||||
|
||||
The source code for this tutorial and the complete _Tour of Heroes_ global styles
|
||||
are available in the <live-example></live-example>.
|
||||
|
||||
Here are the code files discussed on this page.
|
||||
|
||||
<code-tabs>
|
||||
|
@ -4,6 +4,12 @@ The application now has a basic title.
|
||||
Next you will create a new component to display hero information
|
||||
and place that component in the application shell.
|
||||
|
||||
<div class="alert is-helpful">
|
||||
|
||||
For the sample app that this page describes, see the <live-example></live-example>.
|
||||
|
||||
</div>
|
||||
|
||||
## Create the heroes component
|
||||
|
||||
Using the Angular CLI, generate a new component named `heroes`.
|
||||
@ -201,7 +207,7 @@ Note that `AppModule` declares both application components, `AppComponent` and
|
||||
|
||||
## Final code review
|
||||
|
||||
Your app should look like this <live-example></live-example>. Here are the code files discussed on this page.
|
||||
Here are the code files discussed on this page.
|
||||
|
||||
<code-tabs>
|
||||
|
||||
|
@ -3,6 +3,12 @@
|
||||
In this page, you'll expand the Tour of Heroes app to display a list of heroes, and
|
||||
allow users to select a hero and display the hero's details.
|
||||
|
||||
<div class="alert is-helpful">
|
||||
|
||||
For the sample app that this page describes, see the <live-example></live-example>.
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
## Create mock heroes
|
||||
|
||||
@ -220,8 +226,6 @@ The finished `<li>` looks like this:
|
||||
|
||||
## Final code review
|
||||
|
||||
Your app should look like this <live-example></live-example>.
|
||||
|
||||
Here are the code files discussed on this page, including the `HeroesComponent` styles.
|
||||
|
||||
<code-tabs>
|
||||
|
@ -10,6 +10,12 @@ In this page, you'll take the first step in that direction by moving the hero de
|
||||
The `HeroesComponent` will only present the list of heroes.
|
||||
The `HeroDetailComponent` will present details of a selected hero.
|
||||
|
||||
<div class="alert is-helpful">
|
||||
|
||||
For the sample app that this page describes, see the <live-example></live-example>.
|
||||
|
||||
</div>
|
||||
|
||||
## Make the `HeroDetailComponent`
|
||||
|
||||
Use the Angular CLI to generate a new component named `hero-detail`.
|
||||
@ -136,7 +142,7 @@ without touching the parent `HeroesComponent`.
|
||||
|
||||
## Final code review
|
||||
|
||||
Here are the code files discussed on this page and your app should look like this <live-example></live-example>.
|
||||
Here are the code files discussed on this page.
|
||||
|
||||
<code-tabs>
|
||||
|
||||
|
@ -5,6 +5,13 @@ The Tour of Heroes `HeroesComponent` is currently getting and displaying fake da
|
||||
After the refactoring in this tutorial, `HeroesComponent` will be lean and focused on supporting the view.
|
||||
It will also be easier to unit-test with a mock service.
|
||||
|
||||
<div class="alert is-helpful">
|
||||
|
||||
For the sample app that this page describes, see the <live-example></live-example>.
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
## Why services
|
||||
|
||||
Components shouldn't fetch or save data directly and they certainly shouldn't knowingly present fake data.
|
||||
@ -387,7 +394,7 @@ the selection. Use the "clear" button to clear the message history.
|
||||
|
||||
## Final code review
|
||||
|
||||
Here are the code files discussed on this page and your app should look like this <live-example></live-example>.
|
||||
Here are the code files discussed on this page.
|
||||
|
||||
<code-tabs>
|
||||
|
||||
|
@ -7,6 +7,12 @@ There are new requirements for the Tour of Heroes app:
|
||||
* When users click a hero name in either view, navigate to a detail view of the selected hero.
|
||||
* When users click a *deep link* in an email, open the detail view for a particular hero.
|
||||
|
||||
<div class="alert is-helpful">
|
||||
|
||||
For the sample app that this page describes, see the <live-example></live-example>.
|
||||
|
||||
</div>
|
||||
|
||||
When you’re done, users will be able to navigate the app like this:
|
||||
|
||||
<div class="lightbox">
|
||||
@ -466,7 +472,7 @@ from heroes list to the mini detail to the hero details and back to the heroes a
|
||||
|
||||
## Final code review
|
||||
|
||||
Here are the code files discussed on this page and your app should look like this <live-example></live-example>.
|
||||
Here are the code files discussed on this page.
|
||||
|
||||
{@a approutingmodule}
|
||||
{@a appmodule}
|
||||
|
@ -7,7 +7,11 @@ Angular's `HttpClient`.
|
||||
* Users can add, edit, and delete heroes and save these changes over HTTP.
|
||||
* Users can search for heroes by name.
|
||||
|
||||
When you're done with this page, the app should look like this <live-example></live-example>.
|
||||
<div class="alert is-helpful">
|
||||
|
||||
For the sample app that this page describes, see the <live-example></live-example>.
|
||||
|
||||
</div>
|
||||
|
||||
## Enable HTTP services
|
||||
|
||||
@ -519,8 +523,6 @@ If you enter characters that match any existing hero names, you'll see something
|
||||
|
||||
## Final code review
|
||||
|
||||
Your app should look like this <live-example></live-example>.
|
||||
|
||||
Here are the code files discussed on this page (all in the `src/app/` folder).
|
||||
|
||||
{@a heroservice}
|
||||
|
@ -18,6 +18,7 @@
|
||||
{"type": 301, "source": "/docs/*/latest/quickstart.html", "destination": "/start"},
|
||||
{"type": 301, "source": "/docs/*/latest/guide/server-communication.html", "destination": "/guide/http"},
|
||||
{"type": 301, "source": "/docs/*/latest/guide/style-guide.html", "destination": "/guide/styleguide"},
|
||||
{"type": 301, "source": "/guide/bazel", "destination": "https://github.com/angular/angular/blob/master/packages/bazel/src/schematics/README.md"},
|
||||
{"type": 301, "source": "/guide/cli-quickstart", "destination": "/start"},
|
||||
{"type": 301, "source": "/guide/service-worker-getstart", "destination": "/guide/service-worker-getting-started"},
|
||||
{"type": 301, "source": "/guide/service-worker-comm", "destination": "/guide/service-worker-communications"},
|
||||
|
@ -94,6 +94,7 @@
|
||||
"!/api/testing/**",
|
||||
"!/docs/?*",
|
||||
"!/docs/*/**",
|
||||
"!/guide/bazel",
|
||||
"!/guide/change-log",
|
||||
"!/getting-started",
|
||||
"!/getting-started.html",
|
||||
|
@ -23,7 +23,7 @@
|
||||
"build-local-with-viewengine": "yarn ~~build",
|
||||
"prebuild-local-with-viewengine-ci": "node scripts/switch-to-viewengine && yarn setup-local-ci",
|
||||
"build-local-with-viewengine-ci": "yarn ~~build --progress=false",
|
||||
"extract-cli-command-docs": "node tools/transforms/cli-docs-package/extract-cli-commands.js 200a21f8a",
|
||||
"extract-cli-command-docs": "node tools/transforms/cli-docs-package/extract-cli-commands.js 14af4e07c",
|
||||
"lint": "yarn check-env && yarn docs-lint && ng lint && yarn example-lint && yarn tools-lint",
|
||||
"test": "yarn check-env && ng test",
|
||||
"pree2e": "yarn check-env && yarn update-webdriver",
|
||||
@ -154,7 +154,7 @@
|
||||
"lunr": "^2.1.0",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"protractor": "~5.4.4",
|
||||
"puppeteer": "2.1.1",
|
||||
"puppeteer": "3.3.0",
|
||||
"rehype": "^6.0.0",
|
||||
"rehype-slug": "^2.0.0",
|
||||
"remark": "^9.0.0",
|
||||
|
@ -33,7 +33,7 @@ else
|
||||
readonly majorVersionStable=${CI_STABLE_BRANCH%%.*}
|
||||
|
||||
# Do not deploy if the major version is not less than the stable branch major version
|
||||
if [[ !( "$majorVersion" < "$majorVersionStable" ) ]]; then
|
||||
if [[ !( "$majorVersion" -lt "$majorVersionStable" ) ]]; then
|
||||
echo "Skipping deploy of branch \"$CI_BRANCH\" to firebase."
|
||||
echo "We only deploy archive branches with the major version less than the stable branch: \"$CI_STABLE_BRANCH\""
|
||||
exit 0
|
||||
|
@ -5,13 +5,9 @@
|
||||
</div>
|
||||
|
||||
<mat-toolbar color="primary" class="app-toolbar no-print" [class.transitioning]="isTransitioning">
|
||||
<mat-toolbar-row class="notification-container">
|
||||
<aio-notification notificationId="survey-march-2020" expirationDate="2020-04-15" [dismissOnContentClick]="true" (dismissed)="notificationDismissed()">
|
||||
<a href="https://goo.gle/angular-survey-2020">
|
||||
<mat-icon class="icon" svgIcon="insert_comment" aria-label="Announcement"></mat-icon>
|
||||
<span class="message">Help Angular by taking a <b>1 minute survey</b>!</span>
|
||||
<span class="action-button">Go to survey</span>
|
||||
</a>
|
||||
<mat-toolbar-row class="notification-container blm-message">
|
||||
<aio-notification notificationId="blm-2020" expirationDate="2022-04-15" [dismissOnContentClick]="true" (dismissed)="notificationDismissed()">
|
||||
#BlackLivesMatter
|
||||
</aio-notification>
|
||||
</mat-toolbar-row>
|
||||
<mat-toolbar-row>
|
||||
|
@ -183,8 +183,8 @@ section#intro {
|
||||
|
||||
// ANGULAR LINE
|
||||
.background-sky {
|
||||
background-color: $blue;
|
||||
background: $bluegradient;
|
||||
background-color: $black;
|
||||
background: $black;
|
||||
color: $white;
|
||||
}
|
||||
|
||||
|
@ -20,9 +20,16 @@ mat-toolbar.mat-toolbar {
|
||||
}
|
||||
}
|
||||
|
||||
.blm-message {
|
||||
text-align: center;
|
||||
justify-content: center;
|
||||
background: #2d2d2d;
|
||||
font-size: 0.75em;
|
||||
}
|
||||
|
||||
// HOME PAGE OVERRIDE: TOPNAV TOOLBAR
|
||||
aio-shell.page-home mat-toolbar.mat-toolbar {
|
||||
background-color: $blue;
|
||||
background-color: $black;
|
||||
|
||||
@media (min-width: 481px) {
|
||||
&:not(.transitioning) {
|
||||
|
@ -75,7 +75,7 @@
|
||||
"lite-server": "^2.2.2",
|
||||
"lodash": "^4.16.2",
|
||||
"protractor": "~5.4.3",
|
||||
"puppeteer": "2.1.1",
|
||||
"puppeteer": "3.3.0",
|
||||
"rimraf": "^2.5.4",
|
||||
"rollup": "^1.1.0",
|
||||
"rollup-plugin-commonjs": "^9.2.1",
|
||||
|
@ -1418,11 +1418,6 @@
|
||||
"@types/parse5" "*"
|
||||
"@types/tough-cookie" "*"
|
||||
|
||||
"@types/mime-types@^2.1.0":
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@types/mime-types/-/mime-types-2.1.0.tgz#9ca52cda363f699c69466c2a6ccdaad913ea7a73"
|
||||
integrity sha1-nKUs2jY/aZxpRmwqbM2q2RPqenM=
|
||||
|
||||
"@types/mime@*":
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@types/mime/-/mime-2.0.0.tgz#5a7306e367c539b9f6543499de8dd519fac37a8b"
|
||||
@ -1510,6 +1505,13 @@
|
||||
"@types/source-list-map" "*"
|
||||
source-map "^0.6.1"
|
||||
|
||||
"@types/yauzl@^2.9.1":
|
||||
version "2.9.1"
|
||||
resolved "https://registry.yarnpkg.com/@types/yauzl/-/yauzl-2.9.1.tgz#d10f69f9f522eef3cf98e30afb684a1e1ec923af"
|
||||
integrity sha512-A1b8SU4D10uoPjwb0lnHmmu8wZhR9d+9o2PKBQT2jU5YPTKsxac6M2qGAdY7VcL+dHHhARVUDmeg0rOrcd9EjA==
|
||||
dependencies:
|
||||
"@types/node" "*"
|
||||
|
||||
"@webassemblyjs/ast@1.8.5":
|
||||
version "1.8.5"
|
||||
resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.8.5.tgz#51b1c5fe6576a34953bf4b253df9f0d490d9e359"
|
||||
@ -2314,6 +2316,15 @@ binary-extensions@^2.0.0:
|
||||
resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.0.0.tgz#23c0df14f6a88077f5f986c0d167ec03c3d5537c"
|
||||
integrity sha512-Phlt0plgpIIBOGTT/ehfFnbNlfsDEiqmzE2KRXoX1bLIlir4X/MR+zSyBEkL05ffWgnRSf/DXv+WrUAVr93/ow==
|
||||
|
||||
bl@^4.0.1:
|
||||
version "4.0.2"
|
||||
resolved "https://registry.yarnpkg.com/bl/-/bl-4.0.2.tgz#52b71e9088515d0606d9dd9cc7aa48dc1f98e73a"
|
||||
integrity sha512-j4OH8f6Qg2bGuWfRiltT2HYGx0e1QcBTrK9KAHNMwMZdQnDZFk0ZSYIpADjYCB3U12nicC5tVJwSIhwOWjb4RQ==
|
||||
dependencies:
|
||||
buffer "^5.5.0"
|
||||
inherits "^2.0.4"
|
||||
readable-stream "^3.4.0"
|
||||
|
||||
blob@0.0.4:
|
||||
version "0.0.4"
|
||||
resolved "https://registry.yarnpkg.com/blob/-/blob-0.0.4.tgz#bcf13052ca54463f30f9fc7e95b9a47630a94921"
|
||||
@ -2687,6 +2698,11 @@ btoa@^1.1.2:
|
||||
version "1.1.2"
|
||||
resolved "https://registry.yarnpkg.com/btoa/-/btoa-1.1.2.tgz#3e40b81663f81d2dd6596a4cb714a8dc16cfabe0"
|
||||
|
||||
buffer-crc32@~0.2.3:
|
||||
version "0.2.13"
|
||||
resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242"
|
||||
integrity sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI=
|
||||
|
||||
buffer-from@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.0.0.tgz#4cb8832d23612589b0406e9e2956c17f06fdf531"
|
||||
@ -2707,6 +2723,14 @@ buffer@^4.3.0:
|
||||
ieee754 "^1.1.4"
|
||||
isarray "^1.0.0"
|
||||
|
||||
buffer@^5.2.1, buffer@^5.5.0:
|
||||
version "5.6.0"
|
||||
resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.6.0.tgz#a31749dc7d81d84db08abf937b6b8c4033f62786"
|
||||
integrity sha512-/gDYp/UtU0eA1ys8bOs9J6a+E/KWIY+DZ+Q2WESNUA0jFRsJOc0SNUO6xJ5SGA1xueg3NL65W6s+NY5l9cunuw==
|
||||
dependencies:
|
||||
base64-js "^1.0.2"
|
||||
ieee754 "^1.1.4"
|
||||
|
||||
builtin-modules@^1.0.0, builtin-modules@^1.1.1:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-1.1.1.tgz#270f076c5a72c02f5b65a47df94c5fe3a278892f"
|
||||
@ -3290,16 +3314,6 @@ concat-map@0.0.1:
|
||||
version "0.0.1"
|
||||
resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
|
||||
|
||||
concat-stream@1.6.2:
|
||||
version "1.6.2"
|
||||
resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.2.tgz#904bdf194cd3122fc675c77fc4ac3d4ff0fd1a34"
|
||||
integrity sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==
|
||||
dependencies:
|
||||
buffer-from "^1.0.0"
|
||||
inherits "^2.0.3"
|
||||
readable-stream "^2.2.2"
|
||||
typedarray "^0.0.6"
|
||||
|
||||
concat-stream@^1.5.0:
|
||||
version "1.6.0"
|
||||
resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.0.tgz#0aac662fd52be78964d5532f694784e70110acf7"
|
||||
@ -4249,6 +4263,13 @@ end-of-stream@^1.0.0, end-of-stream@^1.1.0:
|
||||
dependencies:
|
||||
once "^1.4.0"
|
||||
|
||||
end-of-stream@^1.4.1:
|
||||
version "1.4.4"
|
||||
resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0"
|
||||
integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==
|
||||
dependencies:
|
||||
once "^1.4.0"
|
||||
|
||||
engine.io-client@1.8.0:
|
||||
version "1.8.0"
|
||||
resolved "https://registry.yarnpkg.com/engine.io-client/-/engine.io-client-1.8.0.tgz#7b730e4127414087596d9be3c88d2bc5fdb6cf5c"
|
||||
@ -4715,15 +4736,16 @@ extglob@^2.0.2, extglob@^2.0.4:
|
||||
snapdragon "^0.8.1"
|
||||
to-regex "^3.0.1"
|
||||
|
||||
extract-zip@^1.6.6:
|
||||
version "1.6.7"
|
||||
resolved "https://registry.yarnpkg.com/extract-zip/-/extract-zip-1.6.7.tgz#a840b4b8af6403264c8db57f4f1a74333ef81fe9"
|
||||
integrity sha1-qEC0uK9kAyZMjbV/Txp0Mz74H+k=
|
||||
extract-zip@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/extract-zip/-/extract-zip-2.0.0.tgz#f53b71d44f4ff5a4527a2259ade000fb8b303492"
|
||||
integrity sha512-i42GQ498yibjdvIhivUsRslx608whtGoFIhF26Z7O4MYncBxp8CwalOs1lnHy21A9sIohWO2+uiE4SRtC9JXDg==
|
||||
dependencies:
|
||||
concat-stream "1.6.2"
|
||||
debug "2.6.9"
|
||||
mkdirp "0.5.1"
|
||||
yauzl "2.4.1"
|
||||
debug "^4.1.1"
|
||||
get-stream "^5.1.0"
|
||||
yauzl "^2.10.0"
|
||||
optionalDependencies:
|
||||
"@types/yauzl" "^2.9.1"
|
||||
|
||||
extsprintf@1.3.0, extsprintf@^1.2.0:
|
||||
version "1.3.0"
|
||||
@ -4774,10 +4796,10 @@ faye-websocket@~0.11.1:
|
||||
dependencies:
|
||||
websocket-driver ">=0.5.1"
|
||||
|
||||
fd-slicer@~1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/fd-slicer/-/fd-slicer-1.0.1.tgz#8b5bcbd9ec327c5041bf9ab023fd6750f1177e65"
|
||||
integrity sha1-i1vL2ewyfFBBv5qwI/1nUPEXfmU=
|
||||
fd-slicer@~1.1.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/fd-slicer/-/fd-slicer-1.1.0.tgz#25c7c89cb1f9077f8891bbe61d8f390eae256f1e"
|
||||
integrity sha1-JcfInLH5B3+IkbvmHY85Dq4lbx4=
|
||||
dependencies:
|
||||
pend "~1.2.0"
|
||||
|
||||
@ -5045,6 +5067,11 @@ from2@^2.1.0:
|
||||
inherits "^2.0.1"
|
||||
readable-stream "^2.0.0"
|
||||
|
||||
fs-constants@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/fs-constants/-/fs-constants-1.0.0.tgz#6be0de9be998ce16af8afc24497b9ee9b7ccd9ad"
|
||||
integrity sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==
|
||||
|
||||
fs-extra@3.0.1:
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-3.0.1.tgz#3794f378c58b342ea7dbbb23095109c4b3b62291"
|
||||
@ -5194,6 +5221,13 @@ get-stream@^4.0.0, get-stream@^4.1.0:
|
||||
dependencies:
|
||||
pump "^3.0.0"
|
||||
|
||||
get-stream@^5.1.0:
|
||||
version "5.1.0"
|
||||
resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-5.1.0.tgz#01203cdc92597f9b909067c3e656cc1f4d3c4dc9"
|
||||
integrity sha512-EXr1FOzrzTfGeL0gQdeFEvOMm2mzMOglyiOXSTpPC+iAjAKftbr3jpCMWynogwYnM+eSj9sHGc6wjIcDvYiygw==
|
||||
dependencies:
|
||||
pump "^3.0.0"
|
||||
|
||||
get-value@^2.0.3, get-value@^2.0.6:
|
||||
version "2.0.6"
|
||||
resolved "https://registry.yarnpkg.com/get-value/-/get-value-2.0.6.tgz#dc15ca1c672387ca76bd37ac0a395ba2042a2c28"
|
||||
@ -5872,7 +5906,7 @@ inherits@2.0.1:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.1.tgz#b17d08d326b4423e568eff719f91b0b1cbdf69f1"
|
||||
|
||||
inherits@2.0.4:
|
||||
inherits@2.0.4, inherits@^2.0.4:
|
||||
version "2.0.4"
|
||||
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
|
||||
integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
|
||||
@ -7225,11 +7259,6 @@ mime-db@1.42.0:
|
||||
resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.42.0.tgz#3e252907b4c7adb906597b4b65636272cf9e7bac"
|
||||
integrity sha512-UbfJCR4UAVRNgMpfImz05smAXK7+c+ZntjaA26ANtkXLlOe947Aag5zdIcKQULAiF9Cq4WxBi9jUs5zkA84bYQ==
|
||||
|
||||
mime-db@1.43.0:
|
||||
version "1.43.0"
|
||||
resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.43.0.tgz#0a12e0502650e473d735535050e7c8f4eb4fae58"
|
||||
integrity sha512-+5dsGEEovYbT8UY9yD7eE4XTc4UwJ1jBYlgaQQF38ENsKR3wj/8q8RFZrF9WIZpB2V1ArTVFUva8sAul1NzRzQ==
|
||||
|
||||
"mime-db@>= 1.40.0 < 2":
|
||||
version "1.40.0"
|
||||
resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.40.0.tgz#a65057e998db090f732a68f6c276d387d4126c32"
|
||||
@ -7249,13 +7278,6 @@ mime-types@^2.1.12, mime-types@~2.1.11, mime-types@~2.1.15, mime-types@~2.1.16,
|
||||
dependencies:
|
||||
mime-db "~1.30.0"
|
||||
|
||||
mime-types@^2.1.25:
|
||||
version "2.1.26"
|
||||
resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.26.tgz#9c921fc09b7e149a65dfdc0da4d20997200b0a06"
|
||||
integrity sha512-01paPWYgLrkqAyrlDorC1uDwl2p3qZT7yl806vW7DvDoxwXi46jsjFbg+WdwotBIk6/MbEhO/dh5aZ5sNj/dWQ==
|
||||
dependencies:
|
||||
mime-db "1.43.0"
|
||||
|
||||
mime-types@~2.1.18, mime-types@~2.1.19:
|
||||
version "2.1.20"
|
||||
resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.20.tgz#930cb719d571e903738520f8470911548ca2cc19"
|
||||
@ -7449,11 +7471,16 @@ mixin-deep@^1.2.0:
|
||||
for-in "^1.0.2"
|
||||
is-extendable "^1.0.1"
|
||||
|
||||
mkdirp-classic@^0.5.2:
|
||||
version "0.5.3"
|
||||
resolved "https://registry.yarnpkg.com/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz#fa10c9115cc6d8865be221ba47ee9bed78601113"
|
||||
integrity sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==
|
||||
|
||||
mkdirp@0.3.0:
|
||||
version "0.3.0"
|
||||
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.3.0.tgz#1bbf5ab1ba827af23575143490426455f481fe1e"
|
||||
|
||||
mkdirp@0.5.1, mkdirp@0.5.x, "mkdirp@>=0.5 0", mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@~0.5.1, mkdirp@~0.5.x:
|
||||
mkdirp@0.5.x, "mkdirp@>=0.5 0", mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@~0.5.1, mkdirp@~0.5.x:
|
||||
version "0.5.1"
|
||||
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903"
|
||||
dependencies:
|
||||
@ -9068,21 +9095,21 @@ punycode@^2.1.1:
|
||||
resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec"
|
||||
integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==
|
||||
|
||||
puppeteer@2.1.1:
|
||||
version "2.1.1"
|
||||
resolved "https://registry.yarnpkg.com/puppeteer/-/puppeteer-2.1.1.tgz#ccde47c2a688f131883b50f2d697bd25189da27e"
|
||||
integrity sha512-LWzaDVQkk1EPiuYeTOj+CZRIjda4k2s5w4MK4xoH2+kgWV/SDlkYHmxatDdtYrciHUKSXTsGgPgPP8ILVdBsxg==
|
||||
puppeteer@3.3.0:
|
||||
version "3.3.0"
|
||||
resolved "https://registry.yarnpkg.com/puppeteer/-/puppeteer-3.3.0.tgz#95839af9fdc0aa4de7e5ee073a4c0adeb9e2d3d7"
|
||||
integrity sha512-23zNqRltZ1PPoK28uRefWJ/zKb5Jhnzbbwbpcna2o5+QMn17F0khq5s1bdH3vPlyj+J36pubccR8wiNA/VE0Vw==
|
||||
dependencies:
|
||||
"@types/mime-types" "^2.1.0"
|
||||
debug "^4.1.0"
|
||||
extract-zip "^1.6.6"
|
||||
extract-zip "^2.0.0"
|
||||
https-proxy-agent "^4.0.0"
|
||||
mime "^2.0.3"
|
||||
mime-types "^2.1.25"
|
||||
progress "^2.0.1"
|
||||
proxy-from-env "^1.0.0"
|
||||
rimraf "^2.6.1"
|
||||
ws "^6.1.0"
|
||||
rimraf "^3.0.2"
|
||||
tar-fs "^2.0.0"
|
||||
unbzip2-stream "^1.3.3"
|
||||
ws "^7.2.3"
|
||||
|
||||
q@1.4.1:
|
||||
version "1.4.1"
|
||||
@ -9316,6 +9343,15 @@ readable-stream@^3.0.6:
|
||||
string_decoder "^1.1.1"
|
||||
util-deprecate "^1.0.1"
|
||||
|
||||
readable-stream@^3.1.1, readable-stream@^3.4.0:
|
||||
version "3.6.0"
|
||||
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198"
|
||||
integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==
|
||||
dependencies:
|
||||
inherits "^2.0.3"
|
||||
string_decoder "^1.1.1"
|
||||
util-deprecate "^1.0.1"
|
||||
|
||||
readable-stream@~2.0.6:
|
||||
version "2.0.6"
|
||||
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.0.6.tgz#8f90341e68a53ccc928788dacfcd11b36eb9b78e"
|
||||
@ -9722,7 +9758,7 @@ rimraf@2, rimraf@^2.2.8, rimraf@^2.5.1, rimraf@^2.5.2, rimraf@^2.5.4, rimraf@^2.
|
||||
dependencies:
|
||||
glob "^7.0.5"
|
||||
|
||||
rimraf@3.0.2:
|
||||
rimraf@3.0.2, rimraf@^3.0.2:
|
||||
version "3.0.2"
|
||||
resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a"
|
||||
integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==
|
||||
@ -10961,6 +10997,16 @@ tapable@^1.1.3:
|
||||
resolved "https://registry.yarnpkg.com/tapable/-/tapable-1.1.3.tgz#a1fccc06b58db61fd7a45da2da44f5f3a3e67ba2"
|
||||
integrity sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA==
|
||||
|
||||
tar-fs@^2.0.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/tar-fs/-/tar-fs-2.1.0.tgz#d1cdd121ab465ee0eb9ccde2d35049d3f3daf0d5"
|
||||
integrity sha512-9uW5iDvrIMCVpvasdFHW0wJPez0K4JnMZtsuIeDI7HyMGJNxmDZDOCQROr7lXyS+iL/QMpj07qcjGYTSdRFXUg==
|
||||
dependencies:
|
||||
chownr "^1.1.1"
|
||||
mkdirp-classic "^0.5.2"
|
||||
pump "^3.0.0"
|
||||
tar-stream "^2.0.0"
|
||||
|
||||
tar-pack@^3.4.0:
|
||||
version "3.4.1"
|
||||
resolved "https://registry.yarnpkg.com/tar-pack/-/tar-pack-3.4.1.tgz#e1dbc03a9b9d3ba07e896ad027317eb679a10a1f"
|
||||
@ -10974,6 +11020,17 @@ tar-pack@^3.4.0:
|
||||
tar "^2.2.1"
|
||||
uid-number "^0.0.6"
|
||||
|
||||
tar-stream@^2.0.0:
|
||||
version "2.1.2"
|
||||
resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-2.1.2.tgz#6d5ef1a7e5783a95ff70b69b97455a5968dc1325"
|
||||
integrity sha512-UaF6FoJ32WqALZGOIAApXx+OdxhekNMChu6axLJR85zMMjXKWFGjbIRe+J6P4UnRGg9rAwWvbTT0oI7hD/Un7Q==
|
||||
dependencies:
|
||||
bl "^4.0.1"
|
||||
end-of-stream "^1.4.1"
|
||||
fs-constants "^1.0.0"
|
||||
inherits "^2.0.3"
|
||||
readable-stream "^3.1.1"
|
||||
|
||||
tar@^2.2.1:
|
||||
version "2.2.1"
|
||||
resolved "https://registry.yarnpkg.com/tar/-/tar-2.2.1.tgz#8e4d2a256c0e2185c6b18ad694aec968b83cb1d1"
|
||||
@ -11106,7 +11163,7 @@ through2@^2.0.0:
|
||||
readable-stream "^2.1.5"
|
||||
xtend "~4.0.1"
|
||||
|
||||
"through@>=2.2.7 <3", through@X.X.X, through@^2.3.6:
|
||||
"through@>=2.2.7 <3", through@X.X.X, through@^2.3.6, through@^2.3.8:
|
||||
version "2.3.8"
|
||||
resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5"
|
||||
|
||||
@ -11378,6 +11435,14 @@ ultron@~1.1.0:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/ultron/-/ultron-1.1.1.tgz#9fe1536a10a664a65266a1e3ccf85fd36302bc9c"
|
||||
|
||||
unbzip2-stream@^1.3.3:
|
||||
version "1.4.3"
|
||||
resolved "https://registry.yarnpkg.com/unbzip2-stream/-/unbzip2-stream-1.4.3.tgz#b0da04c4371311df771cdc215e87f2130991ace7"
|
||||
integrity sha512-mlExGW4w71ebDJviH16lQLtZS32VKqsSfk80GCfUlwT/4/hNRFsoscrF/c++9xinkMzECL1uL9DDwXqFWkruPg==
|
||||
dependencies:
|
||||
buffer "^5.2.1"
|
||||
through "^2.3.8"
|
||||
|
||||
underscore@1.7.x:
|
||||
version "1.7.0"
|
||||
resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.7.0.tgz#6bbaf0877500d36be34ecaa584e0db9fef035209"
|
||||
@ -11978,7 +12043,7 @@ ws@1.1.1:
|
||||
options ">=0.0.5"
|
||||
ultron "1.0.x"
|
||||
|
||||
ws@^6.1.0, ws@^6.2.1:
|
||||
ws@^6.2.1:
|
||||
version "6.2.1"
|
||||
resolved "https://registry.yarnpkg.com/ws/-/ws-6.2.1.tgz#442fdf0a47ed64f59b6a5d8ff130f4748ed524fb"
|
||||
integrity sha512-GIyAXC2cB7LjvpgMt9EKS2ldqr0MTrORaleiOno6TweZ6r3TKtoFQWay/2PceJ3RuBasOHzXNn5Lrw1X0bEjqA==
|
||||
@ -11990,6 +12055,11 @@ ws@^7.2.1:
|
||||
resolved "https://registry.yarnpkg.com/ws/-/ws-7.2.3.tgz#a5411e1fb04d5ed0efee76d26d5c46d830c39b46"
|
||||
integrity sha512-HTDl9G9hbkNDk98naoR/cHDws7+EyYMOdL1BmjsZXRUjf7d+MficC4B7HLUPlSiho0vg+CWKrGIt/VJBd1xunQ==
|
||||
|
||||
ws@^7.2.3:
|
||||
version "7.3.0"
|
||||
resolved "https://registry.yarnpkg.com/ws/-/ws-7.3.0.tgz#4b2f7f219b3d3737bc1a2fbf145d825b94d38ffd"
|
||||
integrity sha512-iFtXzngZVXPGgpTlP1rBqsUK82p9tKqsWRPg5L56egiljujJT3vGAYnHANvFxBieXrTFavhzhxW52jnaWV+w2w==
|
||||
|
||||
ws@~3.3.1:
|
||||
version "3.3.3"
|
||||
resolved "https://registry.yarnpkg.com/ws/-/ws-3.3.3.tgz#f1cf84fe2d5e901ebce94efaece785f187a228f2"
|
||||
@ -12216,12 +12286,13 @@ yargs@^15.3.1:
|
||||
y18n "^4.0.0"
|
||||
yargs-parser "^18.1.1"
|
||||
|
||||
yauzl@2.4.1:
|
||||
version "2.4.1"
|
||||
resolved "https://registry.yarnpkg.com/yauzl/-/yauzl-2.4.1.tgz#9528f442dab1b2284e58b4379bb194e22e0c4005"
|
||||
integrity sha1-lSj0QtqxsihOWLQ3m7GU4i4MQAU=
|
||||
yauzl@^2.10.0:
|
||||
version "2.10.0"
|
||||
resolved "https://registry.yarnpkg.com/yauzl/-/yauzl-2.10.0.tgz#c7eb17c93e112cb1086fa6d8e51fb0667b79a5f9"
|
||||
integrity sha1-x+sXyT4RLLEIb6bY5R+wZnt5pfk=
|
||||
dependencies:
|
||||
fd-slicer "~1.0.1"
|
||||
buffer-crc32 "~0.2.3"
|
||||
fd-slicer "~1.1.0"
|
||||
|
||||
yeast@0.1.2:
|
||||
version "0.1.2"
|
||||
|
@ -1,6 +1,6 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
* Copyright Google LLC All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
|
@ -1,9 +1,9 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
* Copyright Google LLC All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
export const x = 100;
|
||||
export const x = 100;
|
||||
|
@ -1,6 +1,6 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
* Copyright Google LLC All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
|
@ -1,6 +1,6 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
* Copyright Google LLC All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
|
@ -1,6 +1,6 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
* Copyright Google LLC All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
|
@ -1,6 +1,6 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
* Copyright Google LLC All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
|
@ -1,6 +1,6 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
* Copyright Google LLC All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
|
@ -1,6 +1,6 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
* Copyright Google LLC All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
@ -44,4 +44,4 @@ function createPackage(tutorialName) {
|
||||
}
|
||||
|
||||
|
||||
module.exports = { createPackage };
|
||||
module.exports = { createPackage };
|
||||
|
@ -1,6 +1,6 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
* Copyright Google LLC All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
@ -43,4 +43,4 @@ function createPackage(guideName) {
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = { createPackage };
|
||||
module.exports = { createPackage };
|
||||
|
@ -1,6 +1,6 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
* Copyright Google LLC All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
|
@ -1,6 +1,6 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
* Copyright Google LLC All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
@ -39,4 +39,4 @@ function createPackage() {
|
||||
}
|
||||
|
||||
|
||||
module.exports = { createPackage };
|
||||
module.exports = { createPackage };
|
||||
|
@ -1,6 +1,6 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
* Copyright Google LLC All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
@ -44,4 +44,4 @@ function createPackage(tutorialName) {
|
||||
}
|
||||
|
||||
|
||||
module.exports = { createPackage };
|
||||
module.exports = { createPackage };
|
||||
|
@ -1,6 +1,6 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
* Copyright Google LLC All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
|
143
aio/yarn.lock
@ -1429,11 +1429,6 @@
|
||||
resolved "https://registry.yarnpkg.com/@types/lunr/-/lunr-2.3.2.tgz#d4a51703315ed0e53c43257216f1014ce6491562"
|
||||
integrity sha512-zcUZYquYDUEegRRPQtkZ068U9CoIjW6pJMYCVDRK25r76FEWvMm1oHqZQUfQh4ayIZ42lipXOpXEiAtGXc1XUg==
|
||||
|
||||
"@types/mime-types@^2.1.0":
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@types/mime-types/-/mime-types-2.1.0.tgz#9ca52cda363f699c69466c2a6ccdaad913ea7a73"
|
||||
integrity sha1-nKUs2jY/aZxpRmwqbM2q2RPqenM=
|
||||
|
||||
"@types/minimatch@*":
|
||||
version "3.0.3"
|
||||
resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.3.tgz#3dca0e3f33b200fc7d1139c0cd96c1268cadfd9d"
|
||||
@ -1495,6 +1490,13 @@
|
||||
resolved "https://registry.yarnpkg.com/@types/xregexp/-/xregexp-3.0.30.tgz#333d550467dd27ef989f375629f8f279a97cee39"
|
||||
integrity sha512-u1dpabg81Rd660bYebOqMXO0+E63H1hxunPAWGebNb7TpxqZYe9YaVLgkkj6ZnzLs3yLumtVB956o8u8OZdhXw==
|
||||
|
||||
"@types/yauzl@^2.9.1":
|
||||
version "2.9.1"
|
||||
resolved "https://registry.yarnpkg.com/@types/yauzl/-/yauzl-2.9.1.tgz#d10f69f9f522eef3cf98e30afb684a1e1ec923af"
|
||||
integrity sha512-A1b8SU4D10uoPjwb0lnHmmu8wZhR9d+9o2PKBQT2jU5YPTKsxac6M2qGAdY7VcL+dHHhARVUDmeg0rOrcd9EjA==
|
||||
dependencies:
|
||||
"@types/node" "*"
|
||||
|
||||
"@webassemblyjs/ast@1.9.0":
|
||||
version "1.9.0"
|
||||
resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.9.0.tgz#bd850604b4042459a5a41cd7d338cbed695ed964"
|
||||
@ -2424,6 +2426,15 @@ bl@^3.0.0:
|
||||
dependencies:
|
||||
readable-stream "^3.0.1"
|
||||
|
||||
bl@^4.0.1:
|
||||
version "4.0.2"
|
||||
resolved "https://registry.yarnpkg.com/bl/-/bl-4.0.2.tgz#52b71e9088515d0606d9dd9cc7aa48dc1f98e73a"
|
||||
integrity sha512-j4OH8f6Qg2bGuWfRiltT2HYGx0e1QcBTrK9KAHNMwMZdQnDZFk0ZSYIpADjYCB3U12nicC5tVJwSIhwOWjb4RQ==
|
||||
dependencies:
|
||||
buffer "^5.5.0"
|
||||
inherits "^2.0.4"
|
||||
readable-stream "^3.4.0"
|
||||
|
||||
blob@0.0.5:
|
||||
version "0.0.5"
|
||||
resolved "https://registry.yarnpkg.com/blob/-/blob-0.0.5.tgz#d680eeef25f8cd91ad533f5b01eed48e64caf683"
|
||||
@ -2641,7 +2652,7 @@ buffer-alloc@^1.2.0:
|
||||
buffer-alloc-unsafe "^1.1.0"
|
||||
buffer-fill "^1.0.0"
|
||||
|
||||
buffer-crc32@^0.2.1, buffer-crc32@^0.2.13:
|
||||
buffer-crc32@^0.2.1, buffer-crc32@^0.2.13, buffer-crc32@~0.2.3:
|
||||
version "0.2.13"
|
||||
resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242"
|
||||
integrity sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI=
|
||||
@ -2693,6 +2704,14 @@ buffer@^5.1.0:
|
||||
base64-js "^1.0.2"
|
||||
ieee754 "^1.1.4"
|
||||
|
||||
buffer@^5.2.1, buffer@^5.5.0:
|
||||
version "5.6.0"
|
||||
resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.6.0.tgz#a31749dc7d81d84db08abf937b6b8c4033f62786"
|
||||
integrity sha512-/gDYp/UtU0eA1ys8bOs9J6a+E/KWIY+DZ+Q2WESNUA0jFRsJOc0SNUO6xJ5SGA1xueg3NL65W6s+NY5l9cunuw==
|
||||
dependencies:
|
||||
base64-js "^1.0.2"
|
||||
ieee754 "^1.1.4"
|
||||
|
||||
buffers@~0.1.1:
|
||||
version "0.1.1"
|
||||
resolved "https://registry.yarnpkg.com/buffers/-/buffers-0.1.1.tgz#b24579c3bed4d6d396aeee6d9a8ae7f5482ab7bb"
|
||||
@ -3486,7 +3505,7 @@ concat-map@0.0.1:
|
||||
resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
|
||||
integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=
|
||||
|
||||
concat-stream@1.6.2, concat-stream@^1.4.7, concat-stream@^1.5.0, concat-stream@^1.5.2:
|
||||
concat-stream@^1.4.7, concat-stream@^1.5.0, concat-stream@^1.5.2:
|
||||
version "1.6.2"
|
||||
resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.2.tgz#904bdf194cd3122fc675c77fc4ac3d4ff0fd1a34"
|
||||
integrity sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==
|
||||
@ -5100,15 +5119,16 @@ extract-opts@^3.3.1:
|
||||
editions "^2.2.0"
|
||||
typechecker "^4.9.0"
|
||||
|
||||
extract-zip@^1.6.6:
|
||||
version "1.6.7"
|
||||
resolved "https://registry.yarnpkg.com/extract-zip/-/extract-zip-1.6.7.tgz#a840b4b8af6403264c8db57f4f1a74333ef81fe9"
|
||||
integrity sha1-qEC0uK9kAyZMjbV/Txp0Mz74H+k=
|
||||
extract-zip@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/extract-zip/-/extract-zip-2.0.0.tgz#f53b71d44f4ff5a4527a2259ade000fb8b303492"
|
||||
integrity sha512-i42GQ498yibjdvIhivUsRslx608whtGoFIhF26Z7O4MYncBxp8CwalOs1lnHy21A9sIohWO2+uiE4SRtC9JXDg==
|
||||
dependencies:
|
||||
concat-stream "1.6.2"
|
||||
debug "2.6.9"
|
||||
mkdirp "0.5.1"
|
||||
yauzl "2.4.1"
|
||||
debug "^4.1.1"
|
||||
get-stream "^5.1.0"
|
||||
yauzl "^2.10.0"
|
||||
optionalDependencies:
|
||||
"@types/yauzl" "^2.9.1"
|
||||
|
||||
extsprintf@1.3.0:
|
||||
version "1.3.0"
|
||||
@ -5171,10 +5191,10 @@ faye-websocket@~0.11.1:
|
||||
dependencies:
|
||||
websocket-driver ">=0.5.1"
|
||||
|
||||
fd-slicer@~1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/fd-slicer/-/fd-slicer-1.0.1.tgz#8b5bcbd9ec327c5041bf9ab023fd6750f1177e65"
|
||||
integrity sha1-i1vL2ewyfFBBv5qwI/1nUPEXfmU=
|
||||
fd-slicer@~1.1.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/fd-slicer/-/fd-slicer-1.1.0.tgz#25c7c89cb1f9077f8891bbe61d8f390eae256f1e"
|
||||
integrity sha1-JcfInLH5B3+IkbvmHY85Dq4lbx4=
|
||||
dependencies:
|
||||
pend "~1.2.0"
|
||||
|
||||
@ -5656,6 +5676,13 @@ get-stream@^4.0.0, get-stream@^4.1.0:
|
||||
dependencies:
|
||||
pump "^3.0.0"
|
||||
|
||||
get-stream@^5.1.0:
|
||||
version "5.1.0"
|
||||
resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-5.1.0.tgz#01203cdc92597f9b909067c3e656cc1f4d3c4dc9"
|
||||
integrity sha512-EXr1FOzrzTfGeL0gQdeFEvOMm2mzMOglyiOXSTpPC+iAjAKftbr3jpCMWynogwYnM+eSj9sHGc6wjIcDvYiygw==
|
||||
dependencies:
|
||||
pump "^3.0.0"
|
||||
|
||||
get-value@^2.0.3, get-value@^2.0.6:
|
||||
version "2.0.6"
|
||||
resolved "https://registry.yarnpkg.com/get-value/-/get-value-2.0.6.tgz#dc15ca1c672387ca76bd37ac0a395ba2042a2c28"
|
||||
@ -6490,7 +6517,7 @@ inflight@^1.0.4:
|
||||
once "^1.3.0"
|
||||
wrappy "1"
|
||||
|
||||
inherits@2, inherits@2.0.4, inherits@^2.0.0, inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.0, inherits@~2.0.1, inherits@~2.0.3:
|
||||
inherits@2, inherits@2.0.4, inherits@^2.0.0, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.0, inherits@~2.0.1, inherits@~2.0.3:
|
||||
version "2.0.4"
|
||||
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
|
||||
integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
|
||||
@ -8456,7 +8483,7 @@ mime-db@1.43.0, "mime-db@>= 1.43.0 < 2":
|
||||
resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.43.0.tgz#0a12e0502650e473d735535050e7c8f4eb4fae58"
|
||||
integrity sha512-+5dsGEEovYbT8UY9yD7eE4XTc4UwJ1jBYlgaQQF38ENsKR3wj/8q8RFZrF9WIZpB2V1ArTVFUva8sAul1NzRzQ==
|
||||
|
||||
mime-types@^2.1.12, mime-types@^2.1.16, mime-types@^2.1.25, mime-types@~2.1.17, mime-types@~2.1.19, mime-types@~2.1.24:
|
||||
mime-types@^2.1.12, mime-types@^2.1.16, mime-types@~2.1.17, mime-types@~2.1.19, mime-types@~2.1.24:
|
||||
version "2.1.26"
|
||||
resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.26.tgz#9c921fc09b7e149a65dfdc0da4d20997200b0a06"
|
||||
integrity sha512-01paPWYgLrkqAyrlDorC1uDwl2p3qZT7yl806vW7DvDoxwXi46jsjFbg+WdwotBIk6/MbEhO/dh5aZ5sNj/dWQ==
|
||||
@ -8605,6 +8632,11 @@ mixin-deep@^1.2.0:
|
||||
for-in "^1.0.2"
|
||||
is-extendable "^1.0.1"
|
||||
|
||||
mkdirp-classic@^0.5.2:
|
||||
version "0.5.3"
|
||||
resolved "https://registry.yarnpkg.com/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz#fa10c9115cc6d8865be221ba47ee9bed78601113"
|
||||
integrity sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==
|
||||
|
||||
mkdirp@0.5.1, "mkdirp@>=0.5 0", mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@~0.5.1, mkdirp@~0.5.x:
|
||||
version "0.5.1"
|
||||
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903"
|
||||
@ -10311,21 +10343,21 @@ punycode@^2.1.0, punycode@^2.1.1:
|
||||
resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec"
|
||||
integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==
|
||||
|
||||
puppeteer@2.1.1:
|
||||
version "2.1.1"
|
||||
resolved "https://registry.yarnpkg.com/puppeteer/-/puppeteer-2.1.1.tgz#ccde47c2a688f131883b50f2d697bd25189da27e"
|
||||
integrity sha512-LWzaDVQkk1EPiuYeTOj+CZRIjda4k2s5w4MK4xoH2+kgWV/SDlkYHmxatDdtYrciHUKSXTsGgPgPP8ILVdBsxg==
|
||||
puppeteer@3.3.0:
|
||||
version "3.3.0"
|
||||
resolved "https://registry.yarnpkg.com/puppeteer/-/puppeteer-3.3.0.tgz#95839af9fdc0aa4de7e5ee073a4c0adeb9e2d3d7"
|
||||
integrity sha512-23zNqRltZ1PPoK28uRefWJ/zKb5Jhnzbbwbpcna2o5+QMn17F0khq5s1bdH3vPlyj+J36pubccR8wiNA/VE0Vw==
|
||||
dependencies:
|
||||
"@types/mime-types" "^2.1.0"
|
||||
debug "^4.1.0"
|
||||
extract-zip "^1.6.6"
|
||||
extract-zip "^2.0.0"
|
||||
https-proxy-agent "^4.0.0"
|
||||
mime "^2.0.3"
|
||||
mime-types "^2.1.25"
|
||||
progress "^2.0.1"
|
||||
proxy-from-env "^1.0.0"
|
||||
rimraf "^2.6.1"
|
||||
ws "^6.1.0"
|
||||
rimraf "^3.0.2"
|
||||
tar-fs "^2.0.0"
|
||||
unbzip2-stream "^1.3.3"
|
||||
ws "^7.2.3"
|
||||
|
||||
q@1.4.1:
|
||||
version "1.4.1"
|
||||
@ -11058,7 +11090,7 @@ rimraf@2, rimraf@^2.2.8, rimraf@^2.5.2, rimraf@^2.5.4, rimraf@^2.6.0, rimraf@^2.
|
||||
dependencies:
|
||||
glob "^7.1.3"
|
||||
|
||||
rimraf@3.0.2:
|
||||
rimraf@3.0.2, rimraf@^3.0.2:
|
||||
version "3.0.2"
|
||||
resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a"
|
||||
integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==
|
||||
@ -12289,6 +12321,16 @@ tapable@^1.0.0, tapable@^1.1.3:
|
||||
resolved "https://registry.yarnpkg.com/tapable/-/tapable-1.1.3.tgz#a1fccc06b58db61fd7a45da2da44f5f3a3e67ba2"
|
||||
integrity sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA==
|
||||
|
||||
tar-fs@^2.0.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/tar-fs/-/tar-fs-2.1.0.tgz#d1cdd121ab465ee0eb9ccde2d35049d3f3daf0d5"
|
||||
integrity sha512-9uW5iDvrIMCVpvasdFHW0wJPez0K4JnMZtsuIeDI7HyMGJNxmDZDOCQROr7lXyS+iL/QMpj07qcjGYTSdRFXUg==
|
||||
dependencies:
|
||||
chownr "^1.1.1"
|
||||
mkdirp-classic "^0.5.2"
|
||||
pump "^3.0.0"
|
||||
tar-stream "^2.0.0"
|
||||
|
||||
tar-stream@^1.5.0:
|
||||
version "1.6.2"
|
||||
resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-1.6.2.tgz#8ea55dab37972253d9a9af90fdcd559ae435c555"
|
||||
@ -12302,6 +12344,17 @@ tar-stream@^1.5.0:
|
||||
to-buffer "^1.1.1"
|
||||
xtend "^4.0.0"
|
||||
|
||||
tar-stream@^2.0.0:
|
||||
version "2.1.2"
|
||||
resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-2.1.2.tgz#6d5ef1a7e5783a95ff70b69b97455a5968dc1325"
|
||||
integrity sha512-UaF6FoJ32WqALZGOIAApXx+OdxhekNMChu6axLJR85zMMjXKWFGjbIRe+J6P4UnRGg9rAwWvbTT0oI7hD/Un7Q==
|
||||
dependencies:
|
||||
bl "^4.0.1"
|
||||
end-of-stream "^1.4.1"
|
||||
fs-constants "^1.0.0"
|
||||
inherits "^2.0.3"
|
||||
readable-stream "^3.1.1"
|
||||
|
||||
tar-stream@^2.1.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-2.1.0.tgz#d1aaa3661f05b38b5acc9b7020efdca5179a2cc3"
|
||||
@ -12463,7 +12516,7 @@ through2@^3.0.1:
|
||||
dependencies:
|
||||
readable-stream "2 || 3"
|
||||
|
||||
"through@>=2.2.7 <3", through@X.X.X, through@^2.3.6:
|
||||
"through@>=2.2.7 <3", through@X.X.X, through@^2.3.6, through@^2.3.8:
|
||||
version "2.3.8"
|
||||
resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5"
|
||||
integrity sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=
|
||||
@ -12788,6 +12841,14 @@ unbounded@^1.2.0:
|
||||
dependencies:
|
||||
editions "^2.2.0"
|
||||
|
||||
unbzip2-stream@^1.3.3:
|
||||
version "1.4.3"
|
||||
resolved "https://registry.yarnpkg.com/unbzip2-stream/-/unbzip2-stream-1.4.3.tgz#b0da04c4371311df771cdc215e87f2130991ace7"
|
||||
integrity sha512-mlExGW4w71ebDJviH16lQLtZS32VKqsSfk80GCfUlwT/4/hNRFsoscrF/c++9xinkMzECL1uL9DDwXqFWkruPg==
|
||||
dependencies:
|
||||
buffer "^5.2.1"
|
||||
through "^2.3.8"
|
||||
|
||||
underscore@^1.9.1:
|
||||
version "1.9.2"
|
||||
resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.9.2.tgz#0c8d6f536d6f378a5af264a72f7bec50feb7cf2f"
|
||||
@ -13644,7 +13705,7 @@ ws@3.3.2:
|
||||
safe-buffer "~5.1.0"
|
||||
ultron "~1.1.0"
|
||||
|
||||
ws@^6.1.0, ws@^6.2.1:
|
||||
ws@^6.2.1:
|
||||
version "6.2.1"
|
||||
resolved "https://registry.yarnpkg.com/ws/-/ws-6.2.1.tgz#442fdf0a47ed64f59b6a5d8ff130f4748ed524fb"
|
||||
integrity sha512-GIyAXC2cB7LjvpgMt9EKS2ldqr0MTrORaleiOno6TweZ6r3TKtoFQWay/2PceJ3RuBasOHzXNn5Lrw1X0bEjqA==
|
||||
@ -13656,6 +13717,11 @@ ws@^7.1.0:
|
||||
resolved "https://registry.yarnpkg.com/ws/-/ws-7.2.1.tgz#03ed52423cd744084b2cf42ed197c8b65a936b8e"
|
||||
integrity sha512-sucePNSafamSKoOqoNfBd8V0StlkzJKL2ZAhGQinCfNQ+oacw+Pk7lcdAElecBF2VkLNZRiIb5Oi1Q5lVUVt2A==
|
||||
|
||||
ws@^7.2.3:
|
||||
version "7.3.0"
|
||||
resolved "https://registry.yarnpkg.com/ws/-/ws-7.3.0.tgz#4b2f7f219b3d3737bc1a2fbf145d825b94d38ffd"
|
||||
integrity sha512-iFtXzngZVXPGgpTlP1rBqsUK82p9tKqsWRPg5L56egiljujJT3vGAYnHANvFxBieXrTFavhzhxW52jnaWV+w2w==
|
||||
|
||||
ws@~3.3.1:
|
||||
version "3.3.3"
|
||||
resolved "https://registry.yarnpkg.com/ws/-/ws-3.3.3.tgz#f1cf84fe2d5e901ebce94efaece785f187a228f2"
|
||||
@ -13871,12 +13937,13 @@ yargs@^7.0.2:
|
||||
y18n "^3.2.1"
|
||||
yargs-parser "^5.0.0"
|
||||
|
||||
yauzl@2.4.1:
|
||||
version "2.4.1"
|
||||
resolved "https://registry.yarnpkg.com/yauzl/-/yauzl-2.4.1.tgz#9528f442dab1b2284e58b4379bb194e22e0c4005"
|
||||
integrity sha1-lSj0QtqxsihOWLQ3m7GU4i4MQAU=
|
||||
yauzl@^2.10.0:
|
||||
version "2.10.0"
|
||||
resolved "https://registry.yarnpkg.com/yauzl/-/yauzl-2.10.0.tgz#c7eb17c93e112cb1086fa6d8e51fb0667b79a5f9"
|
||||
integrity sha1-x+sXyT4RLLEIb6bY5R+wZnt5pfk=
|
||||
dependencies:
|
||||
fd-slicer "~1.0.1"
|
||||
buffer-crc32 "~0.2.3"
|
||||
fd-slicer "~1.1.0"
|
||||
|
||||
yeast@0.1.2:
|
||||
version "0.1.2"
|
||||
|
@ -1,6 +1,6 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
* Copyright Google LLC All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
@ -31,14 +31,23 @@ var CIconfiguration = {
|
||||
'Android7': {unitTest: {target: 'SL', required: true}, e2e: {target: null, required: true}},
|
||||
'Android8': {unitTest: {target: 'SL', required: true}, e2e: {target: null, required: true}},
|
||||
'Android9': {unitTest: {target: 'SL', required: true}, e2e: {target: null, required: true}},
|
||||
'Android10': {unitTest: {target: 'SL', required: true}, e2e: {target: null, required: true}},
|
||||
// Disable Android 10 tests due to infrastructure failure.
|
||||
// ex:
|
||||
// Chrome Mobile 74.0.3729 (Android 0.0.0) ERROR:
|
||||
// Error: XHR error loading
|
||||
// http://angular-ci.local:9876/base/node_modules/rxjs/internal/operators/zip.js
|
||||
//
|
||||
// Error loading http://angular-ci.local:9876/base/node_modules/rxjs/internal/operators/zip.js as
|
||||
// "../internal/operators/zip" from
|
||||
// http://angular-ci.local:9876/base/node_modules/rxjs/operators/index.js
|
||||
'Android10': {unitTest: {target: 'SL', required: false}, e2e: {target: null, required: true}},
|
||||
// Disable all Safari and iOS tests because of incorrect results
|
||||
// ex:
|
||||
// Mobile Safari 13.0.0 (iOS 13.0.0) styling static template only should capture static values in TStylingKey FAILED
|
||||
// Expected $.content = 'dynamic' to equal '"dynamic"'.
|
||||
// Mobile Safari 12.0.0 (iOS 12.0.0) styling should handle values wrapped into SafeValue FAILED
|
||||
// Expected 'url("http://angular-ci.local:9876/1.png")' to contain 'url("/1.png")'.s
|
||||
// Tracking in: https://github.com/angular/angular/issues/36975
|
||||
// Mobile Safari 13.0.0 (iOS 13.0.0) styling static template only should capture static values in
|
||||
// TStylingKey FAILED Expected $.content = 'dynamic' to equal '"dynamic"'. Mobile Safari 12.0.0
|
||||
// (iOS 12.0.0) styling should handle values wrapped into SafeValue FAILED Expected
|
||||
// 'url("http://angular-ci.local:9876/1.png")' to contain 'url("/1.png")'.s Tracking in:
|
||||
// https://github.com/angular/angular/issues/36975
|
||||
'Safari12': {unitTest: {target: 'SL', required: false}, e2e: {target: null, required: true}},
|
||||
'Safari13': {unitTest: {target: 'SL', required: false}, e2e: {target: null, required: true}},
|
||||
'iOS12': {unitTest: {target: 'SL', required: false}, e2e: {target: null, required: true}},
|
||||
@ -152,17 +161,12 @@ var sauceAliases = {
|
||||
return customLaunchers[item].base == 'SauceLabs';
|
||||
}),
|
||||
'DESKTOP': [
|
||||
'SL_CHROME', 'SL_FIREFOX', 'SL_IE9', 'SL_IE10', 'SL_IE11', 'SL_EDGE', 'SL_SAFARI12', 'SL_SAFARI13', 'SL_FIREFOXESR'
|
||||
],
|
||||
'MOBILE': [
|
||||
'SL_ANDROID7', 'SL_ANDROID8', 'SL_ANDROID9', 'SL_ANDROID10', 'SL_IOS12', 'SL_IOS13'
|
||||
],
|
||||
'ANDROID': [
|
||||
'SL_ANDROID7', 'SL_ANDROID8', 'SL_ANDROID9', 'SL_ANDROID10'
|
||||
],
|
||||
'FIREFOX': [
|
||||
'SL_FIREFOXESR'
|
||||
'SL_CHROME', 'SL_FIREFOX', 'SL_IE9', 'SL_IE10', 'SL_IE11', 'SL_EDGE', 'SL_SAFARI12',
|
||||
'SL_SAFARI13', 'SL_FIREFOXESR'
|
||||
],
|
||||
'MOBILE': ['SL_ANDROID7', 'SL_ANDROID8', 'SL_ANDROID9', 'SL_ANDROID10', 'SL_IOS12', 'SL_IOS13'],
|
||||
'ANDROID': ['SL_ANDROID7', 'SL_ANDROID8', 'SL_ANDROID9', 'SL_ANDROID10'],
|
||||
'FIREFOX': ['SL_FIREFOXESR'],
|
||||
'IE': ['SL_IE9', 'SL_IE10', 'SL_IE11'],
|
||||
'IOS': ['SL_IOS12', 'SL_IOS13'],
|
||||
'SAFARI': ['SL_SAFARI12', 'SL_SAFARI13'],
|
||||
@ -177,11 +181,14 @@ var browserstackAliases = {
|
||||
return customLaunchers[item].base == 'BrowserStack';
|
||||
}),
|
||||
'DESKTOP': [
|
||||
'BS_CHROME', 'BS_FIREFOX', 'BS_IE9', 'BS_IE10', 'BS_IE11', 'BS_EDGE',
|
||||
],
|
||||
'MOBILE': [
|
||||
'BS_ANDROID7', 'BS_WINDOWSPHONE'
|
||||
'BS_CHROME',
|
||||
'BS_FIREFOX',
|
||||
'BS_IE9',
|
||||
'BS_IE10',
|
||||
'BS_IE11',
|
||||
'BS_EDGE',
|
||||
],
|
||||
'MOBILE': ['BS_ANDROID7', 'BS_WINDOWSPHONE'],
|
||||
'ANDROID': ['BS_ANDROID7'],
|
||||
'IE': ['BS_IE9', 'BS_IE10', 'BS_IE11'],
|
||||
'IOS': [],
|
||||
|
@ -37,10 +37,30 @@ genrule(
|
||||
|
||||
pkg_npm(
|
||||
name = "npm_package",
|
||||
srcs = [
|
||||
"BUILD.bazel",
|
||||
"//dev-infra/benchmark:files",
|
||||
],
|
||||
substitutions = {
|
||||
# angular/angular should not consume it's own packages, so we use
|
||||
# substitutions to replace these in the published version of dev-infra.
|
||||
"//dev-infra/": "@npm_angular_dev_infra_private//",
|
||||
"//packages/benchpress": "@npm//@angular/benchpress",
|
||||
"//packages/bazel/": "@npm_angular_bazel//",
|
||||
"//packages/zone.js/dist:zone.js": "@npm//:node_modules/zone.js/dist/zone.js",
|
||||
"//packages/core": "@npm//@angular/core",
|
||||
"//packages/platform-browser": "@npm//@angular/platform-browser",
|
||||
|
||||
# This substitution is particularly verbose because we need to make sure
|
||||
# that only things available via Angular Bazel are imported from
|
||||
# tools/defaults.bzl.
|
||||
"load\(\"//tools:defaults.bzl\", \"ng_module\"\)": "load(\"@npm_angular_bazel//:index.bzl\", \"ng_module\")",
|
||||
},
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
":cli",
|
||||
":package-json",
|
||||
"//dev-infra/benchmark/driver-utilities",
|
||||
"//dev-infra/commit-message",
|
||||
"//dev-infra/ts-circular-dependencies",
|
||||
],
|
||||
|
12
dev-infra/benchmark/BUILD.bazel
Normal file
@ -0,0 +1,12 @@
|
||||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
# Make source files available for distribution via pkg_npm
|
||||
filegroup(
|
||||
name = "files",
|
||||
srcs = glob(["*"]) + [
|
||||
"//dev-infra/benchmark/brotli-cli:files",
|
||||
"//dev-infra/browsers:files",
|
||||
"//dev-infra/benchmark/component_benchmark:files",
|
||||
"//dev-infra/benchmark/ng_rollup_bundle:files",
|
||||
],
|
||||
)
|
19
dev-infra/benchmark/brotli-cli/BUILD.bazel
Normal file
@ -0,0 +1,19 @@
|
||||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
load("@build_bazel_rules_nodejs//:index.bzl", "nodejs_binary")
|
||||
|
||||
nodejs_binary(
|
||||
name = "brotli-cli",
|
||||
data = [
|
||||
"cli.js",
|
||||
"@npm//brotli",
|
||||
],
|
||||
entry_point = ":cli.js",
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
||||
|
||||
# Make source files available for distribution via pkg_npm
|
||||
filegroup(
|
||||
name = "files",
|
||||
srcs = glob(["*"]),
|
||||
)
|
@ -1,6 +1,6 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
* Copyright Google LLC All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
@ -18,4 +18,4 @@ function main(args) {
|
||||
|
||||
if (require.main === module) {
|
||||
main(process.argv.slice(2));
|
||||
}
|
||||
}
|
12
dev-infra/benchmark/component_benchmark/BUILD.bazel
Normal file
@ -0,0 +1,12 @@
|
||||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
exports_files([
|
||||
"protractor-perf.conf.js",
|
||||
"start-server.js",
|
||||
])
|
||||
|
||||
# Make source files available for distribution via pkg_npm
|
||||
filegroup(
|
||||
name = "files",
|
||||
srcs = glob(["*"]) + ["//dev-infra/benchmark/component_benchmark/defaults:files"],
|
||||
)
|
@ -1,4 +1,4 @@
|
||||
load("//tools:defaults.bzl", "protractor_web_test_suite")
|
||||
load("@npm_bazel_protractor//:index.bzl", "protractor_web_test_suite")
|
||||
|
||||
"""
|
||||
Macro that can be used to define a benchmark test. This differentiates from
|
||||
@ -10,11 +10,9 @@ load("//tools:defaults.bzl", "protractor_web_test_suite")
|
||||
def benchmark_test(name, server, tags = [], **kwargs):
|
||||
protractor_web_test_suite(
|
||||
name = name,
|
||||
configuration = "//:protractor-perf.conf.js",
|
||||
data = [
|
||||
"//packages/benchpress",
|
||||
],
|
||||
on_prepare = "//modules/benchmarks:start-server.js",
|
||||
browsers = ["//dev-infra/browsers:chromium"],
|
||||
configuration = "//dev-infra/benchmark/component_benchmark:protractor-perf.conf.js",
|
||||
on_prepare = "//dev-infra/benchmark/component_benchmark:start-server.js",
|
||||
server = server,
|
||||
# Benchmark targets should not run on CI by default.
|
||||
tags = tags + [
|
@ -1,17 +1,19 @@
|
||||
load("//tools:defaults.bzl", "ng_module", "ng_rollup_bundle", "ts_devserver", "ts_library")
|
||||
load("//modules/benchmarks:benchmark_test.bzl", "benchmark_test")
|
||||
load("//dev-infra/benchmark/ng_rollup_bundle:ng_rollup_bundle.bzl", "ng_rollup_bundle")
|
||||
load("//tools:defaults.bzl", "ng_module")
|
||||
load("@npm_bazel_typescript//:index.bzl", "ts_devserver", "ts_library")
|
||||
load(":benchmark_test.bzl", "benchmark_test")
|
||||
|
||||
def copy_default_file(origin, destination):
|
||||
"""
|
||||
Copies a file from tools/components/defaults to the destination.
|
||||
Copies a file from ./defaults to the destination.
|
||||
|
||||
Args:
|
||||
origin: The name of a file in benchpress/defaults to be copied.
|
||||
origin: The name of a file in ./defaults to be copied.
|
||||
destination: Where the original file will be clopied to.
|
||||
"""
|
||||
native.genrule(
|
||||
name = "copy_default_" + origin + "_file_genrule",
|
||||
srcs = ["//tools/components/defaults:" + origin],
|
||||
srcs = ["//dev-infra/benchmark/component_benchmark/defaults:" + origin],
|
||||
outs = [destination],
|
||||
cmd = "cat $(SRCS) >> $@",
|
||||
)
|
||||
@ -105,6 +107,7 @@ def component_benchmark(
|
||||
# Creates ngFactory and ngSummary to be imported by the app's entry point.
|
||||
generate_ve_shims = True,
|
||||
deps = ng_deps,
|
||||
tsconfig = "//dev-infra/benchmark/component_benchmark:tsconfig-e2e.json",
|
||||
)
|
||||
|
||||
# Bundle the application (needed by ts_devserver).
|
||||
@ -117,7 +120,7 @@ def component_benchmark(
|
||||
# The ts_library for the driver that runs tests against the benchmark app.
|
||||
ts_library(
|
||||
name = benchmark_driver,
|
||||
tsconfig = "//modules/benchmarks:tsconfig-e2e.json",
|
||||
tsconfig = "//dev-infra/benchmark/component_benchmark:tsconfig-e2e.json",
|
||||
testonly = True,
|
||||
srcs = [driver],
|
||||
deps = driver_deps,
|
||||
@ -130,7 +133,8 @@ def component_benchmark(
|
||||
port = 4200,
|
||||
static_files = assets + styles,
|
||||
deps = [":" + app_main + ".min_debug.es2015.js"],
|
||||
additional_root_paths = ["tools/components/defaults"],
|
||||
additional_root_paths = ["//dev-infra/benchmark/component_benchmark/defaults"],
|
||||
serving_path = "/app_bundle.js",
|
||||
)
|
||||
|
||||
# Runs a protractor test that's set up to use @angular/benchpress.
|
@ -1,5 +1,11 @@
|
||||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
# Make source files available for distribution via pkg_npm
|
||||
filegroup(
|
||||
name = "files",
|
||||
srcs = glob(["*"]),
|
||||
)
|
||||
|
||||
exports_files([
|
||||
"index.html",
|
||||
"index.ts",
|
@ -1,6 +1,6 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
* Copyright Google LLC All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
@ -1,6 +1,6 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
* Copyright Google LLC All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
@ -36,7 +36,7 @@ exports.config = {
|
||||
showColors: true,
|
||||
defaultTimeoutInterval: 90000,
|
||||
print: function(msg) {
|
||||
console.log(msg);
|
||||
console.info(msg);
|
||||
},
|
||||
},
|
||||
useAllAngular2AppRoots: true
|
@ -1,6 +1,6 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
* Copyright Google LLC All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
@ -0,0 +1,6 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"lib": ["es2015", "dom"],
|
||||
"types": ["node", "jasmine"]
|
||||
}
|
||||
}
|
@ -1,15 +1,16 @@
|
||||
load("//tools:defaults.bzl", "ts_library")
|
||||
|
||||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
load("@npm_bazel_typescript//:index.bzl", "ts_library")
|
||||
|
||||
ts_library(
|
||||
name = "e2e_util",
|
||||
testonly = 1,
|
||||
name = "driver-utilities",
|
||||
srcs = glob(["*.ts"]),
|
||||
module_name = "@angular/dev-infra-private/benchmark/driver-utilities",
|
||||
tsconfig = "//dev-infra/benchmark/component_benchmark:tsconfig-e2e.json",
|
||||
deps = [
|
||||
"//packages:types",
|
||||
"//packages/benchpress",
|
||||
"@npm//@types/fs-extra",
|
||||
"@npm//@types/node",
|
||||
"@npm//@types/selenium-webdriver",
|
||||
"@npm//fs-extra",
|
||||
"@npm//node-uuid",
|
@ -1,6 +1,6 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
* Copyright Google LLC All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
9
dev-infra/benchmark/driver-utilities/index.ts
Normal file
@ -0,0 +1,9 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google LLC All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
export {openBrowser, verifyNoBrowserErrors} from './e2e_util';
|
||||
export {runBenchmark} from './perf_util';
|
@ -1,6 +1,6 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
* Copyright Google LLC All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
@ -22,7 +22,7 @@ const globalOptions = {
|
||||
|
||||
const runner = createBenchpressRunner();
|
||||
|
||||
export function runBenchmark(config: {
|
||||
export async function runBenchmark(config: {
|
||||
id: string,
|
||||
url: string,
|
||||
params: {name: string, value: any}[],
|
||||
@ -34,7 +34,7 @@ export function runBenchmark(config: {
|
||||
}): Promise<any> {
|
||||
openBrowser(config);
|
||||
if (config.setup) {
|
||||
config.setup();
|
||||
await config.setup();
|
||||
}
|
||||
const description: {[key: string]: any} = {};
|
||||
config.params.forEach((param) => description[param.name] = param.value);
|
@ -18,3 +18,9 @@ nodejs_binary(
|
||||
],
|
||||
entry_point = "@npm//:node_modules/rollup/dist/bin/rollup",
|
||||
)
|
||||
|
||||
# Make source files available for distribution via pkg_npm
|
||||
filegroup(
|
||||
name = "files",
|
||||
srcs = glob(["*"]),
|
||||
)
|
@ -1,4 +1,4 @@
|
||||
# Copyright Google Inc. All Rights Reserved.
|
||||
# Copyright Google LLC All Rights Reserved.
|
||||
#
|
||||
# Use of this source code is governed by an MIT-style license that can be
|
||||
# found in the LICENSE file at https://angular.io/license
|
||||
@ -175,10 +175,10 @@ _NG_ROLLUP_BUNDLE_ATTRS = {
|
||||
"_rollup": attr.label(
|
||||
executable = True,
|
||||
cfg = "host",
|
||||
default = Label("//tools/ng_rollup_bundle:rollup_with_build_optimizer"),
|
||||
default = Label("//dev-infra/benchmark/ng_rollup_bundle:rollup_with_build_optimizer"),
|
||||
),
|
||||
"_rollup_config_tmpl": attr.label(
|
||||
default = Label("//tools/ng_rollup_bundle:rollup.config.js"),
|
||||
default = Label("//dev-infra/benchmark/ng_rollup_bundle:rollup.config.js"),
|
||||
allow_single_file = True,
|
||||
),
|
||||
}
|
||||
@ -392,7 +392,7 @@ def ng_rollup_bundle(name, **kwargs):
|
||||
# maintain the comments off behavior. We pass the --comments flag with
|
||||
# a regex that always evaluates to false to do this.
|
||||
"args": ["--comments", "/bogus_string_to_suppress_all_comments^/"],
|
||||
"config_file": "//tools/ng_rollup_bundle:terser_config.json",
|
||||
"config_file": "//dev-infra/benchmark/ng_rollup_bundle:terser_config.json",
|
||||
"sourcemap": False,
|
||||
}
|
||||
|
||||
@ -413,7 +413,7 @@ def ng_rollup_bundle(name, **kwargs):
|
||||
native.filegroup(name = name + ".min_debug.js", srcs = [name + ".min_debug"], visibility = visibility)
|
||||
npm_package_bin(
|
||||
name = "_%s_brotli" % name,
|
||||
tool = "//tools/brotli-cli",
|
||||
tool = "//dev-infra/benchmark/brotli-cli",
|
||||
data = [name + ".min.js"],
|
||||
outs = [name + ".min.js.br"],
|
||||
args = [
|
||||
@ -436,7 +436,7 @@ def ng_rollup_bundle(name, **kwargs):
|
||||
native.filegroup(name = name + ".min_debug.es2015.js", srcs = [name + ".min_debug.es2015"], visibility = visibility)
|
||||
npm_package_bin(
|
||||
name = "_%s_es2015_brotli" % name,
|
||||
tool = "//tools/brotli-cli",
|
||||
tool = "//dev-infra/benchmark/brotli-cli",
|
||||
data = [name + ".min.es2015.js"],
|
||||
outs = [name + ".min.es2015.js.br"],
|
||||
args = [
|
@ -1,10 +1,11 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
* Copyright Google LLC All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
// Rollup configuration
|
||||
// GENERATED BY Bazel
|
||||
|
@ -1,4 +1,4 @@
|
||||
# Copyright 2016 Google Inc.
|
||||
# Copyright Google LLC
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
@ -14,6 +14,8 @@
|
||||
#
|
||||
################################################################################
|
||||
#
|
||||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
load("@io_bazel_rules_webtesting//web:web.bzl", "browser", "web_test_archive")
|
||||
|
||||
# Override of chromium web_test_archive so that the archive is selected based on platform
|
||||
@ -31,7 +33,7 @@ web_test_archive(
|
||||
"@io_bazel_rules_webtesting//common/conditions:mac": {"CHROMIUM": "chrome-mac/Chromium.app/Contents/MacOS/chromium"},
|
||||
"@io_bazel_rules_webtesting//common/conditions:windows": {"CHROMIUM": "chrome-win/chrome.exe"},
|
||||
}),
|
||||
visibility = ["//tools/browsers:__subpackages__"],
|
||||
visibility = ["//dev-infra/browsers:__subpackages__"],
|
||||
)
|
||||
|
||||
# Override of chromedriver web_test_archive so that the archive is selected based on platform
|
||||
@ -55,7 +57,7 @@ web_test_archive(
|
||||
"CHROMEDRIVER": "chromedriver_win32/chromedriver.exe",
|
||||
},
|
||||
}),
|
||||
visibility = ["//tools/browsers:__subpackages__"],
|
||||
visibility = ["//dev-infra/browsers:__subpackages__"],
|
||||
)
|
||||
|
||||
browser(
|
||||
@ -68,3 +70,9 @@ browser(
|
||||
"@io_bazel_rules_webtesting//go/wsl",
|
||||
],
|
||||
)
|
||||
|
||||
# Make source files available for distribution via pkg_npm
|
||||
filegroup(
|
||||
name = "files",
|
||||
srcs = glob(["*"]),
|
||||
)
|
50
dev-infra/browsers/README.md
Normal file
@ -0,0 +1,50 @@
|
||||
# Browser configuration and versioning for testing of Angular
|
||||
|
||||
Within the Angular monorepo, we use Chrome to perform most of the local testing, and rely on Sauce Labs and BrowserStack to do cross-browser testing on our CI.
|
||||
|
||||
The version of Chrome used in tests within this monorepo is configured and controlled via `rules_webtesting` and `puppeteer`. We manually keep the configuration of these two tools in sync to create a consistent testing environment across unit, e2e, and integration tests.
|
||||
|
||||
## rules_webtesting
|
||||
|
||||
Bazel `karma_web_test_suite` and `protractor_web_test_suite` targets will use Chromium provisioned by rules_webtesting. The version of chrome used is specified in the `dev-infra/browsers/browser_repositories.bzl` file. The process of updating the Chrome version in that file consists of several steps:
|
||||
|
||||
1) Visit https://chromium.woolyss.com/ and note the version (commit position) of the latest stable version.
|
||||
|
||||
For example, "Google Chrome 83.0.4103.97 (756066) • Wednesday, 3 Jun 2020". Alternately, you can look in https://omahaproxy.appspot.com/.
|
||||
|
||||
1) Find the closest commit position number available for each platform in chromium-browser-snapshots:
|
||||
https://commondatastorage.googleapis.com/chromium-browser-snapshots/index.html
|
||||
|
||||
For example,
|
||||
|
||||
* https://commondatastorage.googleapis.com/chromium-browser-snapshots/index.html?prefix=Linux_x64/756066/
|
||||
* https://commondatastorage.googleapis.com/chromium-browser-snapshots/index.html?prefix=Mac/756053/
|
||||
* https://commondatastorage.googleapis.com/chromium-browser-snapshots/index.html?prefix=Win/756065/
|
||||
|
||||
You can download the Chromium for your local platform and double check that the --version matches up with
|
||||
what you expect.
|
||||
|
||||
For example,
|
||||
|
||||
``` bash
|
||||
$ ~/Downloads/chrome-mac/Chromium.app/Contents/MacOS/Chromium --version
|
||||
Chromium 83.0.4103.0
|
||||
```
|
||||
|
||||
1) Update the chrome & chrome driver build numbers in `dev-infra/browsers/browser_repositories.bzl` and run either run `bazel query @org_chromium_chromium_amd64//...` to prompt Bazel to calculate the new sha256 for each platform binary or determine the new sha256 values manually.
|
||||
|
||||
For example, with curl & shasum
|
||||
|
||||
``` bash
|
||||
curl https://commondatastorage.googleapis.com/chromium-browser-snapshots/Linux_x64/756066/chrome-linux.zip | shasum -a 256
|
||||
```
|
||||
|
||||
## puppeteer
|
||||
|
||||
Visit https://github.com/puppeteer/puppeteer/blob/master/docs/api.md to determine which version of puppeteer corresponds to the version of Chrome desired. Then update `scripts/puppeteer-chrome-versions.js` and all of the puppeteer versions throughout the repo,
|
||||
|
||||
* `package.json`
|
||||
* `aio/package.json`
|
||||
* `aio/tools/examples/shared/package.json`
|
||||
|
||||
and their corresponding `yarn.lock` files.
|
@ -44,44 +44,53 @@ def platform_http_file(name, licenses, sha256, urls):
|
||||
def browser_repositories():
|
||||
"""Load pinned rules_webtesting browser versions."""
|
||||
|
||||
# To update to a newer version of Chromium see instructions in
|
||||
# https://github.com/angular/angular/blob/master/dev-infra/browsers/README.md.
|
||||
|
||||
platform_http_file(
|
||||
name = "org_chromium_chromium_amd64",
|
||||
licenses = ["notice"], # BSD 3-clause (maybe more?)
|
||||
sha256 = "b1e30c4dec8a451f8fe10d1f2d3c71e491d0333425f32247fe5c80a0a354303d",
|
||||
urls = ["https://commondatastorage.googleapis.com/chromium-browser-snapshots/Linux_x64/664981/chrome-linux.zip"],
|
||||
sha256 = "2cfd74ee58c79d8b7aada05c899a930967e2fd8bb0186582cde02c7340863f64",
|
||||
# 83.0.4103
|
||||
urls = ["https://commondatastorage.googleapis.com/chromium-browser-snapshots/Linux_x64/756066/chrome-linux.zip"],
|
||||
)
|
||||
|
||||
platform_http_file(
|
||||
name = "org_chromium_chromium_macos",
|
||||
licenses = ["notice"], # BSD 3-clause (maybe more?)
|
||||
sha256 = "7c0ba93616f44a421330b1c1262e8899fbdf7916bed8b04c775e0426f6f35ec6",
|
||||
urls = ["https://commondatastorage.googleapis.com/chromium-browser-snapshots/Mac/665002/chrome-mac.zip"],
|
||||
sha256 = "b841ec5ad03b08422d97593fc719f1c5b038703388ad65e6cd8cc8272d58958c",
|
||||
# 83.0.4103
|
||||
urls = ["https://commondatastorage.googleapis.com/chromium-browser-snapshots/Mac/756053/chrome-mac.zip"],
|
||||
)
|
||||
|
||||
platform_http_file(
|
||||
name = "org_chromium_chromium_windows",
|
||||
licenses = ["notice"], # BSD 3-clause (maybe more?)
|
||||
sha256 = "f2facd0066270078d0e8999e684595274c359cac3946299a1ceedba2a5de1c63",
|
||||
urls = ["https://commondatastorage.googleapis.com/chromium-browser-snapshots/Win/664999/chrome-win.zip"],
|
||||
sha256 = "4683d7ac88dfec4b98d1da3012ecc8e42cc8c1a560a7b95589ad4cc96bf90fcb",
|
||||
# 83.0.4103
|
||||
urls = ["https://commondatastorage.googleapis.com/chromium-browser-snapshots/Win/756065/chrome-win.zip"],
|
||||
)
|
||||
|
||||
platform_http_file(
|
||||
name = "org_chromium_chromedriver_amd64",
|
||||
licenses = ["reciprocal"], # BSD 3-clause, ICU, MPL 1.1, libpng (BSD/MIT-like), Academic Free License v. 2.0, BSD 2-clause, MIT
|
||||
sha256 = "0ead02145854b60a3317b59031205b362fb4cfdb680fef20e95c89582e6e38be",
|
||||
urls = ["https://commondatastorage.googleapis.com/chromium-browser-snapshots/Linux_x64/664981/chromedriver_linux64.zip"],
|
||||
sha256 = "95dded16000b82e31445361da7d251ed707e027a4b61e9a3ec5fbd1cc2f62bb1",
|
||||
# 83.0.4103
|
||||
urls = ["https://commondatastorage.googleapis.com/chromium-browser-snapshots/Linux_x64/756066/chromedriver_linux64.zip"],
|
||||
)
|
||||
|
||||
platform_http_file(
|
||||
name = "org_chromium_chromedriver_macos",
|
||||
licenses = ["reciprocal"], # BSD 3-clause, ICU, MPL 1.1, libpng (BSD/MIT-like), Academic Free License v. 2.0, BSD 2-clause, MIT
|
||||
sha256 = "8dd159e27b13b16262afa6993b15321e736c3b484da363c0e03bb050d72522c9",
|
||||
urls = ["https://commondatastorage.googleapis.com/chromium-browser-snapshots/Mac/665002/chromedriver_mac64.zip"],
|
||||
sha256 = "17260e9b2222b0c905a1861285210192baef830f4281778903e7cebb8db683cc",
|
||||
# 83.0.4103
|
||||
urls = ["https://commondatastorage.googleapis.com/chromium-browser-snapshots/Mac/756053/chromedriver_mac64.zip"],
|
||||
)
|
||||
|
||||
platform_http_file(
|
||||
name = "org_chromium_chromedriver_windows",
|
||||
licenses = ["reciprocal"], # BSD 3-clause, ICU, MPL 1.1, libpng (BSD/MIT-like), Academic Free License v. 2.0, BSD 2-clause, MIT
|
||||
sha256 = "1cc881364974102182257a5c5c2b9cfed513689dee28924ca44df082bdf9fd60",
|
||||
urls = ["https://commondatastorage.googleapis.com/chromium-browser-snapshots/Win/664999/chromedriver_win32.zip"],
|
||||
sha256 = "de1423b2d69f96e451e902d686e8d471610d786c345a8de59dd1a5a436e45fc2",
|
||||
# 83.0.4103
|
||||
urls = ["https://commondatastorage.googleapis.com/chromium-browser-snapshots/Win/756065/chromedriver_win32.zip"],
|
||||
)
|
@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
* Copyright Google LLC All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
|
@ -32,6 +32,7 @@ ts_library(
|
||||
"@npm//@types/events",
|
||||
"@npm//@types/jasmine",
|
||||
"@npm//@types/node",
|
||||
"@npm//inquirer",
|
||||
],
|
||||
)
|
||||
|
||||
|
@ -1,11 +1,14 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
* Copyright Google LLC All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
import * as yargs from 'yargs';
|
||||
|
||||
import {info} from '../utils/console';
|
||||
|
||||
import {validateFile} from './validate-file';
|
||||
import {validateCommitRange} from './validate-range';
|
||||
|
||||
@ -51,10 +54,10 @@ export function buildCommitMessageParser(localYargs: yargs.Argv) {
|
||||
// If on CI, and not pull request number is provided, assume the branch
|
||||
// being run on is an upstream branch.
|
||||
if (process.env['CI'] && process.env['CI_PULL_REQUEST'] === 'false') {
|
||||
console.info(
|
||||
`Since valid commit messages are enforced by PR linting on CI, we do not\n` +
|
||||
`need to validate commit messages on CI runs on upstream branches.\n\n` +
|
||||
`Skipping check of provided commit range`);
|
||||
info(`Since valid commit messages are enforced by PR linting on CI, we do not`);
|
||||
info(`need to validate commit messages on CI runs on upstream branches.`);
|
||||
info();
|
||||
info(`Skipping check of provided commit range`);
|
||||
return;
|
||||
}
|
||||
validateCommitRange(argv.range);
|
||||
|
@ -1,6 +1,6 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
* Copyright Google LLC All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
|
@ -1,6 +1,6 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
* Copyright Google LLC All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
@ -9,6 +9,7 @@ import {readFileSync} from 'fs';
|
||||
import {resolve} from 'path';
|
||||
|
||||
import {getRepoBaseDir} from '../utils/config';
|
||||
import {info} from '../utils/console';
|
||||
|
||||
import {validateCommitMessage} from './validate';
|
||||
|
||||
@ -16,7 +17,7 @@ import {validateCommitMessage} from './validate';
|
||||
export function validateFile(filePath: string) {
|
||||
const commitMessage = readFileSync(resolve(getRepoBaseDir(), filePath), 'utf8');
|
||||
if (validateCommitMessage(commitMessage)) {
|
||||
console.info('√ Valid commit message');
|
||||
info('√ Valid commit message');
|
||||
return;
|
||||
}
|
||||
// If the validation did not return true, exit as a failure.
|
||||
|
@ -1,11 +1,13 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
* Copyright Google LLC All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
import {exec} from 'shelljs';
|
||||
import {info} from '../utils/console';
|
||||
import {exec} from '../utils/shelljs';
|
||||
|
||||
import {parseCommitMessage, validateCommitMessage, ValidateCommitMessageOptions} from './validate';
|
||||
|
||||
// Whether the provided commit is a fixup commit.
|
||||
@ -23,7 +25,7 @@ export function validateCommitRange(range: string) {
|
||||
const gitLogFormat = `%s%n%n%b${randomValueSeparator}`;
|
||||
|
||||
// Retrieve the commits in the provided range.
|
||||
const result = exec(`git log --reverse --format=${gitLogFormat} ${range}`, {silent: true});
|
||||
const result = exec(`git log --reverse --format=${gitLogFormat} ${range}`);
|
||||
if (result.code) {
|
||||
throw new Error(`Failed to get all commits in the range: \n ${result.stderr}`);
|
||||
}
|
||||
@ -31,7 +33,7 @@ export function validateCommitRange(range: string) {
|
||||
// Separate the commits from a single string into individual commits
|
||||
const commits = result.split(randomValueSeparator).map(l => l.trim()).filter(line => !!line);
|
||||
|
||||
console.info(`Examining ${commits.length} commit(s) in the provided range: ${range}`);
|
||||
info(`Examining ${commits.length} commit(s) in the provided range: ${range}`);
|
||||
|
||||
// Check each commit in the commit range. Commits are allowed to be fixup commits for other
|
||||
// commits in the provided commit range.
|
||||
@ -46,7 +48,7 @@ export function validateCommitRange(range: string) {
|
||||
});
|
||||
|
||||
if (allCommitsInRangeValid) {
|
||||
console.info('√ All commit messages in range valid.');
|
||||
info('√ All commit messages in range valid.');
|
||||
} else {
|
||||
// Exit with a non-zero exit code if invalid commit messages have
|
||||
// been discovered.
|
||||
|
@ -1,6 +1,6 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
* Copyright Google LLC All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
|
@ -1,10 +1,12 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
* Copyright Google LLC All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
import {error} from '../utils/console';
|
||||
|
||||
import {getCommitMessageConfig} from './config';
|
||||
|
||||
/** Options for commit message validation. */
|
||||
@ -62,8 +64,8 @@ export function parseCommitMessage(commitMsg: string) {
|
||||
/** Validate a commit message against using the local repo's config. */
|
||||
export function validateCommitMessage(
|
||||
commitMsg: string, options: ValidateCommitMessageOptions = {}) {
|
||||
function error(errorMessage: string) {
|
||||
console.error(
|
||||
function printError(errorMessage: string) {
|
||||
error(
|
||||
`INVALID COMMIT MSG: \n` +
|
||||
`${'─'.repeat(40)}\n` +
|
||||
`${commitMsg}\n` +
|
||||
@ -91,7 +93,7 @@ export function validateCommitMessage(
|
||||
// the git history anyway, unless the options provided to not allow squash commits.
|
||||
if (commit.isSquash) {
|
||||
if (options.disallowSquash) {
|
||||
error('The commit must be manually squashed into the target commit');
|
||||
printError('The commit must be manually squashed into the target commit');
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
@ -104,7 +106,7 @@ export function validateCommitMessage(
|
||||
// check.
|
||||
if (commit.isFixup) {
|
||||
if (options.nonFixupCommitHeaders && !options.nonFixupCommitHeaders.includes(commit.header)) {
|
||||
error(
|
||||
printError(
|
||||
'Unable to find match for fixup commit among prior commits: ' +
|
||||
(options.nonFixupCommitHeaders.map(x => `\n ${x}`).join('') || '-'));
|
||||
return false;
|
||||
@ -117,22 +119,23 @@ export function validateCommitMessage(
|
||||
// Checking commit header //
|
||||
////////////////////////////
|
||||
if (commit.header.length > config.maxLineLength) {
|
||||
error(`The commit message header is longer than ${config.maxLineLength} characters`);
|
||||
printError(`The commit message header is longer than ${config.maxLineLength} characters`);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!commit.type) {
|
||||
error(`The commit message header does not match the expected format.`);
|
||||
printError(`The commit message header does not match the expected format.`);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!config.types.includes(commit.type)) {
|
||||
error(`'${commit.type}' is not an allowed type.\n => TYPES: ${config.types.join(', ')}`);
|
||||
printError(`'${commit.type}' is not an allowed type.\n => TYPES: ${config.types.join(', ')}`);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (commit.scope && !config.scopes.includes(commit.scope)) {
|
||||
error(`'${commit.scope}' is not an allowed scope.\n => SCOPES: ${config.scopes.join(', ')}`);
|
||||
printError(
|
||||
`'${commit.scope}' is not an allowed scope.\n => SCOPES: ${config.scopes.join(', ')}`);
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -146,14 +149,14 @@ export function validateCommitMessage(
|
||||
//////////////////////////
|
||||
|
||||
if (commit.bodyWithoutLinking.trim().length < config.minBodyLength) {
|
||||
error(`The commit message body does not meet the minimum length of ${
|
||||
printError(`The commit message body does not meet the minimum length of ${
|
||||
config.minBodyLength} characters`);
|
||||
return false;
|
||||
}
|
||||
|
||||
const bodyByLine = commit.body.split('\n');
|
||||
if (bodyByLine.some(line => line.length > config.maxLineLength)) {
|
||||
error(
|
||||
printError(
|
||||
`The commit messsage body contains lines greater than ${config.maxLineLength} characters`);
|
||||
return false;
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
* Copyright Google LLC All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
|
@ -1,6 +1,6 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
* Copyright Google LLC All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
|
@ -1,12 +1,15 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
* Copyright Google LLC All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {prompt} from 'inquirer';
|
||||
|
||||
import {error, info} from '../utils/console';
|
||||
|
||||
import {runFormatterInParallel} from './run-commands-parallel';
|
||||
|
||||
/**
|
||||
@ -17,16 +20,16 @@ export async function formatFiles(files: string[]) {
|
||||
let failures = await runFormatterInParallel(files, 'format');
|
||||
|
||||
if (failures === false) {
|
||||
console.info('No files matched for formatting.');
|
||||
info('No files matched for formatting.');
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
// The process should exit as a failure if any of the files failed to format.
|
||||
if (failures.length !== 0) {
|
||||
console.error(`Formatting failed, see errors above for more information.`);
|
||||
error(`Formatting failed, see errors above for more information.`);
|
||||
process.exit(1);
|
||||
}
|
||||
console.info(`√ Formatting complete.`);
|
||||
info(`√ Formatting complete.`);
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
@ -38,18 +41,18 @@ export async function checkFiles(files: string[]) {
|
||||
const failures = await runFormatterInParallel(files, 'check');
|
||||
|
||||
if (failures === false) {
|
||||
console.info('No files matched for formatting check.');
|
||||
info('No files matched for formatting check.');
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
if (failures.length) {
|
||||
// Provide output expressing which files are failing formatting.
|
||||
console.group('\nThe following files are out of format:');
|
||||
info.group('\nThe following files are out of format:');
|
||||
for (const file of failures) {
|
||||
console.info(` - ${file}`);
|
||||
info(` - ${file}`);
|
||||
}
|
||||
console.groupEnd();
|
||||
console.info();
|
||||
info.groupEnd();
|
||||
info();
|
||||
|
||||
// If the command is run in a non-CI environment, prompt to format the files immediately.
|
||||
let runFormatter = false;
|
||||
@ -67,13 +70,13 @@ export async function checkFiles(files: string[]) {
|
||||
process.exit(0);
|
||||
} else {
|
||||
// Inform user how to format files in the future.
|
||||
console.info();
|
||||
console.info(`To format the failing file run the following command:`);
|
||||
console.info(` yarn ng-dev format files ${failures.join(' ')}`);
|
||||
info();
|
||||
info(`To format the failing file run the following command:`);
|
||||
info(` yarn ng-dev format files ${failures.join(' ')}`);
|
||||
process.exit(1);
|
||||
}
|
||||
} else {
|
||||
console.info('√ All files correctly formatted.');
|
||||
info('√ All files correctly formatted.');
|
||||
process.exit(0);
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
* Copyright Google LLC All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
|