Compare commits
100 Commits
Author | SHA1 | Date | |
---|---|---|---|
da5d91b97b | |||
ba1ef6b1a5 | |||
acebf6464e | |||
e367aa2ca5 | |||
0ee2b755e2 | |||
f213c7a643 | |||
947c076ff2 | |||
6600bea815 | |||
6cd61aeb1c | |||
581b991432 | |||
4014aab300 | |||
b523844966 | |||
8d322c89b7 | |||
08a7c6f0b5 | |||
31f06ee3f9 | |||
1c5b157f10 | |||
38fe47316c | |||
9488da0d0a | |||
5a8c560373 | |||
d8675c7e72 | |||
bf29bd95a6 | |||
1775498b40 | |||
1eea575dfc | |||
0c624b2ca7 | |||
8659451d13 | |||
2565f67956 | |||
38f4dcd5e1 | |||
6e380cff82 | |||
76a84706b6 | |||
39302ba923 | |||
1c71db846f | |||
48e4b0eb7f | |||
9fd63c3ef6 | |||
aaea3878d9 | |||
8838a1b54c | |||
e742edca78 | |||
8304343e94 | |||
2ec91443e9 | |||
be30f25da6 | |||
c426a2595f | |||
896a8a441e | |||
b1e7f4c952 | |||
d3ee9e3926 | |||
2b7116a4f3 | |||
8be20f3a3e | |||
5112669d29 | |||
3d453fe6df | |||
32a6972cdc | |||
2cd5d4c64b | |||
3248fe865f | |||
b3ea6981cd | |||
f698a6bd73 | |||
97d2673eae | |||
4e93c4f87a | |||
b5f85638f7 | |||
efd13d31fc | |||
3a45bef0e7 | |||
5b105544fc | |||
4c5cfecc56 | |||
9d2348b9af | |||
e7bca0751c | |||
45c909a237 | |||
5b76b939af | |||
261593aab1 | |||
c9ce735675 | |||
b7f95bec04 | |||
d02573d05f | |||
8ff6ed6aa3 | |||
6aaf4eac89 | |||
1d0c93622d | |||
acd6c195a0 | |||
53a9a28ec0 | |||
aa8dd74176 | |||
ed6dcce13b | |||
d170e45784 | |||
7033e7eb22 | |||
893123936b | |||
31fcb9e036 | |||
c303d44df6 | |||
73dd43170b | |||
514271ba9c | |||
8d18b49899 | |||
6b0b7d01bf | |||
079773f54e | |||
b5cb120db0 | |||
0fb78d6c94 | |||
a80a8636ba | |||
0878d67757 | |||
5e0890b3af | |||
43bac21301 | |||
4df415060b | |||
039d70efec | |||
2a71496791 | |||
617a329c24 | |||
a00eae0b62 | |||
e54dd741ef | |||
7b4c1cdbe4 | |||
5e4babeaa9 | |||
f661f460bb | |||
44f233c02c |
@ -28,14 +28,3 @@ test --flaky_test_attempts=2
|
||||
|
||||
# More details on failures
|
||||
build --verbose_failures=true
|
||||
|
||||
# We have seen some flakiness in using TS workers on CircleCI
|
||||
# https://angular-team.slack.com/archives/C07DT5M6V/p1562693245183400
|
||||
# > failures like `ERROR: /home/circleci/ng/packages/core/test/BUILD.bazel:5:1:
|
||||
# > Compiling TypeScript (devmode) //packages/core/test:test_lib failed: Worker process did not return a WorkResponse:`
|
||||
# > I saw that issue a couple times today.
|
||||
# > Example job: https://circleci.com/gh/angular/angular/385517
|
||||
# We expect that TypeScript compilations will parallelize wider than the number of local cores anyway
|
||||
# so we should saturate remote workers with TS compilations
|
||||
build --strategy=AngularTemplateCompile=local
|
||||
build --strategy=TypeScriptCompile=local
|
||||
|
@ -11,12 +11,14 @@
|
||||
# needed for jobs that run tests without Bazel. Bazel runs tests with browsers that will be
|
||||
# fetched by the Webtesting rules. Therefore for jobs that run tests with Bazel, we don't need a
|
||||
# docker image with browsers pre-installed.
|
||||
# **NOTE 1**: If you change the version of the `*-browsers` docker image, make sure the
|
||||
# **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
|
||||
# `CI_CHROMEDRIVER_VERSION_ARG` env var (in `.circleci/env.sh`) points to a ChromeDriver
|
||||
# version that is compatible with the Chrome version in the image.
|
||||
# **NOTE 2**: If you change the version of the docker images, also change the `cache_key` suffix.
|
||||
var_1: &default_docker_image circleci/node:10.16
|
||||
var_2: &browsers_docker_image circleci/node:10.16-browsers
|
||||
var_1: &default_docker_image circleci/node:10.16@sha256:75c05084fff4afa3683a03c5a04a4a3ad95c536ff2439d8fe14e7e1f5c58b09a
|
||||
var_2: &browsers_docker_image circleci/node:10.16-browsers@sha256:d2a96fe1cbef51257ee626b5f645e64dade3e886f00ba9cb7e8ea65b4efe8db1
|
||||
# We don't want to include the current branch name in the cache key because that would prevent
|
||||
# PRs from being able to restore the cache since the branch names are always different for PRs.
|
||||
# The cache key should only consist of dynamic values that change whenever something in the
|
||||
@ -35,7 +37,7 @@ var_4: &init_environment
|
||||
# Overwrite the yarn installed in the docker container with our own version.
|
||||
command: |
|
||||
./.circleci/env.sh
|
||||
ourYarn=$(realpath ./third_party/github.com/yarnpkg/yarn/releases/download/v1.13.0/bin/yarn.js)
|
||||
ourYarn=$(realpath ./third_party/github.com/yarnpkg/yarn/releases/download/v1.17.3/bin/yarn.js)
|
||||
sudo chmod a+x $ourYarn
|
||||
sudo ln -fs $ourYarn /usr/local/bin/yarn
|
||||
echo "Yarn version: $(yarn --version)"
|
||||
@ -187,6 +189,7 @@ jobs:
|
||||
(echo -e "\n.bzl files have lint errors. Please run ''yarn bazel:lint-fix''"; exit 1)'
|
||||
|
||||
- run: yarn gulp lint
|
||||
- run: node tools/verify-codeownership
|
||||
|
||||
test:
|
||||
<<: *job_defaults
|
||||
@ -198,13 +201,6 @@ jobs:
|
||||
# Setup remote execution and run RBE-compatible tests.
|
||||
- *setup_bazel_remote_execution
|
||||
- run: yarn bazel test //... --build_tag_filters=-ivy-only --test_tag_filters=-ivy-only
|
||||
- run: mkdir ~/testlogs
|
||||
- run: cp -Lr dist/testlogs/* ~/testlogs
|
||||
- store_test_results:
|
||||
# Bazel always writes test.xml files under this directory
|
||||
path: ~/testlogs
|
||||
- store_artifacts:
|
||||
path: ~/testlogs
|
||||
|
||||
# Temporary job to test what will happen when we flip the Ivy flag to true
|
||||
test_ivy_aot:
|
||||
@ -255,6 +251,7 @@ jobs:
|
||||
- *attach_workspace
|
||||
- *init_environment
|
||||
- *setup_circleci_bazel_config
|
||||
- *setup_bazel_remote_execution
|
||||
- run:
|
||||
name: Run Bazel tests in saucelabs
|
||||
# All web tests are contained within a single //:test_web_all target for Saucelabs
|
||||
@ -373,7 +370,7 @@ jobs:
|
||||
# Run examples tests. The "CIRCLE_NODE_INDEX" will be set if "parallelism" is enabled.
|
||||
# Since the parallelism is set to "3", there will be three parallel CircleCI containers
|
||||
# with either "0", "1" or "2" as node index. This can be passed to the "--shard" argument.
|
||||
- run: yarn --cwd aio example-e2e --setup --local --cliSpecsConcurrency=5 --shard=${CIRCLE_NODE_INDEX}/${CIRCLE_NODE_TOTAL}
|
||||
- run: yarn --cwd aio example-e2e --setup --local --cliSpecsConcurrency=5 --shard=${CIRCLE_NODE_INDEX}/${CIRCLE_NODE_TOTAL} --retry 2
|
||||
|
||||
test_docs_examples_ivy:
|
||||
<<: *job_defaults
|
||||
@ -399,7 +396,7 @@ jobs:
|
||||
# Run examples tests with ivy. The "CIRCLE_NODE_INDEX" will be set if "parallelism" is enabled.
|
||||
# Since the parallelism is set to "3", there will be three parallel CircleCI containers
|
||||
# with either "0", "1" or "2" as node index. This can be passed to the "--shard" argument.
|
||||
- run: yarn --cwd aio example-e2e --setup --local --ivy --cliSpecsConcurrency=5 --shard=${CIRCLE_NODE_INDEX}/${CIRCLE_NODE_TOTAL}
|
||||
- run: yarn --cwd aio example-e2e --setup --local --ivy --cliSpecsConcurrency=5 --shard=${CIRCLE_NODE_INDEX}/${CIRCLE_NODE_TOTAL} --retry 2
|
||||
|
||||
# This job should only be run on PR builds, where `CI_PULL_REQUEST` is not `false`.
|
||||
aio_preview:
|
||||
|
@ -19,16 +19,20 @@ setPublicVar PROJECT_ROOT "$projectDir";
|
||||
setPublicVar CI_AIO_MIN_PWA_SCORE "95";
|
||||
# This is the branch being built; e.g. `pull/12345` for PR builds.
|
||||
setPublicVar CI_BRANCH "$CIRCLE_BRANCH";
|
||||
setPublicVar CI_BUILD_URL "$CIRCLE_BUILD_URL";
|
||||
# ChromeDriver version compatible with the Chrome version included in the docker image used in
|
||||
# `.circleci/config.yml`. See http://chromedriver.chromium.org/downloads for a list of versions.
|
||||
# This variable is intended to be passed as an arg to the `webdriver-manager update` command (e.g.
|
||||
# `"postinstall": "webdriver-manager update $CI_CHROMEDRIVER_VERSION_ARG"`).
|
||||
setPublicVar CI_CHROMEDRIVER_VERSION_ARG "--versions.chrome 75.0.3770.90";
|
||||
setPublicVar CI_COMMIT "$CIRCLE_SHA1";
|
||||
# `CI_COMMIT_RANGE` will only be available when `CIRCLE_COMPARE_URL` is also available (or can be
|
||||
# retrieved via `get-compare-url.js`), i.e. on push builds (a.k.a. non-PR, non-scheduled builds and
|
||||
# rerun workflows of such builds). That is fine, since we only need it in push builds.
|
||||
setPublicVar CI_COMMIT_RANGE "`[[ ${CIRCLE_PR_NUMBER:-false} != false ]] && echo "" || node $getCommitRangePath "$CIRCLE_BUILD_NUM" "$CIRCLE_COMPARE_URL"`";
|
||||
# `CI_COMMIT_RANGE` is only used on push builds (a.k.a. non-PR, non-scheduled builds and rerun
|
||||
# workflows of such builds).
|
||||
# NOTE: With [CircleCI Pipelines](https://circleci.com/docs/2.0/build-processing) enabled,
|
||||
# `CIRCLE_COMPARE_URL` is no longer available and the commit range cannot be reliably
|
||||
# detected. Fall back to only considering the last commit (which is accurate in the majority
|
||||
# of cases for push builds).
|
||||
setPublicVar CI_COMMIT_RANGE "`[[ ${CIRCLE_PR_NUMBER:-false} != false ]] && echo "" || echo "$CIRCLE_SHA1~1...$CIRCLE_SHA1"`";
|
||||
setPublicVar CI_PULL_REQUEST "${CIRCLE_PR_NUMBER:-false}";
|
||||
setPublicVar CI_REPO_NAME "$CIRCLE_PROJECT_REPONAME";
|
||||
setPublicVar CI_REPO_OWNER "$CIRCLE_PROJECT_USERNAME";
|
||||
|
@ -10,6 +10,13 @@
|
||||
* format of the `CIRCLE_COMPARE_URL` environment variable, or by retrieving the equivalent of
|
||||
* `CIRCLE_COMPARE_URL` for jobs that are part of a rerun workflow and extracting it from there.
|
||||
*
|
||||
* > !!! WARNING !!!
|
||||
* > !!
|
||||
* > !! When [CircleCI Pipelines](https://circleci.com/docs/2.0/build-processing) is enabled, the
|
||||
* > !! `CIRCLE_COMPARE_URL` environment variable is not available at all and this script does not
|
||||
* > !! work.
|
||||
* > !!!!!!!!!!!!!!!
|
||||
*
|
||||
* **Context:**
|
||||
* CircleCI sets the `CIRCLE_COMPARE_URL` environment variable (from which we can extract the commit
|
||||
* range) on push builds (a.k.a. non-PR, non-scheduled builds). Yet, when a workflow is rerun
|
||||
@ -21,7 +28,7 @@
|
||||
* (undocumented) fact that the workspace ID happens to be the same as the workflow ID that first
|
||||
* created it.
|
||||
*
|
||||
* For example, for a job on push build workflow, the CircleCI API will return data that look like:
|
||||
* For example, for a job on push build workflows, the CircleCI API will return data that look like:
|
||||
* ```js
|
||||
* {
|
||||
* compare: 'THE_COMPARE_URL_WE_ARE_LOOKING_FOR',
|
||||
|
146
.github/CODEOWNERS
vendored
146
.github/CODEOWNERS
vendored
@ -44,23 +44,19 @@
|
||||
# alxhub - Alex Rickabaugh
|
||||
# AndrewKushnir - Andrew Kushnir
|
||||
# andrewseguin - Andrew Seguin
|
||||
# benlesh - Ben Lesh
|
||||
# brandonroberts - Brandon Roberts
|
||||
# atscott - Andrew Scott
|
||||
# devversion - Paul Gschwendtner
|
||||
# filipesilva - Filipe Silva
|
||||
# gkalpak - George Kalpakas
|
||||
# hansl - Hans Larsen
|
||||
# IgorMinar - Igor Minar
|
||||
# jasonaden - Jason Aden
|
||||
# jenniferfell - Jennifer Fell
|
||||
# JiaLiPassion - Jia Li
|
||||
# josephperrott - Joey Perrott
|
||||
# kapunahelewong - Kapunahele Wong
|
||||
# kara - Kara Erickson
|
||||
# kyliau - Keen Yee Liau
|
||||
# matsko - Matias Niemelä
|
||||
# mgechev - Minko Gechev
|
||||
# mhevery - Misko Hevery
|
||||
# ocombe - Olivier Combe
|
||||
# petebacondarwin - Pete Bacon Darwin
|
||||
# pkozlowski-opensource - Pawel Kozlowski
|
||||
# robwormald - Rob Wormald
|
||||
@ -88,9 +84,9 @@
|
||||
# (secret team to avoid review requests, it also doesn't inherit from @angular/framework because nested teams can't be secret)
|
||||
#
|
||||
# - IgorMinar
|
||||
# - josephperrott
|
||||
# - kara
|
||||
# - mhevery
|
||||
# - alexeagle
|
||||
|
||||
|
||||
# ===========================================================
|
||||
@ -99,8 +95,8 @@
|
||||
# Used for approving minor documentation-only changes that don't require engineering review.
|
||||
# (secret team to avoid review requests, it also doesn't inherit from @angular/framework because nested teams can't be secret)
|
||||
#
|
||||
# - brandonroberts
|
||||
# - gkalpak
|
||||
# - kapunahelewong
|
||||
# - petebacondarwin
|
||||
|
||||
|
||||
@ -125,10 +121,9 @@
|
||||
# @angular/tools-cli
|
||||
# ===========================================================
|
||||
#
|
||||
# - alexeagle
|
||||
# - filipesilva
|
||||
# - hansl
|
||||
# - mgechev
|
||||
# - vikerman
|
||||
|
||||
|
||||
# ===========================================================
|
||||
@ -179,8 +174,7 @@
|
||||
# @angular/fw-forms
|
||||
# ===========================================================
|
||||
#
|
||||
# - kara
|
||||
# - jasonaden
|
||||
# - AndrewKushnir
|
||||
|
||||
|
||||
# ===========================================================
|
||||
@ -202,7 +196,7 @@
|
||||
# @angular/fw-router
|
||||
# ===========================================================
|
||||
#
|
||||
# - jasonaden
|
||||
# - atscott
|
||||
|
||||
|
||||
# ===========================================================
|
||||
@ -220,7 +214,6 @@
|
||||
#
|
||||
# - gkalpak
|
||||
# - petebacondarwin
|
||||
# - jasonaden
|
||||
|
||||
|
||||
# ===========================================================
|
||||
@ -236,7 +229,7 @@
|
||||
#
|
||||
# - AndrewKushnir
|
||||
# - mhevery
|
||||
# - ocombe
|
||||
# - petebacondarwin
|
||||
# - vikerman
|
||||
|
||||
|
||||
@ -268,8 +261,8 @@
|
||||
# @angular/fw-integration
|
||||
# ===========================================================
|
||||
#
|
||||
# - alexeagle
|
||||
# - IgorMinar
|
||||
# - josephperrott
|
||||
# - mhevery
|
||||
|
||||
|
||||
@ -277,7 +270,6 @@
|
||||
# @angular/docs-infra
|
||||
# ===========================================================
|
||||
#
|
||||
# - brandonroberts
|
||||
# - gkalpak
|
||||
# - IgorMinar
|
||||
# - petebacondarwin
|
||||
@ -287,8 +279,6 @@
|
||||
# @angular/fw-docs-intro
|
||||
# ===========================================================
|
||||
#
|
||||
# - jenniferfell
|
||||
# - brandonroberts
|
||||
# - IgorMinar
|
||||
# - stephenfluin
|
||||
|
||||
@ -297,16 +287,15 @@
|
||||
# @angular/fw-docs-observables
|
||||
# ===========================================================
|
||||
#
|
||||
# - benlesh
|
||||
# - jasonaden
|
||||
# - alxhub
|
||||
|
||||
|
||||
# ===========================================================
|
||||
# @angular/fw-docs-packaging
|
||||
# ===========================================================
|
||||
#
|
||||
# - alexeagle
|
||||
# - IgorMinar
|
||||
# - vikerman
|
||||
|
||||
|
||||
# ===========================================================
|
||||
@ -314,10 +303,9 @@
|
||||
# ===========================================================
|
||||
#
|
||||
# - alan-agius4
|
||||
# - alexeagle
|
||||
# - hansl
|
||||
# - IgorMinar
|
||||
# - mgechev
|
||||
# - vikerman
|
||||
|
||||
|
||||
# ===========================================================
|
||||
@ -325,11 +313,9 @@
|
||||
# ===========================================================
|
||||
#
|
||||
# - alan-agius4
|
||||
# - alexeagle
|
||||
# - hansl
|
||||
# - IgorMinar
|
||||
# - mgechev
|
||||
|
||||
# - vikerman
|
||||
|
||||
|
||||
# ===========================================================
|
||||
@ -348,10 +334,9 @@
|
||||
|
||||
|
||||
# ===========================================================
|
||||
# @angular/fw-dev-infra
|
||||
# @angular/dev-infra-framework
|
||||
# ===========================================================
|
||||
#
|
||||
# - alexeagle
|
||||
# - devversion
|
||||
# - filipesilva
|
||||
# - gkalpak
|
||||
@ -409,6 +394,7 @@
|
||||
# ================================================
|
||||
|
||||
/packages/bazel/** @angular/tools-bazel @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/guide/bazel.md @angular/tools-bazel @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
|
||||
|
||||
|
||||
@ -418,8 +404,11 @@
|
||||
# ================================================
|
||||
|
||||
/packages/compiler/** @angular/fw-compiler @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/packages/examples/compiler/** @angular/fw-compiler @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/packages/compiler-cli/** @angular/fw-compiler @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/guide/angular-compiler-options.md @angular/fw-compiler @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/guide/aot-compiler.md @angular/fw-compiler @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/guide/aot-metadata-errors.md @angular/fw-compiler @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
|
||||
|
||||
|
||||
@ -438,6 +427,7 @@
|
||||
# ================================================
|
||||
|
||||
/packages/compiler-cli/src/ngtools/** @angular/tools-cli @angular/framework-global-approvers
|
||||
/aio/content/guide/cli-builder.md @angular/tools-cli @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/guide/ivy.md @angular/tools-cli @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/guide/web-worker.md @angular/tools-cli @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
|
||||
@ -453,12 +443,18 @@
|
||||
# ================================================
|
||||
|
||||
/packages/core/** @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/packages/examples/core/** @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/packages/common/** @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/packages/platform-browser/** @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/packages/examples/platform-browser/** @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/packages/platform-browser-dynamic/** @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/packages/platform-webworker/** @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/packages/platform-webworker-dynamic/** @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/packages/examples/common/** @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/packages/docs/** @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
|
||||
/aio/content/guide/accessibility.md @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/examples/accessibility/** @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
|
||||
/aio/content/guide/architecture-components.md @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/guide/architecture-modules.md @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
@ -515,6 +511,8 @@
|
||||
|
||||
/aio/content/guide/hierarchical-dependency-injection.md @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/examples/hierarchical-dependency-injection/** @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/examples/providers-viewproviders/** @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/examples/resolution-modifiers/** @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
|
||||
/aio/content/guide/lazy-loading-ngmodules.md @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/examples/lazy-loading-ngmodules/** @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
@ -549,11 +547,12 @@
|
||||
/aio/content/examples/attribute-binding/** @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/examples/two-way-binding/** @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/examples/built-in-directives/** @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/images/guide/built-in-directives/** @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/examples/template-reference-variables/** @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/examples/inputs-outputs/** @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/images/guide/inputs-outputs/** @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/examples/template-expression-operators/** @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
|
||||
|
||||
/aio/content/guide/pipes.md @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/examples/pipes/** @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/images/guide/pipes/** @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
@ -586,6 +585,7 @@
|
||||
|
||||
/packages/common/http/** @angular/fw-http @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/packages/http/** @angular/fw-http @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/packages/examples/http/** @angular/fw-http @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/guide/http.md @angular/fw-http @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/examples/http/** @angular/fw-http @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/images/guide/http/** @angular/fw-http @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
@ -608,6 +608,7 @@
|
||||
# ================================================
|
||||
|
||||
/packages/forms/** @angular/fw-forms @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/packages/examples/forms/** @angular/fw-forms @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/guide/forms.md @angular/fw-forms @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/examples/forms/** @angular/fw-forms @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/images/guide/forms/** @angular/fw-forms @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
@ -651,6 +652,7 @@
|
||||
# ================================================
|
||||
|
||||
/packages/router/** @angular/fw-router @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/packages/examples/router/** @angular/fw-router @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/guide/router.md @angular/fw-router @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/examples/router/** @angular/fw-router @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/images/guide/router/** @angular/fw-router @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
@ -662,6 +664,7 @@
|
||||
# ================================================
|
||||
|
||||
/packages/service-worker/** @angular/fw-service-worker @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/packages/examples/service-worker/** @angular/fw-service-worker @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/guide/service-worker-getting-started.md @angular/fw-service-worker @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/examples/service-worker-getting-started/** @angular/fw-service-worker @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/guide/app-shell.md @angular/fw-service-worker @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
@ -688,6 +691,7 @@
|
||||
/aio/content/examples/upgrade-phonecat-2-hybrid/** @angular/fw-upgrade @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/examples/upgrade-phonecat-3-final/** @angular/fw-upgrade @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/guide/upgrade-performance.md @angular/fw-upgrade @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/guide/upgrade-setup.md @angular/fw-upgrade @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/guide/ajs-quick-reference.md @angular/fw-upgrade @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/examples/ajs-quick-reference/** @angular/fw-upgrade @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
|
||||
@ -725,7 +729,6 @@ testing/** @angular/fw-test
|
||||
/aio/content/examples/i18n/** @angular/fw-i18n @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
|
||||
|
||||
|
||||
# ================================================
|
||||
# @angular security
|
||||
# ================================================
|
||||
@ -776,7 +779,6 @@ testing/** @angular/fw-test
|
||||
/aio/tools/** @angular/docs-infra @angular/framework-global-approvers
|
||||
|
||||
# Hidden docs
|
||||
/aio/content/guide/change-log.md @angular/docs-infra @angular/framework-global-approvers
|
||||
/aio/content/guide/docs-style-guide.md @angular/docs-infra @angular/framework-global-approvers
|
||||
/aio/content/examples/docs-style-guide/** @angular/docs-infra @angular/framework-global-approvers
|
||||
/aio/content/images/guide/docs-style-guide/** @angular/docs-infra @angular/framework-global-approvers
|
||||
@ -789,9 +791,8 @@ testing/** @angular/fw-test
|
||||
# Docs: getting started & tutorial
|
||||
# ================================================
|
||||
|
||||
/aio/content/guide/quickstart.md @angular/fw-docs-intro @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/examples/cli-quickstart/** @angular/fw-docs-intro @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/images/guide/cli-quickstart/** @angular/fw-docs-intro @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/guide/setup-local.md @angular/fw-docs-intro @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/images/guide/setup-local/** @angular/fw-docs-intro @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/tutorial/** @angular/fw-docs-intro @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/images/guide/toh/** @angular/fw-docs-intro @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/examples/toh-pt0/** @angular/fw-docs-intro @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
@ -803,8 +804,8 @@ testing/** @angular/fw-test
|
||||
/aio/content/examples/toh-pt6/** @angular/fw-docs-intro @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/examples/getting-started-v0/** @angular/fw-docs-intro @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/examples/getting-started/** @angular/fw-docs-intro @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/getting-started/** @angular/fw-docs-intro @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/images/guide/getting-started/** @angular/fw-docs-intro @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/start/** @angular/fw-docs-intro @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/images/guide/start/** @angular/fw-docs-intro @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
|
||||
|
||||
# ================================================
|
||||
@ -830,17 +831,18 @@ testing/** @angular/fw-test
|
||||
/aio/content/guide/npm-packages.md @angular/fw-docs-packaging @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/guide/browser-support.md @angular/fw-docs-packaging @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/guide/typescript-configuration.md @angular/fw-docs-packaging @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/guide/setup.md @angular/fw-docs-packaging @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/examples/setup/** @angular/fw-docs-packaging @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/guide/build.md @angular/fw-docs-packaging @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/images/guide/build/** @angular/fw-docs-packaging @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/guide/deployment.md @angular/fw-docs-packaging @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/images/guide/deployment/** @angular/fw-docs-packaging @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/guide/file-structure.md @angular/fw-docs-packaging @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/guide/releases.md @angular/fw-docs-packaging @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/guide/updating.md @angular/fw-docs-packaging @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/guide/workspace-config.md @angular/fw-docs-packaging @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/guide/deprecations.md @angular/fw-docs-packaging @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
|
||||
/aio/content/guide/migration-renderer.md @angular/fw-docs-packaging @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/guide/migration-undecorated-classes.md @angular/fw-docs-packaging @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
|
||||
|
||||
# ================================================
|
||||
@ -877,36 +879,38 @@ testing/** @angular/fw-test
|
||||
|
||||
|
||||
# ================================================
|
||||
# Build & CI Owners
|
||||
# Build, CI & Dev-infra Owners
|
||||
# ================================================
|
||||
|
||||
/* @angular/fw-dev-infra
|
||||
/.buildkite/** @angular/fw-dev-infra
|
||||
/.circleci/** @angular/fw-dev-infra
|
||||
/.devcontainer/** @angular/fw-dev-infra
|
||||
/.github/** @angular/fw-dev-infra
|
||||
/.vscode/** @angular/fw-dev-infra
|
||||
/docs/BAZEL.md @angular/fw-dev-infra
|
||||
/packages/* @angular/fw-dev-infra
|
||||
/scripts/** @angular/fw-dev-infra
|
||||
/third_party/** @angular/fw-dev-infra
|
||||
/tools/build/** @angular/fw-dev-infra
|
||||
/tools/cjs-jasmine/** @angular/fw-dev-infra
|
||||
/tools/gulp-tasks/** @angular/fw-dev-infra
|
||||
/tools/ngcontainer/** @angular/fw-dev-infra
|
||||
/tools/npm/** @angular/fw-dev-infra
|
||||
/tools/npm_workspace/** @angular/fw-dev-infra
|
||||
/tools/public_api_guard/** @angular/fw-dev-infra
|
||||
/tools/rxjs/** @angular/fw-dev-infra
|
||||
/tools/source-map-test/** @angular/fw-dev-infra
|
||||
/tools/symbol-extractor/** @angular/fw-dev-infra
|
||||
/tools/testing/** @angular/fw-dev-infra
|
||||
/tools/ts-api-guardian/** @angular/fw-dev-infra
|
||||
/tools/tslint/** @angular/fw-dev-infra
|
||||
/tools/validate-commit-message/** @angular/fw-dev-infra
|
||||
/tools/yarn/** @angular/fw-dev-infra
|
||||
/tools/*
|
||||
*.bzl @angular/fw-dev-infra
|
||||
/* @angular/dev-infra-framework
|
||||
/.buildkite/** @angular/dev-infra-framework
|
||||
/.circleci/** @angular/dev-infra-framework
|
||||
/.devcontainer/** @angular/dev-infra-framework
|
||||
/.github/** @angular/dev-infra-framework
|
||||
/.vscode/** @angular/dev-infra-framework
|
||||
/docs/BAZEL.md @angular/dev-infra-framework
|
||||
/packages/* @angular/dev-infra-framework
|
||||
/packages/examples/test-utils/** @angular/dev-infra-framework
|
||||
/packages/private/** @angular/dev-infra-framework
|
||||
/scripts/** @angular/dev-infra-framework
|
||||
/third_party/** @angular/dev-infra-framework
|
||||
/tools/build/** @angular/dev-infra-framework
|
||||
/tools/cjs-jasmine/** @angular/dev-infra-framework
|
||||
/tools/gulp-tasks/** @angular/dev-infra-framework
|
||||
/tools/ngcontainer/** @angular/dev-infra-framework
|
||||
/tools/npm/** @angular/dev-infra-framework
|
||||
/tools/npm_workspace/** @angular/dev-infra-framework
|
||||
/tools/public_api_guard/** @angular/dev-infra-framework
|
||||
/tools/rxjs/** @angular/dev-infra-framework
|
||||
/tools/source-map-test/** @angular/dev-infra-framework
|
||||
/tools/symbol-extractor/** @angular/dev-infra-framework
|
||||
/tools/testing/** @angular/dev-infra-framework
|
||||
/tools/ts-api-guardian/** @angular/dev-infra-framework
|
||||
/tools/tslint/** @angular/dev-infra-framework
|
||||
/tools/validate-commit-message/** @angular/dev-infra-framework
|
||||
/tools/yarn/** @angular/dev-infra-framework
|
||||
/tools/* @angular/dev-infra-framework
|
||||
*.bzl @angular/dev-infra-framework
|
||||
|
||||
|
||||
|
||||
@ -922,6 +926,14 @@ testing/** @angular/fw-test
|
||||
|
||||
|
||||
|
||||
# ================================================
|
||||
# Special cases
|
||||
# ================================================
|
||||
|
||||
/aio/content/guide/static-query-migration.md @kara @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
|
||||
|
||||
|
||||
# ================================================
|
||||
# CODEOWNERS Owners owners ...
|
||||
# ================================================
|
||||
|
36
CHANGELOG.md
36
CHANGELOG.md
@ -1,3 +1,39 @@
|
||||
<a name="8.2.7"></a>
|
||||
## [8.2.7](https://github.com/angular/angular/compare/8.2.6...8.2.7) (2019-09-18)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **bazel:** ng_package(data) should support non-text files ([#32721](https://github.com/angular/angular/issues/32721)) ([ba1ef6b](https://github.com/angular/angular/commit/ba1ef6b))
|
||||
* **compiler-cli:** fix typo in diagnostic template info. ([#32684](https://github.com/angular/angular/issues/32684)) ([947c076](https://github.com/angular/angular/commit/947c076)), closes [#32662](https://github.com/angular/angular/issues/32662)
|
||||
* **language-service:** cache module resolution ([#32483](https://github.com/angular/angular/issues/32483)) ([1c5b157](https://github.com/angular/angular/commit/1c5b157))
|
||||
|
||||
|
||||
|
||||
<a name="8.2.6"></a>
|
||||
## [8.2.6](https://github.com/angular/angular/compare/8.2.5...8.2.6) (2019-09-11)
|
||||
|
||||
This release contains various API docs improvements.
|
||||
|
||||
|
||||
|
||||
<a name="8.2.5"></a>
|
||||
## [8.2.5](https://github.com/angular/angular/compare/8.2.4...8.2.5) (2019-09-04)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **common:** HttpParams fromObject accepts ReadonlyArray<string> ([#31072](https://github.com/angular/angular/issues/31072)) ([b3ea698](https://github.com/angular/angular/commit/b3ea698)), closes [#28452](https://github.com/angular/angular/issues/28452)
|
||||
|
||||
|
||||
|
||||
<a name="8.2.4"></a>
|
||||
## [8.2.4](https://github.com/angular/angular/compare/8.2.3...8.2.4) (2019-08-28)
|
||||
|
||||
This release contains various API docs improvements.
|
||||
|
||||
|
||||
|
||||
<a name="8.2.3"></a>
|
||||
## [8.2.3](https://github.com/angular/angular/compare/8.2.2...8.2.3) (2019-08-21)
|
||||
|
||||
|
@ -65,6 +65,9 @@ node_repositories(
|
||||
},
|
||||
node_version = "10.16.0",
|
||||
package_json = ["//:package.json"],
|
||||
yarn_repositories = {
|
||||
"1.17.3": ("yarn-v1.17.3.tar.gz", "yarn-v1.17.3", "e3835194409f1b3afa1c62ca82f561f1c29d26580c9e220c36866317e043c6f3"),
|
||||
},
|
||||
# yarn 1.13.0 under Bazel has a regression on Windows that causes build errors on rebuilds:
|
||||
# ```
|
||||
# ERROR: Source forest creation failed: C:/.../fyuc5c3n/execroot/angular/external (Directory not empty)
|
||||
@ -73,7 +76,7 @@ node_repositories(
|
||||
# It possible that versions of yarn past 1.13.0 do not have this issue, however, before
|
||||
# advancing this version we need to test manually on Windows that the above error does not
|
||||
# happen as the issue is not caught by CI.
|
||||
yarn_version = "1.12.1",
|
||||
yarn_version = "1.17.3",
|
||||
)
|
||||
|
||||
yarn_install(
|
||||
|
@ -46,6 +46,15 @@ Here are the most important tasks you might need to use:
|
||||
- `yarn example-e2e --filter=foo` - limit e2e tests to those containing the word "foo"
|
||||
- `yarn example-e2e --setup --local` - run e2e tests with the local version of Angular contained in the "dist" folder
|
||||
|
||||
> **Note for Windows users**
|
||||
>
|
||||
> Setting up the examples involves creating some [symbolic links](https://en.wikipedia.org/wiki/Symbolic_link) (see [here](./tools/examples/README.md#symlinked-node_modules) for details). On Windows, this requires to either have [Developer Mode enabled](https://blogs.windows.com/windowsdeveloper/2016/12/02/symlinks-windows-10) (supported on Windows 10 or newer) or run the setup commands as administrator.
|
||||
>
|
||||
> The affected commands are:
|
||||
> - `yarn setup` / `yarn setup-*`
|
||||
> - `yarn build` / `yarn build-*`
|
||||
> - `yarn boilerplate:add`
|
||||
> - `yarn example-e2e --setup`
|
||||
|
||||
## Using ServiceWorker locally
|
||||
|
||||
|
21
aio/content/examples/accessibility/e2e/src/app.e2e-spec.ts
Normal file
21
aio/content/examples/accessibility/e2e/src/app.e2e-spec.ts
Normal file
@ -0,0 +1,21 @@
|
||||
'use strict'; // necessary for es6 output in node
|
||||
|
||||
import { browser, element, by } from 'protractor';
|
||||
|
||||
describe('Accessibility example e2e tests', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
browser.get('');
|
||||
});
|
||||
|
||||
it('should display Accessibility Example', function () {
|
||||
expect(element(by.css('h1')).getText()).toEqual('Accessibility Example');
|
||||
});
|
||||
|
||||
it('should take a number and change progressbar width', function () {
|
||||
element(by.css('input')).sendKeys('16');
|
||||
expect(element(by.css('input')).getAttribute('value')).toEqual('016');
|
||||
expect(element(by.css('app-example-progressbar div')).getCssValue('width')).toBe('48px');
|
||||
});
|
||||
|
||||
});
|
0
aio/content/examples/accessibility/src/app/app.component.css
Executable file
0
aio/content/examples/accessibility/src/app/app.component.css
Executable file
13
aio/content/examples/accessibility/src/app/app.component.html
Executable file
13
aio/content/examples/accessibility/src/app/app.component.html
Executable file
@ -0,0 +1,13 @@
|
||||
<h1>Accessibility Example</h1>
|
||||
<!-- #docregion template -->
|
||||
<label>
|
||||
Enter an example progress value
|
||||
<input type="number" min="0" max="100"
|
||||
[value]="progress" (input)="progress = $event.target.value">
|
||||
</label>
|
||||
|
||||
<!-- The user of the progressbar sets an aria-label to communicate what the progress means. -->
|
||||
<app-example-progressbar [value]="progress" aria-label="Example of a progress bar">
|
||||
</app-example-progressbar>
|
||||
<!-- #enddocregion template -->
|
||||
|
10
aio/content/examples/accessibility/src/app/app.component.ts
Executable file
10
aio/content/examples/accessibility/src/app/app.component.ts
Executable file
@ -0,0 +1,10 @@
|
||||
import { Component } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'app-root',
|
||||
templateUrl: './app.component.html',
|
||||
styleUrls: [ './app.component.css' ]
|
||||
})
|
||||
export class AppComponent {
|
||||
progress = 0;
|
||||
}
|
12
aio/content/examples/accessibility/src/app/app.module.ts
Executable file
12
aio/content/examples/accessibility/src/app/app.module.ts
Executable file
@ -0,0 +1,12 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { BrowserModule } from '@angular/platform-browser';
|
||||
|
||||
import { AppComponent } from './app.component';
|
||||
import { ExampleProgressbarComponent } from './progress-bar.component';
|
||||
|
||||
@NgModule({
|
||||
imports: [ BrowserModule ],
|
||||
declarations: [ AppComponent, ExampleProgressbarComponent ],
|
||||
bootstrap: [ AppComponent ]
|
||||
})
|
||||
export class AppModule { }
|
12
aio/content/examples/accessibility/src/app/progress-bar.component.css
Executable file
12
aio/content/examples/accessibility/src/app/progress-bar.component.css
Executable file
@ -0,0 +1,12 @@
|
||||
:host {
|
||||
display: block;
|
||||
width: 300px;
|
||||
height: 25px;
|
||||
border: 1px solid black;
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
.bar {
|
||||
background: blue;
|
||||
height: 100%;
|
||||
}
|
28
aio/content/examples/accessibility/src/app/progress-bar.component.ts
Executable file
28
aio/content/examples/accessibility/src/app/progress-bar.component.ts
Executable file
@ -0,0 +1,28 @@
|
||||
// #docregion progressbar-component
|
||||
import { Component, Input } from '@angular/core';
|
||||
|
||||
/**
|
||||
* Example progressbar component.
|
||||
*/
|
||||
@Component({
|
||||
selector: 'app-example-progressbar',
|
||||
template: `<div class="bar" [style.width.%]="value"></div>`,
|
||||
styleUrls: ['./progress-bar.component.css'],
|
||||
host: {
|
||||
// Sets the role for this component to "progressbar"
|
||||
role: 'progressbar',
|
||||
|
||||
// Sets the minimum and maximum values for the progressbar role.
|
||||
'aria-valuemin': '0',
|
||||
'aria-valuemax': '100',
|
||||
|
||||
// Binding that updates the current value of the progressbar.
|
||||
'[attr.aria-valuenow]': 'value',
|
||||
}
|
||||
})
|
||||
export class ExampleProgressbarComponent {
|
||||
/** Current value of the progressbar. */
|
||||
@Input() value = 0;
|
||||
}
|
||||
|
||||
// #enddocregion progressbar-component
|
14
aio/content/examples/accessibility/src/index.html
Normal file
14
aio/content/examples/accessibility/src/index.html
Normal file
@ -0,0 +1,14 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Accessibility Example</title>
|
||||
<base href="/">
|
||||
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="icon" type="image/x-icon" href="favicon.ico">
|
||||
</head>
|
||||
<body>
|
||||
<app-root>Loading...</app-root>
|
||||
</body>
|
||||
</html>
|
11
aio/content/examples/accessibility/src/main.ts
Normal file
11
aio/content/examples/accessibility/src/main.ts
Normal file
@ -0,0 +1,11 @@
|
||||
import { enableProdMode } from '@angular/core';
|
||||
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
|
||||
|
||||
import { AppModule } from './app/app.module';
|
||||
import { environment } from './environments/environment';
|
||||
|
||||
if (environment.production) {
|
||||
enableProdMode();
|
||||
}
|
||||
|
||||
platformBrowserDynamic().bootstrapModule(AppModule);
|
10
aio/content/examples/accessibility/stackblitz.json
Normal file
10
aio/content/examples/accessibility/stackblitz.json
Normal file
@ -0,0 +1,10 @@
|
||||
{
|
||||
"description": "Accessibility",
|
||||
"files": [
|
||||
"!**/*.d.ts",
|
||||
"!**/*.js",
|
||||
"!**/*.[1,2].*"
|
||||
],
|
||||
"file": "src/app/app.component.ts",
|
||||
"tags": ["Accessibility"]
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
// #docplaster
|
||||
// #docregion imports
|
||||
import { Component } from '@angular/core';
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { CartService } from '../cart.service';
|
||||
// #enddocregion imports
|
||||
|
||||
@ -10,12 +10,14 @@ import { CartService } from '../cart.service';
|
||||
styleUrls: ['./cart.component.css']
|
||||
})
|
||||
// #docregion props-services, submit
|
||||
export class CartComponent {
|
||||
export class CartComponent implements OnInit {
|
||||
items;
|
||||
|
||||
constructor(
|
||||
private cartService: CartService
|
||||
) {
|
||||
) { }
|
||||
|
||||
ngOnInit() {
|
||||
this.items = this.cartService.getItems();
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
// #docplaster
|
||||
// #docregion imports
|
||||
import { Component } from '@angular/core';
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
|
||||
import { CartService } from '../cart.service';
|
||||
// #enddocregion
|
||||
@ -11,7 +11,7 @@ import { CartService } from '../cart.service';
|
||||
styleUrls: ['./shipping.component.css']
|
||||
})
|
||||
// #docregion props, ctor
|
||||
export class ShippingComponent {
|
||||
export class ShippingComponent implements OnInit {
|
||||
shippingCosts;
|
||||
// #enddocregion props
|
||||
|
||||
@ -19,10 +19,12 @@ export class ShippingComponent {
|
||||
constructor(
|
||||
private cartService: CartService
|
||||
) {
|
||||
// #enddocregion inject-cart-service
|
||||
this.shippingCosts = this.cartService.getShippingPrices();
|
||||
// #docregion inject-cart-service
|
||||
}
|
||||
// #enddocregion inject-cart-service
|
||||
|
||||
ngOnInit() {
|
||||
this.shippingCosts = this.cartService.getShippingPrices();
|
||||
}
|
||||
|
||||
// #docregion props
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
<h1>HTTP Sample</h1>
|
||||
<div>
|
||||
<input type="checkbox" id="heroes" [checked]="toggleHeroes" (click)="toggleHeroes()">
|
||||
<input type="checkbox" id="heroes" [checked]="showHeroes" (click)="toggleHeroes()">
|
||||
<label for="heroes">Heroes</label>
|
||||
|
||||
<input type="checkbox" id="config" [checked]="showConfig" (click)="toggleConfig()">
|
||||
|
@ -12,7 +12,7 @@ if (environment.production) {
|
||||
// use the require method provided by webpack
|
||||
declare const require;
|
||||
// we use the webpack raw-loader to return the content as a string
|
||||
const translations = require(`raw-loader!./locale/messages.fr.xlf`);
|
||||
const translations = require('raw-loader!./locale/messages.fr.xlf').default;
|
||||
|
||||
platformBrowserDynamic().bootstrapModule(AppModule, {
|
||||
providers: [
|
||||
|
@ -13,3 +13,19 @@ export class AppComponent {
|
||||
constructor(public flower: FlowerService, public animal: AnimalService) {}
|
||||
}
|
||||
// #enddocregion inject-animal-service
|
||||
|
||||
// When using @Host() together with @SkipSelf() in
|
||||
// child.component.ts for the AnimalService, add the
|
||||
// following viewProviders array to the @Component metadata:
|
||||
|
||||
// viewProviders: [{ provide: AnimalService, useValue: { emoji: '🦔' } }]
|
||||
|
||||
// So, the entire @ChildComponent() decorator and its
|
||||
// metadata should be as follows:
|
||||
|
||||
// @Component({
|
||||
// selector: 'app-root',
|
||||
// templateUrl: './app.component.html',
|
||||
// styleUrls: [ './app.component.css' ],
|
||||
// viewProviders: [{ provide: AnimalService, useValue: { emoji: '🦔' } }]
|
||||
// })
|
||||
|
@ -12,7 +12,7 @@ import { Directive, Input, TemplateRef, ViewContainerRef } from '@angular/core';
|
||||
* then the templated elements are removed removed from the DOM,
|
||||
* the templated elements are (re)inserted into the DOM.
|
||||
*
|
||||
* <div *ngUnless="errorCount" class="success">
|
||||
* <div *appUnless="errorCount" class="success">
|
||||
* Congrats! Everything is great!
|
||||
* </div>
|
||||
*
|
||||
|
@ -84,48 +84,15 @@ The following example shows how to make a simple progress bar accessible by usin
|
||||
|
||||
* The component defines an accessibility-enabled element with both the standard HTML attribute `role`, and ARIA attributes. The ARIA attribute `aria-valuenow` is bound to the user's input.
|
||||
|
||||
```ts
|
||||
import { Component, Input } from '@angular/core';
|
||||
/**
|
||||
* Example progressbar component.
|
||||
*/
|
||||
@Component({
|
||||
selector: 'example-progressbar',
|
||||
template: `<div class="bar" [style.width.%]="value"></div>`,
|
||||
styleUrls: ['./progress-bar.css'],
|
||||
host: {
|
||||
// Sets the role for this component to "progressbar"
|
||||
role: 'progressbar',
|
||||
<code-example path="accessibility/src/app/progress-bar.component.ts" header="src/app/progress-bar.component.ts" region="progressbar-component"></code-example>
|
||||
|
||||
// Sets the minimum and maximum values for the progressbar role.
|
||||
'aria-valuemin': '0',
|
||||
'aria-valuemax': '0',
|
||||
|
||||
// Binding that updates the current value of the progressbar.
|
||||
'[attr.aria-valuenow]': 'value',
|
||||
}
|
||||
})
|
||||
export class ExampleProgressbar {
|
||||
/** Current value of the progressbar. */
|
||||
@Input() value: number = 0;
|
||||
}
|
||||
```
|
||||
|
||||
* In the template, the `aria-label` attribute ensures that the control is accessible to screen readers.
|
||||
|
||||
```html
|
||||
<label>
|
||||
Enter an example progress value
|
||||
<input type="number" min="0" max="100"
|
||||
[value]="progress" (input)="progress = $event.target.value">
|
||||
</label>
|
||||
<code-example path="accessibility/src/app/app.component.html" header="src/app/app.component.html" region="template"></code-example>
|
||||
|
||||
<!-- The user of the progressbar sets an aria-label to communicate what the progress means. -->
|
||||
<example-progressbar [value]="progress" aria-label="Example of a progress bar">
|
||||
</example-progressbar>
|
||||
```
|
||||
|
||||
[See the full example in StackBlitz](https://stackblitz.com/edit/angular-kn5jdi?file=src%2Fapp%2Fapp.component.html).
|
||||
To see the progress bar in a working example app, refer to the <live-example></live-example>.
|
||||
|
||||
## Routing and focus management
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
# Angular compiler options
|
||||
|
||||
When you use [AOT compilation](guide/aot-compiler), you can control how your application is compiled by specifying *template* compiler options in the `tsconfig.json` [TypeScript configuration file](guide/typescript-configuration).
|
||||
When you use [AoT compilation](guide/aot-compiler), you can control how your application is compiled by specifying *template* compiler options in the `tsconfig.json` [TypeScript configuration file](guide/typescript-configuration).
|
||||
|
||||
The template options object, `angularCompilerOptions`, is a sibling to the `compilerOptions` object that supplies standard options to the TypeScript compiler.
|
||||
|
||||
@ -17,7 +17,38 @@ The template options object, `angularCompilerOptions`, is a sibling to the `comp
|
||||
}
|
||||
}
|
||||
```
|
||||
This page describes the available Angular template compiler options.
|
||||
|
||||
{@a tsconfig-extends}
|
||||
## Configuration inheritance with extends
|
||||
|
||||
Like the TypeScript compiler, The Angular AoT compiler also supports `extends` in the `angularCompilerOptions` section of the TypeScript configuration file, `tsconfig.json`.
|
||||
The `extends` property is at the top level, parallel to `compilerOptions` and `angularCompilerOptions`.
|
||||
|
||||
A TypeScript configuration can inherit settings from another file using the `extends` property.
|
||||
The configuration options from the base file are loaded first, then overridden by those in the inheriting `tsconfig` file.
|
||||
|
||||
For example:
|
||||
|
||||
```json
|
||||
{
|
||||
"extends": "../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"experimentalDecorators": true,
|
||||
...
|
||||
},
|
||||
"angularCompilerOptions": {
|
||||
"fullTemplateTypeCheck": true,
|
||||
"preserveWhitespaces": true,
|
||||
...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
For more informaton, see the [TypeScript Handbook](https://www.typescriptlang.org/docs/handbook/tsconfig-json.html).
|
||||
|
||||
## Template options
|
||||
|
||||
The following options are available for configuring the AoT template compiler.
|
||||
|
||||
### `allowEmptyCodegenFiles`
|
||||
|
||||
@ -29,7 +60,7 @@ Modifies how Angular-specific annotations are emitted to improve tree-shaking. N
|
||||
|
||||
* By default, the compiler replaces decorators with a static field in the class, which allows advanced tree-shakers like [Closure compiler](https://github.com/google/closure-compiler) to remove unused classes.
|
||||
|
||||
* The `decorators` value leaves the decorators in place, which makes compilation faster. TypeScript emits calls to the` __decorate` helper. Use `--emitDecoratorMetadata` for runtime reflection (but note taht the resulting code will not properly tree-shake.
|
||||
* The `decorators` value leaves the decorators in place, which makes compilation faster. TypeScript emits calls to the` __decorate` helper. Use `--emitDecoratorMetadata` for runtime reflection (but note that the resulting code will not properly tree-shake.
|
||||
|
||||
### `annotateForClosureCompiler`
|
||||
|
||||
@ -57,7 +88,7 @@ When enabled, the `.js` output of `ngc` does not include any lazy-loaded templat
|
||||
|
||||
### `enableLegacyTemplate`
|
||||
|
||||
When true, enables use of the `<template>` element, which was deprecated in Angular 4.0, in favor of `<ng-template>` (to avoid colliding with the DOM's element of the same name). Default is false. Might be required by some third-party Angular libraries. |
|
||||
When true, enables use of the `<template>` element, which was deprecated in Angular 4.0, in favor of `<ng-template>` (to avoid colliding with the DOM's element of the same name). Default is false. Might be required by some third-party Angular libraries.
|
||||
|
||||
### `flatModuleId`
|
||||
|
||||
|
@ -260,7 +260,7 @@ What it does
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td><code>state()</code></td>
|
||||
<td><code><a href="api/animations/state" class="code-anchor">state()</a></code></td>
|
||||
<td>Creates a named set of CSS styles that should be applied on successful transition to a given state. The state can then be referenced by name within other animation functions.</td>
|
||||
</tr>
|
||||
|
||||
@ -280,7 +280,7 @@ What it does
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td><code>group()</code></td>
|
||||
<td><code><a href="api/animations/group" class="code-anchor">group()</a></code></td>
|
||||
<td>Specifies a group of animation steps (<em>inner animations</em>) to be run in parallel. Animation continues only after all inner animation steps have completed. Used within <code>sequence()</code> or <code>transition().</code></td>
|
||||
</tr>
|
||||
|
||||
|
File diff suppressed because it is too large
Load Diff
540
aio/content/guide/aot-metadata-errors.md
Normal file
540
aio/content/guide/aot-metadata-errors.md
Normal file
@ -0,0 +1,540 @@
|
||||
# AoT metadata errors
|
||||
|
||||
The following are metadata errors you may encounter, with explanations and suggested corrections.
|
||||
|
||||
[Expression form not supported](#expression-form-not-supported)<br>
|
||||
[Reference to a local (non-exported) symbol](#reference-to-a-local-symbol)<br>
|
||||
[Only initialized variables and constants](#only-initialized-variables)<br>
|
||||
[Reference to a non-exported class](#reference-to-a-non-exported-class)<br>
|
||||
[Reference to a non-exported function](#reference-to-a-non-exported-function)<br>
|
||||
[Function calls are not supported](#function-calls-not-supported)<br>
|
||||
[Destructured variable or constant not supported](#destructured-variable-not-supported)<br>
|
||||
[Could not resolve type](#could-not-resolve-type)<br>
|
||||
[Name expected](#name-expected)<br>
|
||||
[Unsupported enum member name](#unsupported-enum-member-name)<br>
|
||||
[Tagged template expressions are not supported](#tagged-template-expressions-not-supported)<br>
|
||||
[Symbol reference expected](#symbol-reference-expected)<br>
|
||||
|
||||
<hr>
|
||||
|
||||
{@a expression-form-not-supported}
|
||||
## Expression form not supported
|
||||
|
||||
<div class="alert is-helpful">
|
||||
|
||||
*The compiler encountered an expression it didn't understand while evaluating Angular metadata.*
|
||||
|
||||
</div>
|
||||
|
||||
Language features outside of the compiler's [restricted expression syntax](guide/aot-compiler#expression-syntax)
|
||||
can produce this error, as seen in the following example:
|
||||
|
||||
```ts
|
||||
// ERROR
|
||||
export class Fooish { ... }
|
||||
...
|
||||
const prop = typeof Fooish; // typeof is not valid in metadata
|
||||
...
|
||||
// bracket notation is not valid in metadata
|
||||
{ provide: 'token', useValue: { [prop]: 'value' } };
|
||||
...
|
||||
```
|
||||
|
||||
You can use `typeof` and bracket notation in normal application code.
|
||||
You just can't use those features within expressions that define Angular metadata.
|
||||
|
||||
Avoid this error by sticking to the compiler's [restricted expression syntax](guide/aot-compiler#expression-syntax)
|
||||
when writing Angular metadata
|
||||
and be wary of new or unusual TypeScript features.
|
||||
|
||||
<hr>
|
||||
|
||||
{@a reference-to-a-local-symbol}
|
||||
## Reference to a local (non-exported) symbol
|
||||
|
||||
<div class="alert is-helpful">
|
||||
|
||||
_Reference to a local (non-exported) symbol 'symbol name'. Consider exporting the symbol._
|
||||
|
||||
</div>
|
||||
|
||||
The compiler encountered a referenced to a locally defined symbol that either wasn't exported or wasn't initialized.
|
||||
|
||||
Here's a `provider` example of the problem.
|
||||
|
||||
```ts
|
||||
// ERROR
|
||||
let foo: number; // neither exported nor initialized
|
||||
|
||||
@Component({
|
||||
selector: 'my-component',
|
||||
template: ... ,
|
||||
providers: [
|
||||
{ provide: Foo, useValue: foo }
|
||||
]
|
||||
})
|
||||
export class MyComponent {}
|
||||
```
|
||||
The compiler generates the component factory, which includes the `useValue` provider code, in a separate module. _That_ factory module can't reach back to _this_ source module to access the local (non-exported) `foo` variable.
|
||||
|
||||
You could fix the problem by initializing `foo`.
|
||||
|
||||
```ts
|
||||
let foo = 42; // initialized
|
||||
```
|
||||
|
||||
The compiler will [fold](guide/aot-compiler#code-folding) the expression into the provider as if you had written this.
|
||||
|
||||
```ts
|
||||
providers: [
|
||||
{ provide: Foo, useValue: 42 }
|
||||
]
|
||||
```
|
||||
|
||||
Alternatively, you can fix it by exporting `foo` with the expectation that `foo` will be assigned at runtime when you actually know its value.
|
||||
|
||||
```ts
|
||||
// CORRECTED
|
||||
export let foo: number; // exported
|
||||
|
||||
@Component({
|
||||
selector: 'my-component',
|
||||
template: ... ,
|
||||
providers: [
|
||||
{ provide: Foo, useValue: foo }
|
||||
]
|
||||
})
|
||||
export class MyComponent {}
|
||||
```
|
||||
|
||||
Adding `export` often works for variables referenced in metadata such as `providers` and `animations` because the compiler can generate _references_ to the exported variables in these expressions. It doesn't need the _values_ of those variables.
|
||||
|
||||
Adding `export` doesn't work when the compiler needs the _actual value_
|
||||
in order to generate code.
|
||||
For example, it doesn't work for the `template` property.
|
||||
|
||||
```ts
|
||||
// ERROR
|
||||
export let someTemplate: string; // exported but not initialized
|
||||
|
||||
@Component({
|
||||
selector: 'my-component',
|
||||
template: someTemplate
|
||||
})
|
||||
export class MyComponent {}
|
||||
```
|
||||
|
||||
The compiler needs the value of the `template` property _right now_ to generate the component factory.
|
||||
The variable reference alone is insufficient.
|
||||
Prefixing the declaration with `export` merely produces a new error, "[`Only initialized variables and constants can be referenced`](#only-initialized-variables)".
|
||||
|
||||
<hr>
|
||||
|
||||
{@a only-initialized-variables}
|
||||
## Only initialized variables and constants
|
||||
|
||||
<div class="alert is-helpful">
|
||||
|
||||
_Only initialized variables and constants can be referenced because the value of this variable is needed by the template compiler._
|
||||
|
||||
</div>
|
||||
|
||||
The compiler found a reference to an exported variable or static field that wasn't initialized.
|
||||
It needs the value of that variable to generate code.
|
||||
|
||||
The following example tries to set the component's `template` property to the value of
|
||||
the exported `someTemplate` variable which is declared but _unassigned_.
|
||||
|
||||
```ts
|
||||
// ERROR
|
||||
export let someTemplate: string;
|
||||
|
||||
@Component({
|
||||
selector: 'my-component',
|
||||
template: someTemplate
|
||||
})
|
||||
export class MyComponent {}
|
||||
```
|
||||
|
||||
You'd also get this error if you imported `someTemplate` from some other module and neglected to initialize it there.
|
||||
|
||||
```ts
|
||||
// ERROR - not initialized there either
|
||||
import { someTemplate } from './config';
|
||||
|
||||
@Component({
|
||||
selector: 'my-component',
|
||||
template: someTemplate
|
||||
})
|
||||
export class MyComponent {}
|
||||
```
|
||||
|
||||
The compiler cannot wait until runtime to get the template information.
|
||||
It must statically derive the value of the `someTemplate` variable from the source code
|
||||
so that it can generate the component factory, which includes
|
||||
instructions for building the element based on the template.
|
||||
|
||||
To correct this error, provide the initial value of the variable in an initializer clause _on the same line_.
|
||||
|
||||
```ts
|
||||
// CORRECTED
|
||||
export let someTemplate = '<h1>Greetings from Angular</h1>';
|
||||
|
||||
@Component({
|
||||
selector: 'my-component',
|
||||
template: someTemplate
|
||||
})
|
||||
export class MyComponent {}
|
||||
```
|
||||
|
||||
<hr>
|
||||
|
||||
{@a reference-to-a-non-exported-class}
|
||||
## Reference to a non-exported class
|
||||
|
||||
<div class="alert is-helpful">
|
||||
|
||||
_Reference to a non-exported class <class name>. Consider exporting the class._
|
||||
|
||||
</div>
|
||||
|
||||
Metadata referenced a class that wasn't exported.
|
||||
|
||||
For example, you may have defined a class and used it as an injection token in a providers array
|
||||
but neglected to export that class.
|
||||
|
||||
```ts
|
||||
// ERROR
|
||||
abstract class MyStrategy { }
|
||||
|
||||
...
|
||||
providers: [
|
||||
{ provide: MyStrategy, useValue: ... }
|
||||
]
|
||||
...
|
||||
```
|
||||
|
||||
Angular generates a class factory in a separate module and that
|
||||
factory [can only access exported classes](guide/aot-compiler#exported-symbols).
|
||||
To correct this error, export the referenced class.
|
||||
|
||||
```ts
|
||||
// CORRECTED
|
||||
export abstract class MyStrategy { }
|
||||
|
||||
...
|
||||
providers: [
|
||||
{ provide: MyStrategy, useValue: ... }
|
||||
]
|
||||
...
|
||||
```
|
||||
<hr>
|
||||
|
||||
{@a reference-to-a-non-exported-function}
|
||||
## Reference to a non-exported function
|
||||
|
||||
<div class="alert is-helpful">
|
||||
|
||||
*Metadata referenced a function that wasn't exported.*
|
||||
|
||||
</div>
|
||||
|
||||
For example, you may have set a providers `useFactory` property to a locally defined function that you neglected to export.
|
||||
|
||||
```ts
|
||||
// ERROR
|
||||
function myStrategy() { ... }
|
||||
|
||||
...
|
||||
providers: [
|
||||
{ provide: MyStrategy, useFactory: myStrategy }
|
||||
]
|
||||
...
|
||||
```
|
||||
|
||||
Angular generates a class factory in a separate module and that
|
||||
factory [can only access exported functions](guide/aot-compiler#exported-symbols).
|
||||
To correct this error, export the function.
|
||||
|
||||
```ts
|
||||
// CORRECTED
|
||||
export function myStrategy() { ... }
|
||||
|
||||
...
|
||||
providers: [
|
||||
{ provide: MyStrategy, useFactory: myStrategy }
|
||||
]
|
||||
...
|
||||
```
|
||||
<hr>
|
||||
|
||||
{@a function-calls-not-supported}
|
||||
## Function calls are not supported
|
||||
|
||||
<div class="alert is-helpful">
|
||||
|
||||
_Function calls are not supported. Consider replacing the function or lambda with a reference to an exported function._
|
||||
|
||||
</div>
|
||||
|
||||
The compiler does not currently support [function expressions or lambda functions](guide/aot-compiler#function-expression).
|
||||
For example, you cannot set a provider's `useFactory` to an anonymous function or arrow function like this.
|
||||
|
||||
```ts
|
||||
// ERROR
|
||||
...
|
||||
providers: [
|
||||
{ provide: MyStrategy, useFactory: function() { ... } },
|
||||
{ provide: OtherStrategy, useFactory: () => { ... } }
|
||||
]
|
||||
...
|
||||
```
|
||||
You also get this error if you call a function or method in a provider's `useValue`.
|
||||
|
||||
```ts
|
||||
// ERROR
|
||||
import { calculateValue } from './utilities';
|
||||
|
||||
...
|
||||
providers: [
|
||||
{ provide: SomeValue, useValue: calculateValue() }
|
||||
]
|
||||
...
|
||||
```
|
||||
|
||||
To correct this error, export a function from the module and refer to the function in a `useFactory` provider instead.
|
||||
|
||||
```ts
|
||||
// CORRECTED
|
||||
import { calculateValue } from './utilities';
|
||||
|
||||
export function myStrategy() { ... }
|
||||
export function otherStrategy() { ... }
|
||||
export function someValueFactory() {
|
||||
return calculateValue();
|
||||
}
|
||||
...
|
||||
providers: [
|
||||
{ provide: MyStrategy, useFactory: myStrategy },
|
||||
{ provide: OtherStrategy, useFactory: otherStrategy },
|
||||
{ provide: SomeValue, useFactory: someValueFactory }
|
||||
]
|
||||
...
|
||||
```
|
||||
|
||||
<hr>
|
||||
|
||||
{@a destructured-variable-not-supported}
|
||||
## Destructured variable or constant not supported
|
||||
|
||||
<div class="alert is-helpful">
|
||||
|
||||
_Referencing an exported destructured variable or constant is not supported by the template compiler. Consider simplifying this to avoid destructuring._
|
||||
|
||||
</div>
|
||||
|
||||
The compiler does not support references to variables assigned by [destructuring](https://www.typescriptlang.org/docs/handbook/variable-declarations.html#destructuring).
|
||||
|
||||
For example, you cannot write something like this:
|
||||
|
||||
```ts
|
||||
// ERROR
|
||||
import { configuration } from './configuration';
|
||||
|
||||
// destructured assignment to foo and bar
|
||||
const {foo, bar} = configuration;
|
||||
...
|
||||
providers: [
|
||||
{provide: Foo, useValue: foo},
|
||||
{provide: Bar, useValue: bar},
|
||||
]
|
||||
...
|
||||
```
|
||||
|
||||
To correct this error, refer to non-destructured values.
|
||||
|
||||
```ts
|
||||
// CORRECTED
|
||||
import { configuration } from './configuration';
|
||||
...
|
||||
providers: [
|
||||
{provide: Foo, useValue: configuration.foo},
|
||||
{provide: Bar, useValue: configuration.bar},
|
||||
]
|
||||
...
|
||||
```
|
||||
|
||||
<hr>
|
||||
|
||||
{@a could-not-resolve-type}
|
||||
## Could not resolve type
|
||||
|
||||
<div class="alert is-helpful">
|
||||
|
||||
*The compiler encountered a type and can't determine which module exports that type.*
|
||||
|
||||
</div>
|
||||
|
||||
This can happen if you refer to an ambient type.
|
||||
For example, the `Window` type is an ambient type declared in the global `.d.ts` file.
|
||||
|
||||
You'll get an error if you reference it in the component constructor,
|
||||
which the compiler must statically analyze.
|
||||
|
||||
```ts
|
||||
// ERROR
|
||||
@Component({ })
|
||||
export class MyComponent {
|
||||
constructor (private win: Window) { ... }
|
||||
}
|
||||
```
|
||||
TypeScript understands ambient types so you don't import them.
|
||||
The Angular compiler does not understand a type that you neglect to export or import.
|
||||
|
||||
In this case, the compiler doesn't understand how to inject something with the `Window` token.
|
||||
|
||||
Do not refer to ambient types in metadata expressions.
|
||||
|
||||
If you must inject an instance of an ambient type,
|
||||
you can finesse the problem in four steps:
|
||||
|
||||
1. Create an injection token for an instance of the ambient type.
|
||||
1. Create a factory function that returns that instance.
|
||||
1. Add a `useFactory` provider with that factory function.
|
||||
1. Use `@Inject` to inject the instance.
|
||||
|
||||
Here's an illustrative example.
|
||||
|
||||
```ts
|
||||
// CORRECTED
|
||||
import { Inject } from '@angular/core';
|
||||
|
||||
export const WINDOW = new InjectionToken('Window');
|
||||
export function _window() { return window; }
|
||||
|
||||
@Component({
|
||||
...
|
||||
providers: [
|
||||
{ provide: WINDOW, useFactory: _window }
|
||||
]
|
||||
})
|
||||
export class MyComponent {
|
||||
constructor (@Inject(WINDOW) private win: Window) { ... }
|
||||
}
|
||||
```
|
||||
|
||||
The `Window` type in the constructor is no longer a problem for the compiler because it
|
||||
uses the `@Inject(WINDOW)` to generate the injection code.
|
||||
|
||||
Angular does something similar with the `DOCUMENT` token so you can inject the browser's `document` object (or an abstraction of it, depending upon the platform in which the application runs).
|
||||
|
||||
```ts
|
||||
import { Inject } from '@angular/core';
|
||||
import { DOCUMENT } from '@angular/platform-browser';
|
||||
|
||||
@Component({ ... })
|
||||
export class MyComponent {
|
||||
constructor (@Inject(DOCUMENT) private doc: Document) { ... }
|
||||
}
|
||||
```
|
||||
<hr>
|
||||
|
||||
{@a name-expected}
|
||||
## Name expected
|
||||
|
||||
<div class="alert is-helpful">
|
||||
|
||||
*The compiler expected a name in an expression it was evaluating.*
|
||||
|
||||
</div>
|
||||
|
||||
This can happen if you use a number as a property name as in the following example.
|
||||
|
||||
```ts
|
||||
// ERROR
|
||||
provider: [{ provide: Foo, useValue: { 0: 'test' } }]
|
||||
```
|
||||
|
||||
Change the name of the property to something non-numeric.
|
||||
|
||||
```ts
|
||||
// CORRECTED
|
||||
provider: [{ provide: Foo, useValue: { '0': 'test' } }]
|
||||
```
|
||||
|
||||
<hr>
|
||||
|
||||
{@a unsupported-enum-member-name}
|
||||
## Unsupported enum member name
|
||||
|
||||
<div class="alert is-helpful">
|
||||
|
||||
*Angular couldn't determine the value of the [enum member](https://www.typescriptlang.org/docs/handbook/enums.html) that you referenced in metadata.*
|
||||
|
||||
</div>
|
||||
|
||||
The compiler can understand simple enum values but not complex values such as those derived from computed properties.
|
||||
|
||||
```ts
|
||||
// ERROR
|
||||
enum Colors {
|
||||
Red = 1,
|
||||
White,
|
||||
Blue = "Blue".length // computed
|
||||
}
|
||||
|
||||
...
|
||||
providers: [
|
||||
{ provide: BaseColor, useValue: Colors.White } // ok
|
||||
{ provide: DangerColor, useValue: Colors.Red } // ok
|
||||
{ provide: StrongColor, useValue: Colors.Blue } // bad
|
||||
]
|
||||
...
|
||||
```
|
||||
|
||||
Avoid referring to enums with complicated initializers or computed properties.
|
||||
|
||||
<hr>
|
||||
|
||||
{@a tagged-template-expressions-not-supported}
|
||||
## Tagged template expressions are not supported
|
||||
|
||||
<div class="alert is-helpful">
|
||||
|
||||
_Tagged template expressions are not supported in metadata._
|
||||
|
||||
</div>
|
||||
|
||||
The compiler encountered a JavaScript ES2015 [tagged template expression](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals#Tagged_template_literals) such as the following.
|
||||
|
||||
```ts
|
||||
// ERROR
|
||||
const expression = 'funky';
|
||||
const raw = String.raw`A tagged template ${expression} string`;
|
||||
...
|
||||
template: '<div>' + raw + '</div>'
|
||||
...
|
||||
```
|
||||
[`String.raw()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/raw)
|
||||
is a _tag function_ native to JavaScript ES2015.
|
||||
|
||||
The AoT compiler does not support tagged template expressions; avoid them in metadata expressions.
|
||||
|
||||
<hr>
|
||||
|
||||
{@a symbol-reference-expected}
|
||||
## Symbol reference expected
|
||||
|
||||
<div class="alert is-helpful">
|
||||
|
||||
*The compiler expected a reference to a symbol at the location specified in the error message.*
|
||||
|
||||
</div>
|
||||
|
||||
This error can occur if you use an expression in the `extends` clause of a class.
|
||||
|
||||
<!--
|
||||
|
||||
Chuck: After reviewing your PR comment I'm still at a loss. See [comment there](https://github.com/angular/angular/pull/17712#discussion_r132025495).
|
||||
|
||||
-->
|
@ -44,6 +44,12 @@ After running this command you will notice that the `angular.json` configuration
|
||||
"browserTarget": "my-app:build",
|
||||
"serverTarget": "my-app:server",
|
||||
"route": "shell"
|
||||
},
|
||||
"configurations": {
|
||||
"production": {
|
||||
"browserTarget": "my-app:build:production",
|
||||
"serverTarget": "my-app:server:production"
|
||||
}
|
||||
}
|
||||
}
|
||||
</code-example>
|
||||
@ -56,4 +62,12 @@ Use the CLI to build the `app-shell` target.
|
||||
ng run my-app:app-shell
|
||||
</code-example>
|
||||
|
||||
Or to use the production configuration.
|
||||
|
||||
<code-example language="bash">
|
||||
ng run my-app:app-shell:production
|
||||
</code-example>
|
||||
|
||||
To verify the build output, open `dist/my-app/index.html`. Look for default text `app-shell works!` to show that the app shell route was rendered as part of the output.
|
||||
|
||||
|
||||
|
@ -8,13 +8,7 @@ This page discusses build-specific configuration options for Angular projects.
|
||||
|
||||
You can define different named build configurations for your project, such as *stage* and *production*, with different defaults.
|
||||
|
||||
Each named build configuration can have defaults for any of the options that apply to the various build targets, such as `build`, `serve`, and `test`. The [Angular CLI](cli) `build`, `serve`, and `test` commands can then replace files with appropriate versions for your intended target environment.
|
||||
|
||||
The following figure shows how a project has multiple build targets, which can be executed using the named configurations that you define.
|
||||
|
||||
<figure>
|
||||
<img src="generated/images/guide/build/build-config-targets.gif" alt="build configurations and targets">
|
||||
</figure>
|
||||
Each named configuration can have defaults for any of the options that apply to the various [builder targets](guide/glossary#target), such as `build`, `serve`, and `test`. The [Angular CLI](cli) `build`, `serve`, and `test` commands can then replace files with appropriate versions for your intended target environment.
|
||||
|
||||
### Configure environment-specific defaults
|
||||
|
||||
@ -170,8 +164,9 @@ You can also configure the `serve` command to use the targeted build configurati
|
||||
```
|
||||
|
||||
{@a size-budgets}
|
||||
{@a configure-size-budgets}
|
||||
|
||||
## Configure size budgets
|
||||
## Configuring size budgets
|
||||
|
||||
As applications grow in functionality, they also grow in size.
|
||||
The CLI allows you to set size thresholds in your configuration to ensure that parts of your application stay within size boundaries that you define.
|
||||
@ -296,10 +291,9 @@ Autoprefixer looks for the `browserslist` configuration when it prefixes your CS
|
||||
|
||||
See the [browserslist repo](https://github.com/browserslist/browserslist) for more examples of how to target specific browsers and versions.
|
||||
|
||||
<div class="alert is-helpful">
|
||||
Backward compatibility
|
||||
### Backward compatibility with Lighthouse
|
||||
|
||||
If you want to produce a progressive web app and are using [Lighthouse](https://developers.google.com/web/tools/lighthouse/) to grade the project, add the following browserslist entry to your `package.json` file, in order to eliminate the [old flexbox](https://developers.google.com/web/tools/lighthouse/audits/old-flexbox) prefixes:
|
||||
If you want to produce a progressive web app and are using [Lighthouse](https://developers.google.com/web/tools/lighthouse/) to grade the project, add the following `browserslist` entry to your `package.json` file, in order to eliminate the [old flexbox](https://developers.google.com/web/tools/lighthouse/audits/old-flexbox) prefixes:
|
||||
|
||||
```
|
||||
"browserslist": [
|
||||
@ -309,7 +303,23 @@ If you want to produce a progressive web app and are using [Lighthouse](https://
|
||||
]
|
||||
```
|
||||
|
||||
</div>
|
||||
### Backward compatibility with CSS grid
|
||||
|
||||
CSS grid layout support in Autoprefixer, which was previously on by default, is off by default in Angular 8 and higher.
|
||||
|
||||
To use CSS grid with IE10/11, you must explicitly enable it using the `autoplace` option.
|
||||
To do this, add the following to the top of the global styles file (or within a specific css selector scope):
|
||||
|
||||
```
|
||||
/* autoprefixer grid: autoplace /
|
||||
```
|
||||
or
|
||||
```
|
||||
/ autoprefixer grid: no-autoplace */
|
||||
```
|
||||
|
||||
For more information, see [Autoprefixer documentation](https://autoprefixer.github.io/).
|
||||
|
||||
|
||||
{@a proxy}
|
||||
|
||||
|
@ -8,7 +8,7 @@ When you are ready to deploy your Angular application to a remote server, you ha
|
||||
|
||||
## Simple deployment options
|
||||
|
||||
Before fully deploying your application, you can test the process, build configuration, and deployed behavior by using one of these interim techniques
|
||||
Before fully deploying your application, you can test the process, build configuration, and deployed behavior by using one of these interim techniques.
|
||||
|
||||
### Building and serving from disk
|
||||
|
||||
@ -41,7 +41,7 @@ You will need two terminals to get the live-reload experience.
|
||||
|
||||
<code-example language="none" class="code-shell">
|
||||
|
||||
lite-server --baseDir="dist"
|
||||
lite-server --baseDir="dist/project-name"
|
||||
|
||||
</code-example>
|
||||
|
||||
@ -53,6 +53,35 @@ This method is for development and testing only, and is not a supported or secur
|
||||
|
||||
</div>
|
||||
|
||||
### Automatic deployment with the CLI
|
||||
|
||||
The Angular CLI command `ng deploy` (introduced in version 8.3.0) executes the `deploy` [CLI builder](https://angular.io/guide/cli-builder) associated with your project. A number of third-party builders implement deployment capabilities to different platforms. You can add any of them to your project by running `ng add [package name]`.
|
||||
|
||||
When you add a package with deployment capability, it'll automatically update your workspace configuration (`angular.json` file) with a `deploy` section for the selected project. You can then use the `ng deploy` command to deploy that project.
|
||||
|
||||
For example, the following command automatically deploys a project to Firebase.
|
||||
|
||||
<code-example language="none" class="code-shell">
|
||||
ng add @angular/fire
|
||||
ng deploy
|
||||
</code-example>
|
||||
|
||||
The command is interactive. In this case, you must have or create a Firebase account, and authenticate using that account. The command prompts you to select a Firebase project for deployment
|
||||
|
||||
After the command produces an optimal build of your application (equivalent to `ng deploy --prod`), it'll upload the production assets to Firebase.
|
||||
|
||||
In the table below, you can find a list of packages which implement deployment functionality to different platforms. The `deploy` command for each package may require different command line options. You can read more by following the links associated with the package names below:
|
||||
|
||||
| Deployment to | Package |
|
||||
|---------------------------------------------------------------|--------------------------------------------------------------------------------|
|
||||
| [Firebase hosting](https://firebase.google.com/docs/hosting) | [`@angular/fire`](https://npmjs.org/package/@angular/fire) |
|
||||
| [Azure](https://azure.microsoft.com/en-us/) | [`@azure/ng-deploy`](https://npmjs.org/package/@azure/ng-deploy) |
|
||||
| [Now](https://zeit.co/now) | [`@zeit/ng-deploy`](https://npmjs.org/package/@zeit/ng-deploy) |
|
||||
| [Netlify](https://www.netlify.com/) | [`@netlify-builder/deploy`](https://npmjs.org/package/@netlify-builder/deploy) |
|
||||
| [GitHub pages](https://pages.github.com/) | [`angular-cli-ghpages`](https://npmjs.org/package/angular-cli-ghpages) |
|
||||
|
||||
If you're deploying to a self-managed server or there's no builder for your favorite cloud platform, you can either create a builder that allows you to use the `ng deploy` command, or read through this guide to learn how to manually deploy your app.
|
||||
|
||||
### Basic deployment to a remote server
|
||||
|
||||
For the simplest deployment, create a production build and copy the output directory to a web server.
|
||||
@ -589,14 +618,14 @@ In `angular.json` add two new configuration sections under the `build` and `serv
|
||||
...
|
||||
},
|
||||
"es5": {
|
||||
"browserTarget": "app:build:es5"
|
||||
"browserTarget": "<app-name>:build:es5"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
</code-example>
|
||||
|
||||
You can then run the serve with this configuration.
|
||||
You can then run the `ng serve` command with this configuration. Make sure to replace `<app-name>` (in `"<app-name>:build:es5"`) with the actual name of the app, as it appears under `projects` in `angular.json`. For example, if your app name is `myAngularApp` the config will become `"browserTarget": "myAngularApp:build:es5"`.
|
||||
|
||||
<code-example language="none" class="code-shell">
|
||||
|
||||
@ -651,24 +680,24 @@ Create an [ES5 serve configuration](guide/deployment#configuring-serve-for-es5)
|
||||
|
||||
<code-example language="json">
|
||||
|
||||
"test": {
|
||||
"e2e": {
|
||||
"builder": "@angular-devkit/build-angular:protractor",
|
||||
"options": {
|
||||
...
|
||||
},
|
||||
"configurations": {
|
||||
"production": {
|
||||
...
|
||||
},
|
||||
"production": {
|
||||
...
|
||||
},
|
||||
"es5": {
|
||||
"devServerTarget": "app:serve:es5"
|
||||
"devServerTarget": "<app-name>:serve:es5"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
</code-example>
|
||||
|
||||
You can then run the e2e's with this configuration
|
||||
You can then run the `ng e2e` command with this configuration. Make sure to replace `<app-name>` (in `"<app-name>:serve:es5"`) with the actual name of the app, as it appears under `projects` in `angular.json`. For example, if your app name is `myAngularApp` the config will become `"devServerTarget": "myAngularApp:serve:es5"`.
|
||||
|
||||
<code-example language="none" class="code-shell">
|
||||
|
||||
|
@ -321,7 +321,7 @@ In a typical Angular project, the polyfill is not used in production builds, so
|
||||
{@a static-query-resolution}
|
||||
### `@ViewChild()` / `@ContentChild()` static resolution as the default
|
||||
|
||||
See our [dedicated migration guide for static queries](guide/static-query-migration).
|
||||
See the [dedicated migration guide for static queries](guide/static-query-migration).
|
||||
|
||||
{@a contentchild-input-together}
|
||||
### `@ContentChild()` / `@Input()` used together
|
||||
@ -389,6 +389,18 @@ As of Angular version 8, all `platform-webworker` APIs are deprecated.
|
||||
This includes both packages: `@angular/platform-webworker` and
|
||||
`@angular/platform-webworker-dynamic`.
|
||||
|
||||
|
||||
## Angular version 9 schematics
|
||||
|
||||
{@a renderer-to-renderer2}
|
||||
### Migrating from `Renderer` to `Renderer2`
|
||||
|
||||
See the [dedicated migration guide for Renderer](guide/migration-renderer).
|
||||
|
||||
{@a undecorated-classes}
|
||||
### Migrating undecorated classes
|
||||
See the [dedicated migration guide for undecorated classes](guide/migration-undecorated-classes).
|
||||
|
||||
{@a removed}
|
||||
## Removed APIs
|
||||
|
||||
@ -396,10 +408,10 @@ The following APIs have been removed starting with version 8.0.0:
|
||||
|
||||
| Package | API | Replacement | Notes |
|
||||
| ------- | -------------- | ----------- | ----- |
|
||||
| [`@angular/http`](https://v7.angular.io/api/http) | All exports | [`@angular/common/http`](https://v7.angular.io/api/common/http) | See [below](#http). |
|
||||
[`@angular/http/testing`](https://v7.angular.io/api/http/testing) | All exports | [`@angular/common/http/testing`](https://v7.angular.io/api/common/http/testing) | See [below](#http). |
|
||||
| `@angular/platform-browser` | [`DOCUMENT`](https://v7.angular.io/api/platform-browser/DOCUMENT) | [`DOCUMENT` in `@angular/common`](https://v7.angular.io/api/common/DOCUMENT) | Updating to version 8 with [`ng update`](cli/update) changes this automatically. |
|
||||
| `@angular/core/testing` | [`TestBed.deprecatedOverrideProvider()`](https://v7.angular.io/api/core/testing/TestBed#deprecatedoverrideprovider) | [`TestBed.overrideProvider()`] (api/core/testing/TestBed#overrideprovider) | none |
|
||||
| [`@angular/http`](https://v7.angular.io/api/http) | All exports | [`@angular/common/http`](api/common/http) | See [below](#http). |
|
||||
[`@angular/http/testing`](https://v7.angular.io/api/http/testing) | All exports | [`@angular/common/http/testing`](api/common/http/testing) | See [below](#http). |
|
||||
| `@angular/platform-browser` | [`DOCUMENT`](https://v7.angular.io/api/platform-browser/DOCUMENT) | [`DOCUMENT` in `@angular/common`](api/common/DOCUMENT) | Updating to version 8 with [`ng update`](cli/update) changes this automatically. |
|
||||
| `@angular/core/testing` | [`TestBed.deprecatedOverrideProvider()`](https://v7.angular.io/api/core/testing/TestBed#deprecatedoverrideprovider) | [`TestBed.overrideProvider()`](api/core/testing/TestBed#overrideprovider) | none |
|
||||
| `@angular/core/testing` | [`TestBedStatic.deprecatedOverrideProvider()`](https://v7.angular.io/api/core/testing/TestBedStatic#deprecatedoverrideprovider) | [`TestBedStatic.overrideProvider()`](api/core/testing/TestBedStatic#overrideprovider) | none |
|
||||
|
||||
|
||||
@ -464,100 +476,3 @@ For more information about using `@angular/common/http`, see the [HttpClient gui
|
||||
| --------------------- | ------------------------------------------- |
|
||||
| `MockBackend` | [`HttpTestingController`](/api/common/http/testing/HttpTestingController) |
|
||||
| `MockConnection` | [`HttpTestingController`](/api/common/http/testing/HttpTestingController) |
|
||||
|
||||
## Renderer to Renderer2 migration
|
||||
|
||||
### Migration Overview
|
||||
|
||||
The `Renderer` class has been marked as deprecated since Angular version 4. This section provides guidance on migrating from this deprecated API to the newer `Renderer2` API and what it means for your app.
|
||||
|
||||
### Why should I migrate to Renderer2?
|
||||
|
||||
The deprecated `Renderer` class has been removed in version 9 of Angular, so it's necessary to migrate to a supported API. Using `Renderer2` is the recommended strategy because it supports a similar set of functionality to `Renderer`. The API surface is quite large (with 19 methods), but the schematic should simplify this process for your applications.
|
||||
|
||||
### Is there action required on my end?
|
||||
|
||||
No. The schematic should handle most cases with the exception of `Renderer.animate()` and `Renderer.setDebugInfo()`, which already aren’t supported.
|
||||
|
||||
### What are the `__ngRendererX` methods? Why are they necessary?
|
||||
|
||||
Some methods either don't have exact equivalents in `Renderer2`, or they correspond to more than one expression. For example, both renderers have a `createElement()` method, but they're not equal because a call such as `renderer.createElement(parentNode, namespaceAndName)` in the `Renderer` corresponds to the following block of code in `Renderer2`:
|
||||
|
||||
```ts
|
||||
const [namespace, name] = splitNamespace(namespaceAndName);
|
||||
const el = renderer.createElement(name, namespace);
|
||||
if (parentNode) {
|
||||
renderer.appendChild(parentNode, el);
|
||||
}
|
||||
return el;
|
||||
```
|
||||
|
||||
Migration has to guarantee that the return values of functions and types of variables stay the same. To handle the majority of cases safely, the schematic declares helper functions at the bottom of the user's file. These helpers encapsulate your own logic and keep the replacements inside your code down to a single function call. Here's an example of how the `createElement()` migration looks:
|
||||
|
||||
|
||||
**Before:**
|
||||
|
||||
```ts
|
||||
public createAndAppendElement() {
|
||||
const el = this.renderer.createElement('span');
|
||||
el.textContent = 'hello world';
|
||||
return el;
|
||||
}
|
||||
```
|
||||
|
||||
**After:**
|
||||
|
||||
<code-example>
|
||||
|
||||
public createAndAppendElement() {
|
||||
const el = __ngRendererCreateElement(this.renderer, this.element, 'span');
|
||||
el.textContent = 'hello world';
|
||||
return el;
|
||||
}
|
||||
// Generated code at the bottom of the file
|
||||
__ngRendererCreateElement(renderer: any, parentNode: any, nameAndNamespace: any) {
|
||||
const [namespace, name] = __ngRendererSplitNamespace(namespaceAndName);
|
||||
const el = renderer.createElement(name, namespace);
|
||||
if (parentNode) {
|
||||
renderer.appendChild(parentNode, el);
|
||||
}
|
||||
return el;
|
||||
}
|
||||
__ngRendererSplitNamespace(nameAndNamespace: any) {
|
||||
// returns the split name and namespace
|
||||
}
|
||||
|
||||
</code-example>
|
||||
|
||||
When implementing these helper functions, the schematic ensures that they're only declared once per file and that their names are unique enough that there's a small chance of colliding with pre-existing functions in your code. The schematic also keeps their parameter types as `any` so that it doesn't have to insert extra logic that ensures that their values have the correct type.
|
||||
|
||||
### I’m a library author. Should I run this migration?
|
||||
|
||||
**Library authors should definitely use this migration to move away from the `Renderer`. Otherwise, the libraries won't work with applications built with version 9.**
|
||||
|
||||
|
||||
### Full list of method migrations
|
||||
|
||||
The following table shows all methods that the migration maps from `Renderer` to `Renderer2`.
|
||||
|
||||
|Renderer|Renderer2|
|
||||
|---|---|
|
||||
|`listen(renderElement, name, callback)`|`listen(renderElement, name, callback)`|
|
||||
|`setElementProperty(renderElement, propertyName, propertyValue)`|`setProperty(renderElement, propertyName, propertyValue)`|
|
||||
|`setText(renderNode, text)`|`setValue(renderNode, text)`|
|
||||
|`listenGlobal(target, name, callback)`|`listen(target, name, callback)`|
|
||||
|`selectRootElement(selectorOrNode, debugInfo?)`|`selectRootElement(selectorOrNode)`|
|
||||
|`createElement(parentElement, name, debugInfo?)`|`appendChild(parentElement, createElement(name))`|
|
||||
|`setElementStyle(el, style, value?)`|`value == null ? removeStyle(el, style) : setStyle(el, style, value)`
|
||||
|`setElementAttribute(el, name, value?)`|`attributeValue == null ? removeAttribute(el, name) : setAttribute(el, name, value)`
|
||||
|`createText(parentElement, value, debugInfo?)`|`appendChild(parentElement, createText(value))`|
|
||||
|`createTemplateAnchor(parentElement)`|`appendChild(parentElement, createComment(''))`|
|
||||
|`setElementClass(renderElement, className, isAdd)`|`isAdd ? addClass(renderElement, className) : removeClass(renderElement, className)`|
|
||||
|`projectNodes(parentElement, nodes)`|`for (let i = 0; i < nodes.length; i<ins></ins>) { appendChild(parentElement, nodes<i>); }`|
|
||||
|`attachViewAfter(node, viewRootNodes)`|`const parentElement = parentNode(node); const nextSibling = nextSibling(node); for (let i = 0; i < viewRootNodes.length; i<ins></ins>) { insertBefore(parentElement, viewRootNodes<i>, nextSibling);}`|
|
||||
|`detachView(viewRootNodes)`|`for (let i = 0; i < viewRootNodes.length; i<ins></ins>) {const node = viewRootNodes<i>; const parentElement = parentNode(node); removeChild(parentElement, node);}`|
|
||||
|`destroyView(hostElement, viewAllNodes)`|`for (let i = 0; i < viewAllNodes.length; i<ins></ins>) { destroyNode(viewAllNodes<i>); }`|
|
||||
|`setBindingDebugInfo()`|This function is a noop in `Renderer2`.|
|
||||
|`createViewRoot(hostElement)`|Should be replaced with a reference to `hostElement`|
|
||||
|`invokeElementMethod(renderElement, methodName, args?)`|`(renderElement as any)<methodName>.apply(renderElement, args);`|
|
||||
|`animate(element, startingStyles, keyframes, duration, delay, easing, previousPlayers?)`|Throws an error (same behavior as `Renderer.animate()`)|
|
||||
|
@ -1,9 +1,9 @@
|
||||
# Hierarchical injectors
|
||||
|
||||
Injectors in Angular have rules that you can leverage to
|
||||
achieve the desired visibility in your apps.
|
||||
achieve the desired visibility of injectables in your apps.
|
||||
By understanding these rules, you can determine in which
|
||||
provider you should declare a provider.
|
||||
NgModule, Component or Directive you should declare a provider.
|
||||
|
||||
## Two injector hierarchies
|
||||
|
||||
@ -146,14 +146,6 @@ in the `providers` list of the `AppModule`.
|
||||
|
||||
Angular creates `ElementInjector`s implicitly for each DOM element.
|
||||
|
||||
<div class="alert is-helpful">
|
||||
|
||||
**Note:** Specifically, `ElementInjector` is more nunaced
|
||||
in that they are created _sparsely_. For a mental model
|
||||
though, assume that each DOM element gets an `ElementInjector`.
|
||||
|
||||
</div>
|
||||
|
||||
Providing a service in the `@Component()` decorator using
|
||||
its `providers` or `viewProviders`
|
||||
property configures an `ElementInjector`.
|
||||
@ -171,10 +163,10 @@ export class TestComponent
|
||||
|
||||
<div class="alert is-helpful">
|
||||
|
||||
**Note:** `ModuleInjector` is not a parent of `ElementInjector`.
|
||||
In theory, each element can have a different `ModuleInjector`.
|
||||
Think of it as `ModuleInjector` is plan-b when the
|
||||
`ElementInjector` hierarchy can't resolve it.
|
||||
**Note:** Please see the
|
||||
[resolution rules](guide/hierarchical-dependency-injection#resolution-rules)
|
||||
section to understand the relationship between the `ModuleInjector` tree and
|
||||
the `ElementInjector` tree.
|
||||
|
||||
</div>
|
||||
|
||||
@ -546,7 +538,7 @@ In the example case, the constraints are:
|
||||
- The ending location just happens to be the same as the component
|
||||
itself, because it is the topmost component in this application.
|
||||
|
||||
2. The `MyAppModule` acts as the fallback injector when the
|
||||
2. The `AppModule` acts as the fallback injector when the
|
||||
injection token can't be found in the `ElementInjector`s.
|
||||
|
||||
### Using the `providers` array
|
||||
@ -570,7 +562,7 @@ The next step is to add a binding to the `ChildComponent` template.
|
||||
</code-example>
|
||||
|
||||
To render the new values, add `<app-child>` to the bottom of
|
||||
the`MyAppComponent` template so the view also displays the sunflower:
|
||||
the`AppComponent` template so the view also displays the sunflower:
|
||||
|
||||
```
|
||||
Child Component
|
||||
@ -580,7 +572,7 @@ Emoji from FlowerService: 🌻
|
||||
In the logical tree, this would be represented as follows:
|
||||
|
||||
```
|
||||
<app-root @NgModule(MyAppModule)
|
||||
<app-root @NgModule(AppModule)
|
||||
@Inject(FlowerService) flower=>"🌺">
|
||||
<#VIEW>
|
||||
<p>Emoji from FlowerService: {{flower.emoji}} (🌺)</p>
|
||||
@ -600,7 +592,7 @@ its search at the `<#VIEW>` belonging to `<app-child>` (`<#VIEW>` is
|
||||
included because it is injected from `@Component()`) and ends with
|
||||
`<app-child>`. In this case, the `FlowerService` is resolved in the
|
||||
`<app-child>`'s `providers` array with sunflower 🌻. The injector doesn't
|
||||
have to look any further in the injector tree. It stops as soon as it as it
|
||||
have to look any further in the injector tree. It stops as soon as it
|
||||
finds the `FlowerService` and never sees the 🌺 (red hibiscus).
|
||||
|
||||
|
||||
@ -626,7 +618,7 @@ set it up on your own, skip ahead to [Modifying service availability](guide/hier
|
||||
The example app features a second service, the `AnimalService` to
|
||||
demonstrate `viewProviders`.
|
||||
|
||||
First, create an `AnimalService` with an `emoji` property of whale 🐳:
|
||||
First, create an `AnimalService` with an `emoji` property of 🐳 (whale):
|
||||
|
||||
<code-example path="providers-viewproviders/src/app/animal.service.ts" header="providers-viewproviders/src/app/animal.service.ts" region="animal-service">
|
||||
|
||||
@ -656,7 +648,7 @@ it has a value of 🐶 (puppy).
|
||||
|
||||
</code-example>
|
||||
|
||||
Add bindings to the `ChildComponent` and the `MyAppComponent` templates.
|
||||
Add bindings to the `ChildComponent` and the `AppComponent` templates.
|
||||
In the `ChildComponent` template, add the following binding:
|
||||
|
||||
<code-example path="providers-viewproviders/src/app/child/child.component.html" header="providers-viewproviders/src/app/child.component.html" region="animal-binding">
|
||||
@ -803,7 +795,7 @@ The `AnimalService` in the logical tree would look like this:
|
||||
</app-root>
|
||||
```
|
||||
|
||||
The projected content of `<app-inspector>` sees the whale 🐳, not
|
||||
The projected content of `<app-inspector>` sees the 🐳 (whale), not
|
||||
the 🐶 (puppy), because the
|
||||
🐶 (puppy) is inside the `<app-child>` `<#VIEW>`. The `<app-inspector>` can
|
||||
only see the 🐶 (puppy)
|
||||
@ -848,7 +840,7 @@ Emoji from FlowerService: 🌺
|
||||
In a logical tree, this same idea might look like this:
|
||||
|
||||
```
|
||||
<app-root @NgModule(MyAppModule)
|
||||
<app-root @NgModule(AppModule)
|
||||
@Inject(FlowerService) flower=>"🌺">
|
||||
<#VIEW>
|
||||
<app-child @Provide(FlowerService="🌻")>
|
||||
@ -871,7 +863,7 @@ because `@Host()` limits the upper bound of the search to the
|
||||
`<#VIEW>`. Here's the idea in the logical tree:
|
||||
|
||||
```
|
||||
<app-root @NgModule(MyAppModule)
|
||||
<app-root @NgModule(AppModule)
|
||||
@Inject(FlowerService) flower=>"🌺">
|
||||
<#VIEW> <!-- end search here with null-->
|
||||
<app-child @Provide(FlowerService="🌻")> <!-- start search here -->
|
||||
@ -902,7 +894,7 @@ for the `AnimalService`, it never sees the 🐳 (whale).
|
||||
|
||||
Just as in the `FlowerService` example, if you add `@SkipSelf()`
|
||||
to the constructor for the `AnimalService`, the injector won't
|
||||
look in the current `<app-parent>`'s `ElementInjector` for the
|
||||
look in the current `<app-child>`'s `ElementInjector` for the
|
||||
`AnimalService`.
|
||||
|
||||
```typescript=
|
||||
@ -914,7 +906,7 @@ export class ChildComponent {
|
||||
}
|
||||
```
|
||||
|
||||
Instead, the injector will begin at the `<app-child>`
|
||||
Instead, the injector will begin at the `<app-root>`
|
||||
`ElementInjector`. Remember that the `<app-child>` class
|
||||
provides the `AnimalService` in the `viewProviders` array
|
||||
with a value of 🐶 (puppy):
|
||||
@ -931,7 +923,7 @@ with a value of 🐶 (puppy):
|
||||
The logical tree looks like this with `@SkipSelf()` in `<app-child>`:
|
||||
|
||||
```
|
||||
<app-root @NgModule(MyAppModule)
|
||||
<app-root @NgModule(AppModule)
|
||||
@Inject(AnimalService=>"🐳")>
|
||||
<#VIEW><!-- search begins here -->
|
||||
<app-child>
|
||||
@ -985,9 +977,21 @@ export class ChildComponent {
|
||||
</app-root>
|
||||
```
|
||||
|
||||
However, if you use `@Host()` and `@SkipSelf()` for the `AnimalService`
|
||||
as follows, you'll get 🐶 (puppy) because that's the value in the
|
||||
`<app-child>`. Here are `@Host()` and `@SkipSelf()` in the `<app-child>`
|
||||
Add a `viewProviders` array with a third animal, 🦔 (hedgehog), to the
|
||||
`app.component.ts` `@Component()` metadata:
|
||||
|
||||
```typescript
|
||||
@Component({
|
||||
selector: 'app-root',
|
||||
templateUrl: './app.component.html',
|
||||
styleUrls: [ './app.component.css' ],
|
||||
viewProviders: [{ provide: AnimalService, useValue: { emoji: '🦔' } }]
|
||||
})
|
||||
```
|
||||
|
||||
Next, add `@SkipSelf()` along with `@Host()` to the constructor for the
|
||||
`Animal Service` in `child.component.ts`. Here are `@Host()`
|
||||
and `@SkipSelf()` in the `<app-child>`
|
||||
constructor :
|
||||
|
||||
```ts
|
||||
@ -1007,15 +1011,16 @@ which is in the `providers` array, the result was `null` because
|
||||
`FlowerService` is visible in `<app-child>`, not its `<#VIEW>`.
|
||||
|
||||
However, the `AnimalService`, which is provided in the
|
||||
`ParentComponent` `viewProviders` array, is visible.
|
||||
`AppComponent` `viewProviders` array, is visible.
|
||||
|
||||
The logical tree representation shows why this is:
|
||||
|
||||
```html
|
||||
<app-root @NgModule(MyAppModule)
|
||||
<app-root @NgModule(AppModule)
|
||||
@Inject(AnimalService=>"🐳")>
|
||||
<#VIEW>
|
||||
<!-- ^^@Host()+@SkipSelf() stop here^^ -->
|
||||
<#VIEW @Provide(AnimalService="🦔")
|
||||
@Inject(AnimalService, @SkipSelf, @Host, @Optional)=>"🦔">
|
||||
<!-- ^^@SkipSelf() starts here, @Host() stops here^^ -->
|
||||
<app-child>
|
||||
<#VIEW @Provide(AnimalService="🐶")
|
||||
@Inject(AnimalService, @SkipSelf, @Host, @Optional)=>"🐶">
|
||||
@ -1030,8 +1035,8 @@ The logical tree representation shows why this is:
|
||||
the `AnimalService` at the `<app-root>`, not the `<app-child>`,
|
||||
where the request originates, and `@Host()` stops the search
|
||||
at the `<app-root>` `<#VIEW>`. Since `AnimalService` is
|
||||
provided via the `viewProviders` array, the injector finds 🐶
|
||||
(puppy) in the `<#VIEW>`.
|
||||
provided via the `viewProviders` array, the injector finds 🦔
|
||||
(hedgehog) in the `<#VIEW>`.
|
||||
|
||||
|
||||
{@a component-injectors}
|
||||
|
@ -40,7 +40,7 @@ into an application class as shown in the following `ConfigService` example.
|
||||
header="app/config/config.service.ts (excerpt)">
|
||||
</code-example>
|
||||
|
||||
## Getting JSON data
|
||||
## Requesting data from server
|
||||
|
||||
Applications often request JSON data from the server.
|
||||
For example, the app might need a configuration file on the server, `config.json`,
|
||||
@ -73,45 +73,37 @@ the component **subscribes** to the method's return value.
|
||||
The subscription callback copies the data fields into the component's `config` object,
|
||||
which is data-bound in the component template for display.
|
||||
|
||||
### Why write a service
|
||||
<div class="callout is-helpful">
|
||||
<header>Why write a service?</header>
|
||||
|
||||
This example is so simple that it is tempting to write the `Http.get()` inside the
|
||||
component itself and skip the service.
|
||||
|
||||
However, data access rarely stays this simple.
|
||||
You typically post-process the data, add error handling, and maybe some retry logic to
|
||||
In practice, however, data access rarely stays this simple.
|
||||
You typically need to post-process the data, add error handling, and maybe some retry logic to
|
||||
cope with intermittent connectivity.
|
||||
|
||||
The component quickly becomes cluttered with data access minutia.
|
||||
The component becomes harder to understand, harder to test, and the data access logic can't be re-used or standardized.
|
||||
|
||||
That's why it is a best practice to separate presentation of data from data access by
|
||||
That's why it's a best practice to separate presentation of data from data access by
|
||||
encapsulating data access in a separate service and delegating to that service in
|
||||
the component, even in simple cases like this one.
|
||||
</div>
|
||||
|
||||
### Type-checking the response
|
||||
### Requesting a typed response
|
||||
|
||||
The subscribe callback above requires bracket notation to extract the data values.
|
||||
You can structure your `HttpClient` request to declare the type of the response object, to make consuming the output easier and more obvious.
|
||||
Specifying the response type acts as a type assertion during the compile time.
|
||||
|
||||
<code-example
|
||||
path="http/src/app/config/config.component.ts"
|
||||
region="v1_callback">
|
||||
</code-example>
|
||||
|
||||
You can't write `data.heroesUrl` because TypeScript correctly complains that the `data` object from the service does not have a `heroesUrl` property.
|
||||
|
||||
The `HttpClient.get()` method parsed the JSON server response into the anonymous `Object` type. It doesn't know what the shape of that object is.
|
||||
|
||||
You can tell `HttpClient` the type of the response to make consuming the output easier and more obvious.
|
||||
|
||||
First, define an interface with the correct shape:
|
||||
To specify the response object type, first define an interface with the required properties.
|
||||
(Use an interface rather than a class; a response cannot be automatically converted to an instance of a class.)
|
||||
|
||||
<code-example
|
||||
path="http/src/app/config/config.service.ts"
|
||||
region="config-interface">
|
||||
</code-example>
|
||||
|
||||
Then, specify that interface as the `HttpClient.get()` call's type parameter in the service:
|
||||
Next, specify that interface as the `HttpClient.get()` call's type parameter in the service.
|
||||
|
||||
<code-example
|
||||
path="http/src/app/config/config.service.ts"
|
||||
@ -119,6 +111,12 @@ Then, specify that interface as the `HttpClient.get()` call's type parameter in
|
||||
header="app/config/config.service.ts (getConfig v.2)">
|
||||
</code-example>
|
||||
|
||||
<div class="alert is-helpful">
|
||||
|
||||
When you pass an interface as a type parameter to the `HttpClient.get()` method, use the RxJS `map` operator to transform the response data as needed by the UI. You can then pass the transformed data to the [async pipe](api/common/AsyncPipe).
|
||||
|
||||
</div>
|
||||
|
||||
The callback in the updated component method receives a typed data object, which is
|
||||
easier and safer to consume:
|
||||
|
||||
@ -128,6 +126,24 @@ easier and safer to consume:
|
||||
header="app/config/config.component.ts (showConfig v.2)">
|
||||
</code-example>
|
||||
|
||||
<div class="alert is-important">
|
||||
|
||||
Specifying the response type is a declaration to TypeScript that it should expect your response to be of the given type.
|
||||
This is a build-time check and doesn't guarantee that the server will actually respond with an object of this type. It is up to the server to ensure that the type specified by the server API is returned.
|
||||
|
||||
</div>
|
||||
|
||||
To access properties that are defined in an interface, you must explicitly convert the Object you get from the JSON to the required response type.
|
||||
For example, the following `subscribe` callback receives `data` as an Object, and then type-casts it in order to access the properties.
|
||||
|
||||
<code-example>
|
||||
.subscribe(data => this.config = {
|
||||
heroesUrl: (data as any).heroesUrl,
|
||||
textfile: (data as any).textfile,
|
||||
});
|
||||
</code-example>
|
||||
|
||||
|
||||
### Reading the full response
|
||||
|
||||
The response body doesn't return all the data you may need. Sometimes servers return special headers or status codes to indicate certain conditions that are important to the application workflow.
|
||||
@ -139,7 +155,7 @@ Tell `HttpClient` that you want the full response with the `observe` option:
|
||||
region="getConfigResponse">
|
||||
</code-example>
|
||||
|
||||
Now `HttpClient.get()` returns an `Observable` of typed `HttpResponse` rather than just the JSON data.
|
||||
Now `HttpClient.get()` returns an `Observable` of type `HttpResponse` rather than just the JSON data.
|
||||
|
||||
The component's `showConfigResponse()` method displays the response headers as well as the configuration:
|
||||
|
||||
@ -152,6 +168,54 @@ The component's `showConfigResponse()` method displays the response headers as w
|
||||
|
||||
As you can see, the response object has a `body` property of the correct type.
|
||||
|
||||
### Making a JSONP request
|
||||
|
||||
Apps can use the the `HttpClient` to make [JSONP](https://en.wikipedia.org/wiki/JSONP) requests across domains when the server doesn't support [CORS protocol](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS).
|
||||
|
||||
Angular JSONP requests return an `Observable`.
|
||||
Follow the pattern for subscribing to observables and use the RxJS `map` operator to transform the response before using the [async pipe](api/common/AsyncPipe) to manage the results.
|
||||
|
||||
In Angular, use JSONP by including `HttpClientJsonpModule` in the `NgModule` imports.
|
||||
In the following example, the `searchHeroes()` method uses a JSONP request to query for heroes whose names contain the search term.
|
||||
|
||||
```ts
|
||||
/* GET heroes whose name contains search term */
|
||||
searchHeroes(term: string): Observable {
|
||||
term = term.trim();
|
||||
|
||||
let heroesURL = `${this.heroesURL}?${term}`;
|
||||
return this.http.jsonp(heroesUrl, 'callback').pipe(
|
||||
catchError(this.handleError('searchHeroes', []) // then handle the error
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
This request passes the `heroesURL` as the first parameter and the callback function name as the second parameter.
|
||||
The response is wrapped in the callback function, which takes the observables returned by the JSONP method and pipes them through to the error handler.
|
||||
|
||||
### Requesting non-JSON data
|
||||
|
||||
Not all APIs return JSON data.
|
||||
In this next example, a `DownloaderService` method reads a text file from the server and logs the file contents, before returning those contents to the caller as an `Observable<string>`.
|
||||
|
||||
<code-example
|
||||
path="http/src/app/downloader/downloader.service.ts"
|
||||
region="getTextFile"
|
||||
header="app/downloader/downloader.service.ts (getTextFile)" linenums="false">
|
||||
</code-example>
|
||||
|
||||
`HttpClient.get()` returns a string rather than the default JSON because of the `responseType` option.
|
||||
|
||||
The RxJS `tap` operator (as in "wiretap") lets the code inspect both success and error values passing through the observable without disturbing them.
|
||||
|
||||
A `download()` method in the `DownloaderComponent` initiates the request by subscribing to the service method.
|
||||
|
||||
<code-example
|
||||
path="http/src/app/downloader/downloader.component.ts"
|
||||
region="download"
|
||||
header="app/downloader/downloader.component.ts (download)" linenums="false">
|
||||
</code-example>
|
||||
|
||||
## Error handling
|
||||
|
||||
What happens if the request fails on the server, or if a poor network connection prevents it from even reaching the server? `HttpClient` will return an _error_ object instead of a successful response.
|
||||
@ -204,7 +268,7 @@ and _pipe them through_ to the error handler.
|
||||
header="app/config/config.service.ts (getConfig v.3 with error handler)">
|
||||
</code-example>
|
||||
|
||||
### `retry()`
|
||||
### Retrying
|
||||
|
||||
Sometimes the error is transient and will go away automatically if you try again.
|
||||
For example, network interruptions are common in mobile scenarios, and trying again
|
||||
@ -242,29 +306,34 @@ If you're following along with these code snippets, note that you must import th
|
||||
header="app/config/config.service.ts (RxJS imports)">
|
||||
</code-example>
|
||||
|
||||
## Requesting non-JSON data
|
||||
## HTTP headers
|
||||
|
||||
Not all APIs return JSON data. In this next example,
|
||||
a `DownloaderService` method reads a text file from the server
|
||||
and logs the file contents, before returning those contents to the caller
|
||||
as an `Observable<string>`.
|
||||
Many servers require extra headers for save operations.
|
||||
For example, they may require a "Content-Type" header to explicitly declare the MIME type of the request body; or the server may require an authorization token.
|
||||
|
||||
### Adding headers
|
||||
|
||||
The `HeroesService` defines such headers in an `httpOptions` object that will be passed
|
||||
to every `HttpClient` save method.
|
||||
|
||||
<code-example
|
||||
path="http/src/app/downloader/downloader.service.ts"
|
||||
region="getTextFile"
|
||||
header="app/downloader/downloader.service.ts (getTextFile)">
|
||||
path="http/src/app/heroes/heroes.service.ts"
|
||||
region="http-options"
|
||||
header="app/heroes/heroes.service.ts (httpOptions)">
|
||||
</code-example>
|
||||
|
||||
`HttpClient.get()` returns a string rather than the default JSON because of the `responseType` option.
|
||||
### Updating headers
|
||||
|
||||
The RxJS `tap` operator (as in "wiretap") lets the code inspect good and error values passing through the observable without disturbing them.
|
||||
You can't directly modify the existing headers within the previous options
|
||||
object because instances of the `HttpHeaders` class are immutable.
|
||||
|
||||
A `download()` method in the `DownloaderComponent` initiates the request by subscribing to the service method.
|
||||
Use the `set()` method instead, to return a clone of the current instance with the new changes applied.
|
||||
|
||||
Here's how you might update the authorization header (after the old token expired) before making the next request.
|
||||
|
||||
<code-example
|
||||
path="http/src/app/downloader/downloader.component.ts"
|
||||
region="download"
|
||||
header="app/downloader/downloader.component.ts (download)">
|
||||
path="http/src/app/heroes/heroes.service.ts"
|
||||
region="update-headers" linenums="false">
|
||||
</code-example>
|
||||
|
||||
## Sending data to the server
|
||||
@ -276,22 +345,6 @@ that fetches heroes and enables users to add, delete, and update them.
|
||||
|
||||
The following sections excerpt methods of the sample's `HeroesService`.
|
||||
|
||||
### Adding headers
|
||||
|
||||
Many servers require extra headers for save operations.
|
||||
For example, they may require a "Content-Type" header to explicitly declare
|
||||
the MIME type of the request body.
|
||||
Or perhaps the server requires an authorization token.
|
||||
|
||||
The `HeroesService` defines such headers in an `httpOptions` object that will be passed
|
||||
to every `HttpClient` save method.
|
||||
|
||||
<code-example
|
||||
path="http/src/app/heroes/heroes.service.ts"
|
||||
region="http-options"
|
||||
header="app/heroes/heroes.service.ts (httpOptions)">
|
||||
</code-example>
|
||||
|
||||
### Making a POST request
|
||||
|
||||
Apps often POST data to a server. They POST when submitting a form.
|
||||
@ -413,118 +466,8 @@ in order to initiate the request.
|
||||
|
||||
We have discussed the basic HTTP functionality in `@angular/common/http`, but sometimes you need to do more than make simple requests and get data back.
|
||||
|
||||
### Configuring the request
|
||||
|
||||
Other aspects of an outgoing request can be configured via the options object
|
||||
passed as the last argument to the `HttpClient` method.
|
||||
|
||||
You [saw earlier](#adding-headers) that the `HeroesService` sets the default headers by
|
||||
passing an options object (`httpOptions`) to its save methods.
|
||||
You can do more.
|
||||
|
||||
#### Update headers
|
||||
|
||||
You can't directly modify the existing headers within the previous options
|
||||
object because instances of the `HttpHeaders` class are immutable.
|
||||
|
||||
Use the `set()` method instead.
|
||||
It returns a clone of the current instance with the new changes applied.
|
||||
|
||||
Here's how you might update the authorization header (after the old token expired)
|
||||
before making the next request.
|
||||
|
||||
<code-example
|
||||
path="http/src/app/heroes/heroes.service.ts"
|
||||
region="update-headers">
|
||||
</code-example>
|
||||
|
||||
#### URL Parameters
|
||||
|
||||
Adding URL search parameters works a similar way.
|
||||
Here is a `searchHeroes` method that queries for heroes whose names contain the search term.
|
||||
|
||||
<code-example
|
||||
path="http/src/app/heroes/heroes.service.ts"
|
||||
region="searchHeroes">
|
||||
</code-example>
|
||||
|
||||
If there is a search term, the code constructs an options object with an HTML URL-encoded search parameter. If the term were "foo", the GET request URL would be `api/heroes/?name=foo`.
|
||||
|
||||
The `HttpParams` are immutable so you'll have to use the `set()` method to update the options.
|
||||
|
||||
### Debouncing requests
|
||||
|
||||
The sample includes an _npm package search_ feature.
|
||||
|
||||
When the user enters a name in a search-box, the `PackageSearchComponent` sends
|
||||
a search request for a package with that name to the NPM web API.
|
||||
|
||||
Here's a pertinent excerpt from the template:
|
||||
|
||||
<code-example
|
||||
path="http/src/app/package-search/package-search.component.html"
|
||||
region="search"
|
||||
header="app/package-search/package-search.component.html (search)">
|
||||
</code-example>
|
||||
|
||||
The `(keyup)` event binding sends every keystroke to the component's `search()` method.
|
||||
|
||||
Sending a request for every keystroke could be expensive.
|
||||
It's better to wait until the user stops typing and then send a request.
|
||||
That's easy to implement with RxJS operators, as shown in this excerpt.
|
||||
|
||||
<code-example
|
||||
path="http/src/app/package-search/package-search.component.ts"
|
||||
region="debounce"
|
||||
header="app/package-search/package-search.component.ts (excerpt)">
|
||||
</code-example>
|
||||
|
||||
The `searchText$` is the sequence of search-box values coming from the user.
|
||||
It's defined as an RxJS `Subject`, which means it is a multicasting `Observable`
|
||||
that can also produce values for itself by calling `next(value)`,
|
||||
as happens in the `search()` method.
|
||||
|
||||
Rather than forward every `searchText` value directly to the injected `PackageSearchService`,
|
||||
the code in `ngOnInit()` _pipes_ search values through three operators:
|
||||
|
||||
1. `debounceTime(500)` - wait for the user to stop typing (1/2 second in this case).
|
||||
1. `distinctUntilChanged()` - wait until the search text changes.
|
||||
1. `switchMap()` - send the search request to the service.
|
||||
|
||||
The code sets `packages$` to this re-composed `Observable` of search results.
|
||||
The template subscribes to `packages$` with the [AsyncPipe](api/common/AsyncPipe)
|
||||
and displays search results as they arrive.
|
||||
|
||||
A search value reaches the service only if it's a new value and the user has stopped typing.
|
||||
|
||||
<div class="alert is-helpful">
|
||||
|
||||
The `withRefresh` option is explained [below](#cache-refresh).
|
||||
|
||||
</div>
|
||||
|
||||
#### _switchMap()_
|
||||
|
||||
The `switchMap()` operator has three important characteristics.
|
||||
|
||||
1. It takes a function argument that returns an `Observable`.
|
||||
`PackageSearchService.search` returns an `Observable`, as other data service methods do.
|
||||
|
||||
2. If a previous search request is still _in-flight_ (as when the connection is poor),
|
||||
it cancels that request and sends a new one.
|
||||
|
||||
3. It returns service responses in their original request order, even if the
|
||||
server returns them out of order.
|
||||
|
||||
|
||||
<div class="alert is-helpful">
|
||||
|
||||
If you think you'll reuse this debouncing logic,
|
||||
consider moving it to a utility function or into the `PackageSearchService` itself.
|
||||
|
||||
</div>
|
||||
|
||||
### Intercepting requests and responses
|
||||
{@a intercepting-requests-and-responses }
|
||||
### HTTP interceptors
|
||||
|
||||
_HTTP Interception_ is a major feature of `@angular/common/http`.
|
||||
With interception, you declare _interceptors_ that inspect and transform HTTP requests from your application to the server.
|
||||
@ -642,7 +585,7 @@ You may have expected the `intercept()` and `handle()` methods to return observa
|
||||
|
||||
Instead they return observables of `HttpEvent<any>`.
|
||||
|
||||
That's because interceptors work at a lower level than those `HttpClient` methods. A single HTTP request can generate multiple _events_, including upload and download progress events. The `HttpResponse` class itself is actually an event, whose type is `HttpEventType.HttpResponseEvent`.
|
||||
That's because interceptors work at a lower level than those `HttpClient` methods. A single HTTP request can generate multiple _events_, including upload and download progress events. The `HttpResponse` class itself is actually an event, whose type is `HttpEventType.Response`.
|
||||
|
||||
Many interceptors are only concerned with the outgoing request and simply return the event stream from `next.handle()` without modifying it.
|
||||
|
||||
@ -845,6 +788,117 @@ the cached response first (and immediately), followed later
|
||||
by the response from the server.
|
||||
Subscribers see a sequence of _two_ responses.
|
||||
|
||||
### Configuring the request
|
||||
|
||||
Other aspects of an outgoing request can be configured via the options object
|
||||
passed as the last argument to the `HttpClient` method.
|
||||
|
||||
In [Adding headers](#adding-headers), the `HeroesService` set the default headers by
|
||||
passing an options object (`httpOptions`) to its save methods.
|
||||
You can do more.
|
||||
|
||||
#### URL query strings
|
||||
|
||||
In this section, you will see how to use the `HttpParams` class to add URL query strings in your `HttpRequest`.
|
||||
|
||||
The following `searchHeroes` method queries for heroes whose names contain the search term.
|
||||
Start by importing `HttpParams` class.
|
||||
|
||||
<code-example hideCopy language="typescript">
|
||||
import {HttpParams} from "@angular/common/http";
|
||||
</code-example>
|
||||
|
||||
<code-example
|
||||
path="http/src/app/heroes/heroes.service.ts"
|
||||
region="searchHeroes" linenums="false">
|
||||
</code-example>
|
||||
|
||||
If there is a search term, the code constructs an options object with an HTML URL-encoded search parameter.
|
||||
If the term were "foo", the GET request URL would be `api/heroes?name=foo`.
|
||||
|
||||
The `HttpParams` are immutable so you'll have to save the returned value of the `.set()` method in order to update the options.
|
||||
|
||||
#### Use `fromString` to create HttpParams
|
||||
|
||||
You can also create HTTP parameters directly from a query string by using the `fromString` variable:
|
||||
|
||||
<code-example hideCopy language="typescript">
|
||||
const params = new HttpParams({fromString: 'name=foo'});
|
||||
</code-example>
|
||||
|
||||
### Debouncing requests
|
||||
|
||||
The sample includes an _npm package search_ feature.
|
||||
|
||||
When the user enters a name in a search-box, the `PackageSearchComponent` sends
|
||||
a search request for a package with that name to the NPM web API.
|
||||
|
||||
Here's a pertinent excerpt from the template:
|
||||
|
||||
<code-example
|
||||
path="http/src/app/package-search/package-search.component.html"
|
||||
region="search"
|
||||
header="app/package-search/package-search.component.html (search)">
|
||||
</code-example>
|
||||
|
||||
The `keyup` event binding sends every keystroke to the component's `search()` method.
|
||||
|
||||
Sending a request for every keystroke could be expensive.
|
||||
It's better to wait until the user stops typing and then send a request.
|
||||
That's easy to implement with RxJS operators, as shown in this excerpt.
|
||||
|
||||
<code-example
|
||||
path="http/src/app/package-search/package-search.component.ts"
|
||||
region="debounce"
|
||||
header="app/package-search/package-search.component.ts (excerpt)">
|
||||
</code-example>
|
||||
|
||||
The `searchText$` is the sequence of search-box values coming from the user.
|
||||
It's defined as an RxJS `Subject`, which means it is a multicasting `Observable`
|
||||
that can also emit values for itself by calling `next(value)`,
|
||||
as happens in the `search()` method.
|
||||
|
||||
Rather than forward every `searchText` value directly to the injected `PackageSearchService`,
|
||||
the code in `ngOnInit()` _pipes_ search values through three operators:
|
||||
|
||||
1. `debounceTime(500)` - wait for the user to stop typing (1/2 second in this case).
|
||||
|
||||
2. `distinctUntilChanged()` - wait until the search text changes.
|
||||
|
||||
3. `switchMap()` - send the search request to the service.
|
||||
|
||||
The code sets `packages$` to this re-composed `Observable` of search results.
|
||||
The template subscribes to `packages$` with the [AsyncPipe](api/common/AsyncPipe)
|
||||
and displays search results as they arrive.
|
||||
|
||||
A search value reaches the service only if it's a new value and the user has stopped typing.
|
||||
|
||||
<div class="alert is-helpful">
|
||||
|
||||
The `withRefresh` option is explained [below](#cache-refresh).
|
||||
|
||||
</div>
|
||||
|
||||
#### _switchMap()_
|
||||
|
||||
The `switchMap()` operator has three important characteristics.
|
||||
|
||||
1. It takes a function argument that returns an `Observable`.
|
||||
`PackageSearchService.search` returns an `Observable`, as other data service methods do.
|
||||
|
||||
2. If a previous search request is still _in-flight_ (as when the network connection is poor),
|
||||
it cancels that request and sends a new one.
|
||||
|
||||
3. It returns service responses in their original request order, even if the
|
||||
server returns them out of order.
|
||||
|
||||
<div class="alert is-helpful">
|
||||
|
||||
If you think you'll reuse this debouncing logic,
|
||||
consider moving it to a utility function or into the `PackageSearchService` itself.
|
||||
|
||||
</div>
|
||||
|
||||
### Listening to progress events
|
||||
|
||||
Sometimes applications transfer large amounts of data and those transfers can take a long time.
|
||||
@ -895,22 +949,26 @@ by returning an observable of simulated events.
|
||||
|
||||
</div>
|
||||
|
||||
## Security: XSRF Protection
|
||||
## Security: XSRF protection
|
||||
|
||||
[Cross-Site Request Forgery (XSRF)](https://en.wikipedia.org/wiki/Cross-site_request_forgery) is an attack technique by which the attacker can trick an authenticated user into unknowingly executing actions on your website. `HttpClient` supports a [common mechanism](https://en.wikipedia.org/wiki/Cross-site_request_forgery#Cookie-to-Header_Token) used to prevent XSRF attacks. When performing HTTP requests, an interceptor reads a token from a cookie, by default `XSRF-TOKEN`, and sets it as an HTTP header, `X-XSRF-TOKEN`. Since only code that runs on your domain could read the cookie, the backend can be certain that the HTTP request came from your client application and not an attacker.
|
||||
[Cross-Site Request Forgery (XSRF)](https://en.wikipedia.org/wiki/Cross-site_request_forgery) is an attack technique by which the attacker can trick an authenticated user into unknowingly executing actions on your website.
|
||||
`HttpClient` supports a [common mechanism](https://en.wikipedia.org/wiki/Cross-site_request_forgery#Cookie-to-Header_Token) used to prevent XSRF attacks.
|
||||
When performing HTTP requests, an interceptor reads a token from a cookie, by default `XSRF-TOKEN`, and sets it as an HTTP header, `X-XSRF-TOKEN`.
|
||||
Since only code that runs on your domain could read the cookie, the backend can be certain that the HTTP request came from your client application and not an attacker.
|
||||
|
||||
By default, an interceptor sends this header on all mutating requests (POST, etc.)
|
||||
to relative URLs but not on GET/HEAD requests or
|
||||
on requests with an absolute URL.
|
||||
By default, an interceptor sends this header on all mutating requests (such as POST)
|
||||
to relative URLs, but not on GET/HEAD requests or on requests with an absolute URL.
|
||||
|
||||
To take advantage of this, your server needs to set a token in a JavaScript readable session cookie called `XSRF-TOKEN` on either the page load or the first GET request. On subsequent requests the server can verify that the cookie matches the `X-XSRF-TOKEN` HTTP header, and therefore be sure that only code running on your domain could have sent the request. The token must be unique for each user and must be verifiable by the server; this prevents the client from making up its own tokens. Set the token to a digest of your site's authentication
|
||||
cookie with a salt for added security.
|
||||
To take advantage of this, your server needs to set a token in a JavaScript readable session cookie called `XSRF-TOKEN` on either the page load or the first GET request.
|
||||
On subsequent requests the server can verify that the cookie matches the `X-XSRF-TOKEN` HTTP header, and therefore be sure that only code running on your domain could have sent the request.
|
||||
The token must be unique for each user and must be verifiable by the server; this prevents the client from making up its own tokens.
|
||||
Set the token to a digest of your site's authentication cookie with a salt for added security.
|
||||
|
||||
In order to prevent collisions in environments where multiple Angular apps share the same domain or subdomain, give each application a unique cookie name.
|
||||
|
||||
<div class="alert is-important">
|
||||
|
||||
*Note that `HttpClient` supports only the client half of the XSRF protection scheme.*
|
||||
*`HttpClient` supports only the client half of the XSRF protection scheme.*
|
||||
Your backend service must be configured to set the cookie for your page, and to verify that
|
||||
the header is present on all eligible requests.
|
||||
If not, Angular's default protection will be ineffective.
|
||||
@ -929,19 +987,13 @@ use `HttpClientXsrfModule.withOptions()` to override the defaults.
|
||||
|
||||
## Testing HTTP requests
|
||||
|
||||
Like any external dependency, the HTTP backend needs to be mocked
|
||||
so your tests can simulate interaction with a remote server.
|
||||
The `@angular/common/http/testing` library makes
|
||||
setting up such mocking straightforward.
|
||||
As for any external dependency, you must mock the HTTP backend so your tests can simulate interaction with a remote server.
|
||||
The `@angular/common/http/testing` library makes it straightforward to set up such mocking .
|
||||
|
||||
### Mocking philosophy
|
||||
|
||||
Angular's HTTP testing library is designed for a pattern of testing wherein
|
||||
the app executes code and makes requests first.
|
||||
|
||||
Then a test expects that certain requests have or have not been made,
|
||||
Angular's HTTP testing library is designed for a pattern of testing in which the app executes code and makes requests first.
|
||||
The test then expects that certain requests have or have not been made,
|
||||
performs assertions against those requests,
|
||||
and finally provide responses by "flushing" each expected request.
|
||||
and finally provides responses by "flushing" each expected request.
|
||||
|
||||
At the end, tests may verify that the app has made no unexpected requests.
|
||||
|
||||
|
@ -193,7 +193,7 @@ text messages with different descriptions (not different meanings), then they ar
|
||||
The angular i18n extractor tool generates a file with a translation unit entry for each `i18n`
|
||||
attribute in a template. By default, it assigns each translation unit a unique id such as this one:
|
||||
|
||||
<code-example path="i18n/doc-files/messages.fr.xlf.html" region="generated-id"></code-example>
|
||||
<code-example path="i18n/doc-files/messages.fr.xlf.html" header="messages.fr.xlf.html" region="generated-id"></code-example>
|
||||
|
||||
When you change the translatable text, the extractor tool generates a new id for that translation unit.
|
||||
You must then update the translation file with the new id.
|
||||
@ -206,7 +206,7 @@ The example below defines the custom id `introductionHeader`:
|
||||
When you specify a custom id, the extractor tool and compiler generate a translation unit with that
|
||||
custom id.
|
||||
|
||||
<code-example path="i18n/doc-files/messages.fr.xlf.html" region="custom-id"></code-example>
|
||||
<code-example path="i18n/doc-files/messages.fr.xlf.html" header="messages.fr.xlf.html" region="custom-id"></code-example>
|
||||
|
||||
The custom id is persistent. The extractor tool does not change it when the translatable text changes.
|
||||
Therefore, you do not need to update the translation. This approach makes maintenance easier.
|
||||
@ -379,7 +379,7 @@ Open a terminal window at the root of the app project and run the CLI command `x
|
||||
ng xi18n
|
||||
</code-example>
|
||||
|
||||
By default, the command creates a file named `messages.xlf` in your `src/` folder.
|
||||
By default, the command creates a file named `messages.xlf` in your project's root directory.
|
||||
|
||||
<div class="alert is-helpful">
|
||||
|
||||
@ -388,7 +388,7 @@ If you don't use the CLI, you have two options:
|
||||
For more information, see the [`ng xi18n` command documentation](cli/xi18n).
|
||||
* You can use the CLI Webpack plugin `AngularCompilerPlugin` from the `@ngtools/webpack` package.
|
||||
Set the parameters `i18nOutFile` and `i18nOutFormat` to trigger the extraction.
|
||||
For more information, see the [Angular Ahead-of-Time Webpack Plugin documentation](https://github.com/angular/angular-cli/tree/master/packages/%40ngtools/webpack).
|
||||
For more information, see the [Angular Ahead-of-Time Webpack Plugin documentation](https://github.com/angular/angular-cli/tree/master/packages/ngtools/webpack).
|
||||
|
||||
</div>
|
||||
|
||||
@ -645,9 +645,9 @@ ready-to-run application package, typically for production.
|
||||
|
||||
When you internationalize with the AOT compiler, you must pre-build a separate application
|
||||
package for each language and serve the appropriate package based on either server-side language
|
||||
detection or url parameters.
|
||||
detection or URL parameters.
|
||||
|
||||
To instruct the AOT compiler to use your translation configuration, set the three "i18n" build configuration options in your `angular.json` file.
|
||||
To instruct the AOT compiler to use your translation configuration, set the three "i18n" build configuration options in your CLI configuration file, `angular.json`.
|
||||
|
||||
* `i18nFile`: the path to the translation file.
|
||||
* `i18nFormat`: the format of the translation file.
|
||||
@ -707,22 +707,24 @@ the CLI configuration file, `angular.json`.
|
||||
"i18nLocale": "fr",
|
||||
"i18nMissingTranslation": "error",
|
||||
}
|
||||
// ...
|
||||
"serve": {
|
||||
"builder": "@angular-devkit/build-angular:dev-server",
|
||||
"options": {
|
||||
"browserTarget": "my-project:build"
|
||||
}
|
||||
},
|
||||
"configurations": {
|
||||
"production": {
|
||||
"browserTarget": "my-project:build:production"
|
||||
...
|
||||
"serve": {
|
||||
"builder": "@angular-devkit/build-angular:dev-server",
|
||||
"options": {
|
||||
"browserTarget": "my-project:build"
|
||||
},
|
||||
"fr": {
|
||||
"browserTarget": "my-project:build:fr"
|
||||
"configurations": {
|
||||
"production": {
|
||||
"browserTarget": "my-project:build:production"
|
||||
},
|
||||
"fr": {
|
||||
"browserTarget": "my-project:build:fr"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
The same configuration options can also be provided through the CLI with your existing `production` configuration.
|
||||
@ -761,6 +763,7 @@ Then provide the `LOCALE_ID` in the main module:
|
||||
|
||||
{@a missing-translation}
|
||||
### Report missing translations
|
||||
|
||||
By default, when a translation is missing, the build succeeds but generates a warning such as
|
||||
`Missing translation for message "foo"`. You can configure the level of warning that is generated by
|
||||
the Angular compiler:
|
||||
@ -770,7 +773,7 @@ compilation, the app will fail to load.
|
||||
* Warning (default): show a 'Missing translation' warning in the console or shell.
|
||||
* Ignore: do nothing.
|
||||
|
||||
You specify the warning level in the `configurations` section your Angular CLI build configuration. The example below shows how to set the warning level to error:
|
||||
You specify the warning level in the `configurations` section of your Angular CLI configuration file, `angular.json`. The example below shows how to set the warning level to error.
|
||||
|
||||
```
|
||||
"configurations": {
|
||||
@ -784,7 +787,7 @@ You specify the warning level in the `configurations` section your Angular CLI b
|
||||
|
||||
If you use the JIT compiler, specify the warning level in the compiler config at bootstrap by adding
|
||||
the 'MissingTranslationStrategy' property. The example below shows how to set the warning level to
|
||||
error:
|
||||
error.
|
||||
|
||||
<code-example path="i18n/doc-files/main.3.ts" header="src/main.ts">
|
||||
</code-example>
|
||||
|
@ -5,13 +5,13 @@
|
||||
By default, NgModules are eagerly loaded, which means that as soon as the app loads, so do all the NgModules, whether or not they are immediately necessary. For large apps with lots of routes, consider lazy loading—a design pattern that loads NgModules as needed. Lazy loading helps keep initial
|
||||
bundle sizes smaller, which in turn helps decrease load times.
|
||||
|
||||
For the final sample app with two lazy loaded modules that this page describes, see the
|
||||
For the final sample app with two lazy-loaded modules that this page describes, see the
|
||||
<live-example></live-example>.
|
||||
|
||||
There are three main steps to setting up a lazy loaded feature module:
|
||||
There are three main steps to setting up a lazy-loaded feature module:
|
||||
|
||||
1. Create the feature module.
|
||||
1. Create the feature module’s routing module.
|
||||
1. Create the feature module with the CLI, using the `--route` flag.
|
||||
1. Create the feature module’s component.
|
||||
1. Configure the routes.
|
||||
|
||||
## Set up an app
|
||||
@ -21,9 +21,9 @@ create one with the CLI. If you do already have an app, skip to
|
||||
[Configure the routes](#config-routes). Enter the following command
|
||||
where `customer-app` is the name of your app:
|
||||
|
||||
```sh
|
||||
<code-example language="bash">
|
||||
ng new customer-app --routing
|
||||
```
|
||||
</code-example>
|
||||
|
||||
This creates an app called `customer-app` and the `--routing` flag
|
||||
generates a file called `app-routing.module.ts`, which is one of
|
||||
@ -32,71 +32,63 @@ Navigate into the project by issuing the command `cd customer-app`.
|
||||
|
||||
## Create a feature module with routing
|
||||
|
||||
Next, you’ll need a feature module to route to. To make one, enter
|
||||
the following command at the terminal window prompt where `customers` is the name of the module:
|
||||
Next, you’ll need a feature module with a component to route to.
|
||||
To make one, enter the following command in the terminal, where `customers` is the name of the feature module, and `customer-list` is the route path for loading the `customers` component:
|
||||
|
||||
```sh
|
||||
ng generate module customers --routing
|
||||
```
|
||||
<code-example language="bash">
|
||||
ng generate module customers --route customer-list --module app.module
|
||||
</code-example>
|
||||
|
||||
This creates a customers folder with two files inside; `CustomersModule`
|
||||
and `CustomersRoutingModule`. `CustomersModule` will act as the gatekeeper
|
||||
for anything that concerns customers. `CustomersRoutingModule` will handle
|
||||
any customer-related routing. This keeps the app’s structure organized as
|
||||
the app grows and allows you to reuse this module while easily keeping its routing intact.
|
||||
This creates a `customers` folder with the new lazy-loadable module `CustomersModule` defined in the file `customers.module.ts`. The command automatically adds the `CustomerComponent` to the new feature module.
|
||||
|
||||
The CLI imports the `CustomersRoutingModule` into the `CustomersModule` by
|
||||
adding a JavaScript import statement at the top of the file and adding
|
||||
`CustomersRoutingModule` to the `@NgModule` `imports` array.
|
||||
Because the new module is meant to be lazy-loaded, the command does NOT add a reference for the new feature module to the root application's module file, `app.module.ts`.
|
||||
Instead, it adds the declared route, `customer-list` to the `Routes` array declared in the module provided as the `--module` option.
|
||||
|
||||
## Add a component to the feature module
|
||||
<code-example language="typescript" header="src/app/app-routing.module.ts">
|
||||
const routes: Routes = [
|
||||
{ path: 'customer-list',
|
||||
loadChildren: () => import('./customers/customers.module').then(m => m.CustomersModule) }
|
||||
];
|
||||
</code-example>
|
||||
|
||||
In order to see the module being lazy loaded in the browser, create a component to render some HTML when the app loads `CustomersModule`. At the command line, enter the following:
|
||||
Notice that the lazy-loading syntax uses `loadChildren` followed by a function that uses the browser's built-in `import('...')` syntax for dynamic imports.
|
||||
The import path is the relative path to the module.
|
||||
|
||||
```sh
|
||||
ng generate component customers/customer-list
|
||||
```
|
||||
### Add another feature module
|
||||
|
||||
This creates a folder inside of `customers` called `customer-list`
|
||||
with the four files that make up the component.
|
||||
Use the same command to create a second lazy-loaded feature module with routing, along with its stub component.
|
||||
|
||||
Just like with the routing module, the CLI imports the
|
||||
`CustomerListComponent` into the `CustomersModule`.
|
||||
<code-example language="bash">
|
||||
ng generate module orders --route order-list --module app.module
|
||||
</code-example>
|
||||
|
||||
This creates a new folder called `orders` containing an `OrdersModule` and `OrdersRoutingModule`, along with the new `OrderComponent` source files.
|
||||
The `order-list` route is added to the `Routes` array in `app-routing.module.ts`, using the lazy-loading syntax.
|
||||
|
||||
## Add another feature module
|
||||
|
||||
For another place to route to, create a second feature module with routing:
|
||||
|
||||
```sh
|
||||
ng generate module orders --routing
|
||||
```
|
||||
|
||||
This makes a new folder called `orders` containing an `OrdersModule` and an `OrdersRoutingModule`.
|
||||
|
||||
Now, just like with the `CustomersModule`, give it some content:
|
||||
|
||||
```sh
|
||||
ng generate component orders/order-list
|
||||
```
|
||||
<code-example language="typescript" header="src/app/app-routing.module.ts">
|
||||
const routes: Routes = [
|
||||
{ path: 'customer-list',
|
||||
loadChildren: () => import('./customers/customers.module').then(m => m.CustomersModule) },
|
||||
{ path: 'order-list',
|
||||
loadChildren: () => import('./orders/orders.module').then(m => m.OrdersModule) }
|
||||
];
|
||||
</code-example>
|
||||
|
||||
## Set up the UI
|
||||
|
||||
Though you can type the URL into the address bar, a nav
|
||||
is easier for the user and more common. Replace the default
|
||||
placeholder markup in `app.component.html` with a custom nav
|
||||
Though you can type the URL into the address bar, a navigation UI is easier for the user and more common.
|
||||
Replace the default placeholder markup in `app.component.html` with a custom nav
|
||||
so you can easily navigate to your modules in the browser:
|
||||
|
||||
|
||||
<code-example path="lazy-loading-ngmodules/src/app/app.component.html" region="app-component-template" header="src/app/app.component.html"></code-example>
|
||||
|
||||
<code-example path="lazy-loading-ngmodules/src/app/app.component.html" header="app.component.html" region="app-component-template" header="src/app/app.component.html"></code-example>
|
||||
|
||||
|
||||
To see your app in the browser so far, enter the following command in the terminal window:
|
||||
|
||||
```sh
|
||||
<code-example language="bash">
|
||||
ng serve
|
||||
```
|
||||
</code-example>
|
||||
|
||||
Then go to `localhost:4200` where you should see “app works!” and three buttons.
|
||||
|
||||
@ -104,59 +96,42 @@ Then go to `localhost:4200` where you should see “app works!” and three butt
|
||||
<img src="generated/images/guide/lazy-loading-ngmodules/three-buttons.png" width="300" alt="three buttons in the browser">
|
||||
</figure>
|
||||
|
||||
|
||||
To make the buttons work, you need to configure the routing modules.
|
||||
These buttons work, because the CLI automatically added the routes to the feature modules to the `routes` array in `app.module.ts`.
|
||||
|
||||
{@a config-routes}
|
||||
|
||||
## Configure the routes
|
||||
|
||||
The two feature modules, `OrdersModule` and `CustomersModule`, have to be
|
||||
wired up to the `AppRoutingModule` so the router knows about them. The structure is as follows:
|
||||
|
||||
<figure>
|
||||
<img src="generated/images/guide/lazy-loading-ngmodules/lazy-load-relationship.jpg" width="400" alt="lazy loaded modules diagram">
|
||||
</figure>
|
||||
|
||||
|
||||
Each feature module acts as a doorway via the router. In the `AppRoutingModule`, you configure the routes to the feature modules, in this case `OrdersModule` and `CustomersModule`. This way, the router knows to go to the feature module. The feature module then connects the `AppRoutingModule` to the `CustomersRoutingModule` or the `OrdersRoutingModule`. Those routing modules tell the router where to go to load relevant components.
|
||||
|
||||
### Routes at the app level
|
||||
## Imports and route configuration
|
||||
|
||||
The CLI automatically added each feature module to the routes map at the application level.
|
||||
Finish this off by adding the default route.
|
||||
In `AppRoutingModule`, update the `routes` array with the following:
|
||||
|
||||
<code-example path="lazy-loading-ngmodules/src/app/app-routing.module.ts" id="app-routing.module.ts" region="const-routes" header="src/app/app-routing.module.ts"></code-example>
|
||||
|
||||
<code-example path="lazy-loading-ngmodules/src/app/app-routing.module.ts" region="const-routes" header="src/app/app-routing.module.ts"></code-example>
|
||||
The first two paths are the routes to the `CustomersModule` and the `OrdersModule`.
|
||||
The final entry defines a default route. The empty path matches everything that doesn't match an earlier path.
|
||||
|
||||
|
||||
The import statements stay the same. The first two paths are the routes to the `CustomersModule` and the `OrdersModule` respectively. Notice that the lazy loading syntax uses `loadChildren` followed by a function that uses the browser's built-in `import('...')` syntax for dynamic imports. The import path is the relative path to the module.
|
||||
|
||||
### Inside the feature module
|
||||
|
||||
Next, take a look at `customers.module.ts`. If you’re using the CLI and following the steps outlined in this page, you don’t have to do anything here. The feature module is like a connector between the `AppRoutingModule` and the feature routing module. The `AppRoutingModule` imports the feature module, `CustomersModule`, and `CustomersModule` in turn imports the `CustomersRoutingModule`.
|
||||
|
||||
|
||||
<code-example path="lazy-loading-ngmodules/src/app/customers/customers.module.ts" region="customers-module" header="src/app/customers/customers.module.ts"></code-example>
|
||||
|
||||
Next, take a look at `customers.module.ts`. If you’re using the CLI and following the steps outlined in this page, you don’t have to do anything here.
|
||||
|
||||
<code-example path="lazy-loading-ngmodules/src/app/customers/customers.module.ts" id="customers.module.ts" region="customers-module" header="src/app/customers/customers.module.ts"></code-example>
|
||||
|
||||
The `customers.module.ts` file imports the `CustomersRoutingModule` and `CustomerListComponent` so the `CustomersModule` class can have access to them. `CustomersRoutingModule` is then listed in the `@NgModule` `imports` array giving `CustomersModule` access to its own routing module, and `CustomerListComponent` is in the `declarations` array, which means `CustomerListComponent` belongs to the `CustomersModule`.
|
||||
|
||||
|
||||
### Configure the feature module’s routes
|
||||
The feature module has its own routing module, `customers-routing.module.ts`. The `AppRoutingModule` imports the feature module, `CustomersModule`, and `CustomersModule` in turn imports the `CustomersRoutingModule`.
|
||||
|
||||
The next step is in `customers-routing.module.ts`. First, import the component at the top of the file with the other JavaScript import statements. Then, add the route to `CustomerListComponent`.
|
||||
|
||||
<code-example path="lazy-loading-ngmodules/src/app/customers/customers-routing.module.ts" region="customers-routing-module" header="src/app/customers/customers-routing.module.ts"></code-example>
|
||||
The feature-specific routing module imports its own feature component, `CustomerListComponent`, along with the other JavaScript import statements. It also adds the route to its own component.
|
||||
|
||||
<code-example path="lazy-loading-ngmodules/src/app/customers/customers-routing.module.ts" id="customers-routing.module.ts" region="customers-routing-module" header="src/app/customers/customers-routing.module.ts"></code-example>
|
||||
|
||||
Notice that the `path` is set to an empty string. This is because the path in `AppRoutingModule` is already set to `customers`, so this route in the `CustomersRoutingModule`, is already within the `customers` context. Every route in this routing module is a child route.
|
||||
|
||||
Repeat this last step of importing the `OrdersListComponent` and configuring the Routes array for the `orders-routing.module.ts`:
|
||||
The other feature module's routing module is configured similarly.
|
||||
|
||||
<code-example path="lazy-loading-ngmodules/src/app/orders/orders-routing.module.ts" region="orders-routing-module-detail" header="src/app/orders/orders-routing.module.ts (excerpt)"></code-example>
|
||||
|
||||
Now, if you view the app in the browser, the three buttons take you to each module.
|
||||
<code-example path="lazy-loading-ngmodules/src/app/orders/orders-routing.module.ts" id="orders-routing.module.ts" region="orders-routing-module-detail" header="src/app/orders/orders-routing.module.ts (excerpt)"></code-example>
|
||||
|
||||
## Confirm it’s working
|
||||
|
||||
@ -167,7 +142,7 @@ You can check to see that a module is indeed being lazy loaded with the Chrome d
|
||||
</figure>
|
||||
|
||||
|
||||
Click on the Orders or Customers button. If you see a chunk appear, you’ve wired everything up properly and the feature module is being lazy loaded. A chunk should appear for Orders and for Customers but will only appear once for each.
|
||||
Click on the Orders or Customers button. If you see a chunk appear, everything is wired up properly and the feature module is being lazy loaded. A chunk should appear for Orders and for Customers but will only appear once for each.
|
||||
|
||||
|
||||
<figure>
|
||||
@ -186,17 +161,17 @@ Then reload with `Cmd+r` or `Ctrl+r`, depending on your platform.
|
||||
|
||||
## `forRoot()` and `forChild()`
|
||||
|
||||
You might have noticed that the CLI adds `RouterModule.forRoot(routes)` to the `app-routing.module.ts` `imports` array. This lets Angular know that this module,
|
||||
`AppRoutingModule`, is a routing module and `forRoot()` specifies that this is the root
|
||||
routing module. It configures all the
|
||||
routes you pass to it, gives you access to the router directives, and registers the `RouterService`.
|
||||
You might have noticed that the CLI adds `RouterModule.forRoot(routes)` to the `app-routing.module.ts` `imports` array.
|
||||
This lets Angular know that this module, `AppRoutingModule`, is a routing module and `forRoot()` specifies that this is the root routing module.
|
||||
It configures all the routes you pass to it, gives you access to the router directives, and registers the `RouterService`.
|
||||
Use `forRoot()` in the `AppRoutingModule`—that is, one time in the app at the root level.
|
||||
|
||||
The CLI also adds `RouterModule.forChild(routes)` to feature routing modules. This way, Angular
|
||||
knows that the route list is only responsible for providing additional routes and is intended for feature modules. You can use `forChild()` in multiple modules.
|
||||
|
||||
`forRoot()` contains injector configuration which is global; such as configuring the Router. `forChild()` has no injector configuration, only directives such as `RouterOutlet` and `RouterLink`.
|
||||
The CLI also adds `RouterModule.forChild(routes)` to feature routing modules.
|
||||
This way, Angular knows that the route list is only responsible for providing additional routes and is intended for feature modules.
|
||||
You can use `forChild()` in multiple modules.
|
||||
|
||||
The `forRoot()` method takes care of the *global* injector configuration for the Router.
|
||||
The `forChild()` method has no injector configuration. It uses directives such as `RouterOutlet` and `RouterLink`.
|
||||
For more information, see the [`forRoot()` pattern](guide/singleton-services#forRoot) section of the [Singleton Services](guide/singleton-services) guide.
|
||||
|
||||
<hr>
|
||||
@ -209,4 +184,3 @@ You may also be interested in the following:
|
||||
* [Types of Feature Modules](guide/module-types).
|
||||
* [Route-level code-splitting in Angular](https://web.dev/route-level-code-splitting-in-angular/)
|
||||
* [Route preloading strategies in Angular](https://web.dev/route-preloading-in-angular/)
|
||||
|
||||
|
96
aio/content/guide/migration-renderer.md
Normal file
96
aio/content/guide/migration-renderer.md
Normal file
@ -0,0 +1,96 @@
|
||||
# `Renderer` to `Renderer2` migration
|
||||
|
||||
## Migration Overview
|
||||
|
||||
The `Renderer` class has been marked as deprecated since Angular version 4. This section provides guidance on migrating from this deprecated API to the newer `Renderer2` API and what it means for your app.
|
||||
|
||||
## Why should I migrate to Renderer2?
|
||||
|
||||
The deprecated `Renderer` class has been removed in version 9 of Angular, so it's necessary to migrate to a supported API. Using `Renderer2` is the recommended strategy because it supports a similar set of functionality to `Renderer`. The API surface is quite large (with 19 methods), but the schematic should simplify this process for your applications.
|
||||
|
||||
## Is there action required on my end?
|
||||
|
||||
No. The schematic should handle most cases with the exception of `Renderer.animate()` and `Renderer.setDebugInfo()`, which already aren’t supported.
|
||||
|
||||
## What are the `__ngRendererX` methods? Why are they necessary?
|
||||
|
||||
Some methods either don't have exact equivalents in `Renderer2`, or they correspond to more than one expression. For example, both renderers have a `createElement()` method, but they're not equal because a call such as `renderer.createElement(parentNode, namespaceAndName)` in the `Renderer` corresponds to the following block of code in `Renderer2`:
|
||||
|
||||
```ts
|
||||
const [namespace, name] = splitNamespace(namespaceAndName);
|
||||
const el = renderer.createElement(name, namespace);
|
||||
if (parentNode) {
|
||||
renderer.appendChild(parentNode, el);
|
||||
}
|
||||
return el;
|
||||
```
|
||||
|
||||
Migration has to guarantee that the return values of functions and types of variables stay the same. To handle the majority of cases safely, the schematic declares helper functions at the bottom of the user's file. These helpers encapsulate your own logic and keep the replacements inside your code down to a single function call. Here's an example of how the `createElement()` migration looks:
|
||||
|
||||
|
||||
**Before:**
|
||||
|
||||
```ts
|
||||
public createAndAppendElement() {
|
||||
const el = this.renderer.createElement('span');
|
||||
el.textContent = 'hello world';
|
||||
return el;
|
||||
}
|
||||
```
|
||||
|
||||
**After:**
|
||||
|
||||
<code-example>
|
||||
|
||||
public createAndAppendElement() {
|
||||
const el = __ngRendererCreateElement(this.renderer, this.element, 'span');
|
||||
el.textContent = 'hello world';
|
||||
return el;
|
||||
}
|
||||
// Generated code at the bottom of the file
|
||||
__ngRendererCreateElement(renderer: any, parentNode: any, nameAndNamespace: any) {
|
||||
const [namespace, name] = __ngRendererSplitNamespace(namespaceAndName);
|
||||
const el = renderer.createElement(name, namespace);
|
||||
if (parentNode) {
|
||||
renderer.appendChild(parentNode, el);
|
||||
}
|
||||
return el;
|
||||
}
|
||||
__ngRendererSplitNamespace(nameAndNamespace: any) {
|
||||
// returns the split name and namespace
|
||||
}
|
||||
|
||||
</code-example>
|
||||
|
||||
When implementing these helper functions, the schematic ensures that they're only declared once per file and that their names are unique enough that there's a small chance of colliding with pre-existing functions in your code. The schematic also keeps their parameter types as `any` so that it doesn't have to insert extra logic that ensures that their values have the correct type.
|
||||
|
||||
### I’m a library author. Should I run this migration?
|
||||
|
||||
**Library authors should definitely use this migration to move away from the `Renderer`. Otherwise, the libraries won't work with applications built with version 9.**
|
||||
|
||||
|
||||
### Full list of method migrations
|
||||
|
||||
The following table shows all methods that the migration maps from `Renderer` to `Renderer2`.
|
||||
|
||||
|Renderer|Renderer2|
|
||||
|---|---|
|
||||
|`listen(renderElement, name, callback)`|`listen(renderElement, name, callback)`|
|
||||
|`setElementProperty(renderElement, propertyName, propertyValue)`|`setProperty(renderElement, propertyName, propertyValue)`|
|
||||
|`setText(renderNode, text)`|`setValue(renderNode, text)`|
|
||||
|`listenGlobal(target, name, callback)`|`listen(target, name, callback)`|
|
||||
|`selectRootElement(selectorOrNode, debugInfo?)`|`selectRootElement(selectorOrNode)`|
|
||||
|`createElement(parentElement, name, debugInfo?)`|`appendChild(parentElement, createElement(name))`|
|
||||
|`setElementStyle(el, style, value?)`|`value == null ? removeStyle(el, style) : setStyle(el, style, value)`
|
||||
|`setElementAttribute(el, name, value?)`|`attributeValue == null ? removeAttribute(el, name) : setAttribute(el, name, value)`
|
||||
|`createText(parentElement, value, debugInfo?)`|`appendChild(parentElement, createText(value))`|
|
||||
|`createTemplateAnchor(parentElement)`|`appendChild(parentElement, createComment(''))`|
|
||||
|`setElementClass(renderElement, className, isAdd)`|`isAdd ? addClass(renderElement, className) : removeClass(renderElement, className)`|
|
||||
|`projectNodes(parentElement, nodes)`|`for (let i = 0; i < nodes.length; i<ins></ins>) { appendChild(parentElement, nodes<i>); }`|
|
||||
|`attachViewAfter(node, viewRootNodes)`|`const parentElement = parentNode(node); const nextSibling = nextSibling(node); for (let i = 0; i < viewRootNodes.length; i<ins></ins>) { insertBefore(parentElement, viewRootNodes<i>, nextSibling);}`|
|
||||
|`detachView(viewRootNodes)`|`for (let i = 0; i < viewRootNodes.length; i<ins></ins>) {const node = viewRootNodes<i>; const parentElement = parentNode(node); removeChild(parentElement, node);}`|
|
||||
|`destroyView(hostElement, viewAllNodes)`|`for (let i = 0; i < viewAllNodes.length; i<ins></ins>) { destroyNode(viewAllNodes<i>); }`|
|
||||
|`setBindingDebugInfo()`|This function is a noop in `Renderer2`.|
|
||||
|`createViewRoot(hostElement)`|Should be replaced with a reference to `hostElement`|
|
||||
|`invokeElementMethod(renderElement, methodName, args?)`|`(renderElement as any)<methodName>.apply(renderElement, args);`|
|
||||
|`animate(element, startingStyles, keyframes, duration, delay, easing, previousPlayers?)`|Throws an error (same behavior as `Renderer.animate()`)|
|
138
aio/content/guide/migration-undecorated-classes.md
Normal file
138
aio/content/guide/migration-undecorated-classes.md
Normal file
@ -0,0 +1,138 @@
|
||||
# Undecorated classes migration (DI)
|
||||
|
||||
This section discusses an Angular version 9 schematic that migrates
|
||||
two inheritance patterns that need to be updated to work with Ivy.
|
||||
|
||||
## What does this migration do?
|
||||
|
||||
This migration adds an empty `@Directive()` decorator to undecorated
|
||||
base classes that are extended by either directives or components.
|
||||
|
||||
Before:
|
||||
```ts
|
||||
export class BaseMenu {
|
||||
constructor(private vcr: ViewContainerRef) {}
|
||||
}
|
||||
|
||||
@Directive({selector: '[settingsMenu]'})
|
||||
export class SettingsMenu extends BaseMenu {}
|
||||
```
|
||||
|
||||
After:
|
||||
```ts
|
||||
@Directive()
|
||||
export class BaseMenu {
|
||||
constructor(private vcr: ViewContainerRef) {}
|
||||
}
|
||||
|
||||
@Directive({selector: '[settingsMenu]'})
|
||||
export class SettingsMenu extends BaseMenu {}
|
||||
```
|
||||
|
||||
The schematic also copies any inherited directive or component metadata to the derived class.
|
||||
|
||||
Before:
|
||||
```ts
|
||||
@Component({
|
||||
selector: 'base-menu',
|
||||
template: '<div></div>'
|
||||
})
|
||||
class BaseMenu {}
|
||||
|
||||
export class SettingsMenu extends BaseMenu {}
|
||||
```
|
||||
|
||||
After:
|
||||
```ts
|
||||
@Component({
|
||||
selector: 'base-menu',
|
||||
template: '<div></div>'
|
||||
})
|
||||
class BaseMenu {}
|
||||
|
||||
@Component({
|
||||
selector: 'settings-menu',
|
||||
template: '<div></div>'
|
||||
})
|
||||
export class SettingsMenu extends BaseMenu {}
|
||||
```
|
||||
|
||||
## Why is this migration necessary?
|
||||
|
||||
When a class has a `@Directive()` or `@Component()` decorator,
|
||||
the Angular compiler generates extra code to inject dependencies into
|
||||
the constructor. When using inheritance, Ivy needs both the parent class
|
||||
and the child class to apply a decorator to generate the correct code.
|
||||
|
||||
You can think of this change as two cases: a parent class is missing a
|
||||
decorator or a child class is missing a decorator. In both scenarios,
|
||||
Angular's run-time needs additional information from the compiler.
|
||||
This additional information comes from adding decorators.
|
||||
|
||||
|
||||
### Decorator missing from parent class
|
||||
|
||||
When the decorator is missing from the parent class,
|
||||
the subclass will inherit a constructor from a class for
|
||||
which the compiler did not generate special constructor
|
||||
info (because it was not decorated as a directive).
|
||||
When Angular then tries to create the subclass,
|
||||
it doesn't have the correct info
|
||||
to create it.
|
||||
|
||||
In View Engine, the compiler has global knowledge, so it
|
||||
can look up the missing data. However, the Ivy compiler
|
||||
only processes each directive in isolation. This means that
|
||||
compilation can be faster, but the compiler can't
|
||||
automatically infer the same
|
||||
information as before. Adding the `@Directive()` explicitly
|
||||
provides this information.
|
||||
|
||||
In the future, add `@Directive()` to base classes that
|
||||
do not already have decorators and are extended by directives.
|
||||
|
||||
### Decorator missing from child class
|
||||
|
||||
When the child class is missing the decorator, the
|
||||
child class inherits from the
|
||||
parent class yet has no decorators of its own.
|
||||
Without a decorator, the compiler has no way of knowing
|
||||
that the class is a `@Directive` or `@Component`, so
|
||||
it doesn't generate the proper instructions for the directive.
|
||||
|
||||
|
||||
## What does it mean to have a `@Directive()` decorator with no metadata inside of it?
|
||||
|
||||
The presence of the `@Directive` decorator causes Angular to generate
|
||||
extra code for the affected class. If that decorator includes no
|
||||
properties (metadata),
|
||||
the directive won't be matched to elements or instantiated
|
||||
directly, but other classes that _extend_ the
|
||||
directive class will inherit this generated code. You can think of
|
||||
this as an "abstract" directive.
|
||||
|
||||
Adding an abstract directive to an `NgModule` will cause an error.
|
||||
A directive must have a `selector` property defined in order to match some element in a template.
|
||||
|
||||
## When do I need a `@Directive()` decorator without a selector?
|
||||
|
||||
If you're using dependency injection, or any Angular-specific
|
||||
feature, such as `@HostBinding()`, `@ViewChild()`, or `@Input()`, you need a
|
||||
`@Directive()` or `@Component()` decorator.
|
||||
The decorator lets the compiler know to generate the correct
|
||||
instructions to create that class and any classes that extend it.
|
||||
If you don't want to use that base class as a directive directly, leave
|
||||
the selector blank. If you do want it to be usable independently,
|
||||
fill in the metadata as usual.
|
||||
|
||||
Classes that don't use Angular features don't need an Angular decorator.
|
||||
|
||||
## I'm a library author. Should I add the `@Directive()` decorator to base classes?
|
||||
|
||||
|
||||
As support for selectorless decorators is introduced in
|
||||
Angular version 9, if you want to support Angular version 8 and earlier, you
|
||||
shouldn't add a selectorless `@Directive()` decorator.
|
||||
You can either add `@Directive()` with a selector or
|
||||
add an explicit constructor to affected subclasses.
|
||||
|
@ -1,6 +1,6 @@
|
||||
# Providers
|
||||
|
||||
A provider is an instruction to the DI system on how to obtain a value for a dependency. Most of the time, these dependencies are services that you create and provide.
|
||||
A provider is an instruction to the [Dependency Injection](/guide/dependency-injection) system on how to obtain a value for a dependency. Most of the time, these dependencies are services that you create and provide.
|
||||
|
||||
For the final sample app using the provider that this page describes,
|
||||
see the <live-example></live-example>.
|
||||
|
@ -519,7 +519,8 @@ During each navigation, the `Router` emits navigation events through the `Router
|
||||
<td>
|
||||
|
||||
An [event](api/router/NavigationCancel) triggered when navigation is canceled.
|
||||
This is due to a [Route Guard](#guards) returning false during navigation.
|
||||
This can happen when a [Route Guard](#guards) returns false during navigation,
|
||||
or redirects by returning a `UrlTree`.
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
|
@ -62,12 +62,22 @@ To learn more about other browsers that are service worker ready, see the [Can I
|
||||
|
||||
## Related resources
|
||||
|
||||
The rest of the articles in this section specifically address the Angular implementation of service workers.
|
||||
|
||||
* [App Shell](guide/app-shell)
|
||||
* [Service Worker Communication](guide/service-worker-communications)
|
||||
* [Service Worker in Production](guide/service-worker-devops)
|
||||
* [Service Worker Configuration](guide/service-worker-config)
|
||||
|
||||
For more information about service workers in general, see [Service Workers: an Introduction](https://developers.google.com/web/fundamentals/primers/service-workers/).
|
||||
|
||||
For more information about browser support, see the [browser support](https://developers.google.com/web/fundamentals/primers/service-workers/#browser_support) section of [Service Workers: an Introduction](https://developers.google.com/web/fundamentals/primers/service-workers/), Jake Archibald's [Is Serviceworker ready?](https://jakearchibald.github.io/isserviceworkerready/), and
|
||||
[Can I Use](http://caniuse.com/#feat=serviceworkers).
|
||||
|
||||
The remainder of this Angular documentation specifically addresses the Angular implementation of service workers.
|
||||
For additional recommendations and examples, see:
|
||||
|
||||
* [Precaching with Angular Service Worker](https://web.dev/precaching-with-the-angular-service-worker/)
|
||||
* [Creating a PWA with Angular CLI](https://web.dev/creating-pwa-with-angular-cli/)
|
||||
|
||||
## Next steps
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
# Singleton services
|
||||
|
||||
A singleton service is a service for which only once instance exists in an app.
|
||||
A singleton service is a service for which only one instance exists in an app.
|
||||
|
||||
For a sample app using the app-wide singleton service that this page describes, see the
|
||||
<live-example name="ngmodules"></live-example> showcasing all the documented features of NgModules.
|
||||
|
@ -6,8 +6,7 @@
|
||||
h4 .syntax { font-size: 100%; }
|
||||
</style>
|
||||
|
||||
The Angular application manages what the user sees and can do, achieving this through the interaction of a
|
||||
component class instance (the *component*) and its user-facing template.
|
||||
The Angular application manages what the user sees and can do, achieving this through the interaction of a component class instance (the *component*) and its user-facing template.
|
||||
|
||||
You may be familiar with the component/template duality from your experience with model-view-controller (MVC) or model-view-viewmodel (MVVM).
|
||||
In Angular, the component plays the part of the controller/viewmodel, and the template represents the view.
|
||||
@ -85,12 +84,10 @@ converts the expression results to strings, and links them with neighboring lite
|
||||
it assigns this composite interpolated result to an **element or directive property**.
|
||||
|
||||
You appear to be inserting the result between element tags and assigning it to attributes.
|
||||
However, interpolation is a special syntax that Angular converts into a *property binding*.
|
||||
|
||||
<div class="alert is-helpful">
|
||||
|
||||
However, interpolation is a special syntax that Angular converts into a
|
||||
property binding.
|
||||
|
||||
If you'd like to use something other than `{{` and `}}`, you can
|
||||
configure the interpolation delimiter via the
|
||||
[interpolation](api/core/Component#interpolation)
|
||||
@ -124,8 +121,8 @@ including:
|
||||
Other notable differences from JavaScript syntax include:
|
||||
|
||||
* No support for the bitwise operators such as `|` and `&`
|
||||
* New template expression operators, such as `|`, `?.` and `!`
|
||||
<!-- link to: guide/template-syntax#expression-operators -->
|
||||
* New [template expression operators](guide/template-syntax#expression-operators), such as `|`, `?.` and `!`
|
||||
|
||||
|
||||
### Expression context
|
||||
|
||||
@ -171,12 +168,29 @@ members of the expression context.
|
||||
|
||||
When using template expressions follow these guidelines:
|
||||
|
||||
* [No visible side effects](guide/template-syntax#no-visible-side-effects)
|
||||
* [Quick execution](guide/template-syntax#quick-execution)
|
||||
* [Simplicity](guide/template-syntax#simplicity)
|
||||
* [Quick execution](guide/template-syntax#quick-execution)
|
||||
* [No visible side effects](guide/template-syntax#no-visible-side-effects)
|
||||
|
||||
#### Simplicity
|
||||
|
||||
### No visible side effects
|
||||
Although it's possible to write complex template expressions, it's a better
|
||||
practice to avoid them.
|
||||
|
||||
A property name or method call should be the norm, but an occasional Boolean negation, `!`, is OK.
|
||||
Otherwise, confine application and business logic to the component,
|
||||
where it is easier to develop and test.
|
||||
|
||||
#### Quick execution
|
||||
|
||||
Angular executes template expressions after every change detection cycle.
|
||||
Change detection cycles are triggered by many asynchronous activities such as
|
||||
promise resolutions, HTTP results, timer events, key presses and mouse moves.
|
||||
|
||||
Expressions should finish quickly or the user experience may drag, especially on slower devices.
|
||||
Consider caching values when their computation is expensive.
|
||||
|
||||
#### No visible side effects
|
||||
|
||||
A template expression should not change any application state other than the value of the
|
||||
target property.
|
||||
@ -187,40 +201,18 @@ The view should be stable throughout a single rendering pass.
|
||||
|
||||
An [idempotent](https://en.wikipedia.org/wiki/Idempotence) expression is ideal because
|
||||
it is free of side effects and improves Angular's change detection performance.
|
||||
|
||||
In Angular terms, an idempotent expression always returns
|
||||
*exactly the same thing* until
|
||||
one of its dependent values changes.
|
||||
*exactly the same thing* until one of its dependent values changes.
|
||||
|
||||
Dependent values should not change during a single turn of the event loop.
|
||||
If an idempotent expression returns a string or a number, it returns the same string or number when called twice in a row. If the expression returns an object, including an `array`, it returns the same object *reference* when called twice in a row.
|
||||
|
||||
<div class="alert is-helpful">
|
||||
|
||||
There is one exception to this behavior that applies to `*ngFor`. `*ngFor` has `trackBy` functionality that can deal with referential inequality of objects that when iterating over them.
|
||||
|
||||
For more information, see the [*ngFor with `trackBy`](guide/template-syntax#ngfor-with-trackby) section of this guide.
|
||||
There is one exception to this behavior that applies to `*ngFor`. `*ngFor` has `trackBy` functionality that can deal with referential inequality of objects when iterating over them. See [*ngFor with `trackBy`](guide/template-syntax#ngfor-with-trackby) for details.
|
||||
|
||||
</div>
|
||||
|
||||
### Quick execution
|
||||
|
||||
Angular executes template expressions after every change detection cycle.
|
||||
Change detection cycles are triggered by many asynchronous activities such as
|
||||
promise resolutions, HTTP results, timer events, key presses and mouse moves.
|
||||
|
||||
Expressions should finish quickly or the user experience may drag, especially on slower devices.
|
||||
Consider caching values when their computation is expensive.
|
||||
|
||||
### Simplicity
|
||||
|
||||
Although it's possible to write complex template expressions, it's a better
|
||||
practice to avoid them.
|
||||
|
||||
A property name or method call should be the norm, but an occasional Boolean negation, `!`, is OK.
|
||||
Otherwise, confine application and business logic to the component,
|
||||
where it is easier to develop and test.
|
||||
|
||||
<!-- end of Interpolation doc -->
|
||||
|
||||
<hr/>
|
||||
@ -278,19 +270,15 @@ Template context names take precedence over component context names.
|
||||
In `deleteHero(hero)` above, the `hero` is the template input variable,
|
||||
not the component's `hero` property.
|
||||
|
||||
### Statement guidelines
|
||||
|
||||
Template statements cannot refer to anything in the global namespace. They
|
||||
can't refer to `window` or `document`.
|
||||
They can't call `console.log` or `Math.max`.
|
||||
|
||||
### Statement guidelines
|
||||
|
||||
As with expressions, avoid writing complex template statements.
|
||||
A method call or simple property assignment should be the norm.
|
||||
|
||||
Now that you have a feel for template expressions and statements,
|
||||
you're ready to learn about the varieties of data binding syntax beyond interpolation.
|
||||
|
||||
|
||||
<hr/>
|
||||
|
||||
{@a binding-syntax}
|
||||
@ -396,7 +384,7 @@ Every public member of a **source** directive is automatically available for bin
|
||||
You don't have to do anything special to access a directive member in a template expression or statement.
|
||||
|
||||
|
||||
## Data-binding and HTML
|
||||
### Data-binding and HTML
|
||||
|
||||
In the normal course of HTML development, you create a visual structure with HTML elements, and
|
||||
you modify those elements by setting element attributes with string constants.
|
||||
@ -415,10 +403,10 @@ Notice that the binding is to the `disabled` property of the button's DOM elemen
|
||||
**not** the attribute. This applies to data-binding in general. Data-binding works with *properties* of DOM elements, components, and directives, not HTML *attributes*.
|
||||
|
||||
|
||||
## HTML attribute vs. DOM property
|
||||
### HTML attribute vs. DOM property
|
||||
|
||||
The distinction between an HTML attribute and a DOM property is key to understanding
|
||||
how Angular binding works. **Attributes are defined by HTML. Properties are accessed from DOM, or the Document Object Model, nodes.**
|
||||
how Angular binding works. **Attributes are defined by HTML. Properties are accessed from DOM (Document Object Model) nodes.**
|
||||
|
||||
* A few HTML attributes have 1:1 mapping to properties; for example, `id`.
|
||||
|
||||
@ -426,31 +414,30 @@ how Angular binding works. **Attributes are defined by HTML. Properties are acce
|
||||
|
||||
* Some DOM properties don't have corresponding attributes; for example, `textContent`.
|
||||
|
||||
This general rule can help you build a mental model of attributes and DOM properties:
|
||||
**attributes initialize DOM properties and then they are done.
|
||||
Property values can change; attribute values can't.**
|
||||
It is important to remember that *HTML attribute* and the *DOM property* are different things, even when they have the same name.
|
||||
In Angular, the only role of HTML attributes is to initialize element and directive state.
|
||||
|
||||
**Template binding works with *properties* and *events*, not *attributes*.**
|
||||
|
||||
When you write a data-binding, you're dealing exclusively with the *DOM properties* and *events* of the target object.
|
||||
|
||||
<div class="alert is-helpful">
|
||||
|
||||
There is, of course, an exception to this rule because attributes can be changed by `setAttribute()`, which will re-initialize corresponding DOM properties again.
|
||||
This general rule can help you build a mental model of attributes and DOM properties:
|
||||
**Attributes initialize DOM properties and then they are done.
|
||||
Property values can change; attribute values can't.**
|
||||
|
||||
There is one exception to this rule.
|
||||
Attributes can be changed by `setAttribute()`, which re-initializes corresponding DOM properties.
|
||||
|
||||
</div>
|
||||
|
||||
Comparing the [`<td>` attributes](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/td)
|
||||
attributes to the [`<td>` properties](https://developer.mozilla.org/en-US/docs/Web/API/HTMLTableCellElement)
|
||||
provides a helpful
|
||||
example for differentiation. In particular, you can navigate from the attributes
|
||||
page to the properties via "DOM interface" link, and navigate the inheritance
|
||||
hierarchy up to `HTMLTableCellElement`.
|
||||
|
||||
**The HTML attribute and the DOM property are not the same thing, even when they have the same name.**
|
||||
|
||||
For more information, see the [MDN Interfaces documentation](https://developer.mozilla.org/en-US/docs/Web/API#Interfaces) which has API docs for all the standard DOM elements and their properties.
|
||||
Comparing the [`<td>` attributes](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/td) attributes to the [`<td>` properties](https://developer.mozilla.org/en-US/docs/Web/API/HTMLTableCellElement) provides a helpful example for differentiation.
|
||||
In particular, you can navigate from the attributes page to the properties via "DOM interface" link, and navigate the inheritance hierarchy up to `HTMLTableCellElement`.
|
||||
|
||||
|
||||
|
||||
|
||||
### Example 1: an `<input>`
|
||||
#### Example 1: an `<input>`
|
||||
|
||||
When the browser renders `<input type="text" value="Sarah">`, it creates a
|
||||
corresponding DOM node with a `value` property initialized to "Sarah".
|
||||
@ -466,7 +453,7 @@ The HTML attribute `value` specifies the *initial* value; the DOM `value` proper
|
||||
|
||||
To see attributes versus DOM properties in a functioning app, see the <live-example name="binding-syntax"></live-example> especially for binding syntax.
|
||||
|
||||
### Example 2: a disabled button
|
||||
#### Example 2: a disabled button
|
||||
|
||||
The `disabled` attribute is another example. A button's `disabled`
|
||||
*property* is `false` by default so the button is enabled.
|
||||
@ -479,8 +466,7 @@ so the button is disabled.
|
||||
<button disabled>Test Button</button>
|
||||
```
|
||||
|
||||
Adding and removing the `disabled` *attribute* disables and
|
||||
enables the button.
|
||||
Adding and removing the `disabled` *attribute* disables and enables the button.
|
||||
However, the value of the *attribute* is irrelevant,
|
||||
which is why you cannot enable a button by writing `<button disabled="false">Still Disabled</button>`.
|
||||
|
||||
@ -488,7 +474,7 @@ To control the state of the button, set the `disabled` *property*,
|
||||
|
||||
<div class="alert is-helpful">
|
||||
|
||||
**Note:** Though you could technically set the `[attr.disabled]` attribute binding, the values are different in that the property binding requires to a boolean value, while its corresponding attribute binding relies on whether the value is `null` or not. Consider the following:
|
||||
Though you could technically set the `[attr.disabled]` attribute binding, the values are different in that the property binding requires to a boolean value, while its corresponding attribute binding relies on whether the value is `null` or not. Consider the following:
|
||||
|
||||
```html
|
||||
<input [disabled]="condition ? true : false">
|
||||
@ -499,26 +485,15 @@ Generally, use property binding over attribute binding as it is more intuitive (
|
||||
|
||||
</div>
|
||||
|
||||
**The HTML attribute and the DOM property are different things, even when they have the same name.**
|
||||
|
||||
**Template binding works with *properties* and *events*, not *attributes*.**
|
||||
|
||||
To see the `disabled` button example in a functioning app, see the <live-example name="binding-syntax"></live-example> especially for binding syntax. This example shows you how to toggle the disabled property from the component.
|
||||
|
||||
|
||||
### Angular and attributes
|
||||
|
||||
In Angular, the only role of attributes is to initialize element and directive state.
|
||||
When you write a data-binding, you're dealing exclusively with properties and events of the target object.
|
||||
|
||||
|
||||
## Binding targets
|
||||
## Binding types and targets
|
||||
|
||||
The **target of a data-binding** is something in the DOM.
|
||||
Depending on the binding type, the target can be a
|
||||
property (element, component, or directive), an
|
||||
event (element, component, or directive), or sometimes an attribute name.
|
||||
The following table summarizes:
|
||||
Depending on the binding type, the target can be a property (element, component, or directive),
|
||||
an event (element, component, or directive), or sometimes an attribute name.
|
||||
The following table summarizes the targets for the different binding types.
|
||||
|
||||
<style>
|
||||
td, th {vertical-align: top}
|
||||
@ -678,10 +653,9 @@ for parent and child components to communicate:
|
||||
|
||||
<code-example path="property-binding/src/app/app.component.html" region="model-property-binding" header="src/app/app.component.html"></code-example>
|
||||
|
||||
### Binding target
|
||||
### Binding targets
|
||||
|
||||
An element property between enclosing square brackets identifies
|
||||
the target property.
|
||||
An element property between enclosing square brackets identifies the target property.
|
||||
The target property in the following code is the image element's `src` property.
|
||||
|
||||
<code-example path="property-binding/src/app/app.component.html" region="property-binding" header="src/app/app.component.html"></code-example>
|
||||
@ -747,7 +721,7 @@ In the following example, the `childItem` property of the `ItemDetailComponent`
|
||||
<code-example path="property-binding/src/app/app.component.html" region="model-property-binding" header="src/app/app.component.html"></code-example>
|
||||
|
||||
You can confirm this by looking in the `ItemDetailComponent` where the `@Input` type is set to a string:
|
||||
<code-example path="property-binding/src/app/item-detail/item-detail.component.ts" region="input-type" header="src/app/item-detail/item-detail.component.ts (setting the @Input() type"></code-example>
|
||||
<code-example path="property-binding/src/app/item-detail/item-detail.component.ts" region="input-type" header="src/app/item-detail/item-detail.component.ts (setting the @Input() type)"></code-example>
|
||||
|
||||
As you can see here, the `parentItem` in `AppComponent` is a string, which the `ItemDetailComponent` expects:
|
||||
<code-example path="property-binding/src/app/app.component.ts" region="parent-data-type" header="src/app/app.component.ts"></code-example>
|
||||
@ -847,10 +821,10 @@ property binding but both approaches render the
|
||||
content harmlessly. The following is the browser output
|
||||
of the `evilTitle` examples.
|
||||
|
||||
```
|
||||
<code-example language="bash">
|
||||
"Template <script>alert("evil never sleeps")</script> Syntax" is the interpolated evil title.
|
||||
"Template alert("evil never sleeps")Syntax" is the property bound evil title.
|
||||
```
|
||||
</code-example>
|
||||
|
||||
<hr/>
|
||||
{@a other-bindings}
|
||||
@ -898,7 +872,7 @@ If you wrote something like this:
|
||||
|
||||
You'd get this error:
|
||||
|
||||
<code-example format="nocode">
|
||||
<code-example language="bash">
|
||||
Template parse errors:
|
||||
Can't bind to 'colspan' since it isn't a known native property
|
||||
</code-example>
|
||||
@ -968,8 +942,8 @@ The following example conditionally sets the font size in “em” and “%”
|
||||
|
||||
<code-example path="attribute-binding/src/app/app.component.html" region="style-binding-condition" header="src/app/app.component.html"></code-example>
|
||||
|
||||
**This technique is suitable for setting a single style, but consider
|
||||
the [`NgStyle`](guide/template-syntax#ngStyle) directive when setting several inline styles at the same time.**
|
||||
This technique is suitable for setting a single style, but consider
|
||||
the [`NgStyle`](guide/template-syntax#ngStyle) directive when setting several inline styles at the same time.
|
||||
|
||||
<div class="alert is-helpful">
|
||||
|
||||
@ -1156,7 +1130,7 @@ Angular desugars the `SizerComponent` binding into this:
|
||||
The `$event` variable contains the payload of the `SizerComponent.sizeChange` event.
|
||||
Angular assigns the `$event` value to the `AppComponent.fontSizePx` when the user clicks the buttons.
|
||||
|
||||
## Two-way binding in forms
|
||||
### Two-way binding in forms
|
||||
|
||||
The two-way binding syntax is a great convenience compared to
|
||||
separate property and event bindings. It would be convenient to
|
||||
@ -1417,7 +1391,7 @@ efficient alternative to showing/hiding.
|
||||
|
||||
<div class="alert is-helpful">
|
||||
|
||||
**Note:** For more information on `NgIf` and `ngIfElse`, see the [API documentation about NgIf](api/common/NgIf).
|
||||
For more information on `NgIf` and `ngIfElse`, see the [API documentation about NgIf](api/common/NgIf).
|
||||
|
||||
</div>
|
||||
|
||||
@ -1448,26 +1422,20 @@ See also the
|
||||
`NgFor` is a repeater directive—a way to present a list of items.
|
||||
You define a block of HTML that defines how a single item should be displayed
|
||||
and then you tell Angular to use that block as a template for rendering each item in the list.
|
||||
The text assigned to `*ngFor` is the instruction that guides the repeater process.
|
||||
|
||||
Here is an example of `NgFor` applied to a simple `<div>`:
|
||||
The following example shows `NgFor` applied to a simple `<div>`. (Don't forget the asterisk (`*`) in front of `ngFor`.)
|
||||
|
||||
<code-example path="built-in-directives/src/app/app.component.html" region="NgFor-1" header="src/app/app.component.html"></code-example>
|
||||
|
||||
You can also apply an `NgFor` to a component element, as in this example:
|
||||
You can also apply an `NgFor` to a component element, as in the following example.
|
||||
|
||||
<code-example path="built-in-directives/src/app/app.component.html" region="NgFor-2" header="src/app/app.component.html"></code-example>
|
||||
|
||||
<div class="alert is-critical">
|
||||
|
||||
Don't forget the asterisk (`*`) in front of `ngFor`.
|
||||
|
||||
</div>
|
||||
|
||||
The text assigned to `*ngFor` is the instruction that guides the repeater process.
|
||||
|
||||
{@a microsyntax}
|
||||
|
||||
#### `*ngFor` microsyntax
|
||||
<div class="callout is-critical">
|
||||
<header>*ngFor microsyntax</header>
|
||||
|
||||
The string assigned to `*ngFor` is not a [template expression](guide/template-syntax#template-expressions). Rather,
|
||||
it's a *microsyntax*—a little language of its own that Angular interprets.
|
||||
@ -1479,15 +1447,15 @@ make it available to the templated HTML for each iteration.*
|
||||
Angular translates this instruction into an `<ng-template>` around the host element,
|
||||
then uses this template repeatedly to create a new set of elements and bindings for each `item`
|
||||
in the list.
|
||||
|
||||
For more information about microsyntax, see the [Structural Directives](guide/structural-directives#microsyntax) guide.
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
{@a template-input-variable}
|
||||
|
||||
{@a template-input-variables}
|
||||
|
||||
|
||||
#### Template input variables
|
||||
|
||||
The `let` keyword before `item` creates a template input variable called `item`.
|
||||
@ -1930,7 +1898,7 @@ in the child template UI.
|
||||
|
||||
Now, in order to see the `@Output()` working, add the following to the parent's template:
|
||||
|
||||
```
|
||||
```html
|
||||
<ul>
|
||||
<li *ngFor="let item of items">{{item}}</li>
|
||||
</ul>
|
||||
@ -1989,7 +1957,7 @@ properties do indeed exist, double check
|
||||
that your properties are annotated with `@Input()` / `@Output()` or that you've declared
|
||||
them in an `inputs`/`outputs` array:
|
||||
|
||||
<code-example language="sh" class="code-shell">
|
||||
<code-example language="bash">
|
||||
Uncaught Error: Template parse errors:
|
||||
Can't bind to 'item' since it isn't a known property of 'app-item-detail'
|
||||
</code-example>
|
||||
@ -2067,7 +2035,7 @@ The generated output would look something like this:
|
||||
|
||||
<div class="alert is-helpful">
|
||||
|
||||
**Note**: The pipe operator has a higher precedence than the ternary operator (`?:`),
|
||||
The pipe operator has a higher precedence than the ternary operator (`?:`),
|
||||
which means `a ? b : c | x` is parsed as `a ? b : (c | x)`.
|
||||
Nevertheless, for a number of reasons,
|
||||
the pipe operator cannot be used without parentheses in the first and second operands of `?:`.
|
||||
@ -2097,7 +2065,7 @@ Consider the next example, with a `nullItem`.
|
||||
|
||||
Since there is no safe navigation operator and `nullItem` is `null`, JavaScript and Angular would throw a `null` reference error and break the rendering process of Angular:
|
||||
|
||||
<code-example format="nocode">
|
||||
<code-example language="bash">
|
||||
TypeError: Cannot read property 'name' of null.
|
||||
</code-example>
|
||||
|
||||
@ -2151,9 +2119,9 @@ The non-null assertion operator, `!`, is optional with the exception that you mu
|
||||
|
||||
### The `$any()` type cast function
|
||||
|
||||
Sometimes a binding expression triggers a type error during [AOT compilation](guide/aot-compiler) and it is not possible or difficult
|
||||
to fully specify the type. To silence the error, you can use the `$any()` cast function to cast
|
||||
the expression to [the `any` type](http://www.typescriptlang.org/docs/handbook/basic-types.html#any) as in the following example:
|
||||
Sometimes a binding expression triggers a type error during [AOT compilation](guide/aot-compiler) and it is not possible or difficult to fully specify the type.
|
||||
To silence the error, you can use the `$any()` cast function to cast
|
||||
the expression to the [`any` type](http://www.typescriptlang.org/docs/handbook/basic-types.html#any) as in the following example:
|
||||
|
||||
<code-example path="built-in-template-functions/src/app/app.component.html" region="any-type-cast-function-1" header="src/app/app.component.html"></code-example>
|
||||
|
||||
@ -2183,7 +2151,7 @@ Refer to the sample code snippet below for a syntax example:
|
||||
|
||||
<code-example path="template-syntax/src/app/svg.component.ts" header="src/app/svg.component.ts"></code-example>
|
||||
|
||||
Add the below code to your `svg.component.svg` file:
|
||||
Add the following code to your `svg.component.svg` file:
|
||||
|
||||
<code-example path="template-syntax/src/app/svg.component.svg" header="src/app/svg.component.svg"></code-example>
|
||||
|
||||
|
@ -472,8 +472,7 @@ which covers testing with the `HttpClientTestingModule` in detail.
|
||||
|
||||
A component, unlike all other parts of an Angular application,
|
||||
combines an HTML template and a TypeScript class.
|
||||
The component truly is the template and the class _working together_.
|
||||
and to adequately test a component, you should test that they work together
|
||||
The component truly is the template and the class _working together_. To adequately test a component, you should test that they work together
|
||||
as intended.
|
||||
|
||||
Such tests require creating the component's host element in the browser DOM,
|
||||
@ -1115,7 +1114,7 @@ The first is a sanity test; it confirms that the stubbed `UserService` is called
|
||||
<div class="alert is-helpful">
|
||||
|
||||
The second parameter to the Jasmine matcher (e.g., `'expected name'`) is an optional failure label.
|
||||
If the expectation fails, Jasmine displays appends this label to the expectation failure message.
|
||||
If the expectation fails, Jasmine appends this label to the expectation failure message.
|
||||
In a spec with multiple expectations, it can help clarify what went wrong and which expectation failed.
|
||||
|
||||
</div>
|
||||
@ -1138,7 +1137,7 @@ The `TwainComponent` displays Mark Twain quotes.
|
||||
region="template"
|
||||
header="app/twain/twain.component.ts (template)"></code-example>
|
||||
|
||||
Note that value of the component's `quote` property passes through an `AsyncPipe`.
|
||||
Note that the value of the component's `quote` property passes through an `AsyncPipe`.
|
||||
That means the property returns either a `Promise` or an `Observable`.
|
||||
|
||||
In this example, the `TwainComponent.getQuote()` method tells you that
|
||||
@ -1151,7 +1150,7 @@ the `quote` property returns an `Observable`.
|
||||
|
||||
The `TwainComponent` gets quotes from an injected `TwainService`.
|
||||
The component starts the returned `Observable` with a placeholder value (`'...'`),
|
||||
before the service can returns its first quote.
|
||||
before the service can return its first quote.
|
||||
|
||||
The `catchError` intercepts service errors, prepares an error message,
|
||||
and returns the placeholder value on the success channel.
|
||||
@ -1212,7 +1211,8 @@ value becomes available. The test must become _asynchronous_.
|
||||
|
||||
#### Async test with _fakeAsync()_
|
||||
|
||||
To use `fakeAsync()` functionality, you need to import `zone-testing`, for details, please read [setup guide](guide/setup#appendix-test-using-fakeasyncasync).
|
||||
To use `fakeAsync()` functionality, you must import `zone.js/dist/zone-testing` in your test setup file.
|
||||
If you created your project with the Angular CLI, `zone-testing` is already imported in `src/test.ts`.
|
||||
|
||||
The following test confirms the expected behavior when the service returns an `ErrorObservable`.
|
||||
|
||||
@ -1231,6 +1231,13 @@ The `fakeAsync()` function enables a linear coding style by running the test bod
|
||||
The test body appears to be synchronous.
|
||||
There is no nested syntax (like a `Promise.then()`) to disrupt the flow of control.
|
||||
|
||||
<div class="alert is-helpful">
|
||||
|
||||
Limitation: The `fakeAsync()` function won't work if the test body makes an `XMLHttpRequest` (XHR) call.
|
||||
XHR calls within a test are rare, but if you need to call XHR, see [`async()`](#async), below.
|
||||
|
||||
</div>
|
||||
|
||||
{@a tick}
|
||||
|
||||
#### The _tick()_ function
|
||||
@ -1238,9 +1245,9 @@ There is no nested syntax (like a `Promise.then()`) to disrupt the flow of contr
|
||||
You do have to call `tick()` to advance the (virtual) clock.
|
||||
|
||||
Calling `tick()` simulates the passage of time until all pending asynchronous activities finish.
|
||||
In this case, it waits for the error handler's `setTimeout()`;
|
||||
In this case, it waits for the error handler's `setTimeout()`.
|
||||
|
||||
The `tick()` function accepts milliseconds as parameter (defaults to 0 if not provided). The parameter represents how much the virtual clock advances. For example, if you have a `setTimeout(fn, 100)` in a `fakeAsync()` test, you need to use tick(100) to trigger the fn callback.
|
||||
The `tick()` function accepts milliseconds as a parameter (defaults to 0 if not provided). The parameter represents how much the virtual clock advances. For example, if you have a `setTimeout(fn, 100)` in a `fakeAsync()` test, you need to use tick(100) to trigger the fn callback.
|
||||
|
||||
<code-example
|
||||
path="testing/src/app/demo/async-helper.spec.ts"
|
||||
@ -1264,7 +1271,7 @@ It's a companion to `fakeAsync()` and you can only call it within a `fakeAsync()
|
||||
Jasmine also provides a `clock` feature to mock dates. Angular automatically runs tests that are run after
|
||||
`jasmine.clock().install()` is called inside a `fakeAsync()` method until `jasmine.clock().uninstall()` is called. `fakeAsync()` is not needed and throws an error if nested.
|
||||
|
||||
By default, this feature is disabled. To enable it, set a global flag before import `zone-testing`.
|
||||
By default, this feature is disabled. To enable it, set a global flag before importing `zone-testing`.
|
||||
|
||||
If you use the Angular CLI, configure this flag in `src/test.ts`.
|
||||
|
||||
@ -1309,7 +1316,7 @@ If you run other `macroTask` such as `HTMLCanvasElement.toBlob()`, `Unknown macr
|
||||
</code-pane>
|
||||
</code-tabs>
|
||||
|
||||
If you want to support such case, you need to define the `macroTask` you want to support in `beforeEach()`.
|
||||
If you want to support such a case, you need to define the `macroTask` you want to support in `beforeEach()`.
|
||||
For example:
|
||||
|
||||
```javascript
|
||||
@ -1338,7 +1345,7 @@ it('toBlob should be able to run in fakeAsync', fakeAsync(() => {
|
||||
|
||||
You might be satisfied with the test coverage of these tests.
|
||||
|
||||
But you might be troubled by the fact that the real service doesn't quite behave this way.
|
||||
However, you might be troubled by the fact that the real service doesn't quite behave this way.
|
||||
The real service sends requests to a remote server.
|
||||
A server takes time to respond and the response certainly won't be available immediately
|
||||
as in the previous two tests.
|
||||
@ -1353,9 +1360,8 @@ from the `getQuote()` spy like this.
|
||||
|
||||
#### Async observable helpers
|
||||
|
||||
The async observable was produced by an `asyncData` helper
|
||||
The `asyncData` helper is a utility function that you'll have to write yourself.
|
||||
Or you can copy this one from the sample code.
|
||||
The async observable was produced by an `asyncData` helper.
|
||||
The `asyncData` helper is a utility function that you'll have to write yourself, or you can copy this one from the sample code.
|
||||
|
||||
<code-example
|
||||
path="testing/src/testing/async-observable-helpers.ts"
|
||||
@ -1406,13 +1412,13 @@ Then you can assert that the quote element displays the expected text.
|
||||
|
||||
#### Async test with _async()_
|
||||
|
||||
To use `async()` functionality, you need to import `zone-testing`, for details, please read [setup guide](guide/setup#appendix-test-using-fakeasyncasync).
|
||||
To use `async()` functionality, you must import `zone.js/dist/zone-testing` in your test setup file.
|
||||
If you created your project with the Angular CLI, `zone-testing` is already imported in `src/test.ts`.
|
||||
|
||||
The `fakeAsync()` utility function has a few limitations.
|
||||
In particular, it won't work if the test body makes an `XHR` call.
|
||||
|
||||
`XHR` calls within a test are rare so you can generally stick with `fakeAsync()`.
|
||||
But if you ever do need to call `XHR`, you'll want to know about `async()`.
|
||||
In particular, it won't work if the test body makes an `XMLHttpRequest` (XHR) call.
|
||||
XHR calls within a test are rare so you can generally stick with [`fakeAsync()`](#fake-async).
|
||||
But if you ever do need to call `XMLHttpRequest`, you'll want to know about `async()`.
|
||||
|
||||
<div class="alert is-helpful">
|
||||
|
||||
@ -1467,8 +1473,7 @@ is `undefined`.
|
||||
|
||||
Now you are responsible for chaining promises, handling errors, and calling `done()` at the appropriate moments.
|
||||
|
||||
Writing test functions with `done()`, is more cumbersome than `async()`and `fakeAsync()`.
|
||||
But it is occasionally necessary when code involves the `intervalTimer()` like `setInterval`.
|
||||
Writing test functions with `done()`, is more cumbersome than `async()`and `fakeAsync()`, but it is occasionally necessary when code involves the `intervalTimer()` like `setInterval`.
|
||||
|
||||
Here are two more versions of the previous test, written with `done()`.
|
||||
The first one subscribes to the `Observable` exposed to the template by the component's `quote` property.
|
||||
@ -1950,7 +1955,7 @@ You'll take a different approach with `ActivatedRoute` because
|
||||
- `paramMap` returns an `Observable` that can emit more than one value
|
||||
during a test.
|
||||
- You need the router helper function, `convertToParamMap()`, to create a `ParamMap`.
|
||||
- Other _routed components_ tests need a test double for `ActivatedRoute`.
|
||||
- Other _routed component_ tests need a test double for `ActivatedRoute`.
|
||||
|
||||
These differences argue for a re-usable stub class.
|
||||
|
||||
@ -2750,7 +2755,7 @@ Debug specs in the browser in the same way that you debug an application.
|
||||
|
||||
1. Reveal the Karma browser window (hidden earlier).
|
||||
1. Click the **DEBUG** button; it opens a new browser tab and re-runs the tests.
|
||||
1. Open the browser's “Developer Tools” (`Ctrl-Shift-I` on windows; `Command-Option-I` in OSX).
|
||||
1. Open the browser's “Developer Tools” (`Ctrl-Shift-I` on Windows; `Command-Option-I` in macOS).
|
||||
1. Pick the "sources" section.
|
||||
1. Open the `1st.spec.ts` test file (Control/Command-P, then start typing the name of the file).
|
||||
1. Set a breakpoint in the test.
|
||||
@ -3246,7 +3251,7 @@ Here are the most useful methods for testers.
|
||||
Angular can't see that you've changed `personComponent.name` and won't update the `name`
|
||||
binding until you call `detectChanges`.
|
||||
|
||||
Runs `checkNoChanges`afterwards to confirm that there are no circular updates unless
|
||||
Runs `checkNoChanges` afterwards to confirm that there are no circular updates unless
|
||||
called as `detectChanges(false)`;
|
||||
|
||||
</td>
|
||||
|
@ -227,7 +227,7 @@ The code for this color change might look like this.
|
||||
|
||||
### Offset
|
||||
|
||||
Keyframes include an *offset* that defines the point in the animation where each style change occurs. Offsets are relative measures from zero to one, marking the beginning and end of the animation, respectively.
|
||||
Keyframes include an *offset* that defines the point in the animation where each style change occurs. Offsets are relative measures from zero to one, marking the beginning and end of the animation, respectively and should be applied to each of the keyframe's steps if used at least once.
|
||||
|
||||
Defining offsets for keyframes is optional. If you omit them, evenly spaced offsets are automatically assigned. For example, three keyframes without predefined offsets receive offsets of 0, 0.5, and 1. Specifying an offset of 0.8 for the middle transition in the above example might look like this.
|
||||
|
||||
|
@ -28,9 +28,34 @@ For details about `tsconfig.json`, see the official
|
||||
|
||||
</div>
|
||||
|
||||
The [Setup](guide/setup-local) guide uses the following `tsconfig.json`:
|
||||
The initial `tsconfig.json` for an Angular app typically looks like this example:
|
||||
|
||||
|
||||
<code-example lang="json" header="tsconfig.json" linenums="false">
|
||||
{
|
||||
"compileOnSave": false,
|
||||
"compilerOptions": {
|
||||
"compilerOptions": {
|
||||
"baseUrl": "./",
|
||||
"outDir": "./dist/out-tsc",
|
||||
"sourceMap": true,
|
||||
"declaration": false,
|
||||
"module": "es2015",
|
||||
"moduleResolution": "node",
|
||||
"emitDecoratorMetadata": true,
|
||||
"experimentalDecorators": true,
|
||||
"importHelpers": true,
|
||||
"target": "es5",
|
||||
"typeRoots": [
|
||||
"node_modules/@types"
|
||||
],
|
||||
"lib": [
|
||||
"es2018",
|
||||
"dom"
|
||||
]
|
||||
}
|
||||
</code-example>
|
||||
|
||||
<code-example path="getting-started/tsconfig.0.json" header="tsconfig.json"></code-example>
|
||||
|
||||
This file contains options and flags that are essential for Angular applications.
|
||||
|
||||
|
@ -203,9 +203,9 @@ One solution is to provide the full URL to your application on the server, and w
|
||||
value and prepend it to the request URL. If you're using the `ngExpressEngine`, as shown in the example in this guide, half
|
||||
the work is already done. We'll assume this is the case, but it's trivial to provide the same functionality.
|
||||
|
||||
Start by creating an [HttpInterceptor](api/common/http/HttpInterceptor):
|
||||
Start by creating an [HttpInterceptor](api/common/http/HttpInterceptor).
|
||||
|
||||
<code-example language="typescript">
|
||||
<code-example language="typescript" header="universal-interceptor.ts">
|
||||
|
||||
import {Injectable, Inject, Optional} from '@angular/core';
|
||||
import {HttpInterceptor, HttpHandler, HttpRequest, HttpHeaders} from '@angular/common/http';
|
||||
@ -233,9 +233,9 @@ export class UniversalInterceptor implements HttpInterceptor {
|
||||
|
||||
</code-example>
|
||||
|
||||
Next, provide the interceptor in the providers for the server `AppModule` (app.server.module.ts):
|
||||
Next, provide the interceptor in the providers for the server `AppModule`.
|
||||
|
||||
<code-example language="typescript">
|
||||
<code-example language="typescript" header="app.server.module.ts">
|
||||
|
||||
import {HTTP_INTERCEPTORS} from '@angular/common/http';
|
||||
import {UniversalInterceptor} from './universal-interceptor';
|
||||
|
@ -216,7 +216,7 @@ the recipe.
|
||||
|
||||
In order to start using any `upgrade/static` APIs, you still need to load the Angular framework as
|
||||
you would in a normal Angular app. You can see how this can be done with SystemJS by following the
|
||||
instructions in the [Setup](guide/setup) guide, selectively copying code from the
|
||||
instructions in the [Upgrade Setup](guide/upgrade-setup "Setup for Upgrading from AngularJS") guide, selectively copying code from the
|
||||
[QuickStart github repository](https://github.com/angular/quickstart).
|
||||
|
||||
You also need to install the `@angular/upgrade` package via `npm install @angular/upgrade --save`
|
||||
|
@ -1,15 +1,15 @@
|
||||
# Setup for Upgrading from AngularJS
|
||||
|
||||
<!--
|
||||
<!--
|
||||
Question: Can we remove this file and instead direct readers to https://github.com/angular/quickstart/blob/master/README.md
|
||||
-->
|
||||
|
||||
<div class="alert is-critical">
|
||||
|
||||
**Audience:** Use this guide **only** in the context of [Upgrading from AngularJS](guide/upgrade "Upgrading from AngularJS to Angular") or [Upgrading for Performance](guide/upgrade-performance "Upgrading for Performance").
|
||||
Those Upgrade guides refer to this Setup guide for information about using the [deprecated QuickStart GitHub repository](https://github.com/angular/quickstart "Deprecated Angular QuickStart GitHub repository"), which was created prior to the current Angular [CLI](cli "CLI Overview").
|
||||
**Audience:** Use this guide **only** in the context of [Upgrading from AngularJS](guide/upgrade "Upgrading from AngularJS to Angular") or [Upgrading for Performance](guide/upgrade-performance "Upgrading for Performance").
|
||||
Those Upgrade guides refer to this Setup guide for information about using the [deprecated QuickStart GitHub repository](https://github.com/angular/quickstart "Deprecated Angular QuickStart GitHub repository"), which was created prior to the current Angular [CLI](cli "CLI Overview").
|
||||
|
||||
**For all other scenarios,** see the current instructions in [Local Environment Setup](guide/setup-local "Setting up for Local Development").
|
||||
**For all other scenarios,** see the current instructions in [Setting up the Local Environment and Workspace](guide/setup-local "Setting up for Local Development").
|
||||
|
||||
|
||||
</div>
|
||||
@ -139,6 +139,11 @@ Consequently, there are many files in the project folder on your machine,
|
||||
most of which you can [learn about later](guide/file-structure).
|
||||
|
||||
|
||||
<div class="alert is-helpful">
|
||||
|
||||
**Reminder:** The "QuickStart seed" example was created prior to the Angular CLI, so there are some differences between what is described here and an Angular CLI application.
|
||||
|
||||
</div>
|
||||
|
||||
{@a app-files}
|
||||
|
||||
@ -265,8 +270,8 @@ The following are all in `src/`
|
||||
|
||||
|
||||
Defines `AppModule`, the [root module](guide/bootstrapping "AppModule: the root module") that tells Angular how to assemble the application.
|
||||
Right now it declares only the `AppComponent`.
|
||||
Soon there will be more components to declare.
|
||||
When initially created, it declares only the `AppComponent`.
|
||||
Over time, you add more components to declare.
|
||||
</td>
|
||||
|
||||
</tr>
|
||||
@ -284,8 +289,8 @@ The following are all in `src/`
|
||||
[bootstraps](guide/bootstrapping)
|
||||
the application's main module (`AppModule`) to run in the browser.
|
||||
The JIT compiler is a reasonable choice during the development of most projects and
|
||||
it's the only viable choice for a sample running in a _live-coding_ environment like Stackblitz.
|
||||
You'll learn about alternative compiling and [deployment](guide/deployment) options later in the documentation.
|
||||
it's the only viable choice for a sample running in a _live-coding_ environment such as Stackblitz.
|
||||
Alternative [compilation](guide/aot-compiler), [build](guide/build), and [deployment](guide/deployment) options are available.
|
||||
|
||||
</td>
|
||||
|
||||
@ -294,43 +299,6 @@ The following are all in `src/`
|
||||
</table>
|
||||
|
||||
|
||||
|
||||
<div class="alert is-helpful">
|
||||
|
||||
|
||||
|
||||
### Next Step
|
||||
|
||||
If you're new to Angular, we recommend you follow the [tutorial](tutorial "Tour of Heroes tutorial").
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
<br></br><br></br>
|
||||
|
||||
{@a install-prerequisites}
|
||||
|
||||
|
||||
|
||||
## Appendix: Node.js and npm
|
||||
|
||||
|
||||
[Node.js](https://nodejs.org/en/) and the [npm](https://www.npmjs.com/) package manager are essential to modern web development with Angular and other platforms.
|
||||
Node.js powers client development and build tools.
|
||||
The _npm_ package manager, which is itself a _Node.js_ application, installs JavaScript libraries.
|
||||
|
||||
<a href="https://docs.npmjs.com/getting-started/installing-node" target="_blank" title="Installing Node.js and updating npm">
|
||||
Get them now</a> if they're not already installed on your machine.
|
||||
|
||||
**Verify that you are running Node.js `v8.x` or higher and npm `5.x` or higher**
|
||||
by running the commands `node -v` and `npm -v` in a terminal/console window.
|
||||
Older versions produce errors.
|
||||
|
||||
We recommend [nvm](https://github.com/creationix/nvm) for managing multiple versions of Node.js and npm.
|
||||
You may need [nvm](https://github.com/creationix/nvm) if you already have projects running on your machine that use other versions of Node.js and npm.
|
||||
|
||||
|
||||
|
||||
## Appendix: Develop locally with IE
|
||||
|
||||
If you develop angular locally with `ng serve`, a `websocket` connection is set up automatically between browser and local dev server, so when your code changes, the browser can automatically refresh.
|
@ -422,8 +422,7 @@ will result in the same thing:
|
||||
</code-example>
|
||||
|
||||
To begin converting your AngularJS application to a hybrid, you need to load the Angular framework.
|
||||
You can see how this can be done with SystemJS by following the instructions in [Setup](guide/setup),
|
||||
selectively copying code from the [QuickStart github repository](https://github.com/angular/quickstart).
|
||||
You can see how this can be done with SystemJS by following the instructions in [Setup for Upgrading to AngularJS](guide/upgrade-setup) for selectively copying code from the [QuickStart github repository](https://github.com/angular/quickstart).
|
||||
|
||||
You also need to install the `@angular/upgrade` package via `npm install @angular/upgrade --save`
|
||||
and add a mapping for the `@angular/upgrade/static` package:
|
||||
@ -1311,7 +1310,7 @@ Turn to the [Angular animations](guide/animations) guide to learn about that.
|
||||
</div>
|
||||
|
||||
Install Angular into the project, along with the SystemJS module loader.
|
||||
Take a look at the results of the [Setup](guide/setup) instructions
|
||||
Take a look at the results of the [upgrade setup instructions](guide/upgrade-setup)
|
||||
and get the following configurations from there:
|
||||
|
||||
* Add Angular and the other new dependencies to `package.json`
|
||||
@ -1352,7 +1351,7 @@ to load the actual application:
|
||||
</code-example>
|
||||
|
||||
You also need to make a couple of adjustments
|
||||
to the `systemjs.config.js` file installed during [setup](guide/setup).
|
||||
to the `systemjs.config.js` file installed during [upgrade setup](guide/upgrade-setup).
|
||||
|
||||
Point the browser to the project root when loading things through SystemJS,
|
||||
instead of using the `<base>` URL.
|
||||
|
@ -13,16 +13,16 @@ Running this command will:
|
||||
- configure your project to use Web Workers, if it isn't already.
|
||||
- add `src/app/app.worker.ts` with scaffolded code to receive messages:
|
||||
|
||||
```typescript
|
||||
<code-example language="typescript" header="src/app/app.worker.ts">
|
||||
addEventListener('message', ({ data }) => {
|
||||
const response = `worker response to ${data}`;
|
||||
postMessage(response);
|
||||
});
|
||||
```
|
||||
</code-example>
|
||||
|
||||
- add scaffolded code to `src/app/app.component.ts` to use the worker:
|
||||
|
||||
```typescript
|
||||
<code-example language="typescript" header="src/app/app.component.ts">
|
||||
if (typeof Worker !== 'undefined') {
|
||||
// Create a new
|
||||
const worker = new Worker('./app.worker', { type: 'module' });
|
||||
@ -34,7 +34,7 @@ Running this command will:
|
||||
// Web Workers are not supported in this environment.
|
||||
// You should add a fallback so that your program still executes correctly.
|
||||
}
|
||||
```
|
||||
</code-example>
|
||||
|
||||
After the initial scaffolding, you will need to refactor your code to use the Web Worker by sending messages to and from.
|
||||
|
||||
|
BIN
aio/content/images/bios/yurzui.jpg
Normal file
BIN
aio/content/images/bios/yurzui.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 30 KiB |
@ -808,5 +808,13 @@
|
||||
"website": "https://github.com/kamilmysliwiec",
|
||||
"bio": "Kamil Mysliwiec is a software engineer truly passionate about Web Technologies. Creator of NestJS, Co-Founder of Trilon.io, speaker, and trainer.",
|
||||
"groups": ["GDE"]
|
||||
},
|
||||
"yurzui": {
|
||||
"name": "Alexey Zuev",
|
||||
"picture": "yurzui.jpg",
|
||||
"twitter": "yurzui",
|
||||
"website": "https://medium.com/@a.yurich.zuev",
|
||||
"bio": "Alexey is a web development addict who likes diving deep into the source code and sharing his knowledge through visualization. Creator of ng-run.com",
|
||||
"groups": ["GDE"]
|
||||
}
|
||||
}
|
||||
|
@ -3,47 +3,56 @@
|
||||
</header>
|
||||
|
||||
<article>
|
||||
<p>Where we'll be presenting:</p>
|
||||
<p>Where we'll be presenting:</p>
|
||||
<table class="is-full-width">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Event</th>
|
||||
<th>Location</th>
|
||||
<th>Date</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Event</th>
|
||||
<th>Location</th>
|
||||
<th>Date</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<!-- NG-DE 2019-->
|
||||
<tr>
|
||||
<th><a href="https://ng-de.org/" title="NG-DE">NG-DE</a></th>
|
||||
<td>Berlin, Germany</td>
|
||||
<td>August 29th workshops, 30-31 conference, 2019</td>
|
||||
</tr>
|
||||
<!-- AngularConnect 2019-->
|
||||
<tr>
|
||||
<th><a href="https://www.angularconnect.com/?utm_source=angular.io&utm_medium=referral" title="AngularConnect">AngularConnect</a></th>
|
||||
<td>London, UK</td>
|
||||
<td>September 19-20, 2019</td>
|
||||
</tr>
|
||||
<!-- ReactiveConf 2019 -->
|
||||
<tr>
|
||||
<th><a href="https://reactiveconf.com/" title="ReactiveConf">ReactiveConf</a></th>
|
||||
<td>Prague, Czech Republic</td>
|
||||
<td>October 30 - November 1, 2019</td>
|
||||
</tr>
|
||||
<!-- AngularConnect 2019-->
|
||||
<tr>
|
||||
<th><a href="https://www.angularconnect.com/?utm_source=angular.io&utm_medium=referral" title="AngularConnect">AngularConnect</a></th>
|
||||
<td>London, UK</td>
|
||||
<td>September 19-20, 2019</td>
|
||||
</tr>
|
||||
<!-- NG Rome 2019-->
|
||||
<tr>
|
||||
<th>
|
||||
<a href="https://ngrome.io"
|
||||
title="NG Rome MMXIX - The Italian Angular Conference">NG Rome MMXIX</a>
|
||||
</th>
|
||||
<td>Rome, Italy</td>
|
||||
<td>Oct 6th workshops, 7th conference, 2019</td>
|
||||
</tr>
|
||||
<!-- ReactiveConf 2019 -->
|
||||
<tr>
|
||||
<th><a href="https://reactiveconf.com/" title="ReactiveConf">ReactiveConf</a></th>
|
||||
<td>Prague, Czech Republic</td>
|
||||
<td>October 30 - November 1, 2019</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<p>Where we already presented:</p>
|
||||
<table class="is-full-width">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Event</th>
|
||||
<th>Location</th>
|
||||
<th>Date</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Event</th>
|
||||
<th>Location</th>
|
||||
<th>Date</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<!-- NG-DE 2019-->
|
||||
<tr>
|
||||
<th><a href="https://ng-de.org/" title="NG-DE">NG-DE</a></th>
|
||||
<td>Berlin, Germany</td>
|
||||
<td>August 29th workshops, 30-31 conference, 2019</td>
|
||||
</tr>
|
||||
<!-- ngJapan-->
|
||||
<tr>
|
||||
<th><a href="https://ngjapan.org" title="ng-japan">ng-japan</a></th>
|
||||
@ -76,7 +85,10 @@
|
||||
</tr>
|
||||
<!-- AngularConnect-->
|
||||
<tr>
|
||||
<th><a href="https://past.angularconnect.com/2018" title="AngularConnect">AngularConnect</a></th>
|
||||
<th>
|
||||
<a href="https://past.angularconnect.com/2018"
|
||||
title="AngularConnect">AngularConnect</a>
|
||||
</th>
|
||||
<td>London, United Kingdom</td>
|
||||
<td>November 5-7, 2018</td>
|
||||
</tr>
|
||||
@ -91,43 +103,46 @@
|
||||
<th><a href="https://angularmix.com/" title="AngularMix">AngularMix</a></th>
|
||||
<td>Orlando, Florida</td>
|
||||
<td>October 10-12, 2018</td>
|
||||
</tr>
|
||||
<!-- Angular Conf Australia-->
|
||||
<tr>
|
||||
<th><a href="https://www.angularconf.com.au/" title="Angular Conf Australia">Angular Conf Australia</a></th>
|
||||
<td>Melbourne, Australia</td>
|
||||
<td>Jun 22, 2018</td>
|
||||
</tr>
|
||||
<!-- ngJapan-->
|
||||
<tr>
|
||||
<th><a href="https://ngjapan.org/en.html" title="ng-japan">ng-japan</a></th>
|
||||
<td>Tokyo, Japan</td>
|
||||
<td>Jun 16, 2018</td>
|
||||
</tr>
|
||||
<!-- WeRDevs-->
|
||||
<tr>
|
||||
<th><a href="https://www.wearedevelopers.com/" title="WeAreDevs">WeAreDevelopers</a></th>
|
||||
<td>Vienna, Austria</td>
|
||||
<td>May 16-18, 2018</td>
|
||||
</tr>
|
||||
<!-- ngconf 2018-->
|
||||
<tr>
|
||||
<th><a href="https://www.ng-conf.org/" title="ng-conf">ng-conf</a></th>
|
||||
<td>Salt Lake City, Utah</td>
|
||||
<td>April 18-20, 2018</td>
|
||||
</tr>
|
||||
<!-- ngVikings-->
|
||||
<tr>
|
||||
<th><a href="https://ngvikings.org/" title="ngVikings">ngVikings</a></th>
|
||||
<td>Helsinki, Finland</td>
|
||||
<td>March 1-2, 2018</td>
|
||||
</tr>
|
||||
<!-- ngAtlanta-->
|
||||
<tr>
|
||||
<th><a href="http://ng-atl.org/" title="ngAtlanta">ngAtlanta</a></th>
|
||||
<td>Atlanta, Georgia</td>
|
||||
<td>January 30, 2018</td>
|
||||
</tr>
|
||||
</tr>
|
||||
<!-- Angular Conf Australia-->
|
||||
<tr>
|
||||
<th>
|
||||
<a href="https://www.angularconf.com.au/"
|
||||
title="Angular Conf Australia">Angular Conf Australia</a>
|
||||
</th>
|
||||
<td>Melbourne, Australia</td>
|
||||
<td>Jun 22, 2018</td>
|
||||
</tr>
|
||||
<!-- ngJapan-->
|
||||
<tr>
|
||||
<th><a href="https://ngjapan.org/en.html" title="ng-japan">ng-japan</a></th>
|
||||
<td>Tokyo, Japan</td>
|
||||
<td>Jun 16, 2018</td>
|
||||
</tr>
|
||||
<!-- WeRDevs-->
|
||||
<tr>
|
||||
<th><a href="https://www.wearedevelopers.com/" title="WeAreDevs">WeAreDevelopers</a></th>
|
||||
<td>Vienna, Austria</td>
|
||||
<td>May 16-18, 2018</td>
|
||||
</tr>
|
||||
<!-- ngconf 2018-->
|
||||
<tr>
|
||||
<th><a href="https://www.ng-conf.org/" title="ng-conf">ng-conf</a></th>
|
||||
<td>Salt Lake City, Utah</td>
|
||||
<td>April 18-20, 2018</td>
|
||||
</tr>
|
||||
<!-- ngVikings-->
|
||||
<tr>
|
||||
<th><a href="https://ngvikings.org/" title="ngVikings">ngVikings</a></th>
|
||||
<td>Helsinki, Finland</td>
|
||||
<td>March 1-2, 2018</td>
|
||||
</tr>
|
||||
<!-- ngAtlanta-->
|
||||
<tr>
|
||||
<th><a href="http://ng-atl.org/" title="ngAtlanta">ngAtlanta</a></th>
|
||||
<td>Atlanta, Georgia</td>
|
||||
<td>January 30, 2018</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</article>
|
||||
|
@ -115,6 +115,12 @@
|
||||
"title": "NGXS",
|
||||
"url": "https://ngxs.io/"
|
||||
},
|
||||
"akita": {
|
||||
"desc": "Akita is a state management pattern, built on top of RxJS, which takes the idea of multiple data stores from Flux and the immutable updates from Redux, along with the concept of streaming data, to create the Observable Data Store model.",
|
||||
"rev": true,
|
||||
"title": "Akita",
|
||||
"url": "https://netbasal.gitbook.io/akita/"
|
||||
},
|
||||
"ab": {
|
||||
"desc": "The official library for Firebase and Angular",
|
||||
"logo": "",
|
||||
@ -265,6 +271,12 @@
|
||||
"title": "Angular UI Toolkit",
|
||||
"url": "https://www.angular-ui-tools.com"
|
||||
},
|
||||
"SenchaforAngular": {
|
||||
"desc": "Build modern web apps faster with 115+ pre-built UI components. Try for free and download today.",
|
||||
"rev": true,
|
||||
"title": "Sencha for Angular",
|
||||
"url": "https://www.sencha.com/products/extangular/"
|
||||
},
|
||||
"IgniteUIforAngular": {
|
||||
"desc": "Ignite UI for Angular is a dependency-free Angular toolkit for building modern web apps.",
|
||||
"rev": true,
|
||||
@ -649,6 +661,12 @@
|
||||
"rev": true,
|
||||
"title": "web.dev/angular",
|
||||
"url": "https://web.dev/angular"
|
||||
},
|
||||
"mdb-angular-boilerplate": {
|
||||
"desc": "Angular CRUD application starter with NgRx state management, Firebase backend and installation guide.",
|
||||
"rev": true,
|
||||
"title": "MDB Angular Boilerplate",
|
||||
"url": "https://github.com/mdbootstrap/Angular-Bootstrap-Boilerplate"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -506,6 +506,12 @@
|
||||
"title": "Upgrading from AngularJS",
|
||||
"tooltip": "Incrementally upgrade an AngularJS application to Angular.",
|
||||
"children": [
|
||||
{
|
||||
"url": "guide/upgrade-setup",
|
||||
"title": "Setup for Upgrading from AngularJS",
|
||||
"tooltip": "Use code from the Angular QuickStart seed as part of upgrading from AngularJS.",
|
||||
"hidden": true
|
||||
},
|
||||
{
|
||||
"url": "guide/upgrade",
|
||||
"title": "Upgrading Instructions",
|
||||
@ -587,21 +593,32 @@
|
||||
"tooltip": "Build, testing, and deployment information.",
|
||||
"children": [
|
||||
{
|
||||
"url": "guide/setup",
|
||||
"url": "guide/upgrade-setup",
|
||||
"title": "Upgrade setup",
|
||||
"tooltip": "How to set up the Angular QuickStart seed in the context of upgrading from AngularJS.",
|
||||
"hidden": true
|
||||
},
|
||||
{
|
||||
"url": "guide/aot-compiler",
|
||||
"title": "Ahead-of-Time Compilation",
|
||||
"tooltip": "Learn why and how to use the Ahead-of-Time (AOT) compiler."
|
||||
},
|
||||
{
|
||||
"url": "guide/angular-compiler-options",
|
||||
"title": "Compiler Options",
|
||||
"tooltip": "Configuration options for the AOT compiler."
|
||||
},
|
||||
"title": "AoT Compiler",
|
||||
"tooltip": "Understanding ahead-of-time compilation.",
|
||||
"children": [
|
||||
{
|
||||
"url": "guide/aot-compiler",
|
||||
"title": "Ahead-of-Time Compilation",
|
||||
"tooltip": "Learn why and how to use the Ahead-of-Time (AoT) compiler."
|
||||
},
|
||||
{
|
||||
"url": "guide/angular-compiler-options",
|
||||
"title": "Angular Compiler Options",
|
||||
"tooltip": "Configuring AoT compilation."
|
||||
},
|
||||
{
|
||||
"url": "guide/aot-metadata-errors",
|
||||
"title": "AoT Metadata Errors",
|
||||
"tooltip": "Troubleshooting AoT compilation."
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"url": "guide/build",
|
||||
"title": "Building & Serving",
|
||||
|
@ -46,7 +46,7 @@ Replace it with the following:
|
||||
|
||||
First, `AppRoutingModule` imports `RouterModule` and `Routes` so the app can have routing functionality. The next import, `HeroesComponent`, will give the Router somewhere to go once you configure the routes.
|
||||
|
||||
Notice that the `CommonModule` references and `declarations` array are unecessary, so are no
|
||||
Notice that the `CommonModule` references and `declarations` array are unnecessary, so are no
|
||||
longer part of `AppRoutingModule`. The following sections explain the rest of the `AppRoutingModule` in more detail.
|
||||
|
||||
|
||||
|
@ -26,6 +26,7 @@
|
||||
{"type": 301, "source": "/guide/service-worker-comm", "destination": "/guide/service-worker-communications"},
|
||||
{"type": 301, "source": "/guide/service-worker-configref", "destination": "/guide/service-worker-config"},
|
||||
{"type": 301, "source": "/guide/webpack", "destination": "https://v5.angular.io/guide/webpack"},
|
||||
{"type": 301, "source": "/guide/setup", "destination": "/guide/setup-local"},
|
||||
{"type": 301, "source": "/guide/setup-systemjs-anatomy", "destination": "/guide/file-structure"},
|
||||
{"type": 301, "source": "/guide/change-log", "destination": "https://github.com/angular/angular/blob/master/CHANGELOG.md"},
|
||||
{"type": 301, "source": "/guide/quickstart", "destination": "/start"},
|
||||
|
@ -122,6 +122,8 @@
|
||||
"!/guide/webpack",
|
||||
"!/guide/webpack.html",
|
||||
"!/guide/webpack/",
|
||||
"!/guide/setup",
|
||||
"!/guide/setup.html",
|
||||
"!/guide/setup-systemjs-anatomy",
|
||||
"!/guide/setup-systemjs-anatomy.html",
|
||||
"!/guide/quickstart",
|
||||
|
@ -23,7 +23,7 @@
|
||||
"build-with-ivy": "yarn ~~build",
|
||||
"prebuild-with-ivy-ci": "yarn setup-local --no-build-packages && node scripts/switch-to-ivy",
|
||||
"build-with-ivy-ci": "yarn ~~build --progress=false",
|
||||
"extract-cli-command-docs": "node tools/transforms/cli-docs-package/extract-cli-commands.js e0055d293",
|
||||
"extract-cli-command-docs": "node tools/transforms/cli-docs-package/extract-cli-commands.js e21aeeecd",
|
||||
"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",
|
||||
@ -79,7 +79,7 @@
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10.9.0 <11.0.0",
|
||||
"yarn": ">=1.12.1 <=1.16.0"
|
||||
"yarn": ">=1.17.3 <=1.18.0"
|
||||
},
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
|
@ -1,87 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
// Imports
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
// Constants
|
||||
const PROJECT_ROOT_DIR = path.resolve(__dirname, '../..');
|
||||
const CODEOWNERS_PATH = path.resolve(PROJECT_ROOT_DIR, '.github/CODEOWNERS');
|
||||
const AIO_CONTENT_DIR = path.resolve(PROJECT_ROOT_DIR, 'aio/content');
|
||||
const AIO_GUIDES_DIR = path.resolve(AIO_CONTENT_DIR, 'guide');
|
||||
const AIO_GUIDE_IMAGES_DIR = path.resolve(AIO_CONTENT_DIR, 'images/guide');
|
||||
const AIO_GUIDE_EXAMPLES_DIR = path.resolve(AIO_CONTENT_DIR, 'examples');
|
||||
|
||||
// Run
|
||||
_main();
|
||||
|
||||
// Functions - Definitions
|
||||
function _main() {
|
||||
const {guides: acGuidePaths, images: acGuideImagesPaths, examples: acExamplePaths} = getPathsFromAioContent();
|
||||
const {guides: coGuidePaths, images: coGuideImagesPaths, examples: coExamplePaths} = getPathsFromCodeowners();
|
||||
|
||||
const guidesDiff = arrayDiff(acGuidePaths, coGuidePaths);
|
||||
const imagesDiff = arrayDiff(acGuideImagesPaths, coGuideImagesPaths);
|
||||
const examplesDiff = arrayDiff(acExamplePaths, coExamplePaths);
|
||||
const hasDiff = !!(guidesDiff.diffCount || imagesDiff.diffCount || examplesDiff.diffCount);
|
||||
|
||||
if (hasDiff) {
|
||||
const expectedGuidesSrc = path.relative(PROJECT_ROOT_DIR, AIO_GUIDES_DIR);
|
||||
const expectedImagesSrc = path.relative(PROJECT_ROOT_DIR, AIO_GUIDE_IMAGES_DIR);
|
||||
const expectedExamplesSrc = path.relative(PROJECT_ROOT_DIR, AIO_GUIDE_EXAMPLES_DIR);
|
||||
const actualSrc = path.relative(PROJECT_ROOT_DIR, CODEOWNERS_PATH);
|
||||
|
||||
reportDiff(guidesDiff, expectedGuidesSrc, actualSrc);
|
||||
reportDiff(imagesDiff, expectedImagesSrc, actualSrc);
|
||||
reportDiff(examplesDiff, expectedExamplesSrc, actualSrc);
|
||||
}
|
||||
|
||||
process.exit(hasDiff ? 1 : 0);
|
||||
}
|
||||
|
||||
function arrayDiff(expected, actual) {
|
||||
const missing = expected.filter(x => !actual.includes(x)).sort();
|
||||
const extra = actual.filter(x => !expected.includes(x)).sort();
|
||||
|
||||
return {missing, extra, diffCount: missing.length + extra.length};
|
||||
}
|
||||
|
||||
function getPathsFromAioContent() {
|
||||
return {
|
||||
guides: fs.readdirSync(AIO_GUIDES_DIR),
|
||||
images: fs.readdirSync(AIO_GUIDE_IMAGES_DIR),
|
||||
examples: fs.readdirSync(AIO_GUIDE_EXAMPLES_DIR).
|
||||
filter(name => fs.statSync(`${AIO_GUIDE_EXAMPLES_DIR}/${name}`).isDirectory()),
|
||||
};
|
||||
}
|
||||
|
||||
function getPathsFromCodeowners() {
|
||||
const guidesOrImagesPathRe = /^\/aio\/content\/(?:(images\/)?guide|(examples))\/([^\s/]+)/;
|
||||
|
||||
return fs.
|
||||
readFileSync(CODEOWNERS_PATH, 'utf8').
|
||||
split('\n').
|
||||
map(l => l.trim().match(guidesOrImagesPathRe)).
|
||||
filter(m => m).
|
||||
reduce((aggr, [, isImage, isExample, path]) => {
|
||||
const list = isExample ? aggr.examples :
|
||||
isImage ? aggr.images :
|
||||
aggr.guides;
|
||||
list.push(path);
|
||||
return aggr;
|
||||
}, {guides: [], images: [], examples: []});
|
||||
}
|
||||
|
||||
function reportDiff(diff, expectedSrc, actualSrc) {
|
||||
if (diff.missing.length) {
|
||||
console.error(
|
||||
`\nEntries in '${expectedSrc}' but not in '${actualSrc}':\n` +
|
||||
diff.missing.map(x => ` - ${x}`).join('\n'));
|
||||
}
|
||||
|
||||
if (diff.extra.length) {
|
||||
console.error(
|
||||
`\nEntries in '${actualSrc}' but not in '${expectedSrc}':\n` +
|
||||
diff.extra.map(x => ` - ${x}`).join('\n'));
|
||||
}
|
||||
}
|
@ -299,13 +299,16 @@ describe('TocService', () => {
|
||||
beforeEach(() => {
|
||||
docId = 'fizz/buzz/';
|
||||
|
||||
// An almost-actual <h2> ... with extra whitespace
|
||||
callGenToc(`
|
||||
<h2 id="setup-to-develop-locally">
|
||||
Setup to <a href="moo">develop</a> <i>locally</i>.
|
||||
<a class="header-link" href="tutorial/toh-pt1#setup-to-develop-locally" aria-hidden="true">
|
||||
<span class="icon icon-link"></span>
|
||||
<span class="icon">icon-link</span>
|
||||
</a>
|
||||
<div class="github-links">
|
||||
<a>GitHub</a>
|
||||
<a>links</a>
|
||||
</div>
|
||||
</h2>
|
||||
`, docId);
|
||||
|
||||
@ -320,7 +323,7 @@ describe('TocService', () => {
|
||||
expect(tocItem.title).toEqual('Setup to develop locally.');
|
||||
});
|
||||
|
||||
it('should have removed anchor link from tocItem html content', () => {
|
||||
it('should have removed anchor link and GitHub links from tocItem html content', () => {
|
||||
expect((tocItem.content as TestSafeHtml)
|
||||
.changingThisBreaksApplicationSecurity)
|
||||
.toEqual('Setup to develop <i>locally</i>.');
|
||||
|
@ -34,12 +34,16 @@ export class TocService {
|
||||
|
||||
const headings = this.findTocHeadings(docElement);
|
||||
const idMap = new Map<string, number>();
|
||||
const tocList = headings.map(heading => ({
|
||||
content: this.extractHeadingSafeHtml(heading),
|
||||
href: `${docId}#${this.getId(heading, idMap)}`,
|
||||
level: heading.tagName.toLowerCase(),
|
||||
title: (heading.textContent || '').trim(),
|
||||
}));
|
||||
const tocList = headings.map(heading => {
|
||||
const {title, content} = this.extractHeadingSafeHtml(heading);
|
||||
|
||||
return {
|
||||
level: heading.tagName.toLowerCase(),
|
||||
href: `${docId}#${this.getId(heading, idMap)}`,
|
||||
title,
|
||||
content,
|
||||
};
|
||||
});
|
||||
|
||||
this.tocList.next(tocList);
|
||||
|
||||
@ -52,29 +56,35 @@ export class TocService {
|
||||
this.tocList.next([]);
|
||||
}
|
||||
|
||||
// This bad boy exists only to strip off the anchor link attached to a heading
|
||||
// Transform the HTML content to be safe to use in the ToC:
|
||||
// - Strip off certain auto-generated elements (such as GitHub links and heading anchor links).
|
||||
// - Strip off any anchor links (but keep their content)
|
||||
// - Mark the HTML as trusted to be used with `[innerHTML]`.
|
||||
private extractHeadingSafeHtml(heading: HTMLHeadingElement) {
|
||||
const div: HTMLDivElement = this.document.createElement('div');
|
||||
div.innerHTML = heading.innerHTML;
|
||||
const anchorLinks = Array.from(div.querySelectorAll('a'));
|
||||
for (const anchorLink of anchorLinks) {
|
||||
if (!anchorLink.classList.contains('header-link')) {
|
||||
// this is an anchor that contains actual content that we want to keep
|
||||
// move the contents of the anchor into its parent
|
||||
const parent = anchorLink.parentNode!;
|
||||
while (anchorLink.childNodes.length) {
|
||||
parent.insertBefore(anchorLink.childNodes[0], anchorLink);
|
||||
}
|
||||
|
||||
// Remove any `.github-links` or `.header-link` elements (along with their content).
|
||||
div.querySelectorAll('.github-links, .header-link').forEach(removeNode);
|
||||
|
||||
// Remove any remaining `a` elements (but keep their content).
|
||||
div.querySelectorAll('a').forEach(anchorLink => {
|
||||
// We want to keep the content of this anchor, so move it into its parent.
|
||||
const parent = anchorLink.parentNode!;
|
||||
while (anchorLink.childNodes.length) {
|
||||
parent.insertBefore(anchorLink.childNodes[0], anchorLink);
|
||||
}
|
||||
// now remove the anchor
|
||||
if (anchorLink.parentNode !== null) {
|
||||
// We cannot use ChildNode.remove() because of IE11
|
||||
anchorLink.parentNode.removeChild(anchorLink);
|
||||
}
|
||||
}
|
||||
// security: the document element which provides this heading content
|
||||
// is always authored by the documentation team and is considered to be safe
|
||||
return this.domSanitizer.bypassSecurityTrustHtml(div.innerHTML.trim());
|
||||
|
||||
// Now, remove the anchor.
|
||||
removeNode(anchorLink);
|
||||
});
|
||||
|
||||
return {
|
||||
// Security: The document element which provides this heading content is always authored by
|
||||
// the documentation team and is considered to be safe.
|
||||
content: this.domSanitizer.bypassSecurityTrustHtml(div.innerHTML.trim()),
|
||||
title: (div.textContent || '').trim(),
|
||||
};
|
||||
}
|
||||
|
||||
private findTocHeadings(docElement: Element): HTMLHeadingElement[] {
|
||||
@ -115,3 +125,11 @@ export class TocService {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Helpers
|
||||
function removeNode(node: Node): void {
|
||||
if (node.parentNode !== null) {
|
||||
// We cannot use `Node.remove()` because of IE11.
|
||||
node.parentNode.removeChild(node);
|
||||
}
|
||||
}
|
||||
|
@ -11,6 +11,10 @@ their sub-folder. Also there are a number of common boilerplate files that are n
|
||||
each example's project. Maintain these common boilerplate files centrally to reduce the amount
|
||||
of effort if one of them needs to change.
|
||||
|
||||
> **Note for Windows users**
|
||||
>
|
||||
> Setting up the examples involves creating some [symbolic links](https://en.wikipedia.org/wiki/Symbolic_link) (see [here](#symlinked-node_modules) for details). On Windows, this requires to either have [Developer Mode enabled](https://blogs.windows.com/windowsdeveloper/2016/12/02/symlinks-windows-10) (supported on Windows 10 or newer) or run the setup commands as administrator.
|
||||
|
||||
## Boilerplate overview
|
||||
|
||||
As mentioned, many of the documentation pages contain snippets extracted from real example applications.
|
||||
@ -52,16 +56,16 @@ This configuration file indicates what type of boilerplate this example needs. E
|
||||
If the file is empty then the default type of cli is assumed.
|
||||
When the boilerplate tooling runs, it will copy into the example folder all of the appropriate files based on the project type.
|
||||
|
||||
<a name="symlinked-node_modules"></a>
|
||||
### A node_modules to share
|
||||
|
||||
With all the boilerplate files in place, the only missing piece are the installed packages. For
|
||||
that you have a `/aio/tools/examples/shared/package.json` which contains **all** the packages
|
||||
needed to run all the examples through all different boilerplates.
|
||||
|
||||
After installing these dependencies, a `node_modules` will be created at
|
||||
`/aio/tools/examples/shared/node_modules`. This folder will be **symlinked** into each example.
|
||||
So it is not a copy like the other boilerplate files. This solution works in all OSes. Windows
|
||||
may require admin rights.
|
||||
After installing these dependencies, a `node_modules/` folder will be created at
|
||||
`/aio/tools/examples/shared/node_modules/`. This folder will be **symlinked** into each example.
|
||||
So it is not a copy like the other boilerplate files.
|
||||
|
||||
### End to end tests
|
||||
|
||||
@ -103,4 +107,4 @@ With every major release, we update the examples to be on the latest version. Th
|
||||
* In the `shared/package.json` file, bump all the `@angular/*`, `@angular-devkit/*`, `rxjs`, `typescript`, and `zone.js` package versions to the version that corresponds with the [framework version](../../../package.json).
|
||||
* In the `shared` folder, run `yarn` to update the dependencies for the shared `node_modules` and the `yarn.lock` file.
|
||||
* In the `boilerplate` folder, go through each sub-folder and update the `package.json` dependencies if one is present.
|
||||
* Follow the [update guide](./shared/boilerplate/UPDATING_CLI.md) to update the common files used in the examples based on project type.
|
||||
* Follow the [update guide](./shared/boilerplate/UPDATING_CLI.md) to update the common files used in the examples based on project type.
|
||||
|
@ -36,10 +36,10 @@ if (argv.ivy) {
|
||||
* Run Protractor End-to-End Tests for Doc Samples
|
||||
*
|
||||
* Flags
|
||||
* --filter to filter/select _example app subdir names
|
||||
* --filter to filter/select _example app subdir names
|
||||
* e.g. --filter=foo // all example apps with 'foo' in their folder names.
|
||||
*
|
||||
* --setup run yarn install, copy boilerplate and update webdriver
|
||||
* --setup to run yarn install, copy boilerplate and update webdriver
|
||||
* e.g. --setup
|
||||
*
|
||||
* --local to use the locally built Angular packages, rather than versions from npm
|
||||
@ -55,6 +55,9 @@ if (argv.ivy) {
|
||||
*
|
||||
* --cliSpecsConcurrency Amount of CLI example specs that should be executed concurrently.
|
||||
* By default runs specs sequentially.
|
||||
*
|
||||
* --retry to retry failed tests (useful for overcoming flakes)
|
||||
* e.g. --retry 3 // To try each test up to 3 times.
|
||||
*/
|
||||
function runE2e() {
|
||||
if (argv.setup) {
|
||||
@ -70,7 +73,7 @@ function runE2e() {
|
||||
|
||||
return Promise.resolve()
|
||||
.then(() => findAndRunE2eTests(argv.filter, outputFile, argv.shard,
|
||||
argv.cliSpecsConcurrency || DEFAULT_CLI_SPECS_CONCURRENCY))
|
||||
argv.cliSpecsConcurrency || DEFAULT_CLI_SPECS_CONCURRENCY, argv.retry || 1))
|
||||
.then((status) => {
|
||||
reportStatus(status, outputFile);
|
||||
if (status.failed.length > 0) {
|
||||
@ -85,7 +88,7 @@ function runE2e() {
|
||||
|
||||
// Finds all of the *e2e-spec.tests under the examples folder along with the corresponding apps
|
||||
// that they should run under. Then run each app/spec collection sequentially.
|
||||
function findAndRunE2eTests(filter, outputFile, shard, cliSpecsConcurrency) {
|
||||
function findAndRunE2eTests(filter, outputFile, shard, cliSpecsConcurrency, maxAttempts) {
|
||||
const shardParts = shard ? shard.split('/') : [0, 1];
|
||||
const shardModulo = parseInt(shardParts[0], 10);
|
||||
const shardDivider = parseInt(shardParts[1], 10);
|
||||
@ -97,9 +100,22 @@ function findAndRunE2eTests(filter, outputFile, shard, cliSpecsConcurrency) {
|
||||
fs.writeFileSync(outputFile, header);
|
||||
|
||||
const status = {passed: [], failed: []};
|
||||
const updateStatus = (specPath, passed) => {
|
||||
const updateStatus = (specDescription, passed) => {
|
||||
const arr = passed ? status.passed : status.failed;
|
||||
arr.push(specPath);
|
||||
arr.push(specDescription);
|
||||
};
|
||||
const runTest = async (specPath, testFn) => {
|
||||
let attempts = 0;
|
||||
let passed = false;
|
||||
|
||||
while (true) {
|
||||
attempts++;
|
||||
passed = await testFn();
|
||||
|
||||
if (passed || (attempts >= maxAttempts)) break;
|
||||
}
|
||||
|
||||
updateStatus(`${specPath} (attempts: ${attempts})`, passed);
|
||||
};
|
||||
|
||||
return getE2eSpecs(EXAMPLES_PATH, filter)
|
||||
@ -117,12 +133,13 @@ function findAndRunE2eTests(filter, outputFile, shard, cliSpecsConcurrency) {
|
||||
|
||||
return e2eSpecPaths.systemjs
|
||||
.reduce(
|
||||
(promise, specPath) => {
|
||||
return promise.then(() => {
|
||||
const examplePath = path.dirname(specPath);
|
||||
return runE2eTestsSystemJS(examplePath, outputFile)
|
||||
.then(passed => updateStatus(examplePath, passed));
|
||||
});
|
||||
async (prevPromise, specPath) => {
|
||||
await prevPromise;
|
||||
|
||||
const examplePath = path.dirname(specPath);
|
||||
const testFn = () => runE2eTestsSystemJS(examplePath, outputFile);
|
||||
|
||||
await runTest(examplePath, testFn);
|
||||
},
|
||||
Promise.resolve())
|
||||
.then(async () => {
|
||||
@ -138,9 +155,11 @@ function findAndRunE2eTests(filter, outputFile, shard, cliSpecsConcurrency) {
|
||||
const bufferOutput = cliSpecsConcurrency > 1;
|
||||
while (specQueue.length) {
|
||||
const chunk = specQueue.splice(0, cliSpecsConcurrency);
|
||||
await Promise.all(chunk.map((testDir, index) => {
|
||||
return runE2eTestsCLI(testDir, outputFile, bufferOutput, ports.pop())
|
||||
.then(passed => updateStatus(testDir, passed));
|
||||
await Promise.all(chunk.map(testDir => {
|
||||
const port = ports.pop();
|
||||
const testFn = () => runE2eTestsCLI(testDir, outputFile, bufferOutput, port);
|
||||
|
||||
return runTest(testDir, testFn);
|
||||
}));
|
||||
}
|
||||
});
|
||||
|
@ -12,7 +12,7 @@
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10.9.0 <11.0.0",
|
||||
"yarn": ">=1.12.1 <=1.16.0"
|
||||
"yarn": ">=1.17.3 <=1.18.0"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
|
3
aio/tools/transforms/angular-api-package/tag-defs/codeGenApi.js
vendored
Normal file
3
aio/tools/transforms/angular-api-package/tag-defs/codeGenApi.js
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
module.exports = function() {
|
||||
return {name: 'codeGenApi'};
|
||||
};
|
@ -107,6 +107,17 @@ It will automatically pick up the settings from Angular's [settings.json](../.vs
|
||||
1. Add `<PATH_TO_YOUR_WORKSPACE>/angular/node_modules/clang-format/bin/<OS>/`
|
||||
where the OS options are: `darwin_x64`, `linux_x64`, and `win32`.
|
||||
|
||||
### Vim
|
||||
1. Install [Vim Clang-Format](https://github.com/rhysd/vim-clang-format).
|
||||
2. Create a [project-specific `.vimrc`](https://andrew.stwrt.ca/posts/project-specific-vimrc/) in
|
||||
your Angular directory containing
|
||||
|
||||
```vim
|
||||
let g:clang_format#command = '$ANGULAR_PATH/node_modules/.bin/clang-format'
|
||||
```
|
||||
|
||||
where `$ANGULAR_PATH` is an environment variable of the absolute path of your Angular directory.
|
||||
|
||||
## Linting/verifying your source code
|
||||
|
||||
You can check that your code is properly formatted and adheres to coding style by running:
|
||||
|
@ -13,8 +13,8 @@ export class TableCell {
|
||||
}
|
||||
|
||||
let tableCreateCount: number;
|
||||
export let maxRow: number;
|
||||
export let maxCol: number;
|
||||
let maxRow: number;
|
||||
let maxCol: number;
|
||||
let numberData: TableCell[][];
|
||||
let charData: TableCell[][];
|
||||
|
||||
|
@ -9,7 +9,7 @@
|
||||
import {Component, Input, NgModule} from '@angular/core';
|
||||
import {BrowserModule, DomSanitizer, SafeStyle} from '@angular/platform-browser';
|
||||
|
||||
import {TreeNode, emptyTree, maxDepth} from '../util';
|
||||
import {TreeNode, emptyTree, getMaxDepth} from '../util';
|
||||
|
||||
let trustedEmptyColor: SafeStyle;
|
||||
let trustedGreyColor: SafeStyle;
|
||||
@ -40,8 +40,8 @@ export class RootTreeComponent {
|
||||
|
||||
function createModule(): any {
|
||||
const components: any[] = [RootTreeComponent];
|
||||
for (let i = 0; i <= maxDepth; i++) {
|
||||
components.push(createTreeComponent(i, i === maxDepth));
|
||||
for (let i = 0; i <= getMaxDepth(); i++) {
|
||||
components.push(createTreeComponent(i, i === getMaxDepth()));
|
||||
}
|
||||
|
||||
@NgModule({imports: [BrowserModule], bootstrap: [RootTreeComponent], declarations: [components]})
|
||||
|
@ -25,10 +25,14 @@ export class TreeNode {
|
||||
}
|
||||
|
||||
let treeCreateCount: number;
|
||||
export let maxDepth: number;
|
||||
let maxDepth: number;
|
||||
let numberData: TreeNode;
|
||||
let charData: TreeNode;
|
||||
|
||||
export function getMaxDepth() {
|
||||
return maxDepth;
|
||||
}
|
||||
|
||||
init();
|
||||
|
||||
function init() {
|
||||
|
@ -6,6 +6,10 @@ package(default_visibility = ["//modules/playground:__subpackages__"])
|
||||
ng_module(
|
||||
name = "relative_assets",
|
||||
srcs = glob(["**/*.ts"]),
|
||||
assets = [
|
||||
"app/style.css",
|
||||
"app/tpl.html",
|
||||
],
|
||||
# This example demonstrates how external resources can be loaded relatively, so we
|
||||
# need to disable resource inlining.
|
||||
inline_resources = False,
|
||||
|
@ -6,7 +6,10 @@ package(default_visibility = ["//modules/playground:__subpackages__"])
|
||||
ng_module(
|
||||
name = "todo",
|
||||
srcs = glob(["**/*.ts"]),
|
||||
assets = ["todo.html"],
|
||||
assets = [
|
||||
"todo.html",
|
||||
"css/base.css",
|
||||
],
|
||||
tsconfig = "//modules/playground:tsconfig-build.json",
|
||||
# TODO: FW-1004 Type checking is currently not complete.
|
||||
type_check = False,
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "angular-srcs",
|
||||
"version": "8.2.3",
|
||||
"version": "8.2.7",
|
||||
"private": true,
|
||||
"description": "Angular - a web framework for modern web apps",
|
||||
"homepage": "https://github.com/angular/angular",
|
||||
@ -8,7 +8,7 @@
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=10.9.0 <11.0.0",
|
||||
"yarn": ">=1.12.1 <=1.16.0"
|
||||
"yarn": ">=1.17.3 <=1.18.0"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@ -122,7 +122,7 @@
|
||||
"@angular/cli": "^8.0.0-beta.15",
|
||||
"@bazel/bazel": "0.28.1",
|
||||
"@bazel/buildifier": "^0.26.0",
|
||||
"@bazel/ibazel": "~0.9.0",
|
||||
"@bazel/ibazel": "^0.10.3",
|
||||
"@types/minimist": "^1.2.0",
|
||||
"browserstacktunnel-wrapper": "2.0.1",
|
||||
"check-side-effects": "0.0.21",
|
||||
|
@ -109,7 +109,7 @@ function main(args: string[]): number {
|
||||
* @param inputPath Path to the file in the input tree.
|
||||
* @param fileContent Content of the file.
|
||||
*/
|
||||
function writeFileFromInputPath(inputPath: string, fileContent: string) {
|
||||
function writeFileFromInputPath(inputPath: string, fileContent: string | Buffer) {
|
||||
// We want the relative path from the given file to its ancestor "root" directory.
|
||||
// This root depends on whether the file lives in the source tree (srcDir) as a basic file
|
||||
// input to ng_package, the bin output tree (binDir) as the output of another rule, or
|
||||
@ -127,7 +127,7 @@ function main(args: string[]): number {
|
||||
|
||||
// Always ensure that the target directory exists.
|
||||
shx.mkdir('-p', path.dirname(outputPath));
|
||||
fs.writeFileSync(outputPath, fileContent, 'utf-8');
|
||||
fs.writeFileSync(outputPath, fileContent);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -135,7 +135,7 @@ function main(args: string[]): number {
|
||||
* @param inputPath a path relative to the binDir, typically from a file in the deps[]
|
||||
*/
|
||||
function copyFileFromInputPath(inputPath: string) {
|
||||
writeFileFromInputPath(inputPath, fs.readFileSync(inputPath, 'utf-8'));
|
||||
writeFileFromInputPath(inputPath, fs.readFileSync(inputPath));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -22,6 +22,7 @@ ng_package(
|
||||
":arbitrary_bin_file",
|
||||
":arbitrary_genfiles_file",
|
||||
":extra-styles.css",
|
||||
":logo.png",
|
||||
],
|
||||
entry_point = ":index.ts",
|
||||
entry_point_name = "waffels",
|
||||
|
BIN
packages/bazel/test/ng_package/example/logo.png
Normal file
BIN
packages/bazel/test/ng_package/example/logo.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.3 KiB |
@ -42,6 +42,7 @@ fesm5
|
||||
fesm5/secondary.js.map
|
||||
fesm5/waffels.js
|
||||
fesm5/waffels.js.map
|
||||
logo.png
|
||||
package.json
|
||||
secondary
|
||||
secondary/package.json
|
||||
@ -987,6 +988,10 @@ export { MyModule };
|
||||
//# sourceMappingURL=waffels.js.map
|
||||
|
||||
|
||||
--- logo.png ---
|
||||
|
||||
9db278d630f5fabd8e7ba16c2e329a3a
|
||||
|
||||
--- package.json ---
|
||||
|
||||
{
|
||||
|
@ -6,6 +6,7 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import * as crypto from 'crypto';
|
||||
import {createPatch} from 'diff';
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
@ -64,6 +65,10 @@ function getDescendantFilesContents(directoryPath: string): string[] {
|
||||
result.push(...getDescendantFilesContents(path.posix.join(directoryPath, dir)));
|
||||
});
|
||||
}
|
||||
// Binary files should equal the same as in the srcdir.
|
||||
else if (path.extname(directoryPath) === '.png') {
|
||||
result.push(`--- ${directoryPath} ---`, '', hashFileContents(directoryPath), '');
|
||||
}
|
||||
// Note that we don't want to include ".map" files in the golden file since these are not
|
||||
// consistent across different environments (e.g. path delimiters)
|
||||
else if (path.extname(directoryPath) !== '.map') {
|
||||
@ -128,6 +133,10 @@ function readFileContents(filePath: string): string {
|
||||
return fs.readFileSync(filePath, 'utf8').replace(/\r/g, '');
|
||||
}
|
||||
|
||||
function hashFileContents(filePath: string): string {
|
||||
return crypto.createHash('md5').update(fs.readFileSync(filePath)).digest('hex');
|
||||
}
|
||||
|
||||
if (require.main === module) {
|
||||
const args = process.argv.slice(2);
|
||||
const acceptingNewGold = (args[0] === '--accept');
|
||||
|
@ -109,7 +109,7 @@ export interface HttpParamsOptions {
|
||||
fromString?: string;
|
||||
|
||||
/** Object map of the HTTP parameters. Mutually exclusive with `fromString`. */
|
||||
fromObject?: {[param: string]: string | string[]};
|
||||
fromObject?: {[param: string]: string | ReadonlyArray<string>};
|
||||
|
||||
/** Encoding codec used to parse and serialize the parameters. */
|
||||
encoder?: HttpParameterCodec;
|
||||
|
@ -238,7 +238,7 @@ class ExpressionDiagnosticsVisitor extends RecursiveTemplateAstVisitor {
|
||||
'The template context does not have an implicit value', spanOf(ast.sourceSpan));
|
||||
} else {
|
||||
this.reportError(
|
||||
`The template context does not defined a member called '${ast.value}'`,
|
||||
`The template context does not define a member called '${ast.value}'`,
|
||||
spanOf(ast.sourceSpan));
|
||||
}
|
||||
}
|
||||
|
@ -32,10 +32,14 @@ js_size_tracking_test(
|
||||
":bundle",
|
||||
":bundle.golden_size_map.json",
|
||||
],
|
||||
goldenFile = "angular/packages/core/test/bundling/core_all/bundle.golden_size_map.json",
|
||||
maxByteDiff = 250,
|
||||
maxPercentageDiff = 15,
|
||||
sourceMap = "angular/packages/core/test/bundling/core_all/bundle.min.js.map",
|
||||
golden_file = "angular/packages/core/test/bundling/core_all/bundle.golden_size_map.json",
|
||||
max_byte_diff = 250,
|
||||
max_percentage_diff = 15,
|
||||
# Ensures that this target runs with "--define=compile=aot" (aka Ivy). This is necessary
|
||||
# because we don't run this test on CI currently, but if we run it manually, we need to
|
||||
# ensure that it runs with Ivy for proper size comparisons.
|
||||
required_compile_mode = "aot",
|
||||
source_map = "angular/packages/core/test/bundling/core_all/bundle.min.js.map",
|
||||
tags = [
|
||||
"ivy-only",
|
||||
"manual",
|
||||
|
62
packages/examples/service-worker/push/BUILD.bazel
Normal file
62
packages/examples/service-worker/push/BUILD.bazel
Normal file
@ -0,0 +1,62 @@
|
||||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
load("//tools:defaults.bzl", "ng_module", "ts_library")
|
||||
load("@npm_bazel_protractor//:index.bzl", "protractor_web_test_suite")
|
||||
load("@npm_bazel_typescript//:index.bzl", "ts_devserver")
|
||||
|
||||
ng_module(
|
||||
name = "sw_push_examples",
|
||||
srcs = glob(
|
||||
["**/*.ts"],
|
||||
exclude = ["**/*_spec.ts"],
|
||||
),
|
||||
# TODO: FW-1004 Type checking is currently not complete.
|
||||
type_check = False,
|
||||
deps = [
|
||||
"//packages/core",
|
||||
"//packages/platform-browser",
|
||||
"//packages/platform-browser-dynamic",
|
||||
"//packages/service-worker",
|
||||
],
|
||||
)
|
||||
|
||||
ts_library(
|
||||
name = "sw_push_e2e_tests_lib",
|
||||
testonly = True,
|
||||
srcs = glob(["**/e2e_test/*_spec.ts"]),
|
||||
tsconfig = "//packages/examples:tsconfig-e2e.json",
|
||||
deps = [
|
||||
"//packages/examples/test-utils",
|
||||
"//packages/private/testing",
|
||||
"@npm//@types/jasminewd2",
|
||||
"@npm//protractor",
|
||||
],
|
||||
)
|
||||
|
||||
ts_devserver(
|
||||
name = "devserver",
|
||||
entry_module = "@angular/examples/service-worker/push/main",
|
||||
index_html = "//packages/examples:index.html",
|
||||
port = 4200,
|
||||
scripts = [
|
||||
"//tools/rxjs:rxjs_umd_modules",
|
||||
"@npm//:node_modules/tslib/tslib.js",
|
||||
],
|
||||
static_files = [
|
||||
"ngsw-worker.js",
|
||||
"@npm//:node_modules/zone.js/dist/zone.js",
|
||||
],
|
||||
deps = [":sw_push_examples"],
|
||||
)
|
||||
|
||||
protractor_web_test_suite(
|
||||
name = "protractor_tests",
|
||||
data = ["//packages/bazel/src/protractor/utils"],
|
||||
on_prepare = "start-server.js",
|
||||
server = ":devserver",
|
||||
deps = [
|
||||
":sw_push_e2e_tests_lib",
|
||||
"@npm//protractor",
|
||||
"@npm//selenium-webdriver",
|
||||
],
|
||||
)
|
22
packages/examples/service-worker/push/e2e_test/push_spec.ts
Normal file
22
packages/examples/service-worker/push/e2e_test/push_spec.ts
Normal file
@ -0,0 +1,22 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. 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 {browser, by, element} from 'protractor';
|
||||
import {verifyNoBrowserErrors} from '../../../test-utils';
|
||||
|
||||
describe('SW `SwPush` example', () => {
|
||||
const pageUrl = '/push';
|
||||
const appElem = element(by.css('example-app'));
|
||||
|
||||
afterEach(verifyNoBrowserErrors);
|
||||
|
||||
it('should be enabled', () => {
|
||||
browser.get(pageUrl);
|
||||
expect(appElem.getText()).toBe('SW enabled: true');
|
||||
});
|
||||
});
|
12
packages/examples/service-worker/push/main.ts
Normal file
12
packages/examples/service-worker/push/main.ts
Normal file
@ -0,0 +1,12 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. 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 {platformBrowserDynamic} from '@angular/platform-browser-dynamic';
|
||||
import {AppModuleNgFactory} from './module.ngfactory';
|
||||
|
||||
platformBrowserDynamic().bootstrapModuleFactory(AppModuleNgFactory);
|
66
packages/examples/service-worker/push/module.ts
Normal file
66
packages/examples/service-worker/push/module.ts
Normal file
@ -0,0 +1,66 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. 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
|
||||
*/
|
||||
// tslint:disable: no-duplicate-imports
|
||||
import {Component, NgModule} from '@angular/core';
|
||||
import {BrowserModule} from '@angular/platform-browser';
|
||||
import {ServiceWorkerModule} from '@angular/service-worker';
|
||||
// #docregion inject-sw-push
|
||||
import {SwPush} from '@angular/service-worker';
|
||||
// #enddocregion inject-sw-push
|
||||
// tslint:enable: no-duplicate-imports
|
||||
|
||||
const PUBLIC_VAPID_KEY_OF_SERVER = '...';
|
||||
|
||||
@Component({
|
||||
selector: 'example-app',
|
||||
template: 'SW enabled: {{ swPush.isEnabled }}',
|
||||
})
|
||||
// #docregion inject-sw-push
|
||||
export class AppComponent {
|
||||
constructor(readonly swPush: SwPush) {}
|
||||
// #enddocregion inject-sw-push
|
||||
|
||||
// #docregion subscribe-to-push
|
||||
private async subscribeToPush() {
|
||||
try {
|
||||
const sub = await this.swPush.requestSubscription({
|
||||
serverPublicKey: PUBLIC_VAPID_KEY_OF_SERVER,
|
||||
});
|
||||
// TODO: Send to server.
|
||||
} catch (err) {
|
||||
console.error('Could not subscribe due to:', err);
|
||||
}
|
||||
}
|
||||
// #enddocregion subscribe-to-push
|
||||
|
||||
private subscribeToNotificationClicks() {
|
||||
// #docregion subscribe-to-notification-clicks
|
||||
this.swPush.notificationClicks.subscribe(
|
||||
({action, notification}) => {
|
||||
// TODO: Do something in response to notification click.
|
||||
});
|
||||
// #enddocregion subscribe-to-notification-clicks
|
||||
}
|
||||
// #docregion inject-sw-push
|
||||
}
|
||||
// #enddocregion inject-sw-push
|
||||
|
||||
@NgModule({
|
||||
bootstrap: [
|
||||
AppComponent,
|
||||
],
|
||||
declarations: [
|
||||
AppComponent,
|
||||
],
|
||||
imports: [
|
||||
BrowserModule,
|
||||
ServiceWorkerModule.register('ngsw-worker.js'),
|
||||
],
|
||||
})
|
||||
export class AppModule {
|
||||
}
|
14
packages/examples/service-worker/push/ngsw-worker.js
Normal file
14
packages/examples/service-worker/push/ngsw-worker.js
Normal file
@ -0,0 +1,14 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. 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
|
||||
*/
|
||||
|
||||
// Mock `ngsw-worker.js` used for testing the examples.
|
||||
// Immediately takes over and unregisters itself.
|
||||
self.addEventListener('install', evt => evt.waitUntil(self.skipWaiting()));
|
||||
self.addEventListener(
|
||||
'activate',
|
||||
evt => evt.waitUntil(self.clients.claim().then(() => self.registration.unregister())));
|
17
packages/examples/service-worker/push/start-server.js
Normal file
17
packages/examples/service-worker/push/start-server.js
Normal file
@ -0,0 +1,17 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. 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
|
||||
*/
|
||||
|
||||
const protractorUtils = require('@bazel/protractor/protractor-utils');
|
||||
const protractor = require('protractor');
|
||||
|
||||
module.exports = async function(config) {
|
||||
const {port} = await protractorUtils.runServer(config.workspace, config.server, '-port', []);
|
||||
const serverUrl = `http://localhost:${port}`;
|
||||
|
||||
protractor.browser.baseUrl = serverUrl;
|
||||
};
|
@ -145,8 +145,6 @@ export class RadioControlValueAccessor implements ControlValueAccessor,
|
||||
/**
|
||||
* @description
|
||||
* A lifecycle method called when the directive is initialized. For internal use only.
|
||||
*
|
||||
* @param changes A object of key/value pairs for the set of changed inputs.
|
||||
*/
|
||||
ngOnInit(): void {
|
||||
this._control = this._injector.get(NgControl);
|
||||
@ -157,8 +155,6 @@ export class RadioControlValueAccessor implements ControlValueAccessor,
|
||||
/**
|
||||
* @description
|
||||
* Lifecycle method called before the directive's instance is destroyed. For internal use only.
|
||||
*
|
||||
* @param changes A object of key/value pairs for the set of changed inputs.
|
||||
*/
|
||||
ngOnDestroy(): void { this._registry.remove(this); }
|
||||
|
||||
|
@ -1338,7 +1338,7 @@ export class FormGroup extends AbstractControl {
|
||||
* Reports false for disabled controls. If you'd like to check for existence in the group
|
||||
* only, use {@link AbstractControl#get get} instead.
|
||||
*
|
||||
* @param name The control name to check for existence in the collection
|
||||
* @param controlName The control name to check for existence in the collection
|
||||
*
|
||||
* @returns false for disabled controls, true otherwise.
|
||||
*/
|
||||
@ -1443,7 +1443,7 @@ export class FormGroup extends AbstractControl {
|
||||
* is a standalone value or a form state object with both a value and a disabled
|
||||
* status.
|
||||
*
|
||||
* @param formState Resets the control with an initial value,
|
||||
* @param value Resets the control with an initial value,
|
||||
* or an object that defines the initial value and disabled state.
|
||||
*
|
||||
* @param options Configuration options that determine how the control propagates changes
|
||||
|
@ -7,7 +7,7 @@
|
||||
*/
|
||||
|
||||
import {StaticSymbolResolverHost} from '@angular/compiler';
|
||||
import {CompilerOptions, MetadataCollector, MetadataReaderHost, createMetadataReaderCache, readMetadata} from '@angular/compiler-cli/src/language_services';
|
||||
import {MetadataCollector, MetadataReaderHost, createMetadataReaderCache, readMetadata} from '@angular/compiler-cli/src/language_services';
|
||||
import * as path from 'path';
|
||||
import * as ts from 'typescript';
|
||||
|
||||
@ -48,13 +48,23 @@ class ReflectorModuleModuleResolutionHost implements ts.ModuleResolutionHost, Me
|
||||
}
|
||||
|
||||
export class ReflectorHost implements StaticSymbolResolverHost {
|
||||
private hostAdapter: ReflectorModuleModuleResolutionHost;
|
||||
private metadataReaderCache = createMetadataReaderCache();
|
||||
private readonly hostAdapter: ReflectorModuleModuleResolutionHost;
|
||||
private readonly metadataReaderCache = createMetadataReaderCache();
|
||||
private readonly moduleResolutionCache: ts.ModuleResolutionCache;
|
||||
private readonly fakeContainingPath: string;
|
||||
|
||||
constructor(
|
||||
getProgram: () => ts.Program, serviceHost: ts.LanguageServiceHost,
|
||||
private options: CompilerOptions) {
|
||||
this.hostAdapter = new ReflectorModuleModuleResolutionHost(serviceHost, getProgram);
|
||||
getProgram: () => ts.Program, private readonly tsLSHost: ts.LanguageServiceHost, _: {}) {
|
||||
// tsLSHost.getCurrentDirectory() returns the directory where tsconfig.json
|
||||
// is located. This is not the same as process.cwd() because the language
|
||||
// service host sets the "project root path" as its current directory.
|
||||
const currentDir = tsLSHost.getCurrentDirectory();
|
||||
this.fakeContainingPath = currentDir ? path.join(currentDir, 'fakeContainingFile.ts') : '';
|
||||
this.hostAdapter = new ReflectorModuleModuleResolutionHost(tsLSHost, getProgram);
|
||||
this.moduleResolutionCache = ts.createModuleResolutionCache(
|
||||
currentDir,
|
||||
s => s, // getCanonicalFileName
|
||||
tsLSHost.getCompilationSettings());
|
||||
}
|
||||
|
||||
getMetadataFor(modulePath: string): {[key: string]: any}[]|undefined {
|
||||
@ -63,15 +73,22 @@ export class ReflectorHost implements StaticSymbolResolverHost {
|
||||
|
||||
moduleNameToFileName(moduleName: string, containingFile?: string): string|null {
|
||||
if (!containingFile) {
|
||||
if (moduleName.indexOf('.') === 0) {
|
||||
if (moduleName.startsWith('.')) {
|
||||
throw new Error('Resolution of relative paths requires a containing file.');
|
||||
}
|
||||
// Any containing file gives the same result for absolute imports
|
||||
containingFile = path.join(this.options.basePath !, 'index.ts').replace(/\\/g, '/');
|
||||
if (!this.fakeContainingPath) {
|
||||
// If current directory is empty then the file must belong to an inferred
|
||||
// project (no tsconfig.json), in which case it's not possible to resolve
|
||||
// the module without the caller explicitly providing a containing file.
|
||||
throw new Error(`Could not resolve '${moduleName}' without a containing file.`);
|
||||
}
|
||||
containingFile = this.fakeContainingPath;
|
||||
}
|
||||
const resolved =
|
||||
ts.resolveModuleName(moduleName, containingFile !, this.options, this.hostAdapter)
|
||||
.resolvedModule;
|
||||
const compilerOptions = this.tsLSHost.getCompilationSettings();
|
||||
const resolved = ts.resolveModuleName(
|
||||
moduleName, containingFile, compilerOptions, this.hostAdapter,
|
||||
this.moduleResolutionCache)
|
||||
.resolvedModule;
|
||||
return resolved ? resolved.resolvedFileName : null;
|
||||
}
|
||||
|
||||
|
@ -20,7 +20,7 @@ describe('reflector_host_spec', () => {
|
||||
const originalJoin = path.join;
|
||||
const originalPosixJoin = path.posix.join;
|
||||
let mockHost =
|
||||
new MockTypescriptHost(['/app/main.ts', '/app/parsing-cases.ts'], toh, 'app/node_modules', {
|
||||
new MockTypescriptHost(['/app/main.ts', '/app/parsing-cases.ts'], toh, 'node_modules', {
|
||||
...path,
|
||||
join: (...args: string[]) => originalJoin.apply(path, args),
|
||||
posix:
|
||||
@ -37,4 +37,4 @@ describe('reflector_host_spec', () => {
|
||||
const result = reflectorHost.moduleNameToFileName('@angular/core');
|
||||
expect(result).not.toBeNull('could not find @angular/core using path.win32');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -180,7 +180,7 @@ describe('plugin', () => {
|
||||
'Identifier \'people_1\' is not defined. The component declaration, template variable declarations, and element references do not contain such a member');
|
||||
});
|
||||
it('should report an unknown context reference', () => {
|
||||
expectError('even_1', 'The template context does not defined a member called \'even_1\'');
|
||||
expectError('even_1', 'The template context does not define a member called \'even_1\'');
|
||||
});
|
||||
it('should report an unknown value in a key expression', () => {
|
||||
expectError(
|
||||
@ -193,8 +193,7 @@ describe('plugin', () => {
|
||||
expectSemanticError('app/ng-if-cases.ts', locationMarker, message);
|
||||
}
|
||||
it('should report an implicit context reference', () => {
|
||||
expectError(
|
||||
'implicit', 'The template context does not defined a member called \'unknown\'');
|
||||
expectError('implicit', 'The template context does not define a member called \'unknown\'');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -122,7 +122,10 @@ export class NavigationEnd extends RouterEvent {
|
||||
}
|
||||
|
||||
/**
|
||||
* An event triggered when a navigation is canceled.
|
||||
* An event triggered when a navigation is canceled, directly or indirectly.
|
||||
*
|
||||
* This can happen when a [route guard](guide/router#milestone-5-route-guards)
|
||||
* returns `false` or initiates a redirect by returning a `UrlTree`.
|
||||
*
|
||||
* @publicApi
|
||||
*/
|
||||
|
@ -863,7 +863,7 @@ export class Router {
|
||||
/**
|
||||
* Applies an array of commands to the current URL tree and creates a new URL tree.
|
||||
*
|
||||
* When given an activate route, applies the given commands starting from the route.
|
||||
* When given an activated route, applies the given commands starting from the route.
|
||||
* Otherwise, applies the given command starting from the root.
|
||||
*
|
||||
* @param commands An array of commands to apply.
|
||||
|
@ -14,7 +14,73 @@ import {ERR_SW_NOT_SUPPORTED, NgswCommChannel, PushEvent} from './low_level';
|
||||
|
||||
|
||||
/**
|
||||
* Subscribe and listen to push notifications from the Service Worker.
|
||||
* Subscribe and listen to
|
||||
* [Web Push Notifications](https://developer.mozilla.org/en-US/docs/Web/API/Push_API/Best_Practices)
|
||||
* through Angular Service Worker.
|
||||
*
|
||||
* @usageNotes
|
||||
*
|
||||
* You can inject a `SwPush` instance into any component or service
|
||||
* as a dependency.
|
||||
*
|
||||
* <code-example path="service-worker/push/module.ts" region="inject-sw-push" header="app.component.ts"></code-example>
|
||||
*
|
||||
* To subscribe, call `SwPush.requestSubscription()`, which asks the user for permission.
|
||||
* The call returns a `Promise` with a new
|
||||
* [`PushSubscription`](https://developer.mozilla.org/en-US/docs/Web/API/PushSubscription)
|
||||
* instance.
|
||||
*
|
||||
* <code-example path="service-worker/push/module.ts" region="subscribe-to-push" header="app.component.ts"></code-example>
|
||||
*
|
||||
* A request is rejected if the user denies permission, or if the browser
|
||||
* blocks or does not support the Push API or ServiceWorkers.
|
||||
* Check `SwPush.isEnabled` to confirm status.
|
||||
*
|
||||
* Invoke Push Notifications by pushing a message with the following payload.
|
||||
*
|
||||
* ```ts
|
||||
* {
|
||||
* "notification": {
|
||||
* "actions": NotificationAction[],
|
||||
* "badge": USVString
|
||||
* "body": DOMString,
|
||||
* "data": any,
|
||||
* "dir": "auto"|"ltr"|"rtl",
|
||||
* "icon": USVString,
|
||||
* "image": USVString,
|
||||
* "lang": DOMString,
|
||||
* "renotify": boolean,
|
||||
* "requireInteraction": boolean,
|
||||
* "silent": boolean,
|
||||
* "tag": DOMString,
|
||||
* "timestamp": DOMTimeStamp,
|
||||
* "title": DOMString,
|
||||
* "vibrate": number[]
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* Only `title` is required. See `Notification`
|
||||
* [instance properties](https://developer.mozilla.org/en-US/docs/Web/API/Notification#Instance_properties).
|
||||
*
|
||||
* While the subscription is active, Service Worker listens for
|
||||
* [PushEvent](https://developer.mozilla.org/en-US/docs/Web/API/PushEvent)
|
||||
* occurrences and creates
|
||||
* [Notification](https://developer.mozilla.org/en-US/docs/Web/API/Notification)
|
||||
* instances in response.
|
||||
*
|
||||
* Unsubscribe using `SwPush.unsubscribe()`.
|
||||
*
|
||||
* An application can subscribe to `SwPush.notificationClicks` observable to be notified when a user
|
||||
* clicks on a notification. For example:
|
||||
*
|
||||
* <code-example path="service-worker/push/module.ts" region="subscribe-to-notification-clicks" header="app.component.ts"></code-example>
|
||||
*
|
||||
* @see [Push Notifications](https://developers.google.com/web/fundamentals/codelabs/push-notifications/)
|
||||
* @see [Angular Push Notifications](https://blog.angular-university.io/angular-push-notifications/)
|
||||
* @see [MDN: Push API](https://developer.mozilla.org/en-US/docs/Web/API/Push_API)
|
||||
* @see [MDN: Notifications API](https://developer.mozilla.org/en-US/docs/Web/API/Notifications_API)
|
||||
* @see [MDN: Web Push API Notifications best practices](https://developer.mozilla.org/en-US/docs/Web/API/Push_API/Best_Practices)
|
||||
*
|
||||
* @publicApi
|
||||
*/
|
||||
@ -27,10 +93,10 @@ export class SwPush {
|
||||
|
||||
/**
|
||||
* Emits the payloads of the received push notification messages as well as the action the user
|
||||
* interacted with. If no action was used the action property will be an empty string `''`.
|
||||
* interacted with. If no action was used the `action` property contains an empty string `''`.
|
||||
*
|
||||
* Note that the `notification` property is **not** a [Notification][Mozilla Notification] object
|
||||
* but rather a
|
||||
* Note that the `notification` property does **not** contain a
|
||||
* [Notification][Mozilla Notification] object but rather a
|
||||
* [NotificationOptions](https://notifications.spec.whatwg.org/#dictdef-notificationoptions)
|
||||
* object that also includes the `title` of the [Notification][Mozilla Notification] object.
|
||||
*
|
||||
@ -78,6 +144,13 @@ export class SwPush {
|
||||
this.subscription = merge(workerDrivenSubscriptions, this.subscriptionChanges);
|
||||
}
|
||||
|
||||
/**
|
||||
* Subscribes to Web Push Notifications,
|
||||
* after requesting and receiving user permission.
|
||||
*
|
||||
* @param options An object containing the `serverPublicKey` string.
|
||||
* @returns A Promise that resolves to the new subscription object.
|
||||
*/
|
||||
requestSubscription(options: {serverPublicKey: string}): Promise<PushSubscription> {
|
||||
if (!this.sw.isEnabled) {
|
||||
return Promise.reject(new Error(ERR_SW_NOT_SUPPORTED));
|
||||
@ -98,6 +171,12 @@ export class SwPush {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Unsubscribes from Service Worker push notifications.
|
||||
*
|
||||
* @returns A Promise that is resolved when the operation succeeds, or is rejected if there is no
|
||||
* active subscription or the unsubscribe operation fails.
|
||||
*/
|
||||
unsubscribe(): Promise<void> {
|
||||
if (!this.sw.isEnabled) {
|
||||
return Promise.reject(new Error(ERR_SW_NOT_SUPPORTED));
|
||||
|
@ -13,22 +13,31 @@ let server;
|
||||
|
||||
const localFolder = __dirname;
|
||||
|
||||
function writeNotFound(res) {
|
||||
res.writeHead(404, {'Content-Type': 'text/html'});
|
||||
res.end('<h1>404, Not Found!</h1>');
|
||||
}
|
||||
|
||||
function requestHandler(req, res) {
|
||||
if (req.url === '/close') {
|
||||
res.end('server closing');
|
||||
setTimeout(() => { process.exit(0); }, 1000);
|
||||
} else {
|
||||
const file = localFolder + req.url;
|
||||
const file = path.resolve(localFolder, req.url);
|
||||
if (!file.startsWith(localFolder + '/')) {
|
||||
writeNotFound(res);
|
||||
return;
|
||||
}
|
||||
|
||||
fs.readFile(file, function(err, contents) {
|
||||
if (!err) {
|
||||
res.end(contents);
|
||||
} else {
|
||||
res.writeHead(404, {'Content-Type': 'text/html'});
|
||||
res.end('<h1>404, Not Found!</h1>');
|
||||
writeNotFound(res);
|
||||
return;
|
||||
};
|
||||
});
|
||||
};
|
||||
};
|
||||
|
||||
server = http.createServer(requestHandler).listen(8080);
|
||||
server = http.createServer(requestHandler).listen(8080);
|
||||
|
@ -51,6 +51,15 @@ addTimestamp() {
|
||||
payloadData="$payloadData\"timestamp\": $timestamp, "
|
||||
}
|
||||
|
||||
# Write the current CI build URL to global variable `$payloadData`.
|
||||
# This allows mapping the data stored in the database to the CI build job that generated it, which
|
||||
# might contain more info/context.
|
||||
# $1: string - The CI build URL.
|
||||
addBuildUrl() {
|
||||
buildUrl="$1"
|
||||
payloadData="$payloadData\"buildUrl\": \"$buildUrl\", "
|
||||
}
|
||||
|
||||
# Write the commit message for the current CI commit range to global variable `$payloadData`.
|
||||
# $1: string - The commit range for this build (in `<SHA-1>...<SHA-2>` format).
|
||||
addMessage() {
|
||||
@ -142,6 +151,7 @@ trackPayloadSize() {
|
||||
addChangeType $CI_COMMIT_RANGE
|
||||
fi
|
||||
addTimestamp
|
||||
addBuildUrl $CI_BUILD_URL
|
||||
addMessage $CI_COMMIT_RANGE
|
||||
uploadData $name
|
||||
fi
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user