Compare commits
123 Commits
Author | SHA1 | Date | |
---|---|---|---|
362b3e4d03 | |||
2952ea57a5 | |||
3541e590f4 | |||
8ef0ae3561 | |||
c3ff66c1ba | |||
a1d9848456 | |||
a3482f7076 | |||
006af0b985 | |||
9dc4815e39 | |||
4263d9ea0d | |||
30253a7df3 | |||
16b83e8e2f | |||
e0c10632ea | |||
686b62129c | |||
dd2587d9e5 | |||
2742649a52 | |||
eb0461d2d4 | |||
e24393c35b | |||
8237e958a6 | |||
9ba898d588 | |||
cd1b0c1b1f | |||
06072e0062 | |||
288e0ef7b6 | |||
88ad5052bf | |||
7e6644a25a | |||
d533d15001 | |||
d0abf1bc54 | |||
56ac18ea8c | |||
11d3b19b32 | |||
d96167fc54 | |||
68fed2fc28 | |||
57457fb7c7 | |||
a2fc5774a7 | |||
c6041b985e | |||
b45d2e0fb1 | |||
42f7419522 | |||
32f5241598 | |||
06e72721ca | |||
3a80069f65 | |||
0818e2c799 | |||
7b8b2d4622 | |||
f6526a0872 | |||
0984022192 | |||
bb7603104f | |||
bdea243fed | |||
2397144e01 | |||
466c754cc8 | |||
45fee069d7 | |||
432f7ce2a0 | |||
e8c34c47af | |||
e81fc14da1 | |||
d5c210a2e1 | |||
7dbde6f570 | |||
78de6211e3 | |||
104c786b90 | |||
9680f4c991 | |||
e63390daf8 | |||
af99f9e98e | |||
8398f9d54c | |||
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 |
@ -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
|
||||
|
@ -7,156 +7,162 @@
|
||||
# To validate changes, use an online parser, eg.
|
||||
# http://yaml-online-parser.appspot.com/
|
||||
|
||||
# Note that the browser docker image comes with Chrome and Firefox preinstalled. This is just
|
||||
# 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
|
||||
# `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
|
||||
# CircleCI configuration version
|
||||
# Version 2.1 allows for extra config reuse features
|
||||
# https://circleci.com/docs/2.0/reusing-config/#getting-started-with-config-reuse
|
||||
version: 2.1
|
||||
|
||||
# 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
|
||||
# cache changes. For example:
|
||||
# 1) yarn lock file changes --> cached "node_modules" are different.
|
||||
# 2) bazel repository definitions change --> cached bazel repositories are different.
|
||||
# **NOTE 1 **: If you change the cache key prefix, also sync the restore_cache fallback to match.
|
||||
# **NOTE 1 **: If you change the cache key prefix, also sync the cache_key_fallback to match.
|
||||
# **NOTE 2 **: Keep the static part of the cache key as prefix to enable correct fallbacks.
|
||||
# See https://circleci.com/docs/2.0/caching/#restoring-cache for how prefixes work in CircleCI.
|
||||
var_3: &cache_key v3-angular-node-10.16-{{ checksum "yarn.lock" }}-{{ checksum "WORKSPACE" }}-{{ checksum "packages/bazel/package.bzl" }}-{{ checksum "aio/yarn.lock" }}
|
||||
|
||||
# Initializes the CI environment by setting up common environment variables.
|
||||
var_4: &init_environment
|
||||
run:
|
||||
name: Initializing environment (setting up variables, overwriting Yarn)
|
||||
# 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)
|
||||
sudo chmod a+x $ourYarn
|
||||
sudo ln -fs $ourYarn /usr/local/bin/yarn
|
||||
echo "Yarn version: $(yarn --version)"
|
||||
|
||||
# Add GitHub to known hosts.
|
||||
mkdir -p ~/.ssh
|
||||
echo 'github.com ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAq2A7hRGmdnm9tUDbO9IDSwBK6TbQa+PXYPCPy6rbTrTtw7PHkccKrpp0yVhp5HdEIcKr6pLlVDBfOLX9QUsyCOV0wzfjIJNlGEYsdlLJizHhbn2mUjvSAHQqZETYP81eFzLQNnPHt4EVVUh7VfDESU84KezmD5QlWpXLmvU31/yMf+Se8xhHTvKSCZIFImWwoG6mbUoWf9nzpIoaSjB+weqqUUmpaaasXVal72J+UX2B+2RPW3RcT0eOzQgqlJL3RKrTJvdsjE3JEAvGq3lGHSZXy28G3skua2SmVi/w4yCE6gbODqnTWlg7+wC604ydGXA8VJiS5ap43JXiUFFAaQ==' >> ~/.ssh/known_hosts
|
||||
|
||||
# use git+ssh instead of https
|
||||
git config --global url."ssh://git@github.com".insteadOf "https://github.com" || true
|
||||
git config --global gc.auto 0 || true
|
||||
|
||||
|
||||
var_5: &setup_bazel_remote_execution
|
||||
run:
|
||||
name: "Setup bazel RBE remote execution"
|
||||
command: |
|
||||
# We need ensure that the same default digest is used for encoding and decoding
|
||||
# with openssl. Openssl versions might have different default digests which can
|
||||
# cause decryption failures based on the openssl version. https://stackoverflow.com/a/39641378/4317734
|
||||
openssl aes-256-cbc -d -in .circleci/gcp_token -md md5 -k "$CI_REPO_NAME" -out /home/circleci/.gcp_credentials
|
||||
echo "export GOOGLE_APPLICATION_CREDENTIALS=/home/circleci/.gcp_credentials" >> $BASH_ENV
|
||||
./.circleci/setup-rbe.sh .bazelrc.user
|
||||
|
||||
# Settings common to each job
|
||||
var_6: &job_defaults
|
||||
working_directory: ~/ng
|
||||
docker:
|
||||
- image: *default_docker_image
|
||||
|
||||
# After checkout, rebase on top of target branch.
|
||||
var_7: &post_checkout
|
||||
run:
|
||||
name: Rebase PR on target branch
|
||||
command: >
|
||||
if [[ -n "${CIRCLE_PR_NUMBER}" ]]; then
|
||||
# User is required for rebase.
|
||||
git config user.name "angular-ci"
|
||||
git config user.email "angular-ci"
|
||||
# Rebase PR on top of target branch.
|
||||
node tools/rebase-pr.js angular/angular ${CIRCLE_PR_NUMBER}
|
||||
else
|
||||
echo "This build is not over a PR, nothing to do."
|
||||
fi
|
||||
|
||||
var_8: &yarn_install
|
||||
run:
|
||||
name: Running Yarn install
|
||||
command: |
|
||||
# Yarn's requests sometimes take more than 10mins to complete.
|
||||
# Print something to stdout, to prevent CircleCI from failing due to not output.
|
||||
while true; do sleep 60; echo "[`date`] Keeping alive..."; done &
|
||||
KEEP_ALIVE_PID=$!
|
||||
yarn install --frozen-lockfile --non-interactive
|
||||
kill $KEEP_ALIVE_PID
|
||||
|
||||
var_9: &setup_circleci_bazel_config
|
||||
run:
|
||||
name: Setting up CircleCI bazel configuration
|
||||
command: sudo cp .circleci/bazel.rc /etc/bazel.bazelrc
|
||||
|
||||
var_10: &restore_cache
|
||||
restore_cache:
|
||||
keys:
|
||||
- *cache_key
|
||||
# This fallback should be the cache_key without variables.
|
||||
- v3-angular-node-10.16-
|
||||
|
||||
# Branch filter that can be specified for jobs that should only run on publish branches
|
||||
# (e.g. master or the patch branch)
|
||||
var_11: &publish_branches_filter
|
||||
branches:
|
||||
only:
|
||||
- master
|
||||
# e.g. 7.0.x, 7.1.x, etc.
|
||||
- /\d+\.\d+\.x/
|
||||
|
||||
# Workspace initially persisted by the `install` job, and then enhanced by `test_aio` and
|
||||
# `build-npm-packages`.
|
||||
# https://circleci.com/docs/2.0/workflows/#using-workspaces-to-share-data-among-jobs
|
||||
# https://circleci.com/blog/deep-diving-into-circleci-workspaces/
|
||||
var_12: &attach_workspace
|
||||
attach_workspace:
|
||||
at: ~/
|
||||
|
||||
var_13: ¬ify_caretaker_on_fail
|
||||
run:
|
||||
when: on_fail
|
||||
name: Notify caretaker about failure
|
||||
# `$SLACK_CARETAKER_WEBHOOK_URL` is a secret env var defined in CircleCI project settings.
|
||||
# The URL comes from https://angular-team.slack.com/apps/A0F7VRE7N-circleci.
|
||||
command: |
|
||||
notificationJson="{\"text\":\":x: \`$CIRCLE_JOB\` job for $CIRCLE_BRANCH branch failed on build $CIRCLE_BUILD_NUM: $CIRCLE_BUILD_URL :scream:\"}"
|
||||
curl --request POST --header "Content-Type: application/json" --data "$notificationJson" $SLACK_CARETAKER_WEBHOOK_URL
|
||||
|
||||
var_14: ¬ify_dev_infra_on_fail
|
||||
run:
|
||||
when: on_fail
|
||||
name: Notify dev-infra about failure
|
||||
# `$SLACK_DEV_INFRA_CI_FAILURES_WEBHOOK_URL` is a secret env var defined in CircleCI project settings.
|
||||
# The URL comes from https://angular-team.slack.com/apps/A0F7VRE7N-circleci.
|
||||
command: |
|
||||
notificationJson="{\"text\":\":x: \`$CIRCLE_JOB\` job for $CIRCLE_BRANCH branch failed on build $CIRCLE_BUILD_NUM: $CIRCLE_BUILD_URL :scream:\"}"
|
||||
curl --request POST --header "Content-Type: application/json" --data "$notificationJson" $SLACK_DEV_INFRA_CI_FAILURES_WEBHOOK_URL
|
||||
var_4: &cache_key_fallback v3-angular-node-10.16-
|
||||
|
||||
# Cache key for the Material unit tests job. **Note** when updating the SHA in the cache keys,
|
||||
# also update the SHA for the "MATERIAL_REPO_COMMIT" environment variable.
|
||||
var_15: &material_unit_tests_cache_key v4-angular-material-18b9ef3f5529f0fa8f034944681486447af7b879
|
||||
var_16: &material_unit_tests_cache_key_short v4-angular-material
|
||||
var_5: &material_unit_tests_cache_key v4-angular-material-18b9ef3f5529f0fa8f034944681486447af7b879
|
||||
var_6: &material_unit_tests_cache_key_fallback v4-angular-material-
|
||||
|
||||
version: 2
|
||||
|
||||
# Workspace initially persisted by the `setup` job, and then enhanced by `build-npm-packages` and
|
||||
# `build-ivy-npm-packages`.
|
||||
# https://circleci.com/docs/2.0/workflows/#using-workspaces-to-share-data-among-jobs
|
||||
# https://circleci.com/blog/deep-diving-into-circleci-workspaces/
|
||||
var_7: &workspace_location ~/
|
||||
|
||||
# Executor Definitions
|
||||
# https://circleci.com/docs/2.0/reusing-config/#authoring-reusable-executors
|
||||
# **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.
|
||||
executors:
|
||||
default-executor:
|
||||
parameters:
|
||||
resource_class:
|
||||
type: string
|
||||
default: medium
|
||||
docker:
|
||||
- image: circleci/node:10.16@sha256:75c05084fff4afa3683a03c5a04a4a3ad95c536ff2439d8fe14e7e1f5c58b09a
|
||||
resource_class: << parameters.resource_class >>
|
||||
working_directory: ~/ng
|
||||
|
||||
browsers-executor:
|
||||
parameters:
|
||||
resource_class:
|
||||
type: string
|
||||
default: medium
|
||||
docker:
|
||||
# The browser docker image comes with Chrome and Firefox preinstalled. This is just
|
||||
# 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.
|
||||
- image: circleci/node:10.16-browsers@sha256:d2a96fe1cbef51257ee626b5f645e64dade3e886f00ba9cb7e8ea65b4efe8db1
|
||||
resource_class: << parameters.resource_class >>
|
||||
working_directory: ~/ng
|
||||
|
||||
# Command Definitions
|
||||
# https://circleci.com/docs/2.0/reusing-config/#authoring-reusable-commands
|
||||
commands:
|
||||
custom_attach_workspace:
|
||||
description: Attach workspace at a predefined location
|
||||
steps:
|
||||
- attach_workspace:
|
||||
at: *workspace_location
|
||||
|
||||
# Initializes the CI environment by setting up common environment variables.
|
||||
init_environment:
|
||||
description: Initializing environment (setting up variables, overwriting Yarn)
|
||||
steps:
|
||||
- run: ./.circleci/env.sh
|
||||
- run:
|
||||
# Overwrite the yarn installed in the docker container with our own version.
|
||||
name: Overwrite yarn with our own version
|
||||
command: |
|
||||
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
|
||||
- run: echo "Yarn version $(yarn --version)"
|
||||
- run:
|
||||
# Configure git as the CircleCI `checkout` command does.
|
||||
# This is needed because we only checkout on the setup job.
|
||||
# Add GitHub to known hosts
|
||||
name: Configure git
|
||||
command: |
|
||||
mkdir -p ~/.ssh
|
||||
echo 'github.com ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAq2A7hRGmdnm9tUDbO9IDSwBK6TbQa+PXYPCPy6rbTrTtw7PHkccKrpp0yVhp5HdEIcKr6pLlVDBfOLX9QUsyCOV0wzfjIJNlGEYsdlLJizHhbn2mUjvSAHQqZETYP81eFzLQNnPHt4EVVUh7VfDESU84KezmD5QlWpXLmvU31/yMf+Se8xhHTvKSCZIFImWwoG6mbUoWf9nzpIoaSjB+weqqUUmpaaasXVal72J+UX2B+2RPW3RcT0eOzQgqlJL3RKrTJvdsjE3JEAvGq3lGHSZXy28G3skua2SmVi/w4yCE6gbODqnTWlg7+wC604ydGXA8VJiS5ap43JXiUFFAaQ==' >> ~/.ssh/known_hosts
|
||||
git config --global url."ssh://git@github.com".insteadOf "https://github.com" || true
|
||||
git config --global gc.auto 0 || true
|
||||
|
||||
setup_circleci_bazel_config:
|
||||
description: Set up CircleCI bazel configuration
|
||||
steps:
|
||||
- run: sudo cp .circleci/bazel.rc /etc/bazel.bazelrc
|
||||
|
||||
setup_bazel_rbe:
|
||||
description: Setup bazel RBE remote execution
|
||||
steps:
|
||||
# We need ensure that the same default digest is used for encoding and decoding
|
||||
# with openssl. Openssl versions might have different default digests which can
|
||||
# cause decryption failures based on the openssl version. https://stackoverflow.com/a/39641378/4317734
|
||||
- run: openssl aes-256-cbc -d -in .circleci/gcp_token -md md5 -k "$CI_REPO_NAME" -out /home/circleci/.gcp_credentials
|
||||
- run: echo "export GOOGLE_APPLICATION_CREDENTIALS=/home/circleci/.gcp_credentials" >> $BASH_ENV
|
||||
- run: ./.circleci/setup-rbe.sh .bazelrc.user
|
||||
|
||||
notify_webhook_on_fail:
|
||||
description: Notify a webhook about failure
|
||||
parameters:
|
||||
# `webhook_url_env_var` are secret env vars defined in CircleCI project settings.
|
||||
# The URLs come from https://angular-team.slack.com/apps/A0F7VRE7N-circleci.
|
||||
webhook_url_env_var:
|
||||
type: env_var_name
|
||||
steps:
|
||||
- run:
|
||||
when: on_fail
|
||||
command: |
|
||||
notificationJson="{\"text\":\":x: \`$CIRCLE_JOB\` job for $CIRCLE_BRANCH branch failed on build $CIRCLE_BUILD_NUM: $CIRCLE_BUILD_URL :scream:\"}"
|
||||
curl --request POST --header "Content-Type: application/json" --data "$notificationJson" ${<< parameters.webhook_url_env_var >>}
|
||||
|
||||
# Job definitions
|
||||
# Jobs can include parameters that are passed in the workflow job invocation.
|
||||
# https://circleci.com/docs/2.0/reusing-config/#authoring-parameterized-jobs
|
||||
jobs:
|
||||
setup:
|
||||
<<: *job_defaults
|
||||
executor: default-executor
|
||||
steps:
|
||||
- checkout
|
||||
- *post_checkout
|
||||
- run:
|
||||
name: Rebase PR on target branch
|
||||
# After checkout, rebase on top of target branch.
|
||||
command: >
|
||||
if [[ -n "${CIRCLE_PR_NUMBER}" ]]; then
|
||||
# User is required for rebase.
|
||||
git config user.name "angular-ci"
|
||||
git config user.email "angular-ci"
|
||||
# Rebase PR on top of target branch.
|
||||
node tools/rebase-pr.js angular/angular ${CIRCLE_PR_NUMBER}
|
||||
else
|
||||
echo "This build is not over a PR, nothing to do."
|
||||
fi
|
||||
# This cache is saved in the build-npm-packages so that Bazel cache is also included.
|
||||
- *restore_cache
|
||||
- *init_environment
|
||||
- *yarn_install
|
||||
- restore_cache:
|
||||
keys:
|
||||
- *cache_key
|
||||
- *cache_key_fallback
|
||||
- init_environment
|
||||
- run:
|
||||
name: Running Yarn install
|
||||
command: yarn install --frozen-lockfile --non-interactive
|
||||
# Yarn's requests sometimes take more than 10mins to complete.
|
||||
no_output_timeout: 45m
|
||||
- run: yarn --cwd aio install --frozen-lockfile --non-interactive
|
||||
# Make the bazel directories and add a file to them if they don't exist already so that
|
||||
# persist_to_workspace does not fail.
|
||||
@ -166,19 +172,18 @@ jobs:
|
||||
touch ~/bazel_repository_cache/MARKER
|
||||
fi
|
||||
# Persist any changes at this point to be reused by further jobs.
|
||||
# **NOTE 1 **: Folders persisted here should be kept in sync with `var_13: &attach_workspace`.
|
||||
# **NOTE 2 **: To add new content to the workspace, always persist on the same root.
|
||||
# **NOTE**: To add new content to the workspace, always persist on the same root.
|
||||
- persist_to_workspace:
|
||||
root: ~/
|
||||
root: *workspace_location
|
||||
paths:
|
||||
- ./ng
|
||||
- ./bazel_repository_cache
|
||||
|
||||
lint:
|
||||
<<: *job_defaults
|
||||
executor: default-executor
|
||||
steps:
|
||||
- *attach_workspace
|
||||
- *init_environment
|
||||
- custom_attach_workspace
|
||||
- init_environment
|
||||
|
||||
- run: 'yarn bazel:format -mode=check ||
|
||||
(echo "BUILD files not formatted. Please run ''yarn bazel:format''" ; exit 1)'
|
||||
@ -187,27 +192,32 @@ 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
|
||||
resource_class: xlarge
|
||||
executor:
|
||||
name: default-executor
|
||||
resource_class: xlarge
|
||||
steps:
|
||||
- *attach_workspace
|
||||
- *init_environment
|
||||
- *setup_circleci_bazel_config
|
||||
- custom_attach_workspace
|
||||
- init_environment
|
||||
- setup_circleci_bazel_config
|
||||
# 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
|
||||
- setup_bazel_rbe
|
||||
- run:
|
||||
command: yarn bazel test //... --build_tag_filters=-ivy-only --test_tag_filters=-ivy-only
|
||||
no_output_timeout: 20m
|
||||
|
||||
# Temporary job to test what will happen when we flip the Ivy flag to true
|
||||
test_ivy_aot:
|
||||
<<: *job_defaults
|
||||
resource_class: xlarge
|
||||
executor:
|
||||
name: default-executor
|
||||
resource_class: xlarge
|
||||
steps:
|
||||
- *attach_workspace
|
||||
- *init_environment
|
||||
- *setup_circleci_bazel_config
|
||||
- *setup_bazel_remote_execution
|
||||
- custom_attach_workspace
|
||||
- init_environment
|
||||
- setup_circleci_bazel_config
|
||||
- setup_bazel_rbe
|
||||
|
||||
# We need to explicitly specify the --symlink_prefix option because otherwise we would
|
||||
# not be able to easily find the output bin directory when uploading artifacts for size
|
||||
@ -239,15 +249,17 @@ jobs:
|
||||
#
|
||||
# NOTE: This is currently limited to master builds only. See the `default_workflow` configuration.
|
||||
test_saucelabs_bazel:
|
||||
<<: *job_defaults
|
||||
# In order to avoid the bottleneck of having a slow host machine, we acquire a better
|
||||
# container for this job. This is necessary because we launch a lot of browsers concurrently
|
||||
# and therefore the tunnel and Karma need to process a lot of file requests and tests.
|
||||
resource_class: xlarge
|
||||
executor:
|
||||
name: default-executor
|
||||
# In order to avoid the bottleneck of having a slow host machine, we acquire a better
|
||||
# container for this job. This is necessary because we launch a lot of browsers concurrently
|
||||
# and therefore the tunnel and Karma need to process a lot of file requests and tests.
|
||||
resource_class: xlarge
|
||||
steps:
|
||||
- *attach_workspace
|
||||
- *init_environment
|
||||
- *setup_circleci_bazel_config
|
||||
- custom_attach_workspace
|
||||
- init_environment
|
||||
- setup_circleci_bazel_config
|
||||
- setup_bazel_rbe
|
||||
- run:
|
||||
name: Run Bazel tests in saucelabs
|
||||
# All web tests are contained within a single //:test_web_all target for Saucelabs
|
||||
@ -262,16 +274,16 @@ jobs:
|
||||
--username $SAUCE_USERNAME \
|
||||
--key $(echo $SAUCE_ACCESS_KEY | rev) \
|
||||
yarn bazel test //:test_web_all
|
||||
- *notify_dev_infra_on_fail
|
||||
no_output_timeout: 20m
|
||||
- notify_webhook_on_fail:
|
||||
webhook_url_env_var: SLACK_DEV_INFRA_CI_FAILURES_WEBHOOK_URL
|
||||
|
||||
test_aio:
|
||||
<<: *job_defaults
|
||||
docker:
|
||||
# Needed because the AIO tests and the PWA score test depend on Chrome being available.
|
||||
- image: *browsers_docker_image
|
||||
# Needed because the AIO tests and the PWA score test depend on Chrome being available.
|
||||
executor: browsers-executor
|
||||
steps:
|
||||
- *attach_workspace
|
||||
- *init_environment
|
||||
- custom_attach_workspace
|
||||
- init_environment
|
||||
# Build aio
|
||||
- run: yarn --cwd aio build --progress=false
|
||||
# Lint the code
|
||||
@ -290,27 +302,27 @@ jobs:
|
||||
- run: yarn --cwd aio redirects-test
|
||||
|
||||
deploy_aio:
|
||||
<<: *job_defaults
|
||||
docker:
|
||||
# Needed because before deploying the deploy-production script runs the PWA score tests.
|
||||
- image: *browsers_docker_image
|
||||
executor: browsers-executor
|
||||
steps:
|
||||
- *attach_workspace
|
||||
- *init_environment
|
||||
- custom_attach_workspace
|
||||
- init_environment
|
||||
# Deploy angular.io to production (if necessary)
|
||||
- run: setPublicVar_CI_STABLE_BRANCH
|
||||
- run: yarn --cwd aio deploy-production
|
||||
|
||||
test_aio_local:
|
||||
<<: *job_defaults
|
||||
docker:
|
||||
# Needed because the AIO tests and the PWA score test depend on Chrome being available.
|
||||
- image: *browsers_docker_image
|
||||
parameters:
|
||||
ivy:
|
||||
type: boolean
|
||||
default: false
|
||||
# Needed because the AIO tests and the PWA score test depend on Chrome being available.
|
||||
executor: browsers-executor
|
||||
steps:
|
||||
- *attach_workspace
|
||||
- *init_environment
|
||||
- custom_attach_workspace
|
||||
- init_environment
|
||||
# Build aio (with local Angular packages)
|
||||
- run: yarn --cwd aio build-local-ci
|
||||
- run: yarn --cwd aio build-local<<# parameters.ivy >>-with-ivy<</ parameters.ivy >>-ci
|
||||
# Run unit tests
|
||||
- run: yarn --cwd aio test --progress=false --watch=false
|
||||
# Run e2e tests
|
||||
@ -318,32 +330,13 @@ jobs:
|
||||
# Run PWA-score tests
|
||||
- run: yarn --cwd aio test-pwa-score-localhost $CI_AIO_MIN_PWA_SCORE
|
||||
# Check the bundle sizes.
|
||||
- run: yarn --cwd aio payload-size aio-local
|
||||
|
||||
test_aio_local_ivy:
|
||||
<<: *job_defaults
|
||||
docker:
|
||||
# Needed because the AIO tests and the PWA score test depend on Chrome being available.
|
||||
- image: *browsers_docker_image
|
||||
steps:
|
||||
- *attach_workspace
|
||||
- *init_environment
|
||||
# Build aio with Ivy (using local Angular packages)
|
||||
- run: yarn --cwd aio build-with-ivy-ci
|
||||
# Run unit tests
|
||||
- run: yarn --cwd aio test --progress=false --watch=false
|
||||
# Run e2e tests
|
||||
- run: yarn --cwd aio e2e --configuration=ci
|
||||
# Run PWA-score tests
|
||||
- run: yarn --cwd aio test-pwa-score-localhost $CI_AIO_MIN_PWA_SCORE
|
||||
# Check the bundle sizes.
|
||||
- run: yarn --cwd aio payload-size aio-local-ivy
|
||||
- run: yarn --cwd aio payload-size aio-local<<# parameters.ivy >>-ivy<</ parameters.ivy >>
|
||||
|
||||
test_aio_tools:
|
||||
<<: *job_defaults
|
||||
executor: default-executor
|
||||
steps:
|
||||
- *attach_workspace
|
||||
- *init_environment
|
||||
- custom_attach_workspace
|
||||
- init_environment
|
||||
# Install
|
||||
- run: yarn --cwd aio install --frozen-lockfile --non-interactive
|
||||
- run: yarn --cwd aio extract-cli-command-docs
|
||||
@ -352,56 +345,42 @@ jobs:
|
||||
- run: ./aio/aio-builds-setup/scripts/test.sh
|
||||
|
||||
test_docs_examples:
|
||||
<<: *job_defaults
|
||||
docker:
|
||||
parameters:
|
||||
ivy:
|
||||
type: boolean
|
||||
default: false
|
||||
executor:
|
||||
# Needed because the example e2e tests depend on Chrome.
|
||||
- image: *browsers_docker_image
|
||||
parallelism: 4
|
||||
resource_class: xlarge
|
||||
steps:
|
||||
- *attach_workspace
|
||||
- *init_environment
|
||||
# Install aio
|
||||
- run: yarn --cwd aio install --frozen-lockfile --non-interactive
|
||||
# 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}
|
||||
|
||||
test_docs_examples_ivy:
|
||||
<<: *job_defaults
|
||||
docker:
|
||||
# Needed because the example e2e tests depend on Chrome.
|
||||
- image: *browsers_docker_image
|
||||
resource_class: xlarge
|
||||
# We increase the parallelism here to five while the "test_docs_examples" job runs with
|
||||
# a parallelism of four. This is necessary because this job also need to run NGCC which
|
||||
# takes up more time and we don't want these jobs to impact the overall CI turnaround.
|
||||
name: browsers-executor
|
||||
resource_class: xlarge
|
||||
parallelism: 5
|
||||
steps:
|
||||
- *attach_workspace
|
||||
- *init_environment
|
||||
- custom_attach_workspace
|
||||
- init_environment
|
||||
# Install aio
|
||||
- run: yarn --cwd aio install --frozen-lockfile --non-interactive
|
||||
# Rename the Ivy packages dist folder to "dist/packages-dist" as the AIO
|
||||
# package installer picks up the locally built packages from that location.
|
||||
# *Note*: We could also adjust the packages installer, but given we won't have
|
||||
# two different folders of Angular distributions in the future, we should keep
|
||||
# the packages installer unchanged.
|
||||
- run: mv dist/packages-dist-ivy-aot dist/packages-dist
|
||||
# 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}
|
||||
- when:
|
||||
condition: << parameters.ivy >>
|
||||
steps:
|
||||
# Rename the Ivy packages dist folder to "dist/packages-dist" as the AIO
|
||||
# package installer picks up the locally built packages from that location.
|
||||
# *Note*: We could also adjust the packages installer, but given we won't have
|
||||
# two different folders of Angular distributions in the future, we should keep
|
||||
# the packages installer unchanged.
|
||||
- run: mv dist/packages-dist-ivy-aot dist/packages-dist
|
||||
# Run examples tests. The "CIRCLE_NODE_INDEX" will be set if "parallelism" is enabled.
|
||||
# Since the parallelism is set to "5", there will be five parallel CircleCI containers.
|
||||
# with either "0", "1", etc as node index. This can be passed to the "--shard" argument.
|
||||
- run: yarn --cwd aio example-e2e --setup --local <<# parameters.ivy >>--ivy<</ parameters.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:
|
||||
<<: *job_defaults
|
||||
executor: default-executor
|
||||
environment:
|
||||
AIO_SNAPSHOT_ARTIFACT_PATH: &aio_preview_artifact_path 'aio/tmp/snapshot.tgz'
|
||||
steps:
|
||||
- *attach_workspace
|
||||
- *init_environment
|
||||
- custom_attach_workspace
|
||||
- init_environment
|
||||
- run: ./aio/scripts/build-artifacts.sh $AIO_SNAPSHOT_ARTIFACT_PATH $CI_PULL_REQUEST $CI_COMMIT
|
||||
- store_artifacts:
|
||||
path: *aio_preview_artifact_path
|
||||
@ -412,13 +391,11 @@ jobs:
|
||||
|
||||
# This job should only be run on PR builds, where `CI_PULL_REQUEST` is not `false`.
|
||||
test_aio_preview:
|
||||
<<: *job_defaults
|
||||
docker:
|
||||
# Needed because the test-preview script runs e2e tests and the PWA score test with Chrome.
|
||||
- image: *browsers_docker_image
|
||||
# Needed because the test-preview script runs e2e tests and the PWA score test with Chrome.
|
||||
executor: browsers-executor
|
||||
steps:
|
||||
- *attach_workspace
|
||||
- *init_environment
|
||||
- custom_attach_workspace
|
||||
- init_environment
|
||||
- run: yarn --cwd aio install --frozen-lockfile --non-interactive
|
||||
- run:
|
||||
name: Wait for preview and run tests
|
||||
@ -434,19 +411,20 @@ jobs:
|
||||
|
||||
# Build the view engine npm packages. No new jobs should depend on this.
|
||||
build-npm-packages:
|
||||
<<: *job_defaults
|
||||
resource_class: xlarge
|
||||
executor:
|
||||
name: default-executor
|
||||
resource_class: xlarge
|
||||
steps:
|
||||
- *attach_workspace
|
||||
- *init_environment
|
||||
- *setup_circleci_bazel_config
|
||||
- *setup_bazel_remote_execution
|
||||
- custom_attach_workspace
|
||||
- init_environment
|
||||
- setup_circleci_bazel_config
|
||||
- setup_bazel_rbe
|
||||
|
||||
- run: scripts/build-packages-dist.sh
|
||||
|
||||
# Save the npm packages from //packages/... for other workflow jobs to read
|
||||
- persist_to_workspace:
|
||||
root: ~/
|
||||
root: *workspace_location
|
||||
paths:
|
||||
- ng/dist/packages-dist
|
||||
|
||||
@ -460,19 +438,20 @@ jobs:
|
||||
|
||||
# Build the ivy npm packages.
|
||||
build-ivy-npm-packages:
|
||||
<<: *job_defaults
|
||||
resource_class: xlarge
|
||||
executor:
|
||||
name: default-executor
|
||||
resource_class: xlarge
|
||||
steps:
|
||||
- *attach_workspace
|
||||
- *init_environment
|
||||
- *setup_circleci_bazel_config
|
||||
- *setup_bazel_remote_execution
|
||||
- custom_attach_workspace
|
||||
- init_environment
|
||||
- setup_circleci_bazel_config
|
||||
- setup_bazel_rbe
|
||||
|
||||
- run: scripts/build-ivy-npm-packages.sh
|
||||
|
||||
# Save the npm packages from //packages/... for other workflow jobs to read
|
||||
- persist_to_workspace:
|
||||
root: ~/
|
||||
root: *workspace_location
|
||||
paths:
|
||||
- ng/dist/packages-dist-ivy-aot
|
||||
|
||||
@ -483,18 +462,17 @@ jobs:
|
||||
# need to re-run manually should be alleviated.
|
||||
# See comments inside the integration/run_tests.sh script.
|
||||
integration_test:
|
||||
<<: *job_defaults
|
||||
parallelism: 4
|
||||
docker:
|
||||
executor:
|
||||
# Needed because the integration tests expect Chrome to be installed (e.g cli-hello-world)
|
||||
- image: *browsers_docker_image
|
||||
# Note: we run Bazel in one of the integration tests, and it can consume >2G
|
||||
# of memory. Together with the system under test, this can exhaust the RAM
|
||||
# on a 4G worker so we use a larger machine here too.
|
||||
resource_class: xlarge
|
||||
name: browsers-executor
|
||||
# Note: we run Bazel in one of the integration tests, and it can consume >2G
|
||||
# of memory. Together with the system under test, this can exhaust the RAM
|
||||
# on a 4G worker so we use a larger machine here too.
|
||||
resource_class: xlarge
|
||||
parallelism: 4
|
||||
steps:
|
||||
- *attach_workspace
|
||||
- *init_environment
|
||||
- custom_attach_workspace
|
||||
- init_environment
|
||||
# Runs the integration tests in parallel across multiple CircleCI container instances. The
|
||||
# amount of container nodes for this job is controlled by the "parallelism" option.
|
||||
- run: ./integration/run_tests.sh ${CIRCLE_NODE_INDEX} ${CIRCLE_NODE_TOTAL}
|
||||
@ -502,7 +480,7 @@ jobs:
|
||||
# This job updates the content of repos like github.com/angular/core-builds
|
||||
# for every green build on angular/angular.
|
||||
publish_snapshot:
|
||||
<<: *job_defaults
|
||||
executor: default-executor
|
||||
steps:
|
||||
# See below - ideally this job should not trigger for non-upstream builds.
|
||||
# But since it does, we have to check this condition.
|
||||
@ -516,8 +494,8 @@ jobs:
|
||||
[[ "$CIRCLE_PROJECT_REPONAME" != "angular" ]]; then
|
||||
circleci step halt
|
||||
fi
|
||||
- *attach_workspace
|
||||
- *init_environment
|
||||
- custom_attach_workspace
|
||||
- init_environment
|
||||
# CircleCI has a config setting to force SSH for all github connections
|
||||
# This is not compatible with our mechanism of using a Personal Access Token
|
||||
# Clear the global setting
|
||||
@ -531,14 +509,12 @@ jobs:
|
||||
- run: ./scripts/ci/publish-build-artifacts.sh
|
||||
|
||||
aio_monitoring_stable:
|
||||
<<: *job_defaults
|
||||
docker:
|
||||
# This job needs Chrome to be globally installed because the tests run with Protractor
|
||||
# which does not load the browser through the Bazel webtesting rules.
|
||||
- image: *browsers_docker_image
|
||||
# This job needs Chrome to be globally installed because the tests run with Protractor
|
||||
# which does not load the browser through the Bazel webtesting rules.
|
||||
executor: browsers-executor
|
||||
steps:
|
||||
- *attach_workspace
|
||||
- *init_environment
|
||||
- custom_attach_workspace
|
||||
- init_environment
|
||||
- run: setPublicVar_CI_STABLE_BRANCH
|
||||
- run:
|
||||
name: Check out `aio/` from the stable branch
|
||||
@ -548,33 +524,36 @@ jobs:
|
||||
- run:
|
||||
name: Run tests against https://angular.io/
|
||||
command: ./aio/scripts/test-production.sh https://angular.io/ $CI_AIO_MIN_PWA_SCORE
|
||||
- *notify_caretaker_on_fail
|
||||
- *notify_dev_infra_on_fail
|
||||
- notify_webhook_on_fail:
|
||||
webhook_url_env_var: SLACK_CARETAKER_WEBHOOK_URL
|
||||
- notify_webhook_on_fail:
|
||||
webhook_url_env_var: SLACK_DEV_INFRA_CI_FAILURES_WEBHOOK_URL
|
||||
|
||||
aio_monitoring_next:
|
||||
<<: *job_defaults
|
||||
docker:
|
||||
# This job needs Chrome to be globally installed because the tests run with Protractor
|
||||
# which does not load the browser through the Bazel webtesting rules.
|
||||
- image: *browsers_docker_image
|
||||
# This job needs Chrome to be globally installed because the tests run with Protractor
|
||||
# which does not load the browser through the Bazel webtesting rules.
|
||||
executor: browsers-executor
|
||||
steps:
|
||||
- *attach_workspace
|
||||
- *init_environment
|
||||
- custom_attach_workspace
|
||||
- init_environment
|
||||
- run:
|
||||
name: Run tests against https://next.angular.io/
|
||||
command: ./aio/scripts/test-production.sh https://next.angular.io/ $CI_AIO_MIN_PWA_SCORE
|
||||
- *notify_caretaker_on_fail
|
||||
- *notify_dev_infra_on_fail
|
||||
- notify_webhook_on_fail:
|
||||
webhook_url_env_var: SLACK_CARETAKER_WEBHOOK_URL
|
||||
- notify_webhook_on_fail:
|
||||
webhook_url_env_var: SLACK_DEV_INFRA_CI_FAILURES_WEBHOOK_URL
|
||||
|
||||
legacy-unit-tests-saucelabs:
|
||||
<<: *job_defaults
|
||||
# In order to avoid the bottleneck of having a slow host machine, we acquire a better
|
||||
# container for this job. This is necessary because we launch a lot of browsers concurrently
|
||||
# and therefore the tunnel and Karma need to process a lot of file requests and tests.
|
||||
resource_class: xlarge
|
||||
executor:
|
||||
name: default-executor
|
||||
# In order to avoid the bottleneck of having a slow host machine, we acquire a better
|
||||
# container for this job. This is necessary because we launch a lot of browsers concurrently
|
||||
# and therefore the tunnel and Karma need to process a lot of file requests and tests.
|
||||
resource_class: xlarge
|
||||
steps:
|
||||
- *attach_workspace
|
||||
- *init_environment
|
||||
- custom_attach_workspace
|
||||
- init_environment
|
||||
- run:
|
||||
name: Preparing environment for running tests on Saucelabs.
|
||||
command: |
|
||||
@ -593,10 +572,10 @@ jobs:
|
||||
- run: ./scripts/saucelabs/stop-tunnel.sh
|
||||
|
||||
legacy-misc-tests:
|
||||
<<: *job_defaults
|
||||
executor: default-executor
|
||||
steps:
|
||||
- *attach_workspace
|
||||
- *init_environment
|
||||
- custom_attach_workspace
|
||||
- init_environment
|
||||
- run: yarn gulp check-cycle
|
||||
# TODO: disabled because the Bazel packages-dist does not seem to have map files for
|
||||
# the ESM5/ES2015 output. See: https://github.com/angular/angular/issues/27966
|
||||
@ -605,23 +584,22 @@ jobs:
|
||||
# Job to run unit tests from angular/material2. Needs a browser since all
|
||||
# component unit tests assume they're running in the browser environment.
|
||||
material-unit-tests:
|
||||
<<: *job_defaults
|
||||
resource_class: xlarge
|
||||
docker:
|
||||
- image: *browsers_docker_image
|
||||
executor:
|
||||
name: browsers-executor
|
||||
resource_class: xlarge
|
||||
steps:
|
||||
- *attach_workspace
|
||||
- *init_environment
|
||||
- custom_attach_workspace
|
||||
- init_environment
|
||||
# Although RBE is configured below for the Material repo, also setup RBE in the Angular repo
|
||||
# to provision Angular's GCP token into the environment variables.
|
||||
- *setup_bazel_remote_execution
|
||||
- setup_bazel_rbe
|
||||
# Restore the cache before cloning the repository because the clone script re-uses
|
||||
# the restored repository if present. This reduces the amount of times the components
|
||||
# repository needs to be cloned (this is slow and increases based on commits in the repo).
|
||||
- restore_cache:
|
||||
keys:
|
||||
- *material_unit_tests_cache_key
|
||||
- *material_unit_tests_cache_key_short
|
||||
- *material_unit_tests_cache_key_fallback
|
||||
- run:
|
||||
name: "Fetching Material repository"
|
||||
command: ./scripts/ci/clone_angular_material_repo.sh
|
||||
@ -644,10 +622,10 @@ jobs:
|
||||
command: ./scripts/ci/run_angular_material_unit_tests.sh
|
||||
|
||||
test_zonejs:
|
||||
<<: *job_defaults
|
||||
executor: default-executor
|
||||
steps:
|
||||
- *attach_workspace
|
||||
- *init_environment
|
||||
- custom_attach_workspace
|
||||
- init_environment
|
||||
# Install
|
||||
- run: yarn --cwd packages/zone.js install --frozen-lockfile --non-interactive
|
||||
# Run zone.js tools tests
|
||||
@ -706,7 +684,9 @@ workflows:
|
||||
- test_aio_local:
|
||||
requires:
|
||||
- build-npm-packages
|
||||
- test_aio_local_ivy:
|
||||
- test_aio_local:
|
||||
name: test_aio_local_ivy
|
||||
ivy: true
|
||||
requires:
|
||||
- build-npm-packages
|
||||
- test_aio_tools:
|
||||
@ -715,7 +695,9 @@ workflows:
|
||||
- test_docs_examples:
|
||||
requires:
|
||||
- build-npm-packages
|
||||
- test_docs_examples_ivy:
|
||||
- test_docs_examples:
|
||||
name: test_docs_examples_ivy
|
||||
ivy: true
|
||||
requires:
|
||||
- build-ivy-npm-packages
|
||||
- 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',
|
||||
|
143
.github/CODEOWNERS
vendored
143
.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,11 +831,11 @@ 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
|
||||
@ -877,36 +878,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 +925,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 ...
|
||||
# ================================================
|
||||
|
55
CHANGELOG.md
55
CHANGELOG.md
@ -1,6 +1,61 @@
|
||||
<a name="8.2.10"></a>
|
||||
## [8.2.10](https://github.com/angular/angular/compare/8.2.9...8.2.10) (2019-10-09)
|
||||
|
||||
This release contains various API docs improvements.
|
||||
|
||||
|
||||
|
||||
<a name="8.2.9"></a>
|
||||
## [8.2.9](https://github.com/angular/angular/compare/8.2.8...8.2.9) (2019-10-02)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **upgrade:** fix AngularJsUrlCodec to support Safari ([#32959](https://github.com/angular/angular/issues/32959)) ([57457fb](https://github.com/angular/angular/commit/57457fb))
|
||||
|
||||
|
||||
|
||||
<a name="8.2.8"></a>
|
||||
## [8.2.8](https://github.com/angular/angular/compare/8.2.7...8.2.8) (2019-09-25)
|
||||
|
||||
This release contains various API docs improvements.
|
||||
|
||||
|
||||
|
||||
<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>
|
||||
|
@ -201,7 +201,7 @@ Must be one of the following:
|
||||
* **test**: Adding missing tests or correcting existing tests
|
||||
|
||||
### Scope
|
||||
The scope should be the name of the npm package affected (as perceived by the person reading the changelog generated from commit messages.
|
||||
The scope should be the name of the npm package affected (as perceived by the person reading the changelog generated from commit messages).
|
||||
|
||||
The following is the list of supported scopes:
|
||||
|
||||
|
@ -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(
|
||||
|
@ -18,7 +18,7 @@ Here are the most important tasks you might need to use:
|
||||
|
||||
* `yarn build` - create a production build of the application (after installing dependencies, boilerplate, etc).
|
||||
* `yarn build-local` - same as `build`, but use `setup-local` instead of `setup`.
|
||||
* `yarn build-with-ivy` - same as `build-local`, but in addition also turns on `ivy` mode in aio.
|
||||
* `yarn build-local-with-ivy` - same as `build-local`, but in addition also turns on `ivy` mode in aio.
|
||||
(Note: To turn on `ivy` mode in examples, see `yarn boilerplate:add` below.)
|
||||
|
||||
* `yarn start` - run a development web server that watches the files; then builds the doc-viewer and reloads the page, as necessary.
|
||||
|
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"]
|
||||
}
|
@ -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>
|
||||
*
|
||||
|
@ -206,6 +206,7 @@ function heroModuleSetup() {
|
||||
nameInput.value = 'quick BROWN fOx';
|
||||
|
||||
// dispatch a DOM event so that Angular learns of input value change.
|
||||
// use newEvent utility function (not provided by Angular) for better browser compatibility
|
||||
nameInput.dispatchEvent(newEvent('input'));
|
||||
|
||||
// Tell Angular to update the display binding through the title pipe
|
||||
|
@ -28,7 +28,7 @@ export class KeyUpComponent_v1 {
|
||||
// #docregion key-up-component-1-class
|
||||
|
||||
onKey(event: KeyboardEvent) { // with type info
|
||||
this.values += (<HTMLInputElement>event.target).value + ' | ';
|
||||
this.values += (event.target as HTMLInputElement).value + ' | ';
|
||||
}
|
||||
// #docregion key-up-component-1-class-no-type
|
||||
}
|
||||
|
@ -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': '100',
|
||||
|
||||
// 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
|
||||
|
||||
|
@ -88,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`
|
||||
|
||||
|
@ -467,7 +467,7 @@ export class AppComponent {
|
||||
The collector can represent a function call or object creation with `new` as long as the syntax is valid.
|
||||
The compiler, however, can later refuse to generate a call to a _particular_ function or creation of a _particular_ object.
|
||||
|
||||
The compiler can only create instances certain classes, supports only core decorators, and only supports calls to macros (functions or static methods) that return expressions.
|
||||
The compiler can only create instances of certain classes, supports only core decorators, and only supports calls to macros (functions or static methods) that return expressions.
|
||||
* New instances
|
||||
|
||||
The compiler only allows metadata that create instances of the class `InjectionToken` from `@angular/core`.
|
||||
|
@ -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.
|
||||
|
||||
|
||||
|
@ -142,7 +142,7 @@ Begin by adding `HostListener` to the list of imported symbols.
|
||||
|
||||
<code-example path="attribute-directives/src/app/highlight.directive.2.ts" header="src/app/highlight.directive.ts (imports)" region="imports"></code-example>
|
||||
|
||||
Then add two eventhandlers that respond when the mouse enters or leaves,
|
||||
Then add two event handlers that respond when the mouse enters or leaves,
|
||||
each adorned by the `HostListener` decorator.
|
||||
|
||||
<code-example path="attribute-directives/src/app/highlight.directive.2.ts" header="src/app/highlight.directive.ts (mouse-methods)" region="mouse-methods"></code-example>
|
||||
|
@ -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}
|
||||
|
||||
|
@ -59,8 +59,7 @@ npm install @example/my-builder
|
||||
As an example, let’s create a builder that executes a shell command.
|
||||
To create a builder, use the `createBuilder()` CLI Builder function, and return a `BuilderOutput` object.
|
||||
|
||||
```
|
||||
|
||||
<code-example language="typescript" header="/command/index.ts">
|
||||
import { BuilderOutput, createBuilder } from '@angular-devkit/architect';
|
||||
|
||||
export default createBuilder(_commandBuilder);
|
||||
@ -72,13 +71,13 @@ function _commandBuilder(
|
||||
...
|
||||
}
|
||||
|
||||
```
|
||||
</code-example>
|
||||
|
||||
Now let’s add some logic to it.
|
||||
The following code retrieves the command and arguments from the user options, spawns the new process, and waits for the process to finish.
|
||||
If the process is successful (returns a code of 0), it resolves the return value.
|
||||
|
||||
```
|
||||
<code-example language="typescript" header="/command/index.ts">
|
||||
import { BuilderOutput, createBuilder } from '@angular-devkit/architect';
|
||||
import * as childProcess from 'child_process';
|
||||
|
||||
@ -95,7 +94,8 @@ function _commandBuilder(
|
||||
});
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
</code-example>
|
||||
|
||||
### Handling output
|
||||
|
||||
@ -105,7 +105,7 @@ This also allows the builder itself to be executed in a separate process, even i
|
||||
|
||||
We can retrieve a Logger instance from the context.
|
||||
|
||||
```
|
||||
<code-example language="typescript" header="/command/index.ts">
|
||||
import { BuilderOutput, createBuilder, BuilderContext } from '@angular-devkit/architect';
|
||||
import * as childProcess from 'child_process';
|
||||
|
||||
@ -130,7 +130,7 @@ function _commandBuilder(
|
||||
});
|
||||
}
|
||||
|
||||
```
|
||||
</code-example>
|
||||
|
||||
### Progress and status reporting
|
||||
|
||||
@ -147,7 +147,7 @@ Use the `BuilderContext.reportStatus()` method to generate a status string of an
|
||||
(Note that there’s no guarantee that a long string will be shown entirely; it could be cut to fit the UI that displays it.)
|
||||
Pass an empty string to remove the status.
|
||||
|
||||
```
|
||||
<code-example language="typescript" header="/command/index.ts">
|
||||
import { BuilderOutput, createBuilder, BuilderContext } from '@angular-devkit/architect';
|
||||
import * as childProcess from 'child_process';
|
||||
|
||||
@ -174,7 +174,8 @@ function _commandBuilder(
|
||||
});
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
</code-example>
|
||||
|
||||
## Builder input
|
||||
|
||||
@ -191,8 +192,7 @@ For our example builder, we expect the `options` value to be a `JsonObject` with
|
||||
|
||||
We can provide the following schema for type validation of these values.
|
||||
|
||||
<code-example language="json">
|
||||
|
||||
<code-example language="json" header="command/schema.json">
|
||||
{
|
||||
"$schema": "http://json-schema.org/schema",
|
||||
"type": "object",
|
||||
@ -222,7 +222,7 @@ To link our builder implementation with its schema and name, we need to create a
|
||||
|
||||
Create a file named `builders.json` file that looks like this.
|
||||
|
||||
<code-example language="json">
|
||||
<code-example language="json" header="builders.json">
|
||||
|
||||
{
|
||||
"builders": {
|
||||
@ -238,7 +238,7 @@ Create a file named `builders.json` file that looks like this.
|
||||
|
||||
In the `package.json` file, add a `builders` key that tells the Architect tool where to find our builder definition file.
|
||||
|
||||
<code-example language="json">
|
||||
<code-example language="json" header="package.json">
|
||||
|
||||
{
|
||||
"name": "@example/command-runner",
|
||||
@ -257,7 +257,7 @@ The first part of this is the package name (resolved using node resolution), an
|
||||
|
||||
Using one of our `options` is very straightforward, we did this in the previous section when we accessed `options.command`.
|
||||
|
||||
<code-example language="typescript">
|
||||
<code-example language="typescript" header="/command/index.ts">
|
||||
context.reportStatus(`Executing "${options.command}"...`);
|
||||
const child = childProcess.spawn(options.command, options.args, { stdio: 'pipe' });
|
||||
|
||||
@ -267,14 +267,14 @@ Using one of our `options` is very straightforward, we did this in the previous
|
||||
|
||||
A builder must have a defined target that associates it with a specific input configuration and [project](guide/glossary#project).
|
||||
|
||||
Targets are defined in the `angular.json` [workspace configuration file](guide/workspace-config).
|
||||
Targets are defined in the `angular.json` [CLI configuration file](guide/workspace-config).
|
||||
A target specifies the builder to use, its default options configuration, and named alternative configurations.
|
||||
The Architect tool uses the target definition to resolve input options for a given run.
|
||||
|
||||
The `angular.json` file has a section for each project, and the "architect" section of each project configures targets for builders used by CLI commands such as 'build', 'test', and 'lint'.
|
||||
By default, for example, the `build` command runs the builder `@angular-devkit/build-angular:browser` to perform the build task, and passes in default option values as specified for the `build` target in `angular.json`.
|
||||
By default, for example, the `build` command runs the builder `@angular-devkit/build-angular:browser` to perform the build task, and passes in default option values as specified for the `build` target in `angular.json`.
|
||||
|
||||
<code-example language="json">
|
||||
<code-example language="json" header="angular.json">
|
||||
{
|
||||
"myApp": {
|
||||
...
|
||||
@ -361,7 +361,7 @@ npm install @example/command-runner
|
||||
|
||||
If we create a new project with `ng new builder-test`, the generated `angular.json` file looks something like this, with only default builder configurations.
|
||||
|
||||
<code-example language="json">
|
||||
<code-example language="json" header="angular.json">
|
||||
|
||||
{
|
||||
// ...
|
||||
@ -413,7 +413,7 @@ We need to update the `angular.json` file to add a target for this builder to th
|
||||
|
||||
* The configurations key is optional, we'll leave it out for now.
|
||||
|
||||
<code-example language="json">
|
||||
<code-example language="json" header="angular.json">
|
||||
|
||||
{
|
||||
"projects": {
|
||||
@ -493,7 +493,7 @@ Use integration testing for your builder, so that you can use the Architect sche
|
||||
Here’s an example of a test that runs the command builder.
|
||||
The test uses the builder to run the `ls` command, then validates that it ran successfully and listed the proper files.
|
||||
|
||||
<code-example language="typescript">
|
||||
<code-example language="typescript" header="command/index_spec.ts">
|
||||
|
||||
import { Architect } from '@angular-devkit/architect';
|
||||
import { TestingArchitectHost } from '@angular-devkit/architect/testing';
|
||||
|
@ -188,8 +188,23 @@ You can rebuild your library whenever you make changes to it, but this extra ste
|
||||
*Incremental builds* functionality improves the library-development experience.
|
||||
Every time a file is changed a partial build is performed that emits the amended files.
|
||||
|
||||
Incremental builds can be run as a backround process in your dev environment. To take advantage of this feature add the `--watch` flag to the build command:
|
||||
Incremental builds can be run as a background process in your dev environment. To take advantage of this feature add the `--watch` flag to the build command:
|
||||
|
||||
<code-example language="bash">
|
||||
ng build my-lib --watch
|
||||
</code-example>
|
||||
|
||||
<div class="alert is-important">
|
||||
|
||||
The CLI `build` command uses a different builder and invokes a different build tool for libraries than it does for applications.
|
||||
|
||||
* The build system for apps, `@angular-devkit/build-angular`, is based on `webpack`, and is included in all new Angular CLI projects.
|
||||
* The build system for libraries is based on `ng-packagr`. It is only added to your dependencies when you add a library using `ng generate library my-lib`.
|
||||
|
||||
The two build systems support different things, and even where they support the same things, they do those things differently.
|
||||
This means that the TypeScript source can result in different JavaScript code in a built library than it would in a built application.
|
||||
|
||||
For this reason, an app that depends on a library should only use TypeScript path mappings that point to the *built library*.
|
||||
TypeScript path mappings should *not* point to the library source `.ts` files.
|
||||
|
||||
</div>
|
@ -304,7 +304,7 @@ Configure the Angular Router to defer loading of all other modules (and their as
|
||||
or by [_lazy loading_](guide/router#asynchronous-routing "Lazy loading")
|
||||
them on demand.
|
||||
|
||||
<div class="callout is-helpful>
|
||||
<div class="callout is-helpful">
|
||||
|
||||
<header>Don't eagerly import something from a lazy-loaded module</header>
|
||||
|
||||
@ -618,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">
|
||||
|
||||
@ -680,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">
|
||||
|
||||
|
@ -396,10 +396,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 +464,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()`)|
|
||||
|
@ -322,6 +322,7 @@ The <code class="no-auto-link">item</code> property is `true`.
|
||||
|
||||
For block code snippets, we generally prefer to display code with
|
||||
the Angular documentation _code-example_ component represented by the `<code-example>` tag.
|
||||
The `<code-example>` tag has a `header` attribute that you use to identify the file that the example comes from. The header should be used whenever possible to establish the context of the example.
|
||||
See [Code snippets and code examples](guide/docs-style-guide#code-snippets-and-code-samples) for more details.
|
||||
|
||||
<h3 class="no-toc">Inline code-snippets</h3>
|
||||
@ -348,6 +349,8 @@ user input in a command shell or the _output_ of some process.
|
||||
**Do not write inline code snippets** unless you have a good reason and the editor's permission to do so.
|
||||
In all other cases, code snippets should be generated automatically from tested code samples.
|
||||
|
||||
For hypothetical examples such as illustrations of configuration options in a JSON file, you should still use The `<code-example>` tag with the `header` attribute to identify the context.
|
||||
|
||||
{@a from-code-samples}
|
||||
|
||||
<h3 class="no-toc">Code snippets and code samples</h3>
|
||||
|
@ -170,7 +170,7 @@ You can download the full code for the example <live-example downloadOnly>here</
|
||||
|
||||
Generic DOM APIs, such as `document.createElement()` or `document.querySelector()`, return an element type that is appropriate for the specified arguments. For example, calling `document.createElement('a')` will return an `HTMLAnchorElement`, which TypeScript knows has an `href` property. Similarly, `document.createElement('div')` will return an `HTMLDivElement`, which TypeScript knows has no `href` property.
|
||||
|
||||
When called with unknown elements, such as a custom element name (`popup-element` in our example), the methods will return a generic type, such as `HTMLELement`, since TypeScript can't infer the correct type of the returned element.
|
||||
When called with unknown elements, such as a custom element name (`popup-element` in our example), the methods will return a generic type, such as `HTMLElement`, since TypeScript can't infer the correct type of the returned element.
|
||||
|
||||
Custom elements created with Angular extend `NgElement` (which in turn extends `HTMLElement`). Additionally, these custom elements will have a property for each input of the corresponding component. For example, our `popup-element` will have a `message` property of type `string`.
|
||||
|
||||
@ -194,7 +194,7 @@ aDialog.body = 'News'; // <-- ERROR: TypeScript knows there is no `body` proper
|
||||
|
||||
This is a good way to quickly get TypeScript features, such as type checking and autocomplete support, for you custom element. But it can get cumbersome if you need it in several places, because you have to cast the return type on every occurrence.
|
||||
|
||||
An alternative way, that only requires defining each custom element's type once, is augmenting the `HTMLELementTagNameMap`, which TypeScript uses to infer the type of a returned element based on its tag name (for DOM methods such as `document.createElement()`, `document.querySelector()`, etc.):
|
||||
An alternative way, that only requires defining each custom element's type once, is augmenting the `HTMLElementTagNameMap`, which TypeScript uses to infer the type of a returned element based on its tag name (for DOM methods such as `document.createElement()`, `document.querySelector()`, etc.):
|
||||
|
||||
```ts
|
||||
declare global {
|
||||
|
@ -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}
|
||||
|
@ -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.
|
||||
@ -763,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:
|
||||
@ -772,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": {
|
||||
@ -786,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/)
|
||||
|
||||
|
@ -2,8 +2,7 @@
|
||||
|
||||
A component has a lifecycle managed by Angular.
|
||||
|
||||
Angular creates it, renders it, creates and renders its children,
|
||||
checks it when its data-bound properties change, and destroys it before removing it from the DOM.
|
||||
Angular creates and renders components along with their children, checks when their data-bound properties change, and destroys them before removing them from the DOM.
|
||||
|
||||
Angular offers **lifecycle hooks**
|
||||
that provide visibility into these key life moments and the ability to act when they occur.
|
||||
|
@ -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>.
|
||||
|
@ -32,7 +32,7 @@ The context also defines a *merge strategy* that determines how changes are merg
|
||||
When you create a new blank schematic with the [Schematics CLI](#cli), the generated entry function is a *rule factory*.
|
||||
A `RuleFactory`object defines a higher-order function that creates a `Rule`.
|
||||
|
||||
<code-example language="TypeScript">
|
||||
<code-example language="TypeScript" header="index.ts">
|
||||
import { Rule, SchematicContext, Tree } from '@angular-devkit/schematics';
|
||||
|
||||
// You don't have to export the function as default.
|
||||
@ -49,7 +49,7 @@ You need a rule, for example, to define how a template in the schematic is to be
|
||||
|
||||
Rules can make use of utilities provided with the `@schematics/angular` package. Look for helper functions for working with modules, dependencies, TypeScript, AST, JSON, Angular CLI workspaces and projects, and more.
|
||||
|
||||
<code-example language="none">
|
||||
<code-example language="TypeScript" header="index.ts">
|
||||
|
||||
import {
|
||||
JsonAstObject,
|
||||
@ -69,8 +69,191 @@ Rules can collect option values from the caller and inject them into templates.
|
||||
The options available to your rules, with their allowed values and defaults, are defined in the schematic's JSON schema file, `<schematic>/schema.json`.
|
||||
You can define variable or enumerated data types for the schema using TypeScript interfaces.
|
||||
|
||||
The schema defines the types and default values of variables used in the schematic.
|
||||
For example, the hypothetical "Hello World" schematic might have the following schema.
|
||||
|
||||
<code-example language="json" header="src/hello-world/schema.json">
|
||||
|
||||
{
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"minLength": 1,
|
||||
"default": "world"
|
||||
},
|
||||
"useColor": {
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
}
|
||||
</code-example>
|
||||
|
||||
|
||||
You can see examples of schema files for the Angular CLI command schematics in [`@schematics/angular`](https://github.com/angular/angular-cli/blob/7.0.x/packages/schematics/angular/application/schema.json).
|
||||
|
||||
### Schematic prompts
|
||||
|
||||
Schematic *prompts* introduce user interaction into schematic execution.
|
||||
You can configure schematic options to display a customizable question to the user.
|
||||
The prompts are displayed before the execution of the schematic, which then uses the response as the value for the option.
|
||||
This allows users to direct the operation of the schematic without requiring in-depth knowledge of the full spectrum of available options.
|
||||
|
||||
The "Hello World" schematic might, for example, ask the user for their name, and display that name in place of the default name "world". To define such a prompt, add an `x-prompt` property to the schema for the `name` variable.
|
||||
|
||||
Similarly, you can add a prompt to allow the user to decide whether the schematic will use color when executing its hello action. The schema with both prompts would be as follows.
|
||||
|
||||
<code-example language="json" header="src/hello-world/schema.json">
|
||||
|
||||
{
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"minLength": 1,
|
||||
"default": "world",
|
||||
"x-prompt": "What is your name?"
|
||||
},
|
||||
"useColor": {
|
||||
"type": "boolean",
|
||||
"x-prompt": "Would you like the response in color?"
|
||||
}
|
||||
}
|
||||
}
|
||||
</code-example>
|
||||
|
||||
#### Prompt short-form syntax
|
||||
|
||||
These examples use a shorthand form of the prompt syntax, supplying only the text of the question.
|
||||
In most cases, this is all that is required.
|
||||
Notice however, that the two prompts expect different types of input.
|
||||
When using the shorthand form, the most appropriate type is automatically selected based on the property's schema.
|
||||
In the example, the `name` prompt uses the `input` type because it it is a string property.
|
||||
The `useColor` prompt uses a `confirmation` type because it is a Boolean property.
|
||||
In this case, "yes" corresponds to `true` and "no" corresponds to `false`.
|
||||
|
||||
There are three supported input types.
|
||||
|
||||
| Input type | Description |
|
||||
| :----------- | :-------------------|
|
||||
| confirmation | A yes or no question; ideal for Boolean options. |
|
||||
| input | Textual input; ideal for string or number options. |
|
||||
| list | A predefined set of allowed values. |
|
||||
|
||||
In the short form, the type is inferred from the property's type and constraints.
|
||||
|
||||
| Property Schema | Prompt Type |
|
||||
| :--------------- | :------------- |
|
||||
| "type": "boolean" | confirmation ("yes"=`true`, "no"=`false`) |
|
||||
| "type": "string" | input |
|
||||
| "type": "number" | input (only valid numbers accepted) |
|
||||
| "type": "integer" | input (only valid numbers accepted) |
|
||||
| "enum": [...] | list (enum members become list selections) |
|
||||
|
||||
In the following example, the property takes an enumerated value, so the schematic automatically chooses the list type, and creates a menu from the possible values.
|
||||
|
||||
<code-example language="json" header="schema.json">
|
||||
|
||||
"style": {
|
||||
"description": "The file extension or preprocessor to use for style files.",
|
||||
"type": "string",
|
||||
"default": "css",
|
||||
"enum": [
|
||||
"css",
|
||||
"scss",
|
||||
"sass",
|
||||
"less",
|
||||
"styl"
|
||||
],
|
||||
"x-prompt": "Which stylesheet format would you like to use?"
|
||||
}
|
||||
|
||||
</code-example>
|
||||
|
||||
The prompt runtime automatically validates the provided response against the constraints provided in the JSON schema.
|
||||
If the value is not acceptable, the user is prompted for a new value.
|
||||
This ensures that any values passed to the schematic meet the expectations of the schematic's implementation, so that you do not need to add additional checks within the schematic's code.
|
||||
|
||||
#### Prompt long-form syntax
|
||||
|
||||
The `x-prompt` field syntax supports a long form for cases where you require additional customization and control over the prompt.
|
||||
In this form, the `x-prompt` field value is a JSON object with subfields that customize the behavior of the prompt.
|
||||
|
||||
| Field | Data Value |
|
||||
| :----------- | :------ |
|
||||
| type | `confirmation`, `input`, or `list` (selected automatically in short form) |
|
||||
| message | string (required) |
|
||||
| items | string and/or label/value object pair (only valid with type `list`) |
|
||||
|
||||
The following example of the long form is from the JSON schema for the schematic that the CLI uses to [generate applications](https://github.com/angular/angular-cli/blob/ba8a6ea59983bb52a6f1e66d105c5a77517f062e/packages/schematics/angular/application/schema.json#L56).
|
||||
It defines the prompt that allows users to choose which style preprocessor they want to use for the application being created.
|
||||
By using the long form, the schematic can provide more explicit formatting of the menu choices.
|
||||
|
||||
<code-example language="json" header="package/schematics/angular/application/schema.json">
|
||||
|
||||
"style": {
|
||||
"description": "The file extension or preprocessor to use for style files.",
|
||||
"type": "string",
|
||||
"default": "css",
|
||||
"enum": [
|
||||
"css",
|
||||
"scss",
|
||||
"sass",
|
||||
"less",
|
||||
"styl"
|
||||
],
|
||||
"x-prompt": {
|
||||
"message": "Which stylesheet format would you like to use?",
|
||||
"type": "list",
|
||||
"items": [
|
||||
{ "value": "css", "label": "CSS" },
|
||||
{ "value": "scss", "label": "SCSS [ https://sass-lang.com/documentation/syntax#scss ]" },
|
||||
{ "value": "sass", "label": "Sass [ https://sass-lang.com/documentation/syntax#the-indented-syntax ]" },
|
||||
{ "value": "less", "label": "Less [ http://lesscss.org ]" },
|
||||
{ "value": "styl", "label": "Stylus [ http://stylus-lang.com ]" }
|
||||
]
|
||||
},
|
||||
},
|
||||
</code-example>
|
||||
|
||||
#### x-prompt schema
|
||||
|
||||
The JSON schema that defines a schematic's options supports extensions to allow the declarative definition of prompts and their respective behavior.
|
||||
No additional logic or changes are required to the code of a schematic to support the prompts.
|
||||
The following JSON schema is a complete description of the long-form syntax for the `x-prompt` field.
|
||||
|
||||
<code-example language="json" header="x-prompt schema">
|
||||
|
||||
{
|
||||
"oneOf": [
|
||||
{ "type": "string" },
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"type": { "type": "string" },
|
||||
"message": { "type": "string" },
|
||||
"items": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"oneOf": [
|
||||
{ "type": "string" },
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"label": { "type": "string" },
|
||||
"value": { }
|
||||
},
|
||||
"required": [ "value" ]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": [ "message" ]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
</code-example>
|
||||
|
||||
{@a cli}
|
||||
|
||||
## Schematics CLI
|
||||
|
@ -207,6 +207,9 @@ There are two possible degraded states:
|
||||
clean copy of the latest known version of the app. Older cached
|
||||
versions are safe to use, so existing tabs continue to run from
|
||||
cache, but new loads of the app will be served from the network.
|
||||
The service worker will try to recover from this state when a new
|
||||
version of the application is detected and installed (that is,
|
||||
when a new `ngsw.json` is available).
|
||||
|
||||
* `SAFE_MODE`: the service worker cannot guarantee the safety of
|
||||
using cached data. Either an unexpected error occurred or all
|
||||
@ -216,6 +219,12 @@ network, running as little service worker code as possible.
|
||||
In both cases, the parenthetical annotation provides the
|
||||
error that caused the service worker to enter the degraded state.
|
||||
|
||||
Both states are temporary; they are saved only for the lifetime of the [ServiceWorker
|
||||
instance](https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorkerGlobalScope).
|
||||
The browser sometimes terminates an idle service worker to conserve memory and
|
||||
processor power, and creates a new service worker instance in response to
|
||||
network events. The new instance starts in the `NORMAL` mode, regardless of the
|
||||
state of the previous instance.
|
||||
|
||||
#### Latest manifest hash
|
||||
|
||||
|
@ -297,7 +297,7 @@ describes additional `NgFor` directive properties and context properties.
|
||||
|
||||
These microsyntax mechanisms are also available to you when you write your own structural directives.
|
||||
For example, microsyntax in Angular allows you to write `<div *ngFor="let item of items">{{item}}</div>`
|
||||
instead of `<ng-template ngFor [ngForOf]="items"><div>{{item}}</div></ng-template`.
|
||||
instead of `<ng-template ngFor [ngForOf]="items"><div>{{item}}</div></ng-template>`.
|
||||
The following sections provide detailed information on constraints, grammar,
|
||||
and translation of microsyntax.
|
||||
|
||||
|
@ -635,7 +635,7 @@ which is the attribute, spelled with a lowercase `s`.
|
||||
|
||||
<code-example path="property-binding/src/app/app.component.html" region="colSpan" header="src/app/app.component.html"></code-example>
|
||||
|
||||
For more details, see the [MDN HTMLTableCellElment](https://developer.mozilla.org/en-US/docs/Web/API/HTMLTableCellElement) documentation.
|
||||
For more details, see the [MDN HTMLTableCellElement](https://developer.mozilla.org/en-US/docs/Web/API/HTMLTableCellElement) documentation.
|
||||
|
||||
<!-- Add link when Attribute Binding updates are merged:
|
||||
For more about `colSpan` and `colspan`, see (Attribute Binding)[guide/template-syntax]. -->
|
||||
@ -721,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>
|
||||
@ -1610,8 +1610,8 @@ by HTML.
|
||||
|
||||
The reference value of itemForm, without the ngForm attribute value, would be
|
||||
the [HTMLFormElement](https://developer.mozilla.org/en-US/docs/Web/API/HTMLFormElement).
|
||||
There is, however, a difference between a Component and a Directive in that a `Component
|
||||
`will be referenced without specifying the attribute value, and a `Directive` will not
|
||||
There is, however, a difference between a Component and a Directive in that a `Component`
|
||||
will be referenced without specifying the attribute value, and a `Directive` will not
|
||||
change the implicit reference (that is, the element).
|
||||
|
||||
|
||||
|
@ -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.
|
||||
@ -1246,9 +1245,9 @@ XHR calls within a test are rare, but if you need to call XHR, see [`async()`](#
|
||||
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"
|
||||
@ -1272,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`.
|
||||
|
||||
@ -1317,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
|
||||
@ -1346,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.
|
||||
@ -1361,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"
|
||||
@ -1475,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.
|
||||
@ -1958,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.
|
||||
|
||||
@ -2758,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.
|
||||
@ -3254,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.
|
||||
|
||||
|
@ -34,7 +34,6 @@ 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",
|
||||
@ -54,6 +53,7 @@ The initial `tsconfig.json` for an Angular app typically looks like this example
|
||||
"dom"
|
||||
]
|
||||
}
|
||||
}
|
||||
</code-example>
|
||||
|
||||
|
||||
|
@ -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';
|
||||
|
@ -1622,7 +1622,7 @@ There are several notable changes here:
|
||||
* You're using the property binding syntax around `ng-class`. Though Angular
|
||||
does have [a very similar `ngClass`](guide/template-syntax#directives)
|
||||
as AngularJS does, its value is not magically evaluated as an expression.
|
||||
In Angular, you always specify in the template when an attribute's value is
|
||||
In Angular, you always specify in the template when an attribute's value is
|
||||
a property expression, as opposed to a literal string.
|
||||
|
||||
* You've replaced `ng-repeat`s with `*ngFor`s.
|
||||
@ -1709,7 +1709,7 @@ Create a new `app.component.ts` file with the following `AppComponent` class:
|
||||
<code-example path="upgrade-phonecat-3-final/app/app.component.ts" header="app/app.component.ts">
|
||||
</code-example>
|
||||
|
||||
It has a simple template that only includes the `<router-outlet>.
|
||||
It has a simple template that only includes the `<router-outlet>`.
|
||||
This component just renders the contents of the active route and nothing else.
|
||||
|
||||
The selector tells Angular to plug this root component into the `<phonecat-app>`
|
||||
|
@ -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>
|
||||
|
@ -66,6 +66,13 @@
|
||||
"rev": true,
|
||||
"title": "Happy Angular Podcast",
|
||||
"url": "https://happy-angular.de/"
|
||||
},
|
||||
"ngruair": {
|
||||
"desc": "Russian language video podcast about Angular.",
|
||||
"logo": "",
|
||||
"rev": true,
|
||||
"title": "NgRuAir",
|
||||
"url": "https://github.com/ngRuAir/ngruair"
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -115,6 +122,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": "",
|
||||
@ -655,6 +668,19 @@
|
||||
"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"
|
||||
},
|
||||
"dotnettricks": {
|
||||
"desc": "Online videos and training for Angular.",
|
||||
"logo": "",
|
||||
"rev": true,
|
||||
"title": "DotNetTricks",
|
||||
"url": "https://www.dotnettricks.com/courses/angular"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -20,7 +20,7 @@ Services are the place where you share data between parts of your application. F
|
||||
|
||||
Up to this point, users can view product information, and simulate sharing and being notified about product changes. They cannot, however, buy products.
|
||||
|
||||
In this section, you'll add a "Buy" button the product details page.
|
||||
In this section, you'll add a "Buy" button to the product details page.
|
||||
You'll also set up a cart service to store information about products in the cart.
|
||||
|
||||
<div class="alert is-helpful">
|
||||
|
@ -2,13 +2,12 @@
|
||||
|
||||
Welcome to Angular!
|
||||
|
||||
This tutorial introduces you to the essentials of Angular.
|
||||
It leverages what you already know about HTML and JavaScript—plus some useful Angular features—to build a simple online store application, with a catalog, shopping cart, and check-out form.
|
||||
You don't need to install anything: you'll build the app using the [StackBlitz](https://stackblitz.com/ "StackBlitz web site") online development environment.
|
||||
This tutorial introduces you to the essentials of Angular by walking you through building a simple e-commerce site with a catalog, shopping cart, and check-out form. It uses the [StackBlitz](https://stackblitz.com/ "StackBlitz website") online development environment so you can get started right away.
|
||||
|
||||
|
||||
<div class="alert is-helpful">
|
||||
|
||||
We are using the StackBlitz Generator to show you a ready-made, simple application that you can examine and play with interactively. In actual development you will typically use the [Angular CLI](guide/glossary#command-line-interface-cli), a powerful command-line tool that lets you generate and modify applications. For more information, see the [CLI Overview](cli).
|
||||
This guide uses the StackBlitz Generator to show you a ready-made, simple application that you can examine and play with interactively. In actual development you will typically use the [Angular CLI](guide/glossary#command-line-interface-cli), a powerful command-line tool that lets you generate and modify applications. For more information, see the [CLI Overview](cli).
|
||||
|
||||
</div>
|
||||
|
||||
@ -16,10 +15,10 @@ We are using the StackBlitz Generator to show you a ready-made, simple applicati
|
||||
<header>New to web development?</header>
|
||||
|
||||
|
||||
You'll find many resources to complement the Angular docs. Mozilla's MDN docs include both [HTML](https://developer.mozilla.org/en-US/docs/Learn/HTML "Learning HTML: Guides and tutorials") and [JavaScript](https://developer.mozilla.org/en-US/docs/Web/JavaScript "JavaScript") introductions. [TypeScript's docs](https://www.typescriptlang.org/docs/home.html "TypeScript documentation") include a 5-minute tutorial. Various online course platforms, such as [Udemy](http://www.udemy.com "Udemy online courses") and [Codecademy](https://www.codecademy.com/ "Codeacademy online courses"), also cover web development basics.
|
||||
There are many resources to complement the Angular docs. Mozilla's MDN docs include both [HTML](https://developer.mozilla.org/en-US/docs/Learn/HTML "Learning HTML: Guides and tutorials") and [JavaScript](https://developer.mozilla.org/en-US/docs/Web/JavaScript "JavaScript") introductions. [TypeScript's docs](https://www.typescriptlang.org/docs/home.html "TypeScript documentation") include a 5-minute tutorial. Various online course platforms, such as [Udemy](http://www.udemy.com "Udemy online courses") and [Codecademy](https://www.codecademy.com/ "Codeacademy online courses"), also cover web development basics.
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
@ -27,11 +26,12 @@ You'll find many resources to complement the Angular docs. Mozilla's MDN docs in
|
||||
## Create a new project
|
||||
|
||||
<h4>
|
||||
<live-example name="getting-started-v0" noDownload>Click here to create a new project in StackBlitz.</live-example>
|
||||
<live-example name="getting-started-v0" noDownload>Click here to create a new project in StackBlitz.</live-example>
|
||||
</h4>
|
||||
|
||||
StackBlitz creates a starter Angular app.
|
||||
We've seeded this particular app with a top bar—containing the store name and checkout icon—and the title for a product list.
|
||||
StackBlitz creates a starter Angular app with a top
|
||||
bar—containing the store name and
|
||||
checkout icon—and the title for a product list.
|
||||
|
||||
|
||||
<figure>
|
||||
@ -42,100 +42,111 @@ We've seeded this particular app with a top bar—containing the store name
|
||||
<div class="callout is-helpful">
|
||||
<header>StackBlitz tips</header>
|
||||
|
||||
* Log into StackBlitz, so you can save and resume your work. If you have a GitHub account, you can log into StackBlitz with that account. In order to save your progress, first fork the project using the Fork button at the top left, then you'll be able to save your work to your own StackBlitz account by clicking the Save button.
|
||||
* To copy a code example from this tutorial, click the icon at the top right of the code example box, and then paste the code snippet from the clipboard into StackBlitz.
|
||||
* If the StackBlitz preview pane isn't showing what you expect, save and then click the refresh button.
|
||||
* StackBlitz is continually improving, so there may be slight differences in generated code, but the app's behavior will be the same.
|
||||
* Log into StackBlitz so you can save and resume your work.
|
||||
If you have a GitHub account, you can log into StackBlitz
|
||||
with that account. In order to save your progress, first
|
||||
fork the project using the Fork button at the top left,
|
||||
then you'll be able to save your work to your own StackBlitz
|
||||
account by clicking the Save button.
|
||||
* To copy a code example from this tutorial, click the icon
|
||||
at the top right of the code example box, and then paste the
|
||||
code snippet from the clipboard into StackBlitz.
|
||||
* If the StackBlitz preview pane isn't showing what you
|
||||
expect, save and then click the refresh button.
|
||||
* StackBlitz is continually improving, so there may be
|
||||
slight differences in generated code, but the app's
|
||||
behavior will be the same.
|
||||
|
||||
</div>
|
||||
|
||||
{@a template-syntax}
|
||||
## Template syntax
|
||||
|
||||
<!--
|
||||
Angular extends HTML with a template syntax that gives components control over the display of content.
|
||||
This section introduces five things you can do in an Angular template to affect what your user sees, based on the component's state and behavior:
|
||||
-->
|
||||
Angular's template syntax extends HTML and JavaScript.
|
||||
This section introduces template syntax by enhancing the "Products" area.
|
||||
|
||||
Angular's template syntax extends HTML and JavaScript.
|
||||
In this section, you'll learn about template syntax by enhancing the "Products" area.
|
||||
<div class="alert is-helpful">
|
||||
|
||||
(So that you can focus on the template syntax, the following steps use predefined product data and methods from the `product-list.component.ts` file.)
|
||||
To help you get going, the following steps use predefined product data and methods from the `product-list.component.ts` file.
|
||||
|
||||
1. In the `product-list` folder, open the template file `product-list.component.html`.
|
||||
</div>
|
||||
|
||||
1. Modify the product list template to display a list of product names.
|
||||
1. In the `product-list` folder, open the template
|
||||
file `product-list.component.html`.
|
||||
|
||||
1. We want each product in the list to be displayed the same way, one after the other on the page. To iterate over the predefined list of products, use the `*ngFor` directive. Put the `*ngFor` directive on a `<div>`, as shown below:
|
||||
1. Modify the product list template to display a list of product names.
|
||||
|
||||
1. Each product in the list displays the same way, one after another on the page. To iterate over the predefined list of products, put the `*ngFor` directive on a `<div>`, as follows:
|
||||
|
||||
<code-example header="src/app/product-list/product-list.component.html" path="getting-started/src/app/product-list/product-list.component.2.html" region="ngfor">
|
||||
</code-example>
|
||||
|
||||
`*ngFor` causes the `<div>` to be repeated for each product in the list.
|
||||
With `*ngFor`, the `<div>` repeats for each product in the list.
|
||||
|
||||
<div class="alert is-helpful">
|
||||
|
||||
`*ngFor` is a "structural directive". Structural directives shape or reshape the DOM's structure, typically by adding, removing, and manipulating the elements to which they are attached. Any directive with an `*` is a structural directive.
|
||||
|
||||
`*ngFor` is a "structural directive". Structural directives shape or reshape the DOM's structure, typically by adding, removing, and manipulating the elements to which they are attached. Any directive with an asterisk, `*`, is a structural directive.
|
||||
|
||||
</div>
|
||||
|
||||
1. To display the names of the products, use the interpolation syntax {{ }}. Interpolation renders a property's value as text. Inside the `<div>`, add an `<h3>` heading to display the interpolation of the product's name property:
|
||||
1. To display the names of the products, use the interpolation syntax `{{ }}`. Interpolation renders a property's value as text. Inside the `<div>`, add an `<h3>` to display the interpolation of the product's name property:
|
||||
|
||||
<code-example path="getting-started/src/app/product-list/product-list.component.2.html" region="interpolation">
|
||||
<code-example path="getting-started/src/app/product-list/product-list.component.2.html" header="src/app/product-list/product-list.component.html" region="interpolation">
|
||||
</code-example>
|
||||
|
||||
The preview pane immediately updates to display the name of each product in the list.
|
||||
The preview pane immediately updates to display the name of each product in the list.
|
||||
|
||||
<figure>
|
||||
<img src="generated/images/guide/start/template-syntax-product-names.png" alt="Product names added to list">
|
||||
</figure>
|
||||
|
||||
1. In the final app, each product name will be a link to product details. Add the anchor now, and set the anchor's title to be the product's name by using the property binding [ ] syntax, as shown below:
|
||||
1. To make each product name a link to product details, add the `<a>` element and set its title to be the product's name by using the property binding `[ ]` syntax, as follows:
|
||||
|
||||
<code-example path="getting-started/src/app/product-list/product-list.component.2.html">
|
||||
<code-example path="getting-started/src/app/product-list/product-list.component.2.html" header="src/app/product-list/product-list.component.html">
|
||||
</code-example>
|
||||
|
||||
<!--
|
||||
To do: Description and code don't match exactly. Do we want to just use product name as the anchor hover text to show a simple property or append "details" to show an expression? Also affects screen shot.
|
||||
-->
|
||||
|
||||
In the preview pane, hover over the displayed product name to see the bound name property value. They are the same. Interpolation {{ }} lets you render the property value as text; property binding [ ] lets you use the property value in a template expression.
|
||||
In the preview pane, hold the pointer over a product
|
||||
name to see the bound name property value, which is
|
||||
the product name plus the word "details".
|
||||
Interpolation `{{ }}` lets you render the
|
||||
property value as text; property binding `[ ]` lets you
|
||||
use the property value in a template expression.
|
||||
|
||||
<figure>
|
||||
<img src="generated/images/guide/start/template-syntax-product-anchor.png" alt="Product name anchor text is product name property">
|
||||
</figure>
|
||||
|
||||
|
||||
1. Add the product descriptions. On the paragraph tag, use an `*ngIf` directive so that the paragraph element is only created if the current product has a description.
|
||||
|
||||
<code-example path="getting-started/src/app/product-list/product-list.component.3.html">
|
||||
1. Add the product descriptions. On the `<p>` element, use an `*ngIf` directive so that Angular only creates the `<p>` element if the current product has a description.
|
||||
|
||||
<code-example path="getting-started/src/app/product-list/product-list.component.3.html" header="src/app/product-list/product-list.component.html">
|
||||
</code-example>
|
||||
|
||||
The app now displays the name and description of each product in the list, as shown below. Notice that the final product does not have a description paragraph at all. Because the product's description property is empty, the paragraph element—including the word "Description"—is not created.
|
||||
The app now displays the name and description of each product in the list. Notice that the final product does not have a description paragraph. Because the product's description property is empty, Angular doesn't create the `<p>` element—including the word "Description".
|
||||
|
||||
<figure>
|
||||
<img src="generated/images/guide/start/template-syntax-product-description.png" alt="Product descriptions added to list">
|
||||
</figure>
|
||||
|
||||
1. Add a button so users can share a product with friends. Bind the button's `click` event to the `share()` event that we defined for you (in `product-list.component.ts`). Event binding is done by using ( ) around the event, as shown below:
|
||||
1. Add a button so users can share a product with friends. Bind the button's `click` event to the `share()` method (in `product-list.component.ts`). Event binding uses a set of parentheses, `( )`, around the event, as in the following `<button>` element:
|
||||
|
||||
<code-example path="getting-started/src/app/product-list/product-list.component.4.html">
|
||||
<code-example path="getting-started/src/app/product-list/product-list.component.4.html" header="src/app/product-list/product-list.component.html">
|
||||
</code-example>
|
||||
|
||||
Each product now has a "Share" button:
|
||||
Each product now has a "Share" button:
|
||||
|
||||
<figure>
|
||||
<img src="generated/images/guide/start/template-syntax-product-share-button.png" alt="Share button added for each product">
|
||||
</figure>
|
||||
|
||||
Test the "Share" button:
|
||||
Test the "Share" button:
|
||||
|
||||
<figure>
|
||||
<img src="generated/images/guide/start/template-syntax-product-share-alert.png" alt="Alert box indicating product has been shared">
|
||||
</figure>
|
||||
|
||||
The app now has a product list and sharing feature.
|
||||
In the process, you've learned to use five common features of Angular's template syntax:
|
||||
The app now has a product list and sharing feature.
|
||||
In the process, you've learned to use five common features of Angular's template syntax:
|
||||
* `*ngFor`
|
||||
* `*ngIf`
|
||||
* Interpolation `{{ }}`
|
||||
@ -145,7 +156,8 @@ In the process, you've learned to use five common features of Angular's template
|
||||
|
||||
<div class="alert is-helpful">
|
||||
|
||||
Learn more: See the [Template Syntax guide](guide/template-syntax "Template Syntax") for information about the full capabilities of Angular's template syntax.
|
||||
For more information about the full capabilities of Angular's
|
||||
template syntax, see [Template Syntax](guide/template-syntax "Template Syntax").
|
||||
|
||||
</div>
|
||||
|
||||
@ -153,50 +165,53 @@ Learn more: See the [Template Syntax guide](guide/template-syntax "Template Synt
|
||||
{@a components}
|
||||
## Components
|
||||
|
||||
*Components* define areas of responsibility in your UI that let you reuse these sets of UI functionality.
|
||||
You've already built one with the product list component.
|
||||
*Components* define areas of responsibility in the user interface, or UI,
|
||||
that let you reuse sets of UI functionality.
|
||||
You've already built one with the product list component.
|
||||
|
||||
A component is comprised of three things:
|
||||
* **A component class,** which handles data and functionality. In the previous section, the product data and the `share()` method were defined for you in the component class.
|
||||
* **An HTML template,** which determines what is presented to the user. In the previous section, you modified the product list's HTML template to display the name, description, and a "Share" button for each product.
|
||||
* **Component-specific styles** that define the look and feel. The product list does not define any styles.
|
||||
A component consists of three things:
|
||||
* **A component class** that handles data and functionality. In the previous section, the product data and the `share()` method in the component class handle data and functionality, respectively.
|
||||
* **An HTML template** that determines the UI. In the previous section, the product list's HTML template displays the name, description, and a "Share" button for each product.
|
||||
* **Component-specific styles** that define the look and feel.
|
||||
Though product list does not define any styles, this is where component CSS
|
||||
resides.
|
||||
|
||||
<!--
|
||||
<!--
|
||||
### Class definition
|
||||
|
||||
Let's take a quick look a the product list component's class definition:
|
||||
Let's take a quick look a the product list component's class definition:
|
||||
|
||||
1. In the `product-list` directory, open `product-list.component.ts`.
|
||||
1. In the `product-list` directory, open `product-list.component.ts`.
|
||||
|
||||
1. Notice the `@Component` decorator. This provides metadata about the component, including its templates, styles, and a selector.
|
||||
1. Notice the `@Component` decorator. This provides metadata about the component, including its templates, styles, and a selector.
|
||||
|
||||
* The `selector` is used to identify the component. The selector is the name you give the Angular component when it is rendered as an HTML element on the page. By convention, Angular component selectors begin with the prefix such as `app-`, followed by the component name.
|
||||
* The `selector` is used to identify the component. The selector is the name you give the Angular component when it is rendered as an HTML element on the page. By convention, Angular component selectors begin with the prefix such as `app-`, followed by the component name.
|
||||
|
||||
* The template and style filename also are provided here. By convention each of the component's parts is in a separate file, all in the same directory and with the same prefix.
|
||||
* The template and style filename also are provided here. By convention each of the component's parts is in a separate file, all in the same directory and with the same prefix.
|
||||
|
||||
1. The component definition also includes an exported class, which handles functionality for the component. This is where the product list data and `Share()` method are defined.
|
||||
1. The component definition also includes an exported class, which handles functionality for the component. This is where the product list data and `Share()` method are defined.
|
||||
|
||||
### Composition
|
||||
-->
|
||||
|
||||
An Angular application is composed of a tree of components, in which each Angular component has a specific purpose and responsibility.
|
||||
An Angular application comprises a tree of components, in which each Angular component has a specific purpose and responsibility.
|
||||
|
||||
Currently, our app has three components:
|
||||
Currently, the example app has three components:
|
||||
|
||||
<figure>
|
||||
<img src="generated/images/guide/start/app-components.png" alt="Online store with three components">
|
||||
</figure>
|
||||
|
||||
* `app-root` (orange box) is the application shell. This is the first component to load, and the parent of all other components. You can think of it as the base page.
|
||||
* `app-root` (orange box) is the application shell. This is the first component to load and the parent of all other components. You can think of it as the base page.
|
||||
* `app-top-bar` (blue background) is the store name and checkout button.
|
||||
* `app-product-list` (purple box) is the product list that you modified in the previous section.
|
||||
* `app-product-list` (purple box) is the product list that you modified in the previous section.
|
||||
|
||||
In the next section, you'll expand the app's capabilities by adding a new component for a product alert. You'll add it as a child of the product list component.
|
||||
The next section expands the app's capabilities by adding a new component—a product alert—as a child of the product list component.
|
||||
|
||||
|
||||
<div class="alert is-helpful">
|
||||
|
||||
Learn more: See [Introduction to Components](guide/architecture-components "Architecture > Introduction to Components") for more information about components and how they interact with templates.
|
||||
For more information about components and how they interact with templates, see [Introduction to Components](guide/architecture-components "Architecture > Introduction to Components").
|
||||
|
||||
</div>
|
||||
|
||||
@ -204,12 +219,12 @@ Learn more: See [Introduction to Components](guide/architecture-components "Arch
|
||||
{@a input}
|
||||
## Input
|
||||
|
||||
Currently, the product list displays the name and description of each product.
|
||||
You might have noticed that the product list component also defines a `products` property that contains imported data for each product. (See the `products` array in `products.ts`.)
|
||||
Currently, the product list displays the name and description of each product.
|
||||
The product list component also defines a `products` property that contains imported data for each product from the `products` array in `products.ts`.
|
||||
|
||||
We're going to create a new alert feature. The alert feature will take a product as an input. It will then check the product's price, and, if the price is greater than $700, it will display a "Notify Me" button that lets users sign up for notifications when the product goes on sale.
|
||||
The next step is to create a new alert feature that takes a product as an input. The alert checks the product's price, and, if the price is greater than $700, displays a "Notify Me" button that lets users sign up for notifications when the product goes on sale.
|
||||
|
||||
1. Create a new product alerts component.
|
||||
1. Create a new product alerts component.
|
||||
|
||||
1. Right click on the `app` folder and use the `Angular Generator` to generate a new component named `product-alerts`.
|
||||
|
||||
@ -217,50 +232,50 @@ We're going to create a new alert feature. The alert feature will take a product
|
||||
<img src="generated/images/guide/start/generate-component.png" alt="StackBlitz command to generate component">
|
||||
</figure>
|
||||
|
||||
The generator creates starter files for all three parts of the component:
|
||||
The generator creates starter files for all three parts of the component:
|
||||
* `product-alerts.component.ts`
|
||||
* `product-alerts.component.html`
|
||||
* `product-alerts.component.css`
|
||||
|
||||
1. Open `product-alerts.component.ts`.
|
||||
|
||||
<code-example header="src/app/product-alerts/product-alerts.component.ts" path="getting-started/src/app/product-alerts/product-alerts.component.1.ts" region="as-generated"></code-example>
|
||||
<code-example header="src/app/product-alerts/product-alerts.component.ts" path="getting-started/src/app/product-alerts/product-alerts.component.1.ts" region="as-generated"></code-example>
|
||||
|
||||
1. Notice the `@Component` decorator. This indicates that the following class is a component. It provides metadata about the component, including its templates, styles, and a selector.
|
||||
1. Notice the `@Component()` decorator. This indicates that the following class is a component. It provides metadata about the component, including its selector, templates, and styles.
|
||||
|
||||
* The `selector` is used to identify the component. The selector is the name you give the Angular component when it is rendered as an HTML element on the page. By convention, Angular component selectors begin with the prefix `app-`, followed by the component name.
|
||||
* The `selector` identifies the component. The selector is the name you give the Angular component when it is rendered as an HTML element on the page. By convention, Angular component selectors begin with the prefix `app-`, followed by the component name.
|
||||
|
||||
* The template and style filenames. These reference the other two files generated for you.
|
||||
* The template and style filenames reference the HTML and CSS files that StackBlitz generates.
|
||||
|
||||
1. The component definition also includes an exported class (`ProductAlertsComponent`), which handles functionality for the component.
|
||||
1. The component definition also exports the class, `ProductAlertsComponent`, which handles functionality for the component.
|
||||
|
||||
1. Set up the new product alerts component to receive a product as input:
|
||||
|
||||
1. Import `Input` from `@angular/core`.
|
||||
|
||||
<code-example path="getting-started/src/app/product-alerts/product-alerts.component.1.ts" region="imports"></code-example>
|
||||
<code-example path="getting-started/src/app/product-alerts/product-alerts.component.1.ts" region="imports" header="src/app/product-list/product-alerts.component.ts"></code-example>
|
||||
|
||||
1. In the `ProductAlertsComponent` class definition, define a property named `product` with an `@Input` decorator. The `@Input` decorator indicates that the property value will be passed in from the component's parent (in this case, the product list component).
|
||||
1. In the `ProductAlertsComponent` class definition, define a property named `product` with an `@Input()` decorator. The `@Input()` decorator indicates that the property value passes in from the component's parent, the product list component.
|
||||
|
||||
<code-example path="getting-started/src/app/product-alerts/product-alerts.component.1.ts" region="input-decorator"></code-example>
|
||||
<code-example path="getting-started/src/app/product-alerts/product-alerts.component.1.ts" region="input-decorator" header="src/app/product-list/product-alerts.component.ts"></code-example>
|
||||
|
||||
1. Define the view for the new product alert component.
|
||||
1. Define the view for the new product alert component.
|
||||
|
||||
Open the `product-alerts.component.html` template and replace the placeholder paragraph with a "Notify Me" button that appears if the product price is over $700.
|
||||
1. Open the `product-alerts.component.html` template and replace the placeholder paragraph with a "Notify Me" button that appears if the product price is over $700.
|
||||
|
||||
<code-example header="src/app/product-alerts/product-alerts.component.html" path="getting-started/src/app/product-alerts/product-alerts.component.1.html"></code-example>
|
||||
|
||||
1. Display the new product alert component as part of (a child of) the product list.
|
||||
1. Display the new product alert component as a child of the product list.
|
||||
|
||||
1. Open `product-list.component.html`.
|
||||
|
||||
1. To include the new component, use its selector (`app-product-alert`) as you would an HTML element.
|
||||
|
||||
1. Pass the current product as input to the component using property binding.
|
||||
|
||||
1. To include the new component, use its selector, `app-product-alert`, as you would an HTML element.
|
||||
|
||||
1. Pass the current product as input to the component using property binding.
|
||||
|
||||
<code-example header="src/app/product-list/product-list.component.html" path="getting-started/src/app/product-list/product-list.component.5.html" region="app-product-alerts"></code-example>
|
||||
|
||||
The new product alert component takes a product as input from the product list. With that input, it shows or hides the "Notify Me" button, based on the price of the product. The Phone XL price is over $700, so the "Notify Me" button appears on that product.
|
||||
The new product alert component takes a product as input from the product list. With that input, it shows or hides the "Notify Me" button, based on the price of the product. The Phone XL price is over $700, so the "Notify Me" button appears on that product.
|
||||
|
||||
<figure>
|
||||
<img src="generated/images/guide/start/product-alert-button.png" alt="Product alert button added to products over $700">
|
||||
@ -269,7 +284,7 @@ The new product alert component takes a product as input from the product list.
|
||||
|
||||
<div class="alert is-helpful">
|
||||
|
||||
Learn more: See [Component Interaction](guide/component-interaction "Components & Templates > Component Interaction") for more information about passing data from a parent to child component, intercepting and acting upon a value from the parent, and detecting and acting on changes to input property values.
|
||||
See [Component Interaction](guide/component-interaction "Components & Templates > Component Interaction") for more information about passing data from a parent to child component, intercepting and acting upon a value from the parent, and detecting and acting on changes to input property values.
|
||||
|
||||
</div>
|
||||
|
||||
@ -277,33 +292,36 @@ Learn more: See [Component Interaction](guide/component-interaction "Components
|
||||
{@a output}
|
||||
## Output
|
||||
|
||||
The "Notify Me" button doesn't do anything yet. In this section, you'll set up the product alert component so that it emits an event up to the product list component when the user clicks "Notify Me". You'll define the notification behavior in the product list component.
|
||||
To make the "Notify Me" button work, you need to configure two things:
|
||||
|
||||
- the product alert component to emit an event when the user clicks "Notify Me"
|
||||
- the product list component to act on that event
|
||||
|
||||
1. Open `product-alerts.component.ts`.
|
||||
|
||||
1. Import `Output` and `EventEmitter` from `@angular/core`:
|
||||
1. Import `Output` and `EventEmitter` from `@angular/core`:
|
||||
|
||||
<code-example header="src/app/product-alerts/product-alerts.component.ts" path="getting-started/src/app/product-alerts/product-alerts.component.ts" region="imports"></code-example>
|
||||
|
||||
1. In the component class, define a property named `notify` with an `@Output` decorator and an instance of event emitter. This makes it possible for the product alert component to emit an event when the value of the notify property changes.
|
||||
1. In the component class, define a property named `notify` with an `@Output()` decorator and an instance of `EventEmitter()`. This allows the product alert component to emit an event when the value of the notify property changes.
|
||||
|
||||
<code-example path="getting-started/src/app/product-alerts/product-alerts.component.ts" region="input-output"></code-example>
|
||||
<code-example path="getting-started/src/app/product-alerts/product-alerts.component.ts" header="src/app/product-alerts/product-alerts.component.ts" region="input-output"></code-example>
|
||||
|
||||
1. In the product alert template (`product-alerts.component.html`), update the "Notify Me" button with an event binding to call the `notify.emit()` method.
|
||||
1. In the product alert template, `product-alerts.component.html`, update the "Notify Me" button with an event binding to call the `notify.emit()` method.
|
||||
|
||||
<code-example header="src/app/product-alerts/product-alerts.component.html" path="getting-started/src/app/product-alerts/product-alerts.component.html"></code-example>
|
||||
|
||||
1. Next, define the behavior that should happen when the button is clicked. Recall that it's the parent (product list component)—not the product alerts component—that's going to take the action. In the `product-list.component.ts` file, define an `onNotify()` method, similar to the `share()` method:
|
||||
1. Next, define the behavior that should happen when the user clicks the button. Recall that it's the parent, product list component—not the product alerts component—that's acts when the child raises the event. In `product-list.component.ts`, define an `onNotify()` method, similar to the `share()` method:
|
||||
|
||||
<code-example header="src/app/product-list/product-list.component.ts" path="getting-started/src/app/product-list/product-list.component.ts" region="on-notify"></code-example>
|
||||
|
||||
1. Finally, update the product list component to receive output from the product alerts component.
|
||||
1. Finally, update the product list component to receive output from the product alerts component.
|
||||
|
||||
In `product-list.component.html`, bind the `app-product-alerts` component (which is what displays the "Notify Me" button) to the `onNotify()` method of the product list component.
|
||||
In `product-list.component.html`, bind the `app-product-alerts` component (which is what displays the "Notify Me" button) to the `onNotify()` method of the product list component.
|
||||
|
||||
<code-example header="src/app/product-list/product-list.component.html" path="getting-started/src/app/product-list/product-list.component.6.html" region="on-notify"></code-example>
|
||||
|
||||
1. Try out the "Notify Me" button:
|
||||
1. Try the "Notify Me" button:
|
||||
|
||||
<figure>
|
||||
<img src="generated/images/guide/start/product-alert-notification.png" alt="Product alert notification confirmation dialog">
|
||||
@ -312,7 +330,7 @@ The "Notify Me" button doesn't do anything yet. In this section, you'll set up t
|
||||
|
||||
<div class="alert is-helpful">
|
||||
|
||||
Learn more: See [Component Interaction](guide/component-interaction "Components & Templates > Component Interaction") for more information about listening for events from child components, reading child properties or invoking child methods, and using a service for bi-directional communication within the family.
|
||||
See [Component Interaction](guide/component-interaction "Components & Templates > Component Interaction") for more information about listening for events from child components, reading child properties or invoking child methods, and using a service for bi-directional communication between components.
|
||||
|
||||
</div>
|
||||
|
||||
@ -322,11 +340,11 @@ Learn more: See [Component Interaction](guide/component-interaction "Components
|
||||
|
||||
Congratulations! You've completed your first Angular app!
|
||||
|
||||
You have a basic online store catalog, with a product list, "Share" button, and "Notify Me" button.
|
||||
You've learned about the foundation of Angular: components and template syntax.
|
||||
You've also learned how the component class and template interact, and how components communicate with each other.
|
||||
You have a basic online store catalog with a product list, "Share" button, and "Notify Me" button.
|
||||
You've learned about the foundation of Angular: components and template syntax.
|
||||
You've also learned how the component class and template interact, and how components communicate with each other.
|
||||
|
||||
To continue exploring Angular, choose either of the following options:
|
||||
* [Continue to the "Routing" section](start/routing "Getting Started: Routing") to create a product details page that can be accessed by clicking a product name and that has its own URL pattern.
|
||||
* [Continue to the "Routing" section](start/routing "Getting Started: Routing") to create a product details page that can be accessed by clicking a product name and that has its own URL pattern.
|
||||
* [Skip ahead to the "Deployment" section](start/deployment "Getting Started: Deployment") to move to local development, or deploy your app to Firebase or your own server.
|
||||
|
||||
|
@ -90,8 +90,16 @@ The product details component handles the display of each product. The Angular R
|
||||
<code-example path="getting-started/src/app/product-details/product-details.component.1.ts" region="get-product">
|
||||
</code-example>
|
||||
|
||||
Angular calls `ngOnInit()` shortly after creating a component.
|
||||
|
||||
The route parameters correspond to the path variables defined in the route. The `productId` is provided from
|
||||
the URL that was matched to the route. You use the `productId` to display the details for each unique product.
|
||||
the URL that was matched to the route. You use the `productId` to display the details for each unique product.
|
||||
|
||||
<div class="alert is-helpful">
|
||||
|
||||
For more information on `ngOnInit()`, see [Lifecycle hooks](guide/lifecycle-hooks).
|
||||
|
||||
</div>
|
||||
|
||||
1. Update the template to display product details information inside an `*ngIf`.
|
||||
|
||||
|
@ -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.
|
||||
|
||||
|
||||
|
@ -19,11 +19,11 @@
|
||||
"build-local": "yarn ~~build",
|
||||
"prebuild-local-ci": "yarn setup-local --no-build-packages",
|
||||
"build-local-ci": "yarn ~~build --progress=false",
|
||||
"prebuild-with-ivy": "yarn setup-local && node scripts/switch-to-ivy",
|
||||
"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 ea386e045",
|
||||
"prebuild-local-with-ivy": "yarn setup-local && node scripts/switch-to-ivy",
|
||||
"build-local-with-ivy": "yarn ~~build",
|
||||
"prebuild-local-with-ivy-ci": "yarn setup-local --no-build-packages && node scripts/switch-to-ivy",
|
||||
"build-local-with-ivy-ci": "yarn ~~build --progress=false",
|
||||
"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": {
|
||||
@ -115,7 +115,7 @@
|
||||
"chalk": "^2.1.0",
|
||||
"chrome-launcher": "^0.10.7",
|
||||
"cjson": "^0.5.0",
|
||||
"codelyzer": "^5.0.0",
|
||||
"codelyzer": "^5.1.1",
|
||||
"cross-spawn": "^5.1.0",
|
||||
"css-selector-parser": "^1.3.0",
|
||||
"dgeni": "^0.4.11",
|
||||
|
@ -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'));
|
||||
}
|
||||
}
|
@ -7,10 +7,11 @@ import { LocationService } from 'app/shared/location.service';
|
||||
selector: `aio-contributor-list`,
|
||||
template: `
|
||||
<div class="flex-center group-buttons">
|
||||
<a *ngFor="let name of groupNames"
|
||||
[class.selected]="name == selectedGroup.name"
|
||||
class="button mat-button filter-button"
|
||||
(click)="selectGroup(name)">{{name}}</a>
|
||||
<a *ngFor="let name of groupNames"
|
||||
[class.selected]="name == selectedGroup.name"
|
||||
class="button mat-button filter-button"
|
||||
(click)="selectGroup(name)"
|
||||
(keyup.enter)="selectGroup(name)">{{name}}</a>
|
||||
</div>
|
||||
<section *ngIf="selectedGroup" class="grid-fluid">
|
||||
<div class="contributor-group">
|
||||
|
@ -8,7 +8,7 @@ import { CONTENT_URL_PREFIX } from 'app/documents/document.service';
|
||||
template: `
|
||||
<div [ngClass]="{ 'flipped': person.isFlipped }" class="contributor-card">
|
||||
|
||||
<div class="card-front" (click)="flipCard(person)">
|
||||
<div class="card-front" (click)="flipCard(person)" (keyup.enter)="flipCard(person)">
|
||||
<h3>{{person.name}}</h3>
|
||||
|
||||
<div class="contributor-image" [style.background-image]="'url('+pictureBase+(person.picture || noPicture)+')'">
|
||||
@ -28,7 +28,7 @@ import { CONTENT_URL_PREFIX } from 'app/documents/document.service';
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card-back" *ngIf="person.isFlipped" (click)="flipCard(person)">
|
||||
<div class="card-back" *ngIf="person.isFlipped" (click)="flipCard(person)" (keyup.enter)="flipCard(person)">
|
||||
<h3>{{person.name}}</h3>
|
||||
<p class="contributor-bio">{{person.bio}}</p>
|
||||
</div>
|
||||
|
@ -4,6 +4,7 @@ import { PlatformLocation } from '@angular/common';
|
||||
import { Category } from './resource.model';
|
||||
import { ResourceService } from './resource.service';
|
||||
|
||||
/* tslint:disable:template-accessibility-elements-content */
|
||||
@Component({
|
||||
selector: 'aio-resource-list',
|
||||
templateUrl: 'resource-list.component.html'
|
||||
|
@ -1,4 +1,4 @@
|
||||
<span class="content" (click)="contentClick()">
|
||||
<span class="content" (click)="contentClick()" (keyup.enter)="contentClick()">
|
||||
<ng-content></ng-content>
|
||||
</span>
|
||||
|
||||
|
@ -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,36 +56,43 @@ 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).
|
||||
querySelectorAll(div, '.github-links, .header-link').forEach(removeNode);
|
||||
|
||||
// Remove any remaining `a` elements (but keep their content).
|
||||
querySelectorAll(div, '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[] {
|
||||
const headings = docElement.querySelectorAll('h1,h2,h3');
|
||||
// const headings = querySelectorAll(docElement, 'h1,h2,h3');
|
||||
const headings = querySelectorAll<HTMLHeadingElement>(docElement, 'h1,h2,h3');
|
||||
const skipNoTocHeadings = (heading: HTMLHeadingElement) => !/(?:no-toc|notoc)/i.test(heading.className);
|
||||
|
||||
return Array.prototype.filter.call(headings, skipNoTocHeadings);
|
||||
return headings.filter(skipNoTocHeadings);
|
||||
}
|
||||
|
||||
private resetScrollSpyInfo() {
|
||||
@ -115,3 +126,21 @@ export class TocService {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Helpers
|
||||
function querySelectorAll<K extends keyof HTMLElementTagNameMap>(parent: Element, selector: K): HTMLElementTagNameMap[K][];
|
||||
function querySelectorAll<K extends keyof SVGElementTagNameMap>(parent: Element, selector: K): SVGElementTagNameMap[K][];
|
||||
function querySelectorAll<E extends Element = Element>(parent: Element, selector: string): E[];
|
||||
function querySelectorAll(parent: Element, selector: string) {
|
||||
// Wrap the `NodeList` as a regular `Array` to have access to array methods.
|
||||
// NOTE: IE11 does not even support some methods of `NodeList`, such as
|
||||
// [NodeList#forEach()](https://developer.mozilla.org/en-US/docs/Web/API/NodeList/forEach).
|
||||
return Array.from(parent.querySelectorAll(selector));
|
||||
}
|
||||
|
||||
function removeNode(node: Node): void {
|
||||
if (node.parentNode !== null) {
|
||||
// We cannot use `Node.remove()` because of IE11.
|
||||
node.parentNode.removeChild(node);
|
||||
}
|
||||
}
|
||||
|
@ -49,8 +49,8 @@ aio-shell.page-resources mat-toolbar.mat-toolbar {
|
||||
aio-shell.folder-api mat-toolbar.mat-toolbar,
|
||||
aio-shell.folder-cli mat-toolbar.mat-toolbar,
|
||||
aio-shell.folder-docs mat-toolbar.mat-toolbar,
|
||||
aio-shell.folder-getting-started mat-toolbar.mat-toolbar,
|
||||
aio-shell.folder-guide mat-toolbar.mat-toolbar,
|
||||
aio-shell.folder-start mat-toolbar.mat-toolbar,
|
||||
aio-shell.folder-tutorial mat-toolbar.mat-toolbar {
|
||||
@media (min-width: 992px) {
|
||||
.hamburger.mat-button {
|
||||
|
@ -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);
|
||||
}));
|
||||
}
|
||||
});
|
||||
|
@ -21,16 +21,6 @@
|
||||
/** IE10 and IE11 requires the following for NgClass support on SVG elements */
|
||||
// import 'classlist.js'; // Run `npm install --save classlist.js`.
|
||||
|
||||
/** IE10 and IE11 requires the following for the Reflect API. */
|
||||
/**
|
||||
* DO NOT REMOVE
|
||||
* By default, Reflect polyfills are auto-included by the CLI and
|
||||
* are required for JIT compilation. StackBlitz examples are
|
||||
* compiled using JIT.
|
||||
*/
|
||||
import 'core-js/es6/reflect';
|
||||
import 'core-js/es7/reflect';
|
||||
|
||||
/**
|
||||
* Web Animations `@angular/platform-browser/animations`
|
||||
* Only required if AnimationBuilder is used within the application and using IE/Edge or Safari.
|
||||
@ -65,8 +55,7 @@ import 'core-js/es7/reflect';
|
||||
/***************************************************************************************************
|
||||
* Zone JS is required by default for Angular itself.
|
||||
*/
|
||||
import 'zone.js/dist/zone'; // Included with Angular CLI.
|
||||
import 'zone.js/dist/zone-patch-canvas';
|
||||
import 'zone.js/dist/zone'; // Included with Angular CLI.
|
||||
|
||||
/***************************************************************************************************
|
||||
* APPLICATION IMPORTS
|
||||
|
@ -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": "",
|
||||
@ -33,7 +33,7 @@
|
||||
"@nguniversal/express-engine": "^8.0.0-rc.1",
|
||||
"@nguniversal/module-map-ngfactory-loader": "^8.0.0-rc.1",
|
||||
"angular": "1.7.8",
|
||||
"angular-in-memory-web-api": "github:brandonroberts/in-memory-web-api-bazel#50a34d8",
|
||||
"angular-in-memory-web-api": "^0.9.0",
|
||||
"angular-route": "1.7.8",
|
||||
"core-js": "^2.5.4",
|
||||
"express": "^4.14.1",
|
||||
@ -69,7 +69,7 @@
|
||||
"karma-chrome-launcher": "~2.2.0",
|
||||
"karma-coverage-istanbul-reporter": "~2.0.1",
|
||||
"karma-jasmine": "~2.0.1",
|
||||
"karma-jasmine-html-reporter": "^0.2.2",
|
||||
"karma-jasmine-html-reporter": "^1.4.2",
|
||||
"lite-server": "^2.2.2",
|
||||
"lodash": "^4.16.2",
|
||||
"protractor": "~5.4.0",
|
||||
|
@ -795,11 +795,10 @@ amdefine@>=0.0.4:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/amdefine/-/amdefine-1.0.1.tgz#4a5282ac164729e93619bcfd3ad151f817ce91f5"
|
||||
|
||||
"angular-in-memory-web-api@github:brandonroberts/in-memory-web-api-bazel#50a34d8":
|
||||
version "0.8.0"
|
||||
resolved "https://codeload.github.com/brandonroberts/in-memory-web-api-bazel/tar.gz/50a34d84b627ec88816242dec77603d6dcb9c880"
|
||||
dependencies:
|
||||
tslib "^1.9.0"
|
||||
angular-in-memory-web-api@^0.9.0:
|
||||
version "0.9.0"
|
||||
resolved "https://registry.yarnpkg.com/angular-in-memory-web-api/-/angular-in-memory-web-api-0.9.0.tgz#6c98d9494fadc6b98f54e68376a1998ccfff04bc"
|
||||
integrity sha512-//PiJ5qb1+Yf/N7270ioQqR2laf4/Irjavg+M+WEn8y4At9LUoYgbQ5HVwvM5xUTlVlL0XkbJRLxREcGGNdIEw==
|
||||
|
||||
angular-route@1.7.8:
|
||||
version "1.7.8"
|
||||
@ -4430,15 +4429,10 @@ karma-coverage-istanbul-reporter@~2.0.1:
|
||||
istanbul-api "^2.0.5"
|
||||
minimatch "^3.0.4"
|
||||
|
||||
karma-jasmine-html-reporter@^0.2.2:
|
||||
version "0.2.2"
|
||||
resolved "https://registry.yarnpkg.com/karma-jasmine-html-reporter/-/karma-jasmine-html-reporter-0.2.2.tgz#48a8e5ef18807617ee2b5e33c1194c35b439524c"
|
||||
dependencies:
|
||||
karma-jasmine "^1.0.2"
|
||||
|
||||
karma-jasmine@^1.0.2:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/karma-jasmine/-/karma-jasmine-1.1.0.tgz#22e4c06bf9a182e5294d1f705e3733811b810acf"
|
||||
karma-jasmine-html-reporter@^1.4.2:
|
||||
version "1.4.2"
|
||||
resolved "https://registry.yarnpkg.com/karma-jasmine-html-reporter/-/karma-jasmine-html-reporter-1.4.2.tgz#16d100fd701271192d27fd28ddc90b710ad36fff"
|
||||
integrity sha512-7g0gPj8+9JepCNJR9WjDyQ2RkZ375jpdurYQyAYv8PorUCadepl8vrD6LmMqOGcM17cnrynBawQYZHaumgDjBw==
|
||||
|
||||
karma-jasmine@~2.0.1:
|
||||
version "2.0.1"
|
||||
|
@ -29,6 +29,7 @@ module.exports =
|
||||
.processor(require('./processors/processClassLikeMembers'))
|
||||
.processor(require('./processors/markBarredODocsAsPrivate'))
|
||||
.processor(require('./processors/filterPrivateDocs'))
|
||||
.processor(require('./processors/filterMembers'))
|
||||
.processor(require('./processors/computeSearchTitle'))
|
||||
.processor(require('./processors/simplifyMemberAnchors'))
|
||||
.processor(require('./processors/computeStability'))
|
||||
@ -176,6 +177,12 @@ module.exports =
|
||||
filterContainedDocs.docTypes = API_CONTAINED_DOC_TYPES;
|
||||
})
|
||||
|
||||
.config(function(filterMembers) {
|
||||
filterMembers.notAllowedPatterns.push(
|
||||
/^ng[A-Z].*Def$/
|
||||
);
|
||||
})
|
||||
|
||||
|
||||
.config(function(computePathsProcessor, EXPORT_DOC_TYPES, generateApiListDoc) {
|
||||
|
||||
|
21
aio/tools/transforms/angular-api-package/processors/filterMembers.js
vendored
Normal file
21
aio/tools/transforms/angular-api-package/processors/filterMembers.js
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
/**
|
||||
* Filter out members (i.e. static and instance properties and methods) that match specific
|
||||
* patterns. Patterns can be added (as `RegExp`s) to the `notAllowedPatterns` array.
|
||||
*
|
||||
* (By default, no members are excluded.)
|
||||
*/
|
||||
module.exports = function filterMembers() {
|
||||
return {
|
||||
$runAfter: ['processing-docs'],
|
||||
$runBefore: ['docs-processed'],
|
||||
notAllowedPatterns: [],
|
||||
$process(docs) {
|
||||
const isAllowed = ({name}) => !this.notAllowedPatterns.some(re => re.test(name));
|
||||
|
||||
docs.forEach(doc => {
|
||||
if (doc.statics) doc.statics = doc.statics.filter(isAllowed);
|
||||
if (doc.members) doc.members = doc.members.filter(isAllowed);
|
||||
});
|
||||
},
|
||||
};
|
||||
};
|
@ -0,0 +1,102 @@
|
||||
const processorFactory = require('./filterMembers');
|
||||
const testPackage = require('../../helpers/test-package');
|
||||
const Dgeni = require('dgeni');
|
||||
|
||||
describe('filterMembers processor', () => {
|
||||
|
||||
it('should be available on the injector', () => {
|
||||
const dgeni = new Dgeni([testPackage('angular-api-package')]);
|
||||
const injector = dgeni.configureInjector();
|
||||
const processor = injector.get('filterMembers');
|
||||
expect(processor.$process).toBeDefined();
|
||||
expect(processor.$runAfter).toEqual(['processing-docs']);
|
||||
expect(processor.$runBefore).toEqual(['docs-processed']);
|
||||
});
|
||||
|
||||
it('should remove members that match one of the not allowed patterns', () => {
|
||||
const processor = processorFactory();
|
||||
processor.notAllowedPatterns = [/^foo/, /bar$/];
|
||||
const docs = [
|
||||
// Doc without members.
|
||||
{ },
|
||||
|
||||
// Doc with static members only.
|
||||
{
|
||||
statics: [
|
||||
{ name: 'fooStatic' }, // Will be removed.
|
||||
{ name: 'FOOStatic' },
|
||||
{ name: 'barStatic' },
|
||||
{ name: 'statiCbar' }, // Will be removed.
|
||||
],
|
||||
},
|
||||
|
||||
// Doc with instance members only.
|
||||
{
|
||||
members: [
|
||||
{ name: 'fooInstance' }, // Will be removed.
|
||||
{ name: 'FOOInstance' },
|
||||
{ name: 'barInstance' },
|
||||
{ name: 'instancEbar' }, // Will be removed.
|
||||
],
|
||||
},
|
||||
|
||||
// Doc with both static and instance members.
|
||||
{
|
||||
statics: [
|
||||
{ name: 'fooStatic' }, // Will be removed.
|
||||
{ name: 'FOOStatic' },
|
||||
{ name: 'barStatic' },
|
||||
{ name: 'statiCbar' }, // Will be removed.
|
||||
],
|
||||
members: [
|
||||
{ name: 'fooInstance' }, // Will be removed.
|
||||
{ name: 'FOOInstance' },
|
||||
{ name: 'barInstance' },
|
||||
{ name: 'instancEbar' }, // Will be removed.
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
processor.$process(docs);
|
||||
|
||||
expect(docs).toEqual([
|
||||
{ },
|
||||
{
|
||||
statics: [ { name: 'FOOStatic' }, { name: 'barStatic' } ],
|
||||
},
|
||||
{
|
||||
members: [ { name: 'FOOInstance' }, { name: 'barInstance' } ],
|
||||
},
|
||||
{
|
||||
statics: [ { name: 'FOOStatic' }, { name: 'barStatic' } ],
|
||||
members: [ { name: 'FOOInstance' }, { name: 'barInstance' } ],
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('should remove no members by default', () => {
|
||||
const processor = processorFactory();
|
||||
const expectedDocs = [
|
||||
{
|
||||
statics: [
|
||||
{ name: '' },
|
||||
{ name: 'foo' },
|
||||
{ name: '__bar' },
|
||||
{ name: 'ngBazDef' },
|
||||
],
|
||||
members: [
|
||||
{ name: '' },
|
||||
{ name: 'foo' },
|
||||
{ name: '__bar' },
|
||||
{ name: 'ngBazDef' },
|
||||
],
|
||||
},
|
||||
];
|
||||
const actualDocs = JSON.parse(JSON.stringify(expectedDocs));
|
||||
|
||||
processor.$process(actualDocs);
|
||||
|
||||
expect(processor.notAllowedPatterns).toEqual([]);
|
||||
expect(actualDocs).toEqual(expectedDocs);
|
||||
});
|
||||
});
|
@ -96,6 +96,16 @@
|
||||
"ban-keywords",
|
||||
"check-format",
|
||||
"require-const-for-all-caps"
|
||||
]
|
||||
],
|
||||
"template-accessibility-alt-text": true,
|
||||
"template-accessibility-elements-content": true,
|
||||
"template-accessibility-label-for": true,
|
||||
"template-accessibility-tabindex-no-positive": true,
|
||||
"template-accessibility-table-scope": true,
|
||||
"template-accessibility-valid-aria": true,
|
||||
"template-click-events-have-key-events": true,
|
||||
"template-mouse-events-have-key-events": true,
|
||||
"template-no-autofocus": true,
|
||||
"template-no-distracting-elements": true
|
||||
}
|
||||
}
|
||||
|
@ -938,10 +938,10 @@ anymatch@^3.0.1:
|
||||
normalize-path "^3.0.0"
|
||||
picomatch "^2.0.4"
|
||||
|
||||
app-root-path@^2.1.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/app-root-path/-/app-root-path-2.1.0.tgz#98bf6599327ecea199309866e8140368fd2e646a"
|
||||
integrity sha1-mL9lmTJ+zqGZMJhm6BQDaP0uZGo=
|
||||
app-root-path@^2.2.1:
|
||||
version "2.2.1"
|
||||
resolved "https://registry.yarnpkg.com/app-root-path/-/app-root-path-2.2.1.tgz#d0df4a682ee408273583d43f6f79e9892624bc9a"
|
||||
integrity sha512-91IFKeKk7FjfmezPKkwtaRvSpnUc4gDwPAjA1YZ9Gn0q0PPeW+vbeUsZuyDwjI7+QTHhcLen2v25fi/AmhvbJA==
|
||||
|
||||
append-transform@^1.0.0:
|
||||
version "1.0.0"
|
||||
@ -2179,12 +2179,12 @@ code-point-at@^1.0.0:
|
||||
resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77"
|
||||
integrity sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=
|
||||
|
||||
codelyzer@^5.0.0:
|
||||
version "5.0.0"
|
||||
resolved "https://registry.yarnpkg.com/codelyzer/-/codelyzer-5.0.0.tgz#e4032efb23a7c5d4bcfe7321fc1789490c679837"
|
||||
integrity sha512-Bif70XYt8NFf/Q9GPTxmC86OsBRfQZq1dBjdruJ5kZhJ8/jKhJL6MvCLKnYtSOG6Rhiv/44DU0cHk6GYthjy8Q==
|
||||
codelyzer@^5.1.1:
|
||||
version "5.1.1"
|
||||
resolved "https://registry.yarnpkg.com/codelyzer/-/codelyzer-5.1.1.tgz#a599fa8c2a5847f553a792b934e493d1506a4a62"
|
||||
integrity sha512-t8ZLSZBUjVFOJVk4jASLgmTdKWK/0ZsQCnPXy6PXw1LWOOormQOVnyy4OYoiZ6rAWTrz60Obx+zA2t8xY53QzQ==
|
||||
dependencies:
|
||||
app-root-path "^2.1.0"
|
||||
app-root-path "^2.2.1"
|
||||
aria-query "^3.0.0"
|
||||
axobject-query "^2.0.2"
|
||||
css-selector-tokenizer "^0.7.1"
|
||||
|
@ -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.4",
|
||||
"version": "8.2.10",
|
||||
"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",
|
||||
|
@ -707,7 +707,7 @@ export function group(
|
||||
*
|
||||
* ```typescript
|
||||
* sequence([
|
||||
* style({ opacity: 0 })),
|
||||
* style({ opacity: 0 }),
|
||||
* animate("1s", style({ opacity: 1 }))
|
||||
* ])
|
||||
* ```
|
||||
|
@ -372,7 +372,8 @@ def ngc_compile_action(
|
||||
node_opts,
|
||||
locale = None,
|
||||
i18n_args = [],
|
||||
dts_bundles_out = None):
|
||||
dts_bundles_out = None,
|
||||
compile_mode = "prodmode"):
|
||||
"""Helper function to create the ngc action.
|
||||
|
||||
This is exposed for google3 to wire up i18n replay rules, and is not intended
|
||||
@ -397,13 +398,13 @@ def ngc_compile_action(
|
||||
is_legacy_ngc = _is_legacy_ngc(ctx)
|
||||
|
||||
mnemonic = "AngularTemplateCompile"
|
||||
progress_message = "Compiling Angular templates (%s) %s" % (_compiler_name(ctx), label)
|
||||
progress_message = "Compiling Angular templates (%s - %s) %s" % (_compiler_name(ctx), compile_mode, label)
|
||||
|
||||
if locale:
|
||||
mnemonic = "AngularI18NMerging"
|
||||
supports_workers = "0"
|
||||
progress_message = ("Recompiling Angular templates (ngc) %s for locale %s" %
|
||||
(label, locale))
|
||||
progress_message = ("Recompiling Angular templates (ngc - %s) %s for locale %s" %
|
||||
(compile_mode, label, locale))
|
||||
else:
|
||||
supports_workers = str(int(ctx.attr._supports_workers))
|
||||
|
||||
@ -463,7 +464,7 @@ def ngc_compile_action(
|
||||
dts_entry_points.append(_R3_SYMBOLS_DTS_FILE)
|
||||
|
||||
ctx.actions.run(
|
||||
progress_message = "Bundling DTS %s" % str(ctx.label),
|
||||
progress_message = "Bundling DTS (%s) %s" % (compile_mode, str(ctx.label)),
|
||||
mnemonic = "APIExtractor",
|
||||
executable = ctx.executable.api_extractor,
|
||||
inputs = filter_inputs,
|
||||
@ -495,7 +496,15 @@ def _filter_ts_inputs(all_inputs):
|
||||
if f.path.endswith(".js") or f.path.endswith(".ts") or f.path.endswith(".json")
|
||||
]
|
||||
|
||||
def _compile_action(ctx, inputs, outputs, dts_bundles_out, messages_out, tsconfig_file, node_opts):
|
||||
def _compile_action(
|
||||
ctx,
|
||||
inputs,
|
||||
outputs,
|
||||
dts_bundles_out,
|
||||
messages_out,
|
||||
tsconfig_file,
|
||||
node_opts,
|
||||
compile_mode):
|
||||
# Give the Angular compiler all the user-listed assets
|
||||
file_inputs = list(ctx.files.assets)
|
||||
|
||||
@ -533,16 +542,16 @@ def _compile_action(ctx, inputs, outputs, dts_bundles_out, messages_out, tsconfi
|
||||
],
|
||||
)
|
||||
|
||||
return ngc_compile_action(ctx, ctx.label, action_inputs, outputs, messages_out, tsconfig_file, node_opts, None, [], dts_bundles_out)
|
||||
return ngc_compile_action(ctx, ctx.label, action_inputs, outputs, messages_out, tsconfig_file, node_opts, None, [], dts_bundles_out, compile_mode)
|
||||
|
||||
def _prodmode_compile_action(ctx, inputs, outputs, tsconfig_file, node_opts):
|
||||
outs = _expected_outs(ctx)
|
||||
return _compile_action(ctx, inputs, outputs + outs.closure_js, None, outs.i18n_messages, tsconfig_file, node_opts)
|
||||
return _compile_action(ctx, inputs, outputs + outs.closure_js, None, outs.i18n_messages, tsconfig_file, node_opts, "prodmode")
|
||||
|
||||
def _devmode_compile_action(ctx, inputs, outputs, tsconfig_file, node_opts):
|
||||
outs = _expected_outs(ctx)
|
||||
compile_action_outputs = outputs + outs.devmode_js + outs.declarations + outs.summaries + outs.metadata
|
||||
_compile_action(ctx, inputs, compile_action_outputs, outs.dts_bundles, None, tsconfig_file, node_opts)
|
||||
_compile_action(ctx, inputs, compile_action_outputs, outs.dts_bundles, None, tsconfig_file, node_opts, "devmode")
|
||||
|
||||
def _ts_expected_outs(ctx, label, srcs_files = []):
|
||||
# rules_typescript expects a function with two or more arguments, but our
|
||||
|
@ -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');
|
||||
|
@ -20,6 +20,12 @@ import {HttpEvent, HttpResponse} from './response';
|
||||
/**
|
||||
* Constructs an instance of `HttpRequestOptions<T>` from a source `HttpMethodOptions` and
|
||||
* the given `body`. This function clones the object and adds the body.
|
||||
*
|
||||
* Note that the `responseType` *options* value is a String that identifies the
|
||||
* single data type of the response.
|
||||
* A single overload version of the method handles each response type.
|
||||
* The value of `responseType` cannot be a union, as the combined signature could imply.
|
||||
*
|
||||
*/
|
||||
function addBody<T>(
|
||||
options: {
|
||||
@ -46,10 +52,15 @@ export type HttpObserve = 'body' | 'events' | 'response';
|
||||
|
||||
/**
|
||||
* Performs HTTP requests.
|
||||
*
|
||||
* This service is available as an injectable class, with methods to perform HTTP requests.
|
||||
* Each request method has multiple signatures, and the return type varies based on
|
||||
* the signature that is called (mainly the values of `observe` and `responseType`).
|
||||
*
|
||||
* Note that the `responseType` *options* value is a String that identifies the
|
||||
* single data type of the response.
|
||||
* A single overload version of the method handles each response type.
|
||||
* The value of `responseType` cannot be a union, as the combined signature could imply.
|
||||
|
||||
*
|
||||
* @usageNotes
|
||||
* Sample HTTP requests for the [Tour of Heroes](/tutorial/toh-pt0) application.
|
||||
|
@ -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;
|
||||
|
@ -198,7 +198,8 @@ export class AngularJSUrlCodec implements UrlCodec {
|
||||
// https://github.com/angular/angular.js/blob/864c7f0/src/ng/urlUtils.js#L60
|
||||
parse(url: string, base?: string) {
|
||||
try {
|
||||
const parsed = new URL(url, base);
|
||||
// Safari 12 throws an error when the URL constructor is called with an undefined base.
|
||||
const parsed = !base ? new URL(url) : new URL(url, base);
|
||||
return {
|
||||
href: parsed.href,
|
||||
protocol: parsed.protocol ? parsed.protocol.replace(/:$/, '') : '',
|
||||
@ -335,4 +336,4 @@ function encodeUriQuery(val: string, pctEncodeSpaces: boolean = false) {
|
||||
.replace(/%2C/gi, ',')
|
||||
.replace(/%3B/gi, ';')
|
||||
.replace(/%20/g, (pctEncodeSpaces ? '%20' : '+'));
|
||||
}
|
||||
}
|
||||
|
79
packages/common/upgrade/test/params.spec.ts
Normal file
79
packages/common/upgrade/test/params.spec.ts
Normal file
@ -0,0 +1,79 @@
|
||||
/**
|
||||
* @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 {AngularJSUrlCodec} from '../src/params';
|
||||
|
||||
describe('AngularJSUrlCodec', () => {
|
||||
const codec = new AngularJSUrlCodec();
|
||||
|
||||
describe('parse', () => {
|
||||
it('should parse a complex URL', () => {
|
||||
const result = codec.parse('http://server.com:1234/foo?bar=true#heading');
|
||||
expect(result.href).toBe('http://server.com:1234/foo?bar=true#heading');
|
||||
expect(result.protocol).toBe('http');
|
||||
expect(result.host).toBe('server.com:1234');
|
||||
expect(result.search).toBe('bar=true');
|
||||
expect(result.hash).toBe('heading');
|
||||
expect(result.hostname).toBe('server.com');
|
||||
expect(result.port).toBe('1234');
|
||||
expect(result.pathname).toBe('/foo');
|
||||
});
|
||||
|
||||
it('should parse a URL without search', () => {
|
||||
const result = codec.parse('http://server.com:1234/foo#heading');
|
||||
expect(result.href).toBe('http://server.com:1234/foo#heading');
|
||||
expect(result.search).toBe('');
|
||||
expect(result.hash).toBe('heading');
|
||||
expect(result.pathname).toBe('/foo');
|
||||
});
|
||||
|
||||
it('should parse a URL without hash', () => {
|
||||
const result = codec.parse('http://server.com:1234/foo?bar=true');
|
||||
expect(result.href).toBe('http://server.com:1234/foo?bar=true');
|
||||
expect(result.search).toBe('bar=true');
|
||||
expect(result.hash).toBe('');
|
||||
expect(result.pathname).toBe('/foo');
|
||||
});
|
||||
|
||||
it('should parse a basic URL', () => {
|
||||
const result = codec.parse('http://server.com');
|
||||
expect(result.href).toBe('http://server.com/');
|
||||
expect(result.protocol).toBe('http');
|
||||
expect(result.host).toBe('server.com');
|
||||
expect(result.search).toBe('');
|
||||
expect(result.hash).toBe('');
|
||||
expect(result.hostname).toBe('server.com');
|
||||
expect(result.port).toBe('');
|
||||
expect(result.pathname).toBe('/');
|
||||
});
|
||||
|
||||
it('should apply a base', () => {
|
||||
const withoutSlash = codec.parse('foo/bar', 'http://abc.xyz');
|
||||
expect(withoutSlash.href).toBe('http://abc.xyz/foo/bar');
|
||||
const withSlash = codec.parse('/foo/bar', 'http://abc.xyz/');
|
||||
expect(withSlash.href).toBe('http://abc.xyz/foo/bar');
|
||||
});
|
||||
|
||||
it('should ignore an empty base', () => {
|
||||
const result = codec.parse('http://abc.xyz', '');
|
||||
expect(result.href).toBe('http://abc.xyz/');
|
||||
});
|
||||
|
||||
it('should throw an error for an invalid URL', () => {
|
||||
expect(() => {
|
||||
codec.parse('/foo/bar');
|
||||
}).toThrowError('Invalid URL (/foo/bar) with base (undefined)');
|
||||
});
|
||||
|
||||
it('should throw an error for an invalid base', () => {
|
||||
expect(() => {
|
||||
codec.parse('http://foo.bar', 'abc');
|
||||
}).toThrowError('Invalid URL (http://foo.bar) with base (abc)');
|
||||
});
|
||||
});
|
||||
});
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
@ -62,12 +62,8 @@ import {Subject, Subscription} from 'rxjs';
|
||||
* @publicApi
|
||||
*/
|
||||
export class EventEmitter<T extends any> extends Subject<T> {
|
||||
// TODO: mark this as internal once all the facades are gone
|
||||
// we can't mark it as internal now because EventEmitter exported via @angular/core would not
|
||||
// contain this property making it incompatible with all the code that uses EventEmitter via
|
||||
// facades, which are local to the code and do not have this property stripped.
|
||||
/**
|
||||
* Internal
|
||||
* @internal
|
||||
*/
|
||||
__isAsync: boolean; // tslint:disable-line
|
||||
|
||||
|
@ -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",
|
||||
|
@ -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
|
||||
|
@ -57,8 +57,38 @@ export const NG_VALIDATORS = new InjectionToken<Array<Validator|Function>>('NgVa
|
||||
export const NG_ASYNC_VALIDATORS =
|
||||
new InjectionToken<Array<Validator|Function>>('NgAsyncValidators');
|
||||
|
||||
/**
|
||||
* A regular expression that matches valid e-mail addresses.
|
||||
*
|
||||
* At a high level, this regexp matches e-mail addresses of the format `local-part@tld`, where:
|
||||
* - `local-part` consists of one or more of the allowed characters (alphanumeric and some
|
||||
* punctuation symbols).
|
||||
* - `local-part` cannot begin or end with a period (`.`).
|
||||
* - `local-part` cannot be longer than 64 characters.
|
||||
* - `tld` consists of one or more `labels` separated by periods (`.`). For example `localhost` or
|
||||
* `foo.com`.
|
||||
* - A `label` consists of one or more of the allowed characters (alphanumeric, dashes (`-`) and
|
||||
* periods (`.`)).
|
||||
* - A `label` cannot begin or end with a dash (`-`) or a period (`.`).
|
||||
* - A `label` cannot be longer than 63 characters.
|
||||
* - The whole address cannot be longer than 254 characters.
|
||||
*
|
||||
* ## Implementation background
|
||||
*
|
||||
* This regexp was ported over from AngularJS (see there for git history):
|
||||
* https://github.com/angular/angular.js/blob/c133ef836/src/ng/directive/input.js#L27
|
||||
* It is based on the
|
||||
* [WHATWG version](https://html.spec.whatwg.org/multipage/input.html#valid-e-mail-address) with
|
||||
* some enhancements to incorporate more RFC rules (such as rules related to domain names and the
|
||||
* lengths of different parts of the address). The main differences from the WHATWG version are:
|
||||
* - Disallow `local-part` to begin or end with a period (`.`).
|
||||
* - Disallow `local-part` length to exceed 64 characters.
|
||||
* - Disallow total address length to exceed 254 characters.
|
||||
*
|
||||
* See [this commit](https://github.com/angular/angular.js/commit/f3f5cf72e) for more details.
|
||||
*/
|
||||
const EMAIL_REGEXP =
|
||||
/^(?=.{1,254}$)(?=.{1,64}@)[-!#$%&'*+/0-9=?A-Z^_`a-z{|}~]+(\.[-!#$%&'*+/0-9=?A-Z^_`a-z{|}~]+)*@[A-Za-z0-9]([A-Za-z0-9-]{0,61}[A-Za-z0-9])?(\.[A-Za-z0-9]([A-Za-z0-9-]{0,61}[A-Za-z0-9])?)*$/;
|
||||
/^(?=.{1,254}$)(?=.{1,64}@)[a-zA-Z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-zA-Z0-9!#$%&'*+/=?^_`{|}~-]+)*@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/;
|
||||
|
||||
/**
|
||||
* @description
|
||||
@ -191,6 +221,20 @@ export class Validators {
|
||||
* @description
|
||||
* Validator that requires the control's value pass an email validation test.
|
||||
*
|
||||
* Tests the value using a [regular expression](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions)
|
||||
* pattern suitable for common usecases. The pattern is based on the definition of a valid email
|
||||
* address in the [WHATWG HTML specification](https://html.spec.whatwg.org/multipage/input.html#valid-e-mail-address)
|
||||
* with some enhancements to incorporate more RFC rules (such as rules related to domain names and
|
||||
* the lengths of different parts of the address).
|
||||
*
|
||||
* The differences from the WHATWG version include:
|
||||
* - Disallow `local-part` (the part before the `@` symbol) to begin or end with a period (`.`).
|
||||
* - Disallow `local-part` to be longer than 64 characters.
|
||||
* - Disallow the whole address to be longer than 254 characters.
|
||||
*
|
||||
* If this pattern does not satisfy your business needs, you can use `Validators.pattern()` to
|
||||
* validate the value against a different pattern.
|
||||
*
|
||||
* @usageNotes
|
||||
*
|
||||
* ### Validate that the field matches a valid email pattern
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user