Compare commits
353 Commits
Author | SHA1 | Date | |
---|---|---|---|
9b812dc729 | |||
2eee8d59fe | |||
f0dc8cfc05 | |||
79471f7610 | |||
54f68c342b | |||
86bfb0172b | |||
52186165ee | |||
e9ae885982 | |||
8a1bd2c401 | |||
018b695ef2 | |||
1d193df360 | |||
0a46b2acfb | |||
b63aa4e7ef | |||
6aae19633b | |||
00242e8d6d | |||
4ea231fdbe | |||
6bad2ca586 | |||
dabf1325ed | |||
987b18596b | |||
f470e69fba | |||
980bcaf176 | |||
48f7f65bf4 | |||
7fe95b1882 | |||
266871baf4 | |||
bc54ecf66e | |||
274833ee94 | |||
ff5934597a | |||
09b60ed36a | |||
0bb83b2ba6 | |||
de04741863 | |||
8ea91887ca | |||
ff184b56eb | |||
15d138fdd3 | |||
1699b54860 | |||
07f6b2c018 | |||
4a193580fe | |||
9c222350f9 | |||
c36edffe2a | |||
13b984d72b | |||
f00eff5916 | |||
31ee45efdc | |||
b68f2d6fa5 | |||
d8def6d728 | |||
e8f464409c | |||
a73b8a62c8 | |||
2fe8f2b1e0 | |||
f1c08c6918 | |||
bf9de8ca53 | |||
df37c47ac5 | |||
788d19c036 | |||
8492499fe7 | |||
48174010a1 | |||
1460e46ba1 | |||
a7ff7d8d27 | |||
eb970b4626 | |||
18d301258a | |||
6a01fa532c | |||
e190a7e9de | |||
53c6425954 | |||
c198dc6c19 | |||
d1b26dd3a1 | |||
ce7131f3fb | |||
934702892e | |||
8a830a72e2 | |||
2abb54c4a1 | |||
44632bb700 | |||
e33b382ab4 | |||
c4da400db4 | |||
c40e6b2edc | |||
978fc27314 | |||
b9f2bbb7d1 | |||
9654d646ae | |||
7f214499e8 | |||
a3d55a9d27 | |||
e222b8761e | |||
1435c0b3a9 | |||
0a7aebbcf5 | |||
bd7f91feac | |||
3bbc89b958 | |||
52d98e563d | |||
b279f8bd62 | |||
954e34cc91 | |||
e1f6d15387 | |||
cbb3794931 | |||
77a5790a9d | |||
4ca401c394 | |||
3dcd5ebdbc | |||
49307f0595 | |||
762fc28fee | |||
338e58c278 | |||
7e2ed89208 | |||
56a3dcf44c | |||
501bacdcf6 | |||
c5f2979a87 | |||
fe02462b5f | |||
3000d19ad1 | |||
dcf9f05c9a | |||
ba56f3c15b | |||
865ad56e9a | |||
5cda2a041b | |||
6a484853a9 | |||
e6ac289518 | |||
4e8614bb92 | |||
9ace748d3a | |||
08c38a1f99 | |||
9aeef0afdb | |||
8aef446373 | |||
f7ee91a17c | |||
8ae0afd3f8 | |||
be82270493 | |||
4cef2c1236 | |||
26e3615e19 | |||
65f20107fe | |||
5a7bcd1862 | |||
152ea36c80 | |||
ea2adb104b | |||
19caace2ab | |||
ccc1c27461 | |||
97268b95f7 | |||
7c41abe64a | |||
991e138650 | |||
d00b421402 | |||
06ffed5141 | |||
5a15126520 | |||
9e4b2f1a4f | |||
393529d3b5 | |||
81b75590d7 | |||
151f0f4b2c | |||
f88f941008 | |||
0e17958640 | |||
e82b45c8a7 | |||
8422ef2a93 | |||
cdf586d0d2 | |||
3828c89792 | |||
0f352b6350 | |||
06bdecffb9 | |||
99ddec152b | |||
31aba96fcb | |||
facce2c9fd | |||
edaf058548 | |||
4bff2d0c8c | |||
cfc608aaf3 | |||
6d9f5fcddb | |||
b19a05c25a | |||
bd75b3b7b3 | |||
1c67b9063b | |||
9a9ae60e0e | |||
ae6fa9260a | |||
f8fa2f2a6c | |||
f3ee9a6144 | |||
d076c51455 | |||
38a7e2a775 | |||
c61c14a127 | |||
652b0959a8 | |||
002fbddcf1 | |||
4761d40a41 | |||
608e0f0d67 | |||
45718b3b21 | |||
b5756bcc39 | |||
60f0462049 | |||
9fd22959d3 | |||
d5dd907396 | |||
5a6bf7de0e | |||
d135bb5e6e | |||
8b9a3e49f2 | |||
44e55fe2e4 | |||
063cc308b6 | |||
c567135dd5 | |||
361f181c8c | |||
b40844523b | |||
c8af830ec8 | |||
e2c9ddb33e | |||
04196ec2d0 | |||
7323072c5c | |||
f0c5400cab | |||
3f2f937c9f | |||
256fe2421a | |||
e6f27bcb0c | |||
8abcf04a5e | |||
bf38df4eb9 | |||
79a025306a | |||
62c936aa49 | |||
77e1b34d91 | |||
26cb128b3c | |||
fdbe07982a | |||
c0e3209915 | |||
70880f0cea | |||
221a5ba634 | |||
68b08cf782 | |||
10560a0872 | |||
561e01ddfd | |||
d1345c78f0 | |||
276f9067c8 | |||
0e90a4263b | |||
e4cc1398aa | |||
8d1f993ceb | |||
df1ab49893 | |||
df03fec8a6 | |||
c01cae22e1 | |||
5a46f94987 | |||
5716605d52 | |||
8043e3131c | |||
54f7245081 | |||
5de24b6dfe | |||
4dc4d7f30a | |||
fcea1a3c22 | |||
8bc4da8665 | |||
a5a2d525ae | |||
4690dcecac | |||
722b2fa6ed | |||
124d1abf19 | |||
736d3ef820 | |||
9b0ad347e1 | |||
d2598ace0a | |||
4a25e4cf95 | |||
3f67bf208d | |||
d85476fd8c | |||
9763edf829 | |||
b6ae54c547 | |||
3de26a84ff | |||
8022d3691b | |||
581336a918 | |||
effc58086c | |||
766615c8a1 | |||
ede4246663 | |||
bb8a6abbf2 | |||
32daa930d0 | |||
b6aa99d3a7 | |||
e10b213784 | |||
a67cf99b0c | |||
04d04fd147 | |||
17361d2b2c | |||
9b88920aa9 | |||
b12e76d1d3 | |||
af001a8cbd | |||
db64b014f8 | |||
7e34975bb0 | |||
0fa48e8c00 | |||
fde3f467e2 | |||
7b378a6920 | |||
3136d9ff2e | |||
325e6cf557 | |||
381d7c4e44 | |||
1b6f3c1ead | |||
c38349127c | |||
a4817729a2 | |||
61c343e3eb | |||
24e6c1e80d | |||
42fc5c9b33 | |||
33e7b285ca | |||
74afdc37da | |||
3c1ffba0ad | |||
14c0017db8 | |||
4878f4890b | |||
b7edef0cd3 | |||
39fa937ab9 | |||
2977829c67 | |||
dbe845e048 | |||
e562acc884 | |||
e295c6a0ae | |||
10b43355f8 | |||
d4e7587bd8 | |||
cabe03cf6d | |||
0cefa9e342 | |||
e073daa48e | |||
994d48a96e | |||
806f8118c8 | |||
dd299f9eb2 | |||
49ec3f312c | |||
71eba450e6 | |||
192f108b0f | |||
dd8651db73 | |||
5bbbe3f684 | |||
a71d8a837b | |||
e8ceae14e1 | |||
c3246e6f16 | |||
685753361e | |||
6b07711f96 | |||
12fb639b7d | |||
644925fd0c | |||
bcc72b0924 | |||
e1091b2ba8 | |||
08841e31d9 | |||
d1fcc2bc13 | |||
fac00442d2 | |||
b8cbcbcf49 | |||
1ed45bd783 | |||
82fd1920b1 | |||
f6d7271ec7 | |||
c1f3faf1df | |||
97202278f9 | |||
132f01c5ca | |||
6a987f1b9c | |||
548b003ed3 | |||
48dc41de01 | |||
817c2b49bc | |||
fed07c735c | |||
390cac6874 | |||
8eb0b8bd40 | |||
d7283c6085 | |||
3fe3a84a4b | |||
28e4187bd6 | |||
7cbc69c890 | |||
1dc134bc6b | |||
6a61d37f95 | |||
8d2e92bcfe | |||
5038f5c909 | |||
6eeca70043 | |||
9f68c35fa9 | |||
21418ea109 | |||
02d8b4ed3c | |||
6748392edc | |||
d9fd301157 | |||
71c5d80ce7 | |||
9798229fde | |||
4b2fcfd5dc | |||
b4d291aa7a | |||
b0ecafdc2f | |||
6816bb62d7 | |||
4b05b8cea0 | |||
a0728aedf7 | |||
ea96f6112a | |||
b706800ea8 | |||
c6f95b1d70 | |||
d896126604 | |||
a20da5ddcc | |||
18878600ba | |||
d7e10f3f7e | |||
4d044ea5b2 | |||
e2d1e0cd98 | |||
4e056580bb | |||
e4c2e6a904 | |||
a50989832d | |||
525307b6a3 | |||
a3ab76b216 | |||
f2265d4b46 | |||
d3ac709b99 | |||
908d43a5bb | |||
71cdb0a08e | |||
c7fbbdfa99 | |||
10e4ab7712 | |||
5114c23c21 | |||
c3e585d7eb | |||
0776daec88 | |||
615e1a58b2 | |||
606758357e | |||
ba2a3595c6 | |||
a50bfe5054 | |||
c8983bc367 | |||
8d6d2c6704 | |||
6711f22e62 | |||
8dd9192fe3 | |||
870e0dab48 |
@ -1,4 +1,3 @@
|
||||
.git
|
||||
node_modules
|
||||
dist
|
||||
aio/content
|
||||
|
42
.bazelrc
42
.bazelrc
@ -1,3 +1,14 @@
|
||||
###############################
|
||||
# Typescript / Angular / Sass #
|
||||
###############################
|
||||
|
||||
# Make compilation fast, by keeping a few copies of the compilers
|
||||
# running as daemons, and cache SourceFile AST's to reduce parse time.
|
||||
build --strategy=AngularTemplateCompile=worker
|
||||
# TODO(alexeagle): re-enable after fixing worker instability with rxjs typings
|
||||
# build --strategy=TypeScriptCompile=worker
|
||||
build --strategy=TypeScriptCompile=standalone
|
||||
|
||||
# Enable debugging tests with --config=debug
|
||||
test:debug --test_arg=--node_options=--inspect-brk --test_output=streamed --test_strategy=exclusive --test_timeout=9999 --nocache_test_results
|
||||
|
||||
@ -74,6 +85,12 @@ query --output=label_kind
|
||||
# By default, failing tests don't print any output, it goes to the log file
|
||||
test --test_output=errors
|
||||
|
||||
# Show which actions are run under workers,
|
||||
# and print all the actions running in parallel.
|
||||
# Helps to demonstrate that bazel uses all the cores on the machine.
|
||||
build --experimental_ui
|
||||
test --experimental_ui
|
||||
|
||||
################################
|
||||
# Settings for CircleCI #
|
||||
################################
|
||||
@ -137,31 +154,6 @@ build:remote --bes_results_url="https://source.cloud.google.com/results/invocati
|
||||
# This allows us to avoid installing a second copy of node_modules
|
||||
common --experimental_allow_incremental_repository_updates
|
||||
|
||||
# This option is changed to true in Bazel 0.27 and exposes a possible
|
||||
# regression in Bazel 0.27.0.
|
||||
# Error observed is in npm_package target `//packages/common/locales:package`:
|
||||
# ```
|
||||
# ERROR: /home/circleci/ng/packages/common/locales/BUILD.bazel:13:1: Assembling
|
||||
# npm package packages/common/locales/package failed: No usable spawn strategy found
|
||||
# for spawn with mnemonic SkylarkAction. Your --spawn_strategyor --strategy flags
|
||||
# are probably too strict. Visit https://github.com/bazelbuild/bazel/issues/7480 for
|
||||
# migration advises
|
||||
# ```
|
||||
# Suspect is https://github.com/bazelbuild/rules_nodejs/blob/master/internal/npm_package/npm_package.bzl#L75-L82:
|
||||
# ```
|
||||
# execution_requirements = {
|
||||
# # Never schedule this action remotely because it's not computationally expensive.
|
||||
# # It just copies files into a directory; it's not worth copying inputs and outputs to a remote worker.
|
||||
# # Also don't run it in a sandbox, because it resolves an absolute path to the bazel-out directory
|
||||
# # allowing the .pack and .publish runnables to work with no symlink_prefix
|
||||
# # See https://github.com/bazelbuild/rules_nodejs/issues/187
|
||||
# "local": "1",
|
||||
# },
|
||||
# ```
|
||||
build --incompatible_list_based_execution_strategy_selection=false
|
||||
test --incompatible_list_based_execution_strategy_selection=false
|
||||
run --incompatible_list_based_execution_strategy_selection=false
|
||||
|
||||
####################################################
|
||||
# User bazel configuration
|
||||
# NOTE: This needs to be the *last* entry in the config.
|
||||
|
42
.buildkite/Dockerfile
Normal file
42
.buildkite/Dockerfile
Normal file
@ -0,0 +1,42 @@
|
||||
# Heavily based on https://github.com/StefanScherer/dockerfiles-windows/ images.
|
||||
# Combines the node windowsservercore image with the Bazel Prerequisites (https://docs.bazel.build/versions/master/install-windows.html).
|
||||
# msys install taken from https://github.com/StefanScherer/dockerfiles-windows/issues/30
|
||||
# VS redist install taken from https://github.com/StefanScherer/dockerfiles-windows/blob/master/apache/Dockerfile
|
||||
# The nanoserver image won't work because MSYS2 does not run in it https://github.com/Alexpux/MSYS2-packages/issues/1493
|
||||
|
||||
# Before building this image, you must locally build node-windows:10.13.0-windowsservercore-1803.
|
||||
# Clone https://github.com/StefanScherer/dockerfiles-windows/commit/4ce7101a766b9b880ac262479dd9126b64d656cf and build using
|
||||
# docker build -t node-windows:10.13.0-windowsservercore-1803 --build-arg core=microsoft/windowsservercore:1803 --build-arg target=microsoft/windowsservercore:1803 .
|
||||
FROM node-windows:10.13.0-windowsservercore-1803
|
||||
|
||||
SHELL ["powershell", "-Command", "$ErrorActionPreference = 'Stop'; $ProgressPreference = 'SilentlyContinue';"]
|
||||
|
||||
# Install 7zip to extract msys2
|
||||
RUN Invoke-WebRequest -UseBasicParsing 'https://www.7-zip.org/a/7z1805-x64.exe' -OutFile 7z.exe
|
||||
# For some reason the last letter in the destination directory is lost. So '/D=C:\\7zip0' will extract to '/D=C:\\7zip'.
|
||||
RUN Start-Process -FilePath 'C:\\7z.exe' -ArgumentList '/S', '/D=C:\\7zip0' -NoNewWindow -Wait
|
||||
|
||||
# Extract msys2
|
||||
RUN Invoke-WebRequest -UseBasicParsing 'http://repo.msys2.org/distrib/x86_64/msys2-base-x86_64-20180531.tar.xz' -OutFile msys2.tar.xz
|
||||
RUN Start-Process -FilePath 'C:\\7zip\\7z' -ArgumentList 'e', 'msys2.tar.xz' -Wait
|
||||
RUN Start-Process -FilePath 'C:\\7zip\\7z' -ArgumentList 'x', 'msys2.tar', '-oC:\\' -Wait
|
||||
RUN Remove-Item msys2.tar.xz
|
||||
RUN Remove-Item msys2.tar
|
||||
RUN Remove-Item 7z.exe
|
||||
RUN Remove-Item -Recurse 7zip
|
||||
|
||||
# Add MSYS2 to PATH, and set BAZEL_SH
|
||||
RUN [Environment]::SetEnvironmentVariable('Path', $env:Path + ';C:\msys64\usr\bin', [System.EnvironmentVariableTarget]::Machine)
|
||||
RUN [Environment]::SetEnvironmentVariable('BAZEL_SH', 'C:\msys64\usr\bin\bash.exe', [System.EnvironmentVariableTarget]::Machine)
|
||||
|
||||
# Install Microsoft Visual C++ Redistributable for Visual Studio 2015
|
||||
RUN Invoke-WebRequest -UseBasicParsing 'https://download.microsoft.com/download/9/3/F/93FCF1E7-E6A4-478B-96E7-D4B285925B00/vc_redist.x64.exe' -OutFile vc_redist.x64.exe
|
||||
RUN Start-Process 'c:\\vc_redist.x64.exe' -ArgumentList '/Install', '/Passive', '/NoRestart' -NoNewWindow -Wait
|
||||
RUN Remove-Item vc_redist.x64.exe
|
||||
|
||||
# Add a fix for https://github.com/docker/for-win/issues/2920 as entry point to the container.
|
||||
SHELL ["cmd", "/c"]
|
||||
COPY "fix-msys64.cmd" "C:\\fix-msys64.cmd"
|
||||
ENTRYPOINT cmd /C C:\\fix-msys64.cmd && cmd /c
|
||||
|
||||
CMD ["cmd.exe"]
|
96
.buildkite/README.md
Normal file
96
.buildkite/README.md
Normal file
@ -0,0 +1,96 @@
|
||||
# BuildKite configuration
|
||||
|
||||
This folder contains configuration for the [BuildKite](https://buildkite.com) based CI checks for
|
||||
this repository.
|
||||
|
||||
BuildKite is a CI provider that provides build coordination and reports while we provide the
|
||||
infrastructure.
|
||||
|
||||
CI runs are triggered by new PRs and will show up on the GitHub checks interface, along with the
|
||||
other current CI solutions.
|
||||
|
||||
Currently it is only used for tests on Windows platforms.
|
||||
|
||||
|
||||
## The build pipeline
|
||||
|
||||
BuildKite uses a pipeline for each repository. The `pipeline.yml` file defines pipeline
|
||||
[build steps](https://buildkite.com/docs/pipelines/defining-steps) for this repository.
|
||||
|
||||
Run results can be seen in the GitHub checks interface and in the
|
||||
[pipeline dashboard](https://buildkite.com/angular/angular).
|
||||
|
||||
Although most configuration is done via `pipeline.yml`, some options are only available
|
||||
in the online [pipeline settings](https://buildkite.com/angular/angular/settings).
|
||||
|
||||
|
||||
## Infrastructure
|
||||
|
||||
BuildKite does not provide the host machines where the builds runs, providing instead the
|
||||
[BuildKite Agent](https://buildkite.com/docs/agent/v3) that should be run our own infrastructure.
|
||||
|
||||
|
||||
### Agents
|
||||
|
||||
This agent polls the BuildKite API for builds, runs them, and reports back the results.
|
||||
Agents are the unit of concurrency: each agent can run one build at any given time.
|
||||
Adding agents allows more builds to be ran at the same time.
|
||||
|
||||
Individual agents can have tags, and pipeline steps can target only agents with certain tags via the
|
||||
`agents` field in `pipeline.yml`.
|
||||
For example: agents on Windows machines are tagged as `windows`, and the Windows specific build
|
||||
steps list `windows: true` in their `agents` field.
|
||||
|
||||
You can see the current agent pool, along with their tags, in the
|
||||
[agents list](https://buildkite.com/organizations/angular/agents).
|
||||
|
||||
|
||||
### Our host machines
|
||||
|
||||
We use [Google Cloud](https://cloud.google.com/) as our cloud provider, under the
|
||||
[Angular project](https://console.cloud.google.com/home/dashboard?project=internal-200822).
|
||||
To access this project you need need to be logged in with a Google account that's a member of
|
||||
team@angular.io.
|
||||
For googlers this may be your google.com account, for others it is an angular.io account.
|
||||
|
||||
In this project we have a number of Windows VMs running, each of them with several agents.
|
||||
The `provision-windows-buildkite.ps1` file contains instructions on how to create new host VMs that
|
||||
are fully configured to run the BuildKite agents as services.
|
||||
|
||||
Our pipeline uses [docker-buildkite-plugin](https://github.com/buildkite-plugins/docker-buildkite-plugin)
|
||||
to run build steps inside docker containers.
|
||||
This way we achieve isolation and hermeticity.
|
||||
|
||||
The `Dockerfile` file describes a custom Docker image that includes NodeJs, Yarn, and the Bazel
|
||||
pre-requisites on Windows.
|
||||
|
||||
To upload a new version of the docker image, follow any build instructions in `Dockerfile` and then
|
||||
run `docker build -t angular/node-bazel-windows:NEW_VERSION`, followed by
|
||||
`docker push angular/node-bazel-windows:NEW_VERSION`.
|
||||
After being pushed it should be available online, and you can use the new version in `pipeline.yml`.
|
||||
|
||||
|
||||
## Caretaker
|
||||
|
||||
BuildKite status can be found at https://www.buildkitestatus.com/.
|
||||
|
||||
Issues related to the BuildKite setup should be escalated to the Tools Team via the current
|
||||
caretaker, followed by Alex Eagle and Filipe Silva.
|
||||
|
||||
Support requests should be submitted via email to support@buildkite.com and cc Igor, Misko, Alex,
|
||||
Jeremy and Manu
|
||||
|
||||
|
||||
## Rollout strategy
|
||||
|
||||
At the moment our BuildKite CI uses 1 host VM running 4 agents, thus being capable of 4 concurrent
|
||||
builds.
|
||||
The only test running is `bazel test //tools/ts-api-guardian:all`, and the PR check is not
|
||||
mandatory.
|
||||
|
||||
In the future we should add cache support to speed up the initial `yarn` install, and also Bazel
|
||||
remote caching to speed up Bazel builds.
|
||||
|
||||
After the current setup is verified as stable and reliable the GitHub PR check can become mandatory.
|
||||
|
||||
The tests ran should also be expanded to cover most, if not all, of the Bazel tests.
|
6
.buildkite/fix-msys64.cmd
Normal file
6
.buildkite/fix-msys64.cmd
Normal file
@ -0,0 +1,6 @@
|
||||
@echo off
|
||||
REM Fix for https://github.com/docker/for-win/issues/2920
|
||||
REM echo "Fixing msys64 folder..."
|
||||
REM Touch all .dll files inside C:\msys64\
|
||||
forfiles /p C:\msys64\ /s /m *.dll /c "cmd /c Copy /B @path+,, >NUL"
|
||||
REM echo "Fixed msys64 folder."
|
10
.buildkite/pipeline.yml
Normal file
10
.buildkite/pipeline.yml
Normal file
@ -0,0 +1,10 @@
|
||||
steps:
|
||||
- label: windows-test
|
||||
commands:
|
||||
- "yarn install --frozen-lockfile --non-interactive --network-timeout 100000"
|
||||
- "yarn bazel test //tools/ts-api-guardian:all --noshow_progress"
|
||||
plugins:
|
||||
- docker#v2.1.0:
|
||||
image: "filipesilva/node-bazel-windows:0.0.2"
|
||||
agents:
|
||||
windows: true
|
94
.buildkite/provision-windows-buildkite.ps1
Normal file
94
.buildkite/provision-windows-buildkite.ps1
Normal file
@ -0,0 +1,94 @@
|
||||
# PowerShell script to provision a Windows Server with BuildKite
|
||||
# This script follows https://buildkite.com/docs/agent/v3/windows.
|
||||
|
||||
# Instructions
|
||||
|
||||
# VM creation:
|
||||
# In Google Cloud Platform, create a Compute Engine instance.
|
||||
# We recommend machine type n1-standard-16 (16 vCPUs, 60 GB memory).
|
||||
# Use a recent windows boot disk with container support such as
|
||||
# "Windows Server version 1803 Datacenter Core for Containers", and add a 128GB SSD disk.
|
||||
# Give it a name, then click "Create".
|
||||
|
||||
# VM setup:
|
||||
# In the Compute Engine menu, select "VM Instances". Click on the VM name you chose before.
|
||||
# Click "Set Windows Password" to choose a username and password.
|
||||
# Click RDP to open a remote desktop via browser, using the username and password.
|
||||
# In the Windows command prompt start an elevated powershell by inputing
|
||||
# "powershell -Command "Start-Process PowerShell -Verb RunAs" followed by Enter.
|
||||
# Download and execute this script from GitHub, passing the token (mandatory), tags (optional)
|
||||
# and number of agents (optional) as args:
|
||||
# ```
|
||||
# Invoke-WebRequest -Uri https://raw.githubusercontent.com/angular/angular/master/.buildkite/provision-windows-buildkite.ps1 -OutFile provision.ps1
|
||||
# .\provision.ps1 -token "MY_TOKEN" -tags "windows=true,another_tag=true" -agents 4
|
||||
# ```
|
||||
# The VM should restart and be fully configured.
|
||||
|
||||
# Creating extra VMs
|
||||
# You can create an image of the current VM by following the instructions below.
|
||||
# https://cloud.google.com/compute/docs/instances/windows/creating-windows-os-image
|
||||
# Then create a new VM and choose "Custom images".
|
||||
|
||||
|
||||
# Script proper.
|
||||
|
||||
# Get the token and tags from arguments.
|
||||
param (
|
||||
[Parameter(Mandatory=$true)][string]$token,
|
||||
[string]$tags = "",
|
||||
[Int]$agents = 1
|
||||
)
|
||||
|
||||
# Allow HTTPS
|
||||
[Net.ServicePointManager]::SecurityProtocol = "tls12, tls11, tls"
|
||||
|
||||
# Helper to add to PATH.
|
||||
# Will take current PATH so avoid running it after anything to modifies only the powershell session path.
|
||||
function Add-Path ([string]$newPathItem) {
|
||||
$Env:Path+= ";" + $newPathItem + ";"
|
||||
[Environment]::SetEnvironmentVariable("Path",$env:Path, [System.EnvironmentVariableTarget]::Machine)
|
||||
}
|
||||
|
||||
# Install Git for Windows
|
||||
Write-Host "Installing Git for Windows."
|
||||
Invoke-WebRequest -Uri https://github.com/git-for-windows/git/releases/download/v2.19.1.windows.1/Git-2.19.1-64-bit.exe -OutFile git.exe
|
||||
.\git.exe /VERYSILENT /NORESTART /NOCANCEL /SP- /CLOSEAPPLICATIONS /RESTARTAPPLICATIONS /COMPONENTS="icons,ext\reg\shellhere,assoc,assoc_sh" /DIR="C:\git"
|
||||
Add-Path "C:\git\bin"
|
||||
# Sleep for 15s while git is installed. Trying to remove the git.exe before it finishes install causes an error.
|
||||
Start-Sleep -s 15
|
||||
Remove-Item git.exe
|
||||
|
||||
# Download NSSM (https://nssm.cc/) to run the BuildKite agent as a service.
|
||||
Write-Host "Downloading NSSM."
|
||||
Invoke-WebRequest -Uri https://nssm.cc/ci/nssm-2.24-101-g897c7ad.zip -OutFile nssm.zip
|
||||
Expand-Archive -Path nssm.zip -DestinationPath C:\nssm
|
||||
Add-Path "C:\nssm\nssm-2.24-101-g897c7ad\win64"
|
||||
Remove-Item nssm.zip
|
||||
|
||||
# Run the BuildKite agent install script
|
||||
Write-Host "Installing BuildKite agent."
|
||||
$env:buildkiteAgentToken = $token
|
||||
$env:buildkiteAgentTags = $tags
|
||||
Set-ExecutionPolicy Bypass -Scope Process -Force
|
||||
iex ((New-Object System.Net.WebClient).DownloadString('https://raw.githubusercontent.com/buildkite/agent/master/install.ps1'))
|
||||
|
||||
# Configure the BuildKite agent clone and timestamp behavior
|
||||
Add-Content C:\buildkite-agent\buildkite-agent.cfg "`ngit-clone-flags=--config core.autocrlf=input --config core.eol=lf --config core.longpaths=true --config core.symlinks=true`n"
|
||||
Add-Content C:\buildkite-agent\buildkite-agent.cfg "`ntimestamp-lines=true`n"
|
||||
|
||||
# Register the BuildKite agent service using NSSM, so that it persists through restarts and is
|
||||
# restarted if the process dies.
|
||||
for ($i=1; $i -le $agents; $i++)
|
||||
{
|
||||
$agentName = "buildkite-agent-$i"
|
||||
Write-Host "Registering $agentName as a service."
|
||||
nssm.exe install $agentName "C:\buildkite-agent\bin\buildkite-agent.exe" "start"
|
||||
nssm.exe set $agentName AppStdout "C:\buildkite-agent\$agentName.log"
|
||||
nssm.exe set $agentName AppStderr "C:\buildkite-agent\$agentName.log"
|
||||
nssm.exe status $agentName
|
||||
nssm.exe start $agentName
|
||||
nssm.exe status $agentName
|
||||
}
|
||||
|
||||
# Restart the machine.
|
||||
Restart-Computer
|
@ -28,14 +28,3 @@ test --flaky_test_attempts=2
|
||||
|
||||
# More details on failures
|
||||
build --verbose_failures=true
|
||||
|
||||
# We have seen some flakiness in using TS workers on CircleCI
|
||||
# https://angular-team.slack.com/archives/C07DT5M6V/p1562693245183400
|
||||
# > failures like `ERROR: /home/circleci/ng/packages/core/test/BUILD.bazel:5:1:
|
||||
# > Compiling TypeScript (devmode) //packages/core/test:test_lib failed: Worker process did not return a WorkResponse:`
|
||||
# > I saw that issue a couple times today.
|
||||
# > Example job: https://circleci.com/gh/angular/angular/385517
|
||||
# We expect that TypeScript compilations will parallelize wider than the number of local cores anyway
|
||||
# so we should saturate remote workers with TS compilations
|
||||
build --strategy=TypeScriptCompile=standalone
|
||||
build --strategy=AngularTemplateCompile=standalone
|
||||
|
@ -58,7 +58,17 @@ var_5: &setup_bazel_remote_execution
|
||||
# cause decryption failures based on the openssl version. https://stackoverflow.com/a/39641378/4317734
|
||||
openssl aes-256-cbc -d -in .circleci/gcp_token -md md5 -k "$CI_REPO_NAME" -out /home/circleci/.gcp_credentials
|
||||
echo "export GOOGLE_APPLICATION_CREDENTIALS=/home/circleci/.gcp_credentials" >> $BASH_ENV
|
||||
./.circleci/setup-rbe.sh .bazelrc.user
|
||||
touch .bazelrc.user
|
||||
sudo bash -c "echo -e 'build --config=remote\n' >> .bazelrc.user"
|
||||
sudo bash -c "echo -e 'build:remote --remote_accept_cached=true\n' >> .bazelrc.user"
|
||||
echo "Reading from remote cache for bazel remote jobs."
|
||||
if [[ "$CI_PULL_REQUEST" == "false" ]]; then
|
||||
sudo bash -c "echo -e 'build:remote --remote_upload_local_results=true\n' >> .bazelrc.user"
|
||||
echo "Uploading local build results to remote cache."
|
||||
else
|
||||
sudo bash -c "echo -e 'build:remote --remote_upload_local_results=false\n' >> .bazelrc.user"
|
||||
echo "Not uploading local build results to remote cache."
|
||||
fi
|
||||
|
||||
# Settings common to each job
|
||||
var_6: &job_defaults
|
||||
@ -128,7 +138,7 @@ var_13: ¬ify_caretaker_on_fail
|
||||
# `$SLACK_CARETAKER_WEBHOOK_URL` is a secret env var defined in CircleCI project settings.
|
||||
# The URL comes from https://angular-team.slack.com/apps/A0F7VRE7N-circleci.
|
||||
command: |
|
||||
notificationJson="{\"text\":\":x: \`$CIRCLE_JOB\` job for $CIRCLE_BRANCH branch failed on build $CIRCLE_BUILD_NUM: $CIRCLE_BUILD_URL :scream:\"}"
|
||||
notificationJson="{\"text\":\":x: \`$CIRCLE_JOB\` job failed on build $CIRCLE_BUILD_NUM: $CIRCLE_BUILD_URL :scream:\"}"
|
||||
curl --request POST --header "Content-Type: application/json" --data "$notificationJson" $SLACK_CARETAKER_WEBHOOK_URL
|
||||
|
||||
var_14: ¬ify_dev_infra_on_fail
|
||||
@ -138,14 +148,9 @@ var_14: ¬ify_dev_infra_on_fail
|
||||
# `$SLACK_DEV_INFRA_CI_FAILURES_WEBHOOK_URL` is a secret env var defined in CircleCI project settings.
|
||||
# The URL comes from https://angular-team.slack.com/apps/A0F7VRE7N-circleci.
|
||||
command: |
|
||||
notificationJson="{\"text\":\":x: \`$CIRCLE_JOB\` job for $CIRCLE_BRANCH branch failed on build $CIRCLE_BUILD_NUM: $CIRCLE_BUILD_URL :scream:\"}"
|
||||
notificationJson="{\"text\":\":x: \`$CIRCLE_JOB\` job failed on build $CIRCLE_BUILD_NUM: $CIRCLE_BUILD_URL :scream:\"}"
|
||||
curl --request POST --header "Content-Type: application/json" --data "$notificationJson" $SLACK_DEV_INFRA_CI_FAILURES_WEBHOOK_URL
|
||||
|
||||
# Cache key for the Material unit tests job. **Note** when updating the SHA in the cache keys,
|
||||
# also update the SHA for the "MATERIAL_REPO_COMMIT" environment variable.
|
||||
var_15: &material_unit_tests_cache_key v4-angular-material-701302dc482d7e4b77990b24e3b5ab330bbf1aa5
|
||||
var_16: &material_unit_tests_cache_key_short v4-angular-material
|
||||
|
||||
version: 2
|
||||
jobs:
|
||||
setup:
|
||||
@ -198,13 +203,6 @@ jobs:
|
||||
# Setup remote execution and run RBE-compatible tests.
|
||||
- *setup_bazel_remote_execution
|
||||
- run: yarn bazel test //... --build_tag_filters=-ivy-only --test_tag_filters=-ivy-only
|
||||
- run: mkdir ~/testlogs
|
||||
- run: cp -Lr dist/testlogs/* ~/testlogs
|
||||
- store_test_results:
|
||||
# Bazel always writes test.xml files under this directory
|
||||
path: ~/testlogs
|
||||
- store_artifacts:
|
||||
path: ~/testlogs
|
||||
|
||||
# Temporary job to test what will happen when we flip the Ivy flag to true
|
||||
test_ivy_aot:
|
||||
@ -240,11 +238,6 @@ jobs:
|
||||
path: dist/bin/packages/core/test/bundling/todo/bundle.min.js.br
|
||||
destination: core/todo/bundle.br
|
||||
|
||||
# This job is currently a PoC for running tests on SauceLabs via bazel. It runs a subset of the
|
||||
# tests in `legacy-unit-tests-saucelabs` (see
|
||||
# [BUILD.bazel](https://github.com/angular/angular/blob/ef44f51d5/BUILD.bazel#L66-L92)).
|
||||
#
|
||||
# NOTE: This is currently limited to master builds only. See the `default_workflow` configuration.
|
||||
test_saucelabs_bazel:
|
||||
<<: *job_defaults
|
||||
# In order to avoid the bottleneck of having a slow host machine, we acquire a better
|
||||
@ -293,8 +286,6 @@ jobs:
|
||||
- run: yarn --cwd aio e2e --configuration=ci
|
||||
# Run PWA-score tests
|
||||
- run: yarn --cwd aio test-pwa-score-localhost $CI_AIO_MIN_PWA_SCORE
|
||||
# Run accessibility tests
|
||||
- run: yarn --cwd aio test-a11y-score-localhost
|
||||
# Check the bundle sizes.
|
||||
- run: yarn --cwd aio payload-size
|
||||
# Run unit tests for Firebase redirects
|
||||
@ -403,7 +394,7 @@ jobs:
|
||||
# Run examples tests with ivy. The "CIRCLE_NODE_INDEX" will be set if "parallelism" is enabled.
|
||||
# Since the parallelism is set to "3", there will be three parallel CircleCI containers
|
||||
# with either "0", "1" or "2" as node index. This can be passed to the "--shard" argument.
|
||||
- run: yarn --cwd aio example-e2e --setup --local --ivy --cliSpecsConcurrency=5 --shard=${CIRCLE_NODE_INDEX}/${CIRCLE_NODE_TOTAL}
|
||||
- run: yarn --cwd aio example-e2e --setup --local --ivy --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:
|
||||
@ -620,55 +611,44 @@ jobs:
|
||||
resource_class: xlarge
|
||||
docker:
|
||||
- image: *browsers_docker_image
|
||||
# The Material unit tests support splitting the browsers across multiple CircleCI
|
||||
# instances. Since by default this job launches two browsers, we run each browser
|
||||
# in its own container instance.
|
||||
# https://github.com/angular/material2/blob/7baeaa797b19da2d2998f0d26f6fede3c8a13714/test/karma.conf.js#L107-L110
|
||||
parallelism: 2
|
||||
environment:
|
||||
# The Material unit tests also support launching the same browser multiple times by
|
||||
# sharding individual specs across the defined multiple instances.
|
||||
# See: https://github.com/angular/material2/blob/7baeaa797b19da2d2998f0d26f6fede3c8a13714/test/karma.conf.js#L113-L116
|
||||
KARMA_PARALLEL_BROWSERS: 3
|
||||
steps:
|
||||
- *attach_workspace
|
||||
- *init_environment
|
||||
# Although RBE is configured below for the Material repo, also setup RBE in the Angular repo
|
||||
# to provision Angular's GCP token into the environment variables.
|
||||
- *setup_bazel_remote_execution
|
||||
# Restore the cache before cloning the repository because the clone script re-uses
|
||||
# the restored repository if present. This reduces the amount of times the components
|
||||
# repository needs to be cloned (this is slow and increases based on commits in the repo).
|
||||
- restore_cache:
|
||||
keys:
|
||||
- *material_unit_tests_cache_key
|
||||
- *material_unit_tests_cache_key_short
|
||||
- run:
|
||||
name: "Fetching Material repository"
|
||||
name: "Cloning Material repository"
|
||||
command: ./scripts/ci/clone_angular_material_repo.sh
|
||||
- restore_cache:
|
||||
# Material directory must be kept in sync with the `$MATERIAL_REPO_TMP_DIR` env variable.
|
||||
# It needs to be hardcoded here, because env variables interpolation is not supported.
|
||||
keys:
|
||||
- v2-angular-material-{{ checksum "/tmp/material2/yarn.lock" }}
|
||||
- v2-angular-material-
|
||||
- run:
|
||||
# Run yarn install to fetch the Bazel binaries as used in the Material repo.
|
||||
name: Installing Material dependencies.
|
||||
command: yarn --cwd ${MATERIAL_REPO_TMP_DIR} install --frozen-lockfile --non-interactive
|
||||
# Save the cache before we run the Material unit tests script. This is necessary
|
||||
# because we don't want to cache the node modules which have been modified to contain
|
||||
# the attached Ivy package output.
|
||||
- save_cache:
|
||||
key: *material_unit_tests_cache_key
|
||||
# Material directory must be kept in sync with the `$MATERIAL_REPO_TMP_DIR` env variable.
|
||||
# It needs to be hardcoded here, because env variables interpolation is not supported.
|
||||
key: v2-angular-material-{{ checksum "/tmp/material2/yarn.lock" }}
|
||||
paths:
|
||||
# Material directory must be kept in sync with the `$MATERIAL_REPO_TMP_DIR` env variable.
|
||||
# It needs to be hardcoded here, because env variables interpolation is not supported.
|
||||
- "/tmp/material2"
|
||||
- run:
|
||||
name: "Setup Bazel RBE remote execution in Material repo"
|
||||
command: |
|
||||
./.circleci/setup-rbe.sh "${MATERIAL_REPO_TMP_DIR}/.bazelrc.user"
|
||||
- "/tmp/material2/node_modules"
|
||||
- run:
|
||||
name: "Running Material unit tests"
|
||||
command: ./scripts/ci/run_angular_material_unit_tests.sh
|
||||
|
||||
test_zonejs:
|
||||
<<: *job_defaults
|
||||
steps:
|
||||
- *attach_workspace
|
||||
- *init_environment
|
||||
# Install
|
||||
- run: yarn --cwd packages/zone.js install --frozen-lockfile --non-interactive
|
||||
# Run zone.js tools tests
|
||||
- run: yarn --cwd packages/zone.js promisetest
|
||||
- run: yarn --cwd packages/zone.js promisefinallytest
|
||||
- run: yarn bazel build //packages/zone.js:npm_package &&
|
||||
cp dist/bin/packages/zone.js/npm_package/dist/zone-mix.js ./packages/zone.js/test/extra/ &&
|
||||
cp dist/bin/packages/zone.js/npm_package/dist/zone-patch-electron.js ./packages/zone.js/test/extra/ &&
|
||||
yarn --cwd packages/zone.js electrontest
|
||||
|
||||
workflows:
|
||||
version: 2
|
||||
default_workflow:
|
||||
@ -676,41 +656,31 @@ workflows:
|
||||
- setup
|
||||
- lint:
|
||||
requires:
|
||||
- setup
|
||||
- setup
|
||||
- test:
|
||||
requires:
|
||||
- setup
|
||||
- setup
|
||||
- test_ivy_aot:
|
||||
requires:
|
||||
- setup
|
||||
- setup
|
||||
- build-npm-packages:
|
||||
requires:
|
||||
- setup
|
||||
- setup
|
||||
- build-ivy-npm-packages:
|
||||
requires:
|
||||
- setup
|
||||
- legacy-misc-tests:
|
||||
requires:
|
||||
- build-npm-packages
|
||||
- legacy-unit-tests-saucelabs:
|
||||
requires:
|
||||
- setup
|
||||
- test_saucelabs_bazel:
|
||||
requires:
|
||||
- setup
|
||||
# This job is currently a PoC and a subset of `legacy-unit-tests-saucelabs`. Running on
|
||||
# master only to avoid wasting resources.
|
||||
#
|
||||
# TODO: Run this job on all branches (including PRs) as soon as it is not a PoC.
|
||||
filters:
|
||||
branches:
|
||||
only: master
|
||||
- setup
|
||||
- test_aio:
|
||||
requires:
|
||||
- setup
|
||||
- setup
|
||||
- legacy-unit-tests-saucelabs:
|
||||
requires:
|
||||
- setup
|
||||
- deploy_aio:
|
||||
requires:
|
||||
- test_aio
|
||||
- legacy-misc-tests:
|
||||
requires:
|
||||
- build-npm-packages
|
||||
- test_aio_local:
|
||||
requires:
|
||||
- build-npm-packages
|
||||
@ -763,9 +733,22 @@ workflows:
|
||||
- material-unit-tests:
|
||||
requires:
|
||||
- build-ivy-npm-packages
|
||||
- test_zonejs:
|
||||
|
||||
saucelabs_tests:
|
||||
jobs:
|
||||
- setup
|
||||
- test_saucelabs_bazel:
|
||||
requires:
|
||||
- setup
|
||||
triggers:
|
||||
- schedule:
|
||||
# Runs the Saucelabs legacy tests every hour. We still want to run Saucelabs
|
||||
# frequently as the caretaker needs up-to-date results when merging PRs or creating
|
||||
# a new release. Also we primarily moved the Saucelabs job into a cronjob that doesn't
|
||||
# run for PRs, in order to ensure that PRs are not affected by Saucelabs flakiness or
|
||||
# incidents. This is still guaranteed (even if we run the job every hour).
|
||||
cron: "0 * * * *"
|
||||
filters: *publish_branches_filter
|
||||
|
||||
aio_monitoring:
|
||||
jobs:
|
||||
|
@ -77,9 +77,7 @@ setPublicVar SAUCE_READY_FILE_TIMEOUT 120
|
||||
# their separate build setups.
|
||||
setPublicVar MATERIAL_REPO_TMP_DIR "/tmp/material2"
|
||||
setPublicVar MATERIAL_REPO_URL "https://github.com/angular/material2.git"
|
||||
setPublicVar MATERIAL_REPO_BRANCH "master"
|
||||
# **NOTE**: When updating the commit SHA, also update the cache key in the CircleCI "config.yml".
|
||||
setPublicVar MATERIAL_REPO_COMMIT "701302dc482d7e4b77990b24e3b5ab330bbf1aa5"
|
||||
setPublicVar MATERIAL_REPO_BRANCH "ivy-2019"
|
||||
|
||||
# Source `$BASH_ENV` to make the variables available immediately.
|
||||
source $BASH_ENV;
|
||||
|
Binary file not shown.
@ -1,20 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
set -u -e -o pipefail
|
||||
|
||||
# The path of the .bazelrc.user file to update should be passed as first parameter to this script.
|
||||
# This allows to setup RBE for both the Angular repo and the Material repo.
|
||||
bazelrc_user="$1"
|
||||
|
||||
echo "Writing RBE configuration to ${bazelrc_user}"
|
||||
|
||||
touch ${bazelrc_user}
|
||||
echo -e 'build --config=remote\n' >> ${bazelrc_user}
|
||||
echo -e 'build:remote --remote_accept_cached=true\n' >> ${bazelrc_user}
|
||||
echo "Reading from remote cache for bazel remote jobs."
|
||||
if [[ "$CI_PULL_REQUEST" == "false" ]]; then
|
||||
echo -e 'build:remote --remote_upload_local_results=true\n' >> ${bazelrc_user}
|
||||
echo "Uploading local build results to remote cache."
|
||||
else
|
||||
echo -e 'build:remote --remote_upload_local_results=false\n' >> ${bazelrc_user}
|
||||
echo "Not uploading local build results to remote cache."
|
||||
fi
|
@ -1,5 +1,3 @@
|
||||
# escape=`
|
||||
|
||||
ARG core=mcr.microsoft.com/windows/servercore:1809
|
||||
ARG target=mcr.microsoft.com/powershell:windowsservercore-1809
|
||||
|
||||
@ -12,57 +10,57 @@ SHELL ["powershell", "-Command", "$ErrorActionPreference = 'Stop'; $ProgressPref
|
||||
|
||||
ENV GPG_VERSION 2.3.4
|
||||
|
||||
RUN Invoke-WebRequest $('https://files.gpg4win.org/gpg4win-vanilla-{0}.exe' -f $env:GPG_VERSION) -OutFile 'gpg4win.exe' -UseBasicParsing ; `
|
||||
RUN Invoke-WebRequest $('https://files.gpg4win.org/gpg4win-vanilla-{0}.exe' -f $env:GPG_VERSION) -OutFile 'gpg4win.exe' -UseBasicParsing ; \
|
||||
Start-Process .\gpg4win.exe -ArgumentList '/S' -NoNewWindow -Wait
|
||||
|
||||
RUN @( `
|
||||
'94AE36675C464D64BAFA68DD7434390BDBE9B9C5', `
|
||||
'FD3A5288F042B6850C66B31F09FE44734EB7990E', `
|
||||
'71DCFD284A79C3B38668286BC97EC7A07EDE3FC1', `
|
||||
'DD8F2338BAE7501E3DD5AC78C273792F7D83545D', `
|
||||
'C4F0DFFF4E8C1A8236409D08E73BC641CC11F4C8', `
|
||||
'B9AE9905FFD7803F25714661B63B535A4C206CA9', `
|
||||
'77984A986EBC2AA786BC0F66B01FBB92821C587A', `
|
||||
'8FCCA13FEF1D0C2E91008E09770F7A9A5AE15600', `
|
||||
'4ED778F539E3634C779C87C6D7062848A1AB005C', `
|
||||
'A48C2BEE680E841632CD4E44F07496B3EB3C1762', `
|
||||
'B9E2F5981AA6E0CD28160D9FF13993A75599653C' `
|
||||
) | foreach { `
|
||||
gpg --keyserver ha.pool.sks-keyservers.net --recv-keys $_ ; `
|
||||
RUN @( \
|
||||
'94AE36675C464D64BAFA68DD7434390BDBE9B9C5', \
|
||||
'FD3A5288F042B6850C66B31F09FE44734EB7990E', \
|
||||
'71DCFD284A79C3B38668286BC97EC7A07EDE3FC1', \
|
||||
'DD8F2338BAE7501E3DD5AC78C273792F7D83545D', \
|
||||
'C4F0DFFF4E8C1A8236409D08E73BC641CC11F4C8', \
|
||||
'B9AE9905FFD7803F25714661B63B535A4C206CA9', \
|
||||
'77984A986EBC2AA786BC0F66B01FBB92821C587A', \
|
||||
'8FCCA13FEF1D0C2E91008E09770F7A9A5AE15600', \
|
||||
'4ED778F539E3634C779C87C6D7062848A1AB005C', \
|
||||
'A48C2BEE680E841632CD4E44F07496B3EB3C1762', \
|
||||
'B9E2F5981AA6E0CD28160D9FF13993A75599653C' \
|
||||
) | foreach { \
|
||||
gpg --keyserver ha.pool.sks-keyservers.net --recv-keys $_ ; \
|
||||
}
|
||||
|
||||
ENV NODE_VERSION=$node_version
|
||||
|
||||
RUN Invoke-WebRequest $('https://nodejs.org/dist/v{0}/SHASUMS256.txt.asc' -f $env:NODE_VERSION) -OutFile 'SHASUMS256.txt.asc' -UseBasicParsing ; `
|
||||
RUN Invoke-WebRequest $('https://nodejs.org/dist/v{0}/SHASUMS256.txt.asc' -f $env:NODE_VERSION) -OutFile 'SHASUMS256.txt.asc' -UseBasicParsing ; \
|
||||
gpg --batch --decrypt --output SHASUMS256.txt SHASUMS256.txt.asc
|
||||
|
||||
RUN Invoke-WebRequest $('https://nodejs.org/dist/v{0}/node-v{0}-win-x64.zip' -f $env:NODE_VERSION) -OutFile 'node.zip' -UseBasicParsing ; `
|
||||
$sum = $(cat SHASUMS256.txt.asc | sls $(' node-v{0}-win-x64.zip' -f $env:NODE_VERSION)) -Split ' ' ; `
|
||||
if ((Get-FileHash node.zip -Algorithm sha256).Hash -ne $sum[0]) { Write-Error 'SHA256 mismatch' } ; `
|
||||
Expand-Archive node.zip -DestinationPath C:\ ; `
|
||||
RUN Invoke-WebRequest $('https://nodejs.org/dist/v{0}/node-v{0}-win-x64.zip' -f $env:NODE_VERSION) -OutFile 'node.zip' -UseBasicParsing ; \
|
||||
$sum = $(cat SHASUMS256.txt.asc | sls $(' node-v{0}-win-x64.zip' -f $env:NODE_VERSION)) -Split ' ' ; \
|
||||
if ((Get-FileHash node.zip -Algorithm sha256).Hash -ne $sum[0]) { Write-Error 'SHA256 mismatch' } ; \
|
||||
Expand-Archive node.zip -DestinationPath C:\ ; \
|
||||
Rename-Item -Path $('C:\node-v{0}-win-x64' -f $env:NODE_VERSION) -NewName 'C:\nodejs'
|
||||
|
||||
ENV YARN_VERSION=$yarn_version
|
||||
|
||||
RUN [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 ; `
|
||||
Invoke-WebRequest $('https://yarnpkg.com/downloads/{0}/yarn-{0}.msi' -f $env:YARN_VERSION) -OutFile yarn.msi -UseBasicParsing ; `
|
||||
$sig = Get-AuthenticodeSignature yarn.msi ; `
|
||||
if ($sig.Status -ne 'Valid') { Write-Error 'Authenticode signature is not valid' } ; `
|
||||
Write-Output $sig.SignerCertificate.Thumbprint ; `
|
||||
if (@( `
|
||||
'7E253367F8A102A91D04829E37F3410F14B68A5F', `
|
||||
'AF764E1EA56C762617BDC757C8B0F3780A0CF5F9' `
|
||||
) -notcontains $sig.SignerCertificate.Thumbprint) { Write-Error 'Unknown signer certificate' } ; `
|
||||
RUN [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 ; \
|
||||
Invoke-WebRequest $('https://yarnpkg.com/downloads/{0}/yarn-{0}.msi' -f $env:YARN_VERSION) -OutFile yarn.msi -UseBasicParsing ; \
|
||||
$sig = Get-AuthenticodeSignature yarn.msi ; \
|
||||
if ($sig.Status -ne 'Valid') { Write-Error 'Authenticode signature is not valid' } ; \
|
||||
Write-Output $sig.SignerCertificate.Thumbprint ; \
|
||||
if (@( \
|
||||
'7E253367F8A102A91D04829E37F3410F14B68A5F', \
|
||||
'AF764E1EA56C762617BDC757C8B0F3780A0CF5F9' \
|
||||
) -notcontains $sig.SignerCertificate.Thumbprint) { Write-Error 'Unknown signer certificate' } ; \
|
||||
Start-Process msiexec.exe -ArgumentList '/i', 'yarn.msi', '/quiet', '/norestart' -NoNewWindow -Wait
|
||||
|
||||
ENV GIT_VERSION 2.20.1
|
||||
ENV GIT_DOWNLOAD_URL https://github.com/git-for-windows/git/releases/download/v${GIT_VERSION}.windows.1/MinGit-${GIT_VERSION}-busybox-64-bit.zip
|
||||
ENV GIT_SHA256 9817ab455d9cbd0b09d8664b4afbe4bbf78d18b556b3541d09238501a749486c
|
||||
|
||||
RUN [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 ; `
|
||||
Invoke-WebRequest -UseBasicParsing $env:GIT_DOWNLOAD_URL -OutFile git.zip; `
|
||||
if ((Get-FileHash git.zip -Algorithm sha256).Hash -ne $env:GIT_SHA256) {exit 1} ; `
|
||||
Expand-Archive git.zip -DestinationPath C:\git; `
|
||||
RUN [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 ; \
|
||||
Invoke-WebRequest -UseBasicParsing $env:GIT_DOWNLOAD_URL -OutFile git.zip; \
|
||||
if ((Get-FileHash git.zip -Algorithm sha256).Hash -ne $env:GIT_SHA256) {exit 1} ; \
|
||||
Expand-Archive git.zip -DestinationPath C:\git; \
|
||||
Remove-Item git.zip
|
||||
|
||||
FROM $target as baseimage
|
||||
@ -76,51 +74,30 @@ COPY --from=download /git /git
|
||||
ARG SETX=/M
|
||||
RUN setx %SETX% PATH "%PATH%;C:\nodejs;C:\yarn\bin;C:\git\cmd;C:\git\mingw64\bin;C:\git\usr\bin"
|
||||
|
||||
CMD [ "node.exe" ]
|
||||
CMD [ "node.exe" ]
|
||||
|
||||
FROM baseimage
|
||||
|
||||
SHELL ["powershell", "-Command", "$ErrorActionPreference = 'Stop'; $ProgressPreference = 'SilentlyContinue';"]
|
||||
|
||||
# Install Bazel prereqs on Windows (https://docs.bazel.build/versions/master/install-windows.html)
|
||||
RUN Invoke-WebRequest -UseBasicParsing 'https://www.7-zip.org/a/7z1805-x64.exe' -OutFile 7z.exe; \
|
||||
Start-Process -FilePath 'C:\\7z.exe' -ArgumentList '/S', '/D=C:\\7zip0' -NoNewWindow -Wait; \
|
||||
Invoke-WebRequest -UseBasicParsing 'http://repo.msys2.org/distrib/x86_64/msys2-base-x86_64-20180531.tar.xz' -OutFile msys2.tar.xz; \
|
||||
Start-Process -FilePath 'C:\\7zip\\7z' -ArgumentList 'e', 'msys2.tar.xz' -Wait; \
|
||||
Start-Process -FilePath 'C:\\7zip\\7z' -ArgumentList 'x', 'msys2.tar', '-oC:\\' -Wait; \
|
||||
Remove-Item msys2.tar.xz; \
|
||||
Remove-Item msys2.tar; \
|
||||
Remove-Item 7z.exe; \
|
||||
Remove-Item -Recurse 7zip; \
|
||||
[Environment]::SetEnvironmentVariable('Path', $env:Path + ';C:\msys64\usr\bin', [System.EnvironmentVariableTarget]::Machine); \
|
||||
[Environment]::SetEnvironmentVariable('BAZEL_SH', 'C:\msys64\usr\bin\bash.exe', [System.EnvironmentVariableTarget]::Machine); \
|
||||
Invoke-WebRequest -UseBasicParsing 'https://download.microsoft.com/download/9/3/F/93FCF1E7-E6A4-478B-96E7-D4B285925B00/vc_redist.x64.exe' -OutFile vc_redist.x64.exe; \
|
||||
Start-Process 'c:\\vc_redist.x64.exe' -ArgumentList '/Install', '/Passive', '/NoRestart' -NoNewWindow -Wait; \
|
||||
Remove-Item vc_redist.x64.exe
|
||||
|
||||
# Install MSYS2
|
||||
RUN Invoke-WebRequest -UseBasicParsing 'https://www.7-zip.org/a/7z1805-x64.exe' -OutFile 7z.exe; `
|
||||
Start-Process -FilePath 'C:\\7z.exe' -ArgumentList '/S', '/D=C:\\7zip0' -NoNewWindow -Wait; `
|
||||
Invoke-WebRequest -UseBasicParsing 'http://repo.msys2.org/distrib/x86_64/msys2-base-x86_64-20180531.tar.xz' -OutFile msys2.tar.xz; `
|
||||
Start-Process -FilePath 'C:\\7zip\\7z' -ArgumentList 'e', 'msys2.tar.xz' -Wait; `
|
||||
Start-Process -FilePath 'C:\\7zip\\7z' -ArgumentList 'x', 'msys2.tar', '-oC:\\' -Wait; `
|
||||
Remove-Item msys2.tar.xz; `
|
||||
Remove-Item msys2.tar; `
|
||||
Remove-Item 7z.exe; `
|
||||
Remove-Item -Recurse 7zip; `
|
||||
[Environment]::SetEnvironmentVariable('Path', $env:Path + ';C:\msys64\usr\bin', [System.EnvironmentVariableTarget]::Machine); `
|
||||
[Environment]::SetEnvironmentVariable('BAZEL_SH', 'C:\msys64\usr\bin\bash.exe', [System.EnvironmentVariableTarget]::Machine)
|
||||
# Add a fix for https://github.com/docker/for-win/issues/2920 as entry point to the container.
|
||||
SHELL ["cmd", "/c"]
|
||||
COPY "fix-msys64.cmd" "C:\\fix-msys64.cmd"
|
||||
ENTRYPOINT cmd /C C:\\fix-msys64.cmd && cmd /c
|
||||
|
||||
# Install MSYS2 packages
|
||||
RUN C:\msys64\usr\bin\bash.exe -l -c \"pacman --needed --noconfirm -S zip unzip patch diffutils git\"
|
||||
|
||||
# Install VS Build Tools (required to build C++ targets)
|
||||
RUN Invoke-WebRequest -UseBasicParsing https://download.visualstudio.microsoft.com/download/pr/df649173-11e9-4af2-8eb7-0eb02ba8958a/cadb5bdac41e55bb8f6a6b7c45273370/vs_buildtools.exe -OutFile vs_BuildTools.exe; `
|
||||
# Installer won't detect DOTNET_SKIP_FIRST_TIME_EXPERIENCE if ENV is used, must use setx /M
|
||||
setx /M DOTNET_SKIP_FIRST_TIME_EXPERIENCE 1; `
|
||||
Start-Process vs_BuildTools.exe `
|
||||
-ArgumentList `
|
||||
'--add', 'Microsoft.VisualStudio.Workload.VCTools', `
|
||||
'--add', 'Microsoft.VisualStudio.Component.VC.Tools.x86.x64', `
|
||||
'--add', 'Microsoft.Component.VC.Runtime.UCRTSDK', `
|
||||
'--add', 'Microsoft.VisualStudio.Component.Windows10SDK.17763', `
|
||||
'--quiet', '--norestart', '--nocache' `
|
||||
-NoNewWindow -Wait; `
|
||||
Remove-Item -Force vs_buildtools.exe; `
|
||||
Remove-Item -Force -Recurse \"${Env:ProgramFiles(x86)}\Microsoft Visual Studio\Installer\"; `
|
||||
Remove-Item -Force -Recurse ${Env:TEMP}\*; `
|
||||
Remove-Item -Force -Recurse \"${Env:ProgramData}\Package Cache\"; `
|
||||
[Environment]::SetEnvironmentVariable('BAZEL_VC', \"${Env:ProgramFiles(x86)}\Microsoft Visual Studio\2019\BuildTools\VC\", [System.EnvironmentVariableTarget]::Machine)
|
||||
|
||||
# Install Python (required to build Python targets)
|
||||
RUN Invoke-WebRequest -UseBasicParsing https://www.python.org/ftp/python/3.5.1/python-3.5.1.exe -OutFile python-3.5.1.exe; `
|
||||
Start-Process python-3.5.1.exe -ArgumentList '/quiet InstallAllUsers=1 PrependPath=1' -Wait; `
|
||||
Remove-Item -Force python-3.5.1.exe
|
||||
|
||||
CMD ["cmd.exe"]
|
||||
CMD ["cmd.exe"]
|
@ -2,17 +2,11 @@
|
||||
# We do this by copying this file to /etc/bazel.bazelrc at the start of the build.
|
||||
# See documentation in /docs/BAZEL.md
|
||||
|
||||
# Save built files and downloaded repositories in a location that can be cached by CodeFresh and
|
||||
# shared between builds. This helps speed up the analysis time significantly with Bazel managed node
|
||||
# dependencies on the CI.
|
||||
# Save downloaded repositories in a location that can be cached by CodeFresh and shared between
|
||||
# builds. This helps speed up the analysis time significantly with Bazel managed node dependencies
|
||||
# on the CI.
|
||||
# https://codefresh.io/docs/docs/configure-ci-cd-pipeline/introduction-to-codefresh-pipelines/#caching-the-artifacts-of-your-build-system
|
||||
build --repository_cache=C:/codefresh/volume/bazel_repository_cache
|
||||
# Setting the output_base to a Docker volume is currently broken because of a Docker bug on Windows:
|
||||
# https://github.com/moby/moby/issues/37024
|
||||
# This affects Bazel because bazel_output_base\external\bazel_tools is an absolute path junction.
|
||||
# When its fixed we can uncomment this line, and use a different output_base for Ivy tests (they
|
||||
# use a separate compiler and destructively replace the cache).
|
||||
# startup --output_base=C:/codefresh/volume/bazel_output_base
|
||||
|
||||
# Don't be spammy in the logs
|
||||
# TODO(gmagolan): Hide progress again once build performance improves
|
||||
@ -29,10 +23,20 @@ build --announce_rc
|
||||
# Bazel doesn't calculate the memory ceiling correctly when running under Docker.
|
||||
# Limit Bazel to consuming resources that fit in CodeFresh VMs
|
||||
# TODO(filipesilva): determine the correct memory limit
|
||||
build --local_resources=10240,8.0,1.0
|
||||
build --local_resources=8000,8.0,1.0
|
||||
|
||||
# Retry in the event of flakes, eg. https://circleci.com/gh/angular/angular/31309
|
||||
test --flaky_test_attempts=2
|
||||
|
||||
# More details on failures
|
||||
build --verbose_failures=true
|
||||
|
||||
# Include PATH in Windows build/tests
|
||||
# https://github.com/bazelbuild/rules_typescript/pull/356
|
||||
build --action_env=PATH
|
||||
test --action_env=PATH --test_env=PATH
|
||||
|
||||
# Exclude tests known to not work on Windows.
|
||||
|
||||
# Chrome web tests are currently broken.
|
||||
test --test_tag_filters=-browser:chromium-local
|
||||
|
@ -2,7 +2,6 @@ version: '1.0'
|
||||
|
||||
steps:
|
||||
BuildImage:
|
||||
title: Build Docker image
|
||||
type: build
|
||||
image_name: node-bazel-windows
|
||||
working_directory: ./.codefresh
|
||||
@ -13,7 +12,7 @@ steps:
|
||||
dockerfile: ./Dockerfile.win-1809
|
||||
|
||||
RunTests:
|
||||
title: Run Bazel tests
|
||||
title: Run Example
|
||||
image: ${{BuildImage}}
|
||||
commands:
|
||||
# Install dependencies
|
||||
@ -21,8 +20,7 @@ steps:
|
||||
# Add Bazel CI config
|
||||
- copy .codefresh\bazel.rc %ProgramData%\bazel.bazelrc
|
||||
# Run tests
|
||||
# At the moment 'browser:chromium-local' are broken in CI while locally they work
|
||||
# VE
|
||||
- yarn bazel test --build_tag_filters=-ivy-only --test_tag_filters=-ivy-only,-browser:chromium-local //...
|
||||
# Ivy
|
||||
- yarn bazel test --define=compile=aot --build_tag_filters=-no-ivy-aot,-fixme-ivy-aot --test_tag_filters=-no-ivy-aot,-fixme-ivy-aot,-browser:chromium-local //...
|
||||
- yarn bazel test //tools/ts-api-guardian:all //packages/language-service/test //packages/compiler/test //packages/compiler-cli/test:ngc //packages/compiler-cli/test/ngtsc:ngtsc
|
||||
- yarn test-ivy-aot //packages/animations/test //packages/common/test //packages/forms/test //packages/http/test //packages/platform-browser/test //packages/platform-browser-dynamic/test //packages/router/test
|
||||
- yarn bazel test //tools/public_api_guard/...
|
||||
- yarn bazel test //packages/compiler-cli/integrationtest:integrationtest //packages/compiler-cli/test/compliance:compliance
|
||||
|
6
.codefresh/fix-msys64.cmd
Normal file
6
.codefresh/fix-msys64.cmd
Normal file
@ -0,0 +1,6 @@
|
||||
@echo off
|
||||
REM Fix for https://github.com/docker/for-win/issues/2920
|
||||
REM echo "Fixing msys64 folder..."
|
||||
REM Touch all .dll files inside C:\msys64\
|
||||
forfiles /p C:\msys64\ /s /m *.dll /c "cmd /c Copy /B @path+,, >NUL"
|
||||
REM echo "Fixed msys64 folder."
|
31
BUILD.bazel
31
BUILD.bazel
@ -18,15 +18,15 @@ filegroup(
|
||||
name = "web_test_bootstrap_scripts",
|
||||
# do not sort
|
||||
srcs = [
|
||||
"@npm//:node_modules/core-js/client/core.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",
|
||||
"@npm//node_modules/core-js:client/core.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",
|
||||
"//:shims_for_IE.js",
|
||||
# Including systemjs because it defines `__eval`, which produces correct stack traces.
|
||||
"@npm//:node_modules/systemjs/dist/system.src.js",
|
||||
"@npm//:node_modules/reflect-metadata/Reflect.js",
|
||||
"@npm//node_modules/systemjs:dist/system.src.js",
|
||||
"@npm//node_modules/reflect-metadata:Reflect.js",
|
||||
],
|
||||
)
|
||||
|
||||
@ -35,15 +35,15 @@ 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)
|
||||
"@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",
|
||||
"@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",
|
||||
],
|
||||
)
|
||||
|
||||
@ -74,7 +74,6 @@ karma_web_test(
|
||||
"//packages/core/test:test_lib",
|
||||
"//packages/forms/test:test_lib",
|
||||
"//packages/http/test:test_lib",
|
||||
"//packages/zone.js/test:karma_jasmine_test_ci",
|
||||
# "//packages/router/test:test_lib",
|
||||
# //packages/router/test:test_lib fails with:
|
||||
# IE 11.0.0 (Windows 8.1.0.0) bootstrap should restore the scrolling position FAILED
|
||||
|
313
CHANGELOG.md
313
CHANGELOG.md
@ -1,72 +1,3 @@
|
||||
<a name="8.1.3"></a>
|
||||
## [8.1.3](https://github.com/angular/angular/compare/8.1.2...8.1.3) (2019-07-30)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **elements:** handle falsy initial value ([#31604](https://github.com/angular/angular/issues/31604)) ([434b796](https://github.com/angular/angular/commit/434b796)), closes [angular/angular#30834](https://github.com/angular/angular/issues/30834)
|
||||
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
* **compiler:** avoid copying from prototype while cloning an object ([#31638](https://github.com/angular/angular/issues/31638)) ([1f3daa0](https://github.com/angular/angular/commit/1f3daa0)), closes [#31627](https://github.com/angular/angular/issues/31627)
|
||||
|
||||
|
||||
|
||||
<a name="8.1.2"></a>
|
||||
## [8.1.2](https://github.com/angular/angular/compare/8.1.0...8.1.2) (2019-07-17)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* use the correct WTF array to iterate over ([#31208](https://github.com/angular/angular/issues/31208)) ([4aed480](https://github.com/angular/angular/commit/4aed480))
|
||||
* **compiler-cli:** Return original sourceFile instead of redirected sourceFile from getSourceFile ([#26036](https://github.com/angular/angular/issues/26036)) ([13dbb98](https://github.com/angular/angular/commit/13dbb98)), closes [#22524](https://github.com/angular/angular/issues/22524)
|
||||
* **core:** export provider interfaces that are part of the public API types ([#31377](https://github.com/angular/angular/issues/31377)) ([bebf089](https://github.com/angular/angular/commit/bebf089)), closes [/github.com/angular/angular/pull/31377#discussion_r299254408](https://github.com//github.com/angular/angular/pull/31377/issues/discussion_r299254408) [/github.com/angular/angular/blob/9e34670b2/packages/core/src/di/interface/provider.ts#L365-L366](https://github.com//github.com/angular/angular/blob/9e34670b2/packages/core/src/di/interface/provider.ts/issues/L365-L366) [/github.com/angular/angular/blob/9e34670b2/packages/core/src/di/interface/provider.ts#L283-L284](https://github.com//github.com/angular/angular/blob/9e34670b2/packages/core/src/di/interface/provider.ts/issues/L283-L284) [/github.com/angular/angular/blob/9e34670b2/packages/core/src/di/index.ts#L23](https://github.com//github.com/angular/angular/blob/9e34670b2/packages/core/src/di/index.ts/issues/L23)
|
||||
|
||||
|
||||
|
||||
<a name="8.1.1"></a>
|
||||
## [8.1.1](https://github.com/angular/angular/compare/8.1.0...8.1.1) (2019-07-10)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **core:** export provider interfaces that are part of the public API types ([#31377](https://github.com/angular/angular/issues/31377)) ([bebf089](https://github.com/angular/angular/commit/bebf089)), closes [/github.com/angular/angular/pull/31377#discussion_r299254408](https://github.com//github.com/angular/angular/pull/31377/issues/discussion_r299254408) [/github.com/angular/angular/blob/9e34670b2/packages/core/src/di/interface/provider.ts#L365-L366](https://github.com//github.com/angular/angular/blob/9e34670b2/packages/core/src/di/interface/provider.ts/issues/L365-L366) [/github.com/angular/angular/blob/9e34670b2/packages/core/src/di/interface/provider.ts#L283-L284](https://github.com//github.com/angular/angular/blob/9e34670b2/packages/core/src/di/interface/provider.ts/issues/L283-L284) [/github.com/angular/angular/blob/9e34670b2/packages/core/src/di/index.ts#L23](https://github.com//github.com/angular/angular/blob/9e34670b2/packages/core/src/di/index.ts/issues/L23)
|
||||
|
||||
|
||||
|
||||
<a name="8.1.0"></a>
|
||||
# [8.1.0](https://github.com/angular/angular/compare/8.1.0-rc.0...8.1.0) (2019-07-02)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **core:** handle `undefined` meta in `injectArgs` ([#31333](https://github.com/angular/angular/issues/31333)) ([80ccd6c](https://github.com/angular/angular/commit/80ccd6c)), closes [CLI #14888](https://github.com/angular/angular-cli/issues/14888)
|
||||
* **service-worker:** cache opaque responses in data groups with `freshness` strategy ([#30977](https://github.com/angular/angular/issues/30977)) ([b0c3453](https://github.com/angular/angular/commit/b0c3453)), closes [#30968](https://github.com/angular/angular/issues/30968)
|
||||
* **service-worker:** cache opaque responses when requests exceeds timeout threshold ([#30977](https://github.com/angular/angular/issues/30977)) ([a9038ef](https://github.com/angular/angular/commit/a9038ef))
|
||||
|
||||
|
||||
|
||||
<a name="8.1.0-rc.0"></a>
|
||||
# [8.1.0-rc.0](https://github.com/angular/angular/compare/8.1.0-next.3...8.1.0-rc.0) (2019-06-26)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **bazel:** exclude all angular schematics folders from metadata build ([#31237](https://github.com/angular/angular/issues/31237)) ([16717fa](https://github.com/angular/angular/commit/16717fa)), closes [#31235](https://github.com/angular/angular/issues/31235)
|
||||
* **bazel:** remove unsupported Css pre-processors from ng new ([#31234](https://github.com/angular/angular/issues/31234)) ([e83667a](https://github.com/angular/angular/commit/e83667a)), closes [#31209](https://github.com/angular/angular/issues/31209)
|
||||
* **bazel:** update ng new schema to match the current ng new schema of [@schematics](https://github.com/schematics)/angular ([#31234](https://github.com/angular/angular/issues/31234)) ([805fc86](https://github.com/angular/angular/commit/805fc86)), closes [#31233](https://github.com/angular/angular/issues/31233)
|
||||
* **compiler:** fix Elements not making a new ParseSourceSpan ([#31190](https://github.com/angular/angular/issues/31190)) ([7035f22](https://github.com/angular/angular/commit/7035f22))
|
||||
* **compiler:** stringify `Object.create(null)` tokens ([#16848](https://github.com/angular/angular/issues/16848)) ([5e53956](https://github.com/angular/angular/commit/5e53956))
|
||||
* **service-worker:** registration failed on Safari ([#31140](https://github.com/angular/angular/issues/31140)) ([a5dd4ed](https://github.com/angular/angular/commit/a5dd4ed)), closes [#31061](https://github.com/angular/angular/issues/31061)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **upgrade:** provide unit test helpers for wiring up injectors ([#16848](https://github.com/angular/angular/issues/16848)) ([3fb78aa](https://github.com/angular/angular/commit/3fb78aa))
|
||||
|
||||
|
||||
|
||||
<a name="8.0.3"></a>
|
||||
## [8.0.3](https://github.com/angular/angular/compare/8.0.2...8.0.3) (2019-06-26)
|
||||
|
||||
@ -80,18 +11,6 @@
|
||||
|
||||
|
||||
|
||||
<a name="8.1.0-next.3"></a>
|
||||
# [8.1.0-next.3](https://github.com/angular/angular/compare/8.1.0-next.2...8.1.0-next.3) (2019-06-19)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **bazel:** builder workspace should use nodejs v10.16.0 ([#31088](https://github.com/angular/angular/issues/31088)) ([a1fc4de](https://github.com/angular/angular/commit/a1fc4de))
|
||||
* **core:** temporarily remove [@deprecated](https://github.com/deprecated) jsdoc tag for a TextBedStatic.get overload ([#30714](https://github.com/angular/angular/issues/30714)) ([6bc9c78](https://github.com/angular/angular/commit/6bc9c78)), closes [#30514](https://github.com/angular/angular/issues/30514)
|
||||
* **language-service:** Remove 'any' in getQuickInfoAtPosition ([#31014](https://github.com/angular/angular/issues/31014)) ([a4601ec](https://github.com/angular/angular/commit/a4601ec))
|
||||
|
||||
|
||||
|
||||
<a name="8.0.2"></a>
|
||||
## [8.0.2](https://github.com/angular/angular/compare/8.0.1...8.0.2) (2019-06-19)
|
||||
|
||||
@ -104,20 +23,6 @@
|
||||
|
||||
|
||||
|
||||
<a name="8.1.0-next.2"></a>
|
||||
# [8.1.0-next.2](https://github.com/angular/angular/compare/8.1.0-next.1...8.1.0-next.2) (2019-06-13)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **bazel:** do not modify tsconfig.json ([#30877](https://github.com/angular/angular/issues/30877)) ([b086676](https://github.com/angular/angular/commit/b086676))
|
||||
* **bazel:** exclude components schematics from build ([#30825](https://github.com/angular/angular/issues/30825)) ([05a43ca](https://github.com/angular/angular/commit/05a43ca))
|
||||
* **bazel:** Load global stylesheet in dev and prod ([#30879](https://github.com/angular/angular/issues/30879)) ([17bfedd](https://github.com/angular/angular/commit/17bfedd))
|
||||
* **common:** expose the `HttpUploadProgressEvent` interface as public API ([#30852](https://github.com/angular/angular/issues/30852)) ([5c18f23](https://github.com/angular/angular/commit/5c18f23)), closes [#30814](https://github.com/angular/angular/issues/30814)
|
||||
* **service-worker:** avoid uncaught rejection warning when registration fails ([#30876](https://github.com/angular/angular/issues/30876)) ([81c2a94](https://github.com/angular/angular/commit/81c2a94))
|
||||
|
||||
|
||||
|
||||
<a name="8.0.1"></a>
|
||||
## [8.0.1](https://github.com/angular/angular/compare/8.0.0...8.0.1) (2019-06-13)
|
||||
|
||||
@ -132,83 +37,24 @@
|
||||
|
||||
|
||||
|
||||
<a name="8.1.0-next.1"></a>
|
||||
# [8.1.0-next.1](https://github.com/angular/angular/compare/8.1.0-beta.0...8.1.0-next.1) (2019-06-05)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **core:** TypeScript related migrations should cater for BOM ([#30719](https://github.com/angular/angular/issues/30719)) ([80394ce](https://github.com/angular/angular/commit/80394ce)), closes [/github.com/angular/angular-cli/blob/master/packages/angular_devkit/schematics/src/tree/recorder.ts#L72](https://github.com//github.com/angular/angular-cli/blob/master/packages/angular_devkit/schematics/src/tree/recorder.ts/issues/L72) [#30713](https://github.com/angular/angular/issues/30713)
|
||||
|
||||
|
||||
|
||||
<a name="8.1.0-beta.0"></a>
|
||||
# [8.1.0-beta.0](https://github.com/angular/angular/compare/8.0.0...8.1.0-beta.0) (2019-05-30)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **bazel:** allow ts_library interop with list-typed inputs ([#30600](https://github.com/angular/angular/issues/30600)) ([3125376](https://github.com/angular/angular/commit/3125376))
|
||||
* **bazel:** Bump ibazel to 0.10.1 for windows fixes ([#30196](https://github.com/angular/angular/issues/30196)) ([1353bf0](https://github.com/angular/angular/commit/1353bf0))
|
||||
* **bazel:** Directly spawn native Bazel binary ([#30306](https://github.com/angular/angular/issues/30306)) ([2a0f497](https://github.com/angular/angular/commit/2a0f497))
|
||||
* **bazel:** Disable sandbox on Mac OS ([#30460](https://github.com/angular/angular/issues/30460)) ([b6b1aec](https://github.com/angular/angular/commit/b6b1aec))
|
||||
* **bazel:** Exclude common/upgrade* in metadata.tsconfig.json ([#30133](https://github.com/angular/angular/issues/30133)) ([1f4c380](https://github.com/angular/angular/commit/1f4c380))
|
||||
* **bazel:** ng test should run specific ts_web_test_suite ([#30526](https://github.com/angular/angular/issues/30526)) ([e688e02](https://github.com/angular/angular/commit/e688e02))
|
||||
* **bazel:** pass correct arguments to http_server in Windows ([#30346](https://github.com/angular/angular/issues/30346)) ([3aff79c](https://github.com/angular/angular/commit/3aff79c)), closes [#29785](https://github.com/angular/angular/issues/29785)
|
||||
* **bazel:** update peerDep ranges ([#30155](https://github.com/angular/angular/issues/30155)) ([4ae0ee8](https://github.com/angular/angular/commit/4ae0ee8))
|
||||
* **bazel:** Use existing npm/yarn lock files ([#30438](https://github.com/angular/angular/issues/30438)) ([ff29ccc](https://github.com/angular/angular/commit/ff29ccc))
|
||||
* **compiler-cli:** log ngcc skipping messages as debug instead of info ([#30232](https://github.com/angular/angular/issues/30232)) ([60a8888](https://github.com/angular/angular/commit/60a8888))
|
||||
* **core:** consistently use ng:/// for sourcemap URLs ([#29826](https://github.com/angular/angular/issues/29826)) ([392473e](https://github.com/angular/angular/commit/392473e))
|
||||
* **core:** CSS sanitizer now allows parens in file names ([#30322](https://github.com/angular/angular/issues/30322)) ([728db88](https://github.com/angular/angular/commit/728db88))
|
||||
* **core:** fix interpolate identifier in AOT ([#30243](https://github.com/angular/angular/issues/30243)) ([30d1f29](https://github.com/angular/angular/commit/30d1f29))
|
||||
* **core:** migrations not always migrating all files ([#30269](https://github.com/angular/angular/issues/30269)) ([349935a](https://github.com/angular/angular/commit/349935a))
|
||||
* **core:** remove deprecated `TestBed.deprecatedOverrideProvider` API ([#30576](https://github.com/angular/angular/issues/30576)) ([a96976e](https://github.com/angular/angular/commit/a96976e))
|
||||
* **core:** require 'static' flag on queries in typings ([#30639](https://github.com/angular/angular/issues/30639)) ([84dd267](https://github.com/angular/angular/commit/84dd267))
|
||||
* **core:** static-query migration errors not printed properly ([#30458](https://github.com/angular/angular/issues/30458)) ([6ceb903](https://github.com/angular/angular/commit/6ceb903))
|
||||
* **core:** static-query migration fails with default parameter values ([#30269](https://github.com/angular/angular/issues/30269)) ([6357d4a](https://github.com/angular/angular/commit/6357d4a))
|
||||
* **core:** static-query migration should gracefully exit if AOT compiler throws ([#30269](https://github.com/angular/angular/issues/30269)) ([509352f](https://github.com/angular/angular/commit/509352f))
|
||||
* **core:** static-query migration should handle queries on accessors ([#30327](https://github.com/angular/angular/issues/30327)) ([0ffdb48](https://github.com/angular/angular/commit/0ffdb48))
|
||||
* **core:** static-query migration should not fallback to test strategy ([#30458](https://github.com/angular/angular/issues/30458)) ([0cdf598](https://github.com/angular/angular/commit/0cdf598))
|
||||
* **core:** static-query migration should not prompt if no queries are used ([#30254](https://github.com/angular/angular/issues/30254)) ([4c12d74](https://github.com/angular/angular/commit/4c12d74))
|
||||
* **core:** static-query usage migration strategy should detect ambiguous query usage ([#30215](https://github.com/angular/angular/issues/30215)) ([8d3365e](https://github.com/angular/angular/commit/8d3365e))
|
||||
* **core:** temporarily remove [@deprecated](https://github.com/deprecated) jsdoc tag for a TextBed.get overload ([#30514](https://github.com/angular/angular/issues/30514)) ([f6bf892](https://github.com/angular/angular/commit/f6bf892)), closes [#29290](https://github.com/angular/angular/issues/29290) [#29905](https://github.com/angular/angular/issues/29905)
|
||||
* **language-service:** Remove tsserverlibrary from rollup globals ([#30123](https://github.com/angular/angular/issues/30123)) ([124e497](https://github.com/angular/angular/commit/124e497))
|
||||
* **router:** ensure `history.state` is set in `eager` update mode ([#30154](https://github.com/angular/angular/issues/30154)) ([b40f6f3](https://github.com/angular/angular/commit/b40f6f3))
|
||||
* **router:** ensure navigations start with the current URL value incase redirect is skipped ([#30344](https://github.com/angular/angular/issues/30344)) ([0fd9d08](https://github.com/angular/angular/commit/0fd9d08)), closes [#30340](https://github.com/angular/angular/issues/30340) [#30160](https://github.com/angular/angular/issues/30160)
|
||||
* **router:** fix a problem with router not responding to back button ([#30160](https://github.com/angular/angular/issues/30160)) ([3327bd8](https://github.com/angular/angular/commit/3327bd8))
|
||||
* **router:** IE 11 bug can break URL unification when comparing objects ([#30393](https://github.com/angular/angular/issues/30393)) ([197584d](https://github.com/angular/angular/commit/197584d))
|
||||
* **router:** type cast correctly for IE 11 bug breaking URL Unification when comparing objects ([#30464](https://github.com/angular/angular/issues/30464)) ([53f3564](https://github.com/angular/angular/commit/53f3564))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **bazel:** use `rbe_autoconfig()` and new container. ([#29336](https://github.com/angular/angular/issues/29336)) ([9abf114](https://github.com/angular/angular/commit/9abf114))
|
||||
* **common:** add ability to watch for AngularJS URL updates through `onUrlChange` hook ([#30466](https://github.com/angular/angular/issues/30466)) ([1aff524](https://github.com/angular/angular/commit/1aff524))
|
||||
* **common:** stricter types for `SlicePipe` ([#30156](https://github.com/angular/angular/issues/30156)) ([95830ee](https://github.com/angular/angular/commit/95830ee))
|
||||
* **core:** deprecate integration with the Web Tracing Framework (WTF) ([#30642](https://github.com/angular/angular/issues/30642)) ([f310a59](https://github.com/angular/angular/commit/f310a59))
|
||||
* **language-service:** Implement `definitionAndBoundSpan` ([#30125](https://github.com/angular/angular/issues/30125)) ([f491673](https://github.com/angular/angular/commit/f491673))
|
||||
* **platform-webworker:** deprecate platform-webworker ([#30642](https://github.com/angular/angular/issues/30642)) ([ccc76f7](https://github.com/angular/angular/commit/ccc76f7))
|
||||
|
||||
|
||||
|
||||
<a name="8.0.0"></a>
|
||||
# [8.0.0](https://github.com/angular/angular/compare/8.0.0-rc.5...8.0.0) (2019-05-28)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add support for TypeScript 3.4 (and drop older versions) ([#29372](https://github.com/angular/angular/issues/29372)) ([ef85336](https://github.com/angular/angular/commit/ef85336))
|
||||
* add support for TypeScript 3.3 (and drop older versions) ([#29004](https://github.com/angular/angular/issues/29004)) ([75748d6](https://github.com/angular/angular/commit/75748d6))
|
||||
* **common:** add ability to watch for AngularJS URL updates through `onUrlChange` hook ([#30466](https://github.com/angular/angular/issues/30466)) ([8022d36](https://github.com/angular/angular/commit/8022d36))
|
||||
* **common:** stricter types for `SlicePipe` ([#30156](https://github.com/angular/angular/issues/30156)) ([722b2fa](https://github.com/angular/angular/commit/722b2fa))
|
||||
* **bazel:** use `rbe_autoconfig()` and new container ([#29336](https://github.com/angular/angular/issues/29336)) ([e562acc](https://github.com/angular/angular/commit/e562acc))
|
||||
* **common:** add @angular/common/upgrade package for `$location`-related APIs ([#30055](https://github.com/angular/angular/issues/30055)) ([152d99e](https://github.com/angular/angular/commit/152d99e))
|
||||
* **common:** add ability to retrieve the state from `Location` service ([#30055](https://github.com/angular/angular/issues/30055)) ([b44b143](https://github.com/angular/angular/commit/b44b143))
|
||||
* **common:** stricter types for SlicePipe ([#30156](https://github.com/angular/angular/issues/30156)) ([722b2fa](https://github.com/angular/angular/commit/722b2fa))
|
||||
* **bazel:** use rbe_autoconfig() and new container. ([#29336](https://github.com/angular/angular/issues/29336)) ([e562acc](https://github.com/angular/angular/commit/e562acc))
|
||||
* **common:** add [@angular](https://github.com/angular)/common/upgrade package for $location-related APIs ([#30055](https://github.com/angular/angular/issues/30055)) ([152d99e](https://github.com/angular/angular/commit/152d99e))
|
||||
* **common:** add ability to retrieve the state from Location service ([#30055](https://github.com/angular/angular/issues/30055)) ([b44b143](https://github.com/angular/angular/commit/b44b143))
|
||||
* **common:** add ability to track all location changes ([#30055](https://github.com/angular/angular/issues/30055)) ([3a9cf3f](https://github.com/angular/angular/commit/3a9cf3f))
|
||||
* **common:** add APIs to read component pieces of URL ([#30055](https://github.com/angular/angular/issues/30055)) ([b635fe8](https://github.com/angular/angular/commit/b635fe8))
|
||||
* **common:** add `MockPlatformLocation` to enable more robust testing of `Location` services ([#30055](https://github.com/angular/angular/issues/30055)) ([d0672c2](https://github.com/angular/angular/commit/d0672c2))
|
||||
* **common:** add `UrlCodec` type for use with upgrade applications ([#30055](https://github.com/angular/angular/issues/30055)) ([ec455e1](https://github.com/angular/angular/commit/ec455e1))
|
||||
* **common:** add MockPlatformLocation to enable more robust testing of Location services ([#30055](https://github.com/angular/angular/issues/30055)) ([d0672c2](https://github.com/angular/angular/commit/d0672c2))
|
||||
* **common:** add UrlCodec type for use with upgrade applications ([#30055](https://github.com/angular/angular/issues/30055)) ([ec455e1](https://github.com/angular/angular/commit/ec455e1))
|
||||
* **common:** provide replacement for AngularJS $location service ([#30055](https://github.com/angular/angular/issues/30055)) ([4277600](https://github.com/angular/angular/commit/4277600))
|
||||
* remove deprecated `DOCUMENT` token from platform-browser ([#28117](https://github.com/angular/angular/issues/28117)) ([3a9d247](https://github.com/angular/angular/commit/3a9d247))
|
||||
* remove deprecated DOCUMENT token from platform-browser ([#28117](https://github.com/angular/angular/issues/28117)) ([3a9d247](https://github.com/angular/angular/commit/3a9d247))
|
||||
* **compiler:** support skipping leading trivia in template source-maps ([#30095](https://github.com/angular/angular/issues/30095)) ([304a12f](https://github.com/angular/angular/commit/304a12f))
|
||||
* **core:** add missing ARIA attributes to html sanitizer ([#29685](https://github.com/angular/angular/issues/29685)) ([909557d](https://github.com/angular/angular/commit/909557d)), closes [#26815](https://github.com/angular/angular/issues/26815)
|
||||
* **router:** deprecate loadChildren:string ([#30073](https://github.com/angular/angular/issues/30073)) ([c61df39](https://github.com/angular/angular/commit/c61df39))
|
||||
@ -217,28 +63,28 @@
|
||||
* **service-worker:** support bypassing SW with specific header/query param ([#30010](https://github.com/angular/angular/issues/30010)) ([6200732](https://github.com/angular/angular/commit/6200732)), closes [#21191](https://github.com/angular/angular/issues/21191)
|
||||
* **compiler-cli:** export tooling definitions ([#29929](https://github.com/angular/angular/issues/29929)) ([e1f51ea](https://github.com/angular/angular/commit/e1f51ea))
|
||||
* **compiler-cli:** lower some exported expressions ([#30038](https://github.com/angular/angular/issues/30038)) ([8e73f9b](https://github.com/angular/angular/commit/8e73f9b))
|
||||
* **core:** add schematics to move deprecated `DOCUMENT` import ([#29950](https://github.com/angular/angular/issues/29950)) ([645e305](https://github.com/angular/angular/commit/645e305))
|
||||
* **core:** add schematics to move deprecated DOCUMENT import ([#29950](https://github.com/angular/angular/issues/29950)) ([645e305](https://github.com/angular/angular/commit/645e305))
|
||||
* **bazel:** update the build to use the new architect api ([#29720](https://github.com/angular/angular/issues/29720)) ([902a53a](https://github.com/angular/angular/commit/902a53a))
|
||||
* remove @angular/http dependency from @angular/platform-server ([#29408](https://github.com/angular/angular/issues/29408)) ([9745f55](https://github.com/angular/angular/commit/9745f55))
|
||||
* remove [@angular](https://github.com/angular)/http dependency from [@angular](https://github.com/angular)/platform-server ([#29408](https://github.com/angular/angular/issues/29408)) ([9745f55](https://github.com/angular/angular/commit/9745f55))
|
||||
* **compiler-cli:** ngcc - make logging more configurable ([#29591](https://github.com/angular/angular/issues/29591)) ([8d3d75e](https://github.com/angular/angular/commit/8d3d75e))
|
||||
* **core:** Add `AbstractType<T>` interface ([#29295](https://github.com/angular/angular/issues/29295)) ([afd4a4e](https://github.com/angular/angular/commit/afd4a4e)), closes [#26491](https://github.com/angular/angular/issues/26491)
|
||||
* **core:** Add "AbstractType<T>" interface ([#29295](https://github.com/angular/angular/issues/29295)) ([afd4a4e](https://github.com/angular/angular/commit/afd4a4e)), closes [#26491](https://github.com/angular/angular/issues/26491)
|
||||
* **core:** template-var-assignment update schematic ([#29608](https://github.com/angular/angular/issues/29608)) ([7c8f4e3](https://github.com/angular/angular/commit/7c8f4e3))
|
||||
* **bazel:** Upgrade rules_nodejs and rules_sass ([#29388](https://github.com/angular/angular/issues/29388)) ([d6d081e](https://github.com/angular/angular/commit/d6d081e))
|
||||
* **service-worker:** support multiple apps on different subpaths of a domain ([#27080](https://github.com/angular/angular/issues/27080)) ([e721c08](https://github.com/angular/angular/commit/e721c08)), closes [#21388](https://github.com/angular/angular/issues/21388)
|
||||
* **bazel:** Eject Bazel ([#29167](https://github.com/angular/angular/issues/29167)) ([36a1550](https://github.com/angular/angular/commit/36a1550))
|
||||
* **bazel:** Hide Bazel files in Bazel builder ([#29110](https://github.com/angular/angular/issues/29110)) ([7060d90](https://github.com/angular/angular/commit/7060d90))
|
||||
* **forms:** clear (remove all) components from a FormArray ([#28918](https://github.com/angular/angular/issues/28918)) ([a68b1a1](https://github.com/angular/angular/commit/a68b1a1)), closes [#18531](https://github.com/angular/angular/issues/18531)
|
||||
* **platform-server:** wait on returned `BEFORE_APP_SERIALIZED` promises ([#29120](https://github.com/angular/angular/issues/29120)) ([7102ea8](https://github.com/angular/angular/commit/7102ea8))
|
||||
* **platform-server:** wait on returned BEFORE_APP_SERIALIZED promises ([#29120](https://github.com/angular/angular/issues/29120)) ([7102ea8](https://github.com/angular/angular/commit/7102ea8))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **bazel:** allow `ts_library` interop with list-typed inputs ([#30600](https://github.com/angular/angular/issues/30600)) ([bf38df4](https://github.com/angular/angular/commit/bf38df4))
|
||||
* **bazel:** allow ts_library interop with list-typed inputs ([#30600](https://github.com/angular/angular/issues/30600)) ([bf38df4](https://github.com/angular/angular/commit/bf38df4))
|
||||
* **bazel:** Disable sandbox on Mac OS ([#30460](https://github.com/angular/angular/issues/30460)) ([3de26a8](https://github.com/angular/angular/commit/3de26a8))
|
||||
* **bazel:** ng test should run specific ts_web_test_suite ([#30526](https://github.com/angular/angular/issues/30526)) ([8bc4da8](https://github.com/angular/angular/commit/8bc4da8))
|
||||
* **core:** remove deprecated `TestBed.deprecatedOverrideProvider` API ([#30576](https://github.com/angular/angular/issues/30576)) ([5a46f94](https://github.com/angular/angular/commit/5a46f94))
|
||||
* **core:** require 'static' flag on queries in typings ([#30641](https://github.com/angular/angular/issues/30641)) ([c8af830](https://github.com/angular/angular/commit/c8af830))
|
||||
* **core:** temporarily remove [@deprecated](https://github.com/deprecated) jsdoc tag for a `TextBed.get` overload ([#30514](https://github.com/angular/angular/issues/30514)) ([561e01d](https://github.com/angular/angular/commit/561e01d)), closes [#29290](https://github.com/angular/angular/issues/29290) [#29905](https://github.com/angular/angular/issues/29905)
|
||||
* **core:** temporarily remove [@deprecated](https://github.com/deprecated) jsdoc tag for a TextBed.get overload ([#30514](https://github.com/angular/angular/issues/30514)) ([561e01d](https://github.com/angular/angular/commit/561e01d)), closes [#29290](https://github.com/angular/angular/issues/29290) [#29905](https://github.com/angular/angular/issues/29905)
|
||||
* **router:** type cast correctly for IE 11 bug breaking URL Unification when comparing objects ([#30464](https://github.com/angular/angular/issues/30464)) ([32daa93](https://github.com/angular/angular/commit/32daa93))
|
||||
* **bazel:** Directly spawn native Bazel binary ([#30306](https://github.com/angular/angular/issues/30306)) ([d1fcc2b](https://github.com/angular/angular/commit/d1fcc2b))
|
||||
* **bazel:** pass correct arguments to http_server in Windows ([#30346](https://github.com/angular/angular/issues/30346)) ([71eba45](https://github.com/angular/angular/commit/71eba45)), closes [#29785](https://github.com/angular/angular/issues/29785)
|
||||
@ -254,7 +100,7 @@
|
||||
* **core:** static-query usage migration strategy should detect ambiguous query usage ([#30215](https://github.com/angular/angular/issues/30215)) ([e295c6a](https://github.com/angular/angular/commit/e295c6a))
|
||||
* **router:** ensure navigations start with the current URL value incase redirect is skipped ([#30344](https://github.com/angular/angular/issues/30344)) ([9b88920](https://github.com/angular/angular/commit/9b88920)), closes [#30340](https://github.com/angular/angular/issues/30340) [#30160](https://github.com/angular/angular/issues/30160)
|
||||
* **router:** IE 11 bug can break URL unification when comparing objects ([#30393](https://github.com/angular/angular/issues/30393)) ([c383491](https://github.com/angular/angular/commit/c383491))
|
||||
* **bazel:** Bump ibazel to 0.10.1 for Windows fixes ([#30196](https://github.com/angular/angular/issues/30196)) ([9f68c35](https://github.com/angular/angular/commit/9f68c35))
|
||||
* **bazel:** Bump ibazel to 0.10.1 for windows fixes ([#30196](https://github.com/angular/angular/issues/30196)) ([9f68c35](https://github.com/angular/angular/commit/9f68c35))
|
||||
* **compiler-cli:** log ngcc skipping messages as debug instead of info ([#30232](https://github.com/angular/angular/issues/30232)) ([548b003](https://github.com/angular/angular/commit/548b003))
|
||||
* **core:** fix interpolate identifier in AOT ([#30243](https://github.com/angular/angular/issues/30243)) ([3fe3a84](https://github.com/angular/angular/commit/3fe3a84))
|
||||
* **router:** ensure `history.state` is set in `eager` update mode ([#30154](https://github.com/angular/angular/issues/30154)) ([9720227](https://github.com/angular/angular/commit/9720227))
|
||||
@ -264,54 +110,57 @@
|
||||
* **bazel:** Exclude common/upgrade* in metadata.tsconfig.json ([#30133](https://github.com/angular/angular/issues/30133)) ([6711f22](https://github.com/angular/angular/commit/6711f22))
|
||||
* **bazel:** update peerDep ranges ([#30155](https://github.com/angular/angular/issues/30155)) ([6067583](https://github.com/angular/angular/commit/6067583))
|
||||
* **bazel:** make name param in ng add optional ([#30074](https://github.com/angular/angular/issues/30074)) ([0b5f480](https://github.com/angular/angular/commit/0b5f480))
|
||||
* **bazel:** Make sure only single copy of @angular/bazel is installed ([#30072](https://github.com/angular/angular/issues/30072)) ([2905bf5](https://github.com/angular/angular/commit/2905bf5))
|
||||
* **bazel:** Make sure only single copy of `[@angular](https://github.com/angular)/bazel` is installed ([#30072](https://github.com/angular/angular/issues/30072)) ([2905bf5](https://github.com/angular/angular/commit/2905bf5))
|
||||
* **bazel:** transitive npm deps in ng_module ([#30065](https://github.com/angular/angular/issues/30065)) ([61365a9](https://github.com/angular/angular/commit/61365a9))
|
||||
* **common:** add upgrade sub-package to `ng_package` rule for @angular/common ([#30117](https://github.com/angular/angular/issues/30117)) ([6de4cbd](https://github.com/angular/angular/commit/6de4cbd)), closes [#30055](https://github.com/angular/angular/issues/30055) [#30116](https://github.com/angular/angular/issues/30116)
|
||||
* **common:** adjust `MockPlatformLocation` to set state to new object ([#30055](https://github.com/angular/angular/issues/30055)) ([825efa8](https://github.com/angular/angular/commit/825efa8))
|
||||
* **common:** add upgrade sub-package to ng_package rule for [@angular](https://github.com/angular)/common ([#30117](https://github.com/angular/angular/issues/30117)) ([6de4cbd](https://github.com/angular/angular/commit/6de4cbd)), closes [#30055](https://github.com/angular/angular/issues/30055) [#30116](https://github.com/angular/angular/issues/30116)
|
||||
* **common:** adjust MockPlatformLocation to set state to new object ([#30055](https://github.com/angular/angular/issues/30055)) ([825efa8](https://github.com/angular/angular/commit/825efa8))
|
||||
* **compiler:** Fix compiler crash due to isSkipSelf of null ([#30075](https://github.com/angular/angular/issues/30075)) ([28fd5ab](https://github.com/angular/angular/commit/28fd5ab))
|
||||
* **upgrade:** do not break if `onMicrotaskEmpty` emits while a `$digest` is in progress ([#29794](https://github.com/angular/angular/issues/29794)) ([0ddf2e7](https://github.com/angular/angular/commit/0ddf2e7)), closes [#24680](https://github.com/angular/angular/issues/24680) [/github.com/angular/angular/blob/78146c189/packages/core/src/util/ng_dev_mode.ts#L12](https://github.com//github.com/angular/angular/blob/78146c189/packages/core/src/util/ng_dev_mode.ts/issues/L12) [#24680](https://github.com/angular/angular/issues/24680)
|
||||
* **bazel:** do not typecheck core schematic files ([#29876](https://github.com/angular/angular/issues/29876)) ([2ba799d](https://github.com/angular/angular/commit/2ba799d))
|
||||
* **bazel:** restore `ng build --prod` ([#30005](https://github.com/angular/angular/issues/30005)) ([96a8289](https://github.com/angular/angular/commit/96a8289))
|
||||
* **common:** prevent repeated application of `HttpParams` mutations ([#29045](https://github.com/angular/angular/issues/29045)) ([8e8e89a](https://github.com/angular/angular/commit/8e8e89a)), closes [#20430](https://github.com/angular/angular/issues/20430)
|
||||
* **common:** async pipe will properly check when it receives an NaN value from an observable ([#22305](https://github.com/angular/angular/issues/22305)) ([3f6bf6d](https://github.com/angular/angular/commit/3f6bf6d)), closes [#15721](https://github.com/angular/angular/issues/15721)
|
||||
* **core:** don't include a local `EventListener` in typings ([#29809](https://github.com/angular/angular/issues/29809)) ([4bde40f](https://github.com/angular/angular/commit/4bde40f)), closes [#29806](https://github.com/angular/angular/issues/29806)
|
||||
* **core:** use shakeable global definitions ([#29929](https://github.com/angular/angular/issues/29929)) ([e5905bb](https://github.com/angular/angular/commit/e5905bb))
|
||||
* **bazel:** restore ng build --prod ([#30005](https://github.com/angular/angular/issues/30005)) ([96a8289](https://github.com/angular/angular/commit/96a8289))
|
||||
* **common:** prevent repeated application of HttpParams mutations ([#29045](https://github.com/angular/angular/issues/29045)) ([8e8e89a](https://github.com/angular/angular/commit/8e8e89a)), closes [#20430](https://github.com/angular/angular/issues/20430)
|
||||
* **common:** async pipe will properly check when it recieves an NaN value from an observable ([#22305](https://github.com/angular/angular/issues/22305)) ([3f6bf6d](https://github.com/angular/angular/commit/3f6bf6d)), closes [#15721](https://github.com/angular/angular/issues/15721)
|
||||
* **core:** don't include a local `EventListener` in typings ([#29809](https://github.com/angular/angular/issues/29809)) ([4bde40f](https://github.com/angular/angular/commit/4bde40f)), closes [/github.com/angular/angular/blob/303eae918d997070a36b523ddc97e018f622c258/packages/core/src/debug/debug_node.ts#L32](https://github.com//github.com/angular/angular/blob/303eae918d997070a36b523ddc97e018f622c258/packages/core/src/debug/debug_node.ts/issues/L32) [#29806](https://github.com/angular/angular/issues/29806)
|
||||
* **core:** use shakeable global definitions ([#29929](https://github.com/angular/angular/issues/29929)) ([e5905bb](https://github.com/angular/angular/commit/e5905bb)), closes [/github.com/angular/angular-cli/blob/5fc1f2499cbe57f9a95e4b0dfced130eb3a8046d/packages/angular_devkit/build_angular/src/angular-cli-files/models/webpack-configs/common.ts#L279-L282](https://github.com//github.com/angular/angular-cli/blob/5fc1f2499cbe57f9a95e4b0dfced130eb3a8046d/packages/angular_devkit/build_angular/src/angular-cli-files/models/webpack-configs/common.ts/issues/L279-L282)
|
||||
* **language-service:** Use proper types instead of any ([#29942](https://github.com/angular/angular/issues/29942)) ([1a56cd5](https://github.com/angular/angular/commit/1a56cd5))
|
||||
* **bazel:** Install packages after `ng add` when invoked independently ([#29852](https://github.com/angular/angular/issues/29852)) ([bd2ce9c](https://github.com/angular/angular/commit/bd2ce9c))
|
||||
* **compiler-cli:** pass config path to `ts.parseJsonConfigFileContent` ([#29872](https://github.com/angular/angular/issues/29872)) ([86a3f90](https://github.com/angular/angular/commit/86a3f90))
|
||||
* **compiler-cli:** pass config path to ts.parseJsonConfigFileContent ([#29872](https://github.com/angular/angular/issues/29872)) ([86a3f90](https://github.com/angular/angular/commit/86a3f90)), closes [/github.com/Microsoft/TypeScript/blob/025d82633915b67003ea38ba40b9239a19721c13/src/compiler/emitter.ts#L56-L57](https://github.com//github.com/Microsoft/TypeScript/blob/025d82633915b67003ea38ba40b9239a19721c13/src/compiler/emitter.ts/issues/L56-L57)
|
||||
* **router:** support non-NgFactory promise in loadChildren typings ([#29832](https://github.com/angular/angular/issues/29832)) ([2bfb6a0](https://github.com/angular/angular/commit/2bfb6a0))
|
||||
* **bazel:** add `configuration_env_vars = ["compile"]` to generated `@npm//@angular/bazel/bin:ngc-wrapped` `nodejs_binary` ([#29694](https://github.com/angular/angular/issues/29694)) ([2e66ddf](https://github.com/angular/angular/commit/2e66ddf))
|
||||
* **bazel:** add configuration_env_vars = ["compile"] to generated [@npm](https://github.com/npm)//[@angular](https://github.com/angular)/bazel/bin:ngc-wrapped nodejs_binary ([#29694](https://github.com/angular/angular/issues/29694)) ([2e66ddf](https://github.com/angular/angular/commit/2e66ddf))
|
||||
* **bazel:** docs formatting ([#29817](https://github.com/angular/angular/issues/29817)) ([cc2e4b6](https://github.com/angular/angular/commit/cc2e4b6))
|
||||
* **bazel:** remove karma-jasmine from `ts_web_test_suite` ([#29695](https://github.com/angular/angular/issues/29695)) ([2bd9214](https://github.com/angular/angular/commit/2bd9214))
|
||||
* **bazel:** remove karma-jasmine from ts_web_test_suite ([#29695](https://github.com/angular/angular/issues/29695)) ([2bd9214](https://github.com/angular/angular/commit/2bd9214))
|
||||
* **bazel:** support running ng-add on minimal applications ([#29681](https://github.com/angular/angular/issues/29681)) ([9810c6c](https://github.com/angular/angular/commit/9810c6c)), closes [#29680](https://github.com/angular/angular/issues/29680)
|
||||
* **common:** add `@Injectable()` to common pipes ([#29834](https://github.com/angular/angular/issues/29834)) ([387fbb8](https://github.com/angular/angular/commit/387fbb8))
|
||||
* **compiler-cli:** ensure `LogicalProjectPaths` always start with a slash ([#29627](https://github.com/angular/angular/issues/29627)) ([e02684e](https://github.com/angular/angular/commit/e02684e))
|
||||
* **compiler-cli:** ensure LogicalProjectPaths always start with a slash ([#29627](https://github.com/angular/angular/issues/29627)) ([e02684e](https://github.com/angular/angular/commit/e02684e))
|
||||
* **core:** add missing migration to npm package ([#29705](https://github.com/angular/angular/issues/29705)) ([96b76dc](https://github.com/angular/angular/commit/96b76dc))
|
||||
* **core:** call `ngOnDestroy` for tree-shakeable providers ([#28943](https://github.com/angular/angular/issues/28943)) ([30b0442](https://github.com/angular/angular/commit/30b0442)), closes [#28927](https://github.com/angular/angular/issues/28927)
|
||||
* **core:** Deprecate `TestBed.get(...):any` ([#29290](https://github.com/angular/angular/issues/29290)) ([609024f](https://github.com/angular/angular/commit/609024f)), closes [#13785](https://github.com/angular/angular/issues/13785) [#26491](https://github.com/angular/angular/issues/26491)
|
||||
* **core:** call ngOnDestroy for tree-shakeable providers ([#28943](https://github.com/angular/angular/issues/28943)) ([30b0442](https://github.com/angular/angular/commit/30b0442)), closes [#28927](https://github.com/angular/angular/issues/28927)
|
||||
* **core:** Deprecate TestBed.get(...):any ([#29290](https://github.com/angular/angular/issues/29290)) ([609024f](https://github.com/angular/angular/commit/609024f)), closes [#13785](https://github.com/angular/angular/issues/13785) [#26491](https://github.com/angular/angular/issues/26491)
|
||||
* **core:** resolve ts compile issues due to lenient tsconfig ([#29843](https://github.com/angular/angular/issues/29843)) ([54058ba](https://github.com/angular/angular/commit/54058ba))
|
||||
* **platform-browser:** insert `APP_ID` in styles, contentAttr and hostAttr ([#17745](https://github.com/angular/angular/issues/17745)) ([712d60e](https://github.com/angular/angular/commit/712d60e))
|
||||
* **platform-browser:** insert APP_ID in styles, contentAttr and hostAttr ([#17745](https://github.com/angular/angular/issues/17745)) ([712d60e](https://github.com/angular/angular/commit/712d60e))
|
||||
* **bazel:** use //:tsconfig.json as the default for ng_module ([#29670](https://github.com/angular/angular/issues/29670)) ([#29711](https://github.com/angular/angular/issues/29711)) ([9e33dc3](https://github.com/angular/angular/commit/9e33dc3))
|
||||
* **platform-browser:** insert APP_ID in styles, contentAttr and hostAttr ([#17745](https://github.com/angular/angular/issues/17745)) ([ca14509](https://github.com/angular/angular/commit/ca14509))
|
||||
* **bazel:** Update schematics to support routing ([#29548](https://github.com/angular/angular/issues/29548)) ([401b8ee](https://github.com/angular/angular/commit/401b8ee))
|
||||
* **bazel:** use `//:tsconfig.json` as the default for `ng_module` ([#29670](https://github.com/angular/angular/issues/29670)) ([b14537a](https://github.com/angular/angular/commit/b14537a))
|
||||
* **bazel:** use //:tsconfig.json as the default for ng_module ([#29670](https://github.com/angular/angular/issues/29670)) ([b14537a](https://github.com/angular/angular/commit/b14537a))
|
||||
* **compiler-cli:** ngcc - cope with processing entry-points multiple times ([#29657](https://github.com/angular/angular/issues/29657)) ([6b39c9c](https://github.com/angular/angular/commit/6b39c9c))
|
||||
* **core:** static-query schematic should detect static queries in getters. ([#29609](https://github.com/angular/angular/issues/29609)) ([33016b8](https://github.com/angular/angular/commit/33016b8))
|
||||
* **common:** escape query selector used when anchor scrolling ([#29577](https://github.com/angular/angular/issues/29577)) ([7671c73](https://github.com/angular/angular/commit/7671c73)), closes [#28193](https://github.com/angular/angular/issues/28193)
|
||||
* **router:** adjust setting navigationTransition when a new navigation cancels an existing one ([#29636](https://github.com/angular/angular/issues/29636)) ([e884c0c](https://github.com/angular/angular/commit/e884c0c)), closes [#29389](https://github.com/angular/angular/issues/29389) [#29590](https://github.com/angular/angular/issues/29590)
|
||||
* **bazel:** allow `ng_module` users to set `createExternalSymbolFactoryReexports` ([#29459](https://github.com/angular/angular/issues/29459)) ([21be0fb](https://github.com/angular/angular/commit/21be0fb))
|
||||
* **bazel:** allow ng_module users to set createExternalSymbolFactoryReexports ([#29459](https://github.com/angular/angular/issues/29459)) ([21be0fb](https://github.com/angular/angular/commit/21be0fb))
|
||||
* **bazel:** workaround problem reading summary files from node_modules ([#29459](https://github.com/angular/angular/issues/29459)) ([769d960](https://github.com/angular/angular/commit/769d960))
|
||||
* **compiler:** inherit param types when class has a constructor which takes no declared parameters and delegates up ([#29232](https://github.com/angular/angular/issues/29232)) ([0007564](https://github.com/angular/angular/commit/0007564))
|
||||
* **core:** parse incorrect ML open tag as text ([#29328](https://github.com/angular/angular/issues/29328)) ([dafbbf8](https://github.com/angular/angular/commit/dafbbf8)), closes [#29231](https://github.com/angular/angular/issues/29231)
|
||||
* **core:** static-query schematic should detect queries in `ngDoCheck` and `ngOnChanges` ([#29492](https://github.com/angular/angular/issues/29492)) ([09fab58](https://github.com/angular/angular/commit/09fab58))
|
||||
* **router:** support `NgFactory` promise in loadChildren typings ([#29392](https://github.com/angular/angular/issues/29392)) ([26a8c59](https://github.com/angular/angular/commit/26a8c59))
|
||||
* **core:** static-query schematic should detect queries in "ngDoCheck" and "ngOnChanges" ([#29492](https://github.com/angular/angular/issues/29492)) ([09fab58](https://github.com/angular/angular/commit/09fab58))
|
||||
* **router:** support NgFactory promise in loadChildren typings ([#29392](https://github.com/angular/angular/issues/29392)) ([26a8c59](https://github.com/angular/angular/commit/26a8c59))
|
||||
* **bazel:** correct regexp test for self-references in metadata ([#29346](https://github.com/angular/angular/issues/29346)) ([9d090cb](https://github.com/angular/angular/commit/9d090cb))
|
||||
* **bazel:** don't produce self-references in metadata ([#29317](https://github.com/angular/angular/issues/29317)) ([3facdeb](https://github.com/angular/angular/commit/3facdeb)), closes [#29315](https://github.com/angular/angular/issues/29315)
|
||||
* **bazel:** fix strict null checks compile error in `packages/bazel/src/schematics/ng-add/index.ts` ([#29282](https://github.com/angular/angular/issues/29282)) ([9a7f560](https://github.com/angular/angular/commit/9a7f560))
|
||||
* **bazel:** Remove @angular/upgrade from dev dependencies ([#29319](https://github.com/angular/angular/issues/29319)) ([1db8bf3](https://github.com/angular/angular/commit/1db8bf3))
|
||||
* **bazel:** fix strict null checks compile error in packages/bazel/src/schematics/ng-add/index.ts ([#29282](https://github.com/angular/angular/issues/29282)) ([9a7f560](https://github.com/angular/angular/commit/9a7f560))
|
||||
* **bazel:** Remove [@angular](https://github.com/angular)/upgrade from dev dependencies ([#29319](https://github.com/angular/angular/issues/29319)) ([1db8bf3](https://github.com/angular/angular/commit/1db8bf3))
|
||||
* **bazel:** Support new e2e project layout ([#29318](https://github.com/angular/angular/issues/29318)) ([8ef690c](https://github.com/angular/angular/commit/8ef690c))
|
||||
* **bazel:** turn off pure call tree shaking for ng_package ([#29210](https://github.com/angular/angular/issues/29210)) ([4990b93](https://github.com/angular/angular/commit/4990b93))
|
||||
* **compiler-cli:** incorrect metadata bundle for multiple unnamed re-exports ([#29360](https://github.com/angular/angular/issues/29360)) ([105cfaf](https://github.com/angular/angular/commit/105cfaf))
|
||||
* **compiler-cli:** incorrect metadata bundle for multiple unnamed re-exports ([#29360](https://github.com/angular/angular/issues/29360)) ([105cfaf](https://github.com/angular/angular/commit/105cfaf)), 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)
|
||||
* **core:** don't wrap `<tr>` and `<col>` elements into a required parent ([#29219](https://github.com/angular/angular/issues/29219)) ([f2dc32e](https://github.com/angular/angular/commit/f2dc32e))
|
||||
* **core:** parse incorrect ML open tag as text ([#29328](https://github.com/angular/angular/issues/29328)) ([4605df8](https://github.com/angular/angular/commit/4605df8)), closes [#29231](https://github.com/angular/angular/issues/29231)
|
||||
* **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/angular/material2/blob/master/tools/package-tools/build-release.ts#L78-L85)
|
||||
* **bazel:** add missing binary path for api-extractor ([#29202](https://github.com/angular/angular/issues/29202)) ([df354d1](https://github.com/angular/angular/commit/df354d1))
|
||||
* **bazel:** ng build should produce prod bundle ([#29136](https://github.com/angular/angular/issues/29136)) ([14ce8a9](https://github.com/angular/angular/commit/14ce8a9))
|
||||
* **compiler:** ensure template is updated if an output is transformed ([#29041](https://github.com/angular/angular/issues/29041)) ([c7e4931](https://github.com/angular/angular/commit/c7e4931))
|
||||
@ -325,16 +174,16 @@
|
||||
|
||||
### BREAKING CHANGES
|
||||
|
||||
* **bazel:** @bazel/typescript is now a peerDependency of @angular/bazel so users of @angular/bazel must add @bazel/typescript to their package.json
|
||||
* **bazel:** `ng_module` now depends on a minimum of build_bazel_rules_nodejs 0.27.12
|
||||
* **core:** In Angular version 8, it's required that all `@ViewChild` and `@ContentChild`
|
||||
queries have a `'static'` flag specifying whether the query is 'static' or
|
||||
* **bazel:** @bazel/typescript is now a peerDependency of @angular/bazel so user's of @angular/bazel must add @bazel/typescript to their package.json
|
||||
* **bazel:** ng_module now depends on a minimum of build_bazel_rules_nodejs 0.27.12
|
||||
* **core:** In Angular version 8, it's required that all @ViewChild and @ContentChild
|
||||
queries have a 'static' flag specifying whether the query is 'static' or
|
||||
'dynamic'. The compiler previously sorted queries automatically, but in
|
||||
8.0 developers are required to explicitly specify which behavior is wanted.
|
||||
This is a temporary requirement as part of a migration; see
|
||||
[static query migration guide](https://v8.angular.io/guide/static-query-migration) for more details.
|
||||
https://v8.angular.io/guide/static-query-migration for more details.
|
||||
|
||||
`@ViewChildren` and `@ContentChildren` queries are always dynamic, and so are
|
||||
@ViewChildren and @ContentChildren queries are always dynamic, and so are
|
||||
unaffected.
|
||||
|
||||
* `TestBed.get()` has two signatures, one which is typed and another which accepts and returns `any`. The signature for `any` is deprecated; all usage of `TestBed.get()` should go through the typed API. This mainly affects string tokens
|
||||
@ -368,8 +217,8 @@ This is a temporary requirement as part of a migration; see
|
||||
- `<tr>` would be wrapped in `<tbody>` if not inside `<tbody>`, `<tfoot>` or `<thead>`;
|
||||
- `<col>` would be wrapped in `<colgroup>` if not inside `<colgroup>`.
|
||||
|
||||
This mechanism of automatic wrapping / auto-correcting was problematic for several reasons:
|
||||
- it is non-obvious and arbitrary (ex. there are more HTML elements that have rules for parent type);
|
||||
This meachanism of automatic wrapping / auto-correcting was problematic for several reasons:
|
||||
- it is non-obvious and arbitrary (ex. there are more HTML elements that has rules for parent type);
|
||||
- it is incorrect for cases where `<tr>` / `<col>` are at the root of a component's content, ex.:
|
||||
|
||||
```html
|
||||
@ -381,13 +230,10 @@ This is a temporary requirement as part of a migration; see
|
||||
In the above example the `<projecting-tr-inside-tbody>` component could be "surprised" to see additional
|
||||
`<tbody>` elements inserted by Angular HTML parser.
|
||||
|
||||
* **http:** The deprecated @angular/http package has been removed, the @angular/common/http package should be used instead.
|
||||
For details on how to migrate, please refer to [the deprecations guide](https://angular.io/guide/deprecations#angularhttp).
|
||||
|
||||
|
||||
* TypeScript 3.1 and 3.2 are no longer supported.
|
||||
|
||||
Please update your TypeScript version to 3.4, as version 3.3 is also not supported.
|
||||
Please update your TypeScript version to 3.3
|
||||
|
||||
|
||||
<a name="8.0.0-rc.5"></a>
|
||||
@ -473,17 +319,6 @@ unaffected.
|
||||
|
||||
|
||||
|
||||
|
||||
<a name="7.2.15"></a>
|
||||
## [7.2.15](https://github.com/angular/angular/compare/7.2.14...7.2.15) (2019-05-07)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **upgrade:** do not break if `onMicrotaskEmpty` emits while a `$digest` is in progress ([#29794](https://github.com/angular/angular/issues/29794)) ([#30107](https://github.com/angular/angular/issues/30107)) ([1084c19](https://github.com/angular/angular/commit/1084c19)), closes [#24680](https://github.com/angular/angular/issues/24680) [/github.com/angular/angular/blob/78146c189/packages/core/src/util/ng_dev_mode.ts#L12](https://github.com//github.com/angular/angular/blob/78146c189/packages/core/src/util/ng_dev_mode.ts/issues/L12) [#24680](https://github.com/angular/angular/issues/24680)
|
||||
|
||||
|
||||
|
||||
<a name="8.0.0-rc.2"></a>
|
||||
# [8.0.0-rc.2](https://github.com/angular/angular/compare/8.0.0-rc.1...8.0.0-rc.2) (2019-04-29)
|
||||
|
||||
@ -513,9 +348,9 @@ unaffected.
|
||||
### Bug Fixes
|
||||
|
||||
* **bazel:** make name param in ng add optional ([#30074](https://github.com/angular/angular/issues/30074)) ([0b5f480](https://github.com/angular/angular/commit/0b5f480))
|
||||
* **bazel:** Make sure only single copy of `@angular/bazel` is installed ([#30072](https://github.com/angular/angular/issues/30072)) ([2905bf5](https://github.com/angular/angular/commit/2905bf5))
|
||||
* **bazel:** Make sure only single copy of `[@angular](https://github.com/angular)/bazel` is installed ([#30072](https://github.com/angular/angular/issues/30072)) ([2905bf5](https://github.com/angular/angular/commit/2905bf5))
|
||||
* **bazel:** transitive npm deps in ng_module ([#30065](https://github.com/angular/angular/issues/30065)) ([61365a9](https://github.com/angular/angular/commit/61365a9))
|
||||
* **common:** add upgrade sub-package to ng_package rule for @angular/common ([#30117](https://github.com/angular/angular/issues/30117)) ([6de4cbd](https://github.com/angular/angular/commit/6de4cbd)), closes [#30055](https://github.com/angular/angular/issues/30055) [#30116](https://github.com/angular/angular/issues/30116)
|
||||
* **common:** add upgrade sub-package to ng_package rule for [@angular](https://github.com/angular)/common ([#30117](https://github.com/angular/angular/issues/30117)) ([6de4cbd](https://github.com/angular/angular/commit/6de4cbd)), closes [#30055](https://github.com/angular/angular/issues/30055) [#30116](https://github.com/angular/angular/issues/30116)
|
||||
* **common:** adjust MockPlatformLocation to set state to new object ([#30055](https://github.com/angular/angular/issues/30055)) ([825efa8](https://github.com/angular/angular/commit/825efa8))
|
||||
* **compiler:** Fix compiler crash due to isSkipSelf of null ([#30075](https://github.com/angular/angular/issues/30075)) ([28fd5ab](https://github.com/angular/angular/commit/28fd5ab))
|
||||
* **upgrade:** do not break if `onMicrotaskEmpty` emits while a `$digest` is in progress ([#29794](https://github.com/angular/angular/issues/29794)) ([0ddf2e7](https://github.com/angular/angular/commit/0ddf2e7)), closes [#24680](https://github.com/angular/angular/issues/24680) [/github.com/angular/angular/blob/78146c189/packages/core/src/util/ng_dev_mode.ts#L12](https://github.com//github.com/angular/angular/blob/78146c189/packages/core/src/util/ng_dev_mode.ts/issues/L12) [#24680](https://github.com/angular/angular/issues/24680)
|
||||
@ -523,7 +358,7 @@ unaffected.
|
||||
|
||||
### Features
|
||||
|
||||
* **common:** add @angular/common/upgrade package for $location-related APIs ([#30055](https://github.com/angular/angular/issues/30055)) ([152d99e](https://github.com/angular/angular/commit/152d99e))
|
||||
* **common:** add [@angular](https://github.com/angular)/common/upgrade package for $location-related APIs ([#30055](https://github.com/angular/angular/issues/30055)) ([152d99e](https://github.com/angular/angular/commit/152d99e))
|
||||
* **common:** add ability to retrieve the state from Location service ([#30055](https://github.com/angular/angular/issues/30055)) ([b44b143](https://github.com/angular/angular/commit/b44b143))
|
||||
* **common:** add ability to track all location changes ([#30055](https://github.com/angular/angular/issues/30055)) ([3a9cf3f](https://github.com/angular/angular/commit/3a9cf3f))
|
||||
* **common:** add APIs to read component pieces of URL ([#30055](https://github.com/angular/angular/issues/30055)) ([b635fe8](https://github.com/angular/angular/commit/b635fe8))
|
||||
@ -586,7 +421,7 @@ unaffected.
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **bazel:** add configuration_env_vars = ["compile"] to generated [@npm](https://github.com/npm)//@angular/bazel/bin:ngc-wrapped nodejs_binary ([#29694](https://github.com/angular/angular/issues/29694)) ([2e66ddf](https://github.com/angular/angular/commit/2e66ddf))
|
||||
* **bazel:** add configuration_env_vars = ["compile"] to generated [@npm](https://github.com/npm)//[@angular](https://github.com/angular)/bazel/bin:ngc-wrapped nodejs_binary ([#29694](https://github.com/angular/angular/issues/29694)) ([2e66ddf](https://github.com/angular/angular/commit/2e66ddf))
|
||||
* **bazel:** docs formatting ([#29817](https://github.com/angular/angular/issues/29817)) ([cc2e4b6](https://github.com/angular/angular/commit/cc2e4b6))
|
||||
* **bazel:** remove karma-jasmine from ts_web_test_suite ([#29695](https://github.com/angular/angular/issues/29695)) ([2bd9214](https://github.com/angular/angular/commit/2bd9214))
|
||||
* **bazel:** support running ng-add on minimal applications ([#29681](https://github.com/angular/angular/issues/29681)) ([9810c6c](https://github.com/angular/angular/commit/9810c6c)), closes [#29680](https://github.com/angular/angular/issues/29680)
|
||||
@ -661,7 +496,7 @@ let service = TestBed.get(SERVICE_TOKEN); // type Service
|
||||
|
||||
### Features
|
||||
|
||||
* remove @angular/http dependency from @angular/platform-server ([#29408](https://github.com/angular/angular/issues/29408)) ([9745f55](https://github.com/angular/angular/commit/9745f55))
|
||||
* remove [@angular](https://github.com/angular)/http dependency from [@angular](https://github.com/angular)/platform-server ([#29408](https://github.com/angular/angular/issues/29408)) ([9745f55](https://github.com/angular/angular/commit/9745f55))
|
||||
* **compiler-cli:** ngcc - make logging more configurable ([#29591](https://github.com/angular/angular/issues/29591)) ([8d3d75e](https://github.com/angular/angular/commit/8d3d75e))
|
||||
* **core:** Add "AbstractType<T>" interface ([#29295](https://github.com/angular/angular/issues/29295)) ([afd4a4e](https://github.com/angular/angular/commit/afd4a4e)), closes [#26491](https://github.com/angular/angular/issues/26491)
|
||||
* **core:** template-var-assignment update schematic ([#29608](https://github.com/angular/angular/issues/29608)) ([7c8f4e3](https://github.com/angular/angular/commit/7c8f4e3))
|
||||
@ -721,7 +556,7 @@ This release contains various API docs improvements.
|
||||
* **bazel:** correct regexp test for self-references in metadata ([#29346](https://github.com/angular/angular/issues/29346)) ([9d090cb](https://github.com/angular/angular/commit/9d090cb))
|
||||
* **bazel:** don't produce self-references in metadata ([#29317](https://github.com/angular/angular/issues/29317)) ([3facdeb](https://github.com/angular/angular/commit/3facdeb)), closes [#29315](https://github.com/angular/angular/issues/29315)
|
||||
* **bazel:** fix strict null checks compile error in packages/bazel/src/schematics/ng-add/index.ts ([#29282](https://github.com/angular/angular/issues/29282)) ([9a7f560](https://github.com/angular/angular/commit/9a7f560))
|
||||
* **bazel:** Remove @angular/upgrade from dev dependencies ([#29319](https://github.com/angular/angular/issues/29319)) ([1db8bf3](https://github.com/angular/angular/commit/1db8bf3))
|
||||
* **bazel:** Remove [@angular](https://github.com/angular)/upgrade from dev dependencies ([#29319](https://github.com/angular/angular/issues/29319)) ([1db8bf3](https://github.com/angular/angular/commit/1db8bf3))
|
||||
* **bazel:** Support new e2e project layout ([#29318](https://github.com/angular/angular/issues/29318)) ([8ef690c](https://github.com/angular/angular/commit/8ef690c))
|
||||
* **bazel:** turn off pure call tree shaking for ng_package ([#29210](https://github.com/angular/angular/issues/29210)) ([4990b93](https://github.com/angular/angular/commit/4990b93))
|
||||
* **compiler-cli:** incorrect metadata bundle for multiple unnamed re-exports ([#29360](https://github.com/angular/angular/issues/29360)) ([105cfaf](https://github.com/angular/angular/commit/105cfaf)), 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)
|
||||
@ -742,8 +577,8 @@ This release contains various API docs improvements.
|
||||
- `<tr>` would be wrapped in `<tbody>` if not inside `<tbody>`, `<tfoot>` or `<thead>`;
|
||||
- `<col>` would be wrapped in `<colgroup>` if not inside `<colgroup>`.
|
||||
|
||||
This mechanism of automatic wrapping / auto-correcting was problematic for several reasons:
|
||||
- it is non-obvious and arbitrary (ex. there are more HTML elements that have rules for parent type);
|
||||
This meachanism of automatic wrapping / auto-correcting was problematic for several reasons:
|
||||
- it is non-obvious and arbitrary (ex. there are more HTML elements that has rules for parent type);
|
||||
- it is incorrect for cases where `<tr>` / `<col>` are at the root of a component's content, ex.:
|
||||
|
||||
```html
|
||||
@ -1144,7 +979,7 @@ This release contains various API docs improvements.
|
||||
* **bazel:** unable to launch protractor test on windows ([#27850](https://github.com/angular/angular/issues/27850)) ([1e6c9be](https://github.com/angular/angular/commit/1e6c9be))
|
||||
* **bazel:** devserver entry_module should have underscore name ([#27719](https://github.com/angular/angular/issues/27719)) ([f57916c](https://github.com/angular/angular/commit/f57916c))
|
||||
* **bazel:** emit full node stack traces when Angular compilation crashes ([#27678](https://github.com/angular/angular/issues/27678)) ([522919a](https://github.com/angular/angular/commit/522919a))
|
||||
* **bazel:** fix major/minor semver check between @angular/bazel npm packager version and angular bazel repo version ([#27635](https://github.com/angular/angular/issues/27635)) ([1cc08b4](https://github.com/angular/angular/commit/1cc08b4))
|
||||
* **bazel:** fix major/minor semver check between [@angular](https://github.com/angular)/bazel npm packager version and angular bazel repo version ([#27635](https://github.com/angular/angular/issues/27635)) ([1cc08b4](https://github.com/angular/angular/commit/1cc08b4))
|
||||
* **bazel:** Load http_archive and rules_nodejs dependencies ([#27609](https://github.com/angular/angular/issues/27609)) ([8313ffc](https://github.com/angular/angular/commit/8313ffc))
|
||||
* **bazel:** ng_package writes unrelevant definitions to bazel out ([#27519](https://github.com/angular/angular/issues/27519)) ([44dfa60](https://github.com/angular/angular/commit/44dfa60)), closes [/github.com/angular/angular/blob/4f9374951d67c75f67a31c110bd61ab72563db7d/packages/bazel/src/ng_package/packager.ts#L105-L124](https://github.com//github.com/angular/angular/blob/4f9374951d67c75f67a31c110bd61ab72563db7d/packages/bazel/src/ng_package/packager.ts/issues/L105-L124)
|
||||
* **bazel:** Set module_name and enable ng test ([#27715](https://github.com/angular/angular/issues/27715)) ([85866de](https://github.com/angular/angular/commit/85866de))
|
||||
@ -1164,7 +999,7 @@ This release contains various API docs improvements.
|
||||
* **core:** export a value for InjectFlags ([#27279](https://github.com/angular/angular/issues/27279)) ([23b06af](https://github.com/angular/angular/commit/23b06af)), closes [#27251](https://github.com/angular/angular/issues/27251)
|
||||
* **core:** More precise return type for `InjectableDecorator` ([#27360](https://github.com/angular/angular/issues/27360)) ([4b9948c](https://github.com/angular/angular/commit/4b9948c)), closes [#26942](https://github.com/angular/angular/issues/26942)
|
||||
* **forms:** typed argument for FormBuilder group ([#26985](https://github.com/angular/angular/issues/26985)) ([b0c7561](https://github.com/angular/angular/commit/b0c7561))
|
||||
* **platform-server:** add @angular/http to the list of peerDependencies ([#27307](https://github.com/angular/angular/issues/27307)) ([32c5be9](https://github.com/angular/angular/commit/32c5be9)), closes [#26154](https://github.com/angular/angular/issues/26154)
|
||||
* **platform-server:** add [@angular](https://github.com/angular)/http to the list of peerDependencies ([#27307](https://github.com/angular/angular/issues/27307)) ([32c5be9](https://github.com/angular/angular/commit/32c5be9)), closes [#26154](https://github.com/angular/angular/issues/26154)
|
||||
* **router:** ensure URL is updated after second redirect with UrlUpdateStrategy="eager" ([#27523](https://github.com/angular/angular/issues/27523)) ([ad26cd6](https://github.com/angular/angular/commit/ad26cd6)), closes [#27116](https://github.com/angular/angular/issues/27116)
|
||||
* **router:** update URL after redirects when urlHandlingStrategy='eager' ([#27356](https://github.com/angular/angular/issues/27356)) ([11a8bd8](https://github.com/angular/angular/commit/11a8bd8)), closes [#27076](https://github.com/angular/angular/issues/27076)
|
||||
* **upgrade:** allow nesting components from different downgraded modules ([#27217](https://github.com/angular/angular/issues/27217)) ([bc0ee01](https://github.com/angular/angular/commit/bc0ee01))
|
||||
@ -1184,7 +1019,7 @@ This release contains various API docs improvements.
|
||||
* **animations:** mark actual descendant node as disabled ([#26180](https://github.com/angular/angular/issues/26180)) ([453589f](https://github.com/angular/angular/commit/453589f))
|
||||
* **bazel:** devserver entry_module should have underscore name ([#27719](https://github.com/angular/angular/issues/27719)) ([b108e9a](https://github.com/angular/angular/commit/b108e9a))
|
||||
* **bazel:** emit full node stack traces when Angular compilation crashes ([#27678](https://github.com/angular/angular/issues/27678)) ([0d8528b](https://github.com/angular/angular/commit/0d8528b))
|
||||
* **bazel:** fix major/minor semver check between @angular/bazel npm packager version and angular bazel repo version ([#27635](https://github.com/angular/angular/issues/27635)) ([3ed1e84](https://github.com/angular/angular/commit/3ed1e84))
|
||||
* **bazel:** fix major/minor semver check between [@angular](https://github.com/angular)/bazel npm packager version and angular bazel repo version ([#27635](https://github.com/angular/angular/issues/27635)) ([3ed1e84](https://github.com/angular/angular/commit/3ed1e84))
|
||||
* **bazel:** Load http_archive and rules_nodejs dependencies ([#27609](https://github.com/angular/angular/issues/27609)) ([89ace1a](https://github.com/angular/angular/commit/89ace1a))
|
||||
* **bazel:** ng_package writes unrelevant definitions to bazel out ([#27519](https://github.com/angular/angular/issues/27519)) ([ef056c5](https://github.com/angular/angular/commit/ef056c5)), closes [/github.com/angular/angular/blob/4f9374951d67c75f67a31c110bd61ab72563db7d/packages/bazel/src/ng_package/packager.ts#L105-L124](https://github.com//github.com/angular/angular/blob/4f9374951d67c75f67a31c110bd61ab72563db7d/packages/bazel/src/ng_package/packager.ts/issues/L105-L124)
|
||||
* **bazel:** Read latest versions from latest-versions.ts & use semver check ([#27591](https://github.com/angular/angular/issues/27591)) ([93078e3](https://github.com/angular/angular/commit/93078e3))
|
||||
@ -1216,7 +1051,7 @@ This release contains various API docs improvements.
|
||||
* **bazel:** ng_package should correctly map to source maps in secondary entry-points ([#27313](https://github.com/angular/angular/issues/27313)) ([fc2c23e](https://github.com/angular/angular/commit/fc2c23e)), closes [#25510](https://github.com/angular/angular/issues/25510)
|
||||
* **compiler-cli:** flatModuleIndex files not generated on windows with multiple input files ([#27200](https://github.com/angular/angular/issues/27200)) ([8087b6b](https://github.com/angular/angular/commit/8087b6b))
|
||||
* **compiler-cli:** ngtsc shim files not being generated on case-insensitive platforms ([#27466](https://github.com/angular/angular/issues/27466)) ([84f2928](https://github.com/angular/angular/commit/84f2928)), closes [/github.com/Microsoft/TypeScript/blob/3e4c5c95abd515eb9713b881d27ab3a93cc00461/src/compiler/sys.ts#L681-L682](https://github.com//github.com/Microsoft/TypeScript/blob/3e4c5c95abd515eb9713b881d27ab3a93cc00461/src/compiler/sys.ts/issues/L681-L682)
|
||||
* **platform-server:** add @angular/http to the list of peerDependencies ([#27307](https://github.com/angular/angular/issues/27307)) ([236ac06](https://github.com/angular/angular/commit/236ac06)), closes [#26154](https://github.com/angular/angular/issues/26154)
|
||||
* **platform-server:** add [@angular](https://github.com/angular)/http to the list of peerDependencies ([#27307](https://github.com/angular/angular/issues/27307)) ([236ac06](https://github.com/angular/angular/commit/236ac06)), closes [#26154](https://github.com/angular/angular/issues/26154)
|
||||
|
||||
|
||||
|
||||
@ -1243,7 +1078,7 @@ This release contains various API docs improvements.
|
||||
* **compiler:** generate inputs with aliases properly ([#26774](https://github.com/angular/angular/issues/26774)) ([19fcfc3](https://github.com/angular/angular/commit/19fcfc3))
|
||||
* **compiler:** generate relative paths only in summary file errors ([#26759](https://github.com/angular/angular/issues/26759)) ([56f44be](https://github.com/angular/angular/commit/56f44be))
|
||||
* **core:** ignore comment nodes under unsafe elements ([#25879](https://github.com/angular/angular/issues/25879)) ([d5cbcef](https://github.com/angular/angular/commit/d5cbcef))
|
||||
* **core:** Remove static dependency from @angular/core to @angular/compiler ([#26734](https://github.com/angular/angular/issues/26734)) ([d042c4a](https://github.com/angular/angular/commit/d042c4a))
|
||||
* **core:** Remove static dependency from [@angular](https://github.com/angular)/core to [@angular](https://github.com/angular)/compiler ([#26734](https://github.com/angular/angular/issues/26734)) ([d042c4a](https://github.com/angular/angular/commit/d042c4a))
|
||||
* **core:** support computed base class in metadata inheritance ([#24014](https://github.com/angular/angular/issues/24014)) ([95743e3](https://github.com/angular/angular/commit/95743e3))
|
||||
* **bazel:** unknown replay compiler error in windows ([#26711](https://github.com/angular/angular/issues/26711)) ([aed95fd](https://github.com/angular/angular/commit/aed95fd))
|
||||
* **core:** ensure that `ɵdefineNgModule` is available in flat-file formats ([#26403](https://github.com/angular/angular/issues/26403)) ([a64859b](https://github.com/angular/angular/commit/a64859b))
|
||||
@ -1350,7 +1185,7 @@ This release contains various API docs improvements.
|
||||
* **compiler:** generate inputs with aliases properly ([#26774](https://github.com/angular/angular/issues/26774)) ([19fcfc3](https://github.com/angular/angular/commit/19fcfc3))
|
||||
* **compiler:** generate relative paths only in summary file errors ([#26759](https://github.com/angular/angular/issues/26759)) ([56f44be](https://github.com/angular/angular/commit/56f44be))
|
||||
* **core:** ignore comment nodes under unsafe elements ([#25879](https://github.com/angular/angular/issues/25879)) ([d5cbcef](https://github.com/angular/angular/commit/d5cbcef))
|
||||
* **core:** Remove static dependency from @angular/core to @angular/compiler ([#26734](https://github.com/angular/angular/issues/26734)) ([d042c4a](https://github.com/angular/angular/commit/d042c4a))
|
||||
* **core:** Remove static dependency from [@angular](https://github.com/angular)/core to [@angular](https://github.com/angular)/compiler ([#26734](https://github.com/angular/angular/issues/26734)) ([d042c4a](https://github.com/angular/angular/commit/d042c4a))
|
||||
* **core:** support computed base class in metadata inheritance ([#24014](https://github.com/angular/angular/issues/24014)) ([95743e3](https://github.com/angular/angular/commit/95743e3))
|
||||
|
||||
|
||||
@ -1362,7 +1197,7 @@ This release contains various API docs improvements.
|
||||
### Bug Fixes
|
||||
|
||||
* **compiler:** generate relative paths only in summary file errors ([#26759](https://github.com/angular/angular/issues/26759)) ([c01f340](https://github.com/angular/angular/commit/c01f340))
|
||||
* **core:** Remove static dependency from @angular/core to @angular/compiler ([#26734](https://github.com/angular/angular/issues/26734)) ([#26879](https://github.com/angular/angular/issues/26879)) ([257ac83](https://github.com/angular/angular/commit/257ac83))
|
||||
* **core:** Remove static dependency from [@angular](https://github.com/angular)/core to [@angular](https://github.com/angular)/compiler ([#26734](https://github.com/angular/angular/issues/26734)) ([#26879](https://github.com/angular/angular/issues/26879)) ([257ac83](https://github.com/angular/angular/commit/257ac83))
|
||||
* **core:** support computed base class in metadata inheritance ([#24014](https://github.com/angular/angular/issues/24014)) ([b3c6409](https://github.com/angular/angular/commit/b3c6409))
|
||||
|
||||
|
||||
@ -1439,7 +1274,7 @@ To learn about the release highlights and our new CLI-powered update workflow fo
|
||||
* **compiler:** update compiler to flatten nested template fns ([#24943](https://github.com/angular/angular/issues/24943)) ([fe14f18](https://github.com/angular/angular/commit/fe14f18))
|
||||
* **compiler:** update compiler to generate new slot allocations ([#25607](https://github.com/angular/angular/issues/25607)) ([27e2039](https://github.com/angular/angular/commit/27e2039))
|
||||
* **core:** In Testability.whenStable update callback, pass more complete ([#25010](https://github.com/angular/angular/issues/25010)) ([16c03c0](https://github.com/angular/angular/commit/16c03c0))
|
||||
* **core:** add missing `peerDependency ` to `@angular/compiler` ([#26033](https://github.com/angular/angular/issues/26033)) ([549de1e](https://github.com/angular/angular/commit/549de1e)), closes [/github.com/angular/angular/commit/919f42fea1df4b9e38b7d688aef5f2de668e9d3e#diff-58563046c4439699f2e6a89187099a54](https://github.com//github.com/angular/angular/commit/919f42fea1df4b9e38b7d688aef5f2de668e9d3e/issues/diff-58563046c4439699f2e6a89187099a54)
|
||||
* **core:** add missing `peerDependency ` to `[@angular](https://github.com/angular)/compiler` ([#26033](https://github.com/angular/angular/issues/26033)) ([549de1e](https://github.com/angular/angular/commit/549de1e)), closes [/github.com/angular/angular/commit/919f42fea1df4b9e38b7d688aef5f2de668e9d3e#diff-58563046c4439699f2e6a89187099a54](https://github.com//github.com/angular/angular/commit/919f42fea1df4b9e38b7d688aef5f2de668e9d3e/issues/diff-58563046c4439699f2e6a89187099a54)
|
||||
* **core:** allow null value for renderer setElement(…) ([#17065](https://github.com/angular/angular/issues/17065)) ([ff15043](https://github.com/angular/angular/commit/ff15043)), closes [#13686](https://github.com/angular/angular/issues/13686)
|
||||
* **core:** do not clear element content when using shadow dom ([#24861](https://github.com/angular/angular/issues/24861)) ([6e828bb](https://github.com/angular/angular/commit/6e828bb))
|
||||
* **core:** size regression with closure compiler ([#25531](https://github.com/angular/angular/issues/25531)) ([1f59f2f](https://github.com/angular/angular/commit/1f59f2f))
|
||||
@ -1885,7 +1720,7 @@ To learn about the release highlights and our new CLI-powered update workflow fo
|
||||
* **upgrade:** propagate return value of resumeBootstrap ([#22754](https://github.com/angular/angular/issues/22754)) ([a2330ff](https://github.com/angular/angular/commit/a2330ff)), closes [#22723](https://github.com/angular/angular/issues/22723)
|
||||
* **upgrade:** two-way binding and listening for event ([#22772](https://github.com/angular/angular/issues/22772)) ([2b3de63](https://github.com/angular/angular/commit/2b3de63)), closes [#22734](https://github.com/angular/angular/issues/22734)
|
||||
* **upgrade:** correctly destroy nested downgraded component ([#22400](https://github.com/angular/angular/issues/22400)) ([8a85888](https://github.com/angular/angular/commit/8a85888)), closes [#22392](https://github.com/angular/angular/issues/22392)
|
||||
* **upgrade:** correctly handle `=` bindings in `@angular/upgrade` ([#22167](https://github.com/angular/angular/issues/22167)) ([f089bf5](https://github.com/angular/angular/commit/f089bf5))
|
||||
* **upgrade:** correctly handle `=` bindings in `[@angular](https://github.com/angular)/upgrade` ([#22167](https://github.com/angular/angular/issues/22167)) ([f089bf5](https://github.com/angular/angular/commit/f089bf5))
|
||||
* **upgrade:** fix empty transclusion content with AngularJS@>=1.5.8 ([#22167](https://github.com/angular/angular/issues/22167)) ([13ab91e](https://github.com/angular/angular/commit/13ab91e)), closes [#22175](https://github.com/angular/angular/issues/22175)
|
||||
|
||||
|
||||
@ -2020,7 +1855,7 @@ To learn about the release highlights and our new CLI-powered update workflow fo
|
||||
* **router:** don't mutate route configs ([#22358](https://github.com/angular/angular/issues/22358)) ([8f0a064](https://github.com/angular/angular/commit/8f0a064)), closes [#22203](https://github.com/angular/angular/issues/22203)
|
||||
* **router:** fix URL serialization so special characters are only encoded where needed ([#22337](https://github.com/angular/angular/issues/22337)) ([789a47e](https://github.com/angular/angular/commit/789a47e)), closes [#10280](https://github.com/angular/angular/issues/10280)
|
||||
* **upgrade:** correctly destroy nested downgraded component ([#22400](https://github.com/angular/angular/issues/22400)) ([4aef9de](https://github.com/angular/angular/commit/4aef9de)), closes [#22392](https://github.com/angular/angular/issues/22392)
|
||||
* **upgrade:** correctly handle `=` bindings in `@angular/upgrade` ([#22167](https://github.com/angular/angular/issues/22167)) ([6638390](https://github.com/angular/angular/commit/6638390))
|
||||
* **upgrade:** correctly handle `=` bindings in `[@angular](https://github.com/angular)/upgrade` ([#22167](https://github.com/angular/angular/issues/22167)) ([6638390](https://github.com/angular/angular/commit/6638390))
|
||||
* **upgrade:** fix empty transclusion content with AngularJS@>=1.5.8 ([#22167](https://github.com/angular/angular/issues/22167)) ([a9a0e27](https://github.com/angular/angular/commit/a9a0e27)), closes [#22175](https://github.com/angular/angular/issues/22175)
|
||||
|
||||
|
||||
@ -2034,7 +1869,7 @@ To learn about the release highlights and our new CLI-powered update workflow fo
|
||||
* **platform-server:** generate correct stylings for camel case names ([#22263](https://github.com/angular/angular/issues/22263)) ([de02a7a](https://github.com/angular/angular/commit/de02a7a)), closes [#19235](https://github.com/angular/angular/issues/19235)
|
||||
* **router:** don't mutate route configs ([#22358](https://github.com/angular/angular/issues/22358)) ([8f0a064](https://github.com/angular/angular/commit/8f0a064)), closes [#22203](https://github.com/angular/angular/issues/22203)
|
||||
* **upgrade:** correctly destroy nested downgraded component ([#22400](https://github.com/angular/angular/issues/22400)) ([4aef9de](https://github.com/angular/angular/commit/4aef9de)), closes [#22392](https://github.com/angular/angular/issues/22392)
|
||||
* **upgrade:** correctly handle `=` bindings in `@angular/upgrade` ([#22167](https://github.com/angular/angular/issues/22167)) ([6638390](https://github.com/angular/angular/commit/6638390))
|
||||
* **upgrade:** correctly handle `=` bindings in `[@angular](https://github.com/angular)/upgrade` ([#22167](https://github.com/angular/angular/issues/22167)) ([6638390](https://github.com/angular/angular/commit/6638390))
|
||||
* **upgrade:** fix empty transclusion content with AngularJS@>=1.5.8 ([#22167](https://github.com/angular/angular/issues/22167)) ([a9a0e27](https://github.com/angular/angular/commit/a9a0e27)), closes [#22175](https://github.com/angular/angular/issues/22175)
|
||||
|
||||
|
||||
@ -2481,7 +2316,7 @@ Note: Due to an animation fix back in 5.1.1 ([c2b3792](https://github.com/angula
|
||||
* **platform-server:** provide a way to hook into renderModule* ([#19023](https://github.com/angular/angular/issues/19023)) ([8dfc3c3](https://github.com/angular/angular/commit/8dfc3c3))
|
||||
* **router:** add ActivationStart/End events ([8f79150](https://github.com/angular/angular/commit/8f79150))
|
||||
* **router:** add events tracking activation of individual routes ([49cd851](https://github.com/angular/angular/commit/49cd851))
|
||||
* **service-worker:** introduce the @angular/service-worker package ([#19274](https://github.com/angular/angular/issues/19274)) ([d442b68](https://github.com/angular/angular/commit/d442b68))
|
||||
* **service-worker:** introduce the [@angular](https://github.com/angular)/service-worker package ([#19274](https://github.com/angular/angular/issues/19274)) ([d442b68](https://github.com/angular/angular/commit/d442b68))
|
||||
* **upgrade:** propagate touched state of NgModelController ([59c23c7](https://github.com/angular/angular/commit/59c23c7))
|
||||
* **upgrade:** support lazy-loading Angular module into AngularJS app ([30e76fc](https://github.com/angular/angular/commit/30e76fc))
|
||||
* update angular to support TypeScript 2.4 ([ca5aeba](https://github.com/angular/angular/commit/ca5aeba))
|
||||
@ -2705,7 +2540,7 @@ Because of multiple bugs and browser inconsistencies, we have dropped the intl a
|
||||
### Bug Fixes
|
||||
|
||||
* **animations:** do not leak DOM nodes/styling for host triggered animations ([#18853](https://github.com/angular/angular/issues/18853)) ([1cc3fe2](https://github.com/angular/angular/commit/1cc3fe2)), closes [#18606](https://github.com/angular/angular/issues/18606)
|
||||
* **common:** fix improper packaging for @angular/common/http ([#18613](https://github.com/angular/angular/issues/18613)) ([a203a95](https://github.com/angular/angular/commit/a203a95))
|
||||
* **common:** fix improper packaging for [@angular](https://github.com/angular)/common/http ([#18613](https://github.com/angular/angular/issues/18613)) ([a203a95](https://github.com/angular/angular/commit/a203a95))
|
||||
* **common:** fix XSSI prefix stripping by using JSON.parse always ([#18466](https://github.com/angular/angular/issues/18466)) ([8821723](https://github.com/angular/angular/commit/8821723)), closes [#18396](https://github.com/angular/angular/issues/18396) [#18453](https://github.com/angular/angular/issues/18453)
|
||||
* **compiler:** normalize the locale name ([#18963](https://github.com/angular/angular/issues/18963)) ([497e017](https://github.com/angular/angular/commit/497e017))
|
||||
* **core:** complete EventEmitter in QueryList on component destroy ([#18902](https://github.com/angular/angular/issues/18902)) ([7d137d7](https://github.com/angular/angular/commit/7d137d7)), closes [#18741](https://github.com/angular/angular/issues/18741)
|
||||
@ -3046,7 +2881,7 @@ Note: the 4.4.0 release on npm accidentally glitched-out midway, so we cut 4.4.1
|
||||
* **compiler-cli:** allow '==' to compare nullable types ([#16731](https://github.com/angular/angular/issues/16731)) ([d761059](https://github.com/angular/angular/commit/d761059))
|
||||
* **core:** detach projected views when a parent view is destroyed ([#16592](https://github.com/angular/angular/issues/16592)) ([f0f6544](https://github.com/angular/angular/commit/f0f6544)), closes [#15578](https://github.com/angular/angular/issues/15578)
|
||||
* **core:** projected views should be dirty checked when the declaring component is dirty checked. ([#16592](https://github.com/angular/angular/issues/16592)) ([fcc91d8](https://github.com/angular/angular/commit/fcc91d8)), closes [#14321](https://github.com/angular/angular/issues/14321)
|
||||
* **http:** flatten metadata for @angular/http/testing ([9da6340](https://github.com/angular/angular/commit/9da6340)), closes [#15521](https://github.com/angular/angular/issues/15521)
|
||||
* **http:** flatten metadata for [@angular](https://github.com/angular)/http/testing ([9da6340](https://github.com/angular/angular/commit/9da6340)), closes [#15521](https://github.com/angular/angular/issues/15521)
|
||||
* **http:** honor RequestArgs.search and RequestArgs.params map type ([aef5245](https://github.com/angular/angular/commit/aef5245)), closes [#15761](https://github.com/angular/angular/issues/15761) [#16392](https://github.com/angular/angular/issues/16392)
|
||||
* **http:** introduce encodingHint for text() for better ArrayBuffer support ([7ae7a84](https://github.com/angular/angular/commit/7ae7a84)), closes [#15932](https://github.com/angular/angular/issues/15932) [#16420](https://github.com/angular/angular/issues/16420)
|
||||
* **router:** fix redirect to a URL with a param having multiple values ([#16376](https://github.com/angular/angular/issues/16376)) ([5d4b36f](https://github.com/angular/angular/commit/5d4b36f)), closes [#16310](https://github.com/angular/angular/issues/16310)
|
||||
@ -3102,7 +2937,7 @@ Note: the 4.4.0 release on npm accidentally glitched-out midway, so we cut 4.4.1
|
||||
* **compiler:** avoid a `...null` spread in extraction ([#16547](https://github.com/angular/angular/issues/16547)) ([d0e1688](https://github.com/angular/angular/commit/d0e1688))
|
||||
* **core:** detach projected views when a parent view is destroyed ([#16592](https://github.com/angular/angular/issues/16592)) ([ee6705a](https://github.com/angular/angular/commit/ee6705a)), closes [#15578](https://github.com/angular/angular/issues/15578)
|
||||
* **core:** projected views should be dirty checked when the declaring component is dirty checked. ([#16592](https://github.com/angular/angular/issues/16592)) ([9218812](https://github.com/angular/angular/commit/9218812)), closes [#14321](https://github.com/angular/angular/issues/14321)
|
||||
* **http:** flatten metadata for @angular/http/testing ([9c70a3c](https://github.com/angular/angular/commit/9c70a3c)), closes [#15521](https://github.com/angular/angular/issues/15521)
|
||||
* **http:** flatten metadata for [@angular](https://github.com/angular)/http/testing ([9c70a3c](https://github.com/angular/angular/commit/9c70a3c)), closes [#15521](https://github.com/angular/angular/issues/15521)
|
||||
* **http:** honor RequestArgs.search and RequestArgs.params map type ([63066f7](https://github.com/angular/angular/commit/63066f7)), closes [#15761](https://github.com/angular/angular/issues/15761) [#16392](https://github.com/angular/angular/issues/16392)
|
||||
* **http:** introduce encodingHint for text() for better ArrayBuffer support ([ec3b6e9](https://github.com/angular/angular/commit/ec3b6e9)), closes [#15932](https://github.com/angular/angular/issues/15932) [#16420](https://github.com/angular/angular/issues/16420)
|
||||
* **router:** fix redirect to a URL with a param having multiple values ([#16376](https://github.com/angular/angular/issues/16376)) ([915eae5](https://github.com/angular/angular/commit/915eae5)), closes [#16310](https://github.com/angular/angular/issues/16310)
|
||||
@ -3732,7 +3567,7 @@ Note: the 4.0.0-rc.0 release on npm accidentally omitted one bug fix, so we cut
|
||||
* **core:** add isStable Observable property to ApplicationRef to indicate when it's stable and unstable ([#14337](https://github.com/angular/angular/issues/14337)) ([c481798](https://github.com/angular/angular/commit/c481798))
|
||||
* **platform-server:** add API to render Module and ModuleFactory to string ([#14381](https://github.com/angular/angular/issues/14381)) ([b4d444a](https://github.com/angular/angular/commit/b4d444a))
|
||||
* **platform-server:** Implement PlatformLocation for platformServer() ([#14405](https://github.com/angular/angular/issues/14405)) ([9e28568](https://github.com/angular/angular/commit/9e28568))
|
||||
* **platform-server:** support @angular/http from @angular/platform-server ([9559d3e](https://github.com/angular/angular/commit/9559d3e))
|
||||
* **platform-server:** support [@angular](https://github.com/angular)/http from [@angular](https://github.com/angular)/platform-server ([9559d3e](https://github.com/angular/angular/commit/9559d3e))
|
||||
* **tsc-wrapped:** add an option to `ngc` to bundle metadata ([#14509](https://github.com/angular/angular/issues/14509)) ([3b89670](https://github.com/angular/angular/commit/3b89670))
|
||||
|
||||
|
||||
@ -4693,7 +4528,7 @@ Note: The 2.2.0-beta.0 release also contains all the changes present in the 2.1.
|
||||
* **compiler:** properly shim `:host:before` and `:host(:before)` ([#12171](https://github.com/angular/angular/issues/12171)) ([aa92512](https://github.com/angular/angular/commit/aa92512)), closes [#12165](https://github.com/angular/angular/issues/12165)
|
||||
* **compiler:** validate `@HostBinding` name ([#12139](https://github.com/angular/angular/issues/12139)) ([13ecc14](https://github.com/angular/angular/commit/13ecc14))
|
||||
* **compiler-cli:** don't clone static symbols when simplifying annotation metadata ([#12158](https://github.com/angular/angular/issues/12158)) ([8c477b2](https://github.com/angular/angular/commit/8c477b2))
|
||||
* **compiler-cli:** remove peerDependency on @angular/platform-server ([#12122](https://github.com/angular/angular/issues/12122)) ([71b7654](https://github.com/angular/angular/commit/71b7654))
|
||||
* **compiler-cli:** remove peerDependency on [@angular](https://github.com/angular)/platform-server ([#12122](https://github.com/angular/angular/issues/12122)) ([71b7654](https://github.com/angular/angular/commit/71b7654))
|
||||
* **compiler-cli:** remove unused parse5 dependency from package.json ([eaaec69](https://github.com/angular/angular/commit/eaaec69))
|
||||
* **forms:** allow optional fields with pattern and minlength validators ([#12147](https://github.com/angular/angular/issues/12147)) ([d22eeb7](https://github.com/angular/angular/commit/d22eeb7))
|
||||
* **forms:** properly validate blank strings with minlength ([#12091](https://github.com/angular/angular/issues/12091)) ([f50c1da](https://github.com/angular/angular/commit/f50c1da))
|
||||
|
@ -222,7 +222,6 @@ The following is the list of supported scopes:
|
||||
* **router**
|
||||
* **service-worker**
|
||||
* **upgrade**
|
||||
* **zone.js**
|
||||
|
||||
There are currently a few exceptions to the "use package name" rule:
|
||||
|
||||
|
27
WORKSPACE
27
WORKSPACE
@ -18,11 +18,8 @@ load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
|
||||
# Fetch rules_nodejs so we can install our npm dependencies
|
||||
http_archive(
|
||||
name = "build_bazel_rules_nodejs",
|
||||
patch_args = ["-p1"],
|
||||
# Patch https://github.com/bazelbuild/rules_nodejs/pull/903
|
||||
patches = ["//tools:rollup_bundle_commonjs_ignoreGlobal.patch"],
|
||||
sha256 = "6d4edbf28ff6720aedf5f97f9b9a7679401bf7fca9d14a0fff80f644a99992b4",
|
||||
urls = ["https://github.com/bazelbuild/rules_nodejs/releases/download/0.32.2/rules_nodejs-0.32.2.tar.gz"],
|
||||
sha256 = "e04a82a72146bfbca2d0575947daa60fda1878c8d3a3afe868a8ec39a6b968bb",
|
||||
urls = ["https://github.com/bazelbuild/rules_nodejs/releases/download/0.31.1/rules_nodejs-0.31.1.tar.gz"],
|
||||
)
|
||||
|
||||
# Check the bazel version and download npm dependencies
|
||||
@ -30,7 +27,6 @@ load("@build_bazel_rules_nodejs//:defs.bzl", "check_bazel_version", "check_rules
|
||||
|
||||
# Bazel version must be at least the following version because:
|
||||
# - 0.26.0 managed_directories feature added which is required for nodejs rules 0.30.0
|
||||
# - 0.27.0 has a fix for managed_directories after `rm -rf node_modules`
|
||||
check_bazel_version(
|
||||
message = """
|
||||
You no longer need to install Bazel on your machine.
|
||||
@ -39,7 +35,7 @@ Try running `yarn bazel` instead.
|
||||
(If you did run that, check that you've got a fresh `yarn install`)
|
||||
|
||||
""",
|
||||
minimum_bazel_version = "0.27.0",
|
||||
minimum_bazel_version = "0.26.0",
|
||||
)
|
||||
|
||||
# The NodeJS rules version must be at least the following version because:
|
||||
@ -50,10 +46,7 @@ Try running `yarn bazel` instead.
|
||||
# - 0.27.12 Adds NodeModuleSources provider for transtive npm deps support
|
||||
# - 0.30.0 yarn_install now uses symlinked node_modules with new managed directories Bazel 0.26.0 feature
|
||||
# - 0.31.1 entry_point attribute of nodejs_binary & rollup_bundle is now a label
|
||||
# - 0.32.0 yarn_install and npm_install no longer puts build files under symlinked node_modules
|
||||
# - 0.32.1 remove override of @bazel/tsetse & exclude typescript lib declarations in node_module_library transitive_declarations
|
||||
# - 0.32.2 resolves bug in @bazel/hide-bazel-files postinstall step
|
||||
check_rules_nodejs_version(minimum_version_string = "0.32.2")
|
||||
check_rules_nodejs_version(minimum_version_string = "0.31.1")
|
||||
|
||||
# Setup the Node.js toolchain
|
||||
node_repositories(
|
||||
@ -77,7 +70,19 @@ node_repositories(
|
||||
|
||||
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,
|
||||
# Temporarily disable node_modules symlinking until the fix for
|
||||
# https://github.com/bazelbuild/bazel/issues/8487 makes it into a
|
||||
# future Bazel release
|
||||
symlink_node_modules = False,
|
||||
yarn_lock = "//:yarn.lock",
|
||||
)
|
||||
|
||||
|
@ -36,14 +36,6 @@ ng serve
|
||||
In your browser, open http://localhost:4200/ to see the new app run.
|
||||
When you use the [ng serve](cli/serve) command to build an app and serve it locally, the server automatically rebuilds the app and reloads the page when you change any of the source files.
|
||||
|
||||
<div class="alert is-helpful">
|
||||
|
||||
When you run `ng new my-first-project` a new folder, named `my-first-project`, will be created in the current working directory. Since you want to be able to create files inside that folder, make sure you have sufficient rights in the current working directory before running the command.
|
||||
|
||||
If the current working directory is not the right place for your project, you can change to a more appropriate directory by running `cd <path-to-other-directory>` first.
|
||||
|
||||
</div>
|
||||
|
||||
## Workspaces and project files
|
||||
|
||||
The [ng new](cli/new) command creates an *Angular workspace* folder and generates a new app skeleton.
|
||||
@ -82,7 +74,7 @@ Command syntax is shown as follows:
|
||||
* Option names are prefixed with a double dash (--).
|
||||
Option aliases are prefixed with a single dash (-).
|
||||
Arguments are not prefixed.
|
||||
For example:
|
||||
For example:
|
||||
<code-example format="." language="bash">
|
||||
ng build my-app -c production
|
||||
</code-example>
|
||||
@ -113,5 +105,5 @@ Schematic options are supplied to the command in the same format as immediate co
|
||||
|
||||
### Building with Bazel
|
||||
|
||||
Optionally, you can configure the Angular CLI to use [Bazel](https://docs.bazel.build) as the build tool. For more information, see [Building with Bazel](guide/bazel).
|
||||
Optionally, you can configure the Angular CLI to use [Bazel](https://docs.bazel.build) as the build tool. For more information, see [Building with Bazel](guide/bazel).
|
||||
|
||||
|
@ -25,8 +25,8 @@ describe('Attribute directives', () => {
|
||||
greenRb.click();
|
||||
browser.actions().mouseMove(highlightedEle).perform();
|
||||
|
||||
// Wait for up to 4s for the background color to be updated,
|
||||
// Wait for up to 2s for the background color to be updated,
|
||||
// to account for slow environments (e.g. CI).
|
||||
browser.wait(() => highlightedEle.getCssValue('background-color').then(c => c === lightGreen), 4000);
|
||||
browser.wait(() => highlightedEle.getCssValue('background-color').then(c => c === lightGreen), 2000);
|
||||
});
|
||||
});
|
||||
|
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 32 KiB |
@ -18,7 +18,7 @@ export class AppComponent {
|
||||
}
|
||||
|
||||
deleteItem(item: Item) {
|
||||
alert(`Delete the ${item.name}.`);
|
||||
alert(`Delete the ${item}.`);
|
||||
}
|
||||
|
||||
onClickMe(event?: KeyboardEvent) {
|
||||
|
@ -16,20 +16,16 @@
|
||||
<!-- #enddocregion checkout-form-1 -->
|
||||
|
||||
<div>
|
||||
<label for="name">
|
||||
Name
|
||||
</label>
|
||||
<input id="name" type="text" formControlName="name">
|
||||
<label>Name</label>
|
||||
<input type="text" formControlName="name">
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="address">
|
||||
Address
|
||||
</label>
|
||||
<input id="address" type="text" formControlName="address">
|
||||
<label>Address</label>
|
||||
<input type="text" formControlName="address">
|
||||
</div>
|
||||
|
||||
<button class="button" type="submit">Purchase</button>
|
||||
|
||||
|
||||
<!-- #docregion checkout-form-1 -->
|
||||
</form>
|
||||
|
@ -10,6 +10,7 @@ export class AppComponent {
|
||||
gender = 'female';
|
||||
fly = true;
|
||||
logo = 'https://angular.io/assets/images/logos/angular/angular.png';
|
||||
heroes: string[] = ['Magneta', 'Celeritas', 'Dynama'];
|
||||
inc(i: number) {
|
||||
this.minutes = Math.min(5, Math.max(0, this.minutes + i));
|
||||
}
|
||||
|
@ -1,14 +0,0 @@
|
||||
// imports
|
||||
import { BrowserModule } from '@angular/platform-browser';
|
||||
import { NgModule } from '@angular/core';
|
||||
|
||||
import { AppComponent } from './app.component';
|
||||
|
||||
// @NgModule decorator with its metadata
|
||||
@NgModule({
|
||||
declarations: [AppComponent],
|
||||
imports: [BrowserModule],
|
||||
providers: [],
|
||||
bootstrap: [AppComponent]
|
||||
})
|
||||
export class AppModule {}
|
@ -7,9 +7,9 @@ import { from } from 'rxjs';
|
||||
const data = from(fetch('/api/endpoint'));
|
||||
// Subscribe to begin listening for async result
|
||||
data.subscribe({
|
||||
next(response) { console.log(response); },
|
||||
error(err) { console.error('Error: ' + err); },
|
||||
complete() { console.log('Completed'); }
|
||||
next(response) { console.log(response); },
|
||||
error(err) { console.error('Error: ' + err); },
|
||||
complete() { console.log('Completed'); }
|
||||
});
|
||||
|
||||
// #enddocregion promise
|
||||
|
@ -2,49 +2,42 @@
|
||||
|
||||
import { browser, element, by } from 'protractor';
|
||||
|
||||
// TODO Not yet complete
|
||||
describe('Template Syntax', () => {
|
||||
// Not yet complete
|
||||
describe('Template Syntax', function () {
|
||||
|
||||
beforeAll(() => {
|
||||
beforeAll(function () {
|
||||
browser.get('');
|
||||
});
|
||||
|
||||
it('should be able to use interpolation with a hero', () => {
|
||||
const heroInterEle = element.all(by.css('h2+p')).get(0);
|
||||
it('should be able to use interpolation with a hero', function () {
|
||||
let heroInterEle = element.all(by.css('h2+p')).get(0);
|
||||
expect(heroInterEle.getText()).toEqual('My current hero is Hercules');
|
||||
});
|
||||
|
||||
it('should be able to use interpolation with a calculation', () => {
|
||||
const theSumEles = element.all(by.cssContainingText('h3~p', 'The sum of'));
|
||||
it('should be able to use interpolation with a calculation', function () {
|
||||
let theSumEles = element.all(by.cssContainingText('h3~p', 'The sum of'));
|
||||
expect(theSumEles.count()).toBe(2);
|
||||
expect(theSumEles.get(0).getText()).toEqual('The sum of 1 + 1 is 2');
|
||||
expect(theSumEles.get(1).getText()).toEqual('The sum of 1 + 1 is not 4');
|
||||
});
|
||||
|
||||
it('should be able to use class binding syntax', () => {
|
||||
const specialEle = element(by.cssContainingText('div', 'Special'));
|
||||
it('should be able to use class binding syntax', function () {
|
||||
let specialEle = element(by.cssContainingText('div', 'Special'));
|
||||
expect(specialEle.getAttribute('class')).toMatch('special');
|
||||
});
|
||||
|
||||
it('should be able to use style binding syntax', () => {
|
||||
const specialButtonEle = element(by.cssContainingText('div.special~button', 'button'));
|
||||
it('should be able to use style binding syntax', function () {
|
||||
let specialButtonEle = element(by.cssContainingText('div.special~button', 'button'));
|
||||
expect(specialButtonEle.getAttribute('style')).toMatch('color: red');
|
||||
});
|
||||
|
||||
it('should two-way bind to sizer', async () => {
|
||||
const div = element(by.css('div#two-way-1'));
|
||||
const incButton = div.element(by.buttonText('+'));
|
||||
const input = div.element(by.css('input'));
|
||||
const initSize = await input.getAttribute('value');
|
||||
let div = element(by.css('div#two-way-1'));
|
||||
let incButton = div.element(by.buttonText('+'));
|
||||
let input = div.element(by.css('input'));
|
||||
let initSize = await input.getAttribute('value');
|
||||
incButton.click();
|
||||
expect(input.getAttribute('value')).toEqual((+initSize + 1).toString());
|
||||
});
|
||||
|
||||
it('should change SVG rectangle\'s fill color on click', async () => {
|
||||
const div = element(by.css('app-svg'));
|
||||
const colorSquare = div.element(by.css('rect'));
|
||||
const initialColor = await colorSquare.getAttribute('fill');
|
||||
colorSquare.click();
|
||||
expect(colorSquare.getAttribute('fill')).not.toEqual(initialColor);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -38,7 +38,6 @@
|
||||
<a href="#safe-navigation-operator">Safe navigation operator <i>?.</i></a><br>
|
||||
<a href="#non-null-assertion-operator">Non-null assertion operator <i>!.</i></a><br>
|
||||
<a href="#enums">Enums</a><br>
|
||||
<a href="#svg-templates">SVG Templates</a><br>
|
||||
|
||||
<!-- Interpolation and expressions -->
|
||||
<hr><h2 id="interpolation">Interpolation</h2>
|
||||
@ -443,7 +442,7 @@ button</button>
|
||||
|
||||
<!-- #docregion without-NgModel -->
|
||||
<input [value]="currentHero.name"
|
||||
(input)="updateCurrentHeroName($event)">
|
||||
(input)="currentHero.name=$event.target.value" >
|
||||
<!-- #enddocregion without-NgModel -->
|
||||
without NgModel
|
||||
<br>
|
||||
@ -753,7 +752,7 @@ bindon-ngModel
|
||||
|
||||
<div>
|
||||
<!-- pipe price to USD and display the $ symbol -->
|
||||
<label>Price: </label>{{product.price | currency:'USD':'symbol'}}
|
||||
<label>Price: </label>{{product.price | currency:'USD':true}}
|
||||
</div>
|
||||
|
||||
<a class="to-toc" href="#toc">top</a>
|
||||
@ -858,9 +857,3 @@ The null hero's name is {{nullHero && nullHero.name}}
|
||||
</p>
|
||||
|
||||
<a class="to-toc" href="#toc">top</a>
|
||||
|
||||
<hr><h2 id="svg-templates">SVG Templates</h2>
|
||||
<!-- #docregion svg-templates -->
|
||||
<app-svg></app-svg>
|
||||
<!-- #enddocregion svg-templates -->
|
||||
<a class="to-toc" href="#toc">top</a>
|
||||
|
@ -5,7 +5,7 @@ import { AfterViewInit, Component, ElementRef, OnInit, QueryList, ViewChildren }
|
||||
|
||||
import { Hero } from './hero';
|
||||
|
||||
export enum Color {Red, Green, Blue}
|
||||
export enum Color {Red, Green, Blue};
|
||||
|
||||
/**
|
||||
* Giant grab bag of stuff to drive the chapter
|
||||
@ -29,7 +29,7 @@ export class AppComponent implements AfterViewInit, OnInit {
|
||||
trackChanges(this.heroesWithTrackBy, () => this.heroesWithTrackByCount++);
|
||||
}
|
||||
|
||||
@ViewChildren('noTrackBy') heroesNoTrackBy: QueryList<ElementRef>;
|
||||
@ViewChildren('noTrackBy') heroesNoTrackBy: QueryList<ElementRef>;
|
||||
@ViewChildren('withTrackBy') heroesWithTrackBy: QueryList<ElementRef>;
|
||||
|
||||
actionName = 'Go for it';
|
||||
@ -66,10 +66,6 @@ export class AppComponent implements AfterViewInit, OnInit {
|
||||
|
||||
currentHero: Hero;
|
||||
|
||||
updateCurrentHeroName(event: Event) {
|
||||
this.currentHero.name = (event.target as any).value;
|
||||
}
|
||||
|
||||
deleteHero(hero?: Hero) {
|
||||
this.alert(`Delete ${hero ? hero.name : 'the hero'}.`);
|
||||
}
|
||||
@ -109,13 +105,13 @@ export class AppComponent implements AfterViewInit, OnInit {
|
||||
|
||||
get nullHero(): Hero { return null; }
|
||||
|
||||
onClickMe(event?: MouseEvent) {
|
||||
const evtMsg = event ? ' Event target class is ' + (event.target as HTMLElement).className : '';
|
||||
onClickMe(event?: KeyboardEvent) {
|
||||
let evtMsg = event ? ' Event target class is ' + (<HTMLElement>event.target).className : '';
|
||||
this.alert('Click me.' + evtMsg);
|
||||
}
|
||||
|
||||
onSave(event?: MouseEvent) {
|
||||
const evtMsg = event ? ' Event target is ' + (event.target as HTMLElement).textContent : '';
|
||||
onSave(event?: KeyboardEvent) {
|
||||
let evtMsg = event ? ' Event target is ' + (<HTMLElement>event.target).textContent : '';
|
||||
this.alert('Saved.' + evtMsg);
|
||||
if (event) { event.stopPropagation(); }
|
||||
}
|
||||
@ -144,9 +140,9 @@ export class AppComponent implements AfterViewInit, OnInit {
|
||||
setCurrentClasses() {
|
||||
// CSS classes: added/removed per current state of component properties
|
||||
this.currentClasses = {
|
||||
saveable: this.canSave,
|
||||
modified: !this.isUnchanged,
|
||||
special: this.isSpecial
|
||||
'saveable': this.canSave,
|
||||
'modified': !this.isUnchanged,
|
||||
'special': this.isSpecial
|
||||
};
|
||||
}
|
||||
// #enddocregion setClasses
|
||||
@ -168,7 +164,7 @@ export class AppComponent implements AfterViewInit, OnInit {
|
||||
// #enddocregion trackByHeroes
|
||||
|
||||
// #docregion trackById
|
||||
trackById(index: number, item: any): number { return item.id; }
|
||||
trackById(index: number, item: any): number { return item['id']; }
|
||||
// #enddocregion trackById
|
||||
}
|
||||
|
||||
|
@ -1,14 +1,13 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { BrowserModule } from '@angular/platform-browser';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { NgModule } from '@angular/core';
|
||||
import { BrowserModule } from '@angular/platform-browser';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
|
||||
import { AppComponent } from './app.component';
|
||||
import { BigHeroDetailComponent, HeroDetailComponent } from './hero-detail.component';
|
||||
import { ClickDirective, ClickDirective2 } from './click.directive';
|
||||
import { HeroFormComponent } from './hero-form.component';
|
||||
import { heroSwitchComponents } from './hero-switch.components';
|
||||
import { SizerComponent } from './sizer.component';
|
||||
import { SvgComponent } from './svg.component';
|
||||
import { HeroFormComponent } from './hero-form.component';
|
||||
import { heroSwitchComponents } from './hero-switch.components';
|
||||
import { SizerComponent } from './sizer.component';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
@ -23,8 +22,7 @@ import { SvgComponent } from './svg.component';
|
||||
heroSwitchComponents,
|
||||
ClickDirective,
|
||||
ClickDirective2,
|
||||
SizerComponent,
|
||||
SvgComponent
|
||||
SizerComponent
|
||||
],
|
||||
bootstrap: [ AppComponent ]
|
||||
})
|
||||
|
@ -1,4 +1,4 @@
|
||||
/* tslint:disable directive-selector directive-class-suffix */
|
||||
/* tslint:disable use-output-property-decorator directive-class-suffix */
|
||||
// #docplaster
|
||||
import { Directive, ElementRef, EventEmitter, Output } from '@angular/core';
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { Component, Input, ViewChild } from '@angular/core';
|
||||
import { NgForm } from '@angular/forms';
|
||||
import { NgForm } from '@angular/forms';
|
||||
|
||||
import { Hero } from './hero';
|
||||
|
||||
@ -15,11 +15,10 @@ export class HeroFormComponent {
|
||||
@Input() hero: Hero;
|
||||
@ViewChild('heroForm', {static: false}) form: NgForm;
|
||||
|
||||
// tslint:disable-next-line:variable-name
|
||||
private _submitMessage = '';
|
||||
|
||||
get submitMessage() {
|
||||
if (this.form && !this.form.valid) {
|
||||
if (!this.form.valid) {
|
||||
this._submitMessage = '';
|
||||
}
|
||||
return this._submitMessage;
|
||||
|
@ -1,4 +0,0 @@
|
||||
svg {
|
||||
display: block;
|
||||
width: 100%;
|
||||
}
|
@ -1,6 +0,0 @@
|
||||
<svg>
|
||||
<g>
|
||||
<rect x="0" y="0" width="100" height="100" [attr.fill]="fillColor" (click)="changeColor()" />
|
||||
<text x="120" y="50">click the rectangle to change the fill color</text>
|
||||
</g>
|
||||
</svg>
|
Before Width: | Height: | Size: 201 B |
@ -1,17 +0,0 @@
|
||||
import { Component } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'app-svg',
|
||||
templateUrl: './svg.component.svg',
|
||||
styleUrls: ['./svg.component.css']
|
||||
})
|
||||
export class SvgComponent {
|
||||
fillColor = 'rgb(255, 0, 0)';
|
||||
|
||||
changeColor() {
|
||||
const r = Math.floor(Math.random() * 256);
|
||||
const g = Math.floor(Math.random() * 256);
|
||||
const b = Math.floor(Math.random() * 256);
|
||||
this.fillColor = `rgb(${r}, ${g}, ${b})`;
|
||||
}
|
||||
}
|
@ -1,4 +1,8 @@
|
||||
/* HeroesComponent's private CSS styles */
|
||||
.selected {
|
||||
background-color: #CFD8DC !important;
|
||||
color: white;
|
||||
}
|
||||
.heroes {
|
||||
margin: 0 0 2em 0;
|
||||
list-style-type: none;
|
||||
@ -15,18 +19,18 @@
|
||||
height: 1.6em;
|
||||
border-radius: 4px;
|
||||
}
|
||||
.heroes li.selected:hover {
|
||||
background-color: #BBD8DC !important;
|
||||
color: white;
|
||||
}
|
||||
.heroes li:hover {
|
||||
color: #607D8B;
|
||||
background-color: #DDD;
|
||||
left: .1em;
|
||||
}
|
||||
.heroes li.selected {
|
||||
background-color: #CFD8DC;
|
||||
color: white;
|
||||
}
|
||||
.heroes li.selected:hover {
|
||||
background-color: #BBD8DC;
|
||||
color: white;
|
||||
.heroes .text {
|
||||
position: relative;
|
||||
top: -3px;
|
||||
}
|
||||
.heroes .badge {
|
||||
display: inline-block;
|
||||
|
@ -34,7 +34,4 @@ export class HeroesComponent implements OnInit {
|
||||
this.selectedHero = hero;
|
||||
}
|
||||
// #enddocregion on-select
|
||||
// #docregion component
|
||||
}
|
||||
// #enddocregion component
|
||||
|
||||
|
@ -1,4 +1,8 @@
|
||||
/* HeroesComponent's private CSS styles */
|
||||
.selected {
|
||||
background-color: #CFD8DC !important;
|
||||
color: white;
|
||||
}
|
||||
.heroes {
|
||||
margin: 0 0 2em 0;
|
||||
list-style-type: none;
|
||||
@ -15,18 +19,18 @@
|
||||
height: 1.6em;
|
||||
border-radius: 4px;
|
||||
}
|
||||
.heroes li.selected:hover {
|
||||
background-color: #BBD8DC !important;
|
||||
color: white;
|
||||
}
|
||||
.heroes li:hover {
|
||||
color: #607D8B;
|
||||
background-color: #DDD;
|
||||
left: .1em;
|
||||
}
|
||||
.heroes li.selected {
|
||||
background-color: #CFD8DC;
|
||||
color: white;
|
||||
}
|
||||
.heroes li.selected:hover {
|
||||
background-color: #BBD8DC;
|
||||
color: white;
|
||||
.heroes .text {
|
||||
position: relative;
|
||||
top: -3px;
|
||||
}
|
||||
.heroes .badge {
|
||||
display: inline-block;
|
||||
|
@ -1,4 +1,8 @@
|
||||
/* HeroesComponent's private CSS styles */
|
||||
.selected {
|
||||
background-color: #CFD8DC !important;
|
||||
color: white;
|
||||
}
|
||||
.heroes {
|
||||
margin: 0 0 2em 0;
|
||||
list-style-type: none;
|
||||
@ -15,18 +19,18 @@
|
||||
height: 1.6em;
|
||||
border-radius: 4px;
|
||||
}
|
||||
.heroes li.selected:hover {
|
||||
background-color: #BBD8DC !important;
|
||||
color: white;
|
||||
}
|
||||
.heroes li:hover {
|
||||
color: #607D8B;
|
||||
background-color: #DDD;
|
||||
left: .1em;
|
||||
}
|
||||
.heroes li.selected {
|
||||
background-color: #CFD8DC;
|
||||
color: white;
|
||||
}
|
||||
.heroes li.selected:hover {
|
||||
background-color: #BBD8DC;
|
||||
color: white;
|
||||
.heroes .text {
|
||||
position: relative;
|
||||
top: -3px;
|
||||
}
|
||||
.heroes .badge {
|
||||
display: inline-block;
|
||||
@ -39,6 +43,8 @@
|
||||
left: -1px;
|
||||
top: -4px;
|
||||
height: 1.8em;
|
||||
min-width: 16px;
|
||||
text-align: right;
|
||||
margin-right: .8em;
|
||||
border-radius: 4px 0 0 4px;
|
||||
}
|
||||
|
@ -1,13 +0,0 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { RouterModule, Routes } from '@angular/router';
|
||||
import { HeroesComponent } from './heroes/heroes.component';
|
||||
|
||||
const routes: Routes = [
|
||||
{ path: 'heroes', component: HeroesComponent }
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
imports: [RouterModule.forRoot(routes)],
|
||||
exports: [RouterModule]
|
||||
})
|
||||
export class AppRoutingModule { }
|
@ -7,7 +7,9 @@ import { RouterModule, Routes } from '@angular/router';
|
||||
// #docregion import-dashboard
|
||||
import { DashboardComponent } from './dashboard/dashboard.component';
|
||||
// #enddocregion import-dashboard
|
||||
// #docregion heroes-route
|
||||
import { HeroesComponent } from './heroes/heroes.component';
|
||||
// #enddocregion heroes-route
|
||||
// #docregion import-herodetail
|
||||
import { HeroDetailComponent } from './hero-detail/hero-detail.component';
|
||||
// #enddocregion import-herodetail
|
||||
@ -37,9 +39,7 @@ const routes: Routes = [
|
||||
imports: [ RouterModule.forRoot(routes) ],
|
||||
// #enddocregion ngmodule-imports
|
||||
// #docregion v1
|
||||
// #docregion export-routermodule
|
||||
exports: [ RouterModule ]
|
||||
// #enddocregion export-routermodule
|
||||
})
|
||||
export class AppRoutingModule {}
|
||||
// #enddocregion , v1
|
||||
|
@ -23,17 +23,13 @@ import { HeroSearchComponent } from './hero-search/hero-search.component';
|
||||
// #docregion v1
|
||||
import { MessagesComponent } from './messages/messages.component';
|
||||
|
||||
// #docregion import-httpclientmodule
|
||||
@NgModule({
|
||||
imports: [
|
||||
// #enddocregion import-httpclientmodule
|
||||
BrowserModule,
|
||||
FormsModule,
|
||||
AppRoutingModule,
|
||||
// #docregion in-mem-web-api-imports
|
||||
// #docregion import-httpclientmodule
|
||||
HttpClientModule,
|
||||
// #enddocregion import-httpclientmodule
|
||||
|
||||
// The HttpClientInMemoryWebApiModule module intercepts HTTP requests
|
||||
// and returns simulated server responses.
|
||||
@ -42,9 +38,7 @@ import { MessagesComponent } from './messages/messages.component';
|
||||
InMemoryDataService, { dataEncapsulation: false }
|
||||
)
|
||||
// #enddocregion in-mem-web-api-imports
|
||||
// #docregion import-httpclientmodule
|
||||
],
|
||||
// #enddocregion import-httpclientmodule
|
||||
declarations: [
|
||||
AppComponent,
|
||||
DashboardComponent,
|
||||
@ -56,9 +50,6 @@ import { MessagesComponent } from './messages/messages.component';
|
||||
// #docregion v1
|
||||
],
|
||||
bootstrap: [ AppComponent ]
|
||||
// #docregion import-httpclientmodule
|
||||
})
|
||||
// #enddocregion import-httpclientmodule
|
||||
|
||||
export class AppModule { }
|
||||
// #enddocregion , v1
|
||||
|
@ -13,6 +13,11 @@ import { catchError, map, tap } from 'rxjs/operators';
|
||||
import { Hero } from './hero';
|
||||
import { MessageService } from './message.service';
|
||||
|
||||
// #docregion http-options
|
||||
const httpOptions = {
|
||||
headers: new HttpHeaders({ 'Content-Type': 'application/json' })
|
||||
};
|
||||
// #enddocregion http-options
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class HeroService {
|
||||
@ -21,12 +26,6 @@ export class HeroService {
|
||||
private heroesUrl = 'api/heroes'; // URL to web api
|
||||
// #enddocregion heroesUrl
|
||||
|
||||
// #docregion http-options
|
||||
httpOptions = {
|
||||
headers: new HttpHeaders({ 'Content-Type': 'application/json' })
|
||||
};
|
||||
// #enddocregion http-options
|
||||
|
||||
// #docregion ctor
|
||||
constructor(
|
||||
private http: HttpClient,
|
||||
@ -97,7 +96,7 @@ export class HeroService {
|
||||
// #docregion addHero
|
||||
/** POST: add a new hero to the server */
|
||||
addHero (hero: Hero): Observable<Hero> {
|
||||
return this.http.post<Hero>(this.heroesUrl, hero, this.httpOptions).pipe(
|
||||
return this.http.post<Hero>(this.heroesUrl, hero, httpOptions).pipe(
|
||||
tap((newHero: Hero) => this.log(`added hero w/ id=${newHero.id}`)),
|
||||
catchError(this.handleError<Hero>('addHero'))
|
||||
);
|
||||
@ -110,7 +109,7 @@ export class HeroService {
|
||||
const id = typeof hero === 'number' ? hero : hero.id;
|
||||
const url = `${this.heroesUrl}/${id}`;
|
||||
|
||||
return this.http.delete<Hero>(url, this.httpOptions).pipe(
|
||||
return this.http.delete<Hero>(url, httpOptions).pipe(
|
||||
tap(_ => this.log(`deleted hero id=${id}`)),
|
||||
catchError(this.handleError<Hero>('deleteHero'))
|
||||
);
|
||||
@ -120,7 +119,7 @@ export class HeroService {
|
||||
// #docregion updateHero
|
||||
/** PUT: update the hero on the server */
|
||||
updateHero (hero: Hero): Observable<any> {
|
||||
return this.http.put(this.heroesUrl, hero, this.httpOptions).pipe(
|
||||
return this.http.put(this.heroesUrl, hero, httpOptions).pipe(
|
||||
tap(_ => this.log(`updated hero id=${hero.id}`)),
|
||||
catchError(this.handleError<any>('updateHero'))
|
||||
);
|
||||
|
@ -1,79 +0,0 @@
|
||||
import { browser, element, by, ExpectedConditions } from 'protractor';
|
||||
|
||||
describe('Lazy Loading AngularJS Tests', function () {
|
||||
const pageElements = {
|
||||
homePageHref: element(by.cssContainingText('app-root nav a', 'Home')),
|
||||
homePageParagraph: element(by.css('app-root app-home p')),
|
||||
ajsUsersPageHref: element(by.cssContainingText('app-root nav a', 'Users')),
|
||||
ajsUsersPageParagraph: element(by.css('app-root app-angular-js div p')),
|
||||
notFoundPageHref: element(by.cssContainingText('app-root nav a', '404 Page')),
|
||||
notFoundPageParagraph: element(by.css('app-root app-app404 p')),
|
||||
};
|
||||
|
||||
beforeAll(async() => {
|
||||
await browser.get('/');
|
||||
});
|
||||
|
||||
it('should display \'Angular Home\' when visiting the home page', async() => {
|
||||
await pageElements.homePageHref.click();
|
||||
|
||||
const paragraphText = await pageElements.homePageParagraph.getText();
|
||||
|
||||
expect(paragraphText).toEqual('Angular Home');
|
||||
});
|
||||
|
||||
it('should display \'Users Page\' page when visiting the AngularJS page at /users', async() => {
|
||||
await pageElements.ajsUsersPageHref.click();
|
||||
await loadAngularJS();
|
||||
|
||||
const paragraphText = await pageElements.ajsUsersPageParagraph.getText();
|
||||
|
||||
expect(paragraphText).toEqual('Users Page');
|
||||
});
|
||||
|
||||
it('should display \'Angular 404\' when visiting an invalid URL', async() => {
|
||||
await pageElements.notFoundPageHref.click();
|
||||
|
||||
const paragraphText = await pageElements.notFoundPageParagraph.getText();
|
||||
|
||||
expect(paragraphText).toEqual('Angular 404');
|
||||
});
|
||||
|
||||
// Workaround for https://github.com/angular/protractor/issues/4724
|
||||
async function loadAngularJS() {
|
||||
// Abort if `resumeBootstrap` has already occured
|
||||
if (await browser.executeScript(`return '__TESTABILITY__NG1_APP_ROOT_INJECTOR__' in window;`)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Might have to re-insert the 'NG_DEFER_BOOTSTRAP!' if the name has been changed since protractor loaded the page
|
||||
if (!await browser.executeScript('window.name.includes(\'NG_DEFER_BOOTSTRAP!\')')) {
|
||||
await browser.executeScript('window.name = \'NG_DEFER_BOOTSTRAP!\' + name');
|
||||
}
|
||||
|
||||
// Wait for the AngularJS bundle to download and initialize
|
||||
await browser.wait(ExpectedConditions.presenceOf(element(by.css('app-root app-angular-js'))), 5000, 'AngularJS app');
|
||||
|
||||
// Run the protractor pre-bootstrap logic and resumeBootstrap
|
||||
// Based on https://github.com/angular/protractor/blob/5.3.0/lib/browser.ts#L950-L969
|
||||
{
|
||||
let moduleNames = [];
|
||||
for (const {name, script, args} of browser.mockModules_) {
|
||||
moduleNames.push(name);
|
||||
await browser.executeScriptWithDescription(script, 'add mock module ' + name, ...args);
|
||||
}
|
||||
|
||||
await browser.executeScriptWithDescription(
|
||||
// TODO: must manually assign __TESTABILITY__NG1_APP_ROOT_INJECTOR__ (https://github.com/angular/angular/issues/22723)
|
||||
`window.__TESTABILITY__NG1_APP_ROOT_INJECTOR__ = angular.resumeBootstrap(arguments[0]) `
|
||||
+ `|| angular.element('app-angular-js').injector();`,
|
||||
'resume bootstrap',
|
||||
moduleNames
|
||||
);
|
||||
}
|
||||
|
||||
// Wait for the initial AngularJS page to finish loading
|
||||
await browser.waitForAngular();
|
||||
}
|
||||
});
|
||||
|
@ -1,3 +0,0 @@
|
||||
{
|
||||
"projectType": "cli-ajs"
|
||||
}
|
@ -1,22 +1,14 @@
|
||||
import { Component, OnInit, OnDestroy, ElementRef } from '@angular/core';
|
||||
import { Component, OnInit, ElementRef } from '@angular/core';
|
||||
import { LazyLoaderService } from '../lazy-loader.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-angular-js',
|
||||
template: '<div ng-view></div>'
|
||||
})
|
||||
export class AngularJSComponent implements OnInit, OnDestroy {
|
||||
constructor(
|
||||
private lazyLoader: LazyLoaderService,
|
||||
private elRef: ElementRef
|
||||
) {}
|
||||
export class AngularJSComponent implements OnInit {
|
||||
constructor(private lazyLoader: LazyLoaderService, private elRef: ElementRef) {}
|
||||
|
||||
ngOnInit() {
|
||||
this.lazyLoader.load(this.elRef.nativeElement);
|
||||
}
|
||||
|
||||
|
||||
ngOnDestroy() {
|
||||
this.lazyLoader.destroy();
|
||||
}
|
||||
}
|
||||
|
@ -19,6 +19,7 @@ import { App404Component } from './app404/app404.component';
|
||||
BrowserModule,
|
||||
AppRoutingModule
|
||||
],
|
||||
providers: [],
|
||||
bootstrap: [AppComponent]
|
||||
})
|
||||
export class AppModule { }
|
||||
|
@ -1,25 +1,23 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import * as angular from 'angular';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class LazyLoaderService {
|
||||
private app: angular.auto.IInjectorService;
|
||||
bootstrapped = false;
|
||||
|
||||
load(el: HTMLElement): void {
|
||||
if (this.bootstrapped) {
|
||||
return;
|
||||
}
|
||||
|
||||
import('./angularjs-app').then(app => {
|
||||
try {
|
||||
this.app = app.bootstrap(el);
|
||||
app.bootstrap(el);
|
||||
this.bootstrapped = true;
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
destroy() {
|
||||
if (this.app) {
|
||||
this.app.get('$rootScope').$destroy();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,181 +0,0 @@
|
||||
# Accessibility in Angular
|
||||
|
||||
The web is used by a wide variety of people, including those who have visual or motor impairments.
|
||||
A variety of assistive technologies are available that make it much easier for these groups to
|
||||
interact with web-based software applications.
|
||||
In addition, designing an application to be more accessible generally improves the user experience for all users.
|
||||
|
||||
For an in-depth introduction to issues and techniques for designing accessible applications, see the [Accessibility](https://developers.google.com/web/fundamentals/accessibility/#what_is_accessibility) section of the Google's [Web Fundamentals](https://developers.google.com/web/fundamentals/).
|
||||
|
||||
This page discusses best practices for designing Angular applications that
|
||||
work well for all users, including those who rely on assistive technologies.
|
||||
|
||||
## Accessibility attributes
|
||||
|
||||
Building accessible web experience often involves setting [ARIA attributes](https://developers.google.com/web/fundamentals/accessibility/semantics-aria)
|
||||
to provide semantic meaning where it might otherwise be missing.
|
||||
Use [attribute binding](guide/template-syntax#attribute-binding) template syntax to control the values of accessibility-related attributes.
|
||||
|
||||
When binding to ARIA attributes in Angular, you must use the `attr.` prefix, as the ARIA
|
||||
specification depends specifically on HTML attributes rather than properties on DOM elements.
|
||||
|
||||
```html
|
||||
<!-- Use attr. when binding to an ARIA attribute -->
|
||||
<button [attr.aria-label]="myActionLabel">...</button>
|
||||
```
|
||||
|
||||
Note that this syntax is only necessary for attribute _bindings_.
|
||||
Static ARIA attributes require no extra syntax.
|
||||
|
||||
```html
|
||||
<!-- Static ARIA attributes require no extra syntax -->
|
||||
<button aria-label="Save document">...</button>
|
||||
```
|
||||
|
||||
NOTE:
|
||||
|
||||
<div class="alert is-helpful">
|
||||
|
||||
By convention, HTML attributes use lowercase names (`tabindex`), while properties use camelCase names (`tabIndex`).
|
||||
|
||||
See the [Template Syntax](https://angular.io/guide/template-syntax#html-attribute-vs-dom-property) guide for more background on the difference between attributes and properties.
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
## Angular UI components
|
||||
|
||||
The [Angular Material](https://material.angular.io/) library, which is maintained by the Angular team, is a suite of reusable UI components that aims to be fully accessible.
|
||||
The [Component Development Kit (CDK)](https://material.angular.io/cdk/categories) includes the `a11y` package that provides tools to support various areas of accessibility.
|
||||
For example:
|
||||
|
||||
* `LiveAnnouncer` is used to announce messages for screen-reader users using an `aria-live` region. See the W3C documentation for more information on [aria-live regions](https://www.w3.org/WAI/PF/aria-1.1/states_and_properties#aria-live).
|
||||
|
||||
* The `cdkTrapFocus` directive traps Tab-key focus within an element. Use it to create accessible experience for components like modal dialogs, where focus must be constrained.
|
||||
|
||||
For full details of these and other tools, see the [Angular CDK accessibility overview](https://material.angular.io/cdk/a11y/overview).
|
||||
|
||||
|
||||
### Augmenting native elements
|
||||
|
||||
Native HTML elements capture a number of standard interaction patterns that are important to accessibility.
|
||||
When authoring Angular components, you should re-use these native elements directly when possible, rather than re-implementing well-supported behaviors.
|
||||
|
||||
For example, instead of creating a custom element for a new variety of button, you can create a component that uses an attribute selector with a native `<button>` element.
|
||||
This most commonly applies to `<button>` and `<a>`, but can be used with many other types of element.
|
||||
|
||||
You can see examples of this pattern in Angular Material: [`MatButton`](https://github.com/angular/components/blob/master/src/material/button/button.ts#L66-L68), [`MatTabNav`](https://github.com/angular/components/blob/master/src/material/tabs/tab-nav-bar/tab-nav-bar.ts#L67), [`MatTable`](https://github.com/angular/components/blob/master/src/material/table/table.ts#L17).
|
||||
|
||||
### Using containers for native elements
|
||||
|
||||
Sometimes using the appropriate native element requires a container element.
|
||||
For example, the native `<input>` element cannot have children, so any custom text entry components need
|
||||
to wrap an `<input>` with additional elements.
|
||||
While you might just include the `<input>` in your custom component's template,
|
||||
this makes it impossible for users of the component to set arbitrary properties and attributes to the input element.
|
||||
Instead, you can create a container component that uses content projection to include the native control in the
|
||||
component's API.
|
||||
|
||||
You can see [`MatFormField`](https://material.angular.io/components/form-field/overview) as an example of this pattern.
|
||||
|
||||
## Case study: Building a custom progress bar
|
||||
|
||||
The following example shows how to make a simple progress bar accessible by using host binding to control accessibility-related attributes.
|
||||
|
||||
* The component defines an accessibility-enabled element with both the standard HTML attribute `role`, and ARIA attributes. The ARIA attribute `aria-valuenow` is bound to the user's input.
|
||||
|
||||
```ts
|
||||
import { Component, Input } from '@angular/core';
|
||||
/**
|
||||
* Example progressbar component.
|
||||
*/
|
||||
@Component({
|
||||
selector: 'example-progressbar',
|
||||
template: `<div class="bar" [style.width.%]="value"></div>`,
|
||||
styleUrls: ['./progress-bar.css'],
|
||||
host: {
|
||||
// Sets the role for this component to "progressbar"
|
||||
role: 'progressbar',
|
||||
|
||||
// Sets the minimum and maximum values for the progressbar role.
|
||||
'aria-valuemin': '0',
|
||||
'aria-valuemax': '0',
|
||||
|
||||
// Binding that updates the current value of the progressbar.
|
||||
'[attr.aria-valuenow]': 'value',
|
||||
}
|
||||
})
|
||||
export class ExampleProgressbar {
|
||||
/** Current value of the progressbar. */
|
||||
@Input() value: number = 0;
|
||||
}
|
||||
```
|
||||
|
||||
* In the template, the `aria-label` attribute ensures that the control is accessible to screen readers.
|
||||
|
||||
```html
|
||||
<label>
|
||||
Enter an example progress value
|
||||
<input type="number" min="0" max="100"
|
||||
[value]="progress" (input)="progress = $event.target.value">
|
||||
</label>
|
||||
|
||||
<!-- The user of the progressbar sets an aria-label to communicate what the progress means. -->
|
||||
<example-progressbar [value]="progress" aria-label="Example of a progress bar">
|
||||
</example-progressbar>
|
||||
```
|
||||
|
||||
[See the full example in StackBlitz](https://stackblitz.com/edit/angular-kn5jdi?file=src%2Fapp%2Fapp.component.html).
|
||||
|
||||
## Routing and focus management
|
||||
|
||||
Tracking and controlling [focus](https://developers.google.com/web/fundamentals/accessibility/focus/) in a UI is an important consideration in designing for accessibility.
|
||||
When using Angular routing, you should decide where page focus goes upon navigation.
|
||||
|
||||
To avoid relying solely on visual cues, you need to make sure your routing code updates focus after page navigation.
|
||||
Use the `NavigationEnd` event from the `Router` service to know when to update
|
||||
focus.
|
||||
|
||||
The following example shows how to find and focus the main content header in the DOM after navigation.
|
||||
|
||||
```ts
|
||||
|
||||
router.events.pipe(filter(e => e instanceof NavigationEnd)).subscribe(() => {
|
||||
const mainHeader = document.querySelector('#main-content-header')
|
||||
if (mainHeader) {
|
||||
mainHeader.focus();
|
||||
}
|
||||
});
|
||||
|
||||
```
|
||||
In a real application, the element that receives focus will depend on your specific
|
||||
application structure and layout.
|
||||
The focused element should put users in a position to immediately move into the main content that has just been routed into view.
|
||||
You should avoid situations where focus returns to the `body` element after a route change.
|
||||
|
||||
|
||||
## Additional resources
|
||||
|
||||
* [Accessibility - Google Web Fundamentals](https://developers.google.com/web/fundamentals/accessibility)
|
||||
|
||||
* [ARIA specification and authoring practices](https://www.w3.org/TR/wai-aria/)
|
||||
|
||||
* [Material Design - Accessibility](https://material.io/design/usability/accessibility.html)
|
||||
|
||||
* [Smashing Magazine](https://www.smashingmagazine.com/search/?q=accessibility)
|
||||
|
||||
* [Inclusive Components](https://inclusive-components.design/)
|
||||
|
||||
* [Accessibility Resources and Code Examples](https://dequeuniversity.com/resources/)
|
||||
|
||||
* [W3C - Web Accessibility Initiative](https://www.w3.org/WAI/people-use-web/)
|
||||
|
||||
* [Rob Dodson A11ycasts](https://www.youtube.com/watch?v=HtTyRajRuyY)
|
||||
|
||||
* [Codelyzer](http://codelyzer.com/rules/) provides linting rules that can help you make sure your code meets accessibility standards.
|
||||
|
||||
Books
|
||||
|
||||
* "A Web for Everyone: Designing Accessible User Experiences", Sarah Horton and Whitney Quesenbery
|
||||
|
||||
* "Inclusive Design Patterns", Heydon Pickering
|
@ -29,7 +29,7 @@ ng generate app-shell --client-project my-app --universal-project server-app
|
||||
|
||||
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="json" linenums="false">
|
||||
<code-example format="." language="none" linenums="false">
|
||||
"server": {
|
||||
"builder": "@angular-devkit/build-angular:server",
|
||||
"options": {
|
||||
|
@ -53,7 +53,7 @@ Angular supports most recent browsers. This includes the following specific vers
|
||||
IE
|
||||
</td>
|
||||
<td>
|
||||
11, 10, 9
|
||||
11<br>10<br>9
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
@ -89,7 +89,7 @@ Angular supports most recent browsers. This includes the following specific vers
|
||||
</td>
|
||||
|
||||
<td>
|
||||
Nougat (7.0), Marshmallow (6.0), Lollipop (5.0, 5.1), KitKat (4.4)
|
||||
Nougat (7.0)<br>Marshmallow (6.0)<br>Lollipop (5.0, 5.1)<br>KitKat (4.4)
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
@ -127,27 +127,25 @@ In Angular CLI version 8 and higher, applications are built using *differential
|
||||
This strategy allows you to continue to build your web application to support multiple browsers, but only load the necessary code that the browser needs.
|
||||
For more information about how this works, see [Differential Loading](guide/deployment#differential-loading) in the [Deployment guide](guide/deployment).
|
||||
|
||||
## Enabling polyfills with CLI projects
|
||||
## Enabling polyfills
|
||||
|
||||
The [Angular CLI](cli) provides support for polyfills.
|
||||
If you are not using the CLI to create your projects, see [Polyfill instructions for non-CLI users](#non-cli).
|
||||
[Angular CLI](cli) users enable polyfills through the `src/polyfills.ts` file that
|
||||
the CLI created with your project.
|
||||
|
||||
When you create a project with the `ng new` command, a `src/polyfills.ts` configuration file is created as part of your project folder.
|
||||
This file incorporates the mandatory and many of the optional polyfills as JavaScript `import` statements.
|
||||
|
||||
* The npm packages for the [_mandatory_ polyfills](#polyfill-libs) (such as `zone.js`) are installed automatically for you when you create your project with `ng new`, and their corresponding `import` statements are already enabled in the `src/polyfills.ts` configuration file.
|
||||
The npm packages for the _mandatory_ polyfills (such as `zone.js`) were installed automatically for you when you created your project and their corresponding `import` statements are ready to go. You probably won't touch these.
|
||||
|
||||
* If you need an _optional_ polyfill, you must install its npm package, then uncomment or create the corresponding import statement in the `src/polyfills.ts` configuration file.
|
||||
|
||||
For example, if you need the optional [web animations polyfill](http://caniuse.com/#feat=web-animation), you could install it with `npm`, using the following command (or the `yarn` equivalent):
|
||||
But if you need an optional polyfill, you'll have to install its npm package.
|
||||
For example, [if you need the web animations polyfill](http://caniuse.com/#feat=web-animation), you could install it with `npm`, using the following command (or the `yarn` equivalent):
|
||||
|
||||
<code-example language="sh" class="code-shell">
|
||||
# install the optional web animations polyfill
|
||||
# note that the web-animations-js polyfill is only here as an example
|
||||
# it isn't a strict requirement of Angular anymore (more below)
|
||||
npm install --save web-animations-js
|
||||
</code-example>
|
||||
|
||||
You can then add the import statement in the `src/polyfills.ts` file.
|
||||
For many polyfills, you can simply un-comment the corresponding `import` statement in the file, as in the following example.
|
||||
Then open the `polyfills.ts` file and un-comment the corresponding `import` statement as in the following example:
|
||||
|
||||
<code-example header="src/polyfills.ts">
|
||||
/**
|
||||
@ -157,14 +155,23 @@ For many polyfills, you can simply un-comment the corresponding `import` stateme
|
||||
import 'web-animations-js'; // Run `npm install --save web-animations-js`.
|
||||
</code-example>
|
||||
|
||||
If the polyfill you want is not already in `polyfills.ts` file, add the `import` statement by hand.
|
||||
If you can't find the polyfill you want in `polyfills.ts`,
|
||||
add it yourself, following the same pattern:
|
||||
|
||||
1. install the npm package
|
||||
1. `import` the file in `polyfills.ts`
|
||||
|
||||
<div class="alert is-helpful">
|
||||
|
||||
Non-CLI users should follow the instructions [below](#non-cli).
|
||||
</div>
|
||||
|
||||
{@a polyfill-libs}
|
||||
|
||||
### Mandatory polyfills
|
||||
These are the polyfills required to run an Angular application on each supported browser:
|
||||
|
||||
|
||||
<table>
|
||||
|
||||
<tr style="vertical-align: top">
|
||||
@ -182,13 +189,26 @@ These are the polyfills required to run an Angular application on each supported
|
||||
<tr style="vertical-align: top">
|
||||
|
||||
<td>
|
||||
Chrome, Firefox, Edge, <br>
|
||||
Safari, Android, IE10+
|
||||
Chrome, Firefox, Edge, Safari 9+
|
||||
</td>
|
||||
|
||||
<td>
|
||||
|
||||
[ES2015](guide/browser-support#core-es6)
|
||||
[ES7/reflect](guide/browser-support#core-es7-reflect) (JIT only)
|
||||
|
||||
</td>
|
||||
|
||||
</tr>
|
||||
|
||||
<tr style="vertical-align: top">
|
||||
|
||||
<td>
|
||||
Safari 7 & 8, IE10 & 11, Android 4.1+
|
||||
</td>
|
||||
|
||||
<td>
|
||||
|
||||
[ES6](guide/browser-support#core-es6)
|
||||
|
||||
</td>
|
||||
|
||||
@ -202,7 +222,7 @@ These are the polyfills required to run an Angular application on each supported
|
||||
|
||||
<td>
|
||||
|
||||
ES2015<br>[classList](guide/browser-support#classlist)
|
||||
[ES6<br>classList](guide/browser-support#classlist)
|
||||
|
||||
</td>
|
||||
|
||||
@ -215,6 +235,12 @@ These are the polyfills required to run an Angular application on each supported
|
||||
|
||||
Some features of Angular may require additional polyfills.
|
||||
|
||||
For example, the animations library relies on the standard web animation API, which is only available in Chrome and Firefox today.
|
||||
(note that the dependency of web-animations-js in Angular is only necessary if `AnimationBuilder` is used.)
|
||||
|
||||
Here are the features which may require additional polyfills:
|
||||
|
||||
|
||||
<table>
|
||||
|
||||
<tr style="vertical-align: top">
|
||||
@ -237,8 +263,31 @@ Some features of Angular may require additional polyfills.
|
||||
|
||||
<td>
|
||||
|
||||
[AnimationBuilder](api/animations/AnimationBuilder).
|
||||
(Standard animation support does not require polyfills.)
|
||||
[JIT compilation](guide/aot-compiler).
|
||||
|
||||
Required to reflect for metadata.
|
||||
</td>
|
||||
|
||||
<td>
|
||||
|
||||
[ES7/reflect](guide/browser-support#core-es7-reflect)
|
||||
|
||||
</td>
|
||||
|
||||
<td>
|
||||
All current browsers. Enabled by default.
|
||||
Can remove if you always use AOT and only use Angular decorators.
|
||||
</td>
|
||||
|
||||
</tr>
|
||||
|
||||
<tr style="vertical-align: top">
|
||||
|
||||
<td>
|
||||
|
||||
[Animations](guide/animations)
|
||||
<br>Only if `Animation Builder` is used within the application--standard
|
||||
animation support in Angular doesn't require any polyfills (as of NG6).
|
||||
|
||||
</td>
|
||||
|
||||
@ -249,9 +298,8 @@ Some features of Angular may require additional polyfills.
|
||||
</td>
|
||||
|
||||
<td>
|
||||
<p>If AnimationBuilder is used, enables scrubbing
|
||||
support for IE/Edge and Safari.
|
||||
(Chrome and Firefox support this natively).</p>
|
||||
<p>If AnimationBuilder is used then the polyfill will enable scrubbing
|
||||
support for IE/Edge and Safari (Chrome and Firefox support this natively).</p>
|
||||
</td>
|
||||
|
||||
</tr>
|
||||
@ -260,10 +308,15 @@ Some features of Angular may require additional polyfills.
|
||||
|
||||
<td>
|
||||
|
||||
If you use the following deprecated i18n pipes:
|
||||
If you use the following deprecated i18n pipes:
|
||||
|
||||
|
||||
[date](api/common/DeprecatedDatePipe),
|
||||
|
||||
[currency](api/common/DeprecatedCurrencyPipe),
|
||||
|
||||
[decimal](api/common/DeprecatedDecimalPipe),
|
||||
|
||||
[percent](api/common/DeprecatedPercentPipe)
|
||||
|
||||
</td>
|
||||
@ -284,7 +337,9 @@ Some features of Angular may require additional polyfills.
|
||||
|
||||
<td>
|
||||
|
||||
[NgClass](api/common/NgClass) on SVG elements
|
||||
[NgClass](api/common/NgClass)
|
||||
|
||||
on SVG elements
|
||||
</td>
|
||||
|
||||
<td>
|
||||
@ -303,7 +358,9 @@ Some features of Angular may require additional polyfills.
|
||||
|
||||
<td>
|
||||
|
||||
[Http](guide/http) when sending and receiving binary data
|
||||
[Http](guide/http)
|
||||
|
||||
when sending and receiving binary data
|
||||
</td>
|
||||
|
||||
<td>
|
||||
@ -326,8 +383,9 @@ Some features of Angular may require additional polyfills.
|
||||
|
||||
<td>
|
||||
|
||||
[Router](guide/router) when using
|
||||
[hash-based routing](guide/router#appendix-locationstrategy-and-browser-url-styles)
|
||||
[Router](guide/router)
|
||||
|
||||
when using [hash-based routing](guide/router#appendix-locationstrategy-and-browser-url-styles)
|
||||
</td>
|
||||
|
||||
<td>
|
||||
@ -346,9 +404,8 @@ Some features of Angular may require additional polyfills.
|
||||
|
||||
|
||||
|
||||
### Suggested polyfills
|
||||
|
||||
The following polyfills are used to test the framework itself. They are a good starting point for an application.
|
||||
### Suggested polyfills ##
|
||||
Below are the polyfills which are used to test the framework itself. They are a good starting point for an application.
|
||||
|
||||
|
||||
<table>
|
||||
@ -369,6 +426,24 @@ The following polyfills are used to test the framework itself. They are a good s
|
||||
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
|
||||
<td>
|
||||
|
||||
<a id='core-es7-reflect' href="https://github.com/zloirock/core-js/tree/v2/fn/reflect">ES7/reflect</a>
|
||||
|
||||
</td>
|
||||
|
||||
<td>
|
||||
MIT
|
||||
</td>
|
||||
|
||||
<td>
|
||||
0.5KB
|
||||
</td>
|
||||
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
|
||||
<td>
|
||||
@ -391,7 +466,7 @@ The following polyfills are used to test the framework itself. They are a good s
|
||||
|
||||
<td>
|
||||
|
||||
<a id='core-es6' href="https://github.com/zloirock/core-js">ES2015</a>
|
||||
<a id='core-es6' href="https://github.com/zloirock/core-js">ES6</a>
|
||||
|
||||
</td>
|
||||
|
||||
@ -520,14 +595,11 @@ The following polyfills are used to test the framework itself. They are a good s
|
||||
computed with the <a href="http://closure-compiler.appspot.com/home">closure compiler</a>.
|
||||
|
||||
{@a non-cli}
|
||||
|
||||
## Polyfills for non-CLI users
|
||||
|
||||
If you are not using the CLI, add your polyfill scripts directly to the host web page (`index.html`).
|
||||
If you are not using the CLI, you should add your polyfill scripts directly to the host web page (`index.html`), perhaps like this.
|
||||
|
||||
For example:
|
||||
|
||||
<code-example header="src/index.html" language="html" linenums="false">
|
||||
<code-example header="src/index.html">
|
||||
<!-- pre-zone polyfills -->
|
||||
<script src="node_modules/core-js/client/shim.min.js"></script>
|
||||
<script src="node_modules/web-animations-js/web-animations.min.js"></script>
|
||||
|
@ -185,7 +185,8 @@ is available to <code>declarations</code> of this module.</p>
|
||||
</td>
|
||||
</tr><tr>
|
||||
<td><code><b>@Injectable()</b><br>class MyService() {}</code></td>
|
||||
<td><p>Declares that a class can be provided and injected by other classes. Without this decorator, the compiler won't generate enough metadata to allow the class to be created properly when it's injected somewhere.</p>
|
||||
<td><p>Declares that a class has dependencies that should be injected into the constructor when the dependency injector is creating an instance of this class.
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody></table>
|
||||
|
@ -153,13 +153,16 @@ The list is by no means exhaustive, but should provide you with a good starting
|
||||
(https://ngmilk.rocks/2015/03/09/angularjs-html5-mode-or-pretty-urls-on-apache-using-htaccess/):
|
||||
|
||||
<code-example format=".">
|
||||
|
||||
RewriteEngine On
|
||||
# If an existing asset or directory is requested go to it as it is
|
||||
RewriteCond %{DOCUMENT_ROOT}%{REQUEST_URI} -f [OR]
|
||||
RewriteCond %{DOCUMENT_ROOT}%{REQUEST_URI} -d
|
||||
RewriteRule ^ - [L]<br>
|
||||
RewriteRule ^ - [L]
|
||||
|
||||
# If the requested resource doesn't exist, use index.html
|
||||
RewriteRule ^ /index.html
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
@ -167,15 +170,18 @@ The list is by no means exhaustive, but should provide you with a good starting
|
||||
[Front Controller Pattern Web Apps](https://www.nginx.com/resources/wiki/start/topics/tutorials/config_pitfalls/#front-controller-pattern-web-apps),
|
||||
modified to serve `index.html`:
|
||||
|
||||
```
|
||||
try_files $uri $uri/ /index.html;
|
||||
```
|
||||
<code-example format=".">
|
||||
|
||||
try_files $uri $uri/ /index.html;
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
* [IIS](https://www.iis.net/): add a rewrite rule to `web.config`, similar to the one shown
|
||||
[here](http://stackoverflow.com/a/26152011/2116927):
|
||||
|
||||
<code-example format='.' language="xml" linenums="false">
|
||||
<code-example format='.' linenums="false">
|
||||
|
||||
<system.webServer>
|
||||
<rewrite>
|
||||
<rules>
|
||||
@ -190,6 +196,7 @@ modified to serve `index.html`:
|
||||
</rules>
|
||||
</rewrite>
|
||||
</system.webServer>
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
@ -207,11 +214,13 @@ and to
|
||||
* [Firebase hosting](https://firebase.google.com/docs/hosting/): add a
|
||||
[rewrite rule](https://firebase.google.com/docs/hosting/url-redirects-rewrites#section-rewrites).
|
||||
|
||||
<code-example format="." language="json">
|
||||
<code-example format=".">
|
||||
|
||||
"rewrites": [ {
|
||||
"source": "**",
|
||||
"destination": "/index.html"
|
||||
} ]
|
||||
|
||||
</code-example>
|
||||
|
||||
{@a cors}
|
||||
@ -403,7 +412,7 @@ Differential loading, which is supported by default in Angular CLI version 8 and
|
||||
|
||||
Differential loading is a strategy where the CLI builds two separate bundles as part of your deployed application.
|
||||
|
||||
* The first bundle contains modern ES2015 syntax, takes advantage of built-in support in modern browsers, ships less polyfills, and results in a smaller bundle size.
|
||||
* The first bundle contains modern ES1015 syntax, takes advantage of built-in support in modern browsers, ships less polyfills, and results in a smaller bundle size.
|
||||
|
||||
* The second bundle contains code in the old ES5 syntax, along with all necessary polyfills. This results in a larger bundle size, but supports older browsers.
|
||||
|
||||
@ -437,19 +446,23 @@ When you create a production build using [`ng build --prod`](cli/build), the CLI
|
||||
The `index.html` file is also modified during the build process to include script tags that enable differential loading. See the sample output below from the `index.html` file produced during a build using `ng build`.
|
||||
|
||||
<code-example language="html" format="." linenums="false">
|
||||
<body>
|
||||
<app-root></app-root>
|
||||
<script src="runtime-es2015.js" type="module"></script>
|
||||
<script src="runtime-es5.js" nomodule></script>
|
||||
<script src="polyfills-es2015.js" type="module"></script>
|
||||
<script src="polyfills-es5.js" nomodule></script>
|
||||
<script src="styles-es2015.js" type="module"></script>
|
||||
<script src="styles-es5.js" nomodule></script>
|
||||
<script src="vendor-es2015.js" type="module"></script>
|
||||
<script src="vendor-es5.js" nomodule></script>
|
||||
<script src="main-es2015.js" type="module"></script>
|
||||
<script src="main-es5.js" nomodule></script>
|
||||
</body>
|
||||
|
||||
<!-- ... -->
|
||||
<body>
|
||||
<app-root></app-root>
|
||||
<script src="runtime-es2015.js" type="module"></script>
|
||||
<script src="runtime-es5.js" nomodule></script>
|
||||
<script src="polyfills-es2015.js" type="module"></script>
|
||||
<script src="polyfills-es5.js" nomodule></script>
|
||||
<script src="styles-es2015.js" type="module"></script>
|
||||
<script src="styles-es5.js" nomodule></script>
|
||||
<script src="vendor-es2015.js" type="module"></script>
|
||||
<script src="vendor-es5.js" nomodule></script>
|
||||
<script src="main-es2015.js" type="module"></script>
|
||||
<script src="main-es5.js" nomodule></script>
|
||||
</body>
|
||||
<!-- ... -->
|
||||
|
||||
</code-example>
|
||||
|
||||
Each script tag has a `type="module"` or `nomodule` attribute. Browsers with native support for ES modules only load the scripts with the `module` type attribute and ignore scripts with the `nomodule` attribute. Legacy browsers only load the scripts with the `nomodule` attribute, and ignore the script tags with the `module` type that load ES modules.
|
||||
@ -510,7 +523,7 @@ By default, legacy browsers such as IE 9-11 are ignored, and the compilation tar
|
||||
|
||||
<div class="alert is-important">
|
||||
|
||||
To see which browsers are supported with the above configuration, see which settings meet to your browser support requirements, see the [Browserslist compatibility page](https://browserl.ist/?q=%3E+0.5%25%2C+last+2+versions%2C+Firefox+ESR%2C+not+dead%2C+not+IE+9-11).
|
||||
To see which browsers are supported with the above configuration, see which settings meet to your browser support requirements, see the [Browserslist compatibility page](https://browserl.ist/?q=%3E+0.5%25%2C+last+2+versions%2C+Firefox+ESR%2C+Chrome+41%2C+not+dead%2C+not+IE+9-11).
|
||||
|
||||
</div>
|
||||
|
||||
@ -647,7 +660,7 @@ ng test --configuration es5
|
||||
|
||||
### Configuring the e2e command
|
||||
|
||||
Create an [ES5 serve configuration](guide/deployment#configuring-serve-for-es5) as explained above, and configuration an ES5 configuration for the E2E target.
|
||||
Create an ES5 serve configuration as explained above (link to the above serve section), and configuration an ES5 configuration for the E2E target.
|
||||
|
||||
<code-example language="json" format="." linenums="false">
|
||||
|
||||
|
@ -368,7 +368,7 @@ These two properties have subtle differences, so switching to `textContent` unde
|
||||
All of the `wtf*` APIs are deprecated and will be removed in a future version.
|
||||
|
||||
{@a webworker-apps}
|
||||
### Running Angular applications in platform-webworker
|
||||
### Running Angular applications in platform-webworker
|
||||
|
||||
The `@angular/platform-*` packages enable Angular to be run in different contexts. For examples,
|
||||
`@angular/platform-server` enables Angular to be run on the server, and `@angular/platform-browser`
|
||||
@ -382,7 +382,7 @@ worker is not the best strategy for most applications.
|
||||
|
||||
Going forward, we will focus our efforts related to web workers around their primary use case of
|
||||
offloading CPU-intensive, non-critical work needed for initial rendering (such as in-memory search
|
||||
and image processing). Learn more in the
|
||||
and image processing). Learn more in the
|
||||
[guide to Using Web Workers with the Angular CLI](guide/web-worker).
|
||||
|
||||
As of Angular version 8, all `platform-webworker` APIs are deprecated.
|
||||
@ -465,99 +465,3 @@ For more information about using `@angular/common/http`, see the [HttpClient gui
|
||||
| `MockBackend` | [`HttpTestingController`](/api/common/http/testing/HttpTestingController) |
|
||||
| `MockConnection` | [`HttpTestingController`](/api/common/http/testing/HttpTestingController) |
|
||||
|
||||
## Renderer to Renderer2 migration
|
||||
|
||||
### Migration Overview
|
||||
|
||||
The `Renderer` class has been marked as deprecated since Angular version 4. This section provides guidance on migrating from this deprecated API to the newer `Renderer2` API and what it means for your app.
|
||||
|
||||
### Why should I migrate to Renderer2?
|
||||
|
||||
The deprecated `Renderer` class has been removed in version 9 of Angular, so it's necessary to migrate to a supported API. Using `Renderer2` is the recommended strategy because it supports a similar set of functionality to `Renderer`. The API surface is quite large (with 19 methods), but the schematic should simplify this process for your applications.
|
||||
|
||||
### Is there action required on my end?
|
||||
|
||||
No. The schematic should handle most cases with the exception of `Renderer.animate()` and `Renderer.setDebugInfo()`, which already aren’t supported.
|
||||
|
||||
### What are the `__ngRendererX` methods? Why are they necessary?
|
||||
|
||||
Some methods either don't have exact equivalents in `Renderer2`, or they correspond to more than one expression. For example, both renderers have a `createElement()` method, but they're not equal because a call such as `renderer.createElement(parentNode, namespaceAndName)` in the `Renderer` corresponds to the following block of code in `Renderer2`:
|
||||
|
||||
```ts
|
||||
const [namespace, name] = splitNamespace(namespaceAndName);
|
||||
const el = renderer.createElement(name, namespace);
|
||||
if (parentNode) {
|
||||
renderer.appendChild(parentNode, el);
|
||||
}
|
||||
return el;
|
||||
```
|
||||
|
||||
Migration has to guarantee that the return values of functions and types of variables stay the same. To handle the majority of cases safely, the schematic declares helper functions at the bottom of the user's file. These helpers encapsulate your own logic and keep the replacements inside your code down to a single function call. Here's an example of how the `createElement()` migration looks:
|
||||
|
||||
|
||||
**Before:**
|
||||
|
||||
```ts
|
||||
public createAndAppendElement() {
|
||||
const el = this.renderer.createElement('span');
|
||||
el.textContent = 'hello world';
|
||||
return el;
|
||||
}
|
||||
```
|
||||
|
||||
**After:**
|
||||
|
||||
<code-example linenums=false>
|
||||
|
||||
public createAndAppendElement() {
|
||||
const el = __ngRendererCreateElement(this.renderer, this.element, 'span');
|
||||
el.textContent = 'hello world';
|
||||
return el;
|
||||
}
|
||||
// Generated code at the bottom of the file
|
||||
__ngRendererCreateElement(renderer: any, parentNode: any, nameAndNamespace: any) {
|
||||
const [namespace, name] = __ngRendererSplitNamespace(namespaceAndName);
|
||||
const el = renderer.createElement(name, namespace);
|
||||
if (parentNode) {
|
||||
renderer.appendChild(parentNode, el);
|
||||
}
|
||||
return el;
|
||||
}
|
||||
__ngRendererSplitNamespace(nameAndNamespace: any) {
|
||||
// returns the split name and namespace
|
||||
}
|
||||
|
||||
</code-example>
|
||||
|
||||
When implementing these helper functions, the schematic ensures that they're only declared once per file and that their names are unique enough that there's a small chance of colliding with pre-existing functions in your code. The schematic also keeps their parameter types as `any` so that it doesn't have to insert extra logic that ensures that their values have the correct type.
|
||||
|
||||
### I’m a library author. Should I run this migration?
|
||||
|
||||
**Library authors should definitely use this migration to move away from the `Renderer`. Otherwise, the libraries won't work with applications built with version 9.**
|
||||
|
||||
|
||||
### Full list of method migrations
|
||||
|
||||
The following table shows all methods that the migration maps from `Renderer` to `Renderer2`.
|
||||
|
||||
|Renderer|Renderer2|
|
||||
|---|---|
|
||||
|`listen(renderElement, name, callback)`|`listen(renderElement, name, callback)`|
|
||||
|`setElementProperty(renderElement, propertyName, propertyValue)`|`setProperty(renderElement, propertyName, propertyValue)`|
|
||||
|`setText(renderNode, text)`|`setValue(renderNode, text)`|
|
||||
|`listenGlobal(target, name, callback)`|`listen(target, name, callback)`|
|
||||
|`selectRootElement(selectorOrNode, debugInfo?)`|`selectRootElement(selectorOrNode)`|
|
||||
|`createElement(parentElement, name, debugInfo?)`|`appendChild(parentElement, createElement(name))`|
|
||||
|`setElementStyle(el, style, value?)`|`value == null ? removeStyle(el, style) : setStyle(el, style, value)`
|
||||
|`setElementAttribute(el, name, value?)`|`attributeValue == null ? removeAttribute(el, name) : setAttribute(el, name, value)`
|
||||
|`createText(parentElement, value, debugInfo?)`|`appendChild(parentElement, createText(value))`|
|
||||
|`createTemplateAnchor(parentElement)`|`appendChild(parentElement, createComment(''))`|
|
||||
|`setElementClass(renderElement, className, isAdd)`|`isAdd ? addClass(renderElement, className) : removeClass(renderElement, className)`|
|
||||
|`projectNodes(parentElement, nodes)`|`for (let i = 0; i < nodes.length; i<ins></ins>) { appendChild(parentElement, nodes<i>); }`|
|
||||
|`attachViewAfter(node, viewRootNodes)`|`const parentElement = parentNode(node); const nextSibling = nextSibling(node); for (let i = 0; i < viewRootNodes.length; i<ins></ins>) { insertBefore(parentElement, viewRootNodes<i>, nextSibling);}`|
|
||||
|`detachView(viewRootNodes)`|`for (let i = 0; i < viewRootNodes.length; i<ins></ins>) {const node = viewRootNodes<i>; const parentElement = parentNode(node); removeChild(parentElement, node);}`|
|
||||
|`destroyView(hostElement, viewAllNodes)`|`for (let i = 0; i < viewAllNodes.length; i<ins></ins>) { destroyNode(viewAllNodes<i>); }`|
|
||||
|`setBindingDebugInfo()`|This function is a noop in `Renderer2`.|
|
||||
|`createViewRoot(hostElement)`|Should be replaced with a reference to `hostElement`|
|
||||
|`invokeElementMethod(renderElement, methodName, args?)`|`(renderElement as any)<methodName>.apply(renderElement, args);`|
|
||||
|`animate(element, startingStyles, keyframes, duration, delay, easing, previousPlayers?)`|Throws an error (same behavior as `Renderer.animate()`)|
|
||||
|
@ -1,5 +1,12 @@
|
||||
# Entry Components
|
||||
|
||||
#### Prerequisites:
|
||||
|
||||
A basic understanding of the following concepts:
|
||||
* [Bootstrapping](guide/bootstrapping).
|
||||
|
||||
<hr />
|
||||
|
||||
An entry component is any component that Angular loads imperatively, (which means you’re not referencing it in the template), by type. You specify an entry component by bootstrapping it in an NgModule, or including it in a routing definition.
|
||||
|
||||
<div class="alert is-helpful">
|
||||
|
@ -1,6 +1,12 @@
|
||||
# Feature Modules
|
||||
|
||||
Feature modules are NgModules for the purpose of organizing code.
|
||||
Feature modules are NgModules for the purpose of organizing code.
|
||||
|
||||
#### Prerequisites
|
||||
A basic understanding of the following:
|
||||
* [Bootstrapping](guide/bootstrapping).
|
||||
* [JavaScript Modules vs. NgModules](guide/ngmodule-vs-jsmodule).
|
||||
* [Frequently Used Modules](guide/frequent-ngmodules).
|
||||
|
||||
For the final sample app with a feature module that this page describes,
|
||||
see the <live-example></live-example>.
|
||||
|
@ -1,5 +1,12 @@
|
||||
# Frequently Used Modules
|
||||
|
||||
#### Prerequisites
|
||||
|
||||
A basic understanding of [Bootstrapping](guide/bootstrapping).
|
||||
|
||||
|
||||
<hr>
|
||||
|
||||
An Angular app needs at least one module that serves as the root module.
|
||||
As you add features to your app, you can add them in modules.
|
||||
The following are frequently used Angular modules with examples
|
||||
|
@ -454,9 +454,10 @@ A form of property [data binding](#data-binding) in which a [template expression
|
||||
That text can be concatenated with neighboring text before it is assigned to an element property
|
||||
or displayed between element tags, as in this example.
|
||||
|
||||
```html
|
||||
<label>My current hero is {{hero.name}}</label>
|
||||
```
|
||||
<code-example language="html" escape="html">
|
||||
<label>My current hero is {{hero.name}}</label>
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
Read more about [interpolation](guide/template-syntax#interpolation) in [Template Syntax](guide/template-syntax).
|
||||
|
@ -1,13 +1,23 @@
|
||||
# Lazy Loading Feature Modules
|
||||
|
||||
#### Prerequisites
|
||||
A basic understanding of the following:
|
||||
* [Feature Modules](guide/feature-modules).
|
||||
* [JavaScript Modules vs. NgModules](guide/ngmodule-vs-jsmodule).
|
||||
* [Frequently Used Modules](guide/frequent-ngmodules).
|
||||
* [Types of Feature Modules](guide/module-types).
|
||||
* [Routing and Navigation](guide/router).
|
||||
|
||||
For the final sample app with two lazy loaded modules that this page describes, see the
|
||||
<live-example></live-example>.
|
||||
|
||||
<hr>
|
||||
|
||||
## High level view
|
||||
|
||||
By default, NgModules are eagerly loaded, which means that as soon as the app loads, so do all the NgModules, whether or not they are immediately necessary. For large apps with lots of routes, consider lazy loading—a design pattern that loads NgModules as needed. Lazy loading helps keep initial
|
||||
bundle sizes smaller, which in turn helps decrease load times.
|
||||
|
||||
For the final sample app with two lazy loaded modules that this page describes, see the
|
||||
<live-example></live-example>.
|
||||
|
||||
There are three main steps to setting up a lazy loaded feature module:
|
||||
|
||||
1. Create the feature module.
|
||||
|
@ -1,5 +1,16 @@
|
||||
|
||||
|
||||
# Types of Feature Modules
|
||||
|
||||
#### Prerequisites
|
||||
|
||||
A basic understanding of the following concepts:
|
||||
* [Feature Modules](guide/feature-modules).
|
||||
* [JavaScript Modules vs. NgModules](guide/ngmodule-vs-jsmodule).
|
||||
* [Frequently Used Modules](guide/frequent-ngmodules).
|
||||
|
||||
<hr>
|
||||
|
||||
There are five general categories of feature modules which
|
||||
tend to fall into the following groups:
|
||||
|
||||
|
@ -1,5 +1,15 @@
|
||||
# NgModule API
|
||||
|
||||
#### Prerequisites
|
||||
|
||||
A basic understanding of the following concepts:
|
||||
* [Bootstrapping](guide/bootstrapping).
|
||||
* [JavaScript Modules vs. NgModules](guide/ngmodule-vs-jsmodule).
|
||||
|
||||
<hr />
|
||||
|
||||
## Purpose of `@NgModule`
|
||||
|
||||
At a high level, NgModules are a way to organize Angular apps
|
||||
and they accomplish this through the metadata in the `@NgModule`
|
||||
decorator.
|
||||
|
@ -1,5 +1,13 @@
|
||||
# NgModule FAQs
|
||||
|
||||
|
||||
#### Prerequisites:
|
||||
|
||||
A basic understanding of the following concepts:
|
||||
* [NgModules](guide/ngmodules).
|
||||
|
||||
<hr />
|
||||
|
||||
NgModules help organize an application into cohesive blocks of functionality.
|
||||
|
||||
This page answers the questions many developers ask about NgModule design and implementation.
|
||||
|
@ -1,9 +1,13 @@
|
||||
# JavaScript Modules vs. NgModules
|
||||
|
||||
#### Prerequisites
|
||||
A basic understanding of [JavaScript/ECMAScript modules](https://hacks.mozilla.org/2015/08/es6-in-depth-modules/).
|
||||
|
||||
<hr>
|
||||
|
||||
JavaScript and Angular use modules to organize code, and
|
||||
though they organize it differently, Angular apps rely on both.
|
||||
|
||||
|
||||
## JavaScript modules
|
||||
|
||||
In JavaScript, modules are individual files with JavaScript code in them. To make what’s in them available, you write an export statement, usually after the relevant code, like this:
|
||||
@ -20,8 +24,6 @@ import { AppComponent } from './app.component';
|
||||
|
||||
JavaScript modules help you namespace, preventing accidental global variables.
|
||||
|
||||
For more information on JavaScript modules, see [JavaScript/ECMAScript modules](https://hacks.mozilla.org/2015/08/es6-in-depth-modules/).
|
||||
|
||||
## NgModules
|
||||
|
||||
<!-- KW-- perMisko: let's discuss. This does not answer the question why it is different. Also, last sentence is confusing.-->
|
||||
|
@ -1,5 +1,13 @@
|
||||
# NgModules
|
||||
|
||||
#### Prerequisites
|
||||
|
||||
A basic understanding of the following concepts:
|
||||
* [Bootstrapping](guide/bootstrapping).
|
||||
* [JavaScript Modules vs. NgModules](guide/ngmodule-vs-jsmodule).
|
||||
|
||||
<hr>
|
||||
|
||||
**NgModules** configure the injector and the compiler and help organize related things together.
|
||||
|
||||
An NgModule is a class marked by the `@NgModule` decorator.
|
||||
@ -12,6 +20,7 @@ For an example app showcasing all the techniques that NgModules related pages
|
||||
cover, see the <live-example></live-example>. For explanations on the individual techniques, visit the relevant NgModule pages under the NgModules
|
||||
section.
|
||||
|
||||
|
||||
## Angular modularity
|
||||
|
||||
Modules are a great way to organize an application and extend it with capabilities from external libraries.
|
||||
@ -48,13 +57,12 @@ You then import these modules into the root module.
|
||||
|
||||
## The basic NgModule
|
||||
|
||||
The [Angular CLI](cli) generates the following basic `AppModule` when creating a new app.
|
||||
The [Angular CLI](cli) generates the following basic app module when creating a new app.
|
||||
|
||||
|
||||
<code-example path="ngmodules/src/app/app.module.1.ts" header="src/app/app.module.ts (default AppModule)" linenums="false"> // @NgModule decorator with its metadata
|
||||
<code-example path="bootstrapping/src/app/app.module.ts" region="whole-ngmodule" header="src/app/app.module.ts" linenums="false">
|
||||
</code-example>
|
||||
|
||||
At the top are the import statements. The next section is where you configure the `@NgModule` by stating what components and directives belong to it (`declarations`) as well as which other modules it uses (`imports`). For more information on the structure of an `@NgModule`, be sure to read [Bootstrapping](guide/bootstrapping).
|
||||
At the top are the import statements. The next section is where you configure the `@NgModule` by stating what components and directives belong to it (`declarations`) as well as which other modules it uses (`imports`). This page builds on [Bootstrapping](guide/bootstrapping), which covers the structure of an NgModule in detail. If you need more information on the structure of an `@NgModule`, be sure to read [Bootstrapping](guide/bootstrapping).
|
||||
|
||||
<hr />
|
||||
|
||||
|
@ -1,10 +1,16 @@
|
||||
# Providers
|
||||
|
||||
A provider is an instruction to the DI system on how to obtain a value for a dependency. Most of the time, these dependencies are services that you create and provide.
|
||||
#### Prerequisites:
|
||||
* A basic understanding of [Bootstrapping](guide/bootstrapping).
|
||||
* Familiarity with [Frequently Used Modules](guide/frequent-ngmodules).
|
||||
|
||||
For the final sample app using the provider that this page describes,
|
||||
see the <live-example></live-example>.
|
||||
|
||||
<hr>
|
||||
|
||||
A provider is an instruction to the DI system on how to obtain a value for a dependency. Most of the time, these dependencies are services that you create and provide.
|
||||
|
||||
## Providing a service
|
||||
|
||||
If you already have an app that was created with the [Angular CLI](cli), you can create a service using the [`ng generate`](cli/generate) CLI command in the root project directory. Replace _User_ with the name of your service.
|
||||
|
@ -1,5 +1,18 @@
|
||||
# Sharing Modules
|
||||
|
||||
#### Prerequisites
|
||||
A basic understanding of the following:
|
||||
* [Feature Modules](guide/feature-modules).
|
||||
* [JavaScript Modules vs. NgModules](guide/ngmodule-vs-jsmodule).
|
||||
* [Frequently Used Modules](guide/frequent-ngmodules).
|
||||
* [Routing and Navigation](guide/router).
|
||||
* [Lazy loading modules](guide/lazy-loading-ngmodules).
|
||||
|
||||
|
||||
<!--* Components (#TBD) We don’t have a page just on the concept of components, but I think one would be helpful for beginners.-->
|
||||
|
||||
<hr>
|
||||
|
||||
Creating shared modules allows you to organize and streamline your code. You can put commonly
|
||||
used directives, pipes, and components into one module and then import just that module wherever
|
||||
you need it in other parts of your app.
|
||||
@ -41,7 +54,7 @@ to import `FormsModule`, `SharedModule` can still export
|
||||
way, you can give other modules access to `FormsModule` without
|
||||
having to import it directly into the `@NgModule` decorator.
|
||||
|
||||
### Using components vs services from other modules
|
||||
### Using components vs services from other modules.
|
||||
|
||||
There is an important distinction between using another module's component and
|
||||
using a service from another module. Import modules when you want to use
|
||||
|
@ -1,10 +1,15 @@
|
||||
# Singleton services
|
||||
|
||||
A singleton service is a service for which only once instance exists in an app.
|
||||
#### Prerequisites:
|
||||
|
||||
* A basic understanding of [Bootstrapping](guide/bootstrapping).
|
||||
* Familiarity with [Providers](guide/providers).
|
||||
|
||||
For a sample app using the app-wide singleton service that this page describes, see the
|
||||
<live-example name="ngmodules"></live-example> showcasing all the documented features of NgModules.
|
||||
|
||||
<hr />
|
||||
|
||||
## Providing a singleton service
|
||||
|
||||
There are two ways to make a service a singleton in Angular:
|
||||
|
@ -6,21 +6,21 @@ In version 9, the default setting for `@ViewChild` and `@ContentChild` queries i
|
||||
|
||||
In preparation for this change, in version 8, we are migrating all applications and libraries to explicitly specify the resolution strategy for `@ViewChild` and `@ContentChild` queries.
|
||||
|
||||
Specifically, this migration adds an explicit "static" flag that dictates when that query's results should be assigned.
|
||||
Adding this flag will ensure your code works the same way when upgrading to version 9.
|
||||
Specifically, this migration adds an explicit "static" flag that dictates when that query's results should be assigned.
|
||||
Adding this flag will ensure your code works the same way when upgrading to version 9.
|
||||
|
||||
Before:
|
||||
|
||||
```
|
||||
// query results sometimes available in `ngOnInit`, sometimes in `ngAfterViewInit` (based on template)
|
||||
@ViewChild('foo') foo: ElementRef;
|
||||
@ViewChild('foo') foo: ElementRef;
|
||||
```
|
||||
|
||||
After:
|
||||
|
||||
```
|
||||
// query results available in ngOnInit
|
||||
@ViewChild('foo', {static: true}) foo: ElementRef;
|
||||
@ViewChild('foo', {static: true}) foo: ElementRef;
|
||||
|
||||
OR
|
||||
|
||||
@ -28,7 +28,7 @@ OR
|
||||
@ViewChild('foo', {static: false}) foo: ElementRef;
|
||||
```
|
||||
|
||||
Starting with version 9, the `static` flag will default to false.
|
||||
Starting with version 9, the `static` flag will default to false.
|
||||
At that time, any `{static: false}` flags can be safely removed, and we will have a schematic that will update your code for you.
|
||||
|
||||
Note: this flag only applies to `@ViewChild` and `@ContentChild` queries specifically, as `@ViewChildren` and `@ContentChildren` queries do not have a concept of static and dynamic (they are always resolved as if they are "dynamic").
|
||||
@ -38,50 +38,50 @@ Note: this flag only applies to `@ViewChild` and `@ContentChild` queries specifi
|
||||
{@a what-to-do-with-todo}
|
||||
### What should I do if I see a `/* TODO: add static flag */` comment printed by the schematic?
|
||||
|
||||
If you see this comment, it means that the schematic couldn't statically figure out the correct flag. In this case, you'll have to add the correct flag based on your application's behavior.
|
||||
If you see this comment, it means that the schematic couldn't statically figure out the correct flag. In this case, you'll have to add the correct flag based on your application's behavior.
|
||||
For more information on how to choose, see the [next question](#how-do-i-choose).
|
||||
|
||||
{@a how-do-i-choose}
|
||||
### How do I choose which `static` flag value to use: `true` or `false`?
|
||||
|
||||
In the official API docs, we have always recommended retrieving query results in [`ngAfterViewInit` for view queries](https://angular.io/api/core/ViewChild#description) and [`ngAfterContentInit` for content queries](https://angular.io/api/core/ContentChild#description).
|
||||
This is because by the time those lifecycle hooks run, change detection has completed for the relevant nodes and we can guarantee that we have collected all the possible query results.
|
||||
In the official API docs, we have always recommended retrieving query results in [`ngAfterViewInit` for view queries](https://angular.io/api/core/ViewChild#description) and [`ngAfterContentInit` for content queries](https://angular.io/api/core/ContentChild#description).
|
||||
This is because by the time those lifecycle hooks run, change detection has completed for the relevant nodes and we can guarantee that we have collected all the possible query results.
|
||||
|
||||
Most applications will want to use `{static: false}` for the same reason. This setting will ensure query matches that are dependent on binding resolution (e.g. results inside `*ngIf`s or `*ngFor`s) will be found by the query.
|
||||
Most applications will want to use `{static: false}` for the same reason. This setting will ensure query matches that are dependent on binding resolution (e.g. results inside `*ngIf`s or `*ngFor`s) will be found by the query.
|
||||
|
||||
There are rarer cases where `{static: true}` flag might be necessary (see [answer here](#should-i-use-static-true)).
|
||||
|
||||
{@a should-i-use-static-true}
|
||||
### Is there a case where I should use `{static: true}`?
|
||||
|
||||
This option was introduced to support creating embedded views on the fly.
|
||||
If you need access to a `TemplateRef` in a query to create a view dynamically, you won't be able to do so in `ngAfterViewInit`.
|
||||
Change detection has already run on that view, so creating a new view with the template will cause an `ExpressionHasChangedAfterChecked` error to be thrown.
|
||||
In this case, you will want to set the `static` flag to `true` and create your view in `ngOnInit`.
|
||||
This option was introduced to support creating embedded views on the fly.
|
||||
If you need access to a `TemplateRef` in a query to create a view dynamically, you won't be able to do so in `ngAfterViewInit`.
|
||||
Change detection has already run on that view, so creating a new view with the template will cause an `ExpressionHasChangedAfterChecked` error to be thrown.
|
||||
In this case, you will want to set the `static` flag to `true` and create your view in `ngOnInit`.
|
||||
In most other cases, the best practice is to use `{static: false}`.
|
||||
|
||||
However, to facilitate the migration to version 8, you may also want to set the `static` flag to `true` if your component code already depends on the query results being available some time **before** `ngAfterViewInit` (for view queries) or `ngAfterContentInit` (for content queries).
|
||||
For example, if your component relies on the query results being populated in the `ngOnInit` hook or in `@Input` setters, you will need to either set the flag to `true` or re-work your component to adjust to later timing.
|
||||
For example, if your component relies on the query results being populated in the `ngOnInit` hook or in `@Input` setters, you will need to either set the flag to `true` or re-work your component to adjust to later timing.
|
||||
|
||||
Note: Selecting the static option means that query results nested in `*ngIf` or `*ngFor` will not be found by the query.
|
||||
These results are only retrievable after change detection runs.
|
||||
Note: Selecting the static option means that query results nested in `*ngIf` or `*ngFor` will not be found by the query.
|
||||
These results are only retrievable after change detection runs.
|
||||
|
||||
{@a what-does-this-flag-mean}
|
||||
### What does this flag mean and why is it necessary?
|
||||
|
||||
The default behavior for queries has historically been undocumented and confusing, and has also commonly led to issues that are difficult to debug.
|
||||
In version 9, we would like to make query behavior more consistent and simple to understand.
|
||||
The default behavior for queries has historically been undocumented and confusing, and has also commonly led to issues that are difficult to debug.
|
||||
In version 9, we would like to make query behavior more consistent and simple to understand.
|
||||
|
||||
To explain why, first it's important to understand how queries have worked up until now.
|
||||
|
||||
Without the `static` flag, the compiler decided when each query would be resolved on a case-by-case basis.
|
||||
All `@ViewChild`/`@ContentChild` queries were categorized into one of two buckets at compile time: "static" or "dynamic".
|
||||
Without the `static` flag, the compiler decided when each query would be resolved on a case-by-case basis.
|
||||
All `@ViewChild`/`@ContentChild` queries were categorized into one of two buckets at compile time: "static" or "dynamic".
|
||||
This classification determined when query results would become available to users.
|
||||
|
||||
- **Static queries** were queries where the result could be determined statically because the result didn't depend on runtime values like bindings.
|
||||
- **Static queries** were queries where the result could be determined statically because the result didn't depend on runtime values like bindings.
|
||||
Results from queries classified as static were available before change detection ran for that view (accessible in `ngOnInit`).
|
||||
|
||||
- **Dynamic queries** were queries where the result could NOT be determined statically because the result depended on runtime values (aka bindings).
|
||||
- **Dynamic queries** were queries where the result could NOT be determined statically because the result depended on runtime values (aka bindings).
|
||||
Results from queries classified as dynamic were not available until after change detection ran for that view (accessible in `ngAfterContentInit` for content queries or `ngAfterViewInit` for view queries).
|
||||
|
||||
For example, let's say we have a component, `Comp`. Inside it, we have this query:
|
||||
@ -96,8 +96,8 @@ and this template:
|
||||
<div foo></div>
|
||||
```
|
||||
|
||||
This `Foo` query would be categorized as static because at compile-time it's known that the `Foo` instance on the `<div>` is the correct result for the query.
|
||||
Because the query result is not dependent on runtime values, we don't have to wait for change detection to run on the template before resolving the query.
|
||||
This `Foo` query would be categorized as static because at compile-time it's known that the `Foo` instance on the `<div>` is the correct result for the query.
|
||||
Because the query result is not dependent on runtime values, we don't have to wait for change detection to run on the template before resolving the query.
|
||||
Consequently, results can be made available in `ngOnInit`.
|
||||
|
||||
Let's say the query is the same, but the component template looks like this:
|
||||
@ -106,62 +106,53 @@ Let's say the query is the same, but the component template looks like this:
|
||||
<div foo *ngIf="showing"></div>
|
||||
```
|
||||
|
||||
With that template, the query would be categorized as a dynamic query.
|
||||
We would need to know the runtime value of `showing` before determining what the correct results are for the query.
|
||||
With that template, the query would be categorized as a dynamic query.
|
||||
We would need to know the runtime value of `showing` before determining what the correct results are for the query.
|
||||
As a result, change detection must run first, and results can only be made available in `ngAfterViewInit` or a setter for the query property.
|
||||
|
||||
The effect of this implementation is that adding an `*ngIf` or `*ngFor` anywhere above a query match can change when that query's results become available.
|
||||
The effect of this implementation is that adding an `*ngIf` or `*ngFor` anywhere above a query match can change when that query's results become available.
|
||||
|
||||
Keep in mind that these categories only applied to `@ViewChild` and `@ContentChild` queries specifically.
|
||||
Keep in mind that these categories only applied to `@ViewChild` and `@ContentChild` queries specifically.
|
||||
`@ViewChildren` and `@ContentChildren` queries did not have a concept of static and dynamic, so they were always resolved as if they were "dynamic".
|
||||
|
||||
This strategy of resolving queries at different times based on the location of potential query matches has caused a lot of confusion. Namely:
|
||||
This strategy of resolving queries at different times based on the location of potential query matches has caused a lot of confusion. Namely:
|
||||
|
||||
* Sometimes query results are available in `ngOnInit`, but sometimes they aren't and it's not clear why (see [21800](https://github.com/angular/angular/issues/21800) or [19872](https://github.com/angular/angular/issues/19872)).
|
||||
|
||||
* `@ViewChild` queries are resolved at a different time from `@ViewChildren` queries, and `@ContentChild` queries are resolved at a different time from `@ContentChildren` queries.
|
||||
* `@ViewChild` queries are resolved at a different time from `@ViewChildren` queries, and `@ContentChild` queries are resolved at a different time from `@ContentChildren` queries.
|
||||
If a user turns a `@ViewChild` query into a `@ViewChildren` query, their code can break suddenly because the timing has shifted.
|
||||
|
||||
|
||||
* Code depending on a query result can suddenly stop working as soon as an `*ngIf` or an `*ngFor` is added to a template.
|
||||
|
||||
* A `@ContentChild` query for the same component will resolve at different times in the lifecycle for each usage of the component.
|
||||
* A `@ContentChild` query for the same component will resolve at different times in the lifecycle for each usage of the component.
|
||||
This leads to buggy behavior where using a component with `*ngIf` is broken in subtle ways that aren't obvious to the component author.
|
||||
|
||||
In version 9, we plan to simplify the behavior so all queries resolve after change detection runs by default.
|
||||
The location of query matches in the template cannot affect when the query result will become available and suddenly break your code, and the default behavior is always the same.
|
||||
This makes the logic more consistent and predictable for users.
|
||||
In version 9, we plan to simplify the behavior so all queries resolve after change detection runs by default.
|
||||
The location of query matches in the template cannot affect when the query result will become available and suddenly break your code, and the default behavior is always the same.
|
||||
This makes the logic more consistent and predictable for users.
|
||||
|
||||
That said, if an application does need query results earlier (for example, the query result is needed to create an embedded view), it's possible to add the `{static: true}` flag to explicitly ask for static resolution.
|
||||
That said, if an application does need query results earlier (for example, the query result is needed to create an embedded view), it's possible to add the `{static: true}` flag to explicitly ask for static resolution.
|
||||
With this flag, users can indicate that they only care about results that are statically available and the query results will be populated before `ngOnInit`.
|
||||
|
||||
{@a view-children-and-content-children}
|
||||
### Does this change affect `@ViewChildren` or `@ContentChildren` queries?
|
||||
|
||||
No, this change only affects `@ViewChild` and `@ContentChild` queries specifically.
|
||||
No, this change only affects `@ViewChild` and `@ContentChild` queries specifically.
|
||||
`@ViewChildren` and `@ContentChildren` queries are already "dynamic" by default and don't support static resolution.
|
||||
|
||||
{@a why-specify-static-false}
|
||||
### Why do I have to specify `{static: false}`? Isn't that the default?
|
||||
|
||||
The goal of this migration is to transition apps that aren't yet on version 9 to a query pattern that is compatible with version 9.
|
||||
However, most applications use libraries, and it's likely that some of these libraries may not be upgraded to version 8 yet (and thus might not have the proper flags).
|
||||
Since the application's version of Angular will be used for compilation, if we change the default, the behavior of queries in the library's components will change to the version 8 default and possibly break.
|
||||
This way, an application's dependencies will behave the same way during the transition as they did in the previous version.
|
||||
The goal of this migration is to transition apps that aren't yet on version 9 to a query pattern that is compatible with version 9.
|
||||
However, most applications use libraries, and it's likely that some of these libraries may not be upgraded to version 8 yet (and thus might not have the proper flags).
|
||||
Since the application's version of Angular will be used for compilation, if we change the default, the behavior of queries in the library's components will change to the version 8 default and possibly break.
|
||||
This way, an application's dependencies will behave the same way during the transition as they did in the previous version.
|
||||
|
||||
In Angular version 9 and later, it will be safe to remove any `{static: false}` flags and we will do this cleanup for you in a schematic.
|
||||
|
||||
{@a libraries}
|
||||
### Can I keep on using Angular libraries that haven’t yet updated to version 8 yet?
|
||||
|
||||
Yes, absolutely!
|
||||
Yes, absolutely!
|
||||
Because we have not changed the default query behavior in version 8 (i.e. the compiler still chooses a timing if no flag is set), when your application runs with a library that has not updated to version 8, the library will run the same way it did in version 7.
|
||||
This guarantees your app will work in version 8 even if libraries take longer to update their code.
|
||||
|
||||
{@a update-library-to-use-static-flag}
|
||||
### Can I update my library to version 8 by adding the `static` flag to view queries, while still being compatible with Angular version 7 apps?
|
||||
|
||||
Yes, the Angular team's recommendation for libraries is to update to version 8 and add the `static` flag. Angular version 7 apps will continue to work with libraries that have this flag.
|
||||
|
||||
However, if you update your library to Angular version 8 and want to take advantage of the new version 8 APIs, or you want more recent dependencies (such as Typescript or RxJS) your library will become incompatible with Angular version 7 apps. If your goal is to make your library compatible with Angular versions 7 and 8, you should not update your lib at all—except for `peerDependencies` in `package.json`.
|
||||
|
||||
In general, the most efficient plan is for libraries to adopt a 6 month major version schedule and bump the major version after each Angular update. That way, libraries stay in the same release cadence as Angular.
|
||||
This guarantees your app will work in version 8 even if libraries take longer to update their code.
|
||||
|
@ -275,179 +275,53 @@ In this example, the `[ngClass]="odd"` stays on the `<div>`.
|
||||
{@a microsyntax}
|
||||
|
||||
|
||||
## Microsyntax
|
||||
### Microsyntax
|
||||
|
||||
The Angular microsyntax lets you configure a directive in a compact, friendly string.
|
||||
The microsyntax parser translates that string into attributes on the `<ng-template>`:
|
||||
|
||||
* The `let` keyword declares a [_template input variable_](guide/structural-directives#template-input-variable)
|
||||
that you reference within the template. The input variables in this example are `hero`, `i`, and `odd`.
|
||||
The parser translates `let hero`, `let i`, and `let odd` into variables named
|
||||
The parser translates `let hero`, `let i`, and `let odd` into variables named,
|
||||
`let-hero`, `let-i`, and `let-odd`.
|
||||
|
||||
* The microsyntax parser title-cases all directives and prefixes them with the directive's
|
||||
attribute name, such as `ngFor`. For example, the `ngFor` input properties,
|
||||
`of` and `trackBy`, become `ngForOf` and `ngForTrackBy`, respectively.
|
||||
* The microsyntax parser takes `of` and `trackBy`, title-cases them (`of` -> `Of`, `trackBy` -> `TrackBy`),
|
||||
and prefixes them with the directive's attribute name (`ngFor`), yielding the names `ngForOf` and `ngForTrackBy`.
|
||||
Those are the names of two `NgFor` _input properties_ .
|
||||
That's how the directive learns that the list is `heroes` and the track-by function is `trackById`.
|
||||
|
||||
* As the `NgFor` directive loops through the list, it sets and resets properties of its own _context_ object.
|
||||
These properties can include, but aren't limited to, `index`, `odd`, and a special property
|
||||
named `$implicit`.
|
||||
These properties include `index` and `odd` and a special property named `$implicit`.
|
||||
|
||||
* The `let-i` and `let-odd` variables were defined as `let i=index` and `let odd=odd`.
|
||||
Angular sets them to the current value of the context's `index` and `odd` properties.
|
||||
|
||||
* The context property for `let-hero` wasn't specified.
|
||||
Its intended source is implicit.
|
||||
Angular sets `let-hero` to the value of the context's `$implicit` property,
|
||||
Angular sets `let-hero` to the value of the context's `$implicit` property
|
||||
which `NgFor` has initialized with the hero for the current iteration.
|
||||
|
||||
* The [`NgFor` API guide](api/common/NgForOf "API: NgFor")
|
||||
* The [API guide](api/common/NgForOf "API: NgFor")
|
||||
describes additional `NgFor` directive properties and context properties.
|
||||
|
||||
* The `NgForOf` directive implements `NgFor`. Read more about additional `NgForOf` directive properties and context properties in the [NgForOf API reference](api/common/NgForOf).
|
||||
|
||||
### Writing your own structural directives
|
||||
|
||||
These microsyntax mechanisms are also available to you when you write your own structural directives.
|
||||
For example, microsyntax in Angular allows you to write `<div *ngFor="let item of items">{{item}}</div>`
|
||||
instead of `<ng-template ngFor [ngForOf]="items"><div>{{item}}</div></ng-template`.
|
||||
The following sections provide detailed information on constraints, grammar,
|
||||
and translation of microsyntax.
|
||||
|
||||
### Constraints
|
||||
|
||||
Microsyntax must meet the following requirements:
|
||||
|
||||
- It must be known ahead of time so that IDEs can parse it without knowing the underlying semantics of the directive or what directives are present.
|
||||
- It must translate to key-value attributes in the DOM.
|
||||
|
||||
### Grammar
|
||||
|
||||
When you write your own structural directives, use the following grammar:
|
||||
|
||||
```
|
||||
*:prefix="( :let | :expression ) (';' | ',')? ( :let | :as | :keyExp )*"
|
||||
```
|
||||
|
||||
The following tables describe each portion of the microsyntax grammar.
|
||||
|
||||
<!-- What should I put in the table headers? -->
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<th></th>
|
||||
<th></th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>prefix</code></td>
|
||||
<td>HTML attribute key</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>key</code></td>
|
||||
<td>HTML attribute key</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>local</code></td>
|
||||
<td>local variable name used in the template</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>export</code></td>
|
||||
<td>value exported by the directive under a given name</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>expression</code></td>
|
||||
<td>standard Angular expression</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<!-- The items in this table seem different. Is there another name for how we should describe them? -->
|
||||
<table>
|
||||
<tr>
|
||||
<th></th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="3"><code>keyExp = :key ":"? :expression ("as" :local)? ";"? </code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="3"><code>let = "let" :local "=" :export ";"?</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="3"><code>as = :export "as" :local ";"?</code></td>
|
||||
</tr>
|
||||
</table>
|
||||
* `NgFor` is implemented by the `NgForOf` directive. Read more about additional `NgForOf` directive properties and context properties [NgForOf API reference](api/common/NgForOf).
|
||||
|
||||
|
||||
### Translation
|
||||
|
||||
A microsyntax is translated to the normal binding syntax as follows:
|
||||
|
||||
<!-- What to put in the table headers below? Are these correct?-->
|
||||
<table>
|
||||
<tr>
|
||||
<th>Microsyntax</th>
|
||||
<th>Translation</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>prefix</code> and naked <code>expression</code></td>
|
||||
<td><code>[prefix]="expression"</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>keyExp</code></td>
|
||||
<td><code>[prefixKey] "expression"
|
||||
(let-prefixKey="export")</code>
|
||||
<br />
|
||||
Notice that the <code>prefix</code>
|
||||
is added to the <code>key</code>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>let</code></td>
|
||||
<td><code>let-local="export"</code></td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
### Microsyntax examples
|
||||
|
||||
The following table demonstrates how Angular desugars microsyntax.
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<th>Microsyntax</th>
|
||||
<th>Desugared</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>*ngFor="let item of [1,2,3]"</code></td>
|
||||
<td><code><ng-template ngFor let-item [ngForOf]="[1,2,3]"></code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>*ngFor="let item of [1,2,3] as items; trackBy: myTrack; index as i"</code></td>
|
||||
<td><code><ng-template ngFor let-item [ngForOf]="[1,2,3]" let-items="ngForOf" [ngForTrackBy]="myTrack" let-i="index"></code>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>*ngIf="exp"</code></td>
|
||||
<td><code><ng-template [ngIf]="exp"></code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>*ngIf="exp as value"</code></td>
|
||||
<td><code><ng-template [ngIf]="exp" let-value="ngIf"></code></td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
These microsyntax mechanisms are available to you when you write your own structural directives.
|
||||
Studying the
|
||||
[source code for `NgIf`](https://github.com/angular/angular/blob/master/packages/common/src/directives/ng_if.ts "Source: NgIf")
|
||||
and [`NgForOf`](https://github.com/angular/angular/blob/master/packages/common/src/directives/ng_for_of.ts "Source: NgForOf")
|
||||
is a great way to learn more.
|
||||
|
||||
|
||||
|
||||
{@a template-input-variable}
|
||||
|
||||
|
||||
{@a template-input-variables}
|
||||
|
||||
|
||||
## Template input variable
|
||||
### Template input variable
|
||||
|
||||
A _template input variable_ is a variable whose value you can reference _within_ a single instance of the template.
|
||||
There are several such variables in this example: `hero`, `i`, and `odd`.
|
||||
@ -472,7 +346,7 @@ variable as the `hero` declared as `#hero`.
|
||||
{@a one-per-element}
|
||||
|
||||
|
||||
## One structural directive per host element
|
||||
### One structural directive per host element
|
||||
|
||||
Someday you'll want to repeat a block of HTML but only when a particular condition is true.
|
||||
You'll _try_ to put both an `*ngFor` and an `*ngIf` on the same host element.
|
||||
|
@ -1796,7 +1796,7 @@ Though `@Input()` and `@Output()` often appear together in apps, you can use
|
||||
them separately. If the nested
|
||||
component is such that it only needs to send data to its parent, you wouldn't
|
||||
need an `@Input()`, only an `@Output()`. The reverse is also true in that if the
|
||||
child only needs to receive data from the parent, you'd only need `@Input()`.
|
||||
child only needs to receive data from the parent, you'd only neeed `@Input()`.
|
||||
|
||||
</div>
|
||||
|
||||
@ -2063,14 +2063,14 @@ to declare inputs and outputs, you can identify
|
||||
members in the `inputs` and `outputs` arrays
|
||||
of the directive metadata, as in this example:
|
||||
|
||||
<code-example path="inputs-outputs/src/app/in-the-metadata/in-the-metadata.component.ts" region="metadata" header="src/app/in-the-metadata/in-the-metadata.component.ts" linenums="false">
|
||||
<code-example path="inputs-outputs/src/app/in-the-metadata/in-the-metadata.component.ts" region="metadata" header="src/app/app.component.html" linenums="false">
|
||||
</code-example>
|
||||
|
||||
While declaring `inputs` and `outputs` in the `@Directive` and `@Component`
|
||||
metadata is possible, it is a better practice to use the `@Input()` and `@Output()`
|
||||
class decorators instead, as follows:
|
||||
|
||||
<code-example path="inputs-outputs/src/app/input-output/input-output.component.ts" region="input-output" header="src/app/input-output/input-output.component.ts" linenums="false">
|
||||
<code-example path="inputs-outputs/src/app/input-output/input-output.component.ts" region="input-output" header="src/app/app.component.html" linenums="false">
|
||||
</code-example>
|
||||
|
||||
See the [Decorate input and output properties](guide/styleguide#decorate-input-and-output-properties) section of the
|
||||
@ -2104,7 +2104,7 @@ offer a solution.
|
||||
Alias inputs and outputs in the metadata using a colon-delimited (`:`) string with
|
||||
the directive property name on the left and the public alias on the right:
|
||||
|
||||
<code-example path="inputs-outputs/src/app/aliasing/aliasing.component.ts" region="alias" header="src/app/aliasing/aliasing.component.ts" linenums="false">
|
||||
<code-example path="inputs-outputs/src/app/aliasing/aliasing.component.ts" region="alias" header="src/app/app.component.html" linenums="false">
|
||||
</code-example>
|
||||
|
||||
|
||||
@ -2112,7 +2112,7 @@ the directive property name on the left and the public alias on the right:
|
||||
|
||||
You can specify the alias for the property name by passing the alias name to the `@Input()`/`@Output()` decorator. The internal name remains as usual.
|
||||
|
||||
<code-example path="inputs-outputs/src/app/aliasing/aliasing.component.ts" region="alias-input-output" header="src/app/aliasing/aliasing.component.ts" linenums="false">
|
||||
<code-example path="inputs-outputs/src/app/aliasing/aliasing.component.ts" region="alias-input-output" header="src/app/app.component.html" linenums="false">
|
||||
</code-example>
|
||||
|
||||
|
||||
@ -2273,27 +2273,3 @@ the component.
|
||||
</code-example>
|
||||
|
||||
The `$any()` cast function works anywhere in a binding expression where a method call is valid.
|
||||
|
||||
## SVG in templates
|
||||
|
||||
It is possible to use SVG as valid templates in Angular. All of the template syntax below is
|
||||
applicable to both SVG and HTML. Learn more in the SVG [1.1](https://www.w3.org/TR/SVG11/) and
|
||||
[2.0](https://www.w3.org/TR/SVG2/) specifications.
|
||||
|
||||
Why would you use SVG as template, instead of simply adding it as image to your application?
|
||||
|
||||
When you use an SVG as the template, you are able to use directives and bindings just like with HTML
|
||||
templates. This means that you will be able to dynamically generate interactive graphics.
|
||||
|
||||
Refer to the sample code snippet below for a syntax example:
|
||||
|
||||
<code-example path="template-syntax/src/app/svg.component.ts" header="src/app/svg.component.ts">
|
||||
</code-example>
|
||||
|
||||
Add the below code to your `svg.component.svg` file:
|
||||
|
||||
<code-example path="template-syntax/src/app/svg.component.svg" header="src/app/svg.component.svg">
|
||||
</code-example>
|
||||
|
||||
Here you can see the use of a `click()` event binding and the property binding syntax
|
||||
(`[attr.fill]="fillColor"`).
|
||||
|
@ -868,8 +868,6 @@ As of Angular version 8, lazy loading code can be accomplished simply by using t
|
||||
|
||||
The service uses the `import()` method to load your bundled AngularJS application lazily. This decreases the initial bundle size of your application as you're not loading code your user doesn't need yet. You also need to provide a way to _bootstrap_ the application manually after it has been loaded. AngularJS provides a way to manually bootstrap an application using the [angular.bootstrap()](https://docs.angularjs.org/api/ng/function/angular.bootstrap) method with a provided HTML element. Your AngularJS app should also expose a `bootstrap` method that bootstraps the AngularJS app.
|
||||
|
||||
To ensure any necessary teardown is triggered in the AngularJS app, such as removal of global listeners, you also implement a method to call the `$rootScope.destroy()` method.
|
||||
|
||||
<code-example path="upgrade-lazy-load-ajs/src/app/angularjs-app/index.ts" header="angularjs-app">
|
||||
</code-example>
|
||||
|
||||
@ -888,7 +886,7 @@ In your Angular application, you need a component as a placeholder for your Angu
|
||||
<code-example path="upgrade-lazy-load-ajs/src/app/angular-js/angular-js.component.ts" header="src/app/angular-js/angular-js.component.ts">
|
||||
</code-example>
|
||||
|
||||
When the Angular Router matches a route that uses AngularJS, the `AngularJSComponent` is rendered, and the content is rendered within the AngularJS [`ng-view`](https://docs.angularjs.org/api/ngRoute/directive/ngView) directive. When the user navigates away from the route, the `$rootScope` is destroyed on the AngularJS application.
|
||||
When the Angular Router matches a route that uses AngularJS, the `AngularJSComponent` is rendered, and the content is rendered within the AngularJS [`ng-view`](https://docs.angularjs.org/api/ngRoute/directive/ngView) directive.
|
||||
|
||||
### Configure a custom route matcher for AngularJS routes
|
||||
|
||||
|
@ -27,7 +27,7 @@ Running this command will:
|
||||
// Create a new
|
||||
const worker = new Worker('./app.worker', { type: 'module' });
|
||||
worker.onmessage = ({ data }) => {
|
||||
console.log(`page got message: ${data}`);
|
||||
console.log('page got message: $\{data\}');
|
||||
};
|
||||
worker.postMessage('hello');
|
||||
} else {
|
||||
|
@ -157,7 +157,7 @@ The JSON schemas that the define the options and defaults for each of these defa
|
||||
|
||||
### Alternate build configurations
|
||||
|
||||
By default, a `production` configuration is defined, and the `ng build` command has `--prod` option that builds using this configuration. The `production` configuration sets defaults that optimize the app in a number of ways, such as bundling files, minimizing excess whitespace, removing comments and dead code, and rewriting code to use short, cryptic names ("minification").
|
||||
By default, a `production` configuration is defined, and the `ng build` command has `--prod` option that builds using this configuration. The `production` configuration sets defaults that optimize the app in a number of ways, such bundling files, minimizing excess whitespace, removing comments and dead code, and rewriting code to use short, cryptic names ("minification").
|
||||
|
||||
You can define and name additional alternate configurations (such as `stage`, for instance) appropriate to your development process. Some examples of different build configurations are `stable`, `archive` and `next` used by AIO itself, and the individual locale-specific configurations required for building localized versions of an app. For details, see [Internationalization (i18n)](guide/i18n#merge-aot).
|
||||
|
||||
@ -227,107 +227,3 @@ The following example uses the `ignore` field to exclude certain files in the as
|
||||
{ "glob": "**/*", "input": "src/assets/", "ignore": ["**/*.svg"], "output": "/assets/" },
|
||||
]
|
||||
</code-example>
|
||||
|
||||
{@a style-script-config}
|
||||
|
||||
### Styles and scripts configuration
|
||||
|
||||
An array entry for the `styles` and `scripts` options can be a simple path string, or an object that points to an extra entry-point file.
|
||||
The associated builder will load that file and its dependencies as a separate bundle during the build.
|
||||
With a configuration object, you have the option of naming the bundle for the entry point, using a `bundleName` field.
|
||||
|
||||
The bundle is injected by default, but you can set `inject` to false to exclude the bundle from injection.
|
||||
For example, the following object values create and name a bundle that contains styles and scripts, and excludes it from injection:
|
||||
|
||||
<code-example format="." language="json" linenums="false">
|
||||
|
||||
"styles": [
|
||||
{ "input": "src/external-module/styles.scss", "inject": false, "bundleName": "external-module" }
|
||||
],
|
||||
"scripts": [
|
||||
{ "input": "src/external-module/main.js", "inject": false, "bundleName": "external-module" }
|
||||
]
|
||||
|
||||
</code-example>
|
||||
|
||||
You can mix simple and complex file references for styles and scripts.
|
||||
|
||||
<code-example format="." language="json" linenums="false">
|
||||
|
||||
"styles": [
|
||||
"src/styles.css",
|
||||
"src/more-styles.css",
|
||||
{ "input": "src/lazy-style.scss", "inject": false },
|
||||
{ "input": "src/pre-rename-style.scss", "bundleName": "renamed-style" },
|
||||
]
|
||||
|
||||
</code-example>
|
||||
|
||||
{@a style-preprocessor}
|
||||
|
||||
#### Style preprocessor options
|
||||
|
||||
In Sass and Stylus you can make use of the `includePaths` functionality for both component and global styles, which allows you to add extra base paths that will be checked for imports.
|
||||
|
||||
To add paths, use the `stylePreprocessorOptions` option:
|
||||
|
||||
<code-example format="." language="json" linenums="false">
|
||||
|
||||
"stylePreprocessorOptions": {
|
||||
"includePaths": [
|
||||
"src/style-paths"
|
||||
]
|
||||
}
|
||||
|
||||
</code-example>
|
||||
|
||||
Files in that folder, such as `src/style-paths/_variables.scss`, can be imported from anywhere in your project without the need for a relative path:
|
||||
|
||||
```ts
|
||||
// src/app/app.component.scss
|
||||
// A relative path works
|
||||
@import '../style-paths/variables';
|
||||
// But now this works as well
|
||||
@import 'variables';
|
||||
```
|
||||
|
||||
Note that you will also need to add any styles or scripts to the `test` builder if you need them for unit tests.
|
||||
See also [Using runtime-global libraries inside your app](guide/using-libraries#using-runtime-global-libraries-inside-your-app).
|
||||
|
||||
|
||||
{@a optimize-and-srcmap}
|
||||
|
||||
### Optimization and source map configuration
|
||||
|
||||
The `optimization` and `sourceMap` command options are simple Boolean flags.
|
||||
You can supply an object as a configuration value for either of these to provide more detailed instruction.
|
||||
|
||||
* The flag `--optimization="true"` applies to both scripts and styles. You can supply a value such as the following to apply optimization to one or the other:
|
||||
|
||||
<code-example format="." language="json" linenums="false">
|
||||
|
||||
"optimization": { "scripts": true, "styles": false }
|
||||
|
||||
</code-example>
|
||||
|
||||
* The flag `--sourceMap="true"` outputs source maps for both scripts and styles.
|
||||
You can configure the option to apply to one or the other.
|
||||
You can also choose to output hidden source maps, or resolve vendor package source maps.
|
||||
For example:
|
||||
|
||||
<code-example format="." language="json" linenums="false">
|
||||
|
||||
"sourceMaps": { "scripts": true, "styles": false, "hidden": true, "vendor": true }
|
||||
|
||||
</code-example>
|
||||
|
||||
<div class="alert is-helpful">
|
||||
|
||||
When using hidden source maps, source maps will not be referenced in the bundle.
|
||||
These are useful if you only want source maps to map error stack traces in error reporting tools,
|
||||
but don't want to expose your source maps in the browser developer tools.
|
||||
|
||||
For [Universal](guide/glossary#universal), you can reduce the code rendered in the HTML page by
|
||||
setting styles optimization to `true` and styles source maps to `false`.
|
||||
|
||||
</div>
|
||||
|
@ -34,12 +34,23 @@
|
||||
"url": "https://dev.to/t/angular",
|
||||
"rev": true,
|
||||
"title": "DEV Community"
|
||||
},
|
||||
"angular-in-depth" : {
|
||||
"desc": "The place where advanced Angular concepts are explained",
|
||||
"url": "https://blog.angularindepth.com",
|
||||
}
|
||||
}
|
||||
},
|
||||
"Groups": {
|
||||
"order": 2,
|
||||
"resources": {
|
||||
"sldkjfslkjfslkjdsklj": {
|
||||
"desc": "Meetup in Barcelona, Spain. Express your motivations, share your ideas and play together creating awesome things in team.",
|
||||
"rev": true,
|
||||
"title": "Angular In Depth"
|
||||
"title": "Angular Beers",
|
||||
"url": "http://www.meetup.com/AngularJS-Beers/"
|
||||
},
|
||||
"sldkjfslkjfslkjdskzzzlj": {
|
||||
"desc": "Angular Conferences and Angular Camps in Barcelona, Spain.",
|
||||
"rev": true,
|
||||
"title": "Angular Camp",
|
||||
"url": "http://angularcamp.org/"
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -51,7 +62,7 @@
|
||||
"logo": "",
|
||||
"rev": true,
|
||||
"title": "Adventures in Angular",
|
||||
"url": "https://devchat.tv/adv-in-angular/"
|
||||
"url": "https://devchat.tv/adventures-in-angular"
|
||||
},
|
||||
"sdlkfjsldfkj": {
|
||||
"desc": "Weekly video podcast hosted by Jeff Whelpley with all the latest and greatest happenings in the wild world of Angular.",
|
||||
@ -66,6 +77,13 @@
|
||||
"rev": true,
|
||||
"title": "Happy Angular Podcast",
|
||||
"url": "https://happy-angular.de/"
|
||||
},
|
||||
"sldkfjsldjf": {
|
||||
"desc": "The live broadcast podcast all about JavaScript",
|
||||
"logo": "",
|
||||
"rev": true,
|
||||
"title": "Javascript Air",
|
||||
"url": "https://javascriptair.com/"
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -77,26 +95,40 @@
|
||||
"Cross-Platform Development": {
|
||||
"order": 5,
|
||||
"resources": {
|
||||
"a2b": {
|
||||
"desc": "Angular and React Native to build applications for Android and iOS",
|
||||
"logo": "",
|
||||
"rev": true,
|
||||
"title": "ReactNative",
|
||||
"url": "http://angular.github.io/react-native-renderer/"
|
||||
},
|
||||
"a3b": {
|
||||
"desc": "Ionic offers a library of mobile-optimized HTML, CSS and JS components and tools for building highly interactive native and progressive web apps.",
|
||||
"logo": "http://ionicframework.com/img/ionic-logo-white.svg",
|
||||
"rev": true,
|
||||
"title": "Ionic",
|
||||
"url": "https://ionicframework.com/docs"
|
||||
"url": "http://ionicframework.com/docs/v2/"
|
||||
},
|
||||
"a4b": {
|
||||
"desc": "Electron Platform for Angular.",
|
||||
"logo": "",
|
||||
"rev": true,
|
||||
"title": "Electron",
|
||||
"url": "https://github.com/maximegris/angular-electron"
|
||||
"url": "http://github.com/angular/angular-electron"
|
||||
},
|
||||
"ab": {
|
||||
"desc": "NativeScript is how you build cross-platform, native iOS and Android apps with Angular and TypeScript. Get 100% access to native APIs via JavaScript and reuse of packages from NPM, CocoaPods and Gradle. Open source and backed by Telerik.",
|
||||
"logo": "",
|
||||
"rev": true,
|
||||
"title": "NativeScript",
|
||||
"url": "https://docs.nativescript.org/angular/start/introduction"
|
||||
"url": "https://github.com/NativeScript/nativescript-angular"
|
||||
},
|
||||
"ab5": {
|
||||
"desc": "An Universal Windows App (uwp) powered by Angular",
|
||||
"logo": "",
|
||||
"rev": true,
|
||||
"title": "Windows (UWP)",
|
||||
"url": "http://github.com/preboot/angular2-universal-windows-app"
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -107,13 +139,7 @@
|
||||
"desc": "Reactive Extensions for Angular",
|
||||
"rev": true,
|
||||
"title": "ngrx",
|
||||
"url": "https://ngrx.io/"
|
||||
},
|
||||
"ngxs": {
|
||||
"desc": "NGXS is a state management pattern + library for Angular. NGXS is modeled after the CQRS pattern popularly implemented in libraries like Redux and NgRx but reduces boilerplate by using modern TypeScript features such as classes and decorators.",
|
||||
"rev": true,
|
||||
"title": "NGXS",
|
||||
"url": "https://ngxs.io/"
|
||||
"url": "http://github.com/ngrx"
|
||||
},
|
||||
"ab": {
|
||||
"desc": "The official library for Firebase and Angular",
|
||||
@ -127,14 +153,21 @@
|
||||
"logo": "http://www.angular-meteor.com/images/logo.png",
|
||||
"rev": true,
|
||||
"title": "Meteor",
|
||||
"url": "https://github.com/urigo/angular-meteor"
|
||||
"url": "http://www.angular-meteor.com/angular2"
|
||||
},
|
||||
"ab3": {
|
||||
"desc": "Apollo is a data stack for modern apps, built with GraphQL.",
|
||||
"logo": "http://docs.apollostack.com/logo/large.png",
|
||||
"rev": true,
|
||||
"title": "Apollo",
|
||||
"url": "https://www.apollographql.com/docs/angular/"
|
||||
"url": "http://docs.apollostack.com/apollo-client/angular2.html"
|
||||
},
|
||||
"ab4": {
|
||||
"desc": "Angular Commerce is a solution for building modern e-commerce applications with power of Google Firebase. Set of components is design agnostic and allows to easily extend functionality.",
|
||||
"logo": "",
|
||||
"rev": true,
|
||||
"title": "AngularCommerce",
|
||||
"url": "https://github.com/NodeArt/angular-commerce"
|
||||
},
|
||||
"ngx-api-utils": {
|
||||
"desc": "ngx-api-utils is a lean library of utilities and helpers to quickly integrate any HTTP API (REST, Ajax, and any other) with Angular.",
|
||||
@ -186,6 +219,12 @@
|
||||
"Tooling": {
|
||||
"order": 2,
|
||||
"resources": {
|
||||
"-KLIzHfBEr1qMMUDxfq3": {
|
||||
"desc": "Generate an Angular CRUD application from an existing database schema",
|
||||
"rev": true,
|
||||
"title": "Celerio Angular Quickstart",
|
||||
"url": "https://github.com/jaxio/celerio-angular-quickstart"
|
||||
},
|
||||
"a1": {
|
||||
"desc": "A Google Chrome Dev Tools extension for debugging Angular applications.",
|
||||
"logo": "https://augury.angular.io/images/augury-logo.svg",
|
||||
@ -220,6 +259,13 @@
|
||||
"title": "Codelyzer",
|
||||
"url": "https://github.com/mgechev/codelyzer"
|
||||
},
|
||||
"e1": {
|
||||
"desc": "This package provides facilities for developers building Angular applications on ASP.NET.",
|
||||
"logo": "",
|
||||
"rev": true,
|
||||
"title": "Universal for ASP.NET",
|
||||
"url": "https://github.com/aspnet/nodeservices"
|
||||
},
|
||||
"f1": {
|
||||
"desc": "This tool generates dedicated documentation for Angular applications.",
|
||||
"logo": "",
|
||||
@ -227,6 +273,13 @@
|
||||
"title": "Compodoc",
|
||||
"url": "https://github.com/compodoc/compodoc"
|
||||
},
|
||||
"ncg": {
|
||||
"desc": "Generate several types of CRUD apps complete with e2e testing using template-sets for Angular, Material Design, Bootstrap, Kendo UI, Ionic, ...",
|
||||
"logo": "https://avatars3.githubusercontent.com/u/27976684",
|
||||
"rev": true,
|
||||
"title": "NinjaCodeGen - Angular CRUD Generator",
|
||||
"url": "https://ninjaCodeGen.com"
|
||||
},
|
||||
"angular-playground": {
|
||||
"desc": "UI development environment for building, testing, and documenting Angular applications.",
|
||||
"rev": true,
|
||||
@ -277,6 +330,12 @@
|
||||
"title": "Clarity Design System",
|
||||
"url": "https://vmware.github.io/clarity/"
|
||||
},
|
||||
"-KLIzI9BTvvP_hUwutXk": {
|
||||
"desc": "UI components for Angular using Semantic UI",
|
||||
"rev": true,
|
||||
"title": "Semantic UI",
|
||||
"url": "https://github.com/vladotesanovic/ngSemantic"
|
||||
},
|
||||
"-KMVB8P4TDfht8c0L1AE": {
|
||||
"desc": "The Angular version of the Angular UI Bootstrap library. This library is being built from scratch in Typescript using the Bootstrap 4 CSS framework.",
|
||||
"rev": true,
|
||||
@ -461,6 +520,12 @@
|
||||
"Books": {
|
||||
"order": 1,
|
||||
"resources": {
|
||||
"-KLI8vJ0ZkvWhqPembZ7": {
|
||||
"desc": "A guide that helps developers get up to speed quickly on Angular and its accompanying technologies.",
|
||||
"rev": true,
|
||||
"title": "How to Get Started and Productive in Angular Fast",
|
||||
"url": "http://www.amazon.com/How-Started-Productive-Angular-Fast-ebook/dp/B01D3B0ET4/ref=sr_1_1_twi_kin_2?ie=UTF8&qid=1462381159&sr=8-1"
|
||||
},
|
||||
"-KLIzGEp8Mh5W-FkiQnL": {
|
||||
"desc": "Your quick, no-nonsense guide to building real-world apps with Angular",
|
||||
"rev": true,
|
||||
@ -471,7 +536,25 @@
|
||||
"desc": "More than 15 books from O'Reilly about Angular",
|
||||
"rev": true,
|
||||
"title": "O'Reilly Media",
|
||||
"url": "https://ssearch.oreilly.com/?q=angular"
|
||||
"url": "https://ssearch.oreilly.com/?q=angular+2&x=0&y=0"
|
||||
},
|
||||
"8ab": {
|
||||
"desc": "This books shows all the steps necessary for the development of SPA (Single Page Application) applications with the brand new Angular",
|
||||
"rev": true,
|
||||
"title": "Practical Angular",
|
||||
"url": "https://leanpub.com/practical-angular-2"
|
||||
},
|
||||
"a2b": {
|
||||
"desc": "Publications and books from Manning about Angular",
|
||||
"rev": true,
|
||||
"title": "Manning Publications",
|
||||
"url": "https://www.manning.com/search?q=angular"
|
||||
},
|
||||
"a4b": {
|
||||
"desc": "From getting started with the Angular toolchain to writing applications with scalable front end architectures, this book walks you through everything you need to know.",
|
||||
"rev": true,
|
||||
"title": "Rangle's Angular Training Book",
|
||||
"url": "http://ngcourse.rangle.io/"
|
||||
},
|
||||
"a5b": {
|
||||
"desc": "The in-depth, complete, and up-to-date book on Angular. Become an Angular expert today.",
|
||||
@ -479,6 +562,12 @@
|
||||
"title": "ng-book",
|
||||
"url": "https://www.ng-book.com/2/"
|
||||
},
|
||||
"a6b": {
|
||||
"desc": "A Practical Introduction to the new Web Development Platform Angular",
|
||||
"rev": true,
|
||||
"title": "Angular Book",
|
||||
"url": "https://leanpub.com/angular2-book"
|
||||
},
|
||||
"a7b": {
|
||||
"desc": "This ebook will help you getting the philosophy of the framework: what comes from 1.x, what has been introduced and why",
|
||||
"rev": true,
|
||||
@ -489,13 +578,13 @@
|
||||
"desc": "More than 10 books from Packt Publishing about Angular",
|
||||
"rev": true,
|
||||
"title": "Packt Publishing",
|
||||
"url": "https://www.packtpub.com/catalogsearch/result/?q=angular"
|
||||
"url": "https://www.packtpub.com/all/?search=angular%202#"
|
||||
},
|
||||
"cnoring-rxjs-fundamentals": {
|
||||
"desc": "A free book that covers all facets of working with Rxjs from your first Observable to how to make your code run at optimal speed with Schedulers.",
|
||||
"rev": true,
|
||||
"title": "RxJS Ultimate",
|
||||
"url": "https://chrisnoring.gitbooks.io/rxjs-5-ultimate/content/"
|
||||
"title": "RxJS 5 Ultimate",
|
||||
"url": "https://www.gitbook.com/book/chrisnoring/rxjs-5-ultimate/details"
|
||||
},
|
||||
"vsavkin-angular-router": {
|
||||
"desc": "This book is a comprehensive guide to the Angular router written by its designer. The book explores the library in depth, including the mental model, design constraints, subtleties of the API.",
|
||||
@ -553,6 +642,18 @@
|
||||
"title": "Academia Binaria (español)",
|
||||
"url": "http://academia-binaria.com/"
|
||||
},
|
||||
"-KLIzIOgdPXzI4LMOzYP": {
|
||||
"desc": "In this course, you will learn the features listed above and so much more. This amazing Angular tutorial will cover the fundamentals of Angular (you don’t even need to know Angular), TypeScript, and introduction to the programming concepts such as conditions, arrays, functions, directives, pipes, etc.",
|
||||
"rev": true,
|
||||
"title": "Eduonix Angular Fundamentals",
|
||||
"url": "https://www.eduonix.com/courses/Web-Development/angular-2-fundamentals-for-web-developers"
|
||||
},
|
||||
"-KMUuOWwciL_S_o0fzFO": {
|
||||
"desc": "The ngMigrate project is brought to you by Todd Motto, a Developer Advocate at Telerik, spreading the good word of Kendo UI, NativeScript and Angular & AngularJS. You can follow him on Twitter for questions, or even requests about this guide.",
|
||||
"rev": true,
|
||||
"title": "ngMigrate",
|
||||
"url": "http://ngmigrate.telerik.com/"
|
||||
},
|
||||
"-KN3uNQvxifu26D6WKJW": {
|
||||
"category": "Education",
|
||||
"desc": "Create the future of web applications by taking Angular for a test drive.",
|
||||
@ -573,14 +674,14 @@
|
||||
"logo": "",
|
||||
"rev": true,
|
||||
"title": "Pluralsight",
|
||||
"url": "https://www.pluralsight.com/paths/angular"
|
||||
"url": "https://www.pluralsight.com/search?q=angular+2&categories=all"
|
||||
},
|
||||
"ab3": {
|
||||
"desc": "Angular courses hosted by Udemy",
|
||||
"logo": "",
|
||||
"rev": true,
|
||||
"title": "Udemy",
|
||||
"url": "https://www.udemy.com/courses/search/?q=angular"
|
||||
"url": "https://www.udemy.com/courses/search/?ref=home&src=ukw&q=angular+2&lang=en"
|
||||
},
|
||||
"ab4": {
|
||||
"desc": "Angular Fundamentals and advanced topics focused on Redux Style Angular Applications",
|
||||
@ -594,7 +695,13 @@
|
||||
"logo": "",
|
||||
"rev": true,
|
||||
"title": "Frontend Masters",
|
||||
"url": "https://frontendmasters.com/courses/angular-core/"
|
||||
"url": "https://frontendmasters.com/courses/angular-2/"
|
||||
},
|
||||
"ac6": {
|
||||
"desc": "French language Angular course covering TypeScript, ES6, Dependency Injection, Observables, and more.",
|
||||
"rev": true,
|
||||
"title": "Wishtack's Angular Course (francais)",
|
||||
"url": "http://courses.wishtack.com/angular-2/ecmascript-6"
|
||||
},
|
||||
"angular-love": {
|
||||
"desc": "Polish language Angular articles and information",
|
||||
@ -602,6 +709,12 @@
|
||||
"title": "angular.love (Polski)",
|
||||
"url": "http://www.angular.love/"
|
||||
},
|
||||
"angular2forms": {
|
||||
"desc": "Learn about how to use Reactive Forms with Angular.",
|
||||
"rev": true,
|
||||
"title": "Angular Forms: Data Binding and Validation",
|
||||
"url": "https://www.lynda.com/AngularJS-tutorials/Angular-2-Forms-Data-Binding-Validation/461451-2.html"
|
||||
},
|
||||
"learn-angular-fr": {
|
||||
"desc": "French language Angular content.",
|
||||
"rev": true,
|
||||
@ -677,7 +790,7 @@
|
||||
"desc": "Basic and Advanced training across Europe in German",
|
||||
"rev": true,
|
||||
"title": "TheCodeCampus (German)",
|
||||
"url": "https://www.thecodecampus.de/schulungen/angular"
|
||||
"url": "https://www.thecodecampus.de/#!/angularjs"
|
||||
},
|
||||
"-KLIzFhfGKi1xttqJ7Uh": {
|
||||
"desc": "4 day in-depth Angular training in Israel",
|
||||
@ -710,6 +823,13 @@
|
||||
"title": "Angular Boot Camp",
|
||||
"url": "https://angularbootcamp.com"
|
||||
},
|
||||
"ab": {
|
||||
"desc": "With Rangle’s Custom Training, you can cover Angular in comprehensive detail, on your premises or theirs. Learn directly from Angular experts who will tailor course material to suit your specific application needs.",
|
||||
"logo": "",
|
||||
"rev": true,
|
||||
"title": "Rangle.io",
|
||||
"url": "http://rangle.io/services/javascript-training/angular2-training/"
|
||||
},
|
||||
"ab3": {
|
||||
"desc": "Trainings & Code Reviews. We help people to get a deep understanding of different technologies through trainings and code reviews. Our services can be arranged online, making it possible to join in from anywhere in the world, or on-site to get the best experience possible.",
|
||||
"logo": "",
|
||||
@ -724,6 +844,12 @@
|
||||
"title": "Learn Javascript (Russian)",
|
||||
"url": "https://learn.javascript.ru/courses/angular"
|
||||
},
|
||||
"sa200": {
|
||||
"desc": "Free Angular training delivered by SFEIR in France",
|
||||
"rev": true,
|
||||
"title": "SFEIR School (French)",
|
||||
"url": "https://school.sfeir.com/project/sa200/"
|
||||
},
|
||||
"zenika-angular": {
|
||||
"desc": "Angular trainings delivered by Zenika (FRANCE)",
|
||||
"rev": true,
|
||||
|
@ -208,16 +208,16 @@
|
||||
"title": "Component Styles",
|
||||
"tooltip": "Add CSS styles that are specific to a component."
|
||||
},
|
||||
{
|
||||
"url": "guide/dynamic-component-loader",
|
||||
"title": "Dynamic Components",
|
||||
"tooltip": "Load components dynamically."
|
||||
},
|
||||
{
|
||||
"url": "guide/elements",
|
||||
"title": "Angular Elements",
|
||||
"tooltip": "Convert components to Custom Elements."
|
||||
},
|
||||
{
|
||||
"url": "guide/dynamic-component-loader",
|
||||
"title": "Dynamic Components",
|
||||
"tooltip": "Load components dynamically."
|
||||
},
|
||||
{
|
||||
"url": "guide/attribute-directives",
|
||||
"title": "Attribute Directives",
|
||||
@ -456,11 +456,6 @@
|
||||
"title": "Internationalization (i18n)",
|
||||
"tooltip": "Translate the app's template text into multiple languages."
|
||||
},
|
||||
{
|
||||
"url": "guide/accessibility",
|
||||
"title": "Accessibility",
|
||||
"tooltip": "Design apps to be accessible to all users."
|
||||
},
|
||||
{
|
||||
"title": "Service Workers & PWA",
|
||||
"tooltip": "Angular service workers: Controlling caching of application resources.",
|
||||
|
@ -42,7 +42,7 @@ We've seeded this particular app with a top bar—containing the store name
|
||||
<div class="callout is-helpful">
|
||||
<header>StackBlitz tips</header>
|
||||
|
||||
* Log into StackBlitz, so you can save and resume your work. If you have a GitHub account, you can log into StackBlitz with that account. In order to save your progress, first fork the project using the Fork button at the top left, then you'll be able to save your work to your own StackBlitz account by clicking the Save button.
|
||||
* Log into StackBlitz, so you can save and resume your work. If you have a GitHub account, you can log into StackBlitz with that account.
|
||||
* To copy a code example from this tutorial, click the icon at the top right of the code example box, and then paste the code snippet from the clipboard into StackBlitz.
|
||||
* If the StackBlitz preview pane isn't showing what you expect, save and then click the refresh button.
|
||||
* StackBlitz is continually improving, so there may be slight differences in generated code, but the app's behavior will be the same.
|
||||
|
@ -13,11 +13,13 @@ Using the Angular CLI, generate a new component named `heroes`.
|
||||
</code-example>
|
||||
|
||||
The CLI creates a new folder, `src/app/heroes/`, and generates
|
||||
the three files of the `HeroesComponent` along with a test file.
|
||||
the four files of the `HeroesComponent`.
|
||||
|
||||
The `HeroesComponent` class file is as follows:
|
||||
|
||||
<code-example path="toh-pt1/src/app/heroes/heroes.component.ts" region="v1" header="app/heroes/heroes.component.ts (initial version)" linenums="false">
|
||||
<code-example
|
||||
path="toh-pt1/src/app/heroes/heroes.component.ts" region="v1"
|
||||
header="app/heroes/heroes.component.ts (initial version)" linenums="false">
|
||||
</code-example>
|
||||
|
||||
You always import the `Component` symbol from the Angular core library
|
||||
@ -36,13 +38,13 @@ The CLI generated three metadata properties:
|
||||
The [CSS element selector](https://developer.mozilla.org/en-US/docs/Web/CSS/Type_selectors),
|
||||
`'app-heroes'`, matches the name of the HTML element that identifies this component within a parent component's template.
|
||||
|
||||
The `ngOnInit()` is a [lifecycle hook](guide/lifecycle-hooks#oninit).
|
||||
Angular calls `ngOnInit()` shortly after creating a component.
|
||||
The `ngOnInit` is a [lifecycle hook](guide/lifecycle-hooks#oninit).
|
||||
Angular calls `ngOnInit` shortly after creating a component.
|
||||
It's a good place to put initialization logic.
|
||||
|
||||
Always `export` the component class so you can `import` it elsewhere ... like in the `AppModule`.
|
||||
|
||||
### Add a `hero` property
|
||||
### Add a _hero_ property
|
||||
|
||||
Add a `hero` property to the `HeroesComponent` for a hero named "Windstorm."
|
||||
|
||||
@ -52,17 +54,17 @@ Add a `hero` property to the `HeroesComponent` for a hero named "Windstorm."
|
||||
### Show the hero
|
||||
|
||||
Open the `heroes.component.html` template file.
|
||||
Delete the default text generated by the Angular CLI and
|
||||
replace it with a data binding to the new `hero` property.
|
||||
Delete the default text generated by the Angular CLI and
|
||||
replace it with a data binding to the new `hero` property.
|
||||
|
||||
<code-example path="toh-pt1/src/app/heroes/heroes.component.1.html" header="heroes.component.html" region="show-hero-1" linenums="false">
|
||||
</code-example>
|
||||
|
||||
## Show the `HeroesComponent` view
|
||||
## Show the _HeroesComponent_ view
|
||||
|
||||
To display the `HeroesComponent`, you must add it to the template of the shell `AppComponent`.
|
||||
|
||||
Remember that `app-heroes` is the [element selector](#selector) for the `HeroesComponent`.
|
||||
Remember that `app-heroes` is the [element selector](#selector) for the `HeroesComponent`.
|
||||
So add an `<app-heroes>` element to the `AppComponent` template file, just below the title.
|
||||
|
||||
<code-example path="toh-pt1/src/app/app.component.html" header="src/app/app.component.html" linenums="false">
|
||||
@ -100,7 +102,10 @@ The page no longer displays properly because you changed the hero from a string
|
||||
Update the binding in the template to announce the hero's name
|
||||
and show both `id` and `name` in a details layout like this:
|
||||
|
||||
<code-example path="toh-pt1/src/app/heroes/heroes.component.1.html" region="show-hero-2" header="heroes.component.html (HeroesComponent's template)" linenums="false">
|
||||
<code-example
|
||||
path="toh-pt1/src/app/heroes/heroes.component.1.html"
|
||||
region="show-hero-2"
|
||||
header="heroes.component.html (HeroesComponent's template)" linenums="false">
|
||||
</code-example>
|
||||
|
||||
The browser refreshes and displays the hero's information.
|
||||
@ -108,12 +113,14 @@ The browser refreshes and displays the hero's information.
|
||||
## Format with the _UppercasePipe_
|
||||
|
||||
Modify the `hero.name` binding like this.
|
||||
<code-example path="toh-pt1/src/app/heroes/heroes.component.html" header="src/app/heroes/heroes.component.html" region="pipe">
|
||||
<code-example
|
||||
path="toh-pt1/src/app/heroes/heroes.component.html"
|
||||
region="pipe">
|
||||
</code-example>
|
||||
|
||||
The browser refreshes and now the hero's name is displayed in capital letters.
|
||||
|
||||
The word `uppercase` in the interpolation binding,
|
||||
The word `uppercase` in the interpolation binding,
|
||||
right after the pipe operator ( | ),
|
||||
activates the built-in `UppercasePipe`.
|
||||
|
||||
@ -126,7 +133,7 @@ Users should be able to edit the hero name in an `<input>` textbox.
|
||||
|
||||
The textbox should both _display_ the hero's `name` property
|
||||
and _update_ that property as the user types.
|
||||
That means data flows from the component class _out to the screen_ and
|
||||
That means data flow from the component class _out to the screen_ and
|
||||
from the screen _back to the class_.
|
||||
|
||||
To automate that data flow, setup a two-way data binding between the `<input>` form element and the `hero.name` property.
|
||||
@ -139,7 +146,7 @@ Refactor the details area in the `HeroesComponent` template so it looks like thi
|
||||
|
||||
</code-example>
|
||||
|
||||
**[(ngModel)]** is Angular's two-way data binding syntax.
|
||||
**[(ngModel)]** is Angular's two-way data binding syntax.
|
||||
|
||||
Here it binds the `hero.name` property to the HTML textbox so that data can flow _in both directions:_ from the `hero.name` property to the textbox, and from the textbox back to the `hero.name`.
|
||||
|
||||
@ -155,7 +162,7 @@ Template parse errors:
|
||||
Can't bind to 'ngModel' since it isn't a known property of 'input'.
|
||||
</code-example>
|
||||
|
||||
Although `ngModel` is a valid Angular directive, it isn't available by default.
|
||||
Although `ngModel` is a valid Angular directive, it isn't available by default.
|
||||
|
||||
It belongs to the optional `FormsModule` and you must _opt-in_ to using it.
|
||||
|
||||
@ -163,7 +170,7 @@ It belongs to the optional `FormsModule` and you must _opt-in_ to using it.
|
||||
|
||||
Angular needs to know how the pieces of your application fit together
|
||||
and what other files and libraries the app requires.
|
||||
This information is called _metadata_.
|
||||
This information is called _metadata_
|
||||
|
||||
Some of the metadata is in the `@Component` decorators that you added to your component classes.
|
||||
Other critical metadata is in [`@NgModule`](guide/ngmodules) decorators.
|
||||
@ -175,7 +182,7 @@ This is where you _opt-in_ to the `FormsModule`.
|
||||
|
||||
### Import _FormsModule_
|
||||
|
||||
Open `AppModule` (`app.module.ts`) and import the `FormsModule` symbol from the `@angular/forms` library.
|
||||
Open `AppModule` (`app.module.ts`) and import the `FormsModule` symbol from the `@angular/forms` library.
|
||||
|
||||
<code-example path="toh-pt1/src/app/app.module.ts" header="app.module.ts (FormsModule symbol import)"
|
||||
region="formsmodule-js-import">
|
||||
@ -183,13 +190,13 @@ Open `AppModule` (`app.module.ts`) and import the `FormsModule` symbol from the
|
||||
|
||||
Then add `FormsModule` to the `@NgModule` metadata's `imports` array, which contains a list of external modules that the app needs.
|
||||
|
||||
<code-example path="toh-pt1/src/app/app.module.ts" header="app.module.ts (@NgModule imports)"
|
||||
<code-example path="toh-pt1/src/app/app.module.ts" header="app.module.ts ( @NgModule imports)"
|
||||
region="ng-imports">
|
||||
</code-example>
|
||||
|
||||
When the browser refreshes, the app should work again. You can edit the hero's name and see the changes reflected immediately in the `<h2>` above the textbox.
|
||||
|
||||
### Declare `HeroesComponent`
|
||||
### Declare _HeroesComponent_
|
||||
|
||||
Every component must be declared in _exactly one_ [NgModule](guide/ngmodules).
|
||||
|
||||
@ -199,11 +206,11 @@ So why did the application work?
|
||||
It worked because the Angular CLI declared `HeroesComponent` in the `AppModule` when it generated that component.
|
||||
|
||||
Open `src/app/app.module.ts` and find `HeroesComponent` imported near the top.
|
||||
<code-example path="toh-pt1/src/app/app.module.ts" header="src/app/app.module.ts" region="heroes-import" >
|
||||
<code-example path="toh-pt1/src/app/app.module.ts" region="heroes-import" >
|
||||
</code-example>
|
||||
|
||||
The `HeroesComponent` is declared in the `@NgModule.declarations` array.
|
||||
<code-example path="toh-pt1/src/app/app.module.ts" header="src/app/app.module.ts" region="declarations">
|
||||
<code-example path="toh-pt1/src/app/app.module.ts" region="declarations">
|
||||
</code-example>
|
||||
|
||||
Note that `AppModule` declares both application components, `AppComponent` and `HeroesComponent`.
|
||||
@ -221,7 +228,7 @@ Your app should look like this <live-example></live-example>. Here are the code
|
||||
<code-pane header="src/app/heroes/heroes.component.html" path="toh-pt1/src/app/heroes/heroes.component.html">
|
||||
</code-pane>
|
||||
|
||||
<code-pane header="src/app/app.module.ts"
|
||||
<code-pane header="src/app/app.module.ts"
|
||||
path="toh-pt1/src/app/app.module.ts">
|
||||
</code-pane>
|
||||
|
||||
@ -231,7 +238,7 @@ Your app should look like this <live-example></live-example>. Here are the code
|
||||
<code-pane header="src/app/app.component.html" path="toh-pt1/src/app/app.component.html">
|
||||
</code-pane>
|
||||
|
||||
<code-pane header="src/app/hero.ts"
|
||||
<code-pane header="src/app/hero.ts"
|
||||
path="toh-pt1/src/app/hero.ts">
|
||||
</code-pane>
|
||||
|
||||
@ -240,10 +247,10 @@ Your app should look like this <live-example></live-example>. Here are the code
|
||||
## Summary
|
||||
|
||||
* You used the CLI to create a second `HeroesComponent`.
|
||||
* You displayed the `HeroesComponent` by adding it to the `AppComponent` shell.
|
||||
* You displayed the `HeroesComponent` by adding it to the `AppComponent` shell.
|
||||
* You applied the `UppercasePipe` to format the name.
|
||||
* You used two-way data binding with the `ngModel` directive.
|
||||
* You learned about the `AppModule`.
|
||||
* You imported the `FormsModule` in the `AppModule` so that Angular would recognize and apply the `ngModel` directive.
|
||||
* You imported the `FormsModule` in the `AppModule` so that Angular would recognize and apply the `ngModel` directive.
|
||||
* You learned the importance of declaring components in the `AppModule`
|
||||
and appreciated that the CLI declared it for you.
|
||||
|
@ -21,17 +21,19 @@ header="src/app/mock-heroes.ts">
|
||||
|
||||
## Displaying heroes
|
||||
|
||||
You're about to display the list of heroes at the top of the `HeroesComponent`.
|
||||
|
||||
Open the `HeroesComponent` class file and import the mock `HEROES`.
|
||||
|
||||
<code-example path="toh-pt2/src/app/heroes/heroes.component.ts" region="import-heroes" header="src/app/heroes/heroes.component.ts (import HEROES)">
|
||||
</code-example>
|
||||
|
||||
In the same file (`HeroesComponent` class), define a component property called `heroes` to expose the `HEROES` array for binding.
|
||||
In the same file (`HeroesComponent` class), define a component property called `heroes` to expose `HEROES` array for binding.
|
||||
|
||||
<code-example path="toh-pt2/src/app/heroes/heroes.component.ts" header="src/app/heroes/heroes.component.ts" region="component">
|
||||
<code-example path="toh-pt2/src/app/heroes/heroes.component.ts" region="component">
|
||||
</code-example>
|
||||
|
||||
### List heroes with `*ngFor`
|
||||
### List heroes with _*ngFor_
|
||||
|
||||
Open the `HeroesComponent` template file and make the following changes:
|
||||
|
||||
@ -45,7 +47,7 @@ Make it look like this:
|
||||
<code-example path="toh-pt2/src/app/heroes/heroes.component.1.html" region="list" header="heroes.component.html (heroes template)" linenums="false">
|
||||
</code-example>
|
||||
|
||||
That shows one hero. To list them all, add an `*ngFor` to the `<li>` to iterate through the list of heroes:
|
||||
Now change the `<li>` to this:
|
||||
|
||||
<code-example path="toh-pt2/src/app/heroes/heroes.component.1.html" region="li">
|
||||
</code-example>
|
||||
@ -53,10 +55,10 @@ That shows one hero. To list them all, add an `*ngFor` to the `<li>` to iterate
|
||||
The [`*ngFor`](guide/template-syntax#ngFor) is Angular's _repeater_ directive.
|
||||
It repeats the host element for each element in a list.
|
||||
|
||||
The syntax in this example is as follows:
|
||||
In this example
|
||||
|
||||
* `<li>` is the host element.
|
||||
* `heroes` holds the mock heroes list from the `HeroesComponent` class, the mock heroes list.
|
||||
* `<li>` is the host element
|
||||
* `heroes` is the list from the `HeroesComponent` class.
|
||||
* `hero` holds the current hero object for each iteration through the list.
|
||||
|
||||
<div class="alert is-important">
|
||||
@ -125,10 +127,9 @@ This is an example of Angular's [event binding](guide/template-syntax#event-bind
|
||||
The parentheses around `click` tell Angular to listen for the `<li>` element's `click` event.
|
||||
When the user clicks in the `<li>`, Angular executes the `onSelect(hero)` expression.
|
||||
|
||||
|
||||
In the next section, define an `onSelect()` method in `HeroesComponent` to
|
||||
display the hero that was defined in the `*ngFor` expression.
|
||||
|
||||
`onSelect()` is a `HeroesComponent` method that you're about to write.
|
||||
Angular calls it with the `hero` object displayed in the clicked `<li>`,
|
||||
the same `hero` defined previously in the `*ngFor` expression.
|
||||
|
||||
### Add the click event handler
|
||||
|
||||
@ -141,11 +142,10 @@ to the component's `selectedHero`.
|
||||
<code-example path="toh-pt2/src/app/heroes/heroes.component.ts" region="on-select" header="src/app/heroes/heroes.component.ts (onSelect)" linenums="false">
|
||||
</code-example>
|
||||
|
||||
### Add a details section
|
||||
### Update the details template
|
||||
|
||||
Currently, you have a list in the component template. To click on a hero on the list
|
||||
and reveal details about that hero, you need a section for the details to render in the
|
||||
template. Add the following to `heroes.component.html` beneath the list section:
|
||||
The template still refers to the component's old `hero` property which no longer exists.
|
||||
Rename `hero` to `selectedHero`.
|
||||
|
||||
<code-example path="toh-pt2/src/app/heroes/heroes.component.html" region="selectedHero-details" header="heroes.component.html (selected hero details)" linenums="false">
|
||||
</code-example>
|
||||
@ -162,7 +162,7 @@ Open the browser developer tools and look in the console for an error message li
|
||||
|
||||
When the app starts, the `selectedHero` is `undefined` _by design_.
|
||||
|
||||
Binding expressions in the template that refer to properties of `selectedHero`—expressions like `{{selectedHero.name}}`—_must fail_ because there is no selected hero.
|
||||
Binding expressions in the template that refer to properties of `selectedHero` — expressions like `{{selectedHero.name}}` — _must fail_ because there is no selected hero.
|
||||
|
||||
|
||||
#### The fix - hide empty details with _*ngIf_
|
||||
@ -192,7 +192,7 @@ The heroes appear in a list and details about the clicked hero appear at the bot
|
||||
|
||||
#### Why it works
|
||||
|
||||
When `selectedHero` is undefined, the `ngIf` removes the hero detail from the DOM. There are no `selectedHero` bindings to consider.
|
||||
When `selectedHero` is undefined, the `ngIf` removes the hero detail from the DOM. There are no `selectedHero` bindings to worry about.
|
||||
|
||||
When the user picks a hero, `selectedHero` has a value and
|
||||
`ngIf` puts the hero detail into the DOM.
|
||||
@ -240,7 +240,7 @@ Here are the code files discussed on this page, including the `HeroesComponent`
|
||||
|
||||
<code-pane header="src/app/mock-heroes.ts" path="toh-pt2/src/app/mock-heroes.ts">
|
||||
</code-pane>
|
||||
|
||||
|
||||
<code-pane header="src/app/heroes/heroes.component.ts" path="toh-pt2/src/app/heroes/heroes.component.ts">
|
||||
</code-pane>
|
||||
|
||||
|
@ -72,7 +72,7 @@ Amend the `@angular/core` import statement to include the `Input` symbol.
|
||||
|
||||
Add a `hero` property, preceded by the `@Input()` decorator.
|
||||
|
||||
<code-example path="toh-pt3/src/app/hero-detail/hero-detail.component.ts" header="src/app/hero-detail/hero-detail.component.ts" region="input-hero" linenums="false">
|
||||
<code-example path="toh-pt3/src/app/hero-detail/hero-detail.component.ts" region="input-hero" linenums="false">
|
||||
</code-example>
|
||||
|
||||
That's the only change you should make to the `HeroDetailComponent` class.
|
||||
|
@ -11,18 +11,18 @@ Components shouldn't fetch or save data directly and they certainly shouldn't kn
|
||||
They should focus on presenting data and delegate data access to a service.
|
||||
|
||||
In this tutorial, you'll create a `HeroService` that all application classes can use to get heroes.
|
||||
Instead of creating that service with `new`,
|
||||
you'll rely on Angular [*dependency injection*](guide/dependency-injection)
|
||||
Instead of creating that service with `new`,
|
||||
you'll rely on Angular [*dependency injection*](guide/dependency-injection)
|
||||
to inject it into the `HeroesComponent` constructor.
|
||||
|
||||
Services are a great way to share information among classes that _don't know each other_.
|
||||
You'll create a `MessageService` and inject it in two places:
|
||||
|
||||
1. in `HeroService` which uses the service to send a message
|
||||
2. in `MessagesComponent` which displays that message
|
||||
1. in `HeroService` which uses the service to send a message.
|
||||
2. in `MessagesComponent` which displays that message.
|
||||
|
||||
|
||||
## Create the `HeroService`
|
||||
## Create the _HeroService_
|
||||
|
||||
Using the Angular CLI, create a service called `hero`.
|
||||
|
||||
@ -30,24 +30,24 @@ Using the Angular CLI, create a service called `hero`.
|
||||
ng generate service hero
|
||||
</code-example>
|
||||
|
||||
The command generates a skeleton `HeroService` class in `src/app/hero.service.ts` as follows:
|
||||
The command generates skeleton `HeroService` class in `src/app/hero.service.ts`
|
||||
The `HeroService` class should look like the following example.
|
||||
|
||||
<code-example path="toh-pt4/src/app/hero.service.1.ts" region="new"
|
||||
header="src/app/hero.service.ts (new service)" linenums="false">
|
||||
</code-example>
|
||||
|
||||
|
||||
### `@Injectable()` services
|
||||
### _@Injectable()_ services
|
||||
|
||||
Notice that the new service imports the Angular `Injectable` symbol and annotates
|
||||
the class with the `@Injectable()` decorator. This marks the class as one that participates in the _dependency injection system_. The `HeroService` class is going to provide an injectable service, and it can also have its own injected dependencies.
|
||||
It doesn't have any dependencies yet, but [it will soon](#inject-message-service).
|
||||
|
||||
The `@Injectable()` decorator accepts a metadata object for the service, the same way the `@Component()` decorator did for your component classes.
|
||||
The `@Injectable()` decorator accepts a metadata object for the service, the same way the `@Component()` decorator did for your component classes.
|
||||
|
||||
### Get hero data
|
||||
|
||||
The `HeroService` could get hero data from anywhere—a web service, local storage, or a mock data source.
|
||||
The `HeroService` could get hero data from anywhere—a web service, local storage, or a mock data source.
|
||||
|
||||
Removing data access from components means you can change your mind about the implementation anytime, without touching any components.
|
||||
They don't know how the service works.
|
||||
@ -56,25 +56,27 @@ The implementation in _this_ tutorial will continue to deliver _mock heroes_.
|
||||
|
||||
Import the `Hero` and `HEROES`.
|
||||
|
||||
<code-example path="toh-pt4/src/app/hero.service.ts" header="src/app/hero.service.ts" region="import-heroes">
|
||||
<code-example path="toh-pt4/src/app/hero.service.ts" region="import-heroes">
|
||||
</code-example>
|
||||
|
||||
Add a `getHeroes` method to return the _mock heroes_.
|
||||
|
||||
<code-example path="toh-pt4/src/app/hero.service.1.ts" header="src/app/hero.service.ts" region="getHeroes">
|
||||
<code-example path="toh-pt4/src/app/hero.service.1.ts" region="getHeroes">
|
||||
</code-example>
|
||||
|
||||
{@a provide}
|
||||
## Provide the `HeroService`
|
||||
|
||||
You must make the `HeroService` available to the dependency injection system
|
||||
before Angular can _inject_ it into the `HeroesComponent` by registering a _provider_. A provider is something that can create or deliver a service; in this case, it instantiates the `HeroService` class to provide the service.
|
||||
You must make the `HeroService` available to the dependency injection system
|
||||
before Angular can _inject_ it into the `HeroesComponent`,
|
||||
as you will do [below](#inject). You do this by registering a _provider_. A provider is something that can create or deliver a service; in this case, it instantiates the `HeroService` class to provide the service.
|
||||
|
||||
To make sure that the `HeroService` can provide this service, register it
|
||||
with the _injector_, which is the object that is responsible for choosing
|
||||
and injecting the provider where the app requires it.
|
||||
Now, you need to make sure that the `HeroService` is registered as the provider of this service.
|
||||
You are registering it with an _injector_, which is the object that is responsible for choosing and injecting the provider where it is required.
|
||||
|
||||
By default, the Angular CLI command `ng generate service` registers a provider with the _root injector_ for your service by including provider metadata, that is `providedIn: 'root'` in the `@Injectable()` decorator.
|
||||
By default, the Angular CLI command `ng generate service` registers a provider with the _root injector_ for your service by including provider metadata in the `@Injectable` decorator.
|
||||
|
||||
If you look at the `@Injectable()` statement right before the `HeroService` class definition, you can see that the `providedIn` metadata value is 'root':
|
||||
|
||||
```
|
||||
@Injectable({
|
||||
@ -82,8 +84,8 @@ By default, the Angular CLI command `ng generate service` registers a provider w
|
||||
})
|
||||
```
|
||||
|
||||
When you provide the service at the root level, Angular creates a single, shared instance of `HeroService` and injects into any class that asks for it.
|
||||
Registering the provider in the `@Injectable` metadata also allows Angular to optimize an app by removing the service if it turns out not to be used after all.
|
||||
When you provide the service at the root level, Angular creates a single, shared instance of `HeroService` and injects into any class that asks for it.
|
||||
Registering the provider in the `@Injectable` metadata also allows Angular to optimize an app by removing the service if it turns out not to be used after all.
|
||||
|
||||
<div class="alert is-helpful">
|
||||
|
||||
@ -113,7 +115,7 @@ Import the `HeroService` instead.
|
||||
|
||||
Replace the definition of the `heroes` property with a simple declaration.
|
||||
|
||||
<code-example path="toh-pt4/src/app/heroes/heroes.component.ts" header="src/app/heroes/heroes.component.ts" region="heroes">
|
||||
<code-example path="toh-pt4/src/app/heroes/heroes.component.ts" region="heroes">
|
||||
</code-example>
|
||||
|
||||
{@a inject}
|
||||
@ -122,24 +124,24 @@ Replace the definition of the `heroes` property with a simple declaration.
|
||||
|
||||
Add a private `heroService` parameter of type `HeroService` to the constructor.
|
||||
|
||||
<code-example path="toh-pt4/src/app/heroes/heroes.component.ts" header="src/app/heroes/heroes.component.ts" region="ctor">
|
||||
<code-example path="toh-pt4/src/app/heroes/heroes.component.ts" region="ctor">
|
||||
</code-example>
|
||||
|
||||
The parameter simultaneously defines a private `heroService` property and identifies it as a `HeroService` injection site.
|
||||
|
||||
When Angular creates a `HeroesComponent`, the [Dependency Injection](guide/dependency-injection) system
|
||||
sets the `heroService` parameter to the singleton instance of `HeroService`.
|
||||
sets the `heroService` parameter to the singleton instance of `HeroService`.
|
||||
|
||||
### Add `getHeroes()`
|
||||
### Add _getHeroes()_
|
||||
|
||||
Create a function to retrieve the heroes from the service.
|
||||
|
||||
<code-example path="toh-pt4/src/app/heroes/heroes.component.1.ts" header="src/app/heroes/heroes.component.ts" region="getHeroes">
|
||||
<code-example path="toh-pt4/src/app/heroes/heroes.component.1.ts" region="getHeroes">
|
||||
</code-example>
|
||||
|
||||
{@a oninit}
|
||||
|
||||
### Call it in `ngOnInit()`
|
||||
### Call it in `ngOnInit`
|
||||
|
||||
While you could call `getHeroes()` in the constructor, that's not the best practice.
|
||||
|
||||
@ -148,29 +150,29 @@ The constructor shouldn't _do anything_.
|
||||
It certainly shouldn't call a function that makes HTTP requests to a remote server as a _real_ data service would.
|
||||
|
||||
Instead, call `getHeroes()` inside the [*ngOnInit lifecycle hook*](guide/lifecycle-hooks) and
|
||||
let Angular call `ngOnInit()` at an appropriate time _after_ constructing a `HeroesComponent` instance.
|
||||
let Angular call `ngOnInit` at an appropriate time _after_ constructing a `HeroesComponent` instance.
|
||||
|
||||
<code-example path="toh-pt4/src/app/heroes/heroes.component.ts" header="src/app/heroes/heroes.component.ts" region="ng-on-init">
|
||||
<code-example path="toh-pt4/src/app/heroes/heroes.component.ts" region="ng-on-init">
|
||||
</code-example>
|
||||
|
||||
### See it run
|
||||
|
||||
After the browser refreshes, the app should run as before,
|
||||
After the browser refreshes, the app should run as before,
|
||||
showing a list of heroes and a hero detail view when you click on a hero name.
|
||||
|
||||
## Observable data
|
||||
|
||||
The `HeroService.getHeroes()` method has a _synchronous signature_,
|
||||
which implies that the `HeroService` can fetch heroes synchronously.
|
||||
The `HeroesComponent` consumes the `getHeroes()` result
|
||||
The `HeroesComponent` consumes the `getHeroes()` result
|
||||
as if heroes could be fetched synchronously.
|
||||
|
||||
<code-example path="toh-pt4/src/app/heroes/heroes.component.1.ts" header="src/app/heroes/heroes.component.ts" region="get-heroes">
|
||||
<code-example path="toh-pt4/src/app/heroes/heroes.component.1.ts" region="get-heroes">
|
||||
</code-example>
|
||||
|
||||
This will not work in a real app.
|
||||
You're getting away with it now because the service currently returns _mock heroes_.
|
||||
But soon the app will fetch heroes from a remote server,
|
||||
But soon the app will fetch heroes from a remote server,
|
||||
which is an inherently _asynchronous_ operation.
|
||||
|
||||
The `HeroService` must wait for the server to respond,
|
||||
@ -179,11 +181,13 @@ and the browser will not block while the service waits.
|
||||
|
||||
`HeroService.getHeroes()` must have an _asynchronous signature_ of some kind.
|
||||
|
||||
It can take a callback. It could return a `Promise`. It could return an `Observable`.
|
||||
|
||||
In this tutorial, `HeroService.getHeroes()` will return an `Observable`
|
||||
because it will eventually use the Angular `HttpClient.get` method to fetch the heroes
|
||||
in part because it will eventually use the Angular `HttpClient.get` method to fetch the heroes
|
||||
and [`HttpClient.get()` returns an `Observable`](guide/http).
|
||||
|
||||
### Observable `HeroService`
|
||||
### Observable _HeroService_
|
||||
|
||||
`Observable` is one of the key classes in the [RxJS library](http://reactivex.io/rxjs/).
|
||||
|
||||
@ -192,12 +196,13 @@ In this tutorial, you'll simulate getting data from the server with the RxJS `of
|
||||
|
||||
Open the `HeroService` file and import the `Observable` and `of` symbols from RxJS.
|
||||
|
||||
<code-example path="toh-pt4/src/app/hero.service.ts" header="src/app/hero.service.ts (Observable imports)" region="import-observable">
|
||||
<code-example path="toh-pt4/src/app/hero.service.ts"
|
||||
header="src/app/hero.service.ts (Observable imports)" region="import-observable">
|
||||
</code-example>
|
||||
|
||||
Replace the `getHeroes()` method with the following:
|
||||
Replace the `getHeroes` method with this one.
|
||||
|
||||
<code-example path="toh-pt4/src/app/hero.service.ts" header="src/app/hero.service.ts" region="getHeroes-1"></code-example>
|
||||
<code-example path="toh-pt4/src/app/hero.service.ts" region="getHeroes-1"></code-example>
|
||||
|
||||
`of(HEROES)` returns an `Observable<Hero[]>` that emits _a single value_, the array of mock heroes.
|
||||
|
||||
@ -207,7 +212,7 @@ In the [HTTP tutorial](tutorial/toh-pt6), you'll call `HttpClient.get<Hero[]>()`
|
||||
|
||||
</div>
|
||||
|
||||
### Subscribe in `HeroesComponent`
|
||||
### Subscribe in _HeroesComponent_
|
||||
|
||||
The `HeroService.getHeroes` method used to return a `Hero[]`.
|
||||
Now it returns an `Observable<Hero[]>`.
|
||||
@ -219,11 +224,11 @@ Find the `getHeroes` method and replace it with the following code
|
||||
|
||||
<code-tabs>
|
||||
|
||||
<code-pane header="heroes.component.ts (Observable)"
|
||||
<code-pane header="heroes.component.ts (Observable)"
|
||||
path="toh-pt4/src/app/heroes/heroes.component.ts" region="getHeroes">
|
||||
</code-pane>
|
||||
|
||||
<code-pane header="heroes.component.ts (Original)"
|
||||
<code-pane header="heroes.component.ts (Original)"
|
||||
path="toh-pt4/src/app/heroes/heroes.component.1.ts" region="getHeroes">
|
||||
</code-pane>
|
||||
|
||||
@ -237,9 +242,9 @@ or the browser could freeze the UI while it waited for the server's response.
|
||||
|
||||
That _won't work_ when the `HeroService` is actually making requests of a remote server.
|
||||
|
||||
The new version waits for the `Observable` to emit the array of heroes—which
|
||||
could happen now or several minutes from now.
|
||||
The `subscribe()` method passes the emitted array to the callback,
|
||||
The new version waits for the `Observable` to emit the array of heroes—
|
||||
which could happen now or several minutes from now.
|
||||
Then `subscribe` passes the emitted array to the callback,
|
||||
which sets the component's `heroes` property.
|
||||
|
||||
This asynchronous approach _will work_ when
|
||||
@ -247,14 +252,14 @@ the `HeroService` requests heroes from the server.
|
||||
|
||||
## Show messages
|
||||
|
||||
This section guides you through the following:
|
||||
In this section you will
|
||||
|
||||
* adding a `MessagesComponent` that displays app messages at the bottom of the screen
|
||||
* creating an injectable, app-wide `MessageService` for sending messages to be displayed
|
||||
* injecting `MessageService` into the `HeroService`
|
||||
* displaying a message when `HeroService` fetches heroes successfully
|
||||
* add a `MessagesComponent` that displays app messages at the bottom of the screen.
|
||||
* create an injectable, app-wide `MessageService` for sending messages to be displayed
|
||||
* inject `MessageService` into the `HeroService`
|
||||
* display a message when `HeroService` fetches heroes successfully.
|
||||
|
||||
### Create `MessagesComponent`
|
||||
### Create _MessagesComponent_
|
||||
|
||||
Use the CLI to create the `MessagesComponent`.
|
||||
|
||||
@ -264,18 +269,18 @@ Use the CLI to create the `MessagesComponent`.
|
||||
|
||||
The CLI creates the component files in the `src/app/messages` folder and declares the `MessagesComponent` in `AppModule`.
|
||||
|
||||
Modify the `AppComponent` template to display the generated `MessagesComponent`.
|
||||
Modify the `AppComponent` template to display the generated `MessagesComponent`
|
||||
|
||||
<code-example
|
||||
header = "src/app/app.component.html"
|
||||
header = "/src/app/app.component.html"
|
||||
path="toh-pt4/src/app/app.component.html">
|
||||
</code-example>
|
||||
|
||||
You should see the default paragraph from `MessagesComponent` at the bottom of the page.
|
||||
|
||||
### Create the `MessageService`
|
||||
### Create the _MessageService_
|
||||
|
||||
Use the CLI to create the `MessageService` in `src/app`.
|
||||
Use the CLI to create the `MessageService` in `src/app`.
|
||||
|
||||
<code-example language="sh" class="code-shell">
|
||||
ng generate service message
|
||||
@ -283,7 +288,9 @@ Use the CLI to create the `MessageService` in `src/app`.
|
||||
|
||||
Open `MessageService` and replace its contents with the following.
|
||||
|
||||
<code-example header = "src/app/message.service.ts" path="toh-pt4/src/app/message.service.ts">
|
||||
<code-example
|
||||
header = "/src/app/message.service.ts"
|
||||
path="toh-pt4/src/app/message.service.ts">
|
||||
</code-example>
|
||||
|
||||
The service exposes its cache of `messages` and two methods: one to `add()` a message to the cache and another to `clear()` the cache.
|
||||
@ -291,19 +298,19 @@ The service exposes its cache of `messages` and two methods: one to `add()` a me
|
||||
{@a inject-message-service}
|
||||
### Inject it into the `HeroService`
|
||||
|
||||
In `HeroService`, import the `MessageService`.
|
||||
Re-open the `HeroService` and import the `MessageService`.
|
||||
|
||||
<code-example
|
||||
header = "src/app/hero.service.ts (import MessageService)"
|
||||
header = "/src/app/hero.service.ts (import MessageService)"
|
||||
path="toh-pt4/src/app/hero.service.ts" region="import-message-service">
|
||||
</code-example>
|
||||
|
||||
Modify the constructor with a parameter that declares a private `messageService` property.
|
||||
Angular will inject the singleton `MessageService` into that property
|
||||
Angular will inject the singleton `MessageService` into that property
|
||||
when it creates the `HeroService`.
|
||||
|
||||
<code-example
|
||||
path="toh-pt4/src/app/hero.service.ts" header="src/app/hero.service.ts" region="ctor">
|
||||
path="toh-pt4/src/app/hero.service.ts" region="ctor">
|
||||
</code-example>
|
||||
|
||||
<div class="alert is-helpful">
|
||||
@ -315,29 +322,32 @@ you inject the `MessageService` into the `HeroService` which is injected into th
|
||||
|
||||
### Send a message from `HeroService`
|
||||
|
||||
Modify the `getHeroes()` method to send a message when the heroes are fetched.
|
||||
Modify the `getHeroes` method to send a message when the heroes are fetched.
|
||||
|
||||
<code-example path="toh-pt4/src/app/hero.service.ts" header="src/app/hero.service.ts" region="getHeroes">
|
||||
<code-example path="toh-pt4/src/app/hero.service.ts" region="getHeroes">
|
||||
</code-example>
|
||||
|
||||
### Display the message from `HeroService`
|
||||
|
||||
The `MessagesComponent` should display all messages,
|
||||
The `MessagesComponent` should display all messages,
|
||||
including the message sent by the `HeroService` when it fetches heroes.
|
||||
|
||||
Open `MessagesComponent` and import the `MessageService`.
|
||||
|
||||
<code-example header="src/app/messages/messages.component.ts (import MessageService)" path="toh-pt4/src/app/messages/messages.component.ts" region="import-message-service">
|
||||
<code-example
|
||||
header = "/src/app/messages/messages.component.ts (import MessageService)"
|
||||
path="toh-pt4/src/app/messages/messages.component.ts" region="import-message-service">
|
||||
</code-example>
|
||||
|
||||
Modify the constructor with a parameter that declares a **public** `messageService` property.
|
||||
Angular will inject the singleton `MessageService` into that property
|
||||
Angular will inject the singleton `MessageService` into that property
|
||||
when it creates the `MessagesComponent`.
|
||||
|
||||
<code-example path="toh-pt4/src/app/messages/messages.component.ts" header="src/app/messages/messages.component.ts" region="ctor">
|
||||
<code-example
|
||||
path="toh-pt4/src/app/messages/messages.component.ts" region="ctor">
|
||||
</code-example>
|
||||
|
||||
The `messageService` property **must be public** because you're going to bind to it in the template.
|
||||
The `messageService` property **must be public** because you're about to bind to it in the template.
|
||||
|
||||
<div class="alert is-important">
|
||||
|
||||
@ -345,7 +355,7 @@ Angular only binds to _public_ component properties.
|
||||
|
||||
</div>
|
||||
|
||||
### Bind to the `MessageService`
|
||||
### Bind to the _MessageService_
|
||||
|
||||
Replace the CLI-generated `MessagesComponent` template with the following.
|
||||
|
||||
@ -380,11 +390,11 @@ Here are the code files discussed on this page and your app should look like thi
|
||||
|
||||
<code-tabs>
|
||||
|
||||
<code-pane header="src/app/hero.service.ts"
|
||||
<code-pane header="src/app/hero.service.ts"
|
||||
path="toh-pt4/src/app/hero.service.ts">
|
||||
</code-pane>
|
||||
|
||||
<code-pane header="src/app/message.service.ts"
|
||||
<code-pane header="src/app/message.service.ts"
|
||||
path="toh-pt4/src/app/message.service.ts">
|
||||
</code-pane>
|
||||
|
||||
|
@ -36,74 +36,79 @@ Use the CLI to generate it.
|
||||
|
||||
The generated file looks like this:
|
||||
|
||||
<code-example path="toh-pt5/src/app/app-routing.module.0.ts" header="src/app/app-routing.module.ts (generated)">
|
||||
<code-example path="toh-pt5/src/app/app-routing.module.0.ts"
|
||||
header="src/app/app-routing.module.ts (generated)">
|
||||
</code-example>
|
||||
|
||||
Replace it with the following:
|
||||
You generally don't declare components in a routing module so you can delete the
|
||||
`@NgModule.declarations` array and delete `CommonModule` references too.
|
||||
|
||||
<code-example path="toh-pt5/src/app/app-routing.module.1.ts" header="src/app/app-routing.module.ts (updated)">
|
||||
You'll configure the router with `Routes` in the `RouterModule`
|
||||
so import those two symbols from the `@angular/router` library.
|
||||
|
||||
Add an `@NgModule.exports` array with `RouterModule` in it.
|
||||
Exporting `RouterModule` makes router directives available for use
|
||||
in the `AppModule` components that will need them.
|
||||
|
||||
`AppRoutingModule` looks like this now:
|
||||
|
||||
<code-example path="toh-pt5/src/app/app-routing.module.ts"
|
||||
region="v1"
|
||||
header="src/app/app-routing.module.ts (v1)">
|
||||
</code-example>
|
||||
|
||||
First, `AppRoutingModule` imports `RouterModule` and `Routes` so the app can have routing functionality. The next import, `HeroesComponent`, will give the Router somewhere to go once you configure the routes.
|
||||
### Add routes
|
||||
|
||||
Notice that the `CommonModule` references and `declarations` array are unecessary, so are no
|
||||
longer part of `AppRoutingModule`. The following sections explain the rest of the `AppRoutingModule` in more detail.
|
||||
|
||||
|
||||
### Routes
|
||||
|
||||
The next part of the file is where you configure your routes.
|
||||
*Routes* tell the Router which view to display when a user clicks a link or
|
||||
*Routes* tell the router which view to display when a user clicks a link or
|
||||
pastes a URL into the browser address bar.
|
||||
|
||||
Since `AppRoutingModule` already imports `HeroesComponent`, you can use it in the `routes` array:
|
||||
|
||||
<code-example path="toh-pt5/src/app/app-routing.module.ts" header="src/app/app-routing.module.ts"
|
||||
region="heroes-route">
|
||||
</code-example>
|
||||
|
||||
A typical Angular `Route` has two properties:
|
||||
|
||||
* `path`: a string that matches the URL in the browser address bar.
|
||||
* `component`: the component that the router should create when navigating to this route.
|
||||
1. `path`: a string that matches the URL in the browser address bar.
|
||||
1. `component`: the component that the router should create when navigating to this route.
|
||||
|
||||
This tells the router to match that URL to `path: 'heroes'`
|
||||
and display the `HeroesComponent` when the URL is something like `localhost:4200/heroes`.
|
||||
You intend to navigate to the `HeroesComponent` when the URL is something like `localhost:4200/heroes`.
|
||||
|
||||
### `RouterModule.forRoot()`
|
||||
Import the `HeroesComponent` so you can reference it in a `Route`.
|
||||
Then define an array of routes with a single `route` to that component.
|
||||
|
||||
The `@NgModule` metadata initializes the router and starts it listening for browser location changes.
|
||||
<code-example path="toh-pt5/src/app/app-routing.module.ts"
|
||||
region="heroes-route">
|
||||
</code-example>
|
||||
|
||||
The following line adds the `RouterModule` to the `AppRoutingModule` `imports` array and
|
||||
configures it with the `routes` in one step by calling
|
||||
`RouterModule.forRoot()`:
|
||||
Once you've finished setting up, the router will match that URL to `path: 'heroes'`
|
||||
and display the `HeroesComponent`.
|
||||
|
||||
<code-example path="toh-pt5/src/app/app-routing.module.ts" header="src/app/app-routing.module.ts" region="ngmodule-imports">
|
||||
### _RouterModule.forRoot()_
|
||||
|
||||
You first must initialize the router and start it listening for browser location changes.
|
||||
|
||||
Add `RouterModule` to the `@NgModule.imports` array and
|
||||
configure it with the `routes` in one step by calling
|
||||
`RouterModule.forRoot()` _within_ the `imports` array, like this:
|
||||
|
||||
<code-example path="toh-pt5/src/app/app-routing.module.ts"
|
||||
region="ngmodule-imports">
|
||||
</code-example>
|
||||
|
||||
<div class="alert is-helpful">
|
||||
|
||||
The method is called `forRoot()` because you configure the router at the application's root level.
|
||||
The `forRoot()` method supplies the service providers and directives needed for routing,
|
||||
The `forRoot()` method supplies the service providers and directives needed for routing,
|
||||
and performs the initial navigation based on the current browser URL.
|
||||
|
||||
</div>
|
||||
|
||||
Next, `AppRoutingModule` exports `RouterModule` so it will be available throughout the app.
|
||||
## Add _RouterOutlet_
|
||||
|
||||
Open the `AppComponent` template and replace the `<app-heroes>` element with a `<router-outlet>` element.
|
||||
|
||||
<code-example path="toh-pt5/src/app/app-routing.module.ts" header="src/app/app-routing.module.ts (exports array)" region="export-routermodule">
|
||||
<code-example path="toh-pt5/src/app/app.component.html"
|
||||
region="outlet"
|
||||
header="src/app/app.component.html (router-outlet)">
|
||||
</code-example>
|
||||
|
||||
## Add `RouterOutlet`
|
||||
|
||||
Open the `AppComponent` template and replace the `<app-heroes>` element with a `<router-outlet>` element.
|
||||
|
||||
<code-example path="toh-pt5/src/app/app.component.html" region="outlet" header="src/app/app.component.html (router-outlet)">
|
||||
</code-example>
|
||||
|
||||
The `AppComponent` template no longer needs `<app-heroes>` because the app will only display the `HeroesComponent` when the user navigates to it.
|
||||
You removed `<app-heroes>` because you will only display the `HeroesComponent` when the user navigates to it.
|
||||
|
||||
The `<router-outlet>` tells the router where to display routed views.
|
||||
|
||||
@ -124,7 +129,7 @@ You should still be running with this CLI command.
|
||||
|
||||
The browser should refresh and display the app title but not the list of heroes.
|
||||
|
||||
Look at the browser's address bar.
|
||||
Look at the browser's address bar.
|
||||
The URL ends in `/`.
|
||||
The route path to `HeroesComponent` is `/heroes`.
|
||||
|
||||
@ -135,26 +140,29 @@ You should see the familiar heroes master/detail view.
|
||||
|
||||
## Add a navigation link (`routerLink`)
|
||||
|
||||
Ideally, users should be able to click a link to navigate rather
|
||||
than pasting a route URL into the address bar.
|
||||
Users shouldn't have to paste a route URL into the address bar.
|
||||
They should be able to click a link to navigate.
|
||||
|
||||
Add a `<nav>` element and, within that, an anchor element that, when clicked,
|
||||
Add a `<nav>` element and, within that, an anchor element that, when clicked,
|
||||
triggers navigation to the `HeroesComponent`.
|
||||
The revised `AppComponent` template looks like this:
|
||||
|
||||
<code-example path="toh-pt5/src/app/app.component.html" region="heroes" header="src/app/app.component.html (heroes RouterLink)">
|
||||
<code-example
|
||||
path="toh-pt5/src/app/app.component.html"
|
||||
region="heroes"
|
||||
header="src/app/app.component.html (heroes RouterLink)">
|
||||
</code-example>
|
||||
|
||||
A [`routerLink` attribute](#routerlink) is set to `"/heroes"`,
|
||||
the string that the router matches to the route to `HeroesComponent`.
|
||||
The `routerLink` is the selector for the [`RouterLink` directive](/api/router/RouterLink)
|
||||
The `routerLink` is the selector for the [`RouterLink` directive](#routerlink)
|
||||
that turns user clicks into router navigations.
|
||||
It's another of the public directives in the `RouterModule`.
|
||||
|
||||
The browser refreshes and displays the app title and heroes link,
|
||||
The browser refreshes and displays the app title and heroes link,
|
||||
but not the heroes list.
|
||||
|
||||
Click the link.
|
||||
Click the link.
|
||||
The address bar updates to `/heroes` and the list of heroes appears.
|
||||
|
||||
<div class="alert is-helpful">
|
||||
@ -168,7 +176,7 @@ as listed in the [final code review](#appcomponent) below.
|
||||
## Add a dashboard view
|
||||
|
||||
Routing makes more sense when there are multiple views.
|
||||
So far there's only the heroes view.
|
||||
So far there's only the heroes view.
|
||||
|
||||
Add a `DashboardComponent` using the CLI:
|
||||
|
||||
@ -178,18 +186,18 @@ Add a `DashboardComponent` using the CLI:
|
||||
|
||||
The CLI generates the files for the `DashboardComponent` and declares it in `AppModule`.
|
||||
|
||||
Replace the default file content in these three files as follows:
|
||||
Replace the default file content in these three files as follows and then return for a little discussion:
|
||||
|
||||
<code-tabs>
|
||||
<code-pane
|
||||
<code-pane
|
||||
header="src/app/dashboard/dashboard.component.html" path="toh-pt5/src/app/dashboard/dashboard.component.1.html">
|
||||
</code-pane>
|
||||
|
||||
<code-pane
|
||||
<code-pane
|
||||
header="src/app/dashboard/dashboard.component.ts" path="toh-pt5/src/app/dashboard/dashboard.component.ts">
|
||||
</code-pane>
|
||||
|
||||
<code-pane
|
||||
<code-pane
|
||||
header="src/app/dashboard/dashboard.component.css" path="toh-pt5/src/app/dashboard/dashboard.component.css">
|
||||
</code-pane>
|
||||
</code-tabs>
|
||||
@ -203,11 +211,11 @@ The _template_ presents a grid of hero name links.
|
||||
The _class_ is similar to the `HeroesComponent` class.
|
||||
* It defines a `heroes` array property.
|
||||
* The constructor expects Angular to inject the `HeroService` into a private `heroService` property.
|
||||
* The `ngOnInit()` lifecycle hook calls `getHeroes()`.
|
||||
* The `ngOnInit()` lifecycle hook calls `getHeroes`.
|
||||
|
||||
This `getHeroes()` returns the sliced list of heroes at positions 1 and 5, returning only four of the Top Heroes (2nd, 3rd, 4th, and 5th).
|
||||
This `getHeroes` returns the sliced list of heroes at positions 1 and 5, returning only four of the Top Heroes (2nd, 3rd, 4th, and 5th).
|
||||
|
||||
<code-example path="toh-pt5/src/app/dashboard/dashboard.component.ts" header="src/app/dashboard/dashboard.component.ts" region="getHeroes">
|
||||
<code-example path="toh-pt5/src/app/dashboard/dashboard.component.ts" region="getHeroes">
|
||||
</code-example>
|
||||
|
||||
### Add the dashboard route
|
||||
@ -216,24 +224,29 @@ To navigate to the dashboard, the router needs an appropriate route.
|
||||
|
||||
Import the `DashboardComponent` in the `AppRoutingModule`.
|
||||
|
||||
<code-example path="toh-pt5/src/app/app-routing.module.ts" region="import-dashboard" header="src/app/app-routing.module.ts (import DashboardComponent)">
|
||||
<code-example
|
||||
path="toh-pt5/src/app/app-routing.module.ts"
|
||||
region="import-dashboard"
|
||||
header="src/app/app-routing.module.ts (import DashboardComponent)">
|
||||
</code-example>
|
||||
|
||||
Add a route to the `AppRoutingModule.routes` array that matches a path to the `DashboardComponent`.
|
||||
|
||||
<code-example path="toh-pt5/src/app/app-routing.module.ts" header="src/app/app-routing.module.ts" region="dashboard-route">
|
||||
<code-example
|
||||
path="toh-pt5/src/app/app-routing.module.ts"
|
||||
region="dashboard-route">
|
||||
</code-example>
|
||||
|
||||
### Add a default route
|
||||
|
||||
When the app starts, the browser's address bar points to the web site's root.
|
||||
When the app starts, the browsers address bar points to the web site's root.
|
||||
That doesn't match any existing route so the router doesn't navigate anywhere.
|
||||
The space below the `<router-outlet>` is blank.
|
||||
|
||||
To make the app navigate to the dashboard automatically, add the following
|
||||
route to the `AppRoutingModule.Routes` array.
|
||||
|
||||
<code-example path="toh-pt5/src/app/app-routing.module.ts" header="src/app/app-routing.module.ts" region="redirect-route">
|
||||
<code-example path="toh-pt5/src/app/app-routing.module.ts" region="redirect-route">
|
||||
</code-example>
|
||||
|
||||
This route redirects a URL that fully matches the empty path to the route whose path is `'/dashboard'`.
|
||||
@ -279,28 +292,36 @@ The heroes list view should no longer show hero details as it does now.
|
||||
Open the `HeroesComponent` template (`heroes/heroes.component.html`) and
|
||||
delete the `<app-hero-detail>` element from the bottom.
|
||||
|
||||
Clicking a hero item now does nothing.
|
||||
Clicking a hero item now does nothing.
|
||||
You'll [fix that shortly](#heroes-component-links) after you enable routing to the `HeroDetailComponent`.
|
||||
|
||||
### Add a _hero detail_ route
|
||||
|
||||
A URL like `~/detail/11` would be a good URL for navigating to the *Hero Detail* view of the hero whose `id` is `11`.
|
||||
A URL like `~/detail/11` would be a good URL for navigating to the *Hero Detail* view of the hero whose `id` is `11`.
|
||||
|
||||
Open `AppRoutingModule` and import `HeroDetailComponent`.
|
||||
|
||||
<code-example path="toh-pt5/src/app/app-routing.module.ts" region="import-herodetail" header="src/app/app-routing.module.ts (import HeroDetailComponent)">
|
||||
<code-example
|
||||
path="toh-pt5/src/app/app-routing.module.ts"
|
||||
region="import-herodetail"
|
||||
header="src/app/app-routing.module.ts (import HeroDetailComponent)">
|
||||
</code-example>
|
||||
|
||||
Then add a _parameterized_ route to the `AppRoutingModule.routes` array that matches the path pattern to the _hero detail_ view.
|
||||
|
||||
<code-example path="toh-pt5/src/app/app-routing.module.ts" header="src/app/app-routing.module.ts" region="detail-route">
|
||||
<code-example
|
||||
path="toh-pt5/src/app/app-routing.module.ts"
|
||||
region="detail-route">
|
||||
</code-example>
|
||||
|
||||
The colon (:) in the `path` indicates that `:id` is a placeholder for a specific hero `id`.
|
||||
|
||||
At this point, all application routes are in place.
|
||||
|
||||
<code-example path="toh-pt5/src/app/app-routing.module.ts" region="routes" header="src/app/app-routing.module.ts (all routes)">
|
||||
<code-example
|
||||
path="toh-pt5/src/app/app-routing.module.ts"
|
||||
region="routes"
|
||||
header="src/app/app-routing.module.ts (all routes)">
|
||||
</code-example>
|
||||
|
||||
### `DashboardComponent` hero links
|
||||
@ -310,14 +331,14 @@ The `DashboardComponent` hero links do nothing at the moment.
|
||||
Now that the router has a route to `HeroDetailComponent`,
|
||||
fix the dashboard hero links to navigate via the _parameterized_ dashboard route.
|
||||
|
||||
<code-example
|
||||
path="toh-pt5/src/app/dashboard/dashboard.component.html"
|
||||
region="click"
|
||||
<code-example
|
||||
path="toh-pt5/src/app/dashboard/dashboard.component.html"
|
||||
region="click"
|
||||
header="src/app/dashboard/dashboard.component.html (hero links)">
|
||||
</code-example>
|
||||
|
||||
You're using Angular [interpolation binding](guide/template-syntax#interpolation) within the `*ngFor` repeater
|
||||
to insert the current iteration's `hero.id` into each
|
||||
You're using Angular [interpolation binding](guide/template-syntax#interpolation) within the `*ngFor` repeater
|
||||
to insert the current iteration's `hero.id` into each
|
||||
[`routerLink`](#routerlink).
|
||||
|
||||
{@a heroes-component-links}
|
||||
@ -326,15 +347,21 @@ to insert the current iteration's `hero.id` into each
|
||||
The hero items in the `HeroesComponent` are `<li>` elements whose click events
|
||||
are bound to the component's `onSelect()` method.
|
||||
|
||||
<code-example path="toh-pt4/src/app/heroes/heroes.component.html" region="list" header="src/app/heroes/heroes.component.html (list with onSelect)">
|
||||
<code-example
|
||||
path="toh-pt4/src/app/heroes/heroes.component.html"
|
||||
region="list"
|
||||
header="src/app/heroes/heroes.component.html (list with onSelect)">
|
||||
</code-example>
|
||||
|
||||
Strip the `<li>` back to just its `*ngFor`,
|
||||
wrap the badge and name in an anchor element (`<a>`),
|
||||
and add a `routerLink` attribute to the anchor that
|
||||
and add a `routerLink` attribute to the anchor that
|
||||
is the same as in the dashboard template
|
||||
|
||||
<code-example path="toh-pt5/src/app/heroes/heroes.component.html" region="list" header="src/app/heroes/heroes.component.html (list with links)">
|
||||
<code-example
|
||||
path="toh-pt5/src/app/heroes/heroes.component.html"
|
||||
region="list"
|
||||
header="src/app/heroes/heroes.component.html (list with links)">
|
||||
</code-example>
|
||||
|
||||
You'll have to fix the private stylesheet (`heroes.component.css`) to make
|
||||
@ -343,16 +370,19 @@ Revised styles are in the [final code review](#heroescomponent) at the bottom of
|
||||
|
||||
#### Remove dead code (optional)
|
||||
|
||||
While the `HeroesComponent` class still works,
|
||||
While the `HeroesComponent` class still works,
|
||||
the `onSelect()` method and `selectedHero` property are no longer used.
|
||||
|
||||
It's nice to tidy up and you'll be grateful to yourself later.
|
||||
Here's the class after pruning away the dead code.
|
||||
|
||||
<code-example path="toh-pt5/src/app/heroes/heroes.component.ts" region="class" header="src/app/heroes/heroes.component.ts (cleaned up)" linenums="false">
|
||||
<code-example
|
||||
path="toh-pt5/src/app/heroes/heroes.component.ts"
|
||||
region="class"
|
||||
header="src/app/heroes/heroes.component.ts (cleaned up)" linenums="false">
|
||||
</code-example>
|
||||
|
||||
## Routable `HeroDetailComponent`
|
||||
## Routable *HeroDetailComponent*
|
||||
|
||||
Previously, the parent `HeroesComponent` set the `HeroDetailComponent.hero`
|
||||
property and the `HeroDetailComponent` displayed the hero.
|
||||
@ -360,16 +390,18 @@ property and the `HeroDetailComponent` displayed the hero.
|
||||
`HeroesComponent` doesn't do that anymore.
|
||||
Now the router creates the `HeroDetailComponent` in response to a URL such as `~/detail/11`.
|
||||
|
||||
The `HeroDetailComponent` needs a new way to obtain the hero-to-display.
|
||||
This section explains the following:
|
||||
The `HeroDetailComponent` needs a new way to obtain the _hero-to-display_.
|
||||
|
||||
* Get the route that created it
|
||||
* Get the route that created it,
|
||||
* Extract the `id` from the route
|
||||
* Acquire the hero with that `id` from the server via the `HeroService`
|
||||
|
||||
Add the following imports:
|
||||
|
||||
<code-example path="toh-pt5/src/app/hero-detail/hero-detail.component.ts" region="added-imports" header="src/app/hero-detail/hero-detail.component.ts">
|
||||
<code-example
|
||||
path="toh-pt5/src/app/hero-detail/hero-detail.component.ts"
|
||||
region="added-imports"
|
||||
header="src/app/hero-detail/hero-detail.component.ts">
|
||||
</code-example>
|
||||
|
||||
{@a hero-detail-ctor}
|
||||
@ -377,25 +409,27 @@ Add the following imports:
|
||||
Inject the `ActivatedRoute`, `HeroService`, and `Location` services
|
||||
into the constructor, saving their values in private fields:
|
||||
|
||||
<code-example path="toh-pt5/src/app/hero-detail/hero-detail.component.ts" header="toh-pt5/src/app/hero-detail/hero-detail.component.ts" region="ctor">
|
||||
<code-example
|
||||
path="toh-pt5/src/app/hero-detail/hero-detail.component.ts" region="ctor">
|
||||
</code-example>
|
||||
|
||||
The [`ActivatedRoute`](api/router/ActivatedRoute) holds information about the route to this instance of the `HeroDetailComponent`.
|
||||
This component is interested in the route's parameters extracted from the URL.
|
||||
The "id" parameter is the `id` of the hero to display.
|
||||
This component is interested in the route's bag of parameters extracted from the URL.
|
||||
The _"id"_ parameter is the `id` of the hero to display.
|
||||
|
||||
The [`HeroService`](tutorial/toh-pt4) gets hero data from the remote server
|
||||
and this component will use it to get the hero-to-display.
|
||||
and this component will use it to get the _hero-to-display_.
|
||||
|
||||
The [`location`](api/common/Location) is an Angular service for interacting with the browser.
|
||||
You'll use it [later](#goback) to navigate back to the view that navigated here.
|
||||
|
||||
### Extract the `id` route parameter
|
||||
### Extract the _id_ route parameter
|
||||
|
||||
In the `ngOnInit()` [lifecycle hook](guide/lifecycle-hooks#oninit)
|
||||
call `getHero()` and define it as follows.
|
||||
|
||||
<code-example path="toh-pt5/src/app/hero-detail/hero-detail.component.ts" header="src/app/hero-detail/hero-detail.component.ts" region="ngOnInit">
|
||||
<code-example
|
||||
path="toh-pt5/src/app/hero-detail/hero-detail.component.ts" region="ngOnInit">
|
||||
</code-example>
|
||||
|
||||
The `route.snapshot` is a static image of the route information shortly after the component was created.
|
||||
@ -413,14 +447,18 @@ Add it now.
|
||||
|
||||
### Add `HeroService.getHero()`
|
||||
|
||||
Open `HeroService` and add the following `getHero()` method with the `id` after the `getHeroes()` method:
|
||||
Open `HeroService` and add this `getHero()` method
|
||||
|
||||
<code-example path="toh-pt5/src/app/hero.service.ts" region="getHero" header="src/app/hero.service.ts (getHero)">
|
||||
<code-example
|
||||
path="toh-pt5/src/app/hero.service.ts"
|
||||
region="getHero"
|
||||
header="src/app/hero.service.ts (getHero)">
|
||||
</code-example>
|
||||
|
||||
<div class="alert is-important">
|
||||
|
||||
Note the backticks ( ` ) that define a JavaScript
|
||||
Note the backticks ( ` ) that
|
||||
define a JavaScript
|
||||
[_template literal_](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals) for embedding the `id`.
|
||||
</div>
|
||||
|
||||
@ -443,7 +481,7 @@ the router navigates to the detail view for the hero with `id: 11`, "Dr Nice".
|
||||
|
||||
### Find the way back
|
||||
|
||||
By clicking the browser's back button,
|
||||
By clicking the browser's back button,
|
||||
you can go back to the hero list or dashboard view,
|
||||
depending upon which sent you to the detail view.
|
||||
|
||||
@ -452,10 +490,13 @@ It would be nice to have a button on the `HeroDetail` view that can do that.
|
||||
Add a *go back* button to the bottom of the component template and bind it
|
||||
to the component's `goBack()` method.
|
||||
|
||||
<code-example path="toh-pt5/src/app/hero-detail/hero-detail.component.html" region="back-button" header="src/app/hero-detail/hero-detail.component.html (back button)">
|
||||
<code-example
|
||||
path="toh-pt5/src/app/hero-detail/hero-detail.component.html"
|
||||
region="back-button"
|
||||
header="src/app/hero-detail/hero-detail.component.html (back button)">
|
||||
</code-example>
|
||||
|
||||
Add a `goBack()` _method_ to the component class that navigates backward one step
|
||||
Add a `goBack()` _method_ to the component class that navigates backward one step
|
||||
in the browser's history stack
|
||||
using the `Location` service that you [injected previously](#hero-detail-ctor).
|
||||
|
||||
@ -468,93 +509,95 @@ Refresh the browser and start clicking.
|
||||
Users can navigate around the app, from the dashboard to hero details and back,
|
||||
from heroes list to the mini detail to the hero details and back to the heroes again.
|
||||
|
||||
You've met all of the navigational requirements that propelled this page.
|
||||
|
||||
## Final code review
|
||||
|
||||
Here are the code files discussed on this page and your app should look like this <live-example></live-example>.
|
||||
|
||||
{@a approutingmodule}
|
||||
{@a appmodule}
|
||||
#### `AppRoutingModule`, `AppModule`, and `HeroService`
|
||||
#### _AppRoutingModule_, _AppModule_, and _HeroService_
|
||||
|
||||
<code-tabs>
|
||||
<code-pane
|
||||
header="src/app/app-routing.module.ts"
|
||||
<code-pane
|
||||
header="src/app/app-routing.module.ts"
|
||||
path="toh-pt5/src/app/app-routing.module.ts">
|
||||
</code-pane>
|
||||
<code-pane
|
||||
header="src/app/app.module.ts"
|
||||
<code-pane
|
||||
header="src/app/app.module.ts"
|
||||
path="toh-pt5/src/app/app.module.ts">
|
||||
</code-pane>
|
||||
<code-pane
|
||||
header="src/app/hero.service.ts"
|
||||
<code-pane
|
||||
header="src/app/hero.service.ts"
|
||||
path="toh-pt5/src/app/hero.service.ts">
|
||||
</code-pane>
|
||||
</code-tabs>
|
||||
|
||||
{@a appcomponent}
|
||||
#### `AppComponent`
|
||||
#### _AppComponent_
|
||||
|
||||
<code-tabs>
|
||||
<code-pane
|
||||
<code-pane
|
||||
header="src/app/app.component.html"
|
||||
path="toh-pt5/src/app/app.component.html">
|
||||
</code-pane>
|
||||
|
||||
<code-pane
|
||||
<code-pane
|
||||
header="src/app/app.component.css"
|
||||
path="toh-pt5/src/app/app.component.css">
|
||||
</code-pane>
|
||||
</code-tabs>
|
||||
|
||||
{@a dashboardcomponent}
|
||||
#### `DashboardComponent`
|
||||
#### _DashboardComponent_
|
||||
|
||||
<code-tabs>
|
||||
<code-pane
|
||||
<code-pane
|
||||
header="src/app/dashboard/dashboard.component.html" path="toh-pt5/src/app/dashboard/dashboard.component.html">
|
||||
</code-pane>
|
||||
|
||||
<code-pane
|
||||
<code-pane
|
||||
header="src/app/dashboard/dashboard.component.ts" path="toh-pt5/src/app/dashboard/dashboard.component.ts">
|
||||
</code-pane>
|
||||
|
||||
<code-pane
|
||||
<code-pane
|
||||
header="src/app/dashboard/dashboard.component.css" path="toh-pt5/src/app/dashboard/dashboard.component.css">
|
||||
</code-pane>
|
||||
</code-tabs>
|
||||
|
||||
{@a heroescomponent}
|
||||
#### `HeroesComponent`
|
||||
#### _HeroesComponent_
|
||||
|
||||
<code-tabs>
|
||||
<code-pane
|
||||
<code-pane
|
||||
header="src/app/heroes/heroes.component.html" path="toh-pt5/src/app/heroes/heroes.component.html">
|
||||
</code-pane>
|
||||
|
||||
<code-pane
|
||||
header="src/app/heroes/heroes.component.ts"
|
||||
<code-pane
|
||||
header="src/app/heroes/heroes.component.ts"
|
||||
path="toh-pt5/src/app/heroes/heroes.component.ts">
|
||||
</code-pane>
|
||||
|
||||
<code-pane
|
||||
header="src/app/heroes/heroes.component.css"
|
||||
<code-pane
|
||||
header="src/app/heroes/heroes.component.css"
|
||||
path="toh-pt5/src/app/heroes/heroes.component.css">
|
||||
</code-pane>
|
||||
</code-tabs>
|
||||
|
||||
{@a herodetailcomponent}
|
||||
#### `HeroDetailComponent`
|
||||
#### _HeroDetailComponent_
|
||||
|
||||
<code-tabs>
|
||||
<code-pane
|
||||
<code-pane
|
||||
header="src/app/hero-detail/hero-detail.component.html" path="toh-pt5/src/app/hero-detail/hero-detail.component.html">
|
||||
</code-pane>
|
||||
|
||||
<code-pane
|
||||
<code-pane
|
||||
header="src/app/hero-detail/hero-detail.component.ts" path="toh-pt5/src/app/hero-detail/hero-detail.component.ts">
|
||||
</code-pane>
|
||||
|
||||
<code-pane
|
||||
<code-pane
|
||||
header="src/app/hero-detail/hero-detail.component.css" path="toh-pt5/src/app/hero-detail/hero-detail.component.css">
|
||||
</code-pane>
|
||||
</code-tabs>
|
||||
@ -563,7 +606,7 @@ Here are the code files discussed on this page and your app should look like thi
|
||||
|
||||
* You added the Angular router to navigate among different components.
|
||||
* You turned the `AppComponent` into a navigation shell with `<a>` links and a `<router-outlet>`.
|
||||
* You configured the router in an `AppRoutingModule`
|
||||
* You configured the router in an `AppRoutingModule`
|
||||
* You defined simple routes, a redirect route, and a parameterized route.
|
||||
* You used the `routerLink` directive in anchor elements.
|
||||
* You refactored a tightly-coupled master/detail view into a routed detail view.
|
||||
|
@ -11,147 +11,174 @@ When you're done with this page, the app should look like this <live-example></l
|
||||
|
||||
## Enable HTTP services
|
||||
|
||||
`HttpClient` is Angular's mechanism for communicating with a remote server over HTTP.
|
||||
`HttpClient` is Angular's mechanism for communicating with a remote server over HTTP.
|
||||
|
||||
Make `HttpClient` available everywhere in the app in two steps. First, add it to the root `AppModule` by importing it:
|
||||
To make `HttpClient` available everywhere in the app:
|
||||
|
||||
<code-example path="toh-pt6/src/app/app.module.ts" region="import-http-client" header="src/app/app.module.ts (HttpClientModule import)">
|
||||
</code-example>
|
||||
|
||||
Next, still in the `AppModule`, add `HttpClient` to the `imports` array:
|
||||
|
||||
<code-example path="toh-pt6/src/app/app.module.ts" region="import-httpclientmodule" header="src/app/app.module.ts (imports array excerpt)">
|
||||
* open the root `AppModule`
|
||||
* import the `HttpClientModule` symbol from `@angular/common/http`
|
||||
|
||||
<code-example
|
||||
path="toh-pt6/src/app/app.module.ts"
|
||||
region="import-http-client"
|
||||
header="src/app/app.module.ts (Http Client import)">
|
||||
</code-example>
|
||||
|
||||
* add it to the `@NgModule.imports` array
|
||||
|
||||
## Simulate a data server
|
||||
|
||||
This tutorial sample mimics communication with a remote data server by using the
|
||||
[In-memory Web API](https://github.com/angular/in-memory-web-api "In-memory Web API") module.
|
||||
This tutorial sample _mimics_ communication with a remote data server by using the
|
||||
[_In-memory Web API_](https://github.com/angular/in-memory-web-api "In-memory Web API") module.
|
||||
|
||||
After installing the module, the app will make requests to and receive responses from the `HttpClient`
|
||||
without knowing that the *In-memory Web API* is intercepting those requests,
|
||||
applying them to an in-memory data store, and returning simulated responses.
|
||||
|
||||
By using the In-memory Web API, you won't have to set up a server to learn about `HttpClient`.
|
||||
This facility is a great convenience for the tutorial.
|
||||
You won't have to set up a server to learn about `HttpClient`.
|
||||
|
||||
It may also be convenient in the early stages of your own app development when
|
||||
the server's web api is ill-defined or not yet implemented.
|
||||
|
||||
<div class="alert is-important">
|
||||
|
||||
**Important:** the In-memory Web API module has nothing to do with HTTP in Angular.
|
||||
**Important:** the *In-memory Web API* module has nothing to do with HTTP in Angular.
|
||||
|
||||
If you're just reading this tutorial to learn about `HttpClient`, you can [skip over](#import-heroes) this step.
|
||||
If you're coding along with this tutorial, stay here and add the In-memory Web API now.
|
||||
If you're just _reading_ this tutorial to learn about `HttpClient`, you can [skip over](#import-heroes) this step.
|
||||
If you're _coding along_ with this tutorial, stay here and add the *In-memory Web API* now.
|
||||
|
||||
</div>
|
||||
|
||||
Install the In-memory Web API package from npm with the following command:
|
||||
Install the *In-memory Web API* package from _npm_
|
||||
|
||||
<code-example language="sh" class="code-shell">
|
||||
npm install angular-in-memory-web-api --save
|
||||
</code-example>
|
||||
|
||||
In the `AppModule`, import the `HttpClientInMemoryWebApiModule` and the `InMemoryDataService` class,
|
||||
which you will create in a moment.
|
||||
|
||||
<code-example path="toh-pt6/src/app/app.module.ts" region="import-in-mem-stuff" header="src/app/app.module.ts (In-memory Web API imports)">
|
||||
</code-example>
|
||||
|
||||
After the `HttpClientModule`, add the `HttpClientInMemoryWebApiModule`
|
||||
to the `AppModule` `imports` array and configure it with the `InMemoryDataService`.
|
||||
|
||||
<code-example path="toh-pt6/src/app/app.module.ts" header="src/app/app.module.ts (imports array excerpt)" region="in-mem-web-api-imports">
|
||||
</code-example>
|
||||
|
||||
The `forRoot()` configuration method takes an `InMemoryDataService` class
|
||||
that primes the in-memory database.
|
||||
|
||||
Generate the class `src/app/in-memory-data.service.ts` with the following command:
|
||||
The class `src/app/in-memory-data.service.ts` is generated by the following command:
|
||||
|
||||
<code-example language="sh" class="code-shell">
|
||||
ng generate service InMemoryData
|
||||
</code-example>
|
||||
|
||||
Replace the default contents of `in-memory-data.service.ts` with the following:
|
||||
This class has the following content:
|
||||
|
||||
<code-example path="toh-pt6/src/app/in-memory-data.service.ts" region="init" header="src/app/in-memory-data.service.ts" linenums="false"></code-example>
|
||||
|
||||
The `in-memory-data.service.ts` file replaces `mock-heroes.ts`, which is now safe to delete.
|
||||
This file replaces `mock-heroes.ts`, which is now safe to delete.
|
||||
|
||||
When the server is ready, you'll detach the In-memory Web API, and the app's requests will go through to the server.
|
||||
When your server is ready, detach the *In-memory Web API*, and the app's requests will go through to the server.
|
||||
|
||||
Now back to the `HttpClient` story.
|
||||
|
||||
Import the `HttpClientInMemoryWebApiModule` and the `InMemoryDataService` class.
|
||||
|
||||
<code-example
|
||||
path="toh-pt6/src/app/app.module.ts"
|
||||
region="import-in-mem-stuff"
|
||||
header="src/app/app.module.ts (In-memory Web API imports)">
|
||||
</code-example>
|
||||
|
||||
Add the `HttpClientInMemoryWebApiModule` to the `@NgModule.imports` array—
|
||||
_after importing the `HttpClientModule`_,
|
||||
—while configuring it with the `InMemoryDataService`.
|
||||
|
||||
<code-example
|
||||
path="toh-pt6/src/app/app.module.ts"
|
||||
region="in-mem-web-api-imports">
|
||||
</code-example>
|
||||
|
||||
The `forRoot()` configuration method takes an `InMemoryDataService` class
|
||||
that primes the in-memory database.
|
||||
|
||||
{@a import-heroes}
|
||||
## Heroes and HTTP
|
||||
|
||||
In the `HeroService`, import `HttpClient` and `HttpHeaders`:
|
||||
Import some HTTP symbols that you'll need:
|
||||
|
||||
<code-example path="toh-pt6/src/app/hero.service.ts" region="import-httpclient" header="src/app/hero.service.ts (import HTTP symbols)">
|
||||
<code-example
|
||||
path="toh-pt6/src/app/hero.service.ts"
|
||||
region="import-httpclient"
|
||||
header="src/app/hero.service.ts (import HTTP symbols)">
|
||||
</code-example>
|
||||
|
||||
Still in the `HeroService`, inject `HttpClient` into the constructor in a private property called `http`.
|
||||
Inject `HttpClient` into the constructor in a private property called `http`.
|
||||
|
||||
<code-example path="toh-pt6/src/app/hero.service.ts" header="src/app/hero.service.ts" region="ctor" >
|
||||
<code-example
|
||||
path="toh-pt6/src/app/hero.service.ts"
|
||||
region="ctor" >
|
||||
</code-example>
|
||||
|
||||
Notice that you keep injecting the `MessageService` but since you'll call it so frequently, wrap it in a private `log()` method:
|
||||
Keep injecting the `MessageService`. You'll call it so frequently that
|
||||
you'll wrap it in a private `log()` method.
|
||||
|
||||
<code-example path="toh-pt6/src/app/hero.service.ts" header="src/app/hero.service.ts" region="log" >
|
||||
<code-example
|
||||
path="toh-pt6/src/app/hero.service.ts"
|
||||
region="log" >
|
||||
</code-example>
|
||||
|
||||
Define the `heroesUrl` of the form `:base/:collectionName` with the address of the heroes resource on the server.
|
||||
Here `base` is the resource to which requests are made,
|
||||
and `collectionName` is the heroes data object in the `in-memory-data-service.ts`.
|
||||
|
||||
<code-example path="toh-pt6/src/app/hero.service.ts" header="src/app/hero.service.ts" region="heroesUrl" >
|
||||
<code-example
|
||||
path="toh-pt6/src/app/hero.service.ts"
|
||||
region="heroesUrl" >
|
||||
</code-example>
|
||||
|
||||
### Get heroes with `HttpClient`
|
||||
### Get heroes with _HttpClient_
|
||||
|
||||
The current `HeroService.getHeroes()`
|
||||
The current `HeroService.getHeroes()`
|
||||
uses the RxJS `of()` function to return an array of mock heroes
|
||||
as an `Observable<Hero[]>`.
|
||||
|
||||
<code-example path="toh-pt4/src/app/hero.service.ts" region="getHeroes-1" header="src/app/hero.service.ts (getHeroes with RxJs 'of()')">
|
||||
<code-example
|
||||
path="toh-pt4/src/app/hero.service.ts"
|
||||
region="getHeroes-1"
|
||||
header="src/app/hero.service.ts (getHeroes with RxJs 'of()')">
|
||||
</code-example>
|
||||
|
||||
Convert that method to use `HttpClient` as follows:
|
||||
|
||||
<code-example path="toh-pt6/src/app/hero.service.ts" header="src/app/hero.service.ts" region="getHeroes-1">
|
||||
Convert that method to use `HttpClient`
|
||||
<code-example
|
||||
path="toh-pt6/src/app/hero.service.ts"
|
||||
region="getHeroes-1">
|
||||
</code-example>
|
||||
|
||||
Refresh the browser. The hero data should successfully load from the
|
||||
mock server.
|
||||
|
||||
You've swapped `of()` for `http.get()` and the app keeps working without any other changes
|
||||
You've swapped `of` for `http.get` and the app keeps working without any other changes
|
||||
because both functions return an `Observable<Hero[]>`.
|
||||
|
||||
### `HttpClient` methods return one value
|
||||
### Http methods return one value
|
||||
|
||||
All `HttpClient` methods return an RxJS `Observable` of something.
|
||||
|
||||
HTTP is a request/response protocol.
|
||||
HTTP is a request/response protocol.
|
||||
You make a request, it returns a single response.
|
||||
|
||||
In general, an observable _can_ return multiple values over time.
|
||||
An observable from `HttpClient` always emits a single value and then completes, never to emit again.
|
||||
|
||||
This particular `HttpClient.get()` call returns an `Observable<Hero[]>`; that is, "_an observable of hero arrays_". In practice, it will only return a single hero array.
|
||||
This particular `HttpClient.get` call returns an `Observable<Hero[]>`, literally "_an observable of hero arrays_". In practice, it will only return a single hero array.
|
||||
|
||||
### `HttpClient.get()` returns response data
|
||||
### _HttpClient.get_ returns response data
|
||||
|
||||
`HttpClient.get()` returns the body of the response as an untyped JSON object by default.
|
||||
`HttpClient.get` returns the _body_ of the response as an untyped JSON object by default.
|
||||
Applying the optional type specifier, `<Hero[]>` , gives you a typed result object.
|
||||
|
||||
The server's data API determines the shape of the JSON data.
|
||||
The shape of the JSON data is determined by the server's data API.
|
||||
The _Tour of Heroes_ data API returns the hero data as an array.
|
||||
|
||||
<div class="alert is-helpful">
|
||||
|
||||
Other APIs may bury the data that you want within an object.
|
||||
You might have to dig that data out by processing the `Observable` result
|
||||
with the RxJS `map()` operator.
|
||||
with the RxJS `map` operator.
|
||||
|
||||
Although not discussed here, there's an example of `map()` in the `getHeroNo404()`
|
||||
Although not discussed here, there's an example of `map` in the `getHeroNo404()`
|
||||
method included in the sample source code.
|
||||
|
||||
</div>
|
||||
@ -165,51 +192,59 @@ To catch errors, you **"pipe" the observable** result from `http.get()` through
|
||||
|
||||
Import the `catchError` symbol from `rxjs/operators`, along with some other operators you'll need later.
|
||||
|
||||
<code-example path="toh-pt6/src/app/hero.service.ts" header="src/app/hero.service.ts" region="import-rxjs-operators">
|
||||
<code-example
|
||||
path="toh-pt6/src/app/hero.service.ts"
|
||||
region="import-rxjs-operators">
|
||||
</code-example>
|
||||
|
||||
Now extend the observable result with the `pipe()` method and
|
||||
Now extend the observable result with the `.pipe()` method and
|
||||
give it a `catchError()` operator.
|
||||
|
||||
<code-example path="toh-pt6/src/app/hero.service.ts" region="getHeroes-2" header="src/app/hero.service.ts">
|
||||
<code-example
|
||||
path="toh-pt6/src/app/hero.service.ts"
|
||||
region="getHeroes-2" >
|
||||
</code-example>
|
||||
|
||||
The `catchError()` operator intercepts an **`Observable` that failed**.
|
||||
It passes the error an error handler that can do what it wants with the error.
|
||||
It passes the error an _error handler_ that can do what it wants with the error.
|
||||
|
||||
The following `handleError()` method reports the error and then returns an
|
||||
innocuous result so that the application keeps working.
|
||||
|
||||
#### `handleError`
|
||||
#### _handleError_
|
||||
|
||||
The following `handleError()` will be shared by many `HeroService` methods
|
||||
so it's generalized to meet their different needs.
|
||||
|
||||
Instead of handling the error directly, it returns an error handler function to `catchError` that it
|
||||
Instead of handling the error directly, it returns an _error handler_ function to `catchError` that it
|
||||
has configured with both the name of the operation that failed and a safe return value.
|
||||
|
||||
<code-example path="toh-pt6/src/app/hero.service.ts" header="src/app/hero.service.ts" region="handleError">
|
||||
<code-example
|
||||
path="toh-pt6/src/app/hero.service.ts"
|
||||
region="handleError">
|
||||
</code-example>
|
||||
|
||||
After reporting the error to the console, the handler constructs
|
||||
a user friendly message and returns a safe value to the app so the app can keep working.
|
||||
After reporting the error to console, the handler constructs
|
||||
a user friendly message and returns a safe value to the app so it can keep working.
|
||||
|
||||
Because each service method returns a different kind of `Observable` result,
|
||||
`handleError()` takes a type parameter so it can return the safe value as the type that the app expects.
|
||||
|
||||
### Tap into the Observable
|
||||
### Tap into the _Observable_
|
||||
|
||||
The `HeroService` methods will **tap** into the flow of observable values
|
||||
and send a message, via the `log()` method, to the message area at the bottom of the page.
|
||||
and send a message (via `log()`) to the message area at the bottom of the page.
|
||||
|
||||
They'll do that with the RxJS `tap()` operator,
|
||||
which looks at the observable values, does something with those values,
|
||||
They'll do that with the RxJS `tap` operator,
|
||||
which _looks_ at the observable values, does _something_ with those values,
|
||||
and passes them along.
|
||||
The `tap()` call back doesn't touch the values themselves.
|
||||
The `tap` call back doesn't touch the values themselves.
|
||||
|
||||
Here is the final version of `getHeroes()` with the `tap()` that logs the operation.
|
||||
Here is the final version of `getHeroes` with the `tap` that logs the operation.
|
||||
|
||||
<code-example path="toh-pt6/src/app/hero.service.ts" header="src/app/hero.service.ts" region="getHeroes" >
|
||||
<code-example
|
||||
path="toh-pt6/src/app/hero.service.ts"
|
||||
region="getHeroes" >
|
||||
</code-example>
|
||||
|
||||
### Get hero by id
|
||||
@ -219,20 +254,20 @@ Most web APIs support a _get by id_ request in the form `:baseURL/:id`.
|
||||
Here, the _base URL_ is the `heroesURL` defined in the [Heroes and HTTP](tutorial/toh-pt6#heroes-and-http) section (`api/heroes`) and _id_ is
|
||||
the number of the hero that you want to retrieve. For example, `api/heroes/11`.
|
||||
|
||||
Update the `HeroService` `getHero()` method with the following to make that request:
|
||||
Add a `HeroService.getHero()` method to make that request:
|
||||
|
||||
<code-example path="toh-pt6/src/app/hero.service.ts" region="getHero" header="src/app/hero.service.ts"></code-example>
|
||||
|
||||
There are three significant differences from `getHeroes()`:
|
||||
There are three significant differences from `getHeroes()`.
|
||||
|
||||
* `getHero()` constructs a request URL with the desired hero's id.
|
||||
* The server should respond with a single hero rather than an array of heroes.
|
||||
* `getHero()` returns an `Observable<Hero>` ("_an observable of Hero objects_")
|
||||
* it constructs a request URL with the desired hero's id.
|
||||
* the server should respond with a single hero rather than an array of heroes.
|
||||
* therefore, `getHero` returns an `Observable<Hero>` ("_an observable of Hero objects_")
|
||||
rather than an observable of hero _arrays_ .
|
||||
|
||||
## Update heroes
|
||||
|
||||
Edit a hero's name in the hero detail view.
|
||||
Edit a hero's name in the _hero detail_ view.
|
||||
As you type, the hero name updates the heading at the top of the page.
|
||||
But when you click the "go back button", the changes are lost.
|
||||
|
||||
@ -244,21 +279,24 @@ binding that invokes a new component method named `save()`.
|
||||
|
||||
<code-example path="toh-pt6/src/app/hero-detail/hero-detail.component.html" region="save" header="src/app/hero-detail/hero-detail.component.html (save)"></code-example>
|
||||
|
||||
In the `HeroDetail` component class, add the following `save()` method, which persists hero name changes using the hero service
|
||||
Add the following `save()` method, which persists hero name changes using the hero service
|
||||
`updateHero()` method and then navigates back to the previous view.
|
||||
|
||||
<code-example path="toh-pt6/src/app/hero-detail/hero-detail.component.ts" region="save" header="src/app/hero-detail/hero-detail.component.ts (save)"></code-example>
|
||||
|
||||
#### Add `HeroService.updateHero()`
|
||||
#### Add _HeroService.updateHero()_
|
||||
|
||||
The overall structure of the `updateHero()` method is similar to that of
|
||||
`getHeroes()`, but it uses `http.put()` to persist the changed hero
|
||||
on the server. Add the following to the `HeroService`.
|
||||
on the server.
|
||||
|
||||
<code-example path="toh-pt6/src/app/hero.service.ts" region="updateHero" header="src/app/hero.service.ts (update)">
|
||||
<code-example
|
||||
path="toh-pt6/src/app/hero.service.ts"
|
||||
region="updateHero"
|
||||
header="src/app/hero.service.ts (update)">
|
||||
</code-example>
|
||||
|
||||
The `HttpClient.put()` method takes three parameters:
|
||||
The `HttpClient.put()` method takes three parameters
|
||||
* the URL
|
||||
* the data to update (the modified hero in this case)
|
||||
* options
|
||||
@ -266,19 +304,20 @@ The `HttpClient.put()` method takes three parameters:
|
||||
The URL is unchanged. The heroes web API knows which hero to update by looking at the hero's `id`.
|
||||
|
||||
The heroes web API expects a special header in HTTP save requests.
|
||||
That header is in the `httpOptions` constant defined in the `HeroService`. Add the following to the `HeroService` class.
|
||||
That header is in the `httpOptions` constant defined in the `HeroService`.
|
||||
|
||||
<code-example path="toh-pt6/src/app/hero.service.ts" region="http-options" header="src/app/hero.service.ts">
|
||||
<code-example
|
||||
path="toh-pt6/src/app/hero.service.ts"
|
||||
region="http-options"
|
||||
header="src/app/hero.service.ts">
|
||||
</code-example>
|
||||
|
||||
Refresh the browser, change a hero name and save your change. The `save()`
|
||||
method in `HeroDetailComponent`navigates to the previous view.
|
||||
Refresh the browser, change a hero name and save your change. Navigating to the previous view is implemented in the `save()` method defined in `HeroDetailComponent`.
|
||||
The hero now appears in the list with the changed name.
|
||||
|
||||
|
||||
## Add a new hero
|
||||
|
||||
To add a hero, this app only needs the hero's name. You can use an `<input>`
|
||||
To add a hero, this app only needs the hero's name. You can use an `input`
|
||||
element paired with an add button.
|
||||
|
||||
Insert the following into the `HeroesComponent` template, just after
|
||||
@ -286,26 +325,29 @@ the heading:
|
||||
|
||||
<code-example path="toh-pt6/src/app/heroes/heroes.component.html" region="add" header="src/app/heroes/heroes.component.html (add)"></code-example>
|
||||
|
||||
In response to a click event, call the component's click handler, `add()`, and then
|
||||
clear the input field so that it's ready for another name. Add the following to the
|
||||
`HeroesComponent` class:
|
||||
In response to a click event, call the component's click handler and then
|
||||
clear the input field so that it's ready for another name.
|
||||
|
||||
<code-example path="toh-pt6/src/app/heroes/heroes.component.ts" region="add" header="src/app/heroes/heroes.component.ts (add)"></code-example>
|
||||
|
||||
When the given name is non-blank, the handler creates a `Hero`-like object
|
||||
from the name (it's only missing the `id`) and passes it to the services `addHero()` method.
|
||||
|
||||
When `addHero()` saves successfully, the `subscribe()` callback
|
||||
When `addHero` saves successfully, the `subscribe` callback
|
||||
receives the new hero and pushes it into to the `heroes` list for display.
|
||||
|
||||
You'll write `HeroService.addHero` in the next section.
|
||||
|
||||
#### Add _HeroService.addHero()_
|
||||
|
||||
Add the following `addHero()` method to the `HeroService` class.
|
||||
|
||||
<code-example path="toh-pt6/src/app/hero.service.ts" region="addHero" header="src/app/hero.service.ts (addHero)"></code-example>
|
||||
|
||||
`addHero()` differs from `updateHero()` in two ways:
|
||||
`HeroService.addHero()` differs from `updateHero` in two ways.
|
||||
|
||||
* It calls `HttpClient.post()` instead of `put()`.
|
||||
* It expects the server to generate an id for the new hero,
|
||||
* it calls `HttpClient.post()` instead of `put()`.
|
||||
* it expects the server to generate an id for the new hero,
|
||||
which it returns in the `Observable<Hero>` to the caller.
|
||||
|
||||
Refresh the browser and add some heroes.
|
||||
@ -317,7 +359,7 @@ Each hero in the heroes list should have a delete button.
|
||||
Add the following button element to the `HeroesComponent` template, after the hero
|
||||
name in the repeated `<li>` element.
|
||||
|
||||
<code-example path="toh-pt6/src/app/heroes/heroes.component.html" header="src/app/hero.service.ts" region="delete"></code-example>
|
||||
<code-example path="toh-pt6/src/app/heroes/heroes.component.html" region="delete"></code-example>
|
||||
|
||||
The HTML for the list of heroes should look like this:
|
||||
|
||||
@ -327,7 +369,7 @@ To position the delete button at the far right of the hero entry,
|
||||
add some CSS to the `heroes.component.css`. You'll find that CSS
|
||||
in the [final review code](#heroescomponent) below.
|
||||
|
||||
Add the `delete()` handler to the component class.
|
||||
Add the `delete()` handler to the component.
|
||||
|
||||
<code-example path="toh-pt6/src/app/heroes/heroes.component.ts" region="delete" header="src/app/heroes/heroes.component.ts (delete)"></code-example>
|
||||
|
||||
@ -337,29 +379,31 @@ The component's `delete()` method immediately removes the _hero-to-delete_ from
|
||||
anticipating that the `HeroService` will succeed on the server.
|
||||
|
||||
There's really nothing for the component to do with the `Observable` returned by
|
||||
`heroService.delete()` **but it must subscribe anyway**.
|
||||
`heroService.delete()`. **It must subscribe anyway**.
|
||||
|
||||
<div class="alert is-important">
|
||||
|
||||
If you neglect to `subscribe()`, the service will not send the delete request to the server.
|
||||
As a rule, an `Observable` _does nothing_ until something subscribes.
|
||||
|
||||
If you neglect to `subscribe()`, the service will not send the delete request to the server!
|
||||
As a rule, an `Observable` _does nothing_ until something subscribes!
|
||||
|
||||
Confirm this for yourself by temporarily removing the `subscribe()`,
|
||||
clicking "Dashboard", then clicking "Heroes".
|
||||
You'll see the full list of heroes again.
|
||||
|
||||
</div>
|
||||
|
||||
Next, add a `deleteHero()` method to `HeroService` like this.
|
||||
#### Add _HeroService.deleteHero()_
|
||||
|
||||
Add a `deleteHero()` method to `HeroService` like this.
|
||||
|
||||
<code-example path="toh-pt6/src/app/hero.service.ts" region="deleteHero" header="src/app/hero.service.ts (delete)"></code-example>
|
||||
|
||||
Note the following key points:
|
||||
Note that
|
||||
|
||||
* `deleteHero()` calls `HttpClient.delete()`.
|
||||
* The URL is the heroes resource URL plus the `id` of the hero to delete.
|
||||
* You don't send data as you did with `put()` and `post()`.
|
||||
* You still send the `httpOptions`.
|
||||
* it calls `HttpClient.delete`.
|
||||
* the URL is the heroes resource URL plus the `id` of the hero to delete
|
||||
* you don't send data as you did with `put` and `post`.
|
||||
* you still send the `httpOptions`.
|
||||
|
||||
Refresh the browser and try the new delete functionality.
|
||||
|
||||
@ -369,36 +413,43 @@ In this last exercise, you learn to chain `Observable` operators together
|
||||
so you can minimize the number of similar HTTP requests
|
||||
and consume network bandwidth economically.
|
||||
|
||||
You will add a heroes search feature to the Dashboard.
|
||||
As the user types a name into a search box,
|
||||
You will add a *heroes search* feature to the *Dashboard*.
|
||||
As the user types a name into a search box,
|
||||
you'll make repeated HTTP requests for heroes filtered by that name.
|
||||
Your goal is to issue only as many requests as necessary.
|
||||
|
||||
#### `HeroService.searchHeroes()`
|
||||
#### _HeroService.searchHeroes_
|
||||
|
||||
Start by adding a `searchHeroes()` method to the `HeroService`.
|
||||
Start by adding a `searchHeroes` method to the `HeroService`.
|
||||
|
||||
<code-example path="toh-pt6/src/app/hero.service.ts" region="searchHeroes" header="src/app/hero.service.ts">
|
||||
<code-example
|
||||
path="toh-pt6/src/app/hero.service.ts"
|
||||
region="searchHeroes"
|
||||
header="src/app/hero.service.ts">
|
||||
</code-example>
|
||||
|
||||
The method returns immediately with an empty array if there is no search term.
|
||||
The rest of it closely resembles `getHeroes()`, the only significant difference being
|
||||
the URL, which includes a query string with the search term.
|
||||
The rest of it closely resembles `getHeroes()`.
|
||||
The only significant difference is the URL,
|
||||
which includes a query string with the search term.
|
||||
|
||||
### Add search to the Dashboard
|
||||
|
||||
Open the `DashboardComponent` template and
|
||||
add the hero search element, `<app-hero-search>`, to the bottom of the markup.
|
||||
Open the `DashboardComponent` _template_ and
|
||||
Add the hero search element, `<app-hero-search>`, to the bottom of the `DashboardComponent` template.
|
||||
|
||||
<code-example path="toh-pt6/src/app/dashboard/dashboard.component.html" header="src/app/dashboard/dashboard.component.html" linenums="false">
|
||||
<code-example
|
||||
path="toh-pt6/src/app/dashboard/dashboard.component.html" header="src/app/dashboard/dashboard.component.html" linenums="false">
|
||||
</code-example>
|
||||
|
||||
This template looks a lot like the `*ngFor` repeater in the `HeroesComponent` template.
|
||||
|
||||
For this to work, the next step is to add a component with a selector that matches `<app-hero-search>`.
|
||||
Unfortunately, adding this element breaks the app.
|
||||
Angular can't find a component with a selector that matches `<app-hero-search>`.
|
||||
|
||||
The `HeroSearchComponent` doesn't exist yet. Fix that.
|
||||
|
||||
### Create `HeroSearchComponent`
|
||||
### Create _HeroSearchComponent_
|
||||
|
||||
Create a `HeroSearchComponent` with the CLI.
|
||||
|
||||
@ -406,62 +457,70 @@ Create a `HeroSearchComponent` with the CLI.
|
||||
ng generate component hero-search
|
||||
</code-example>
|
||||
|
||||
The CLI generates the three `HeroSearchComponent` files and adds the component to the `AppModule` declarations.
|
||||
The CLI generates the three `HeroSearchComponent` files and adds the component to the `AppModule` declarations
|
||||
|
||||
Replace the generated `HeroSearchComponent` template with an `<input>` and a list of matching search results, as follows.
|
||||
Replace the generated `HeroSearchComponent` _template_ with a text box and a list of matching search results like this.
|
||||
|
||||
<code-example path="toh-pt6/src/app/hero-search/hero-search.component.html" header="src/app/hero-search/hero-search.component.html"></code-example>
|
||||
|
||||
Add private CSS styles to `hero-search.component.css`
|
||||
as listed in the [final code review](#herosearchcomponent) below.
|
||||
|
||||
As the user types in the search box, an input event binding calls the
|
||||
component's `search()` method with the new search box value.
|
||||
As the user types in the search box, an *input* event binding calls the component's `search()`
|
||||
method with the new search box value.
|
||||
|
||||
{@a asyncpipe}
|
||||
|
||||
### `AsyncPipe`
|
||||
### _AsyncPipe_
|
||||
|
||||
The `*ngFor` repeats hero objects. Notice that the `*ngFor` iterates over a list called `heroes$`, not `heroes`. The `$` is a convention that indicates `heroes$` is an `Observable`, not an array.
|
||||
As expected, the `*ngFor` repeats hero objects.
|
||||
|
||||
Look closely and you'll see that the `*ngFor` iterates over a list called `heroes$`, not `heroes`.
|
||||
|
||||
<code-example path="toh-pt6/src/app/hero-search/hero-search.component.html" header="src/app/hero-search/hero-search.component.html" region="async"></code-example>
|
||||
<code-example path="toh-pt6/src/app/hero-search/hero-search.component.html" region="async"></code-example>
|
||||
|
||||
Since `*ngFor` can't do anything with an `Observable`, use the
|
||||
pipe character (`|`) followed by `async`. This identifies Angular's `AsyncPipe` and subscribes to an `Observable` automatically so you won't have to
|
||||
The `$` is a convention that indicates `heroes$` is an `Observable`, not an array.
|
||||
|
||||
The `*ngFor` can't do anything with an `Observable`.
|
||||
But there's also a pipe character (`|`) followed by `async`,
|
||||
which identifies Angular's `AsyncPipe`.
|
||||
|
||||
The `AsyncPipe` subscribes to an `Observable` automatically so you won't have to
|
||||
do so in the component class.
|
||||
|
||||
### Edit the `HeroSearchComponent` class
|
||||
### Fix the _HeroSearchComponent_ class
|
||||
|
||||
Replace the generated `HeroSearchComponent` class and metadata as follows.
|
||||
|
||||
<code-example path="toh-pt6/src/app/hero-search/hero-search.component.ts" header="src/app/hero-search/hero-search.component.ts"></code-example>
|
||||
|
||||
Notice the declaration of `heroes$` as an `Observable`:
|
||||
|
||||
<code-example path="toh-pt6/src/app/hero-search/hero-search.component.ts" header="src/app/hero-search/hero-search.component.ts" region="heroes-stream">
|
||||
Notice the declaration of `heroes$` as an `Observable`
|
||||
<code-example
|
||||
path="toh-pt6/src/app/hero-search/hero-search.component.ts"
|
||||
region="heroes-stream">
|
||||
</code-example>
|
||||
|
||||
You'll set it in [`ngOnInit()`](#search-pipe).
|
||||
You'll set it in [`ngOnInit()`](#search-pipe).
|
||||
Before you do, focus on the definition of `searchTerms`.
|
||||
|
||||
### The `searchTerms` RxJS subject
|
||||
### The _searchTerms_ RxJS subject
|
||||
|
||||
The `searchTerms` property is an RxJS `Subject`.
|
||||
The `searchTerms` property is declared as an RxJS `Subject`.
|
||||
|
||||
<code-example path="toh-pt6/src/app/hero-search/hero-search.component.ts" header="src/app/hero-search/hero-search.component.ts" region="searchTerms"></code-example>
|
||||
<code-example path="toh-pt6/src/app/hero-search/hero-search.component.ts" region="searchTerms"></code-example>
|
||||
|
||||
A `Subject` is both a source of observable values and an `Observable` itself.
|
||||
A `Subject` is both a source of _observable_ values and an `Observable` itself.
|
||||
You can subscribe to a `Subject` as you would any `Observable`.
|
||||
|
||||
You can also push values into that `Observable` by calling its `next(value)` method
|
||||
as the `search()` method does.
|
||||
|
||||
The event binding to the textbox's `input` event calls the `search()` method.
|
||||
The `search()` method is called via an _event binding_ to the
|
||||
textbox's `input` event.
|
||||
|
||||
<code-example path="toh-pt6/src/app/hero-search/hero-search.component.html" header="src/app/hero-search/hero-search.component.html" region="input"></code-example>
|
||||
<code-example path="toh-pt6/src/app/hero-search/hero-search.component.html" region="input"></code-example>
|
||||
|
||||
Every time the user types in the textbox, the binding calls `search()` with the textbox value, a "search term".
|
||||
Every time the user types in the textbox, the binding calls `search()` with the textbox value, a "search term".
|
||||
The `searchTerms` becomes an `Observable` emitting a steady stream of search terms.
|
||||
|
||||
{@a search-pipe}
|
||||
@ -469,24 +528,28 @@ The `searchTerms` becomes an `Observable` emitting a steady stream of search ter
|
||||
### Chaining RxJS operators
|
||||
|
||||
Passing a new search term directly to the `searchHeroes()` after every user keystroke would create an excessive amount of HTTP requests,
|
||||
taxing server resources and burning through data plans.
|
||||
taxing server resources and burning through the cellular network data plan.
|
||||
|
||||
Instead, the `ngOnInit()` method pipes the `searchTerms` observable through a sequence of RxJS operators that reduce the number of calls to the `searchHeroes()`,
|
||||
ultimately returning an observable of timely hero search results (each a `Hero[]`).
|
||||
|
||||
Here's a closer look at the code.
|
||||
Here's the code.
|
||||
|
||||
<code-example path="toh-pt6/src/app/hero-search/hero-search.component.ts" header="src/app/hero-search/hero-search.component.ts" region="search">
|
||||
<code-example
|
||||
path="toh-pt6/src/app/hero-search/hero-search.component.ts"
|
||||
region="search">
|
||||
</code-example>
|
||||
|
||||
Each operator works as follows:
|
||||
|
||||
|
||||
* `debounceTime(300)` waits until the flow of new string events pauses for 300 milliseconds
|
||||
before passing along the latest string. You'll never make requests more frequently than 300ms.
|
||||
|
||||
|
||||
* `distinctUntilChanged()` ensures that a request is sent only if the filter text changed.
|
||||
|
||||
* `switchMap()` calls the search service for each search term that makes it through `debounce()` and `distinctUntilChanged()`.
|
||||
|
||||
* `switchMap()` calls the search service for each search term that makes it through `debounce` and `distinctUntilChanged`.
|
||||
It cancels and discards previous search observables, returning only the latest search service observable.
|
||||
|
||||
|
||||
@ -500,7 +563,7 @@ It cancels and discards previous search observables, returning only the latest s
|
||||
`switchMap()` preserves the original request order while returning only the observable from the most recent HTTP method call.
|
||||
Results from prior calls are canceled and discarded.
|
||||
|
||||
Note that canceling a previous `searchHeroes()` Observable
|
||||
Note that _canceling_ a previous `searchHeroes()` _Observable_
|
||||
doesn't actually abort a pending HTTP request.
|
||||
Unwanted results are simply discarded before they reach your application code.
|
||||
|
||||
@ -527,78 +590,78 @@ Here are the code files discussed on this page (all in the `src/app/` folder).
|
||||
{@a heroservice}
|
||||
{@a inmemorydataservice}
|
||||
{@a appmodule}
|
||||
#### `HeroService`, `InMemoryDataService`, `AppModule`
|
||||
#### _HeroService_, _InMemoryDataService_, _AppModule_
|
||||
|
||||
<code-tabs>
|
||||
<code-pane
|
||||
header="hero.service.ts"
|
||||
<code-pane
|
||||
header="hero.service.ts"
|
||||
path="toh-pt6/src/app/hero.service.ts">
|
||||
</code-pane>
|
||||
<code-pane
|
||||
<code-pane
|
||||
header="in-memory-data.service.ts"
|
||||
path="toh-pt6/src/app/in-memory-data.service.ts">
|
||||
</code-pane>
|
||||
<code-pane
|
||||
header="app.module.ts"
|
||||
<code-pane
|
||||
header="app.module.ts"
|
||||
path="toh-pt6/src/app/app.module.ts">
|
||||
</code-pane>
|
||||
</code-tabs>
|
||||
|
||||
{@a heroescomponent}
|
||||
#### `HeroesComponent`
|
||||
#### _HeroesComponent_
|
||||
|
||||
<code-tabs>
|
||||
<code-pane
|
||||
header="heroes/heroes.component.html"
|
||||
<code-pane
|
||||
header="heroes/heroes.component.html"
|
||||
path="toh-pt6/src/app/heroes/heroes.component.html">
|
||||
</code-pane>
|
||||
<code-pane
|
||||
header="heroes/heroes.component.ts"
|
||||
<code-pane
|
||||
header="heroes/heroes.component.ts"
|
||||
path="toh-pt6/src/app/heroes/heroes.component.ts">
|
||||
</code-pane>
|
||||
<code-pane
|
||||
header="heroes/heroes.component.css"
|
||||
<code-pane
|
||||
header="heroes/heroes.component.css"
|
||||
path="toh-pt6/src/app/heroes/heroes.component.css">
|
||||
</code-pane>
|
||||
</code-tabs>
|
||||
|
||||
{@a herodetailcomponent}
|
||||
#### `HeroDetailComponent`
|
||||
#### _HeroDetailComponent_
|
||||
|
||||
<code-tabs>
|
||||
<code-pane
|
||||
<code-pane
|
||||
header="hero-detail/hero-detail.component.html"
|
||||
path="toh-pt6/src/app/hero-detail/hero-detail.component.html">
|
||||
</code-pane>
|
||||
<code-pane
|
||||
header="hero-detail/hero-detail.component.ts"
|
||||
<code-pane
|
||||
header="hero-detail/hero-detail.component.ts"
|
||||
path="toh-pt6/src/app/hero-detail/hero-detail.component.ts">
|
||||
</code-pane>
|
||||
</code-tabs>
|
||||
|
||||
{@a dashboardcomponent}
|
||||
#### `DashboardComponent`
|
||||
#### _DashboardComponent_
|
||||
|
||||
<code-tabs>
|
||||
<code-pane
|
||||
<code-pane
|
||||
header="src/app/dashboard/dashboard.component.html"
|
||||
path="toh-pt6/src/app/dashboard/dashboard.component.html">
|
||||
</code-pane>
|
||||
</code-tabs>
|
||||
|
||||
{@a herosearchcomponent}
|
||||
#### `HeroSearchComponent`
|
||||
#### _HeroSearchComponent_
|
||||
|
||||
<code-tabs>
|
||||
<code-pane
|
||||
<code-pane
|
||||
header="hero-search/hero-search.component.html"
|
||||
path="toh-pt6/src/app/hero-search/hero-search.component.html">
|
||||
</code-pane>
|
||||
<code-pane
|
||||
<code-pane
|
||||
header="hero-search/hero-search.component.ts"
|
||||
path="toh-pt6/src/app/hero-search/hero-search.component.ts">
|
||||
</code-pane>
|
||||
<code-pane
|
||||
<code-pane
|
||||
header="hero-search/hero-search.component.css"
|
||||
path="toh-pt6/src/app/hero-search/hero-search.component.css">
|
||||
</code-pane>
|
||||
|
@ -10,22 +10,17 @@ module.exports = function (config) {
|
||||
require('karma-chrome-launcher'),
|
||||
require('karma-jasmine-html-reporter'),
|
||||
require('karma-coverage-istanbul-reporter'),
|
||||
require('@angular-devkit/build-angular/plugins/karma'),
|
||||
{'reporter:jasmine-seed': ['type', JasmineSeedReporter]},
|
||||
require('@angular-devkit/build-angular/plugins/karma')
|
||||
],
|
||||
client: {
|
||||
clearContext: false, // leave Jasmine Spec Runner output visible in browser
|
||||
jasmine: {
|
||||
random: true,
|
||||
seed: '',
|
||||
},
|
||||
clearContext: false // leave Jasmine Spec Runner output visible in browser
|
||||
},
|
||||
coverageIstanbulReporter: {
|
||||
dir: require('path').join(__dirname, './coverage/site'),
|
||||
reports: ['html', 'lcovonly', 'text-summary'],
|
||||
fixWebpackSourcePaths: true,
|
||||
fixWebpackSourcePaths: true
|
||||
},
|
||||
reporters: ['progress', 'kjhtml', 'jasmine-seed'],
|
||||
reporters: ['progress', 'kjhtml'],
|
||||
port: 9876,
|
||||
colors: true,
|
||||
logLevel: config.LOG_INFO,
|
||||
@ -33,18 +28,6 @@ module.exports = function (config) {
|
||||
browsers: ['Chrome'],
|
||||
browserNoActivityTimeout: 60000,
|
||||
singleRun: false,
|
||||
restartOnFileChange: true,
|
||||
restartOnFileChange: true
|
||||
});
|
||||
};
|
||||
|
||||
// Helpers
|
||||
function JasmineSeedReporter(baseReporterDecorator) {
|
||||
baseReporterDecorator(this);
|
||||
|
||||
this.onBrowserComplete = (browser, result) => {
|
||||
const seed = result.order && result.order.random && result.order.seed;
|
||||
if (seed) this.write(`${browser}: Randomized with seed ${seed}.\n`);
|
||||
};
|
||||
|
||||
this.onRunComplete = () => undefined;
|
||||
}
|
||||
|
@ -19,7 +19,7 @@
|
||||
"build-local": "yarn ~~build",
|
||||
"prebuild-with-ivy": "yarn setup-local && node scripts/switch-to-ivy",
|
||||
"build-with-ivy": "yarn ~~build",
|
||||
"extract-cli-command-docs": "node tools/transforms/cli-docs-package/extract-cli-commands.js f99913e9f",
|
||||
"extract-cli-command-docs": "node tools/transforms/cli-docs-package/extract-cli-commands.js 7da10691d",
|
||||
"lint": "yarn check-env && yarn docs-lint && ng lint && yarn example-lint && yarn tools-lint",
|
||||
"test": "yarn check-env && ng test",
|
||||
"pree2e": "yarn check-env && yarn update-webdriver",
|
||||
@ -33,10 +33,8 @@
|
||||
"set-opensearch-url": "node --eval \"const sh = require('shelljs'); sh.set('-e'); sh.sed('-i', /PLACEHOLDER_URL/g, process.argv[1], 'dist/assets/opensearch.xml');\"",
|
||||
"presmoke-tests": "yarn update-webdriver",
|
||||
"smoke-tests": "protractor tests/deployment/e2e/protractor.conf.js --suite smoke --baseUrl",
|
||||
"test-a11y-score": "node scripts/test-aio-a11y",
|
||||
"test-a11y-score-localhost": "run-p --race \"~~light-server -s dist -p 4200 --quiet\" \"test-a11y-score http://localhost:4200\" --",
|
||||
"test-pwa-score": "run-s \"~~audit-web-app {1} all:0,pwa:{2} {3}\" --",
|
||||
"test-pwa-score-localhost": "run-p --race \"~~light-server -s dist -p 4200 --quiet\" \"test-pwa-score http://localhost:4200 {1} {2}\" --",
|
||||
"test-pwa-score": "node scripts/test-pwa-score",
|
||||
"test-pwa-score-localhost": "run-p --race \"~~http-server dist -p 4200 --silent\" \"test-pwa-score http://localhost:4200 {1} {2}\" --",
|
||||
"example-e2e": "yarn example-check-local && node ./tools/examples/run-example-e2e",
|
||||
"example-lint": "tslint --config \"content/examples/tslint.json\" \"content/examples/**/*.ts\" --exclude \"content/examples/styleguide/**/*.avoid.ts\"",
|
||||
"example-use-local": "node tools/ng-packages-installer overwrite ./tools/examples/shared --debug",
|
||||
@ -66,16 +64,15 @@
|
||||
"generate-zips": "node ./tools/example-zipper/generateZips",
|
||||
"build-404-page": "node scripts/build-404-page",
|
||||
"update-webdriver": "webdriver-manager update --standalone false --gecko false $CI_CHROMEDRIVER_VERSION_ARG",
|
||||
"~~audit-web-app": "node scripts/audit-web-app",
|
||||
"~~check-env": "node scripts/check-environment",
|
||||
"~~clean-generated": "node --eval \"require('shelljs').rm('-rf', 'src/generated')\"",
|
||||
"~~build": "ng build --configuration=stable",
|
||||
"post~~build": "yarn build-404-page",
|
||||
"~~light-server": "light-server --bind=localhost --historyindex=/index.html --no-reload"
|
||||
"~~http-server": "http-server"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10.9.0 <11.0.0",
|
||||
"yarn": ">=1.12.1 <=1.16.0"
|
||||
"yarn": ">=1.12.1 <=1.14.0"
|
||||
},
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
@ -109,13 +106,13 @@
|
||||
"archiver": "^1.3.0",
|
||||
"canonical-path": "1.0.0",
|
||||
"chalk": "^2.1.0",
|
||||
"chrome-launcher": "^0.10.7",
|
||||
"chrome-launcher": "^0.10.5",
|
||||
"cjson": "^0.5.0",
|
||||
"codelyzer": "^5.0.0",
|
||||
"cross-spawn": "^5.1.0",
|
||||
"css-selector-parser": "^1.3.0",
|
||||
"dgeni": "^0.4.11",
|
||||
"dgeni-packages": "^0.27.5",
|
||||
"dgeni-packages": "^0.27.1",
|
||||
"entities": "^1.1.1",
|
||||
"eslint": "^3.19.0",
|
||||
"eslint-plugin-jasmine": "^2.2.0",
|
||||
@ -126,23 +123,24 @@
|
||||
"hast-util-is-element": "^1.0.0",
|
||||
"hast-util-to-string": "^1.0.0",
|
||||
"html": "^1.0.0",
|
||||
"http-server": "^0.11.1",
|
||||
"ignore": "^3.3.3",
|
||||
"image-size": "^0.5.1",
|
||||
"jasmine": "^3.4.0",
|
||||
"jasmine-core": "^3.4.0",
|
||||
"jasmine-spec-reporter": "^4.2.1",
|
||||
"jasmine": "^2.6.0",
|
||||
"jasmine-core": "^2.8.0",
|
||||
"jasmine-marbles": "^0.3.1",
|
||||
"jasmine-spec-reporter": "^4.1.0",
|
||||
"jasmine-ts": "^0.2.1",
|
||||
"jsdom": "^9.12.0",
|
||||
"json-schema-traverse": "^0.4.1",
|
||||
"json5": "^1.0.1",
|
||||
"karma": "^4.1.0",
|
||||
"karma-chrome-launcher": "^2.2.0",
|
||||
"karma-cli": "^2.0.0",
|
||||
"karma-coverage-istanbul-reporter": "^2.0.5",
|
||||
"karma-jasmine": "^2.0.1",
|
||||
"karma-jasmine-html-reporter": "^1.4.2",
|
||||
"light-server": "^2.6.2",
|
||||
"lighthouse": "^5.1.0",
|
||||
"karma": "^1.7.0",
|
||||
"karma-chrome-launcher": "^2.1.1",
|
||||
"karma-cli": "^1.0.1",
|
||||
"karma-coverage-istanbul-reporter": "^1.3.0",
|
||||
"karma-jasmine": "^1.1.0",
|
||||
"karma-jasmine-html-reporter": "^0.2.2",
|
||||
"lighthouse": "^4.3.0",
|
||||
"lighthouse-logger": "^1.2.0",
|
||||
"lodash": "^4.17.4",
|
||||
"lunr": "^2.1.0",
|
||||
@ -169,4 +167,4 @@
|
||||
"xregexp": "^4.0.0",
|
||||
"yargs": "^7.0.2"
|
||||
}
|
||||
}
|
||||
}
|
@ -16,8 +16,8 @@
|
||||
"uncompressed": {
|
||||
"runtime-es5": 3042,
|
||||
"runtime-es2015": 3048,
|
||||
"main-es5": 511036,
|
||||
"main-es2015": 450486,
|
||||
"main-es5": 504845,
|
||||
"main-es2015": 443921,
|
||||
"polyfills-es5": 130136,
|
||||
"polyfills-es2015": 52931
|
||||
}
|
||||
@ -28,8 +28,8 @@
|
||||
"uncompressed": {
|
||||
"runtime-es5": 2932,
|
||||
"runtime-es2015": 2938,
|
||||
"main-es5": 565073,
|
||||
"main-es2015": 583108,
|
||||
"main-es5": 563201,
|
||||
"main-es2015": 580852,
|
||||
"polyfills-es5": 130136,
|
||||
"polyfills-es2015": 52931
|
||||
}
|
||||
|
@ -1,179 +0,0 @@
|
||||
#!/bin/env node
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Usage:
|
||||
* ```sh
|
||||
* node scripts/audit-web-app <url> <min-scores> [<log-file>]
|
||||
* ```
|
||||
*
|
||||
* Runs audits against the specified URL on specific categories (accessibility, best practices, performance, PWA, SEO).
|
||||
* It fails, if the score in any category is below the score specified in `<min-scores>`. (Only runs audits for the
|
||||
* specified categories.)
|
||||
*
|
||||
* `<min-scores>` is either a number (in which case it is interpreted as `all:<min-score>`) or a list of comma-separated
|
||||
* strings of the form `key:value`, where `key` is one of `accessibility`, `best-practices`, `performance`, `pwa`, `seo`
|
||||
* or `all` and `value` is a number (between 0 and 100).
|
||||
*
|
||||
* Examples:
|
||||
* - `95` _(Same as `all:95`.)_
|
||||
* - `all:95` _(Run audits for all categories and require a score of 95 or higher.)_
|
||||
* - `all:95,pwa:100` _(Same as `all:95`, except that a scope of 100 is required for the `pwa` category.)_
|
||||
* - `performance:90` _(Only run audits for the `performance` category and require a score of 90 or higher.)_
|
||||
*
|
||||
* If `<log-file>` is defined, the full results will be logged there.
|
||||
*
|
||||
* (Skips HTTPS-related audits, when run for an HTTP URL.)
|
||||
*/
|
||||
|
||||
// Imports
|
||||
const chromeLauncher = require('chrome-launcher');
|
||||
const lighthouse = require('lighthouse');
|
||||
const printer = require('lighthouse/lighthouse-cli/printer');
|
||||
const logger = require('lighthouse-logger');
|
||||
|
||||
// Constants
|
||||
const AUDIT_CATEGORIES = ['accessibility', 'best-practices', 'performance', 'pwa', 'seo'];
|
||||
const CHROME_LAUNCH_OPTS = {chromeFlags: ['--headless']};
|
||||
const LIGHTHOUSE_FLAGS = {logLevel: process.env.CI ? 'error' : 'info'}; // Be less verbose on CI.
|
||||
const SKIPPED_HTTPS_AUDITS = ['redirects-http', 'uses-http2'];
|
||||
const VIEWER_URL = 'https://googlechrome.github.io/lighthouse/viewer';
|
||||
const WAIT_FOR_SW_DELAY = 5000;
|
||||
|
||||
// Run
|
||||
_main(process.argv.slice(2));
|
||||
|
||||
// Functions - Definitions
|
||||
async function _main(args) {
|
||||
const {url, minScores, logFile} = parseInput(args);
|
||||
const isOnHttp = /^http:/.test(url);
|
||||
const lhFlags = {...LIGHTHOUSE_FLAGS, onlyCategories: Object.keys(minScores).sort()};
|
||||
const lhConfig = {
|
||||
extends: 'lighthouse:default',
|
||||
// Since the Angular ServiceWorker waits for the app to stabilize before registering,
|
||||
// wait a few seconds after load to allow Lighthouse to reliably detect it.
|
||||
passes: [{passName: 'defaultPass', pauseAfterLoadMs: WAIT_FOR_SW_DELAY}],
|
||||
};
|
||||
|
||||
console.log(`Running web-app audits for '${url}'...`);
|
||||
console.log(` Audit categories: ${lhFlags.onlyCategories.join(', ')}`);
|
||||
|
||||
// If testing on HTTP, skip HTTPS-specific tests.
|
||||
// (Note: Browsers special-case localhost and run ServiceWorker even on HTTP.)
|
||||
if (isOnHttp) skipHttpsAudits(lhConfig);
|
||||
|
||||
logger.setLevel(lhFlags.logLevel);
|
||||
|
||||
try {
|
||||
console.log('');
|
||||
const startTime = Date.now();
|
||||
const results = await launchChromeAndRunLighthouse(url, lhFlags, lhConfig);
|
||||
const success = await processResults(results, minScores, logFile);
|
||||
console.log(`\n(Completed in ${((Date.now() - startTime) / 1000).toFixed(1)}s.)\n`);
|
||||
|
||||
if (!success) {
|
||||
throw new Error('One or more scores are too low.');
|
||||
}
|
||||
} catch (err) {
|
||||
onError(err);
|
||||
}
|
||||
}
|
||||
|
||||
function formatScore(score) {
|
||||
return `${(score * 100).toFixed(0).padStart(3)}`;
|
||||
}
|
||||
|
||||
async function launchChromeAndRunLighthouse(url, flags, config) {
|
||||
const chrome = await chromeLauncher.launch(CHROME_LAUNCH_OPTS);
|
||||
flags.port = chrome.port;
|
||||
|
||||
try {
|
||||
return await lighthouse(url, flags, config);
|
||||
} finally {
|
||||
await chrome.kill();
|
||||
}
|
||||
}
|
||||
|
||||
function onError(err) {
|
||||
console.error(err);
|
||||
console.error('');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
function parseInput(args) {
|
||||
const [url, minScoresRaw, logFile] = args;
|
||||
|
||||
if (!url) {
|
||||
onError('Invalid arguments: <url> not specified.');
|
||||
} else if (!minScoresRaw) {
|
||||
onError('Invalid arguments: <min-scores> not specified.');
|
||||
}
|
||||
|
||||
const minScores = parseMinScores(minScoresRaw || '');
|
||||
const unknownCategories = Object.keys(minScores).filter(cat => !AUDIT_CATEGORIES.includes(cat));
|
||||
const allValuesValid = Object.values(minScores).every(x => (0 <= x) && (x <= 1));
|
||||
|
||||
if (unknownCategories.length > 0) {
|
||||
onError(`Invalid arguments: <min-scores> contains unknown category(-ies): ${unknownCategories.join(', ')}`);
|
||||
} else if (!allValuesValid) {
|
||||
onError(`Invalid arguments: <min-scores> has non-numeric or out-of-range values: ${minScoresRaw}`);
|
||||
}
|
||||
|
||||
return {url, minScores, logFile};
|
||||
}
|
||||
|
||||
function parseMinScores(raw) {
|
||||
const minScores = {};
|
||||
|
||||
if (/^\d+$/.test(raw)) {
|
||||
raw = `all:${raw}`;
|
||||
}
|
||||
|
||||
raw.
|
||||
split(',').
|
||||
map(x => x.split(':')).
|
||||
forEach(([key, val]) => minScores[key] = Number(val) / 100);
|
||||
|
||||
if (minScores.hasOwnProperty('all')) {
|
||||
AUDIT_CATEGORIES.forEach(cat => minScores.hasOwnProperty(cat) || (minScores[cat] = minScores.all));
|
||||
delete minScores.all;
|
||||
}
|
||||
|
||||
return minScores;
|
||||
}
|
||||
|
||||
async function processResults(results, minScores, logFile) {
|
||||
const lhVersion = results.lhr.lighthouseVersion;
|
||||
const categories = results.lhr.categories;
|
||||
const report = results.report;
|
||||
|
||||
if (logFile) {
|
||||
console.log(`\nSaving results in '${logFile}'...`);
|
||||
console.log(` LightHouse viewer: ${VIEWER_URL}`);
|
||||
|
||||
await printer.write(report, printer.OutputMode.json, logFile);
|
||||
}
|
||||
|
||||
console.log(`\nLighthouse version: ${lhVersion}`);
|
||||
console.log('\nAudit results:');
|
||||
|
||||
const maxTitleLen = Math.max(...Object.values(categories).map(({title}) => title.length));
|
||||
const success = Object.keys(categories).sort().reduce((aggr, cat) => {
|
||||
const {title, score} = categories[cat];
|
||||
const paddedTitle = `${title}:`.padEnd(maxTitleLen + 1);
|
||||
const minScore = minScores[cat];
|
||||
const passed = !isNaN(score) && (score >= minScore);
|
||||
|
||||
console.log(
|
||||
` - ${paddedTitle} ${formatScore(score)} (Required: ${formatScore(minScore)}) ${passed ? 'OK' : 'FAILED'}`);
|
||||
|
||||
return aggr && passed;
|
||||
}, true);
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
function skipHttpsAudits(config) {
|
||||
console.log(` Skipping HTTPS-related audits: ${SKIPPED_HTTPS_AUDITS.join(', ')}`);
|
||||
config.settings = {...config.settings, skipAudits: SKIPPED_HTTPS_AUDITS};
|
||||
}
|
@ -1,38 +0,0 @@
|
||||
#!/bin/env node
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Usage:
|
||||
* ```sh
|
||||
* node scripts/test-aio-a11y <origin>
|
||||
* ```
|
||||
*
|
||||
* Runs accessibility audits on several (pre-defined) pages on the specified origin. It fails, if
|
||||
* the score for any page is below the minimum (see `MIN_SCORES_PER_PAGE` below).
|
||||
*
|
||||
* `<origin>` is the origin (scheme + hostname + port) of an angular.io deployment. It can be remote
|
||||
* (e.g. `https://next.angular.io`) or local (e.g. `http://localhost:4200`).
|
||||
*/
|
||||
|
||||
// Imports
|
||||
const sh = require('shelljs');
|
||||
sh.set('-e');
|
||||
|
||||
// Constants
|
||||
const MIN_SCORES_PER_PAGE = {
|
||||
'': 100,
|
||||
'api': 100,
|
||||
'api/core/Directive': 90,
|
||||
'cli': 91,
|
||||
'cli/add': 91,
|
||||
'docs': 100,
|
||||
'guide/docs-style-guide': 88,
|
||||
'start': 90,
|
||||
};
|
||||
|
||||
// Run
|
||||
const auditWebAppCmd = `"${process.execPath}" "${__dirname}/audit-web-app"`;
|
||||
const origin = process.argv[2];
|
||||
for (const [page, minScore] of Object.entries(MIN_SCORES_PER_PAGE)) {
|
||||
sh.exec(`${auditWebAppCmd} ${origin}/${page} accessibility:${minScore}`);
|
||||
}
|
@ -25,8 +25,5 @@ set +x -eu -o pipefail
|
||||
# Run PWA-score tests.
|
||||
yarn test-pwa-score "$targetUrl" "$minPwaScore"
|
||||
|
||||
# Run a11y tests.
|
||||
yarn test-a11y-score "$targetUrl"
|
||||
|
||||
echo -e "\nAll checks passed!"
|
||||
)
|
||||
|
135
aio/scripts/test-pwa-score.js
Normal file
135
aio/scripts/test-pwa-score.js
Normal file
@ -0,0 +1,135 @@
|
||||
#!/bin/env node
|
||||
|
||||
/**
|
||||
* Usage:
|
||||
* ```sh
|
||||
* node scripts/test-pwa-score <url> <min-score> [<log-file>]
|
||||
* ```
|
||||
*
|
||||
* Fails if the score is below `<min-score>`.
|
||||
* If `<log-file>` is defined, the full results will be logged there.
|
||||
*
|
||||
* (Skips HTTPS-related audits, when run for HTTP URL.)
|
||||
*/
|
||||
|
||||
// Imports
|
||||
const chromeLauncher = require('chrome-launcher');
|
||||
const lighthouse = require('lighthouse');
|
||||
const printer = require('lighthouse/lighthouse-cli/printer');
|
||||
const logger = require('lighthouse-logger');
|
||||
|
||||
// Constants
|
||||
const CHROME_LAUNCH_OPTS = {};
|
||||
const LIGHTHOUSE_FLAGS = {logLevel: 'info'};
|
||||
const SKIPPED_HTTPS_AUDITS = ['redirects-http'];
|
||||
const VIEWER_URL = 'https://googlechrome.github.io/lighthouse/viewer/';
|
||||
const WAIT_FOR_SW_DELAY = 5000;
|
||||
|
||||
// Be less verbose on CI.
|
||||
if (process.env.CI) {
|
||||
LIGHTHOUSE_FLAGS.logLevel = 'error';
|
||||
}
|
||||
|
||||
// Run
|
||||
_main(process.argv.slice(2));
|
||||
|
||||
// Functions - Definitions
|
||||
async function _main(args) {
|
||||
const {url, minScore, logFile} = parseInput(args);
|
||||
const isOnHttp = /^http:/.test(url);
|
||||
const config = {
|
||||
extends: 'lighthouse:default',
|
||||
// Since the Angular ServiceWorker waits for the app to stabilize before registering,
|
||||
// wait a few seconds after load to allow Lighthouse to reliably detect it.
|
||||
passes: [{passName: 'defaultPass', pauseAfterLoadMs: WAIT_FOR_SW_DELAY}],
|
||||
}
|
||||
|
||||
console.log(`Running PWA audit for '${url}'...`);
|
||||
|
||||
// If testing on HTTP, skip HTTPS-specific tests.
|
||||
// (Note: Browsers special-case localhost and run ServiceWorker even on HTTP.)
|
||||
if (isOnHttp) skipHttpsAudits(config);
|
||||
|
||||
logger.setLevel(LIGHTHOUSE_FLAGS.logLevel);
|
||||
|
||||
try {
|
||||
const results = await launchChromeAndRunLighthouse(url, LIGHTHOUSE_FLAGS, config);
|
||||
const score = await processResults(results, logFile);
|
||||
evaluateScore(minScore, score);
|
||||
} catch (err) {
|
||||
onError(err);
|
||||
}
|
||||
}
|
||||
|
||||
function evaluateScore(expectedScore, actualScore) {
|
||||
console.log('\nLighthouse PWA score:');
|
||||
console.log(` - Expected: ${expectedScore.toFixed(0).padStart(3)} / 100 (or higher)`);
|
||||
console.log(` - Actual: ${actualScore.toFixed(0).padStart(3)} / 100\n`);
|
||||
|
||||
if (isNaN(actualScore) || (actualScore < expectedScore)) {
|
||||
throw new Error(`PWA score is too low. (${actualScore} < ${expectedScore})`);
|
||||
}
|
||||
}
|
||||
|
||||
async function launchChromeAndRunLighthouse(url, flags, config) {
|
||||
const chrome = await chromeLauncher.launch(CHROME_LAUNCH_OPTS);
|
||||
flags.port = chrome.port;
|
||||
|
||||
try {
|
||||
return await lighthouse(url, flags, config);
|
||||
} finally {
|
||||
await chrome.kill();
|
||||
}
|
||||
}
|
||||
|
||||
function onError(err) {
|
||||
console.error(err);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
function parseInput(args) {
|
||||
const url = args[0];
|
||||
const minScore = Number(args[1]);
|
||||
const logFile = args[2];
|
||||
|
||||
if (!url) {
|
||||
onError('Invalid arguments: <URL> not specified.');
|
||||
} else if (isNaN(minScore)) {
|
||||
onError('Invalid arguments: <MIN_SCORE> not specified or not a number.');
|
||||
}
|
||||
|
||||
return {url, minScore, logFile};
|
||||
}
|
||||
|
||||
async function processResults(results, logFile) {
|
||||
const lhVersion = results.lhr.lighthouseVersion;
|
||||
const categories = results.lhr.categories;
|
||||
const report = results.report;
|
||||
|
||||
if (logFile) {
|
||||
console.log(`\nSaving results in '${logFile}'...`);
|
||||
console.log(`(LightHouse viewer: ${VIEWER_URL})`);
|
||||
|
||||
await printer.write(report, printer.OutputMode.json, logFile);
|
||||
}
|
||||
|
||||
const categoryData = Object.keys(categories).map(name => categories[name]);
|
||||
const maxTitleLen = Math.max(...categoryData.map(({title}) => title.length));
|
||||
|
||||
console.log(`\nLighthouse version: ${lhVersion}`);
|
||||
|
||||
console.log('\nAudit scores:');
|
||||
categoryData.forEach(({title, score}) => {
|
||||
const paddedTitle = `${title}:`.padEnd(maxTitleLen + 1);
|
||||
const paddedScore = (score * 100).toFixed(0).padStart(3);
|
||||
console.log(` - ${paddedTitle} ${paddedScore} / 100`);
|
||||
});
|
||||
|
||||
return categories.pwa.score * 100;
|
||||
}
|
||||
|
||||
function skipHttpsAudits(config) {
|
||||
console.info(`Skipping HTTPS-related audits (${SKIPPED_HTTPS_AUDITS.join(', ')})...`);
|
||||
const settings = config.settings || (config.settings = {});
|
||||
settings.skipAudits = SKIPPED_HTTPS_AUDITS;
|
||||
}
|
@ -630,11 +630,6 @@ describe('AppComponent', () => {
|
||||
};
|
||||
|
||||
|
||||
beforeEach(() => {
|
||||
tocContainer = null;
|
||||
toc = null;
|
||||
});
|
||||
|
||||
it('should show/hide `<aio-toc>` based on `hasFloatingToc`', () => {
|
||||
expect(tocContainer).toBeFalsy();
|
||||
expect(toc).toBeFalsy();
|
||||
|
@ -40,7 +40,6 @@ describe('AnnouncementBarComponent', () => {
|
||||
it('should make a single request to the server', () => {
|
||||
component.ngOnInit();
|
||||
httpMock.expectOne('generated/announcements.json');
|
||||
expect().nothing(); // Prevent jasmine from complaining about no expectations.
|
||||
});
|
||||
|
||||
it('should set the announcement to the first "live" one in the list loaded from `announcements.json`', () => {
|
||||
|
@ -15,7 +15,7 @@
|
||||
</aio-select>
|
||||
|
||||
<div class="form-search">
|
||||
<input #filter placeholder="Filter" (input)="setQuery($event.target.value)" aria-label="Filter Search">
|
||||
<input #filter placeholder="Filter" (input)="setQuery($event.target.value)">
|
||||
<i class="material-icons">search</i>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -29,7 +29,6 @@ describe('ApiService', () => {
|
||||
|
||||
it('should not immediately connect to the server', () => {
|
||||
httpMock.expectNone({});
|
||||
expect().nothing(); // Prevent jasmine from complaining about no expectations.
|
||||
});
|
||||
|
||||
it('subscribers should be completed/unsubscribed when service destroyed', () => {
|
||||
@ -92,7 +91,6 @@ describe('ApiService', () => {
|
||||
it('should connect to the server w/ expected URL', () => {
|
||||
service.fetchSections();
|
||||
httpMock.expectOne('generated/docs/api/api-list.json');
|
||||
expect().nothing(); // Prevent jasmine from complaining about no expectations.
|
||||
});
|
||||
|
||||
it('should refresh the #sections observable w/ new content on second call', () => {
|
||||
|
@ -5,8 +5,6 @@ import { CodeExampleComponent } from './code-example.component';
|
||||
import { CodeExampleModule } from './code-example.module';
|
||||
import { Logger } from 'app/shared/logger.service';
|
||||
import { MockLogger } from 'testing/logger.service';
|
||||
import { MockPrettyPrinter } from 'testing/pretty-printer.service';
|
||||
import { PrettyPrinter } from './pretty-printer.service';
|
||||
|
||||
describe('CodeExampleComponent', () => {
|
||||
let hostComponent: HostComponent;
|
||||
@ -21,7 +19,6 @@ describe('CodeExampleComponent', () => {
|
||||
],
|
||||
providers: [
|
||||
{ provide: Logger, useClass: MockLogger },
|
||||
{ provide: PrettyPrinter, useClass: MockPrettyPrinter },
|
||||
]
|
||||
});
|
||||
|
||||
|
@ -2,12 +2,10 @@ import { Component, ViewChild, NO_ERRORS_SCHEMA } from '@angular/core';
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { Logger } from 'app/shared/logger.service';
|
||||
import { MockLogger } from 'testing/logger.service';
|
||||
import { MockPrettyPrinter } from 'testing/pretty-printer.service';
|
||||
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
|
||||
|
||||
import { CodeTabsComponent } from './code-tabs.component';
|
||||
import { CodeTabsModule } from './code-tabs.module';
|
||||
import { PrettyPrinter } from './pretty-printer.service';
|
||||
|
||||
describe('CodeTabsComponent', () => {
|
||||
let fixture: ComponentFixture<HostComponent>;
|
||||
@ -21,7 +19,6 @@ describe('CodeTabsComponent', () => {
|
||||
schemas: [ NO_ERRORS_SCHEMA ],
|
||||
providers: [
|
||||
{ provide: Logger, useClass: MockLogger },
|
||||
{ provide: PrettyPrinter, useClass: MockPrettyPrinter },
|
||||
]
|
||||
});
|
||||
|
||||
|
@ -1,104 +1,114 @@
|
||||
import { Component, ViewChild, AfterViewInit } from '@angular/core';
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { MatSnackBar } from '@angular/material/snack-bar';
|
||||
import { By } from '@angular/platform-browser';
|
||||
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
|
||||
import { first } from 'rxjs/operators';
|
||||
|
||||
import { CodeComponent } from './code.component';
|
||||
import { CodeModule } from './code.module';
|
||||
import { CopierService } from 'app/shared//copier.service';
|
||||
import { Logger } from 'app/shared/logger.service';
|
||||
import { MockPrettyPrinter } from 'testing/pretty-printer.service';
|
||||
import { PrettyPrinter } from './pretty-printer.service';
|
||||
|
||||
const oneLineCode = 'const foo = "bar";';
|
||||
|
||||
const smallMultiLineCode =
|
||||
`<hero-details>
|
||||
const smallMultiLineCode = `
|
||||
<hero-details>
|
||||
<h2>Bah Dah Bing</h2>
|
||||
<hero-team>
|
||||
<h3>NYC Team</h3>
|
||||
</hero-team>
|
||||
</hero-details>`;
|
||||
|
||||
const bigMultiLineCode = `${smallMultiLineCode}\n${smallMultiLineCode}\n${smallMultiLineCode}`;
|
||||
const bigMultiLineCode = smallMultiLineCode + smallMultiLineCode + smallMultiLineCode;
|
||||
|
||||
describe('CodeComponent', () => {
|
||||
let hostComponent: HostComponent;
|
||||
let fixture: ComponentFixture<HostComponent>;
|
||||
|
||||
// WARNING: Chance of cross-test pollution
|
||||
// CodeComponent injects PrettyPrintService
|
||||
// Once PrettyPrintService runs once _anywhere_, its ctor loads `prettify.js`
|
||||
// which sets `window['prettyPrintOne']`
|
||||
// That global survives these tests unless
|
||||
// we take strict measures to wipe it out in the `afterAll`
|
||||
// and make sure THAT runs after the tests by making component creation async
|
||||
afterAll(() => {
|
||||
delete (window as any)['prettyPrint'];
|
||||
delete (window as any)['prettyPrintOne'];
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [ NoopAnimationsModule, CodeModule ],
|
||||
declarations: [ HostComponent ],
|
||||
providers: [
|
||||
PrettyPrinter,
|
||||
CopierService,
|
||||
{ provide: Logger, useClass: TestLogger },
|
||||
{ provide: PrettyPrinter, useClass: MockPrettyPrinter },
|
||||
{provide: Logger, useClass: TestLogger }
|
||||
]
|
||||
});
|
||||
}).compileComponents();
|
||||
});
|
||||
|
||||
// Must be async because
|
||||
// CodeComponent creates PrettyPrintService which async loads `prettify.js`.
|
||||
// If not async, `afterAll` finishes before tests do!
|
||||
beforeEach(async(() => {
|
||||
fixture = TestBed.createComponent(HostComponent);
|
||||
hostComponent = fixture.componentInstance;
|
||||
|
||||
fixture.detectChanges();
|
||||
});
|
||||
}));
|
||||
|
||||
describe('pretty printing', () => {
|
||||
const getFormattedCode = () => fixture.nativeElement.querySelector('code').innerHTML;
|
||||
const untilCodeFormatted = () => {
|
||||
const emitter = hostComponent.codeComponent.codeFormatted;
|
||||
return emitter.pipe(first()).toPromise();
|
||||
};
|
||||
const hasLineNumbers = async () => {
|
||||
// presence of `<li>`s are a tell-tale for line numbers
|
||||
await untilCodeFormatted();
|
||||
return 0 < fixture.nativeElement.querySelectorAll('li').length;
|
||||
};
|
||||
|
||||
it('should format a one-line code sample without linenums by default', () => {
|
||||
it('should format a one-line code sample', async () => {
|
||||
hostComponent.setCode(oneLineCode);
|
||||
expect(getFormattedCode()).toBe(
|
||||
`Formatted code (language: auto, linenums: false): ${oneLineCode}`);
|
||||
await untilCodeFormatted();
|
||||
|
||||
// 'pln' spans are a tell-tale for syntax highlighting
|
||||
const spans = fixture.nativeElement.querySelectorAll('span.pln');
|
||||
expect(spans.length).toBeGreaterThan(0, 'formatted spans');
|
||||
});
|
||||
|
||||
it('should add line numbers to one-line code sample when linenums is `true`', () => {
|
||||
it('should format a one-line code sample without linenums by default', async () => {
|
||||
hostComponent.setCode(oneLineCode);
|
||||
hostComponent.linenums = true;
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(getFormattedCode()).toBe(
|
||||
`Formatted code (language: auto, linenums: true): ${oneLineCode}`);
|
||||
expect(await hasLineNumbers()).toBe(false);
|
||||
});
|
||||
|
||||
it('should add line numbers to one-line code sample when linenums is `\'true\'`', () => {
|
||||
hostComponent.setCode(oneLineCode);
|
||||
it('should add line numbers to one-line code sample when linenums set true', async () => {
|
||||
hostComponent.linenums = 'true';
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(getFormattedCode()).toBe(
|
||||
`Formatted code (language: auto, linenums: true): ${oneLineCode}`);
|
||||
expect(await hasLineNumbers()).toBe(true);
|
||||
});
|
||||
|
||||
it('should format a small multi-line code without linenums by default', async () => {
|
||||
hostComponent.setCode(smallMultiLineCode);
|
||||
expect(getFormattedCode()).toBe(
|
||||
`Formatted code (language: auto, linenums: false): ${smallMultiLineCode}`);
|
||||
expect(await hasLineNumbers()).toBe(false);
|
||||
});
|
||||
|
||||
it('should add line numbers to a big multi-line code by default', async () => {
|
||||
hostComponent.setCode(bigMultiLineCode);
|
||||
expect(getFormattedCode()).toBe(
|
||||
`Formatted code (language: auto, linenums: true): ${bigMultiLineCode}`);
|
||||
expect(await hasLineNumbers()).toBe(true);
|
||||
});
|
||||
|
||||
it('should format big multi-line code without linenums when linenums is `false`', async () => {
|
||||
hostComponent.setCode(bigMultiLineCode);
|
||||
it('should format big multi-line code without linenums when linenums set false', async () => {
|
||||
hostComponent.linenums = false;
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(getFormattedCode()).toBe(
|
||||
`Formatted code (language: auto, linenums: false): ${bigMultiLineCode}`);
|
||||
});
|
||||
|
||||
it('should format big multi-line code without linenums when linenums is `\'false\'`', async () => {
|
||||
hostComponent.setCode(bigMultiLineCode);
|
||||
hostComponent.linenums = 'false';
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(getFormattedCode()).toBe(
|
||||
`Formatted code (language: auto, linenums: false): ${bigMultiLineCode}`);
|
||||
expect(await hasLineNumbers()).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
@ -107,16 +117,9 @@ describe('CodeComponent', () => {
|
||||
hostComponent.linenums = false;
|
||||
fixture.detectChanges();
|
||||
|
||||
hostComponent.setCode(`
|
||||
abc
|
||||
let x = text.split('\\n');
|
||||
ghi
|
||||
|
||||
jkl
|
||||
`);
|
||||
hostComponent.setCode(' abc\n let x = text.split(\'\\n\');\n ghi\n\n jkl\n');
|
||||
const codeContent = fixture.nativeElement.querySelector('code').textContent;
|
||||
expect(codeContent).toEqual(
|
||||
'Formatted code (language: auto, linenums: false): abc\n let x = text.split(\'\\n\');\nghi\n\njkl');
|
||||
expect(codeContent).toEqual('abc\n let x = text.split(\'\\n\');\nghi\n\njkl');
|
||||
});
|
||||
|
||||
it('should trim whitespace from the code before rendering', () => {
|
||||
|
@ -47,7 +47,6 @@ describe('DocumentService', () => {
|
||||
docService.currentDocument.subscribe();
|
||||
|
||||
httpMock.expectOne(CONTENT_URL_PREFIX + 'initial/doc.json');
|
||||
expect().nothing(); // Prevent jasmine from complaining about no expectations.
|
||||
});
|
||||
|
||||
it('should emit a document each time the location changes', () => {
|
||||
@ -186,7 +185,6 @@ describe('DocumentService', () => {
|
||||
docService.currentDocument.subscribe();
|
||||
|
||||
httpMock.expectOne(CONTENT_URL_PREFIX + 'index.json');
|
||||
expect().nothing(); // Prevent jasmine from complaining about no expectations.
|
||||
});
|
||||
|
||||
it('should map the "folder" locations to the correct document request', () => {
|
||||
@ -194,7 +192,6 @@ describe('DocumentService', () => {
|
||||
docService.currentDocument.subscribe();
|
||||
|
||||
httpMock.expectOne(CONTENT_URL_PREFIX + 'guide.json');
|
||||
expect().nothing(); // Prevent jasmine from complaining about no expectations.
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -4,9 +4,9 @@ import { VersionInfo } from 'app/navigation/navigation.service';
|
||||
@Component({
|
||||
selector: 'aio-mode-banner',
|
||||
template: `
|
||||
<div *ngIf="mode == 'archive'" class="mode-banner alert archive-warning">
|
||||
<p>This is the <strong>archived documentation for Angular v{{version?.major}}.</strong>
|
||||
Please visit <a href="https://angular.io/">angular.io</a> to see documentation for the current version of Angular.</p>
|
||||
<div *ngIf="mode == 'archive'" class="mode-banner">
|
||||
This is the <strong>archived documentation for Angular v{{version?.major}}.</strong>
|
||||
Please visit <a href="https://angular.io/">angular.io</a> to see documentation for the current version of Angular.
|
||||
</div>
|
||||
`
|
||||
})
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user