Compare commits
176 Commits
Author | SHA1 | Date | |
---|---|---|---|
dbbfab3ee8 | |||
b87ae8e5f9 | |||
8b1d28ff48 | |||
463e967abb | |||
71b8bbef95 | |||
a2cf677714 | |||
9e1ce318a8 | |||
a00c2a9210 | |||
a677d8b330 | |||
7fe89ba4ea | |||
a8af83bf36 | |||
c75e16aae8 | |||
1affcc63c0 | |||
34395aeea1 | |||
f64de2e9f3 | |||
86ad77c1b1 | |||
d5c815f1f2 | |||
32c6d5313a | |||
388c1a3638 | |||
334a3ba125 | |||
fd1d161844 | |||
280ea13342 | |||
3b1e539bab | |||
8e57965e3c | |||
761bb0aafd | |||
1b881b6ba2 | |||
f03d724fea | |||
cf8d934ba9 | |||
67f4852acb | |||
08f6a64893 | |||
ee49bbebc7 | |||
da78b02d71 | |||
d787925841 | |||
4e6efb361d | |||
aa371335dc | |||
090b6d92da | |||
a5cbfa2aab | |||
e8ed37a0e7 | |||
e5cbebef0d | |||
654ec1570a | |||
728d903a67 | |||
495f8e1bc6 | |||
d778a65447 | |||
e8dbba417d | |||
00c145f14c | |||
8c4c9858a2 | |||
caf3a53385 | |||
6ba1cd4365 | |||
bb18cba253 | |||
5abb9360d8 | |||
16c59aecf1 | |||
47d1216f6b | |||
5e614bfda8 | |||
efecf36eb5 | |||
b68d29791f | |||
136cd097c8 | |||
7be198a8e3 | |||
b4ee74667b | |||
67444e70ab | |||
09e55eabf3 | |||
1c838eb976 | |||
24ec9e57ad | |||
190330a612 | |||
9417086f6c | |||
535ff0564a | |||
e8c8ec7075 | |||
03d2813c20 | |||
fb5b2a89cd | |||
ea495d958f | |||
2edb87e7f8 | |||
903d28fe86 | |||
6945f7978e | |||
27afe01910 | |||
36b6110e7d | |||
24a1e146da | |||
2256920292 | |||
2a25ac2ac9 | |||
56693339c2 | |||
c976b88dcf | |||
3e08794abf | |||
b598e884f6 | |||
cf916a03d3 | |||
ee6498f37e | |||
1a0b2ff4fb | |||
dfb331cd18 | |||
11c926ce47 | |||
a0119b1144 | |||
c938fb4619 | |||
2f49a23d64 | |||
c6eaaf5b3d | |||
5e38ec8acc | |||
0cd5964f67 | |||
f3938a6a2b | |||
3baa74449c | |||
df4e97c81e | |||
d629f2c6a8 | |||
6ee47d5e76 | |||
9226b421e8 | |||
b06847f43d | |||
a54a752147 | |||
d00a2e8920 | |||
86981b395d | |||
feec963106 | |||
09fc669b4f | |||
cb339b87f3 | |||
fdcf877f83 | |||
5b5b9897c9 | |||
0e7365724e | |||
4ef1a3cd97 | |||
38b5ed05ea | |||
642c015f23 | |||
0977d95802 | |||
d374787db6 | |||
a634deb885 | |||
bcbd7ed8f0 | |||
f7de2be3f3 | |||
1e97d511c7 | |||
7e1e00c21e | |||
481f4b7412 | |||
4bc0084e5a | |||
b6864494a1 | |||
0021437ee1 | |||
d91ecd2c8b | |||
d0018e6bf6 | |||
587ca854cc | |||
8b9f03d9fa | |||
40f1f94fe0 | |||
2270467d60 | |||
8efda5b353 | |||
7b7f2d9c1b | |||
c469e25cf2 | |||
a21cde2960 | |||
9b774348b3 | |||
ce219ccfa2 | |||
53bbb01047 | |||
c6741bf36a | |||
cc06bf50f3 | |||
21e78ad022 | |||
05e855092b | |||
2817764433 | |||
145639d0f8 | |||
3eb327b67b | |||
be6af26dc1 | |||
637e81e9bb | |||
abc3cbb33f | |||
27eb8f2723 | |||
81671cea9a | |||
843fc7df9f | |||
cfe4732e41 | |||
48cabb44c7 | |||
9d0db2fe4b | |||
069146e7f3 | |||
fc895ba189 | |||
9a51528072 | |||
7fc38d4947 | |||
b1f84ec78f | |||
f351e8a5b1 | |||
ff2c342356 | |||
01b05b3f7c | |||
3b78f9eb0f | |||
f6ca619343 | |||
ab69c31fc5 | |||
38de40c9ec | |||
53352c40c3 | |||
dd3cab6eb1 | |||
ea1a3d3603 | |||
cf1b436010 | |||
cdcbb53a2d | |||
db2db6e7f5 | |||
1df6b16263 | |||
f4871212b7 | |||
2a23eca917 | |||
b5da4c6676 | |||
b8c2f3b93f | |||
75cf86e70f | |||
bf25165354 |
@ -1,5 +1,6 @@
|
||||
node_modules
|
||||
dist
|
||||
aio/content
|
||||
aio/node_modules
|
||||
aio/tools/examples/shared/node_modules
|
||||
integration/bazel
|
||||
|
8
.bazelrc
@ -1,5 +1,3 @@
|
||||
# Load any settings specific to the current user
|
||||
try-import .bazelrc.user
|
||||
################################
|
||||
# Settings for Angular team members only
|
||||
################################
|
||||
@ -33,7 +31,7 @@ test:debug --test_arg=--node_options=--inspect-brk --test_output=streamed --test
|
||||
# eventually a surprising failure with auto-discovery of the C++ toolchain in
|
||||
# MacOS High Sierra.
|
||||
# See https://github.com/bazelbuild/bazel/issues/4603
|
||||
build --symlink_prefix=/
|
||||
build --symlink_prefix=dist/
|
||||
|
||||
# Performance: avoid stat'ing input files
|
||||
build --watchfs
|
||||
@ -117,3 +115,7 @@ build:remote --remote_instance_name=projects/internal-200822/instances/default_i
|
||||
# Do not accept remote cache.
|
||||
# We need to understand the security risks of using prior build artifacts.
|
||||
build:remote --remote_accept_cached=false
|
||||
|
||||
# Load any settings specific to the current user. Needs to be last statement in this
|
||||
# config, as the user configuration should be able to overwrite flags from this file.
|
||||
try-import .bazelrc.user
|
||||
|
@ -26,13 +26,28 @@ var_2: &browsers_docker_image circleci/node:10.12-browsers
|
||||
# **NOTE 1 **: If you change the cache key prefix, also sync the restore_cache 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 v2-angular-node-10.12-{{ checksum "yarn.lock" }}-{{ checksum "WORKSPACE" }}-{{ checksum "packages/bazel/package.bzl" }}
|
||||
var_3: &cache_key v3-angular-node-10.12-{{ checksum "yarn.lock" }}-{{ checksum "WORKSPACE" }}-{{ checksum "packages/bazel/package.bzl" }}-{{ checksum "aio/yarn.lock" }}
|
||||
|
||||
# Define common ENV vars
|
||||
var_4: &define_env_vars
|
||||
# Initializes the CI environment by setting up common environment variables.
|
||||
var_4: &init_environment
|
||||
run:
|
||||
name: Define environment variables
|
||||
command: ./.circleci/env.sh
|
||||
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:
|
||||
@ -48,11 +63,20 @@ var_6: &job_defaults
|
||||
docker:
|
||||
- image: *default_docker_image
|
||||
|
||||
# After checkout, rebase on top of master.
|
||||
# Similar to travis behavior, but not quite the same.
|
||||
# See https://discuss.circleci.com/t/1662
|
||||
# After checkout, rebase on top of target branch.
|
||||
var_7: &post_checkout
|
||||
post: git pull --ff-only origin "refs/pull/${CI_PULL_REQUEST//*pull\//}/merge"
|
||||
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:
|
||||
@ -70,33 +94,63 @@ var_9: &setup_circleci_bazel_config
|
||||
name: Setting up CircleCI bazel configuration
|
||||
command: sudo cp .circleci/bazel.rc /etc/bazel.bazelrc
|
||||
|
||||
# Sets up Yarn by downloading it and installing it globally. We don't use Yarn from the
|
||||
# docker image because this means that we can only use the Yarn version that comes with
|
||||
# a specific version of NodeJS. We want to be able to update Yarn without having to
|
||||
# update the docker image that comes with NodeJS.
|
||||
var_10: &download_yarn
|
||||
run:
|
||||
name: Downloading Yarn
|
||||
command: curl -o- -L https://yarnpkg.com/install.sh | PROFILE=$BASH_ENV bash -s -- --version "$CI_YARN_VERSION"
|
||||
|
||||
var_11: &restore_cache
|
||||
var_10: &restore_cache
|
||||
restore_cache:
|
||||
keys:
|
||||
- *cache_key
|
||||
# This fallback should be the cache_key without variables.
|
||||
- v2-angular-node-10.12-
|
||||
- v3-angular-node-10.12-
|
||||
|
||||
# Branch filter that can be specified for jobs that should only run on publish branches
|
||||
# (e.g. master or the patch branch)
|
||||
var_12: &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_13: &attach_workspace
|
||||
attach_workspace:
|
||||
at: ~/
|
||||
|
||||
version: 2
|
||||
jobs:
|
||||
setup:
|
||||
<<: *job_defaults
|
||||
steps:
|
||||
- checkout
|
||||
- *post_checkout
|
||||
# This cache is saved in the build-npm-packages so that Bazel cache is also included.
|
||||
- *restore_cache
|
||||
- *init_environment
|
||||
- *yarn_install
|
||||
- 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.
|
||||
- run: |
|
||||
if [ ! -d ~/bazel_repository_cache ]; then
|
||||
mkdir ~/bazel_repository_cache
|
||||
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.
|
||||
- persist_to_workspace:
|
||||
root: ~/
|
||||
paths:
|
||||
- ./ng
|
||||
- ./bazel_repository_cache
|
||||
|
||||
lint:
|
||||
<<: *job_defaults
|
||||
steps:
|
||||
- checkout:
|
||||
<<: *post_checkout
|
||||
- *restore_cache
|
||||
- *define_env_vars
|
||||
- *download_yarn
|
||||
- *yarn_install
|
||||
- *attach_workspace
|
||||
- *init_environment
|
||||
|
||||
- run: 'yarn bazel:format -mode=check ||
|
||||
(echo "BUILD files not formatted. Please run ''yarn bazel:format''" ; exit 1)'
|
||||
@ -104,18 +158,14 @@ jobs:
|
||||
- run: 'yarn bazel:lint ||
|
||||
(echo -e "\n.bzl files have lint errors. Please run ''yarn bazel:lint-fix''"; exit 1)'
|
||||
|
||||
- run: ./node_modules/.bin/gulp lint
|
||||
- run: yarn gulp lint
|
||||
|
||||
test:
|
||||
<<: *job_defaults
|
||||
resource_class: xlarge
|
||||
steps:
|
||||
- checkout:
|
||||
<<: *post_checkout
|
||||
- *restore_cache
|
||||
- *define_env_vars
|
||||
- *download_yarn
|
||||
- *yarn_install
|
||||
- *attach_workspace
|
||||
- *init_environment
|
||||
- *setup_circleci_bazel_config
|
||||
|
||||
# Setup remote execution and run RBE-compatible tests.
|
||||
@ -130,12 +180,8 @@ jobs:
|
||||
<<: *job_defaults
|
||||
resource_class: xlarge
|
||||
steps:
|
||||
- checkout:
|
||||
<<: *post_checkout
|
||||
- *restore_cache
|
||||
- *define_env_vars
|
||||
- *download_yarn
|
||||
- *yarn_install
|
||||
- *attach_workspace
|
||||
- *init_environment
|
||||
- *setup_circleci_bazel_config
|
||||
- *setup_bazel_remote_execution
|
||||
|
||||
@ -169,11 +215,8 @@ jobs:
|
||||
# Needed because the AIO tests and the PWA score test depend on Chrome being available.
|
||||
- image: *browsers_docker_image
|
||||
steps:
|
||||
- checkout:
|
||||
<<: *post_checkout
|
||||
- *restore_cache
|
||||
- *define_env_vars
|
||||
- *download_yarn
|
||||
- *attach_workspace
|
||||
- *init_environment
|
||||
# Build aio
|
||||
- run: yarn --cwd aio build --progress=false
|
||||
# Lint the code
|
||||
@ -197,11 +240,8 @@ jobs:
|
||||
# Needed because before deploying the deploy-production script runs the PWA score tests.
|
||||
- image: *browsers_docker_image
|
||||
steps:
|
||||
- checkout:
|
||||
<<: *post_checkout
|
||||
- *restore_cache
|
||||
- *define_env_vars
|
||||
- *download_yarn
|
||||
- *attach_workspace
|
||||
- *init_environment
|
||||
# Deploy angular.io to production (if necessary)
|
||||
- run: setPublicVar CI_STABLE_BRANCH "$(npm info @angular/core dist-tags.latest | sed -r 's/^\s*([0-9]+\.[0-9]+)\.[0-9]+.*$/\1.x/')"
|
||||
- run: yarn --cwd aio deploy-production
|
||||
@ -212,13 +252,8 @@ jobs:
|
||||
# Needed because the AIO tests and the PWA score test depend on Chrome being available.
|
||||
- image: *browsers_docker_image
|
||||
steps:
|
||||
- checkout:
|
||||
<<: *post_checkout
|
||||
- *restore_cache
|
||||
- attach_workspace:
|
||||
at: dist
|
||||
- *define_env_vars
|
||||
- *download_yarn
|
||||
- *attach_workspace
|
||||
- *init_environment
|
||||
# Build aio (with local Angular packages)
|
||||
- run: yarn --cwd aio build-local --progress=false
|
||||
# Run PWA-score tests
|
||||
@ -232,26 +267,22 @@ jobs:
|
||||
test_aio_local_ivy:
|
||||
<<: *job_defaults
|
||||
steps:
|
||||
- checkout:
|
||||
<<: *post_checkout
|
||||
- *restore_cache
|
||||
- attach_workspace:
|
||||
at: dist
|
||||
- *define_env_vars
|
||||
- *download_yarn
|
||||
- *attach_workspace
|
||||
- *init_environment
|
||||
# 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, it's likely not
|
||||
# worth the efforts to change the AIO packages installer.
|
||||
- run: mv dist/packages-dist-ivy-aot dist/packages-dist
|
||||
# Build aio with Ivy (using local Angular packages)
|
||||
- run: yarn --cwd aio build-with-ivy --progress=false
|
||||
|
||||
test_aio_tools:
|
||||
<<: *job_defaults
|
||||
steps:
|
||||
- checkout:
|
||||
<<: *post_checkout
|
||||
- *restore_cache
|
||||
- attach_workspace:
|
||||
at: dist
|
||||
- *define_env_vars
|
||||
- *download_yarn
|
||||
- *attach_workspace
|
||||
- *init_environment
|
||||
# Install
|
||||
- run: yarn --cwd aio install --frozen-lockfile --non-interactive
|
||||
- run: yarn --cwd aio extract-cli-command-docs
|
||||
@ -264,42 +295,43 @@ jobs:
|
||||
docker:
|
||||
# Needed because the example e2e tests depend on Chrome.
|
||||
- image: *browsers_docker_image
|
||||
parallelism: 3
|
||||
parallelism: 4
|
||||
resource_class: xlarge
|
||||
steps:
|
||||
- checkout:
|
||||
<<: *post_checkout
|
||||
- *restore_cache
|
||||
- attach_workspace:
|
||||
at: dist
|
||||
- *define_env_vars
|
||||
- *download_yarn
|
||||
- *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 --shard=${CIRCLE_NODE_INDEX}/${CIRCLE_NODE_TOTAL}
|
||||
- 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
|
||||
parallelism: 3
|
||||
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.
|
||||
parallelism: 5
|
||||
steps:
|
||||
- checkout:
|
||||
<<: *post_checkout
|
||||
- *restore_cache
|
||||
- attach_workspace:
|
||||
at: dist
|
||||
- *define_env_vars
|
||||
- *download_yarn
|
||||
- *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 --shard=${CIRCLE_NODE_INDEX}/${CIRCLE_NODE_TOTAL}
|
||||
- run: yarn --cwd aio example-e2e --setup --local --ivy --cliSpecsConcurrency=5 --shard=${CIRCLE_NODE_INDEX}/${CIRCLE_NODE_TOTAL}
|
||||
|
||||
# This job should only be run on PR builds, where `CI_PULL_REQUEST` is not `false`.
|
||||
aio_preview:
|
||||
@ -307,11 +339,8 @@ jobs:
|
||||
environment:
|
||||
AIO_SNAPSHOT_ARTIFACT_PATH: &aio_preview_artifact_path 'aio/tmp/snapshot.tgz'
|
||||
steps:
|
||||
- checkout:
|
||||
<<: *post_checkout
|
||||
- *restore_cache
|
||||
- *define_env_vars
|
||||
- *download_yarn
|
||||
- *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
|
||||
@ -327,11 +356,8 @@ jobs:
|
||||
# Needed because the test-preview script runs e2e tests and the PWA score test with Chrome.
|
||||
- image: *browsers_docker_image
|
||||
steps:
|
||||
- checkout:
|
||||
<<: *post_checkout
|
||||
- *restore_cache
|
||||
- *define_env_vars
|
||||
- *download_yarn
|
||||
- *attach_workspace
|
||||
- *init_environment
|
||||
- run: yarn --cwd aio install --frozen-lockfile --non-interactive
|
||||
- run:
|
||||
name: Wait for preview and run tests
|
||||
@ -350,53 +376,44 @@ jobs:
|
||||
<<: *job_defaults
|
||||
resource_class: xlarge
|
||||
steps:
|
||||
- checkout:
|
||||
<<: *post_checkout
|
||||
- *restore_cache
|
||||
- *define_env_vars
|
||||
- *download_yarn
|
||||
- *yarn_install
|
||||
- *attach_workspace
|
||||
- *init_environment
|
||||
- *setup_circleci_bazel_config
|
||||
- *setup_bazel_remote_execution
|
||||
|
||||
- run: scripts/build-packages-dist.sh
|
||||
|
||||
# Save the npm packages from //packages/... for other workflow jobs to read
|
||||
# https://circleci.com/docs/2.0/workflows/#using-workspaces-to-share-data-among-jobs
|
||||
- persist_to_workspace:
|
||||
root: dist
|
||||
root: ~/
|
||||
paths:
|
||||
- packages-dist
|
||||
- ng/dist/packages-dist
|
||||
|
||||
# Save dependencies and bazel repository cache to use on subsequent runs.
|
||||
- save_cache:
|
||||
key: *cache_key
|
||||
paths:
|
||||
- "node_modules"
|
||||
- "aio/node_modules"
|
||||
- "~/bazel_repository_cache"
|
||||
|
||||
|
||||
# Build the ivy npm packages.
|
||||
build-ivy-npm-packages:
|
||||
<<: *job_defaults
|
||||
resource_class: xlarge
|
||||
steps:
|
||||
- checkout:
|
||||
<<: *post_checkout
|
||||
- *restore_cache
|
||||
- *define_env_vars
|
||||
- *download_yarn
|
||||
- *yarn_install
|
||||
- *attach_workspace
|
||||
- *init_environment
|
||||
- *setup_circleci_bazel_config
|
||||
- *setup_bazel_remote_execution
|
||||
|
||||
- run: scripts/build-ivy-npm-packages.sh
|
||||
|
||||
# Save the npm packages from //packages/... for other workflow jobs to read
|
||||
# https://circleci.com/docs/2.0/workflows/#using-workspaces-to-share-data-among-jobs
|
||||
- persist_to_workspace:
|
||||
root: dist
|
||||
root: ~/
|
||||
paths:
|
||||
- packages-dist-ivy-aot
|
||||
- ng/dist/packages-dist-ivy-aot
|
||||
|
||||
# We run the integration tests outside of Bazel for now.
|
||||
# They are a separate workflow job so that they can be easily re-run.
|
||||
@ -415,15 +432,8 @@ jobs:
|
||||
# on a 4G worker so we use a larger machine here too.
|
||||
resource_class: xlarge
|
||||
steps:
|
||||
- checkout:
|
||||
<<: *post_checkout
|
||||
- *restore_cache
|
||||
- attach_workspace:
|
||||
at: dist
|
||||
- *define_env_vars
|
||||
- *download_yarn
|
||||
# Some integration tests get their dependencies from the root `node_modules/`.
|
||||
- *yarn_install
|
||||
- *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}
|
||||
@ -433,21 +443,20 @@ jobs:
|
||||
publish_snapshot:
|
||||
<<: *job_defaults
|
||||
steps:
|
||||
- checkout:
|
||||
<<: *post_checkout
|
||||
- *define_env_vars
|
||||
# See below - ideally this job should not trigger for non-upstream builds.
|
||||
# But since it does, we have to check this condition.
|
||||
- run:
|
||||
name: Skip this job for Pull Requests and Fork builds
|
||||
# Note, `|| true` on the end makes this step always exit 0
|
||||
command: '[[
|
||||
"$CI_PULL_REQUEST" != "false"
|
||||
|| "$CI_REPO_OWNER" != "angular"
|
||||
|| "$CI_REPO_NAME" != "angular"
|
||||
]] && circleci step halt || true'
|
||||
- attach_workspace:
|
||||
at: dist
|
||||
# Note: Using `CIRCLE_*` env variables (instead of those defined in `env.sh` so that this
|
||||
# step can be run before `init_environment`.
|
||||
command: >
|
||||
if [[ -n "${CIRCLE_PR_NUMBER}" ]] ||
|
||||
[[ "$CIRCLE_PROJECT_USERNAME" != "angular" ]] ||
|
||||
[[ "$CIRCLE_PROJECT_REPONAME" != "angular" ]]; then
|
||||
circleci step halt
|
||||
fi
|
||||
- *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
|
||||
@ -464,11 +473,8 @@ jobs:
|
||||
# which does not load the browser through the Bazel webtesting rules.
|
||||
- image: *browsers_docker_image
|
||||
steps:
|
||||
- checkout:
|
||||
<<: *post_checkout
|
||||
- *restore_cache
|
||||
- *define_env_vars
|
||||
- *download_yarn
|
||||
- *attach_workspace
|
||||
- *init_environment
|
||||
- run:
|
||||
name: Run tests against the deployed apps
|
||||
command: ./aio/scripts/test-production.sh $CI_AIO_MIN_PWA_SCORE
|
||||
@ -479,21 +485,6 @@ jobs:
|
||||
command: 'curl --request POST --header "Content-Type: application/json" --data "{\"text\":\":x: \`$CIRCLE_JOB\` job failed on build $CIRCLE_BUILD_NUM: $CIRCLE_BUILD_URL :scream:\"}" $SLACK_CARETAKER_WEBHOOK_URL'
|
||||
when: on_fail
|
||||
|
||||
legacy-unit-tests-local:
|
||||
<<: *job_defaults
|
||||
docker:
|
||||
- image: *browsers_docker_image
|
||||
steps:
|
||||
- checkout:
|
||||
<<: *post_checkout
|
||||
- *restore_cache
|
||||
- *define_env_vars
|
||||
- *download_yarn
|
||||
- *yarn_install
|
||||
- run: yarn tsc -p packages
|
||||
- run: yarn tsc -p modules
|
||||
- run: yarn karma start ./karma-js.conf.js --single-run --browsers=ChromeNoSandbox
|
||||
|
||||
legacy-unit-tests-saucelabs:
|
||||
<<: *job_defaults
|
||||
# In order to avoid the bottleneck of having a slow host machine, we acquire a better
|
||||
@ -501,12 +492,8 @@ jobs:
|
||||
# and therefore the tunnel and Karma need to process a lot of file requests and tests.
|
||||
resource_class: xlarge
|
||||
steps:
|
||||
- checkout:
|
||||
<<: *post_checkout
|
||||
- *restore_cache
|
||||
- *define_env_vars
|
||||
- *download_yarn
|
||||
- *yarn_install
|
||||
- *attach_workspace
|
||||
- *init_environment
|
||||
- run:
|
||||
name: Preparing environment for running tests on Saucelabs.
|
||||
command: |
|
||||
@ -527,14 +514,8 @@ jobs:
|
||||
legacy-misc-tests:
|
||||
<<: *job_defaults
|
||||
steps:
|
||||
- checkout:
|
||||
<<: *post_checkout
|
||||
- *restore_cache
|
||||
- *define_env_vars
|
||||
- *download_yarn
|
||||
- *yarn_install
|
||||
- attach_workspace:
|
||||
at: dist
|
||||
- *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
|
||||
@ -544,13 +525,25 @@ workflows:
|
||||
version: 2
|
||||
default_workflow:
|
||||
jobs:
|
||||
- lint
|
||||
- test
|
||||
- test_ivy_aot
|
||||
- build-npm-packages
|
||||
- test_aio
|
||||
- legacy-unit-tests-local
|
||||
- legacy-unit-tests-saucelabs
|
||||
- setup
|
||||
- lint:
|
||||
requires:
|
||||
- setup
|
||||
- test:
|
||||
requires:
|
||||
- setup
|
||||
- test_ivy_aot:
|
||||
requires:
|
||||
- setup
|
||||
- build-npm-packages:
|
||||
requires:
|
||||
- setup
|
||||
- test_aio:
|
||||
requires:
|
||||
- setup
|
||||
- legacy-unit-tests-saucelabs:
|
||||
requires:
|
||||
- setup
|
||||
- deploy_aio:
|
||||
requires:
|
||||
- test_aio
|
||||
@ -562,7 +555,7 @@ workflows:
|
||||
- build-npm-packages
|
||||
# - test_aio_local_ivy:
|
||||
# requires:
|
||||
# - build-npm-packages
|
||||
# - build-ivy-npm-packages
|
||||
- test_aio_tools:
|
||||
requires:
|
||||
- build-npm-packages
|
||||
@ -571,8 +564,10 @@ workflows:
|
||||
- build-npm-packages
|
||||
# - test_docs_examples_ivy:
|
||||
# requires:
|
||||
# - build-npm-packages
|
||||
# - build-ivy-npm-packages
|
||||
- aio_preview:
|
||||
requires:
|
||||
- setup
|
||||
# Only run on PR builds. (There can be no previews for non-PR builds.)
|
||||
filters:
|
||||
branches:
|
||||
@ -601,16 +596,15 @@ workflows:
|
||||
# Get the artifacts to publish from the build-packages-dist job
|
||||
# since the publishing script expects the legacy outputs layout.
|
||||
- build-npm-packages
|
||||
- legacy-misc-tests
|
||||
- legacy-unit-tests-local
|
||||
- legacy-unit-tests-saucelabs
|
||||
|
||||
- legacy-misc-tests
|
||||
|
||||
aio_monitoring:
|
||||
jobs:
|
||||
- aio_monitoring
|
||||
triggers:
|
||||
- schedule:
|
||||
# Runs AIO monitoring job at 00:00AM every day.
|
||||
cron: "0 0 * * *"
|
||||
filters:
|
||||
branches:
|
||||
|
@ -1,8 +1,9 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Variables
|
||||
readonly envHelpersPath="`dirname $0`/env-helpers.inc.sh";
|
||||
readonly getCommitRangePath="`dirname $0`/get-commit-range.js";
|
||||
readonly projectDir=$(realpath "$(dirname ${BASH_SOURCE[0]})/..")
|
||||
readonly envHelpersPath="$projectDir/.circleci/env-helpers.inc.sh";
|
||||
readonly getCommitRangePath="$projectDir/.circleci/get-commit-range.js";
|
||||
|
||||
# Load helpers and make them available everywhere (through `$BASH_ENV`).
|
||||
source $envHelpersPath;
|
||||
@ -14,7 +15,7 @@ echo "source $envHelpersPath;" >> $BASH_ENV;
|
||||
####################################################################################################
|
||||
# See https://circleci.com/docs/2.0/env-vars/#built-in-environment-variables for more info.
|
||||
####################################################################################################
|
||||
setPublicVar PROJECT_ROOT "$(pwd)";
|
||||
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";
|
||||
@ -31,7 +32,6 @@ setPublicVar CI_COMMIT_RANGE "`[[ ${CIRCLE_PR_NUMBER:-false} != false ]] && echo
|
||||
setPublicVar CI_PULL_REQUEST "${CIRCLE_PR_NUMBER:-false}";
|
||||
setPublicVar CI_REPO_NAME "$CIRCLE_PROJECT_REPONAME";
|
||||
setPublicVar CI_REPO_OWNER "$CIRCLE_PROJECT_USERNAME";
|
||||
setPublicVar CI_YARN_VERSION "1.13.0";
|
||||
|
||||
|
||||
####################################################################################################
|
||||
@ -54,6 +54,7 @@ else
|
||||
setPublicVar SAUCE_USERNAME "angular-ci";
|
||||
setSecretVar SAUCE_ACCESS_KEY "9b988f434ff8-fbca-8aa4-4ae3-35442987";
|
||||
fi
|
||||
setPublicVar SAUCE_LOG_FILE /tmp/angular/sauce-connect.log
|
||||
setPublicVar SAUCE_READY_FILE /tmp/angular/sauce-connect-ready-file.lock
|
||||
setPublicVar SAUCE_PID_FILE /tmp/angular/sauce-connect-pid-file.lock
|
||||
setPublicVar SAUCE_TUNNEL_IDENTIFIER "angular-${CIRCLE_BUILD_NUM}-${CIRCLE_NODE_INDEX}"
|
||||
|
110
.github/CODEOWNERS
vendored
@ -39,12 +39,14 @@
|
||||
# (just to make this file easier to understand)
|
||||
# ================================================
|
||||
|
||||
# alan-agius4 - Alan Agius
|
||||
# alexeagle - Alex Eagle
|
||||
# alxhub - Alex Rickabaugh
|
||||
# AndrewKushnir - Andrew Kushnir
|
||||
# andrewseguin - Andrew Seguin
|
||||
# benlesh - Ben Lesh
|
||||
# brandonroberts - Brandon Roberts
|
||||
# devversion - Paul Gschwendtner
|
||||
# filipesilva - Filipe Silva
|
||||
# gkalpak - George Kalpakas
|
||||
# hansl - Hans Larsen
|
||||
@ -86,6 +88,7 @@
|
||||
# - IgorMinar
|
||||
# - kara
|
||||
# - mhevery
|
||||
# - alexeagle
|
||||
|
||||
|
||||
# ===========================================================
|
||||
@ -293,6 +296,17 @@
|
||||
# - IgorMinar
|
||||
|
||||
|
||||
# ===========================================================
|
||||
# @angular/tools-docs-libraries
|
||||
# ===========================================================
|
||||
#
|
||||
# - alan-agius4
|
||||
# - alexeagle
|
||||
# - hansl
|
||||
# - IgorMinar
|
||||
# - mgechev
|
||||
|
||||
|
||||
# ===========================================================
|
||||
# @angular/fw-docs-marketing
|
||||
# ===========================================================
|
||||
@ -313,6 +327,9 @@
|
||||
# ===========================================================
|
||||
#
|
||||
# - alexeagle
|
||||
# - devversion
|
||||
# - filipesilva
|
||||
# - gkalpak
|
||||
# - IgorMinar
|
||||
|
||||
|
||||
@ -347,10 +364,19 @@
|
||||
|
||||
/packages/animations/** @angular/fw-animations @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/packages/platform-browser/animations/** @angular/fw-animations @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
|
||||
/aio/content/guide/animations.md @angular/fw-animations @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/examples/animations/** @angular/fw-animations @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/images/guide/animations/** @angular/fw-animations @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
|
||||
/aio/content/guide/complex-animation-sequences.md @angular/fw-animations @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
|
||||
/aio/content/guide/reusable-animations.md @angular/fw-animations @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
|
||||
/aio/content/guide/route-animations.md @angular/fw-animations @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
|
||||
/aio/content/guide/transition-and-triggers.md @angular/fw-animations @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
|
||||
|
||||
|
||||
# ================================================
|
||||
@ -381,12 +407,13 @@
|
||||
|
||||
|
||||
# ================================================
|
||||
# @angular/compiler-cli/ngtools
|
||||
# Framework/cli integration
|
||||
#
|
||||
# a rule to control API changes between @angular/compiler-cli and @angular/cli
|
||||
# ================================================
|
||||
|
||||
/packages/compiler-cli/src/ngtools/** @angular/tools-cli @angular/framework-global-approvers
|
||||
/aio/content/guide/ivy.md @angular/tools-cli @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
|
||||
|
||||
|
||||
@ -406,6 +433,14 @@
|
||||
/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
|
||||
|
||||
/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
|
||||
/aio/content/guide/architecture-next-steps.md @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/guide/architecture-services.md @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/guide/architecture.md @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/examples/architecture/** @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/images/guide/architecture/** @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
|
||||
/aio/content/guide/attribute-directives.md @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/examples/attribute-directives/** @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/images/guide/attribute-directives/** @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
@ -413,6 +448,8 @@
|
||||
/aio/content/guide/bootstrapping.md @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/examples/bootstrapping/** @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
|
||||
/aio/content/guide/cheatsheet.md @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
|
||||
/aio/content/guide/component-interaction.md @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/examples/component-interaction/** @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/images/guide/component-interaction/** @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
@ -428,7 +465,13 @@
|
||||
/aio/content/examples/dependency-injection-in-action/** @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/images/guide/dependency-injection-in-action/** @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
|
||||
/aio/content/guide/dependency-injection-pattern.md @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/guide/dependency-injection-navtree.md @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
|
||||
/aio/content/guide/dependency-injection-providers.md @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
|
||||
/aio/content/guide/displaying-data.md @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/examples/displaying-data/** @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/images/guide/displaying-data/** @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
|
||||
/aio/content/guide/dynamic-component-loader.md @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/examples/dynamic-component-loader/** @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
@ -455,12 +498,9 @@
|
||||
/aio/content/images/guide/lifecycle-hooks/** @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
|
||||
/aio/content/examples/ngcontainer/** @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/images/guide/ngcontainer/** @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
|
||||
/aio/content/guide/ngmodules.md @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/examples/ngmodules/** @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/examples/ngmodule/** @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/images/guide/ngmodule/** @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
|
||||
/aio/content/guide/ngmodule-api.md @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
|
||||
@ -472,6 +512,8 @@
|
||||
/aio/content/guide/module-types.md @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
|
||||
/aio/content/guide/template-syntax.md @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/examples/event-binding/** @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/examples/interpolation/** @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/examples/template-syntax/** @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/images/guide/template-syntax/** @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
|
||||
@ -494,6 +536,10 @@
|
||||
/aio/content/examples/structural-directives/** @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/images/guide/structural-directives/** @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
|
||||
/aio/content/guide/user-input.md @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/examples/user-input/** @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/images/guide/user-input/** @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
|
||||
|
||||
|
||||
# ================================================
|
||||
@ -519,6 +565,7 @@
|
||||
/aio/content/guide/elements.md @angular/fw-elements @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
|
||||
|
||||
|
||||
# ================================================
|
||||
# @angular/forms
|
||||
# ================================================
|
||||
@ -649,6 +696,7 @@ testing/** @angular/fw-test
|
||||
/packages/platform-browser/src/security/** @angular/fw-security
|
||||
/aio/content/guide/security.md @angular/fw-security @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/examples/security/** @angular/fw-security @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/images/guide/security/** @angular/fw-security @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
|
||||
|
||||
|
||||
@ -679,6 +727,14 @@ testing/** @angular/fw-test
|
||||
/aio/tests/** @angular/docs-infra @angular/framework-global-approvers
|
||||
/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
|
||||
/aio/content/guide/visual-studio-2015.md @angular/docs-infra @angular/framework-global-approvers
|
||||
/aio/content/examples/visual-studio-2015/** @angular/docs-infra @angular/framework-global-approvers
|
||||
|
||||
|
||||
|
||||
# ================================================
|
||||
@ -686,7 +742,17 @@ testing/** @angular/fw-test
|
||||
# ================================================
|
||||
|
||||
/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/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
|
||||
/aio/content/examples/toh-pt1/** @angular/fw-docs-intro @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/examples/toh-pt2/** @angular/fw-docs-intro @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/examples/toh-pt3/** @angular/fw-docs-intro @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/examples/toh-pt4/** @angular/fw-docs-intro @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/examples/toh-pt5/** @angular/fw-docs-intro @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/examples/toh-pt6/** @angular/fw-docs-intro @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
|
||||
|
||||
|
||||
@ -694,17 +760,15 @@ testing/** @angular/fw-test
|
||||
# Docs: observables
|
||||
# ================================================
|
||||
|
||||
/aio/content/examples/observables/** @angular/fw-docs-observables @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/images/guide/observables/** @angular/fw-docs-observables @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/guide/observables.md @angular/fw-docs-observables @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/examples/observables/** @angular/fw-docs-observables @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/guide/comparing-observables.md @angular/fw-docs-observables @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/examples/observables-in-angular/** @angular/fw-docs-observables @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/images/guide/observables-in-angular/** @angular/fw-docs-observables @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/guide/observables-in-angular.md @angular/fw-docs-observables @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/examples/practical-observable-usage/** @angular/fw-docs-observables @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/examples/observables-in-angular/** @angular/fw-docs-observables @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/guide/practical-observable-usage.md @angular/fw-docs-observables @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/examples/rx-library/** @angular/fw-docs-observables @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/examples/practical-observable-usage/** @angular/fw-docs-observables @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/guide/rx-library.md @angular/fw-docs-observables @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/examples/rx-library/** @angular/fw-docs-observables @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
|
||||
|
||||
|
||||
@ -715,12 +779,26 @@ 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-systemjs-anatomy.md @angular/fw-docs-packaging @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/examples/quickstart/** @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/guide/file-structure.md @angular/fw-docs-packaging @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/guide/releases.md @angular/fw-docs-packaging @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/guide/updating.md @angular/fw-docs-packaging @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/guide/workspace-config.md @angular/fw-docs-packaging @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
|
||||
|
||||
|
||||
# ================================================
|
||||
# Docs: libraries
|
||||
# ================================================
|
||||
|
||||
/aio/content/guide/creating-libraries.md @angular/tools-docs-libraries @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/guide/libraries.md @angular/tools-docs-libraries @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/guide/using-libraries.md @angular/tools-docs-libraries @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
|
||||
|
||||
|
||||
@ -729,6 +807,7 @@ testing/** @angular/fw-test
|
||||
# ================================================
|
||||
|
||||
/aio/content/marketing/** @angular/fw-docs-marketing @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/images/bios/** @angular/fw-docs-marketing @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/images/marketing/** @angular/fw-docs-marketing @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/navigation.json @angular/fw-docs-marketing @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/license.md @angular/fw-docs-marketing @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
@ -742,8 +821,11 @@ testing/** @angular/fw-test
|
||||
/* @angular/fw-dev-infra
|
||||
/.buildkite/** @angular/fw-dev-infra
|
||||
/.circleci/** @angular/fw-dev-infra
|
||||
/.codefresh/** @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/** @angular/fw-dev-infra
|
||||
@ -756,6 +838,10 @@ testing/** @angular/fw-test
|
||||
# ================================================
|
||||
|
||||
/tools/public_api_guard/** @angular/fw-public-api
|
||||
/aio/content/guide/glossary.md @angular/fw-public-api
|
||||
/aio/content/guide/styleguide.md @angular/fw-public-api
|
||||
/aio/content/examples/styleguide/** @angular/fw-public-api
|
||||
/aio/content/images/guide/styleguide/** @angular/fw-public-api
|
||||
|
||||
|
||||
|
||||
|
4
.github/ISSUE_TEMPLATE/1-bug-report.md
vendored
@ -37,7 +37,9 @@ Please create and share minimal reproduction of the issue starting with this tem
|
||||
<!-- ✍️--> https://stackblitz.com/...
|
||||
|
||||
<!--
|
||||
If StackBlitz is not suitable for reproduction of your issue, please create a minimal GitHub repository with the reproduction of the issue. Share the link to the repo below along with step-by-step instructions to reproduce the problem, as well as expected and actual behavior.
|
||||
If StackBlitz is not suitable for reproduction of your issue, please create a minimal GitHub repository with the reproduction of the issue.
|
||||
A good way to make a minimal reproduction is to create a new app via `ng new repro-app` and add the minimum possible code to show the problem.
|
||||
Share the link to the repo below along with step-by-step instructions to reproduce the problem, as well as expected and actual behavior.
|
||||
|
||||
Issues that don't have enough info and can't be reproduced will be closed.
|
||||
|
||||
|
1
.github/angular-robot.yml
vendored
@ -56,6 +56,7 @@ merge:
|
||||
- "**/.gitkeep"
|
||||
- "**/yarn.lock"
|
||||
- "**/package.json"
|
||||
- "**/third_party/**"
|
||||
- "**/tsconfig-build.json"
|
||||
- "**/tsconfig.json"
|
||||
- "**/BUILD.bazel"
|
||||
|
12
.vscode/extensions.json
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
// See http://go.microsoft.com/fwlink/?LinkId=827846 to learn about workspace recommendations.
|
||||
// Extension identifier format: ${publisher}.${name}. Example: vscode.csharp
|
||||
|
||||
// List of extensions which should be recommended for users of this workspace.
|
||||
"recommendations": [
|
||||
"devondcarew.bazel-code",
|
||||
"gkalpak.aio-docs-utils",
|
||||
"ms-vscode.vscode-typescript-tslint-plugin",
|
||||
"xaver.clang-format",
|
||||
],
|
||||
}
|
14
.vscode/settings.json
vendored
@ -1,15 +1,27 @@
|
||||
{
|
||||
"[javascript]": {
|
||||
"editor.formatOnSave": true,
|
||||
},
|
||||
"[typescript]": {
|
||||
"editor.formatOnSave": true,
|
||||
},
|
||||
// Please install https://marketplace.visualstudio.com/items?itemName=xaver.clang-format to take advantage of `clang-format` in VSCode.
|
||||
// (See https://clang.llvm.org/docs/ClangFormat.html for more info `clang-format`.)
|
||||
"clang-format.executable": "${workspaceRoot}/node_modules/.bin/clang-format",
|
||||
"files.watcherExclude": {
|
||||
"**/.git/objects/**": true,
|
||||
"**/.git/subtree-cache/**": true,
|
||||
"**/node_modules/**": true,
|
||||
"**/bazel-out/**": true,
|
||||
"**/dist/**": true,
|
||||
"**/aio/src/generated/**": true,
|
||||
},
|
||||
"search.exclude": {
|
||||
"**/node_modules": true,
|
||||
"**/bower_components": true,
|
||||
"**/bazel-out": true,
|
||||
"**/dist": true,
|
||||
"**/aio/src/generated": true,
|
||||
},
|
||||
}
|
||||
"git.ignoreLimitWarning": true,
|
||||
}
|
||||
|
46
BUILD.bazel
@ -1,7 +1,5 @@
|
||||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
load("@build_bazel_rules_nodejs//:defs.bzl", "node_modules_filegroup")
|
||||
|
||||
exports_files([
|
||||
"tsconfig.json",
|
||||
"LICENSE",
|
||||
@ -12,10 +10,10 @@ filegroup(
|
||||
name = "web_test_bootstrap_scripts",
|
||||
# do not sort
|
||||
srcs = [
|
||||
"@ngdeps//node_modules/reflect-metadata:Reflect.js",
|
||||
"@ngdeps//node_modules/zone.js:dist/zone.js",
|
||||
"@ngdeps//node_modules/zone.js:dist/zone-testing.js",
|
||||
"@ngdeps//node_modules/zone.js:dist/task-tracking.js",
|
||||
"@npm//node_modules/reflect-metadata:Reflect.js",
|
||||
"@npm//node_modules/zone.js:dist/zone.js",
|
||||
"@npm//node_modules/zone.js:dist/zone-testing.js",
|
||||
"@npm//node_modules/zone.js:dist/task-tracking.js",
|
||||
"//:test-events.js",
|
||||
],
|
||||
)
|
||||
@ -25,32 +23,14 @@ filegroup(
|
||||
srcs = [
|
||||
# We also declare the unminfied AngularJS files since these can be used for
|
||||
# local debugging (e.g. see: packages/upgrade/test/common/test_helpers.ts)
|
||||
"@ngdeps//node_modules/angular:angular.js",
|
||||
"@ngdeps//node_modules/angular:angular.min.js",
|
||||
"@ngdeps//node_modules/angular-1.5:angular.js",
|
||||
"@ngdeps//node_modules/angular-1.5:angular.min.js",
|
||||
"@ngdeps//node_modules/angular-1.6:angular.js",
|
||||
"@ngdeps//node_modules/angular-1.6:angular.min.js",
|
||||
"@ngdeps//node_modules/angular-mocks:angular-mocks.js",
|
||||
"@ngdeps//node_modules/angular-mocks-1.5:angular-mocks.js",
|
||||
"@ngdeps//node_modules/angular-mocks-1.6:angular-mocks.js",
|
||||
"@npm//node_modules/angular:angular.js",
|
||||
"@npm//node_modules/angular:angular.min.js",
|
||||
"@npm//node_modules/angular-1.5:angular.js",
|
||||
"@npm//node_modules/angular-1.5:angular.min.js",
|
||||
"@npm//node_modules/angular-1.6:angular.js",
|
||||
"@npm//node_modules/angular-1.6:angular.min.js",
|
||||
"@npm//node_modules/angular-mocks:angular-mocks.js",
|
||||
"@npm//node_modules/angular-mocks-1.5:angular-mocks.js",
|
||||
"@npm//node_modules/angular-mocks-1.6:angular-mocks.js",
|
||||
],
|
||||
)
|
||||
|
||||
load("@build_bazel_rules_nodejs//:defs.bzl", "nodejs_binary")
|
||||
|
||||
# A nodejs_binary for @angular/bazel/ngc-wrapped to use by default in
|
||||
# ng_module that depends on @npm//@angular/bazel instead of the
|
||||
# output of the //packages/bazel/src/ngc-wrapped ts_library rule. This
|
||||
# default is for downstream users that depend on the @angular/bazel npm
|
||||
# package. The generated @npm//@angular/bazel/ngc-wrapped target
|
||||
# does not work because it does not have the node `--expose-gc` flag
|
||||
# set which is required to support the call to `global.gc()`.
|
||||
nodejs_binary(
|
||||
name = "@angular/bazel/ngc-wrapped",
|
||||
configuration_env_vars = ["compile"],
|
||||
data = ["@npm//@angular/bazel"],
|
||||
entry_point = "@angular/bazel/src/ngc-wrapped/index.js",
|
||||
install_source_map_support = False,
|
||||
templated_args = ["--node_options=--expose-gc"],
|
||||
)
|
||||
|
50
CHANGELOG.md
@ -1,3 +1,53 @@
|
||||
<a name="7.2.10"></a>
|
||||
## [7.2.10](https://github.com/angular/angular/compare/7.2.9...7.2.10) (2019-03-20)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **compiler-cli:** incorrect metadata bundle for multiple unnamed re-exports ([#29360](https://github.com/angular/angular/issues/29360)) ([cf8d934](https://github.com/angular/angular/commit/cf8d934)), closes [/github.com/angular/material2/blob/master/tools/package-tools/build-release.ts#L78-L85](https://github.com//github.com/angular/material2/blob/master/tools/package-tools/build-release.ts/issues/L78-L85)
|
||||
|
||||
|
||||
|
||||
<a name="7.2.9"></a>
|
||||
## [7.2.9](https://github.com/angular/angular/compare/7.2.8...7.2.9) (2019-03-12)
|
||||
|
||||
This release contains various API docs improvements.
|
||||
|
||||
<a name="7.2.8"></a>
|
||||
## [7.2.8](https://github.com/angular/angular/compare/7.2.7...7.2.8) (2019-03-06)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **animations:** ensure `position` and `display` styles are handled outside of keyframes/web-animations ([#28911](https://github.com/angular/angular/issues/28911)) ([86981b3](https://github.com/angular/angular/commit/86981b3)), closes [#24923](https://github.com/angular/angular/issues/24923) [#25635](https://github.com/angular/angular/issues/25635)
|
||||
* **router:** removed obsolete TODO comment ([#29085](https://github.com/angular/angular/issues/29085)) ([2a25ac2](https://github.com/angular/angular/commit/2a25ac2))
|
||||
* **service-worker:** detect new version even if files are identical to an old one ([#26006](https://github.com/angular/angular/issues/26006)) ([5669333](https://github.com/angular/angular/commit/5669333)), closes [#24338](https://github.com/angular/angular/issues/24338)
|
||||
* **service-worker:** ignore passive mixed content requests ([#25994](https://github.com/angular/angular/issues/25994)) ([b598e88](https://github.com/angular/angular/commit/b598e88)), closes [/github.com/angular/angular/issues/23012#issuecomment-376430187](https://github.com//github.com/angular/angular/issues/23012/issues/issuecomment-376430187) [#23012](https://github.com/angular/angular/issues/23012)
|
||||
|
||||
|
||||
|
||||
<a name="7.2.7"></a>
|
||||
## [7.2.7](https://github.com/angular/angular/compare/7.2.6...7.2.7) (2019-02-27)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **bazel:** pin browser repositories using [@npm](https://github.com/npm)_bazel_karma//:browser_repositories.bzl in bazel schematics ([#28896](https://github.com/angular/angular/issues/28896)) ([b686449](https://github.com/angular/angular/commit/b686449))
|
||||
* **core:** traverse and sanitize content of unsafe elements ([#28804](https://github.com/angular/angular/issues/28804)) ([fdcf877](https://github.com/angular/angular/commit/fdcf877)), closes [#25879](https://github.com/angular/angular/issues/25879) [#25879](https://github.com/angular/angular/issues/25879) [#26007](https://github.com/angular/angular/issues/26007) [#28427](https://github.com/angular/angular/issues/28427)
|
||||
* **language-service:** Fix completions for input/output with alias ([#28904](https://github.com/angular/angular/issues/28904)) ([d0018e6](https://github.com/angular/angular/commit/d0018e6)), closes [#27959](https://github.com/angular/angular/issues/27959)
|
||||
|
||||
|
||||
|
||||
<a name="7.2.6"></a>
|
||||
## [7.2.6](https://github.com/angular/angular/compare/7.2.5...7.2.6) (2019-02-20)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **compiler-cli:** incorrect bundled metadata for static class member call expressions ([#28762](https://github.com/angular/angular/issues/28762)) ([ab69c31](https://github.com/angular/angular/commit/ab69c31)), closes [/github.com/angular/angular/blob/master/packages/core/src/change_detection/differs/keyvalue_differs.ts#L121](https://github.com//github.com/angular/angular/blob/master/packages/core/src/change_detection/differs/keyvalue_differs.ts/issues/L121) [#28741](https://github.com/angular/angular/issues/28741)
|
||||
|
||||
|
||||
|
||||
<a name="7.2.5"></a>
|
||||
## [7.2.5](https://github.com/angular/angular/compare/7.2.4...7.2.5) (2019-02-15)
|
||||
|
||||
|
73
WORKSPACE
@ -8,49 +8,41 @@ load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
|
||||
# path = "../rules_nodejs",
|
||||
#)
|
||||
#local_repository(
|
||||
# name = "build_bazel_rules_typescript",
|
||||
# name = "npm_bazel_typescript",
|
||||
# path = "../rules_typescript",
|
||||
#)
|
||||
|
||||
# Fetch rules_nodejs so we can install our npm dependencies
|
||||
http_archive(
|
||||
name = "build_bazel_rules_nodejs",
|
||||
sha256 = "1416d03823fed624b49a0abbd9979f7c63bbedfd37890ddecedd2fe25cccebc6",
|
||||
urls = ["https://github.com/bazelbuild/rules_nodejs/releases/download/0.18.6/rules_nodejs-0.18.6.tar.gz"],
|
||||
)
|
||||
|
||||
# Fetch the rxjs repository since we build rxjs from source
|
||||
# TODO(gregmagolan): use rxjs bundles in the bazel build
|
||||
http_archive(
|
||||
name = "rxjs",
|
||||
sha256 = "72b0b4e517f43358f554c125e40e39f67688cd2738a8998b4a266981ed32f403",
|
||||
strip_prefix = "package/src",
|
||||
url = "https://registry.yarnpkg.com/rxjs/-/rxjs-6.3.3.tgz",
|
||||
)
|
||||
|
||||
# Use a mock @npm repository while we are building angular from source
|
||||
# downstream. Angular will get its npm dependencies with in @ngdeps which
|
||||
# is setup in ng_setup_workspace().
|
||||
# TODO(gregmagolan): remove @ngdeps once angular is no longer build from source
|
||||
# downstream and have build use @npm for npm dependencies
|
||||
local_repository(
|
||||
name = "npm",
|
||||
path = "tools/npm_workspace",
|
||||
sha256 = "5c86b055c57e15bf32d9009a15bcd6d8e190c41b1ff2fb18037b75e0012e4e7c",
|
||||
urls = ["https://github.com/bazelbuild/rules_nodejs/releases/download/0.26.0/rules_nodejs-0.26.0.tar.gz"],
|
||||
)
|
||||
|
||||
# Check the bazel version and download npm dependencies
|
||||
load("@build_bazel_rules_nodejs//:defs.bzl", "check_bazel_version", "node_repositories")
|
||||
load("@build_bazel_rules_nodejs//:defs.bzl", "check_bazel_version", "node_repositories", "yarn_install")
|
||||
load("@build_bazel_rules_nodejs//:package.bzl", "check_rules_nodejs_version")
|
||||
|
||||
# Bazel version must be at least v0.21.0 because:
|
||||
# - 0.21.0 Using --incompatible_strict_action_env flag fixes cache when running `yarn bazel`
|
||||
# (see https://github.com/angular/angular/issues/27514#issuecomment-451438271)
|
||||
check_bazel_version("0.21.0", """
|
||||
check_bazel_version(
|
||||
message = """
|
||||
You no longer need to install Bazel on your machine.
|
||||
Angular has a dependency on the @bazel/bazel package which supplies it.
|
||||
Try running `yarn bazel` instead.
|
||||
(If you did run that, check that you've got a fresh `yarn install`)
|
||||
|
||||
""")
|
||||
""",
|
||||
minimum_bazel_version = "0.21.0",
|
||||
)
|
||||
|
||||
# The NodeJS rules version must be at least v0.15.3 because:
|
||||
# - 0.15.2 Re-introduced the prod_only attribute on yarn_install
|
||||
# - 0.15.3 Includes a fix for the `jasmine_node_test` rule ignoring target tags
|
||||
# - 0.16.8 Supports npm installed bazel workspaces
|
||||
# - 0.26.0 Fix for data files in yarn_install and npm_install
|
||||
check_rules_nodejs_version("0.26.0")
|
||||
|
||||
# Setup the Node.js toolchain
|
||||
node_repositories(
|
||||
@ -60,13 +52,28 @@ node_repositories(
|
||||
yarn_version = "1.12.1",
|
||||
)
|
||||
|
||||
# Setup the angular toolchain which installs npm dependencies into @ngdeps
|
||||
load("//tools:ng_setup_workspace.bzl", "ng_setup_workspace")
|
||||
yarn_install(
|
||||
name = "npm",
|
||||
data = [
|
||||
"//:tools/npm/@angular_bazel/index.js",
|
||||
"//:tools/npm/@angular_bazel/package.json",
|
||||
"//:tools/postinstall-patches.js",
|
||||
"//:tools/yarn/check-yarn.js",
|
||||
],
|
||||
package_json = "//:package.json",
|
||||
# Don't install devDependencies, they are large and not used under Bazel
|
||||
prod_only = True,
|
||||
yarn_lock = "//:yarn.lock",
|
||||
)
|
||||
|
||||
ng_setup_workspace()
|
||||
yarn_install(
|
||||
name = "ts-api-guardian_deps",
|
||||
package_json = "@angular//tools/ts-api-guardian:package.json",
|
||||
yarn_lock = "@angular//tools/ts-api-guardian:yarn.lock",
|
||||
)
|
||||
|
||||
# Install all bazel dependencies of the @ngdeps npm packages
|
||||
load("@ngdeps//:install_bazel_dependencies.bzl", "install_bazel_dependencies")
|
||||
# Install all bazel dependencies of the @npm npm packages
|
||||
load("@npm//:install_bazel_dependencies.bzl", "install_bazel_dependencies")
|
||||
|
||||
install_bazel_dependencies()
|
||||
|
||||
@ -76,7 +83,7 @@ load("//packages/bazel:package.bzl", "rules_angular_dev_dependencies")
|
||||
rules_angular_dev_dependencies()
|
||||
|
||||
# Load karma dependencies
|
||||
load("@build_bazel_rules_karma//:package.bzl", "rules_karma_dependencies")
|
||||
load("@npm_bazel_karma//:package.bzl", "rules_karma_dependencies")
|
||||
|
||||
rules_karma_dependencies()
|
||||
|
||||
@ -87,12 +94,12 @@ web_test_repositories()
|
||||
|
||||
# Temporary work-around for https://github.com/angular/angular/issues/28681
|
||||
# TODO(gregmagolan): go back to @io_bazel_rules_webtesting browser_repositories
|
||||
load("@angular//:browser_repositories.bzl", "browser_repositories")
|
||||
load("@npm_bazel_karma//:browser_repositories.bzl", "browser_repositories")
|
||||
|
||||
browser_repositories()
|
||||
|
||||
# Setup the rules_typescript tooolchain
|
||||
load("@build_bazel_rules_typescript//:defs.bzl", "ts_setup_workspace")
|
||||
load("@npm_bazel_typescript//:index.bzl", "ts_setup_workspace")
|
||||
|
||||
ts_setup_workspace()
|
||||
|
||||
|
2
aio/.gitignore
vendored
@ -26,11 +26,13 @@
|
||||
!.vscode/extensions.json
|
||||
|
||||
# misc
|
||||
/.firebase/
|
||||
/.sass-cache
|
||||
/connect.lock
|
||||
/coverage
|
||||
/libpeerconnection.log
|
||||
debug.log
|
||||
firebase-debug.log
|
||||
npm-debug.log
|
||||
testem.log
|
||||
/typings
|
||||
|
@ -26,8 +26,8 @@ ARG AIO_GITHUB_ORGANIZATION=angular
|
||||
ARG TEST_AIO_GITHUB_ORGANIZATION=test-org
|
||||
ARG AIO_GITHUB_REPO=angular
|
||||
ARG TEST_AIO_GITHUB_REPO=test-repo
|
||||
ARG AIO_GITHUB_TEAM_SLUGS=aio-contributors
|
||||
ARG TEST_AIO_GITHUB_TEAM_SLUGS=aio-contributors
|
||||
ARG AIO_GITHUB_TEAM_SLUGS=aio-auto-previews,aio-contributors
|
||||
ARG TEST_AIO_GITHUB_TEAM_SLUGS=test-team-1,test-team-2
|
||||
ARG AIO_NGINX_HOSTNAME=$AIO_DOMAIN_NAME
|
||||
ARG TEST_AIO_NGINX_HOSTNAME=$TEST_AIO_DOMAIN_NAME
|
||||
ARG AIO_NGINX_PORT_HTTP=80
|
||||
|
@ -2,7 +2,7 @@
|
||||
<h1>My First Attribute Directive</h1>
|
||||
<!-- #docregion applied -->
|
||||
<p appHighlight>Highlight me!</p>
|
||||
<!-- #enddocregion applied, -->
|
||||
<!-- #enddocregion applied -->
|
||||
|
||||
<!-- #docregion color-1 -->
|
||||
<p appHighlight highlightColor="yellow">Highlighted in yellow</p>
|
||||
@ -11,4 +11,4 @@
|
||||
|
||||
<!-- #docregion color-2 -->
|
||||
<p appHighlight [highlightColor]="color">Highlighted with parent component's color</p>
|
||||
<!-- #enddocregion color-2 -->
|
||||
<!-- #enddocregion color-2 -->
|
@ -0,0 +1,3 @@
|
||||
<!-- #docregion unsupported -->
|
||||
<p app:Highlight>This is invalid</p>
|
||||
<!-- #enddocregion unsupported -->
|
@ -0,0 +1,20 @@
|
||||
'use strict'; // necessary for es6 output in node
|
||||
|
||||
import { browser, element, by } from 'protractor';
|
||||
|
||||
describe('Built Template Functions Example', function () {
|
||||
beforeAll(function () {
|
||||
browser.get('');
|
||||
});
|
||||
|
||||
it('should have title Built-in Template Functions', function () {
|
||||
let title = element.all(by.css('h1')).get(0);
|
||||
expect(title.getText()).toEqual('Built-in Template Functions');
|
||||
});
|
||||
|
||||
it('should display $any( ) in h2', function () {
|
||||
let header = element(by.css('h2'));
|
||||
expect(header.getText()).toContain('$any( )');
|
||||
});
|
||||
|
||||
});
|
@ -0,0 +1,15 @@
|
||||
<h1>{{title}}</h1>
|
||||
|
||||
<h2><code>$any( )</code> type cast function and an undeclared member</h2>
|
||||
|
||||
<p>There is no such member as <code>bestByDate</code> in the following two examples, so nothing renders:</p>
|
||||
<!-- #docregion any-type-cast-function-1 -->
|
||||
<p>The item's undeclared best by date is: {{$any(item).bestByDate}}</p>
|
||||
<!-- #enddocregion any-type-cast-function-1 -->
|
||||
|
||||
<h2>using <code>this</code></h2>
|
||||
<!-- #docregion any-type-cast-function-2 -->
|
||||
<p>The item's undeclared best by date is: {{$any(this).bestByDate}}</p>
|
||||
<!-- #enddocregion any-type-cast-function-2 -->
|
||||
|
||||
|
@ -0,0 +1,16 @@
|
||||
import { TestBed, async } from '@angular/core/testing';
|
||||
import { AppComponent } from './app.component';
|
||||
describe('AppComponent', () => {
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [
|
||||
AppComponent
|
||||
],
|
||||
}).compileComponents();
|
||||
}));
|
||||
it('should create the app', async(() => {
|
||||
const fixture = TestBed.createComponent(AppComponent);
|
||||
const app = fixture.debugElement.componentInstance;
|
||||
expect(app).toBeTruthy();
|
||||
}));
|
||||
});
|
@ -0,0 +1,16 @@
|
||||
import { Component } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'app-root',
|
||||
templateUrl: './app.component.html',
|
||||
styleUrls: ['./app.component.css']
|
||||
})
|
||||
export class AppComponent {
|
||||
title = 'Built-in Template Functions';
|
||||
|
||||
item = {
|
||||
name : 'Telephone',
|
||||
origin : 'Sweden',
|
||||
price: 98
|
||||
};
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
import { BrowserModule } from '@angular/platform-browser';
|
||||
import { NgModule } from '@angular/core';
|
||||
|
||||
|
||||
import { AppComponent } from './app.component';
|
||||
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
AppComponent
|
||||
],
|
||||
imports: [
|
||||
BrowserModule
|
||||
],
|
||||
providers: [],
|
||||
bootstrap: [AppComponent]
|
||||
})
|
||||
export class AppModule { }
|
@ -0,0 +1,14 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Built-in Template Functions 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/built-in-template-functions/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);
|
@ -0,0 +1,10 @@
|
||||
{
|
||||
"description": "Built-in Template Functions",
|
||||
"files": [
|
||||
"!**/*.d.ts",
|
||||
"!**/*.js",
|
||||
"!**/*.[1,2].*"
|
||||
],
|
||||
"file": "src/app/app.component.ts",
|
||||
"tags": ["Built-in Template Functions"]
|
||||
}
|
@ -4,7 +4,8 @@
|
||||
"cmd": "yarn",
|
||||
"args": [
|
||||
"e2e",
|
||||
"--no-webdriver-update"
|
||||
"--no-webdriver-update",
|
||||
"--port={PORT}"
|
||||
]
|
||||
}
|
||||
]
|
||||
|
@ -4,7 +4,8 @@
|
||||
"cmd": "yarn",
|
||||
"args": [
|
||||
"e2e",
|
||||
"--no-webdriver-update"
|
||||
"--no-webdriver-update",
|
||||
"--port={PORT}"
|
||||
]
|
||||
}
|
||||
]
|
||||
|
71
aio/content/examples/event-binding/e2e/src/app.e2e-spec.ts
Normal file
@ -0,0 +1,71 @@
|
||||
'use strict'; // necessary for es6 output in node
|
||||
|
||||
import { browser, element, by, protractor } from 'protractor';
|
||||
|
||||
describe('Event binding example', function () {
|
||||
|
||||
beforeEach(function () {
|
||||
browser.get('');
|
||||
});
|
||||
|
||||
let saveButton = element.all(by.css('button')).get(0);
|
||||
let onSaveButton = element.all(by.css('button')).get(1);
|
||||
let myClick = element.all(by.css('button')).get(2);
|
||||
let deleteButton = element.all(by.css('button')).get(3);
|
||||
let saveNoProp = element.all(by.css('button')).get(4);
|
||||
let saveProp = element.all(by.css('button')).get(5);
|
||||
|
||||
|
||||
it('should display Event Binding with Angular', function () {
|
||||
expect(element(by.css('h1')).getText()).toEqual('Event Binding');
|
||||
});
|
||||
|
||||
it('should display 6 buttons', function() {
|
||||
expect(saveButton.getText()).toBe('Save');
|
||||
expect(onSaveButton.getText()).toBe('on-click Save');
|
||||
expect(myClick.getText()).toBe('click with myClick');
|
||||
expect(deleteButton.getText()).toBe('Delete');
|
||||
expect(saveNoProp.getText()).toBe('Save, no propagation');
|
||||
expect(saveProp.getText()).toBe('Save with propagation');
|
||||
});
|
||||
|
||||
it('should support user input', function () {
|
||||
let input = element(by.css('input'));
|
||||
let bindingResult = element.all(by.css('h4')).get(1);
|
||||
expect(bindingResult.getText()).toEqual('Result: teapot');
|
||||
input.sendKeys('abc');
|
||||
expect(bindingResult.getText()).toEqual('Result: teapotabc');
|
||||
});
|
||||
|
||||
it('should hide the item img', async () => {
|
||||
let deleteButton = element.all(by.css('button')).get(3);
|
||||
await deleteButton.click();
|
||||
browser.switchTo().alert().accept();
|
||||
expect(element.all(by.css('img')).get(0).getCssValue('display')).toEqual('none');
|
||||
});
|
||||
|
||||
it('should show two alerts', async () => {
|
||||
let parentDiv = element.all(by.css('.parent-div'));
|
||||
let childDiv = element.all(by.css('div > div')).get(1);
|
||||
await parentDiv.click();
|
||||
browser.switchTo().alert().accept();
|
||||
expect(childDiv.getText()).toEqual('Click me too! (child)');
|
||||
await childDiv.click();
|
||||
expect(browser.switchTo().alert().getText()).toEqual('Click me. Event target class is child-div');
|
||||
browser.switchTo().alert().accept();
|
||||
});
|
||||
|
||||
it('should show 1 alert from Save, no prop, button', async () => {
|
||||
await saveNoProp.click();
|
||||
expect(browser.switchTo().alert().getText()).toEqual('Saved. Event target is Save, no propagation');
|
||||
browser.switchTo().alert().accept();
|
||||
});
|
||||
|
||||
it('should show 2 alerts from Save w/prop button', async () => {
|
||||
await saveProp.click();
|
||||
expect(browser.switchTo().alert().getText()).toEqual('Saved.');
|
||||
browser.switchTo().alert().accept();
|
||||
expect(browser.switchTo().alert().getText()).toEqual('Saved.');
|
||||
browser.switchTo().alert().accept();
|
||||
});
|
||||
});
|
25
aio/content/examples/event-binding/src/app/app.component.css
Normal file
@ -0,0 +1,25 @@
|
||||
.group {
|
||||
background-color: #dae8f9;
|
||||
padding: 1rem;
|
||||
margin: 1rem 0;
|
||||
}
|
||||
|
||||
.parent-div {
|
||||
background-color: #bdd1f7;
|
||||
border: solid 1px rgb(25, 118, 210);
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.parent-div:hover {
|
||||
background-color: #8fb4f9;
|
||||
}
|
||||
|
||||
.child-div {
|
||||
margin-top: 1rem;
|
||||
background-color: #fff;
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.child-div:hover {
|
||||
background-color: #eee;
|
||||
}
|
@ -0,0 +1,53 @@
|
||||
<h1 id="event-binding">Event Binding</h1>
|
||||
|
||||
<div class="group">
|
||||
<h3>Target event</h3>
|
||||
<!-- #docregion event-binding-1 -->
|
||||
<button (click)="onSave($event)">Save</button>
|
||||
<!-- #enddocregion event-binding-1 -->
|
||||
|
||||
<!-- #docregion event-binding-2 -->
|
||||
<button on-click="onSave($event)">on-click Save</button>
|
||||
<!-- #enddocregion event-binding-2 -->
|
||||
|
||||
<!-- #docregion custom-directive -->
|
||||
<h4>myClick is an event on the custom ClickDirective:</h4>
|
||||
<button (myClick)="clickMessage=$event" clickable>click with myClick</button>
|
||||
{{clickMessage}}
|
||||
<!-- #enddocregion custom-directive -->
|
||||
|
||||
</div>
|
||||
|
||||
<div class="group">
|
||||
<h3>$event and event handling statements</h3>
|
||||
<h4>Result: {{currentItem.name}}</h4>
|
||||
|
||||
<!-- #docregion event-binding-3-->
|
||||
<input [value]="currentItem.name"
|
||||
(input)="currentItem.name=$event.target.value" >
|
||||
without NgModel
|
||||
<!-- #enddocregion event-binding-3-->
|
||||
</div>
|
||||
|
||||
<div class="group">
|
||||
<h3>Binding to a nested component</h3>
|
||||
<h4>Custom events with EventEmitter</h4>
|
||||
<!-- #docregion event-binding-to-component -->
|
||||
<app-item-detail (deleteRequest)="deleteItem($event)" [item]="currentItem"></app-item-detail>
|
||||
<!-- #enddocregion event-binding-to-component -->
|
||||
|
||||
|
||||
<h4>Click to see event target class:</h4>
|
||||
<div class="parent-div" (click)="onClickMe($event)" clickable>Click me (parent)
|
||||
<div class="child-div">Click me too! (child) </div>
|
||||
</div>
|
||||
|
||||
<h3>Saves only once:</h3>
|
||||
<div (click)="onSave()" clickable>
|
||||
<button (click)="onSave($event)">Save, no propagation</button>
|
||||
</div>
|
||||
|
||||
<h3>Saves twice:</h3>
|
||||
<div (click)="onSave()" clickable>
|
||||
<button (click)="onSave()">Save with propagation</button>
|
||||
</div>
|
@ -0,0 +1,27 @@
|
||||
import { TestBed, async } from '@angular/core/testing';
|
||||
import { AppComponent } from './app.component';
|
||||
describe('AppComponent', () => {
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [
|
||||
AppComponent
|
||||
],
|
||||
}).compileComponents();
|
||||
}));
|
||||
it('should create the app', async(() => {
|
||||
const fixture = TestBed.createComponent(AppComponent);
|
||||
const app = fixture.debugElement.componentInstance;
|
||||
expect(app).toBeTruthy();
|
||||
}));
|
||||
it(`should have as title 'Featured product:'`, async(() => {
|
||||
const fixture = TestBed.createComponent(AppComponent);
|
||||
const app = fixture.debugElement.componentInstance;
|
||||
expect(app.title).toEqual('Featured product:');
|
||||
}));
|
||||
it('should render title in a p tag', async(() => {
|
||||
const fixture = TestBed.createComponent(AppComponent);
|
||||
fixture.detectChanges();
|
||||
const compiled = fixture.debugElement.nativeElement;
|
||||
expect(compiled.querySelector('p').textContent).toContain('Featured product:');
|
||||
}));
|
||||
});
|
29
aio/content/examples/event-binding/src/app/app.component.ts
Normal file
@ -0,0 +1,29 @@
|
||||
import { Component } from '@angular/core';
|
||||
import { Item } from './item';
|
||||
|
||||
@Component({
|
||||
selector: 'app-root',
|
||||
templateUrl: './app.component.html',
|
||||
styleUrls: ['./app.component.css']
|
||||
})
|
||||
export class AppComponent {
|
||||
|
||||
currentItem = { name: 'teapot'} ;
|
||||
clickMessage = '';
|
||||
|
||||
onSave(event?: KeyboardEvent) {
|
||||
const evtMsg = event ? ' Event target is ' + (<HTMLElement>event.target).textContent : '';
|
||||
alert('Saved.' + evtMsg);
|
||||
if (event) { event.stopPropagation(); }
|
||||
}
|
||||
|
||||
deleteItem(item?: Item) {
|
||||
alert(`Delete the ${item}.`);
|
||||
}
|
||||
|
||||
onClickMe(event?: KeyboardEvent) {
|
||||
const evtMsg = event ? ' Event target class is ' + (<HTMLElement>event.target).className : '';
|
||||
alert('Click me.' + evtMsg);
|
||||
}
|
||||
|
||||
}
|
22
aio/content/examples/event-binding/src/app/app.module.ts
Normal file
@ -0,0 +1,22 @@
|
||||
import { BrowserModule } from '@angular/platform-browser';
|
||||
import { NgModule } from '@angular/core';
|
||||
|
||||
|
||||
import { AppComponent } from './app.component';
|
||||
import { ItemDetailComponent } from './item-detail/item-detail.component';
|
||||
import { ClickDirective } from './click.directive';
|
||||
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
AppComponent,
|
||||
ItemDetailComponent,
|
||||
ClickDirective
|
||||
],
|
||||
imports: [
|
||||
BrowserModule
|
||||
],
|
||||
providers: [],
|
||||
bootstrap: [AppComponent]
|
||||
})
|
||||
export class AppModule { }
|
@ -0,0 +1,18 @@
|
||||
/* tslint:disable use-output-property-decorator directive-class-suffix */
|
||||
import { Directive, ElementRef, EventEmitter, Output } from '@angular/core';
|
||||
|
||||
@Directive({selector: '[myClick]'})
|
||||
export class ClickDirective {
|
||||
@Output('myClick') clicks = new EventEmitter<string>(); // @Output(alias) propertyName = ...
|
||||
|
||||
toggle = false;
|
||||
|
||||
constructor(el: ElementRef) {
|
||||
el.nativeElement
|
||||
.addEventListener('click', (event: Event) => {
|
||||
this.toggle = !this.toggle;
|
||||
this.clicks.emit(this.toggle ? 'Click!' : '');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,11 @@
|
||||
.detail {
|
||||
border: 1px solid rgb(25, 118, 210);
|
||||
padding: 1rem;
|
||||
margin: 1rem 0;
|
||||
}
|
||||
|
||||
img {
|
||||
max-width: 100px;
|
||||
display: block;
|
||||
padding: 1rem 0;
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
<div class="detail">
|
||||
<p>This is the ItemDetailComponent</p>
|
||||
<!-- #docregion line-through -->
|
||||
<img src="{{itemImageUrl}}" [style.display]="displayNone">
|
||||
<span [style.text-decoration]="lineThrough">{{ item.name }}
|
||||
</span>
|
||||
<button (click)="delete()">Delete</button>
|
||||
<!-- #enddocregion line-through -->
|
||||
</div>
|
@ -0,0 +1,25 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { ItemDetailComponent } from './item-detail.component';
|
||||
|
||||
describe('ItemDetailComponent', () => {
|
||||
let component: ItemDetailComponent;
|
||||
let fixture: ComponentFixture<ItemDetailComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ ItemDetailComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(ItemDetailComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
@ -0,0 +1,30 @@
|
||||
/* tslint:disable use-input-property-decorator use-output-property-decorator */
|
||||
import { Component, EventEmitter, Input, Output } from '@angular/core';
|
||||
|
||||
import { Item } from '../item';
|
||||
|
||||
@Component({
|
||||
selector: 'app-item-detail',
|
||||
styleUrls: ['./item-detail.component.css'],
|
||||
templateUrl: './item-detail.component.html'
|
||||
})
|
||||
export class ItemDetailComponent {
|
||||
|
||||
@Input() item;
|
||||
itemImageUrl = 'assets/teapot.svg';
|
||||
lineThrough = '';
|
||||
displayNone = '';
|
||||
@Input() prefix = '';
|
||||
|
||||
// #docregion deleteRequest
|
||||
// This component makes a request but it can't actually delete a hero.
|
||||
@Output() deleteRequest = new EventEmitter<Item>();
|
||||
|
||||
delete() {
|
||||
this.deleteRequest.emit(this.item.name);
|
||||
this.displayNone = this.displayNone ? '' : 'none';
|
||||
this.lineThrough = this.lineThrough ? '' : 'line-through';
|
||||
}
|
||||
// #enddocregion deleteRequest
|
||||
|
||||
}
|
4
aio/content/examples/event-binding/src/app/item.ts
Normal file
@ -0,0 +1,4 @@
|
||||
export class Item {
|
||||
name: '';
|
||||
}
|
||||
|
1
aio/content/examples/event-binding/src/assets/teapot.svg
Normal file
After Width: | Height: | Size: 23 KiB |
14
aio/content/examples/event-binding/src/index.html
Normal file
@ -0,0 +1,14 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>EventBinding</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></app-root>
|
||||
</body>
|
||||
</html>
|
12
aio/content/examples/event-binding/src/main.ts
Normal file
@ -0,0 +1,12 @@
|
||||
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)
|
||||
.catch(err => console.log(err));
|
10
aio/content/examples/event-binding/stackblitz.json
Normal file
@ -0,0 +1,10 @@
|
||||
{
|
||||
"description": "Event Binding",
|
||||
"files": [
|
||||
"!**/*.d.ts",
|
||||
"!**/*.js",
|
||||
"!**/*.[1,2].*"
|
||||
],
|
||||
"file": "src/app/app.component.ts",
|
||||
"tags": ["Event Binding"]
|
||||
}
|
@ -5,7 +5,8 @@
|
||||
"cmd": "yarn",
|
||||
"args": [
|
||||
"e2e",
|
||||
"--no-webdriver-update"
|
||||
"--no-webdriver-update",
|
||||
"--port={PORT}"
|
||||
]
|
||||
}
|
||||
]
|
||||
|
@ -50,9 +50,10 @@
|
||||
|
||||
<div (keyup)="0">
|
||||
<h4>Template context: template reference variables (#customerInput):</h4>
|
||||
<label>Type something:
|
||||
<!-- #docregion template-reference-variable -->
|
||||
<input #customerInput>{{customerInput.value}}</label>
|
||||
<label>Type something:
|
||||
<input #customerInput>{{customerInput.value}}
|
||||
</label>
|
||||
<!-- #enddocregion template-reference-variable -->
|
||||
</div>
|
||||
|
||||
|
@ -13,15 +13,4 @@ describe('AppComponent', () => {
|
||||
const app = fixture.debugElement.componentInstance;
|
||||
expect(app).toBeTruthy();
|
||||
}));
|
||||
it(`should have as title 'Featured product:'`, async(() => {
|
||||
const fixture = TestBed.createComponent(AppComponent);
|
||||
const app = fixture.debugElement.componentInstance;
|
||||
expect(app.title).toEqual('Featured product:');
|
||||
}));
|
||||
it('should render title in a p tag', async(() => {
|
||||
const fixture = TestBed.createComponent(AppComponent);
|
||||
fixture.detectChanges();
|
||||
const compiled = fixture.debugElement.nativeElement;
|
||||
expect(compiled.querySelector('p').textContent).toContain('Featured product:');
|
||||
}));
|
||||
});
|
||||
|
@ -1,30 +0,0 @@
|
||||
import { Component } from '@angular/core';
|
||||
|
||||
|
||||
@Component({
|
||||
selector: 'app-root',
|
||||
templateUrl: './app.component.html',
|
||||
styleUrls: ['./app.component.css']
|
||||
})
|
||||
export class AppComponent {
|
||||
itemImageUrl = '../assets/lamp.png';
|
||||
isUnchanged = true;
|
||||
classes = 'special';
|
||||
// #docregion parent-data-type
|
||||
parentItem = 'bananas';
|
||||
// #enddocregion parent-data-type
|
||||
|
||||
// #docregion pass-object
|
||||
currentItem = [{
|
||||
id: 21,
|
||||
name: 'peaches'
|
||||
}];
|
||||
// #enddocregion pass-object
|
||||
|
||||
interpolationTitle = 'Interpolation';
|
||||
propertyTitle = 'Property binding';
|
||||
|
||||
// #docregion malicious-content
|
||||
evilTitle = 'Template <script>alert("evil never sleeps")</script>Syntax';
|
||||
// #enddocregion malicious-content
|
||||
}
|
@ -31,6 +31,7 @@ describe('Router', () => {
|
||||
heroDetailTitle: heroDetail.element(by.xpath('*[2]')),
|
||||
|
||||
adminHref: hrefEles.get(2),
|
||||
adminPage: element(by.css('app-root > div > app-admin')),
|
||||
adminPreloadList: element.all(by.css('app-root > div > app-admin > app-admin-dashboard > ul > li')),
|
||||
|
||||
loginHref: hrefEles.get(3),
|
||||
@ -138,6 +139,31 @@ describe('Router', () => {
|
||||
expect(page.secondaryOutlet.count()).toBe(1, 'secondary outlet');
|
||||
});
|
||||
|
||||
it('should redirect with secondary route', async () => {
|
||||
const page = getPageStruct();
|
||||
|
||||
// go to login page and login
|
||||
await browser.get('');
|
||||
await page.loginHref.click();
|
||||
await page.loginButton.click();
|
||||
|
||||
// open secondary outlet
|
||||
await page.contactHref.click();
|
||||
|
||||
// go to login page and logout
|
||||
await page.loginHref.click();
|
||||
await page.loginButton.click();
|
||||
|
||||
// attempt to go to admin page, redirects to login with secondary outlet open
|
||||
await page.adminHref.click();
|
||||
|
||||
// login, get redirected back to admin with outlet still open
|
||||
await page.loginButton.click();
|
||||
|
||||
expect(await page.adminPage.isDisplayed()).toBeTruthy();
|
||||
expect(page.secondaryOutlet.count()).toBeTruthy();
|
||||
});
|
||||
|
||||
async function crisisCenterEdit(index: number, save: boolean) {
|
||||
const page = getPageStruct();
|
||||
await page.crisisHref.click();
|
||||
|
@ -27,10 +27,10 @@ export class LoginComponent {
|
||||
if (this.authService.isLoggedIn) {
|
||||
// Get the redirect URL from our auth service
|
||||
// If no redirect has been set, use the default
|
||||
let redirect = this.authService.redirectUrl ? this.authService.redirectUrl : '/crisis-center/admin';
|
||||
let redirect = this.authService.redirectUrl ? this.router.parseUrl(this.authService.redirectUrl) : '/admin';
|
||||
|
||||
// Redirect the user
|
||||
this.router.navigate([redirect]);
|
||||
this.router.navigateByUrl(redirect);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -28,7 +28,7 @@ export class LoginComponent {
|
||||
if (this.authService.isLoggedIn) {
|
||||
// Get the redirect URL from our auth service
|
||||
// If no redirect has been set, use the default
|
||||
let redirect = this.authService.redirectUrl ? this.authService.redirectUrl : '/admin';
|
||||
let redirect = this.authService.redirectUrl ? this.router.parseUrl(this.authService.redirectUrl) : '/admin';
|
||||
|
||||
// #docregion preserve
|
||||
// Set our navigation extras object
|
||||
@ -39,7 +39,7 @@ export class LoginComponent {
|
||||
};
|
||||
|
||||
// Redirect the user
|
||||
this.router.navigate([redirect], navigationExtras);
|
||||
this.router.navigateByUrl(redirect, navigationExtras);
|
||||
// #enddocregion preserve
|
||||
}
|
||||
});
|
||||
|
@ -1,7 +1,7 @@
|
||||
{
|
||||
"projectType": "service-worker",
|
||||
"e2e": [
|
||||
{"cmd": "yarn", "args": ["e2e", "--no-webdriver-update"]},
|
||||
{"cmd": "yarn", "args": ["e2e", "--no-webdriver-update", "--port={PORT}"]},
|
||||
{"cmd": "yarn", "args": ["build", "--prod"]},
|
||||
{"cmd": "node", "args": ["--eval", "assert(fs.existsSync('./dist/ngsw.json'), 'ngsw.json is missing')"]},
|
||||
{"cmd": "node", "args": ["--eval", "assert(fs.existsSync('./dist/ngsw-worker.js'), 'ngsw-worker.js is missing')"]},
|
||||
|
@ -152,7 +152,7 @@
|
||||
<div [ngSwitch]="hero?.emotion">
|
||||
<app-happy-hero *ngSwitchCase="'happy'" [hero]="hero"></app-happy-hero>
|
||||
<app-sad-hero *ngSwitchCase="'sad'" [hero]="hero"></app-sad-hero>
|
||||
<app-confused-hero *ngSwitchCase="'app-confused'" [hero]="hero"></app-confused-hero>
|
||||
<app-confused-hero *ngSwitchCase="'confused'" [hero]="hero"></app-confused-hero>
|
||||
<app-unknown-hero *ngSwitchDefault [hero]="hero"></app-unknown-hero>
|
||||
</div>
|
||||
<!-- #enddocregion built-in, ngswitch -->
|
||||
|
@ -32,8 +32,6 @@
|
||||
height: 56px;
|
||||
padding: 0 16px 0 72px;
|
||||
padding-left: 8px;
|
||||
background-color: #673AB7;
|
||||
background: #0033FF;
|
||||
background-color: #222;
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,5 @@
|
||||
/*#docregion*/
|
||||
.spinner {
|
||||
position: absolute;
|
||||
left: 7em;
|
||||
top: 20em;
|
||||
position: absolute;
|
||||
|
@ -32,8 +32,6 @@
|
||||
height: 56px;
|
||||
padding: 0 16px 0 72px;
|
||||
padding-left: 8px;
|
||||
background-color: #673AB7;
|
||||
background: #0033FF;
|
||||
background-color: #222;
|
||||
}
|
||||
|
||||
|
@ -4,86 +4,103 @@ import { interval, of } from 'rxjs';
|
||||
import { delay, take } from 'rxjs/operators';
|
||||
|
||||
describe('Angular async helper', () => {
|
||||
let actuallyDone = false;
|
||||
|
||||
beforeEach(() => { actuallyDone = false; });
|
||||
describe('async', () => {
|
||||
let actuallyDone = false;
|
||||
|
||||
afterEach(() => { expect(actuallyDone).toBe(true, 'actuallyDone should be true'); });
|
||||
beforeEach(() => { actuallyDone = false; });
|
||||
|
||||
it('should run normal test', () => { actuallyDone = true; });
|
||||
afterEach(() => { expect(actuallyDone).toBe(true, 'actuallyDone should be true'); });
|
||||
|
||||
it('should run normal async test', (done: DoneFn) => {
|
||||
setTimeout(() => {
|
||||
actuallyDone = true;
|
||||
done();
|
||||
}, 0);
|
||||
it('should run normal test', () => { actuallyDone = true; });
|
||||
|
||||
it('should run normal async test', (done: DoneFn) => {
|
||||
setTimeout(() => {
|
||||
actuallyDone = true;
|
||||
done();
|
||||
}, 0);
|
||||
});
|
||||
|
||||
it('should run async test with task',
|
||||
async(() => { setTimeout(() => { actuallyDone = true; }, 0); }));
|
||||
|
||||
it('should run async test with task', async(() => {
|
||||
const id = setInterval(() => {
|
||||
actuallyDone = true;
|
||||
clearInterval(id);
|
||||
}, 100);
|
||||
}));
|
||||
|
||||
it('should run async test with successful promise', async(() => {
|
||||
const p = new Promise(resolve => { setTimeout(resolve, 10); });
|
||||
p.then(() => { actuallyDone = true; });
|
||||
}));
|
||||
|
||||
it('should run async test with failed promise', async(() => {
|
||||
const p = new Promise((resolve, reject) => { setTimeout(reject, 10); });
|
||||
p.catch(() => { actuallyDone = true; });
|
||||
}));
|
||||
|
||||
// Use done. Can also use async or fakeAsync.
|
||||
it('should run async test with successful delayed Observable', (done: DoneFn) => {
|
||||
const source = of (true).pipe(delay(10));
|
||||
source.subscribe(val => actuallyDone = true, err => fail(err), done);
|
||||
});
|
||||
|
||||
it('should run async test with successful delayed Observable', async(() => {
|
||||
const source = of (true).pipe(delay(10));
|
||||
source.subscribe(val => actuallyDone = true, err => fail(err));
|
||||
}));
|
||||
|
||||
it('should run async test with successful delayed Observable', fakeAsync(() => {
|
||||
const source = of (true).pipe(delay(10));
|
||||
source.subscribe(val => actuallyDone = true, err => fail(err));
|
||||
|
||||
tick(10);
|
||||
}));
|
||||
});
|
||||
|
||||
it('should run async test with task',
|
||||
async(() => { setTimeout(() => { actuallyDone = true; }, 0); }));
|
||||
describe('fakeAsync', () => {
|
||||
// #docregion fake-async-test-tick
|
||||
it('should run timeout callback with delay after call tick with millis', fakeAsync(() => {
|
||||
let called = false;
|
||||
setTimeout(() => { called = true; }, 100);
|
||||
tick(100);
|
||||
expect(called).toBe(true);
|
||||
}));
|
||||
// #enddocregion fake-async-test-tick
|
||||
|
||||
it('should run async test with task', async(() => {
|
||||
const id = setInterval(() => {
|
||||
actuallyDone = true;
|
||||
clearInterval(id);
|
||||
}, 100);
|
||||
}));
|
||||
// #docregion fake-async-test-date
|
||||
it('should get Date diff correctly in fakeAsync', fakeAsync(() => {
|
||||
const start = Date.now();
|
||||
tick(100);
|
||||
const end = Date.now();
|
||||
expect(end - start).toBe(100);
|
||||
}));
|
||||
// #enddocregion fake-async-test-date
|
||||
|
||||
it('should run async test with successful promise', async(() => {
|
||||
const p = new Promise(resolve => { setTimeout(resolve, 10); });
|
||||
p.then(() => { actuallyDone = true; });
|
||||
}));
|
||||
// #docregion fake-async-test-rxjs
|
||||
it('should get Date diff correctly in fakeAsync with rxjs scheduler', fakeAsync(() => {
|
||||
// need to add `import 'zone.js/dist/zone-patch-rxjs-fake-async'
|
||||
// to patch rxjs scheduler
|
||||
let result = null;
|
||||
of ('hello').pipe(delay(1000)).subscribe(v => { result = v; });
|
||||
expect(result).toBeNull();
|
||||
tick(1000);
|
||||
expect(result).toBe('hello');
|
||||
|
||||
it('should run async test with failed promise', async(() => {
|
||||
const p = new Promise((resolve, reject) => { setTimeout(reject, 10); });
|
||||
p.catch(() => { actuallyDone = true; });
|
||||
}));
|
||||
const start = new Date().getTime();
|
||||
let dateDiff = 0;
|
||||
interval(1000).pipe(take(2)).subscribe(() => dateDiff = (new Date().getTime() - start));
|
||||
|
||||
// Use done. Can also use async or fakeAsync.
|
||||
it('should run async test with successful delayed Observable', (done: DoneFn) => {
|
||||
const source = of (true).pipe(delay(10));
|
||||
source.subscribe(val => actuallyDone = true, err => fail(err), done);
|
||||
tick(1000);
|
||||
expect(dateDiff).toBe(1000);
|
||||
tick(1000);
|
||||
expect(dateDiff).toBe(2000);
|
||||
}));
|
||||
// #enddocregion fake-async-test-rxjs
|
||||
});
|
||||
|
||||
// #docregion fake-async-test-tick
|
||||
it('should run timeout callback with delay after call tick with millis', fakeAsync(() => {
|
||||
let called = false;
|
||||
setTimeout(() => { called = true; }, 100);
|
||||
tick(100);
|
||||
expect(called).toBe(true);
|
||||
}));
|
||||
// #enddocregion fake-async-test-tick
|
||||
|
||||
// #docregion fake-async-test-date
|
||||
it('should get Date diff correctly in fakeAsync', fakeAsync(() => {
|
||||
const start = Date.now();
|
||||
tick(100);
|
||||
const end = Date.now();
|
||||
expect(end - start).toBe(100);
|
||||
}));
|
||||
// #enddocregion fake-async-test-date
|
||||
|
||||
// #docregion fake-async-test-rxjs
|
||||
it('should get Date diff correctly in fakeAsync with rxjs scheduler', fakeAsync(() => {
|
||||
// need to add `import 'zone.js/dist/zone-patch-rxjs-fake-async'
|
||||
// to patch rxjs scheduler
|
||||
let result = null;
|
||||
of ('hello').pipe(delay(1000)).subscribe(v => { result = v; });
|
||||
expect(result).toBeNull();
|
||||
tick(1000);
|
||||
expect(result).toBe('hello');
|
||||
|
||||
const start = new Date().getTime();
|
||||
let dateDiff = 0;
|
||||
interval(1000).pipe(take(2)).subscribe(() => dateDiff = (new Date().getTime() - start));
|
||||
|
||||
tick(1000);
|
||||
expect(dateDiff).toBe(1000);
|
||||
tick(1000);
|
||||
expect(dateDiff).toBe(2000);
|
||||
}));
|
||||
// #enddocregion fake-async-test-rxjs
|
||||
|
||||
// #docregion fake-async-test-clock
|
||||
describe('use jasmine.clock()', () => {
|
||||
// need to config __zone_symbol__fakeAsyncPatchLock flag
|
||||
@ -124,16 +141,4 @@ describe('Angular async helper', () => {
|
||||
});
|
||||
// #enddocregion async-test-promise-then
|
||||
|
||||
it('should run async test with successful delayed Observable', async(() => {
|
||||
const source = of (true).pipe(delay(10));
|
||||
source.subscribe(val => actuallyDone = true, err => fail(err));
|
||||
}));
|
||||
|
||||
it('should run async test with successful delayed Observable', fakeAsync(() => {
|
||||
const source = of (true).pipe(delay(10));
|
||||
source.subscribe(val => actuallyDone = true, err => fail(err));
|
||||
|
||||
tick(10);
|
||||
}));
|
||||
|
||||
});
|
||||
|
@ -11,7 +11,7 @@
|
||||
<!-- #docregion name-input -->
|
||||
<div>
|
||||
<label>name:
|
||||
<input [(ngModel)]="hero.name" placeholder="name">
|
||||
<input [(ngModel)]="hero.name" placeholder="name"/>
|
||||
</label>
|
||||
</div>
|
||||
<!-- #enddocregion name-input -->
|
||||
|
@ -5,6 +5,6 @@
|
||||
<div><span>id: </span>{{hero.id}}</div>
|
||||
<div>
|
||||
<label>name:
|
||||
<input [(ngModel)]="hero.name" placeholder="name">
|
||||
<input [(ngModel)]="hero.name" placeholder="name"/>
|
||||
</label>
|
||||
</div>
|
||||
|
@ -18,7 +18,7 @@
|
||||
<div><span>id: </span>{{selectedHero.id}}</div>
|
||||
<div>
|
||||
<label>name:
|
||||
<input [(ngModel)]="selectedHero.name" placeholder="name">
|
||||
<input [(ngModel)]="selectedHero.name" placeholder="name"/>
|
||||
</label>
|
||||
</div>
|
||||
<!-- #enddocregion selectedHero-details -->
|
||||
|
@ -42,7 +42,7 @@ export class HeroService {
|
||||
// #enddocregion getHeroes-2
|
||||
tap(_ => this.log('fetched heroes')),
|
||||
// #docregion getHeroes-2
|
||||
catchError(this.handleError('getHeroes', []))
|
||||
catchError(this.handleError<Hero[]>('getHeroes', []))
|
||||
);
|
||||
// #docregion getHeroes-1
|
||||
}
|
||||
|
@ -453,7 +453,7 @@ The compiler understands all syntax forms that the _collector_ supports, but it
|
||||
|
||||
The compiler can only reference _exported symbols_.
|
||||
|
||||
Decorated component class members must be public. You cannot make an `@Input()` property private or internal.
|
||||
Decorated component class members must be public. You cannot make an `@Input()` property private or protected.
|
||||
|
||||
Data bound properties must also be public.
|
||||
|
||||
|
59
aio/content/guide/app-shell.md
Normal file
@ -0,0 +1,59 @@
|
||||
# App shell
|
||||
|
||||
App shell is a way to render a portion of your application via a route at build time.
|
||||
It can improve the user experience by quickly launching a static rendered page (a skeleton common to all pages) while the browser downloads the full client version and switches to it automatically after the code loads.
|
||||
|
||||
This gives users a meaningful first paint of your application that appears quickly because the browser can simply render the HTML and CSS without the need to initialize any JavaScript.
|
||||
|
||||
Learn more in [The App Shell Model](https://developers.google.com/web/fundamentals/architecture/app-shell).
|
||||
|
||||
## Step 1: Prepare the application
|
||||
|
||||
You can do this with the following CLI command:
|
||||
<code-example format="." language="bash" linenums="false">
|
||||
ng new my-app --routing
|
||||
</code-example>
|
||||
|
||||
For an existing application, you have to manually add the `RouterModule` and defining a `<router-outlet>` within your application.
|
||||
|
||||
## Step 2: Create the app shell
|
||||
|
||||
Use the CLI to automatically create the app shell.
|
||||
|
||||
<code-example format="." language="bash" linenums="false">
|
||||
ng generate app-shell --client-project my-app --universal-project server-app
|
||||
</code-example>
|
||||
|
||||
* `my-app` takes the name of your client application.
|
||||
* `server-app` takes the name of the Universal (or server) application.
|
||||
|
||||
After running this command you will notice that the `angular.json` configuration file has been updated to add two new targets, with a few other changes.
|
||||
|
||||
<code-example format="." language="none" linenums="false">
|
||||
"server": {
|
||||
"builder": "@angular-devkit/build-angular:server",
|
||||
"options": {
|
||||
"outputPath": "dist/my-app-server",
|
||||
"main": "src/main.server.ts",
|
||||
"tsConfig": "tsconfig.server.json"
|
||||
}
|
||||
},
|
||||
"app-shell": {
|
||||
"builder": "@angular-devkit/build-angular:app-shell",
|
||||
"options": {
|
||||
"browserTarget": "my-app:build",
|
||||
"serverTarget": "my-app:server",
|
||||
"route": "shell"
|
||||
}
|
||||
}
|
||||
</code-example>
|
||||
|
||||
## Step 3: Verify the app is built with the shell content
|
||||
|
||||
Use the CLI to build the `app-shell` target.
|
||||
|
||||
<code-example format="." language="bash" linenums="false">
|
||||
ng run my-app:app-shell
|
||||
</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.
|
@ -1,6 +1,6 @@
|
||||
# Introduction to modules
|
||||
|
||||
Angular apps are modular and Angular has its own modularity system called *NgModules*.
|
||||
Angular apps are modular and Angular has its own modularity system called *NgModules*.
|
||||
NgModules are containers for a cohesive block of code dedicated to an application domain, a workflow, or a closely related set of capabilities. They can contain components, service providers, and other code files whose scope is defined by the containing NgModule. They can import functionality that is exported from other NgModules, and export selected functionality for use by other NgModules.
|
||||
|
||||
Every Angular app has at least one NgModule class, [the *root module*](guide/bootstrapping), which is conventionally named `AppModule` and resides in a file named `app.module.ts`. You launch your app by *bootstrapping* the root NgModule.
|
||||
@ -27,7 +27,7 @@ Here's a simple root NgModule definition.
|
||||
|
||||
<div class="alert is-helpful">
|
||||
|
||||
The `export` property of `AppComponent` is included here for illustration; it isn't actually necessary in this example. A root NgModule has no reason to *export* anything because other modules don't need to *import* the root NgModule.
|
||||
`AppComponent` is included in the `exports` list here for illustration; it isn't actually necessary in this example. A root NgModule has no reason to *export* anything because other modules don't need to *import* the root NgModule.
|
||||
|
||||
</div>
|
||||
|
||||
@ -89,7 +89,7 @@ For example, import Angular's `Component` decorator from the `@angular/core` lib
|
||||
|
||||
<code-example path="architecture/src/app/app.component.ts" region="import" linenums="false"></code-example>
|
||||
|
||||
You also import NgModules from Angular *libraries* using JavaScript import statements.
|
||||
You also import NgModules from Angular *libraries* using JavaScript import statements.
|
||||
For example, the following code imports the `BrowserModule` NgModule from the `platform-browser` library.
|
||||
|
||||
<code-example path="architecture/src/app/mini-app.ts" region="import-browser-module" linenums="false"></code-example>
|
||||
|
@ -73,7 +73,7 @@ Angular provides predefined pipes for common transformations, and you can also d
|
||||
|
||||
## Services and dependency injection
|
||||
|
||||
For data or logic that isn't associated with a specific view, and that you want to share across components, you create a *service* class. A service class definition is immediately preceded by the `@Injectable()` decorator. The decorator provides the metadata that allows your service to be *injected* into client components as a dependency.
|
||||
For data or logic that isn't associated with a specific view, and that you want to share across components, you create a *service* class. A service class definition is immediately preceded by the `@Injectable()` decorator. The decorator provides the metadata that allows other providers to be **injected** as dependencies into your class.
|
||||
|
||||
*Dependency injection* (DI) lets you keep your component classes lean and efficient. They don't fetch data from the server, validate user input, or log directly to the console; they delegate such tasks to services.
|
||||
|
||||
|
@ -41,6 +41,10 @@ when the user hovers over that element. You can apply it like this:
|
||||
|
||||
{@a write-directive}
|
||||
|
||||
Please note that directives _do not_ support namespaces.
|
||||
|
||||
<code-example path="attribute-directives/src/app/app.component.avoid.html" linenums="false" header="src/app/app.component.avoid.html (unsupported)" region="unsupported"></code-example>
|
||||
|
||||
### Write the directive code
|
||||
|
||||
Create the directive class file in a terminal window with the CLI command [`ng generate directive`](cli/generate).
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
You can create and publish new libraries to extend Angular functionality. If you find that you need to solve the same problem in more than one app (or want to share your solution with other developers), you have a candidate for a library.
|
||||
|
||||
An simple example might be a button that sends users to your company website, that would be included in all apps that your company builds.
|
||||
A simple example might be a button that sends users to your company website, that would be included in all apps that your company builds.
|
||||
|
||||
## Getting started
|
||||
|
||||
@ -42,7 +42,7 @@ This builder, among other things, ensures that the library is always built with
|
||||
|
||||
To make library code reusable you must define a public API for it. This "user layer" defines what is available to consumers of your library. A user of your library should be able to access public functionality (such as NgModules, service providers and general utility functions) through a single import path.
|
||||
|
||||
The public API for your library is maintained in the `index.ts` file of your library folder.
|
||||
The public API for your library is maintained in the `public-api.ts` file in your library folder.
|
||||
Anything exported from this file is made public when your library is imported into an application.
|
||||
Use an NgModule to expose services and components.
|
||||
|
||||
|
@ -151,7 +151,7 @@ through the injector tree until it reaches the root injector.
|
||||
|
||||
* The first injector configured with a provider supplies the dependency (a service instance or value) to the constructor.
|
||||
|
||||
* If no provider is found in the root injector, the DI framework returns null to the constructor.
|
||||
* If no provider is found in the root injector, the DI framework throws an error.
|
||||
|
||||
There are a number of options for modifying the default search behavior, using _parameter decorators_
|
||||
on the service-valued parameters of a class constructor.
|
||||
|
@ -182,8 +182,7 @@ Child modules and component injectors are independent of each other, and create
|
||||
|
||||
Thanks to [injector inheritance](guide/hierarchical-dependency-injection),
|
||||
you can still inject application-wide services into these components.
|
||||
A component's injector is a child of its parent component's injector,
|
||||
and a descendent of its parent's parent's injector, and so on all the way back to the application's _root_ injector. Angular can inject a service provided by any injector in that lineage.
|
||||
A component's injector is a child of its parent component's injector, and inherits from all ancestor injectors all the way back to the application's _root_ injector. Angular can inject a service provided by any injector in that lineage.
|
||||
|
||||
For example, Angular can inject `HeroListComponent` with both the `HeroService` provided in `HeroComponent` and the `UserService` provided in `AppModule`.
|
||||
|
||||
|
@ -476,7 +476,7 @@ That's easy to implement with RxJS operators, as shown in this excerpt.
|
||||
<code-example
|
||||
path="http/src/app/package-search/package-search.component.ts"
|
||||
region="debounce"
|
||||
header="app/package-search/package-search.component.ts (excerpt))">
|
||||
header="app/package-search/package-search.component.ts (excerpt)">
|
||||
</code-example>
|
||||
|
||||
The `searchText$` is the sequence of search-box values coming from the user.
|
||||
@ -865,6 +865,9 @@ with the `reportProgress` option set true to enable tracking of progress events.
|
||||
|
||||
Every progress event triggers change detection, so only turn them on if you truly intend to report progress in the UI.
|
||||
|
||||
When using [`HttpClient#request()`](api/common/http/HttpClient#request) with an HTTP method, configure with
|
||||
[`observe: 'events'`](api/common/http/HttpClient#request) to see all events, including the progress of transfers.
|
||||
|
||||
</div>
|
||||
|
||||
Next, pass this request object to the `HttpClient.request()` method, which
|
||||
|
@ -49,18 +49,6 @@ locale id to find the correct corresponding locale data.
|
||||
|
||||
By default, Angular uses the locale `en-US`, which is English as spoken in the United States of America.
|
||||
|
||||
To set your app's locale to another value, use the CLI parameter `--configuration` with the value of the locale id that you want to use:
|
||||
|
||||
<code-example language="sh" class="code-shell">
|
||||
ng serve --configuration=fr
|
||||
</code-example>
|
||||
|
||||
If you use JIT, you also need to define the `LOCALE_ID` provider in your main module:
|
||||
|
||||
<code-example path="i18n/doc-files/app.module.ts" header="src/app/app.module.ts" linenums="false">
|
||||
</code-example>
|
||||
|
||||
|
||||
For more information about Unicode locale identifiers, see the
|
||||
[CLDR core spec](http://cldr.unicode.org/core-spec#Unicode_Language_and_Locale_Identifiers).
|
||||
|
||||
|
@ -158,7 +158,7 @@ You can <a href="generated/zips/cli-quickstart/cli-quickstart.zip" target="_blan
|
||||
</div>
|
||||
|
||||
|
||||
For more information about Angular project files and the file structure, see [Workspace and project file struture](guide/file-structure).
|
||||
For more information about Angular project files and the file structure, see [Workspace and project file structure](guide/file-structure).
|
||||
|
||||
|
||||
|
||||
|
@ -1055,7 +1055,7 @@ The default route should redirect to the `HeroListComponent` _only_ when the _en
|
||||
Remember to restore the redirect to `pathMatch = 'full'`.
|
||||
|
||||
Learn more in Victor Savkin's
|
||||
[post on redirects](http://victorsavkin.com/post/146722301646/angular-router-empty-paths-componentless-routes).
|
||||
[post on redirects](http://vsavkin.tumblr.com/post/146722301646/angular-router-empty-paths-componentless-routes).
|
||||
|
||||
|
||||
</div>
|
||||
@ -1280,8 +1280,9 @@ The **Routing Module** has several characteristics:
|
||||
### Integrate routing with your app
|
||||
|
||||
The sample routing application does not include routing by default.
|
||||
When you use the [Angular CLI](cli) to create a project that will use routing, set the `--routing` option for the project or app, and for each NgModule.
|
||||
When you create or initialize a new project (using the CLI [`ng new`](cli/new) command) or a new app (using the [`ng generate app`](cli/generate) command), specify the `--routing` option. This tells the CLI to include the `@angular/router` npm package and create a file named `app-routing.module.ts`.
|
||||
When you create a new workspace and initial application, the [Angular CLI](cli) [`ng new`](cli/new) command prompts you to add "Angular routing". Enter `Y`.
|
||||
When you generate a new application using the [`ng generate app`](cli/generate) command, specify the `--routing` option. These options tell the CLI to include the `@angular/router` npm package and create a file named `app-routing.module.ts`.
|
||||
|
||||
You can then use routing in any NgModule that you add to the project or app.
|
||||
|
||||
For example, the following command generates an NgModule that can use routing.
|
||||
@ -3849,7 +3850,7 @@ Update the `AuthGuard` to provide a `session_id` query that will remain after na
|
||||
|
||||
Add an `anchor` element so you can jump to a certain point on the page.
|
||||
|
||||
Add the `NavigationExtras` object to the `router.navigate` method that navigates you to the `/login` route.
|
||||
Add the `NavigationExtras` object to the `router.navigate()` method that navigates you to the `/login` route.
|
||||
|
||||
|
||||
<code-example path="router/src/app/auth/auth.guard.4.ts" linenums="false" header="src/app/auth/auth.guard.ts (v3)">
|
||||
@ -3860,7 +3861,7 @@ Add the `NavigationExtras` object to the `router.navigate` method that navigates
|
||||
|
||||
You can also preserve query parameters and fragments across navigations without having to provide them
|
||||
again when navigating. In the `LoginComponent`, you'll add an *object* as the
|
||||
second argument in the `router.navigate` function
|
||||
second argument in the `router.navigateUrl()` function
|
||||
and provide the `queryParamsHandling` and `preserveFragment` to pass along the current query parameters
|
||||
and fragment to the next route.
|
||||
|
||||
|
@ -301,7 +301,7 @@ an administrator ever needs to deactivate the service worker quickly.
|
||||
### Fail-safe
|
||||
|
||||
To deactivate the service worker, remove or rename the
|
||||
`ngsw-config.json` file. When the service worker's request
|
||||
`ngsw.json` file. When the service worker's request
|
||||
for `ngsw.json` returns a `404`, then the service worker
|
||||
removes all of its caches and de-registers itself,
|
||||
essentially self-destructing.
|
||||
|
@ -1749,447 +1749,6 @@ A consistent class and file name convention make these modules easy to spot and
|
||||
</table>
|
||||
|
||||
|
||||
|
||||
<a href="#toc">Back to top</a>
|
||||
|
||||
|
||||
## Coding conventions
|
||||
|
||||
Have a consistent set of coding, naming, and whitespace conventions.
|
||||
|
||||
|
||||
|
||||
{@a 03-01}
|
||||
|
||||
### Classes
|
||||
|
||||
#### Style 03-01
|
||||
|
||||
<div class="s-rule do">
|
||||
|
||||
|
||||
|
||||
**Do** use upper camel case when naming classes.
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<div class="s-why">
|
||||
|
||||
|
||||
|
||||
**Why?** Follows conventional thinking for class names.
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<div class="s-why-last">
|
||||
|
||||
|
||||
|
||||
**Why?** Classes can be instantiated and construct an instance.
|
||||
By convention, upper camel case indicates a constructable asset.
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<code-example path="styleguide/src/03-01/app/core/exception.service.avoid.ts" region="example" header="app/shared/exception.service.ts">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<code-example path="styleguide/src/03-01/app/core/exception.service.ts" region="example" header="app/shared/exception.service.ts">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
<a href="#toc">Back to top</a>
|
||||
|
||||
|
||||
{@a 03-02}
|
||||
|
||||
### Constants
|
||||
|
||||
#### Style 03-02
|
||||
|
||||
<div class="s-rule do">
|
||||
|
||||
|
||||
|
||||
**Do** declare variables with `const` if their values should not change during the application lifetime.
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<div class="s-why">
|
||||
|
||||
|
||||
|
||||
**Why?** Conveys to readers that the value is invariant.
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<div class="s-why-last">
|
||||
|
||||
|
||||
|
||||
**Why?** TypeScript helps enforce that intent by requiring immediate initialization and by
|
||||
preventing subsequent re-assignment.
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<div class="s-rule consider">
|
||||
|
||||
|
||||
|
||||
**Consider** spelling `const` variables in lower camel case.
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<div class="s-why">
|
||||
|
||||
|
||||
|
||||
**Why?** Lower camel case variable names (`heroRoutes`) are easier to read and understand
|
||||
than the traditional UPPER_SNAKE_CASE names (`HERO_ROUTES`).
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<div class="s-why-last">
|
||||
|
||||
|
||||
|
||||
**Why?** The tradition of naming constants in UPPER_SNAKE_CASE reflects
|
||||
an era before the modern IDEs that quickly reveal the `const` declaration.
|
||||
TypeScript prevents accidental reassignment.
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<div class="s-rule do">
|
||||
|
||||
|
||||
|
||||
**Do** tolerate _existing_ `const` variables that are spelled in UPPER_SNAKE_CASE.
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<div class="s-why-last">
|
||||
|
||||
|
||||
|
||||
**Why?** The tradition of UPPER_SNAKE_CASE remains popular and pervasive,
|
||||
especially in third party modules.
|
||||
It is rarely worth the effort to change them at the risk of breaking existing code and documentation.
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<code-example path="styleguide/src/03-02/app/core/data.service.ts" header="app/shared/data.service.ts">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
<a href="#toc">Back to top</a>
|
||||
|
||||
|
||||
{@a 03-03}
|
||||
|
||||
### Interfaces
|
||||
|
||||
#### Style 03-03
|
||||
|
||||
<div class="s-rule do">
|
||||
|
||||
|
||||
|
||||
**Do** name an interface using upper camel case.
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<div class="s-rule consider">
|
||||
|
||||
|
||||
|
||||
**Consider** naming an interface without an `I` prefix.
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<div class="s-rule consider">
|
||||
|
||||
|
||||
|
||||
**Consider** using a class instead of an interface for services and declarables (components, directives, and pipes).
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<div class="s-rule consider">
|
||||
|
||||
|
||||
|
||||
**Consider** using an interface for data models.
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<div class="s-why">
|
||||
|
||||
|
||||
|
||||
**Why?** <a href="https://github.com/Microsoft/TypeScript/wiki/Coding-guidelines">TypeScript guidelines</a>
|
||||
discourage the `I` prefix.
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<div class="s-why">
|
||||
|
||||
|
||||
|
||||
**Why?** A class alone is less code than a _class-plus-interface_.
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<div class="s-why">
|
||||
|
||||
|
||||
|
||||
**Why?** A class can act as an interface (use `implements` instead of `extends`).
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<div class="s-why-last">
|
||||
|
||||
|
||||
|
||||
**Why?** An interface-class can be a provider lookup token in Angular dependency injection.
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<code-example path="styleguide/src/03-03/app/core/hero-collector.service.avoid.ts" region="example" header="app/shared/hero-collector.service.ts">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<code-example path="styleguide/src/03-03/app/core/hero-collector.service.ts" region="example" header="app/shared/hero-collector.service.ts">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
<a href="#toc">Back to top</a>
|
||||
|
||||
{@a 03-04}
|
||||
|
||||
### Properties and methods
|
||||
|
||||
#### Style 03-04
|
||||
|
||||
|
||||
<div class="s-rule do">
|
||||
|
||||
|
||||
|
||||
**Do** use lower camel case to name properties and methods.
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<div class="s-rule avoid">
|
||||
|
||||
|
||||
|
||||
**Avoid** prefixing private properties and methods with an underscore.
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<div class="s-why">
|
||||
|
||||
|
||||
|
||||
**Why?** Follows conventional thinking for properties and methods.
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<div class="s-why">
|
||||
|
||||
|
||||
|
||||
**Why?** JavaScript lacks a true private property or method.
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<div class="s-why-last">
|
||||
|
||||
|
||||
|
||||
**Why?** TypeScript tooling makes it easy to identify private vs. public properties and methods.
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<code-example path="styleguide/src/03-04/app/core/toast.service.avoid.ts" region="example" header="app/shared/toast.service.ts">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<code-example path="styleguide/src/03-04/app/core/toast.service.ts" region="example" header="app/shared/toast.service.ts">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
<a href="#toc">Back to top</a>
|
||||
|
||||
{@a 03-06}
|
||||
|
||||
### Import line spacing
|
||||
|
||||
#### Style 03-06
|
||||
|
||||
|
||||
<div class="s-rule consider">
|
||||
|
||||
|
||||
|
||||
**Consider** leaving one empty line between third party imports and application imports.
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<div class="s-rule consider">
|
||||
|
||||
|
||||
|
||||
**Consider** listing import lines alphabetized by the module.
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<div class="s-rule consider">
|
||||
|
||||
|
||||
|
||||
**Consider** listing destructured imported symbols alphabetically.
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<div class="s-why">
|
||||
|
||||
|
||||
|
||||
**Why?** The empty line separates _your_ stuff from _their_ stuff.
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<div class="s-why-last">
|
||||
|
||||
|
||||
|
||||
**Why?** Alphabetizing makes it easier to read and locate symbols.
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<code-example path="styleguide/src/03-06/app/heroes/shared/hero.service.avoid.ts" region="example" header="app/heroes/shared/hero.service.ts">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<code-example path="styleguide/src/03-06/app/heroes/shared/hero.service.ts" region="example" header="app/heroes/shared/hero.service.ts">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
<a href="#toc">Back to top</a>
|
||||
|
||||
|
||||
|
@ -797,7 +797,6 @@ content harmlessly.
|
||||
|
||||
|
||||
<hr/>
|
||||
|
||||
{@a other-bindings}
|
||||
|
||||
## Attribute, class, and style bindings
|
||||
@ -944,56 +943,44 @@ Note that a _style property_ name can be written in either
|
||||
|
||||
{@a event-binding}
|
||||
|
||||
## Event binding ( <span class="syntax">(event)</span> )
|
||||
## Event binding `(event)`
|
||||
|
||||
The bindings directives you've met so far flow data in one direction: **from a component to an element**.
|
||||
Event binding allows you to listen for certain events such as
|
||||
keystrokes, mouse movements, clicks, and touches. For an example
|
||||
demonstrating all of the points in this section, see the <live-example name="event-binding">event binding example</live-example>.
|
||||
|
||||
Users don't just stare at the screen. They enter text into input boxes. They pick items from lists.
|
||||
They click buttons. Such user actions may result in a flow of data in the opposite direction:
|
||||
**from an element to a component**.
|
||||
|
||||
The only way to know about a user action is to listen for certain events such as
|
||||
keystrokes, mouse movements, clicks, and touches.
|
||||
You declare your interest in user actions through Angular event binding.
|
||||
|
||||
Event binding syntax consists of a **target event** name
|
||||
Angular event binding syntax consists of a **target event** name
|
||||
within parentheses on the left of an equal sign, and a quoted
|
||||
[template statement](guide/template-syntax#template-statements) on the right.
|
||||
template statement on the right.
|
||||
The following event binding listens for the button's click events, calling
|
||||
the component's `onSave()` method whenever a click occurs:
|
||||
|
||||
<code-example path="template-syntax/src/app/app.component.html" region="event-binding-1" header="src/app/app.component.html" linenums="false">
|
||||
</code-example>
|
||||
<figure>
|
||||
<img src='generated/images/guide/event-binding/syntax-diagram.svg' alt="Syntax diagram">
|
||||
</figure>
|
||||
|
||||
### Target event
|
||||
|
||||
A **name between parentheses** — for example, `(click)` —
|
||||
identifies the target event. In the following example, the target is the button's click event.
|
||||
As above, the target is the button's click event.
|
||||
|
||||
<code-example path="template-syntax/src/app/app.component.html" region="event-binding-1" header="src/app/app.component.html" linenums="false">
|
||||
<code-example path="event-binding/src/app/app.component.html" region="event-binding-1" header="src/app/app.component.html" linenums="false">
|
||||
</code-example>
|
||||
|
||||
Some people prefer the `on-` prefix alternative, known as the **canonical form**:
|
||||
Alternatively, use the `on-` prefix, known as the canonical form:
|
||||
|
||||
<code-example path="template-syntax/src/app/app.component.html" region="event-binding-2" header="src/app/app.component.html" linenums="false">
|
||||
<code-example path="event-binding/src/app/app.component.html" region="event-binding-2" header="src/app/app.component.html" linenums="false">
|
||||
</code-example>
|
||||
|
||||
Element events may be the more common targets, but Angular looks first to see if the name matches an event property
|
||||
of a known directive, as it does in the following example:
|
||||
|
||||
<code-example path="template-syntax/src/app/app.component.html" region="event-binding-3" header="src/app/app.component.html" linenums="false">
|
||||
<code-example path="event-binding/src/app/app.component.html" region="custom-directive" header="src/app/app.component.html" linenums="false">
|
||||
</code-example>
|
||||
|
||||
<div class="alert is-helpful">
|
||||
|
||||
The `myClick` directive is further described in the section
|
||||
on [aliasing input/output properties](guide/template-syntax#aliasing-io).
|
||||
|
||||
</div>
|
||||
|
||||
If the name fails to match an element event or an output property of a known directive,
|
||||
Angular reports an “unknown directive” error.
|
||||
|
||||
|
||||
### *$event* and event handling statements
|
||||
|
||||
In an event binding, Angular sets up an event handler for the target event.
|
||||
@ -1003,72 +990,73 @@ The template statement typically involves a receiver, which performs an action
|
||||
in response to the event, such as storing a value from the HTML control
|
||||
into a model.
|
||||
|
||||
The binding conveys information about the event, including data values, through
|
||||
an **event object named `$event`**.
|
||||
The binding conveys information about the event. This information can include data values such as an event object, string, or number named `$event`.
|
||||
|
||||
The shape of the event object is determined by the target event.
|
||||
The target event determines the shape of the `$event` object.
|
||||
If the target event is a native DOM element event, then `$event` is a
|
||||
[DOM event object](https://developer.mozilla.org/en-US/docs/Web/Events),
|
||||
with properties such as `target` and `target.value`.
|
||||
|
||||
Consider this example:
|
||||
|
||||
<code-example path="template-syntax/src/app/app.component.html" region="without-NgModel" header="src/app/app.component.html" linenums="false">
|
||||
<code-example path="event-binding/src/app/app.component.html" region="event-binding-3" header="src/app/app.component.html" linenums="false">
|
||||
</code-example>
|
||||
|
||||
This code sets the input box `value` property by binding to the `name` property.
|
||||
To listen for changes to the value, the code binds to the input box's `input` event.
|
||||
This code sets the `<input>` `value` property by binding to the `name` property.
|
||||
To listen for changes to the value, the code binds to the `input`
|
||||
event of the `<input>` element.
|
||||
When the user makes changes, the `input` event is raised, and the binding executes
|
||||
the statement within a context that includes the DOM event object, `$event`.
|
||||
|
||||
To update the `name` property, the changed text is retrieved by following the path `$event.target.value`.
|
||||
|
||||
If the event belongs to a directive (recall that components are directives),
|
||||
`$event` has whatever shape the directive decides to produce.
|
||||
If the event belongs to a directive—recall that components
|
||||
are directives—`$event` has whatever shape the directive produces.
|
||||
|
||||
{@a eventemitter}
|
||||
|
||||
{@a custom-event}
|
||||
|
||||
### Custom events with <span class="syntax">EventEmitter</span>
|
||||
### Custom events with `EventEmitter`
|
||||
|
||||
Directives typically raise custom events with an Angular [EventEmitter](api/core/EventEmitter).
|
||||
The directive creates an `EventEmitter` and exposes it as a property.
|
||||
The directive calls `EventEmitter.emit(payload)` to fire an event, passing in a message payload, which can be anything.
|
||||
Parent directives listen for the event by binding to this property and accessing the payload through the `$event` object.
|
||||
|
||||
Consider a `HeroDetailComponent` that presents hero information and responds to user actions.
|
||||
Although the `HeroDetailComponent` has a delete button it doesn't know how to delete the hero itself.
|
||||
The best it can do is raise an event reporting the user's delete request.
|
||||
Consider an `ItemDetailComponent` that presents item information and responds to user actions.
|
||||
Although the `ItemDetailComponent` has a delete button, it doesn't know how to delete the hero. It can only raise an event reporting the user's delete request.
|
||||
|
||||
Here are the pertinent excerpts from that `HeroDetailComponent`:
|
||||
Here are the pertinent excerpts from that `ItemDetailComponent`:
|
||||
|
||||
<code-example path="template-syntax/src/app/hero-detail.component.ts" linenums="false" header="src/app/hero-detail.component.ts (template)" region="template-1">
|
||||
|
||||
<code-example path="event-binding/src/app/item-detail/item-detail.component.html" linenums="false" header="src/app/item-detail/item-detail.component.ts (template)" region="line-through">
|
||||
</code-example>
|
||||
|
||||
<code-example path="template-syntax/src/app/hero-detail.component.ts" linenums="false" header="src/app/hero-detail.component.ts (deleteRequest)" region="deleteRequest">
|
||||
<code-example path="event-binding/src/app/item-detail/item-detail.component.ts" linenums="false" header="src/app/item-detail/item-detail.component.ts (deleteRequest)" region="deleteRequest">
|
||||
</code-example>
|
||||
|
||||
|
||||
The component defines a `deleteRequest` property that returns an `EventEmitter`.
|
||||
When the user clicks *delete*, the component invokes the `delete()` method,
|
||||
telling the `EventEmitter` to emit a `Hero` object.
|
||||
telling the `EventEmitter` to emit an `Item` object.
|
||||
|
||||
Now imagine a hosting parent component that binds to the `HeroDetailComponent`'s `deleteRequest` event.
|
||||
Now imagine a hosting parent component that binds to the `deleteRequest` event
|
||||
of the `ItemDetailComponent`.
|
||||
|
||||
<code-example path="template-syntax/src/app/app.component.html" linenums="false" header="src/app/app.component.html (event-binding-to-component)" region="event-binding-to-component">
|
||||
<code-example path="event-binding/src/app/app.component.html" linenums="false" header="src/app/app.component.html (event-binding-to-component)" region="event-binding-to-component">
|
||||
</code-example>
|
||||
|
||||
When the `deleteRequest` event fires, Angular calls the parent component's `deleteHero` method,
|
||||
passing the *hero-to-delete* (emitted by `HeroDetail`) in the `$event` variable.
|
||||
When the `deleteRequest` event fires, Angular calls the parent component's
|
||||
`deleteItem()` method, passing the *item-to-delete* (emitted by `ItemDetail`)
|
||||
in the `$event` variable.
|
||||
|
||||
### Template statements have side effects
|
||||
|
||||
The `deleteHero` method has a side effect: it deletes a hero.
|
||||
Template statement side effects are not just OK, but expected.
|
||||
Though [template expressions](guide/template-syntax#template-expressions) shouldn't have [side effects](guide/template-syntax#avoid-side-effects), template
|
||||
statements usually do. The `deleteItem()` method does have
|
||||
a side effect: it deletes an item.
|
||||
|
||||
Deleting the hero updates the model, perhaps triggering other changes
|
||||
including queries and saves to a remote server.
|
||||
These changes percolate through the system and are ultimately displayed in this and other views.
|
||||
Deleting an item updates the model, and depending on your code, triggers
|
||||
other changes including queries and saving to a remote server.
|
||||
These changes propagate through the system and ultimately display in this and other views.
|
||||
|
||||
|
||||
<hr/>
|
||||
@ -2009,29 +1997,29 @@ You'll need this template operator when you turn on strict null checks. It's opt
|
||||
|
||||
<hr/>
|
||||
|
||||
{@a built-in-template-functions}
|
||||
|
||||
## Built-in template functions
|
||||
|
||||
{@a any-type-cast-function}
|
||||
|
||||
## The `$any` type cast function (`$any( <expression> )`)
|
||||
### The `$any()` type cast function
|
||||
|
||||
Sometimes a binding expression will be reported as a type error and it is not possible or difficult
|
||||
to fully specify the type. To silence the error, you can use the `$any` cast function to cast
|
||||
the expression to [the `any` type](http://www.typescriptlang.org/docs/handbook/basic-types.html#any).
|
||||
Sometimes a binding expression triggers a type error during [AOT compilation](guide/aot-compiler) and it is not possible or difficult
|
||||
to fully specify the type. To silence the error, you can use the `$any()` cast function to cast
|
||||
the expression to [the `any` type](http://www.typescriptlang.org/docs/handbook/basic-types.html#any) as in the following example:
|
||||
|
||||
<code-example path="template-syntax/src/app/app.component.html" region="any-type-cast-function-1" header="src/app/app.component.html" linenums="false">
|
||||
<code-example path="built-in-template-functions/src/app/app.component.html" region="any-type-cast-function-1" header="src/app/app.component.html" linenums="false">
|
||||
</code-example>
|
||||
|
||||
In this example, when the Angular compiler turns your template into TypeScript code,
|
||||
it prevents TypeScript from reporting that `marker` is not a member of the `Hero`
|
||||
interface.
|
||||
When the Angular compiler turns this template into TypeScript code,
|
||||
it prevents TypeScript from reporting that `bestByDate` is not a member of the `item`
|
||||
object when it runs type checking on the template.
|
||||
|
||||
The `$any` cast function can be used in conjunction with `this` to allow access to undeclared members of
|
||||
The `$any()` cast function also works with `this` to allow access to undeclared members of
|
||||
the component.
|
||||
|
||||
<code-example path="template-syntax/src/app/app.component.html" region="any-type-cast-function-2" header="src/app/app.component.html" linenums="false">
|
||||
<code-example path="built-in-template-functions/src/app/app.component.html" region="any-type-cast-function-2" header="src/app/app.component.html" linenums="false">
|
||||
</code-example>
|
||||
|
||||
The `$any` cast function can be used anywhere in a binding expression where a method call is valid.
|
||||
|
||||
## Summary
|
||||
You've completed this survey of template syntax.
|
||||
Now it's time to put that knowledge to work on your own components and directives.
|
||||
The `$any()` cast function works anywhere in a binding expression where a method call is valid.
|
||||
|
@ -23,7 +23,7 @@ Just run the [`ng test`](cli/test) CLI command:
|
||||
</code-example>
|
||||
|
||||
The `ng test` command builds the app in _watch mode_,
|
||||
and launches the [karma test runner](https://karma-runner.github.io).
|
||||
and launches the [Karma test runner](https://karma-runner.github.io).
|
||||
|
||||
The console output looks a bit like this:
|
||||
|
||||
@ -55,15 +55,15 @@ The tests run again, the browser refreshes, and the new test results appear.
|
||||
|
||||
#### Configuration
|
||||
|
||||
The CLI takes care of Jasmine and karma configuration for you.
|
||||
The CLI takes care of Jasmine and Karma configuration for you.
|
||||
|
||||
You can fine-tune many options by editing the `karma.conf.js` and
|
||||
the `test.ts` files in the `src/` folder.
|
||||
|
||||
The `karma.conf.js` file is a partial karma configuration file.
|
||||
The `karma.conf.js` file is a partial Karma configuration file.
|
||||
The CLI constructs the full runtime configuration in memory, based on application structure specified in the `angular.json` file, supplemented by `karma.conf.js`.
|
||||
|
||||
Search the web for more details about Jasmine and karma configuration.
|
||||
Search the web for more details about Jasmine and Karma configuration.
|
||||
|
||||
#### Other test frameworks
|
||||
|
||||
@ -289,7 +289,7 @@ written without assistance from Angular testing utilities.
|
||||
#### Services with dependencies
|
||||
|
||||
Services often depend on other services that Angular injects into the constructor.
|
||||
In many cases, it easy to create and _inject_ these dependencies by hand while
|
||||
In many cases, it's easy to create and _inject_ these dependencies by hand while
|
||||
calling the service's constructor.
|
||||
|
||||
The `MasterService` is a simple example:
|
||||
@ -318,7 +318,7 @@ Prefer spies as they are usually the easiest way to mock services.
|
||||
|
||||
These standard testing techniques are great for unit testing services in isolation.
|
||||
|
||||
However, you almost always inject service into application classes using Angular
|
||||
However, you almost always inject services into application classes using Angular
|
||||
dependency injection and you should have tests that reflect that usage pattern.
|
||||
Angular testing utilities make it easy to investigate how injected services behave.
|
||||
|
||||
@ -428,7 +428,7 @@ to extract the setup variables that it needs.
|
||||
Many developers feel this approach is cleaner and more explicit than the
|
||||
traditional `beforeEach()` style.
|
||||
|
||||
Although this testing guide follows the tradition style and
|
||||
Although this testing guide follows the traditional style and
|
||||
the default [CLI schematics](https://github.com/angular/angular-cli)
|
||||
generate test files with `beforeEach()` and `TestBed`,
|
||||
feel free to adopt _this alternative approach_ in your own projects.
|
||||
@ -2398,7 +2398,7 @@ So when you call `createComponent()`, the `TestBed` compiles implicitly.
|
||||
|
||||
That's not a problem when the source code is in memory.
|
||||
But the `BannerComponent` requires external files
|
||||
that the compile must read from the file system,
|
||||
that the compiler must read from the file system,
|
||||
an inherently _asynchronous_ operation.
|
||||
|
||||
If the `TestBed` were allowed to continue, the tests would run and fail mysteriously
|
||||
@ -2816,7 +2816,7 @@ Consider adding component tests such as this one:
|
||||
|
||||
Debug specs in the browser in the same way that you debug an application.
|
||||
|
||||
1. Reveal the karma browser window (hidden earlier).
|
||||
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. Pick the "sources" section.
|
||||
|
@ -1,17 +1,17 @@
|
||||
# Angular Universal: server-side rendering
|
||||
# Server-side Rendering (SSR): An intro to Angular Universal
|
||||
|
||||
This guide describes **Angular Universal**, a technology that runs your Angular application on the server.
|
||||
This guide describes **Angular Universal**, a technology that renders Angular applications on the server.
|
||||
|
||||
A normal Angular application executes in the _browser_, rendering pages in the DOM in response to user actions.
|
||||
Angular Universal generates _static_ application pages on the _server_
|
||||
through a process called _server-side rendering_ (SSR).
|
||||
When Universal is integrated with your app, it can generate and serve those pages in response to requests from browsers.
|
||||
It can also pre-generate pages as HTML files that you serve later.
|
||||
Angular Universal executes on the _server_, generating _static_ application pages that later get bootstrapped on
|
||||
the client. This means that the application generally renders more quickly, giving users a chance to view the application
|
||||
layout before it becomes fully interactive.
|
||||
|
||||
You can easily prepare an app for server-side rendering using the [Angular CLI](guide/glossary#cli). The CLI schematic `@nguniversal/express-engine` performs the required steps, as described below.
|
||||
For a more detailed look at different techniques and concepts surrounding SSR, please check out this
|
||||
[article](https://developers.google.com/web/updates/2019/02/rendering-on-the-web).
|
||||
|
||||
This guide describes a Universal sample application that launches quickly as a server-rendered page.
|
||||
Meanwhile, the browser downloads the full client version and switches to it automatically after the code loads.
|
||||
You can easily prepare an app for server-side rendering using the [Angular CLI](guide/glossary#cli).
|
||||
The CLI schematic `@nguniversal/express-engine` performs the required steps, as described below.
|
||||
|
||||
<div class="alert is-helpful">
|
||||
|
||||
@ -20,471 +20,16 @@ Meanwhile, the browser downloads the full client version and switches to it auto
|
||||
|
||||
</div>
|
||||
|
||||
{@a why-do-it}
|
||||
|
||||
## Why use server-side rendering?
|
||||
|
||||
There are three main reasons to create a Universal version of your app.
|
||||
|
||||
1. Facilitate web crawlers (SEO)
|
||||
1. Improve performance on mobile and low-powered devices
|
||||
1. Show the first page quickly
|
||||
|
||||
{@a seo}
|
||||
{@a web-crawlers}
|
||||
### Facilitate web crawlers
|
||||
|
||||
Google, Bing, Facebook, Twitter, and other social media sites rely on web crawlers to index your application content and make that content searchable on the web.
|
||||
These web crawlers may be unable to navigate and index your highly interactive Angular application as a human user could do.
|
||||
|
||||
Angular Universal can generate a static version of your app that is easily searchable, linkable, and navigable without JavaScript.
|
||||
Universal also makes a site preview available since each URL returns a fully rendered page.
|
||||
|
||||
Enabling web crawlers is often referred to as
|
||||
[search engine optimization (SEO)](https://static.googleusercontent.com/media/www.google.com/en//webmasters/docs/search-engine-optimization-starter-guide.pdf).
|
||||
|
||||
{@a no-javascript}
|
||||
|
||||
### Improve performance on mobile and low-powered devices
|
||||
|
||||
Some devices don't support JavaScript or execute JavaScript so poorly that the user experience is unacceptable.
|
||||
For these cases, you may require a server-rendered, no-JavaScript version of the app.
|
||||
This version, however limited, may be the only practical alternative for
|
||||
people who otherwise couldn't use the app at all.
|
||||
|
||||
{@a startup-performance}
|
||||
|
||||
### Show the first page quickly
|
||||
|
||||
Displaying the first page quickly can be critical for user engagement.
|
||||
[53 percent of mobile site visits are abandoned](https://www.thinkwithgoogle.com/marketing-resources/data-measurement/mobile-page-speed-new-industry-benchmarks/) if pages take longer than 3 seconds to load.
|
||||
Your app may have to launch faster to engage these users before they decide to do something else.
|
||||
|
||||
With Angular Universal, you can generate landing pages for the app that look like the complete app.
|
||||
The pages are pure HTML, and can display even if JavaScript is disabled.
|
||||
The pages don't handle browser events, but they _do_ support navigation through the site using [`routerLink`](guide/router#router-link).
|
||||
|
||||
In practice, you'll serve a static version of the landing page to hold the user's attention.
|
||||
At the same time, you'll load the full Angular app behind it.
|
||||
The user perceives near-instant performance from the landing page
|
||||
and gets the full interactive experience after the full app loads.
|
||||
|
||||
{@a how-does-it-work}
|
||||
|
||||
## Universal web servers
|
||||
|
||||
A Universal web server responds to application page requests with static HTML rendered by the [Universal template engine](#universal-engine).
|
||||
The server receives and responds to HTTP requests from clients (usually browsers), and serves static assets such as scripts, CSS, and images.
|
||||
It may respond to data requests, either directly or as a proxy to a separate data server.
|
||||
|
||||
The sample web server for this guide is based on the popular [Express](https://expressjs.com/) framework.
|
||||
|
||||
<div class="alert is-helpful">
|
||||
|
||||
**Note:** _Any_ web server technology can serve a Universal app as long as it can call Universal's `renderModuleFactory()` function.
|
||||
The principles and decision points discussed here apply to any web server technology.
|
||||
|
||||
</div>
|
||||
|
||||
To make a Universal app, install the `platform-server` package, which provides server implementations
|
||||
of the DOM, `XMLHttpRequest`, and other low-level features that don't rely on a browser.
|
||||
Compile the client application with the `platform-server` module (instead of the `platform-browser` module)
|
||||
and run the resulting Universal app on a web server.
|
||||
|
||||
The server ([Node Express](https://expressjs.com/) in this guide's example)
|
||||
passes client requests for application pages to Universal's `renderModuleFactory()` function.
|
||||
|
||||
The `renderModuleFactory()` function takes as inputs a *template* HTML page (usually `index.html`),
|
||||
an Angular *module* containing components,
|
||||
and a *route* that determines which components to display.
|
||||
The route comes from the client's request to the server.
|
||||
|
||||
Each request results in the appropriate view for the requested route.
|
||||
The `renderModuleFactory()` function renders the view within the `<app>` tag of the template,
|
||||
creating a finished HTML page for the client.
|
||||
|
||||
Finally, the server returns the rendered page to the client.
|
||||
|
||||
{@a summary}
|
||||
## Preparing for server-side rendering
|
||||
|
||||
Before your app can be rendered on a server, you must make changes in the app itself, and also set up the server.
|
||||
|
||||
1. Install dependencies.
|
||||
1. Prepare your app by modifying both the app code and its configuration.
|
||||
1. Add a build target, and build a Universal bundle using the CLI with the `@nguniversal/express-engine` schematic.
|
||||
1. Set up a server to run Universal bundles.
|
||||
1. Pack and run the app on the server.
|
||||
|
||||
The following sections go into each of these main steps in more detail.
|
||||
|
||||
<div class="alert is-helpful">
|
||||
|
||||
**Note:** The [Universal tutorial](#the-example) below walks you through the steps using the Tour of Heroes sample app, and goes into more detail about what you can do and why you might want to do it.
|
||||
|
||||
To see a working version of an app with server-side rendering, clone the [Angular Universal starter](https://github.com/angular/universal-starter).
|
||||
|
||||
</div>
|
||||
|
||||
<div class="callout is-critical">
|
||||
|
||||
<header>Security for server requests</header>
|
||||
|
||||
HTTP requests issued from a browser app aren't the same as those issued by the Universal app on the server.
|
||||
Universal HTTP requests have different security requirements
|
||||
|
||||
When a browser makes an HTTP request, the server can make assumptions about cookies, XSRF headers, and so on.
|
||||
For example, the browser automatically sends authentication cookies for the current user.
|
||||
Angular Universal can't forward these credentials to a separate data server.
|
||||
If your server handles HTTP requests, you'll have to add your own security plumbing.
|
||||
|
||||
</div>
|
||||
|
||||
## Step 1: Install dependencies
|
||||
|
||||
Install `@angular/platform-server` into your project. Use the same version as the other `@angular` packages in your project. You also need `ts-loader`, `webpack-cli` for your webpack build and `@nguniversal/module-map-ngfactory-loader` to handle lazy-loading in the context of a server-render.
|
||||
|
||||
<code-example language="sh" class="code-shell">
|
||||
$ npm install --save @angular/platform-server @nguniversal/module-map-ngfactory-loader ts-loader webpack-cli
|
||||
</code-example>
|
||||
|
||||
## Step 2: Prepare your app
|
||||
|
||||
To prepare your app for Universal rendering, take the following steps:
|
||||
|
||||
* Add Universal support to your app.
|
||||
|
||||
* Create a server root module.
|
||||
|
||||
* Create a main file to export the server root module.
|
||||
|
||||
* Configure the server root module.
|
||||
|
||||
### 2a. Add Universal support to your app
|
||||
|
||||
Make your `AppModule` compatible with Universal by adding `.withServerTransition()` and an application ID to your `BrowserModule` import in `src/app/app.module.ts`.
|
||||
|
||||
<code-example format="." language="typescript" linenums="false">
|
||||
@NgModule({
|
||||
bootstrap: [AppComponent],
|
||||
imports: [
|
||||
// Add .withServerTransition() to support Universal rendering.
|
||||
// The application ID can be any identifier which is unique on
|
||||
// the page.
|
||||
BrowserModule.withServerTransition({appId: 'my-app'}),
|
||||
...
|
||||
],
|
||||
|
||||
})
|
||||
export class AppModule {}
|
||||
</code-example>
|
||||
|
||||
### 2b. Create a server root module
|
||||
|
||||
Create a module named `AppServerModule` to act as the root module when running on the server. This example places it alongside `app.module.ts` in a file named `app.server.module.ts`. The new module imports everything from the root `AppModule`, and adds `ServerModule`. It also adds `ModuleMapLoaderModule` to help make lazy-loaded routes possible during server-side renders with the Angular CLI.
|
||||
|
||||
Here's an example in `src/app/app.server.module.ts`.
|
||||
|
||||
<code-example format="." language="typescript" linenums="false">
|
||||
import {NgModule} from '@angular/core';
|
||||
import {ServerModule} from '@angular/platform-server';
|
||||
import {ModuleMapLoaderModule} from '@nguniversal/module-map-ngfactory-loader';
|
||||
|
||||
import {AppModule} from './app.module';
|
||||
import {AppComponent} from './app.component';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
// The AppServerModule should import your AppModule followed
|
||||
// by the ServerModule from @angular/platform-server.
|
||||
AppModule,
|
||||
ServerModule,
|
||||
ModuleMapLoaderModule // <-- *Important* to have lazy-loaded routes work
|
||||
],
|
||||
// Since the bootstrapped component is not inherited from your
|
||||
// imported AppModule, it needs to be repeated here.
|
||||
bootstrap: [AppComponent],
|
||||
})
|
||||
export class AppServerModule {}
|
||||
</code-example>
|
||||
|
||||
### 2c. Create a main file to export AppServerModule
|
||||
|
||||
Create a main file for your Universal bundle in the app `src/` folder to export your `AppServerModule` instance. This example calls the file `main.server.ts`.
|
||||
|
||||
<code-example format="." language="typescript" linenums="false">
|
||||
export { AppServerModule } from './app/app.server.module';
|
||||
</code-example>
|
||||
|
||||
### 2d. Create a configuration file for AppServerModule
|
||||
|
||||
Copy `tsconfig.app.json` to `tsconfig.server.json` and modify it as follows:
|
||||
|
||||
* In `"compilerOptions"`, set the `"module"` target to `"commonjs"`.
|
||||
* Add a section for `"angularCompilerOptions"` and set `"entryModule"` to point to your `AppServerModule` instance. Use the format `importPath#symbolName`. In this example, the entry module is `app/app.server.module#AppServerModule`.
|
||||
|
||||
<code-example format="." language="none" linenums="false">
|
||||
{
|
||||
"extends": "../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "../out-tsc/app",
|
||||
"baseUrl": "./",
|
||||
// Set the module format to "commonjs":
|
||||
"module": "commonjs",
|
||||
"types": []
|
||||
},
|
||||
"exclude": [
|
||||
"test.ts",
|
||||
"**/*.spec.ts"
|
||||
],
|
||||
// Add "angularCompilerOptions" with the AppServerModule you wrote
|
||||
// set as the "entryModule".
|
||||
"angularCompilerOptions": {
|
||||
"entryModule": "app/app.server.module#AppServerModule"
|
||||
}
|
||||
}
|
||||
</code-example>
|
||||
|
||||
## Step 3: Create a new build target and build the bundle
|
||||
|
||||
Open the Angular configuration file, `angular.json`, for your project, and add a new target in the `"architect"` section for the server build. The following example names the new target `"server"`.
|
||||
|
||||
<code-example format="." language="none" linenums="false">
|
||||
"architect": {
|
||||
"build": { ... }
|
||||
"server": {
|
||||
"builder": "@angular-devkit/build-angular:server",
|
||||
"options": {
|
||||
"outputPath": "dist/my-project-server",
|
||||
"main": "src/main.server.ts",
|
||||
"tsConfig": "src/tsconfig.server.json"
|
||||
}
|
||||
}
|
||||
}
|
||||
</code-example>
|
||||
|
||||
To build a server bundle for your application, use the `ng run` command, with the format `projectName#serverTarget`. In our example, there are now two targets configured, `"build"` and `"server"`.
|
||||
|
||||
<code-example format="." language="none" linenums="false">
|
||||
# This builds your project using the server target, and places the output
|
||||
# in dist/my-project-server/
|
||||
$ ng run my-project:server
|
||||
|
||||
Date: 2017-07-24T22:42:09.739Z
|
||||
Hash: 9cac7d8e9434007fd8da
|
||||
Time: 4933ms
|
||||
chunk {0} main.js (main) 9.49 kB [entry] [rendered]
|
||||
chunk {1} styles.css (styles) 0 bytes [entry] [rendered]
|
||||
</code-example>
|
||||
|
||||
## Step 4: Set up a server to run Universal bundles
|
||||
|
||||
To run a Universal bundle, you need to send it to a server.
|
||||
|
||||
The following example passes `AppServerModule` (compiled with AoT) to the `PlatformServer` method `renderModuleFactory()`, which serializes the app and returns the result to the browser.
|
||||
|
||||
<code-example format="." language="typescript" linenums="false">
|
||||
app.engine('html', (_, options, callback) => {
|
||||
renderModuleFactory(AppServerModuleNgFactory, {
|
||||
// Our index.html
|
||||
document: template,
|
||||
url: options.req.url,
|
||||
// configure DI to make lazy-loading work differently
|
||||
// (we need to instantly render the view)
|
||||
extraProviders: [
|
||||
provideModuleMap(LAZY_MODULE_MAP)
|
||||
]
|
||||
}).then(html => {
|
||||
callback(null, html);
|
||||
});
|
||||
});
|
||||
</code-example>
|
||||
|
||||
This technique gives you complete flexibility. For convenience, you can also use the `@nguniversal/express-engine` tool that has some built-in features.
|
||||
|
||||
<code-example format="." language="typescript" linenums="false">
|
||||
import { ngExpressEngine } from '@nguniversal/express-engine';
|
||||
|
||||
app.engine('html', ngExpressEngine({
|
||||
bootstrap: AppServerModuleNgFactory,
|
||||
providers: [
|
||||
provideModuleMap(LAZY_MODULE_MAP)
|
||||
]
|
||||
}));
|
||||
</code-example>
|
||||
|
||||
The following simple example implements a bare-bones Node Express server to fire everything up.
|
||||
(Note that this is for demonstration only. In a real production environment, you need to set up additional authentication and security.)
|
||||
|
||||
At the root level of your project, next to `package.json`, create a file named `server.ts` and add the following content.
|
||||
|
||||
<code-example format="." language="typescript" linenums="false">
|
||||
// These are important and needed before anything else
|
||||
import 'zone.js/dist/zone-node';
|
||||
import 'reflect-metadata';
|
||||
|
||||
import { renderModuleFactory } from '@angular/platform-server';
|
||||
import { enableProdMode } from '@angular/core';
|
||||
import { provideModuleMap } from '@nguniversal/module-map-ngfactory-loader';
|
||||
|
||||
import * as express from 'express';
|
||||
import { join } from 'path';
|
||||
import { readFileSync } from 'fs';
|
||||
|
||||
// Faster server renders w/ Prod mode (dev mode never needed)
|
||||
enableProdMode();
|
||||
|
||||
// Express server
|
||||
const app = express();
|
||||
|
||||
const PORT = process.env.PORT || 4000;
|
||||
const DIST_FOLDER = join(process.cwd(), 'dist');
|
||||
|
||||
// Our index.html we'll use as our template
|
||||
const template = readFileSync(join(DIST_FOLDER, 'browser', 'index.html')).toString();
|
||||
|
||||
const { AppServerModuleNgFactory, LAZY_MODULE_MAP } = require('./server/main');
|
||||
|
||||
|
||||
app.engine('html', (_, options, callback) => {
|
||||
renderModuleFactory(AppServerModuleNgFactory, {
|
||||
// Our index.html
|
||||
document: template,
|
||||
url: options.req.url,
|
||||
// DI so that we can get lazy-loading to work differently (since we need it to just instantly render it)
|
||||
extraProviders: [
|
||||
provideModuleMap(LAZY_MODULE_MAP)
|
||||
]
|
||||
}).then(html => {
|
||||
callback(null, html);
|
||||
});
|
||||
});
|
||||
|
||||
app.set('view engine', 'html');
|
||||
app.set('views', join(DIST_FOLDER, 'browser'));
|
||||
|
||||
// Server static files from /browser
|
||||
app.get('*.*', express.static(join(DIST_FOLDER, 'browser')));
|
||||
|
||||
// All regular routes use the Universal engine
|
||||
app.get('*', (req, res) => {
|
||||
res.render(join(DIST_FOLDER, 'browser', 'index.html'), { req });
|
||||
});
|
||||
|
||||
// Start up the Node server
|
||||
app.listen(PORT, () => {
|
||||
console.log(`Node server listening on http://localhost:${PORT}`);
|
||||
});
|
||||
</code-example>
|
||||
|
||||
## Step 5: Pack and run the app on the server
|
||||
|
||||
Set up a webpack configuration to handle the Node Express `server.ts` file and serve your application.
|
||||
|
||||
In your app root directory, create a webpack configuration file (`webpack.server.config.js`) that compiles the `server.ts` file and its dependencies into `dist/server.js`.
|
||||
|
||||
<code-example format="." language="typescript" linenums="false">
|
||||
const path = require('path');
|
||||
const webpack = require('webpack');
|
||||
|
||||
module.exports = {
|
||||
entry: { server: './server.ts' },
|
||||
resolve: { extensions: ['.js', '.ts'] },
|
||||
target: 'node',
|
||||
// this makes sure we include node_modules and other 3rd party libraries
|
||||
externals: [/(node_modules|main\..*\.js)/],
|
||||
output: {
|
||||
path: path.join(__dirname, 'dist'),
|
||||
filename: '[name].js'
|
||||
},
|
||||
module: {
|
||||
rules: [
|
||||
{ test: /\.ts$/, loader: 'ts-loader' }
|
||||
]
|
||||
},
|
||||
plugins: [
|
||||
// Temporary Fix for issue: https://github.com/angular/angular/issues/11580
|
||||
// for "WARNING Critical dependency: the request of a dependency is an expression"
|
||||
new webpack.ContextReplacementPlugin(
|
||||
/(.+)?angular(\\|\/)core(.+)?/,
|
||||
path.join(__dirname, 'src'), // location of your src
|
||||
{} // a map of your routes
|
||||
),
|
||||
new webpack.ContextReplacementPlugin(
|
||||
/(.+)?express(\\|\/)(.+)?/,
|
||||
path.join(__dirname, 'src'),
|
||||
{}
|
||||
)
|
||||
]
|
||||
}
|
||||
</code-example>
|
||||
|
||||
The project's `dist/` folder now contains both browser and server folders.
|
||||
|
||||
<code-example format="." language="none" linenums="false">
|
||||
dist/
|
||||
browser/
|
||||
server/
|
||||
</code-example>
|
||||
|
||||
To run the app on the server, type the following in a command shell.
|
||||
|
||||
<code-example format="." language="bash" linenums="false">
|
||||
node dist/server.js
|
||||
</code-example>
|
||||
|
||||
### Creating scripts
|
||||
|
||||
Now let's create a few handy scripts to help us do all of this in the future.
|
||||
You can add these in the `"scripts"` section of the `package.json`.
|
||||
|
||||
<code-example format="." language="none" linenums="false">
|
||||
"scripts": {
|
||||
"build:ssr": "npm run build:client-and-server-bundles && npm run webpack:server",
|
||||
"serve:ssr": "node dist/server.js",
|
||||
"build:client-and-server-bundles": "ng build --prod && ng run my-project:server:production",
|
||||
"webpack:server": "webpack --config webpack.server.config.js --progress --colors",
|
||||
...
|
||||
}
|
||||
</code-example>
|
||||
|
||||
To run a production build of your app with Universal on your local system, use the following command.
|
||||
|
||||
<code-example format="." language="bash" linenums="false">
|
||||
npm run build:ssr && npm run serve:ssr
|
||||
</code-example>
|
||||
|
||||
### Working around the browser APIs
|
||||
|
||||
Because a Universal `platform-server` app doesn't execute in the browser, you may have to work around some of the browser APIs and capabilities that are missing on the server.
|
||||
|
||||
For example, your server-side page can't reference browser-only native objects such as `window`, `document`, `navigator`, or `location`.
|
||||
If you don't need these on the server-rendered page, you can side-step them with conditional logic.
|
||||
Alternatively, you can find an injectable Angular abstraction over the object you need such as `Location` or `Document`;
|
||||
it may substitute adequately for the specific API that you're calling.
|
||||
If Angular doesn't provide it, you can write your own abstraction that delegates to the browser API while in the browser and to a satisfactory alternative implementation while on the server.
|
||||
|
||||
Similarly, without mouse or keyboard events, a server-side app can't rely on a user clicking a button to show a component.
|
||||
The app must determine what to render based solely on the incoming client request.
|
||||
This is a good argument for making the app [routable](guide/router).
|
||||
|
||||
Because the user of a server-rendered page can't do much more than click links,
|
||||
you should swap in the real client app as quickly as possible for a proper interactive experience.
|
||||
|
||||
{@a the-example}
|
||||
|
||||
## Universal tutorial
|
||||
|
||||
The [Tour of Heroes tutorial](tutorial) is the foundation for this walkthrough.
|
||||
|
||||
The core application files are mostly untouched, with a few exceptions described below.
|
||||
You'll add more files to support building and serving with Universal.
|
||||
|
||||
In this example, the Angular CLI compiles and bundles the Universal version of the app with the
|
||||
[Ahead-of-Time (AoT) compiler](guide/aot-compiler).
|
||||
A Node Express web server turns client requests into the HTML pages rendered by Universal.
|
||||
A Node Express web server compiles HTML pages with Universal based on client requests.
|
||||
|
||||
To create server-side app module, `app.server.module.ts`, run the following CLI command.
|
||||
To create the server-side app module, `app.server.module.ts`, run the following CLI command.
|
||||
|
||||
<code-example format="." language="bash">
|
||||
|
||||
@ -512,34 +57,203 @@ webpack.server.config.js <i>* webpack server configuration</i>
|
||||
</code-example>
|
||||
|
||||
The files marked with `*` are new and not in the original tutorial sample.
|
||||
This guide covers them in the sections below.
|
||||
|
||||
### Universal in action
|
||||
|
||||
To start rendering your app with Universal on your local system, use the following command.
|
||||
|
||||
<code-example format="." language="bash" linenums="false">
|
||||
npm run build:ssr && npm run serve:ssr
|
||||
</code-example>
|
||||
|
||||
Open a browser and navigate to http://localhost:4000/.
|
||||
You should see the familiar Tour of Heroes dashboard page.
|
||||
|
||||
Navigation via `routerLinks` works correctly because they use the native anchor (`<a>`) tags.
|
||||
You can go from the Dashboard to the Heroes page and back.
|
||||
You can click a hero on the Dashboard page to display its Details page.
|
||||
|
||||
If you throttle your network speed so that the client-side scripts take longer to download (instructions below),
|
||||
you'll notice:
|
||||
* Clicking a hero on the Heroes page does nothing.
|
||||
* You can't add or delete a hero.
|
||||
* The search box on the Dashboard page is ignored.
|
||||
* The *Back* and *Save* buttons on the Details page don't work.
|
||||
|
||||
User events other than `routerLink` clicks aren't supported.
|
||||
You must wait for the full client app to bootstrap and run, or buffer the events using libraries like
|
||||
[preboot](https://github.com/angular/preboot), which allow you to replay these events once the client-side scripts load.
|
||||
|
||||
The transition from the server-rendered app to the client app happens quickly on a development machine, but you should
|
||||
always test your apps in real-world scenarios.
|
||||
|
||||
You can simulate a slower network to see the transition more clearly as follows:
|
||||
|
||||
1. Open the Chrome Dev Tools and go to the Network tab.
|
||||
1. Find the [Network Throttling](https://developers.google.com/web/tools/chrome-devtools/network-performance/reference#throttling)
|
||||
dropdown on the far right of the menu bar.
|
||||
1. Try one of the "3G" speeds.
|
||||
|
||||
The server-rendered app still launches quickly but the full client app may take seconds to load.
|
||||
|
||||
{@a why-do-it}
|
||||
## Why use server-side rendering?
|
||||
|
||||
There are three main reasons to create a Universal version of your app.
|
||||
|
||||
1. Facilitate web crawlers through [search engine optimization (SEO)](https://static.googleusercontent.com/media/www.google.com/en//webmasters/docs/search-engine-optimization-starter-guide.pdf)
|
||||
1. Improve performance on mobile and low-powered devices
|
||||
1. Show the first page quickly with a [first-contentful paint (FCP)](https://developers.google.com/web/tools/lighthouse/audits/first-contentful-paint)
|
||||
|
||||
{@a seo}
|
||||
{@a web-crawlers}
|
||||
### Facilitate web crawlers (SEO)
|
||||
|
||||
Google, Bing, Facebook, Twitter, and other social media sites rely on web crawlers to index your application content and
|
||||
make that content searchable on the web.
|
||||
These web crawlers may be unable to navigate and index your highly interactive Angular application as a human user could do.
|
||||
|
||||
Angular Universal can generate a static version of your app that is easily searchable, linkable, and navigable without JavaScript.
|
||||
Universal also makes a site preview available since each URL returns a fully rendered page.
|
||||
|
||||
{@a no-javascript}
|
||||
### Improve performance on mobile and low-powered devices
|
||||
|
||||
Some devices don't support JavaScript or execute JavaScript so poorly that the user experience is unacceptable.
|
||||
For these cases, you may require a server-rendered, no-JavaScript version of the app.
|
||||
This version, however limited, may be the only practical alternative for
|
||||
people who otherwise couldn't use the app at all.
|
||||
|
||||
{@a startup-performance}
|
||||
### Show the first page quickly
|
||||
|
||||
Displaying the first page quickly can be critical for user engagement.
|
||||
[53 percent of mobile site visits are abandoned](https://www.thinkwithgoogle.com/marketing-resources/data-measurement/mobile-page-speed-new-industry-benchmarks/)
|
||||
if pages take longer than 3 seconds to load.
|
||||
Your app may have to launch faster to engage these users before they decide to do something else.
|
||||
|
||||
With Angular Universal, you can generate landing pages for the app that look like the complete app.
|
||||
The pages are pure HTML, and can display even if JavaScript is disabled.
|
||||
The pages don't handle browser events, but they _do_ support navigation through the site using [`routerLink`](guide/router#router-link).
|
||||
|
||||
In practice, you'll serve a static version of the landing page to hold the user's attention.
|
||||
At the same time, you'll load the full Angular app behind it.
|
||||
The user perceives near-instant performance from the landing page
|
||||
and gets the full interactive experience after the full app loads.
|
||||
|
||||
{@a how-does-it-work}
|
||||
## Universal web servers
|
||||
|
||||
A Universal web server responds to application page requests with static HTML rendered by the [Universal template engine](#universal-engine).
|
||||
The server receives and responds to HTTP requests from clients (usually browsers), and serves static assets such as scripts, CSS, and images.
|
||||
It may respond to data requests, either directly or as a proxy to a separate data server.
|
||||
|
||||
The sample web server for this guide is based on the popular [Express](https://expressjs.com/) framework.
|
||||
|
||||
<div class="alert is-helpful">
|
||||
|
||||
**Note:** _Any_ web server technology can serve a Universal app as long as it can call Universal's `renderModuleFactory()` function.
|
||||
The principles and decision points discussed here apply to any web server technology.
|
||||
|
||||
</div>
|
||||
|
||||
Universal applications use the Angular `platform-server` package (as opposed to `platform-browser`), which provides
|
||||
server implementations of the DOM, `XMLHttpRequest`, and other low-level features that don't rely on a browser.
|
||||
|
||||
The server ([Node Express](https://expressjs.com/) in this guide's example)
|
||||
passes client requests for application pages to the NgUniversal `ngExpressEngine`. Under the hood, this
|
||||
calls Universal's `renderModuleFactory()` function, while providing caching and other helpful utilities.
|
||||
|
||||
The `renderModuleFactory()` function takes as inputs a *template* HTML page (usually `index.html`),
|
||||
an Angular *module* containing components,
|
||||
and a *route* that determines which components to display.
|
||||
The route comes from the client's request to the server.
|
||||
|
||||
Each request results in the appropriate view for the requested route.
|
||||
The `renderModuleFactory()` function renders the view within the `<app>` tag of the template,
|
||||
creating a finished HTML page for the client.
|
||||
|
||||
Finally, the server returns the rendered page to the client.
|
||||
|
||||
### Working around the browser APIs
|
||||
|
||||
Because a Universal app doesn't execute in the browser, some of the browser APIs and capabilities may be missing on the server.
|
||||
|
||||
For example, server-side applications can't reference browser-only global objects such as `window`, `document`, `navigator`, or `location`.
|
||||
|
||||
Angular provides some injectable abstractions over these objects, such as [`Location`](api/common/Location)
|
||||
or [`DOCUMENT`](api/common/DOCUMENT); it may substitute adequately for these APIs.
|
||||
If Angular doesn't provide it, it's possible to write new abstractions that delegate to the browser APIs while in the browser
|
||||
and to an alternative implementation while on the server (aka shimming).
|
||||
|
||||
Similarly, without mouse or keyboard events, a server-side app can't rely on a user clicking a button to show a component.
|
||||
The app must determine what to render based solely on the incoming client request.
|
||||
This is a good argument for making the app [routable](guide/router).
|
||||
|
||||
{@a http-urls}
|
||||
|
||||
### Using absolute URLs for server requests
|
||||
|
||||
The tutorial's `HeroService` and `HeroSearchService` delegate to the Angular `HttpClient` module to fetch application data.
|
||||
These services send requests to _relative_ URLs such as `api/heroes`.
|
||||
In a Universal app, HTTP URLs must be _absolute_ (for example, `https://my-server.com/api/heroes`) even when the Universal web server is capable of handling relative requests.
|
||||
This means you need to change your services to make requests with absolute URLs when running on the server and with relative URLs when running in the browser.
|
||||
In a Universal app, HTTP URLs must be _absolute_ (for example, `https://my-server.com/api/heroes`).
|
||||
This means you need to change your services to make requests with absolute URLs when running on the server and with relative
|
||||
URLs when running in the browser.
|
||||
|
||||
One solution is to provide the server's runtime origin under Angular's [`APP_BASE_HREF`](api/common/APP_BASE_HREF) token,
|
||||
inject it into the service, and prepend the origin to the request URL.
|
||||
One solution is to provide the full URL to your application on the server, and write an interceptor that can retrieve this
|
||||
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 changing the `HeroService` constructor to take a second `origin` parameter that is optionally injected via the `APP_BASE_HREF` token.
|
||||
Start by creating an [HttpInterceptor](api/common/http/HttpInterceptor):
|
||||
|
||||
<code-example format="." language="typescript">
|
||||
|
||||
import {Injectable, Inject, Optional} from '@angular/core';
|
||||
import {HttpInterceptor, HttpHandler, HttpRequest, HttpHeaders} from '@angular/common/http';
|
||||
import {Request} from 'express';
|
||||
import {REQUEST} from '@nguniversal/express-engine/tokens';
|
||||
|
||||
@Injectable()
|
||||
export class UniversalInterceptor implements HttpInterceptor {
|
||||
|
||||
constructor(@Optional() @Inject(REQUEST) protected request: Request) {}
|
||||
|
||||
intercept(req: HttpRequest<any>, next: HttpHandler) {
|
||||
let serverReq: HttpRequest<any> = req;
|
||||
if (this.request) {
|
||||
let newUrl = `${this.request.protocol}://${this.request.get('host')}`;
|
||||
if (!req.url.startsWith('/')) {
|
||||
newUrl += '/';
|
||||
}
|
||||
newUrl += req.url;
|
||||
serverReq = req.clone({url: newUrl});
|
||||
}
|
||||
return next.handle(serverReq);
|
||||
}
|
||||
}
|
||||
|
||||
<code-example path="universal/src/app/hero.service.ts" region="ctor" header="src/app/hero.service.ts (constructor with optional origin)">
|
||||
</code-example>
|
||||
|
||||
The constructor uses the `@Optional()` directive to prepend the origin to `heroesUrl` _if it exists_.
|
||||
You don't provide `APP_BASE_HREF` in the browser version, so `heroesUrl` remains relative.
|
||||
Next, provide the interceptor in the providers for the server `AppModule` (app.server.module.ts):
|
||||
|
||||
<div class="alert is-helpful">
|
||||
<code-example format="." language="typescript">
|
||||
|
||||
**Note:** You can ignore `APP_BASE_HREF` in the browser if you've specified `<base href="/">` in the `index.html` file to satisfy the router's need for a base address (as the tutorial sample does).
|
||||
import {HTTP_INTERCEPTORS} from '@angular/common/http';
|
||||
import {UniversalInterceptor} from './universal-interceptor';
|
||||
|
||||
</div>
|
||||
@NgModule({
|
||||
...
|
||||
providers: [{
|
||||
provide: HTTP_INTERCEPTORS,
|
||||
useClass: UniversalInterceptor,
|
||||
multi: true
|
||||
}],
|
||||
})
|
||||
export class AppServerModule {}
|
||||
|
||||
</code-example>
|
||||
|
||||
Now, on every HTTP request made on the server, this interceptor will fire and replace the request URL with the absolute
|
||||
URL provided in the Express `Request` object.
|
||||
|
||||
{@a universal-engine}
|
||||
### Universal template engine
|
||||
@ -549,30 +263,35 @@ The important bit in the `server.ts` file is the `ngExpressEngine()` function.
|
||||
<code-example path="universal/server.ts" header="server.ts" region="ngExpressEngine">
|
||||
</code-example>
|
||||
|
||||
The `ngExpressEngine()` function is a wrapper around Universal's `renderModuleFactory()` function which turns a client's requests into server-rendered HTML pages.
|
||||
You'll call that function within a _template engine_ that's appropriate for your server stack.
|
||||
The `ngExpressEngine()` function is a wrapper around Universal's `renderModuleFactory()` function which turns a client's
|
||||
requests into server-rendered HTML pages.
|
||||
|
||||
* The first parameter is `AppServerModule`.
|
||||
It's the bridge between the Universal server-side renderer and your application.
|
||||
It's the bridge between the Universal server-side renderer and the Angular application.
|
||||
|
||||
* The second parameter, `extraProviders`, is optional. It lets you specify dependency providers that apply only when running on this server.
|
||||
* The second parameter, `extraProviders`, is optional. It lets you specify dependency providers that apply only when
|
||||
running on this server.
|
||||
You can do this when your app needs information that can only be determined by the currently running server instance.
|
||||
The required information in this case is the running server's *origin*, provided under the `APP_BASE_HREF` token, so that the app can [calculate absolute HTTP URLs](#http-urls).
|
||||
One example could be the running server's *origin*, which could be used to [calculate absolute HTTP URLs](#http-urls) if
|
||||
not using the `Request` token as shown above.
|
||||
|
||||
The `ngExpressEngine()` function returns a `Promise` callback that resolves to the rendered page.
|
||||
It's up to your engine to decide what to do with that page.
|
||||
It's up to the engine to decide what to do with that page.
|
||||
This engine's `Promise` callback returns the rendered page to the web server,
|
||||
which then forwards it to the client in the HTTP response.
|
||||
|
||||
<div class="alert is-helpful">
|
||||
|
||||
**Note:** These wrappers help hide the complexity of the `renderModuleFactory()` function. There are more wrappers for different backend technologies
|
||||
at the [Universal repository](https://github.com/angular/universal).
|
||||
**Note:** These wrappers help hide the complexity of the `renderModuleFactory()` function. There are more wrappers
|
||||
for different backend technologies at the [Universal repository](https://github.com/angular/universal).
|
||||
|
||||
</div>
|
||||
|
||||
### Filtering request URLs
|
||||
|
||||
NOTE: the basic behavior described below is handled automatically when using the NgUniversal Express schematic, this
|
||||
is helpful when trying to understand the underlying behavior or replicate it without using the schematic.
|
||||
|
||||
The web server must distinguish _app page requests_ from other kinds of requests.
|
||||
|
||||
It's not as simple as intercepting a request to the root address `/`.
|
||||
@ -586,11 +305,11 @@ All static asset requests have a file extension (such as `main.js` or `/node_mod
|
||||
|
||||
Because we use routing, we can easily recognize the three types of requests and handle them differently.
|
||||
|
||||
1. Data request - request URL that begins `/api`.
|
||||
2. App navigation - request URL with no file extension.
|
||||
3. Static asset - all other requests.
|
||||
1. **Data request**: request URL that begins `/api`.
|
||||
1. **App navigation**: request URL with no file extension.
|
||||
1. **Static asset**: all other requests.
|
||||
|
||||
A Node Express server is a pipeline of middleware that filters and processes URL requests one after the other.
|
||||
A Node Express server is a pipeline of middleware that filters and processes requests one after the other.
|
||||
You configure the Node Express server pipeline with calls to `app.get()` like this one for data requests.
|
||||
|
||||
<code-example path="universal/server.ts" header="server.ts (data URL)" region="data-request" linenums="false">
|
||||
@ -616,41 +335,11 @@ The following code filters for request URLs with no extensions and treats them a
|
||||
A single `app.use()` treats all other URLs as requests for static assets
|
||||
such as JavaScript, image, and style files.
|
||||
|
||||
To ensure that clients can only download the files that they are permitted to see, put all client-facing asset files in the `/dist` folder and only honor requests for files from the `/dist` folder.
|
||||
To ensure that clients can only download the files that they are permitted to see, put all client-facing asset files in
|
||||
the `/dist` folder and only honor requests for files from the `/dist` folder.
|
||||
|
||||
The following Node Express code routes all remaining requests to `/dist`, and returns a `404 - NOT FOUND` error if the file isn't found.
|
||||
The following Node Express code routes all remaining requests to `/dist`, and returns a `404 - NOT FOUND` error if the
|
||||
file isn't found.
|
||||
|
||||
<code-example path="universal/server.ts" header="server.ts (static files)" region="static" linenums="false">
|
||||
</code-example>
|
||||
|
||||
|
||||
### Universal in action
|
||||
|
||||
Open a browser to http://localhost:4000/.
|
||||
You should see the familiar Tour of Heroes dashboard page.
|
||||
|
||||
Navigation via `routerLinks` works correctly.
|
||||
You can go from the Dashboard to the Heroes page and back.
|
||||
You can click a hero on the Dashboard page to display its Details page.
|
||||
|
||||
Notice, however, that clicks, mouse-moves, and keyboard entries are inert.
|
||||
|
||||
* Clicking a hero on the Heroes page does nothing.
|
||||
* You can't add or delete a hero.
|
||||
* The search box on the Dashboard page is ignored.
|
||||
* The *Back* and *Save* buttons on the Details page don't work.
|
||||
|
||||
User events other than `routerLink` clicks aren't supported.
|
||||
You must wait for the full client app to arrive.
|
||||
It won't arrive until you compile the client app
|
||||
and move the output into the `dist/` folder.
|
||||
|
||||
The transition from the server-rendered app to the client app happens quickly on a development machine.
|
||||
You can simulate a slower network to see the transition more clearly and
|
||||
better appreciate the launch-speed advantage of a Universal app running on a low-powered, poorly connected device.
|
||||
|
||||
Open the Chrome Dev Tools and go to the Network tab.
|
||||
Find the [Network Throttling](https://developers.google.com/web/tools/chrome-devtools/network-performance/reference#throttling) dropdown on the far right of the menu bar.
|
||||
|
||||
Try one of the "3G" speeds.
|
||||
The server-rendered app still launches quickly but the full client app may take seconds to load.
|
||||
|
BIN
aio/content/images/bios/CaerusKaru.jpg
Normal file
After Width: | Height: | Size: 23 KiB |
BIN
aio/content/images/bios/JiaLiPassion.jpg
Normal file
After Width: | Height: | Size: 38 KiB |
BIN
aio/content/images/bios/alan-agius4.jpg
Normal file
After Width: | Height: | Size: 31 KiB |
Before Width: | Height: | Size: 7.0 KiB After Width: | Height: | Size: 14 KiB |
BIN
aio/content/images/bios/cexbrayat.jpg
Normal file
After Width: | Height: | Size: 34 KiB |
BIN
aio/content/images/bios/clydin.jpg
Normal file
After Width: | Height: | Size: 15 KiB |
BIN
aio/content/images/bios/denny.jpg
Normal file
After Width: | Height: | Size: 44 KiB |
BIN
aio/content/images/bios/gregmagolan.jpg
Normal file
After Width: | Height: | Size: 14 KiB |
BIN
aio/content/images/bios/jennifer.jpg
Normal file
After Width: | Height: | Size: 28 KiB |
BIN
aio/content/images/bios/judy.png
Normal file
After Width: | Height: | Size: 127 KiB |
BIN
aio/content/images/bios/justinschwartzenberger.jpg
Normal file
After Width: | Height: | Size: 24 KiB |
BIN
aio/content/images/bios/kapunahele.jpg
Normal file
After Width: | Height: | Size: 23 KiB |
BIN
aio/content/images/bios/keilla.jpg
Normal file
After Width: | Height: | Size: 18 KiB |
BIN
aio/content/images/bios/leonardo.jpg
Normal file
After Width: | Height: | Size: 32 KiB |
BIN
aio/content/images/bios/luixaviles.jpg
Normal file
After Width: | Height: | Size: 28 KiB |
BIN
aio/content/images/bios/mira.jpg
Normal file
After Width: | Height: | Size: 29 KiB |
BIN
aio/content/images/bios/samjulien.jpg
Normal file
After Width: | Height: | Size: 22 KiB |
BIN
aio/content/images/bios/sharon.jpg
Normal file
After Width: | Height: | Size: 18 KiB |
BIN
aio/content/images/bios/sidd-ajmera.jpg
Normal file
After Width: | Height: | Size: 28 KiB |
BIN
aio/content/images/bios/sreevani.jpg
Normal file
After Width: | Height: | Size: 24 KiB |
BIN
aio/content/images/bios/tomastrajan.jpg
Normal file
After Width: | Height: | Size: 22 KiB |