mirror of
https://github.com/angular/angular.git
synced 2026-09-14 13:54:52 +08:00
Compare commits
178 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 6280a98ff1 | |||
| bf6b44be35 | |||
| c2c520341f | |||
| e813f3c7e9 | |||
| 536939e138 | |||
| 083a9c4dc4 | |||
| d982fa7da2 | |||
| 763c5f8991 | |||
| 0111f6c75e | |||
| c90193475f | |||
| 51baa0b448 | |||
| 19db22e911 | |||
| fa358feef0 | |||
| 1dd58b5e2a | |||
| 0a68506d77 | |||
| b8c15eb257 | |||
| 416d535d1e | |||
| ba9f30c9a6 | |||
| 5dee53b352 | |||
| 35653ce337 | |||
| 12938577ef | |||
| 560188bf12 | |||
| d7c358139e | |||
| 568269f124 | |||
| 136358f1a0 | |||
| 724e88e042 | |||
| 1798ce99b6 | |||
| 0bc77f4cab | |||
| 542d4c7805 | |||
| ff172d2818 | |||
| ddce357d1d | |||
| ce66cfc765 | |||
| ab90d8dcc2 | |||
| af21c5f008 | |||
| 2360708cc0 | |||
| 05fd839eb1 | |||
| 4204936b49 | |||
| 60a9910f05 | |||
| 97fe33866e | |||
| c578aeec74 | |||
| 21a04ef5be | |||
| e03eb282cb | |||
| d7912ca7d6 | |||
| 8cccc830d2 | |||
| 21f1d5979d | |||
| 61bfb7afba | |||
| bf340479ca | |||
| 3654d7ab1a | |||
| b5ecae307e | |||
| c7683e1cbd | |||
| 933f1945ed | |||
| 8ea4e1e6eb | |||
| 3dec816276 | |||
| 060d36f67a | |||
| 82399133cb | |||
| c92b242f22 | |||
| 9d1f489542 | |||
| 7059655a0b | |||
| f2e5234e07 | |||
| 2949ae340c | |||
| 3172b4cc99 | |||
| 42db488ed7 | |||
| 45ed2b7584 | |||
| b11a939e21 | |||
| df339d8abf | |||
| 701405fa71 | |||
| 4f1a813596 | |||
| 2c52284e44 | |||
| 183b5f3fde | |||
| ad93c28889 | |||
| f02ba99820 | |||
| 45eb491a25 | |||
| 054cb99421 | |||
| 05f0cd7727 | |||
| 2e96cede3e | |||
| 39be06037d | |||
| 2bc65351b4 | |||
| 8869fce1a4 | |||
| 74ff3b55c1 | |||
| 35f20afcac | |||
| a524a50361 | |||
| 37dbe55945 | |||
| cb479bfa46 | |||
| 5a04963305 | |||
| f92add1157 | |||
| 338de1b4cf | |||
| 79025a69ba | |||
| add2bb8e89 | |||
| 29ac86deb8 | |||
| 2d444db6b4 | |||
| b8e27fafa1 | |||
| 97e90f8470 | |||
| c14c5fcb86 | |||
| af5b3c181a | |||
| 3e2b443b20 | |||
| 29ad2aa44b | |||
| 063fc8cde3 | |||
| 4b2c3617d0 | |||
| e56e6d6166 | |||
| 5879e4616f | |||
| 3c690bc1e9 | |||
| 018550ed50 | |||
| 39a1364e43 | |||
| 9b97b840af | |||
| 5e71f2abc0 | |||
| 3ae133c69e | |||
| 9cd2626e63 | |||
| f1a15dadfa | |||
| 333c23d0d9 | |||
| 19018ca61e | |||
| bcef46d83e | |||
| 77c9508e51 | |||
| 3f7ecec59b | |||
| 45fedb50c4 | |||
| 25fde5db4c | |||
| 42ab829684 | |||
| 4e82bcd08c | |||
| d7ad280941 | |||
| 9cd8e50848 | |||
| b056f1b823 | |||
| 1c0d0f0579 | |||
| ffbf6b67ac | |||
| c3c82b69fb | |||
| f70b84f755 | |||
| 51e3722e77 | |||
| 1d3cf22abb | |||
| 23c4c9601e | |||
| 9f6fa5b746 | |||
| 7dd6450a26 | |||
| 13fad9febe | |||
| 5d13e58aed | |||
| 4a173eb3f8 | |||
| 8e9efbd1f2 | |||
| f76bf8a27d | |||
| 6984dde030 | |||
| 9afb6e31a4 | |||
| fbf6f1f9bf | |||
| 13eb6ca1bf | |||
| 6ec6c87ce1 | |||
| bfe6985d1e | |||
| e9317aee71 | |||
| fbf42ed9d8 | |||
| 216af1c234 | |||
| a267484cde | |||
| 57d1bc09be | |||
| ac7f0460f9 | |||
| 4d27448f21 | |||
| eae93b235f | |||
| 883194a3f8 | |||
| 663c1f905e | |||
| eb511d9c3f | |||
| 141bb16632 | |||
| 74a2e2e2ec | |||
| 018b917602 | |||
| 06bfcb2be3 | |||
| 323ef1ebf7 | |||
| fe2855167a | |||
| ff2dc4a719 | |||
| 86f0213303 | |||
| d48e0ccc01 | |||
| f94055dee6 | |||
| 7604585cd3 | |||
| af36adf540 | |||
| 58a8bd3df2 | |||
| c793efd9b7 | |||
| f6543c25aa | |||
| 3f9faad356 | |||
| 40bf2a6a81 | |||
| dcea70fab7 | |||
| 1e4e9ee9a7 | |||
| d80018b7ca | |||
| 8786c609f8 | |||
| d93ca6cc81 | |||
| 4dbcf9766b | |||
| e586cb8124 | |||
| de008f1149 | |||
| 0c61ea426a | |||
| 967e9345b3 |
@@ -31,8 +31,8 @@ var_3: &cache_key v6-angular-node-14-{{ checksum "month.txt" }}-{{ checksum ".ba
|
||||
var_4: &cache_key_fallback v6-angular-node-14-{{ checksum "month.txt" }}-{{ checksum ".bazelversion" }}
|
||||
|
||||
# Windows needs its own cache key because binaries in node_modules are different.
|
||||
var_3_win: &cache_key_win v7-angular-win-node-14-{{ checksum "month.txt" }}-{{ checksum ".bazelversion" }}-{{ checksum "yarn.lock" }}-{{ checksum "WORKSPACE" }}
|
||||
var_4_win: &cache_key_win_fallback v7-angular-win-node-14-{{ checksum "month.txt" }}-{{ checksum ".bazelversion" }}
|
||||
var_3_win: &cache_key_win v8-angular-win-node-14-{{ checksum "month.txt" }}-{{ checksum ".bazelversion" }}-{{ checksum "yarn.lock" }}-{{ checksum "WORKSPACE" }}
|
||||
var_4_win: &cache_key_win_fallback v8-angular-win-node-14-{{ checksum "month.txt" }}-{{ checksum ".bazelversion" }}
|
||||
|
||||
# Cache key for the `components-repo-unit-tests` job. **Note** when updating the SHA in the
|
||||
# cache keys also update the SHA for the "COMPONENTS_REPO_COMMIT" environment variable.
|
||||
@@ -64,8 +64,6 @@ var_10: &only_on_main_branch
|
||||
branches:
|
||||
only:
|
||||
- main
|
||||
# TODO(BRANCH_RENAME_CLEANUP): remove
|
||||
- master
|
||||
|
||||
# Filter to run a job on all releasable branches.
|
||||
var_11: &only_release_branches
|
||||
@@ -73,8 +71,6 @@ var_11: &only_release_branches
|
||||
branches:
|
||||
only:
|
||||
- main
|
||||
# TODO(BRANCH_RENAME_CLEANUP): remove
|
||||
- master
|
||||
- /\d+\.\d+\.x/
|
||||
|
||||
# CircleCI orbs
|
||||
@@ -308,7 +304,7 @@ jobs:
|
||||
path: dist/bin/packages/core/test/bundling/todo/bundle.min.js.br
|
||||
destination: core/todo/bundle.br
|
||||
|
||||
# NOTE: This is currently limited to master builds only. See the `monitoring` configuration.
|
||||
# NOTE: This is currently limited to main builds only. See the `monitoring` configuration.
|
||||
saucelabs:
|
||||
executor:
|
||||
name: default-executor
|
||||
|
||||
+1
-1
@@ -51,7 +51,7 @@ setSecretVar CI_SECRET_PAYLOAD_FIREBASE_TOKEN "$ANGULAR_PAYLOAD_TOKEN";
|
||||
# Define SauceLabs environment variables for CircleCI.
|
||||
####################################################################################################
|
||||
setPublicVar SAUCE_USERNAME "angular-framework";
|
||||
setSecretVar SAUCE_ACCESS_KEY "0c731274ed5f-cbc9-16f4-021a-9835e39f";
|
||||
setSecretVar SAUCE_ACCESS_KEY "f4bf7c639c5a-c6bb-d6a4-a4b5-800aa111";
|
||||
# TODO(josephperrott): Remove environment variables once all saucelabs tests are via bazel method.
|
||||
setPublicVar SAUCE_LOG_FILE /tmp/angular/sauce-connect.log
|
||||
setPublicVar SAUCE_READY_FILE /tmp/angular/sauce-connect-ready-file.lock
|
||||
|
||||
@@ -101,14 +101,6 @@ function getRefFromBranchList(gitOutput) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
// TODO(BRANCH_RENAME_CLEANUP): remove
|
||||
if (a === 'master') {
|
||||
return -1;
|
||||
}
|
||||
if (b === 'master') {
|
||||
return 1;
|
||||
}
|
||||
|
||||
const aIsSemver = semverRegex.test(a);
|
||||
const bIsSemver = semverRegex.test(b);
|
||||
if (aIsSemver && bIsSemver) {
|
||||
|
||||
@@ -10,5 +10,5 @@ contact_links:
|
||||
url: https://github.com/angular/components/issues/new/choose
|
||||
about: Issues and feature requests for Angular Material
|
||||
- name: Support Request
|
||||
url: https://github.com/angular/angular/blob/master/CONTRIBUTING.md#question
|
||||
url: https://github.com/angular/angular/blob/main/CONTRIBUTING.md#question
|
||||
about: Questions and requests for support
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
## PR Checklist
|
||||
Please check if your PR fulfills the following requirements:
|
||||
|
||||
- [ ] The commit message follows our guidelines: https://github.com/angular/angular/blob/master/CONTRIBUTING.md#commit
|
||||
- [ ] The commit message follows our guidelines: https://github.com/angular/angular/blob/main/CONTRIBUTING.md#commit
|
||||
- [ ] Tests for the changes have been added (for bug fixes / features)
|
||||
- [ ] Docs have been added / updated (for bug fixes / features)
|
||||
|
||||
|
||||
@@ -42,13 +42,26 @@ merge:
|
||||
exclude:
|
||||
- 'packages/*'
|
||||
- 'packages/bazel/*'
|
||||
- 'packages/bazel/src/*'
|
||||
- 'packages/bazel/src/api-extractor/**'
|
||||
- 'packages/bazel/src/ng_package/**'
|
||||
- 'packages/bazel/src/builders/**'
|
||||
- 'packages/bazel/src/ng_module/**'
|
||||
- 'packages/bazel/src/ng_package/**'
|
||||
- 'packages/bazel/src/protractor/**'
|
||||
- 'packages/bazel/src/schematics/**'
|
||||
- 'packages/bazel/src/types_bundle/**'
|
||||
- 'packages/compiler-cli/src/ngcc/**'
|
||||
- 'packages/compiler-cli/linker/**'
|
||||
- 'packages/compiler-cli/ngcc/**'
|
||||
- 'packages/compiler-cli/src/ngtsc/sourcemaps/**'
|
||||
# 'private' mostly contains entrypoints for 3P Angular.
|
||||
# Note that 'private/migrations' _is_ used.
|
||||
- 'packages/compiler-cli/private/bazel.ts'
|
||||
- 'packages/compiler-cli/private/localize.ts'
|
||||
- 'packages/compiler-cli/private/tooling.ts'
|
||||
- 'packages/compiler-cli/private/babel.d.ts'
|
||||
# google3 defines its own binary entrypoints.
|
||||
- 'packages/compiler-cli/src/bin/**'
|
||||
- 'packages/docs/**'
|
||||
- 'packages/elements/schematics/**'
|
||||
- 'packages/examples/**'
|
||||
@@ -56,7 +69,8 @@ merge:
|
||||
- 'packages/localize/**'
|
||||
- 'packages/private/**'
|
||||
- 'packages/service-worker/**'
|
||||
- 'packages/common/locales/**'
|
||||
- 'packages/common/locales/generate-locales-tool/**'
|
||||
- 'packages/common/locales/index.bzl'
|
||||
- 'packages/http/**'
|
||||
- '**/.gitignore'
|
||||
- '**/.gitkeep'
|
||||
@@ -97,7 +111,7 @@ merge:
|
||||
|
||||
# whether the PR shouldn't have a conflict with the base branch
|
||||
noConflict: true
|
||||
# list of labels that a PR needs to have, checked with a regexp (e.g. "target:" will work for the label "target: master")
|
||||
# list of labels that a PR needs to have, checked with a regexp.
|
||||
requiredLabels:
|
||||
- 'target: *'
|
||||
|
||||
@@ -119,7 +133,7 @@ merge:
|
||||
# the comment that will be added when the merge label is added despite failing checks, leave empty or set to false to disable
|
||||
# {{MERGE_LABEL}} will be replaced by the value of the mergeLabel option
|
||||
# {{PLACEHOLDER}} will be replaced by the list of failing checks
|
||||
mergeRemovedComment: "I see that you just added the `{{MERGE_LABEL}}` label, but the following checks are still failing:\n{{PLACEHOLDER}}\n\n**If you want your PR to be merged, it has to pass all the CI checks.**\n\nIf you can't get the PR to a green state due to flakes or broken master, please try rebasing to master and/or restarting the CI job. If that fails and you believe that the issue is not due to your change, please contact the caretaker and ask for help."
|
||||
mergeRemovedComment: "I see that you just added the `{{MERGE_LABEL}}` label, but the following checks are still failing:\n{{PLACEHOLDER}}\n\n**If you want your PR to be merged, it has to pass all the CI checks.**\n\nIf you can't get the PR to a green state due to flakes or broken `main`, please try rebasing to `main` and/or restarting the CI job. If that fails and you believe that the issue is not due to your change, please contact the caretaker and ask for help."
|
||||
|
||||
# options for the triage plugin
|
||||
triage:
|
||||
|
||||
@@ -13,6 +13,13 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b # tag=v3.0.2
|
||||
- uses: angular/dev-infra/github-actions/commit-message-based-labels@a7d07a6f66c05791fdde2dd372da4ba55c1d524b
|
||||
- uses: angular/dev-infra/github-actions/commit-message-based-labels@d9ebdd25b15708c18acc99ac1bfb8101d765f703
|
||||
with:
|
||||
angular-robot-key: ${{ secrets.ANGULAR_ROBOT_PRIVATE_KEY }}
|
||||
post_approval_changes:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b # tag=v3.0.2
|
||||
- uses: angular/dev-infra/github-actions/post-approval-changes@d9ebdd25b15708c18acc99ac1bfb8101d765f703
|
||||
with:
|
||||
angular-robot-key: ${{ secrets.ANGULAR_ROBOT_PRIVATE_KEY }}
|
||||
|
||||
@@ -14,6 +14,6 @@ jobs:
|
||||
if: github.repository == 'angular/angular'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: angular/dev-infra/github-actions/feature-request@a7d07a6f66c05791fdde2dd372da4ba55c1d524b
|
||||
- uses: angular/dev-infra/github-actions/feature-request@d9ebdd25b15708c18acc99ac1bfb8101d765f703
|
||||
with:
|
||||
angular-robot-key: ${{ secrets.ANGULAR_ROBOT_PRIVATE_KEY }}
|
||||
|
||||
@@ -14,6 +14,6 @@ jobs:
|
||||
if: github.repository == 'angular/angular'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: angular/dev-infra/github-actions/lock-closed@a7d07a6f66c05791fdde2dd372da4ba55c1d524b
|
||||
- uses: angular/dev-infra/github-actions/lock-closed@d9ebdd25b15708c18acc99ac1bfb8101d765f703
|
||||
with:
|
||||
lock-bot-key: ${{ secrets.LOCK_BOT_PRIVATE_KEY }}
|
||||
|
||||
@@ -4,11 +4,7 @@ on:
|
||||
schedule:
|
||||
- cron: '0 0 * * 0'
|
||||
push:
|
||||
branches: [
|
||||
main,
|
||||
# TODO(BRANCH_RENAME_CLEANUP): remove
|
||||
master,
|
||||
]
|
||||
branches: [main]
|
||||
workflow_dispatch:
|
||||
|
||||
# Declare default permissions as read only.
|
||||
|
||||
+3
-3
@@ -74,7 +74,7 @@ Fixes #<issue number>
|
||||
# =============================
|
||||
#
|
||||
# The full specification of the Angular Commit Message Format can be found at
|
||||
# https://github.com/angular/angular/blob/master/CONTRIBUTING.md#commit
|
||||
# https://github.com/angular/angular/blob/main/CONTRIBUTING.md#commit
|
||||
#
|
||||
# The following is an excerpt of the specification with the most commonly needed info.
|
||||
#
|
||||
@@ -108,10 +108,10 @@ Fixes #<issue number>
|
||||
# │ platform-browser-dynamic|platform-server|router|service-worker|
|
||||
# │ upgrade|zone.js|packaging|changelog|docs-infra|migrations|ngcc|ve|
|
||||
# │ devtools
|
||||
# │ https://github.com/angular/angular/blob/master/CONTRIBUTING.md#scope
|
||||
# │ https://github.com/angular/angular/blob/main/CONTRIBUTING.md#scope
|
||||
# │
|
||||
# └─⫸ Commit Type: build|ci|docs|feat|fix|perf|refactor|style|test
|
||||
# https://github.com/angular/angular/blob/master/CONTRIBUTING.md#type
|
||||
# https://github.com/angular/angular/blob/main/CONTRIBUTING.md#type
|
||||
#
|
||||
#
|
||||
# Commit Message Body
|
||||
|
||||
+15
-3
@@ -236,6 +236,12 @@ groups:
|
||||
'aio/content/images/guide/attribute-directives/**',
|
||||
'aio/content/guide/bootstrapping.md',
|
||||
'aio/content/examples/bootstrapping/**',
|
||||
'aio/content/guide/change-detection.md',
|
||||
'aio/content/guide/change-detection-zone-pollution.md',
|
||||
'aio/content/guide/change-detection-slow-computations.md',
|
||||
'aio/content/guide/change-detection-skipping-subtrees.md',
|
||||
'aio/content/images/guide/change-detection/**',
|
||||
'aio/content/guide/class-binding.md',
|
||||
'aio/content/guide/cheatsheet.md',
|
||||
'aio/content/guide/component-interaction.md',
|
||||
'aio/content/examples/component-interaction/**',
|
||||
@@ -287,6 +293,7 @@ groups:
|
||||
'aio/content/guide/ngmodule-faq.md',
|
||||
'aio/content/guide/ngmodule-vs-jsmodule.md',
|
||||
'aio/content/guide/module-types.md',
|
||||
'aio/content/guide/template-overview.md',
|
||||
'aio/content/guide/template-syntax.md',
|
||||
'aio/content/guide/built-in-template-functions.md',
|
||||
'aio/content/examples/built-in-template-functions/**',
|
||||
@@ -297,6 +304,7 @@ groups:
|
||||
'aio/content/examples/interpolation/**',
|
||||
'aio/content/examples/template-syntax/**',
|
||||
'aio/content/images/guide/template-syntax/**',
|
||||
'aio/content/guide/binding-overview.md',
|
||||
'aio/content/guide/binding-syntax.md',
|
||||
'aio/content/examples/binding-syntax/**',
|
||||
'aio/content/guide/property-binding.md',
|
||||
@@ -314,20 +322,22 @@ groups:
|
||||
'aio/content/guide/inputs-outputs.md',
|
||||
'aio/content/examples/inputs-outputs/**',
|
||||
'aio/content/images/guide/inputs-outputs/**',
|
||||
'aio/content/guide/understanding-template-expr-overview.md',
|
||||
'aio/content/guide/template-expression-operators.md',
|
||||
'aio/content/examples/template-expression-operators/**',
|
||||
'aio/content/guide/pipes.md',
|
||||
'aio/content/guide/pipes-custom-data-trans.md',
|
||||
'aio/content/guide/pipes-overview.md',
|
||||
'aio/content/guide/pipes-transform-data.md',
|
||||
'aio/content/guide/pipe-template.md',
|
||||
'aio/content/examples/pipes/**',
|
||||
'aio/content/images/guide/pipes/**',
|
||||
'aio/content/guide/providers.md',
|
||||
'aio/content/examples/providers/**',
|
||||
'aio/content/images/guide/providers/**',
|
||||
'aio/content/guide/singleton-services.md',
|
||||
'aio/content/guide/set-document-title.md',
|
||||
'aio/content/examples/set-document-title/**',
|
||||
'aio/content/images/guide/set-document-title/**',
|
||||
'aio/content/guide/sharing-ngmodules.md',
|
||||
'aio/content/guide/standalone-components.md',
|
||||
'aio/content/guide/structural-directives.md',
|
||||
'aio/content/examples/structural-directives/**',
|
||||
'aio/content/guide/svg-in-templates.md',
|
||||
@@ -436,6 +446,7 @@ groups:
|
||||
'aio/content/examples/forms-overview/**',
|
||||
'aio/content/images/guide/forms-overview/**',
|
||||
'aio/content/guide/form-validation.md',
|
||||
'aio/content/guide/typed-forms.md',
|
||||
'aio/content/examples/form-validation/**',
|
||||
'aio/content/images/guide/form-validation/**',
|
||||
'aio/content/guide/dynamic-form.md',
|
||||
@@ -975,6 +986,7 @@ groups:
|
||||
'aio/content/examples/setup/**',
|
||||
'aio/content/guide/build.md',
|
||||
'aio/content/images/guide/build/**',
|
||||
'aio/content/images/guide/cli/**',
|
||||
'aio/content/guide/cli-builder.md',
|
||||
'aio/content/examples/cli-builder/**',
|
||||
'aio/content/guide/deployment.md',
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
load("@build_bazel_rules_nodejs//:index.bzl", "nodejs_binary")
|
||||
load("//tools:defaults.bzl", "nodejs_binary")
|
||||
|
||||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
|
||||
+304
-518
@@ -1,50 +1,341 @@
|
||||
<a name="13.3.6"></a>
|
||||
# 13.3.6 (2022-05-04)
|
||||
## Special Thanks
|
||||
Andrew Kushnir, Andrew Scott, George Kalpakas, Paul Gschwendtner, Pawel Kozlowski, Ryan Day and dario-piotrowicz
|
||||
|
||||
<!-- CHANGELOG SPLIT MARKER -->
|
||||
|
||||
<a name="14.0.0-next.15"></a>
|
||||
# 14.0.0-next.15 (2022-04-27)
|
||||
<a name="14.0.0"></a>
|
||||
# 14.0.0 (2022-06-02)
|
||||
## Breaking Changes
|
||||
### animations
|
||||
- The `AnimationDriver.getParentElement` method has become required, so any
|
||||
implementors of this interface are now required to provide an implementation
|
||||
for this method. This breakage is unlikely to affect application developers,
|
||||
as `AnimationDriver` is not expected to be implemented in user code.
|
||||
### common
|
||||
- Adds new required class member that any implementors of the LocationStrategy will need to satisfy.
|
||||
Location does not depend on PlatformLocation anymore.
|
||||
### compiler
|
||||
- Keyframes names are now prefixed with the component's "scope name".
|
||||
For example, the following keyframes rule in a component definition,
|
||||
whose "scope name" is host-my-cmp:
|
||||
|
||||
@keyframes foo { ... }
|
||||
|
||||
will become:
|
||||
|
||||
@keyframes host-my-cmp_foo { ... }
|
||||
|
||||
Any TypeScript/JavaScript code which relied on the names of keyframes rules
|
||||
will no longer match.
|
||||
|
||||
The recommended solutions in this case are to either:
|
||||
- change the component's view encapsulation to the `None` or `ShadowDom`
|
||||
- define keyframes rules in global stylesheets (e.g styles.css)
|
||||
- define keyframes rules programmatically in code.
|
||||
- Keyframes names are now prefixed with the component's "scope name".
|
||||
For example, the following keyframes rule in a component definition,
|
||||
whose "scope name" is host-my-cmp:
|
||||
|
||||
@keyframes foo { ... }
|
||||
|
||||
will become:
|
||||
|
||||
@keyframes host-my-cmp_foo { ... }
|
||||
|
||||
Any TypeScript/JavaScript code which relied on the names of keyframes rules
|
||||
will no longer match.
|
||||
|
||||
The recommended solutions in this case are to either:
|
||||
- change the component's view encapsulation to the `None` or `ShadowDom`
|
||||
- define keyframes rules in global stylesheets (e.g styles.css)
|
||||
- define keyframes rules programmatically in code.
|
||||
### core
|
||||
- Support for Node.js v12 has been removed as it will become EOL on 2022-04-30. Please use Node.js v14.15 or later.
|
||||
- TypeScript versions older than 4.6 are no longer supported.
|
||||
- Forms [email] input coercion
|
||||
|
||||
Forms [email] input value will be considered as true if it is defined with any value rather
|
||||
than false and 'false'.
|
||||
- Since Ivy, TestBed doesn't use AOT summaries. The `aotSummaries` fields in TestBed APIs were present, but unused. The fields were deprecated in previous major version and in v14 those fields are removed. The `aotSummaries` fields were completely unused, so you can just drop them from the TestBed APIs usage.
|
||||
### forms
|
||||
- Forms classes accept a generic.
|
||||
|
||||
Forms model classes now accept a generic type parameter. Untyped versions of these classes are available to opt-out of the new, stricter behavior.
|
||||
- objects with a length key set to zero will no longer validate as empty.
|
||||
|
||||
This is technically a breaking change, since objects with a key `length` and value `0` will no longer validate as empty. This is a very minor change, and any reliance on this behavior is probably a bug anyway.
|
||||
### http
|
||||
- Queries including + will now actually query for + instead of space.
|
||||
Most workarounds involving custom codecs will be unaffected.
|
||||
Possible server-side workarounds will need to be undone.
|
||||
- JSONP will throw an error when headers are set on a reques
|
||||
|
||||
JSONP does not support headers being set on requests. Before when
|
||||
a request was sent to a JSONP backend that had headers set the headers
|
||||
were ignored. The JSONP backend will now throw an error if it
|
||||
receives a request that has any headers set. Any uses of JSONP
|
||||
on requests with headers set will need to remove the headers
|
||||
to avoid the error.
|
||||
### platform-browser
|
||||
- This change may cause a breaking change in unit tests that are implicitly depending on a specific
|
||||
number and sequence of change detections in order for their assertions to pass.
|
||||
- This may break invalid calls to `TransferState` methods.
|
||||
|
||||
This tightens parameter types of `TransferState` usage, and is a minor breaking change which may reveal existing problematic calls.
|
||||
### router
|
||||
- The type of `Route.pathMatch` is now more strict. Places that use
|
||||
`pathMatch` will likely need to be updated to have an explicit
|
||||
`Route`/`Routes` type so that TypeScript does not infer the type as
|
||||
`string`.
|
||||
- When returning a `Promise` from the
|
||||
`LoadChildrenCallback`, the possible type is now restricted to
|
||||
`Type<any>|NgModuleFactory<any>` rather than `any`.
|
||||
- `initialNavigation: 'enabled'` was deprecated in v11 and is replaced by
|
||||
`initialNavigation: 'enabledBlocking'`.
|
||||
- The type of `component` on `ActivatedRoute` and `ActivatedRouteSnapshot`
|
||||
includes `string`. In reality, this is not the case. The component
|
||||
cannot be anything other than a component class.
|
||||
- * The type of `initialUrl` is set to `string|UrlTree` but in reality,
|
||||
the `Router` only sets it to a value that will always be `UrlTree`
|
||||
* `initialUrl` is documented as "The target URL passed into the
|
||||
`Router#navigateByUrl()` call before navigation" but the value
|
||||
actually gets set to something completely different. It's set to the
|
||||
current internal `UrlTree` of the Router at the time navigation
|
||||
occurs.
|
||||
|
||||
With this change, there is no exact replacement for the old value of
|
||||
`initialUrl` because it was enver intended to be exposed.
|
||||
`Router.url` is likely the best replacement for this.
|
||||
In more specific use-cases, tracking the `finalUrl` between successful
|
||||
navigations can also be used as a replacement.
|
||||
- Lazy loaded configs are now also validated once loaded like the
|
||||
initial set of routes are. Lazy loaded modules which have invalid Route
|
||||
configs will now error. Note that this is only done in dev mode so
|
||||
there is no production impact of this change.
|
||||
- When a guard returns a `UrlTree`, the router would previously schedule
|
||||
the redirect navigation within a `setTimeout`. This timeout is now removed,
|
||||
which can result in test failures due to incorrectly written tests.
|
||||
Tests which perform navigations should ensure that all timeouts are
|
||||
flushed before making assertions. Tests should ensure they are capable
|
||||
of handling all redirects from the original navigation.
|
||||
### bazel
|
||||
- Previously, resolvers were waiting to be completed
|
||||
before proceeding with the navigation and the Router would take the last
|
||||
value emitted from the resolver.
|
||||
The router now takes only the first emitted value by the resolvers
|
||||
and then proceeds with navigation. This is now consistent with `Observables`
|
||||
returned by other guards: only the first value is used.
|
||||
### zone.js
|
||||
- in TaskTrackingZoneSpec track a periodic task until it is cancelled
|
||||
|
||||
The breaking change is scoped only to the plugin
|
||||
`zone.js/plugins/task-tracking`. If you used `TaskTrackingZoneSpec` and
|
||||
checked the pending macroTasks e.g. using `(this.ngZone as any)._inner
|
||||
._parent._properties.TaskTrackingZone.getTasksFor('macroTask')`, then
|
||||
its behavior slightly changed for periodic macrotasks. For example,
|
||||
previously the `setInterval` macrotask was no longer tracked after its
|
||||
callback was executed for the first time. Now it's tracked until
|
||||
the task is explicitly cancelled, e.g with `clearInterval(id)`.
|
||||
|
||||
fixes 45350
|
||||
## Deprecations
|
||||
### common
|
||||
- The `ngModuleFactory` input of the `NgComponentOutlet` directive is deprecated in favor of a newly added `ngModule` input. The `ngModule` input accepts references to the NgModule class directly, without the need to resolve module factory first.
|
||||
### forms
|
||||
- The `initialValueIsDefault` option has been deprecated and replaced with the otherwise-identical `nonNullable` option, for the sake of naming consistency.
|
||||
- It is now deprecated to provide *both* `AbstractControlOption`s and an async validators argument to a FormControl. Previously, the async validators would just be silently dropped, resulting in a probably buggy forms. Now, the constructor call is deprecated, and Angular will print a warning in devmode.
|
||||
### router
|
||||
- The `resolver` argument of the `RouterOutletContract.activateWith` function and the `resolver` field of the `OutletContext` class are deprecated. Passing component factory resolvers are no longer needed. The `ComponentFactoryResolver`-related symbols were deprecated in `@angular/core` package since v13.
|
||||
### animations
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [68597bb0ca](https://github.com/angular/angular/commit/68597bb0ca958445655653a0a3cb206c01c7e0b3) | feat | speed up dev-turnaround by bundling types only when packaging ([#45405](https://github.com/angular/angular/pull/45405)) |
|
||||
| [970a3b5c70](https://github.com/angular/angular/commit/970a3b5c70fee29aa40945836ebeb464d75438e4) | fix | add this_is_bazel marker ([#45728](https://github.com/angular/angular/pull/45728)) |
|
||||
| [a6fa37bc6e](https://github.com/angular/angular/commit/a6fa37bc6e705fee48f555d4b8022915094e8b16) | feat | make validateStyleProperty check dev-mode only ([#45570](https://github.com/angular/angular/pull/45570)) |
|
||||
| [79d334b138](https://github.com/angular/angular/commit/79d334b1385685d0fd1451534df2ed3cbaefa0b6) | feat | provide warnings for non-animatable CSS properties ([#45212](https://github.com/angular/angular/pull/45212)) |
|
||||
| [f8dc660605](https://github.com/angular/angular/commit/f8dc66060591e1ce42265a7862c3df16e40b05e1) | fix | allow animations with unsupported CSS properties ([#44729](https://github.com/angular/angular/pull/44729)) |
|
||||
| [2a75754ee8](https://github.com/angular/angular/commit/2a75754ee823032e651bc596887a2186dc6846ae) | fix | apply default params when resolved value is null or undefined ([#45339](https://github.com/angular/angular/pull/45339)) |
|
||||
| [e46b379204](https://github.com/angular/angular/commit/e46b37920438d84bff895498c0a102dd1ffba178) | fix | implement missing transition delay ([#44799](https://github.com/angular/angular/pull/44799)) |
|
||||
| [5c7c56bc85](https://github.com/angular/angular/commit/5c7c56bc859b195bf6710f6c1479d9e18dde35b1) | perf | improve algorithm to balance animation namespaces ([#45057](https://github.com/angular/angular/pull/45057)) |
|
||||
| [4c778cdb28](https://github.com/angular/angular/commit/4c778cdb28de128c6ddecd2fd6cb6257d675500b) | perf | made errors in the animations package tree shakeable ([#45004](https://github.com/angular/angular/pull/45004)) |
|
||||
| [7a81481fb2](https://github.com/angular/angular/commit/7a81481fb29e94b550db7ad68270eccf26bb2743) | perf | Remove generic objects in favor of Maps ([#44482](https://github.com/angular/angular/pull/44482)) |
|
||||
| [6642e3c8fd](https://github.com/angular/angular/commit/6642e3c8fd7d3b5ed22710667b2c41a46f14eda8) | perf | remove no longer needed CssKeyframes classes ([#44903](https://github.com/angular/angular/pull/44903)) |
|
||||
| [59559fdbac](https://github.com/angular/angular/commit/59559fdbacc00bee380b1e88c79d08b6e856ebbe) | refactor | make `AnimationDriver.getParentElement` required ([#45114](https://github.com/angular/angular/pull/45114)) |
|
||||
### common
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [31d7c3bd71](https://github.com/angular/angular/commit/31d7c3bd71fdab3fef1b4615ecb8124fe6c165bd) | feat | add getState method to LocationStrategy interface ([#45648](https://github.com/angular/angular/pull/45648)) |
|
||||
| [c89cf63059](https://github.com/angular/angular/commit/c89cf63059370bba43717483e3d9627499dfe815) | feat | support NgModule as an input to the NgComponentOutlet ([#44815](https://github.com/angular/angular/pull/44815)) |
|
||||
| [38c03a2035](https://github.com/angular/angular/commit/38c03a20358db3f8621c023b98e627cd385731c0) | feat | support years greater than 9999 ([#43622](https://github.com/angular/angular/pull/43622)) |
|
||||
| [bedb257afc](https://github.com/angular/angular/commit/bedb257afc1ca12eb221536ea44ade960e62cda0) | fix | cleanup URL change listeners when the root view is removed ([#44901](https://github.com/angular/angular/pull/44901)) |
|
||||
| [10691c626b](https://github.com/angular/angular/commit/10691c626bf381bedfa278e9d50ab922b4b656cd) | fix | properly cast http param values to strings ([#42643](https://github.com/angular/angular/pull/42643)) |
|
||||
| [05d50b849b](https://github.com/angular/angular/commit/05d50b849bb891c37c3eefab81a45099057dfd78) | perf | make `NgLocalization` token tree-shakable ([#45118](https://github.com/angular/angular/pull/45118)) |
|
||||
### compiler
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [bb8d7091c6](https://github.com/angular/angular/commit/bb8d7091c64a785758ae347b1a9bcdeeed95897c) | fix | exclude empty styles from emitted metadata ([#45459](https://github.com/angular/angular/pull/45459)) |
|
||||
| [4d6a1d6722](https://github.com/angular/angular/commit/4d6a1d672210219328b33f4f96210870563066ee) | fix | scope css keyframes in emulated view encapsulation ([#42608](https://github.com/angular/angular/pull/42608)) |
|
||||
| [f03e313f24](https://github.com/angular/angular/commit/f03e313f24465cbe9ce99aa5f9f482a6c6b5485f) | fix | scope css keyframes in emulated view encapsulation ([#42608](https://github.com/angular/angular/pull/42608)) |
|
||||
### compiler-cli
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [9cf14ff03d](https://github.com/angular/angular/commit/9cf14ff03dc7a848413b323dfb943fcd26f1217e) | feat | exclude abstract classes from `strictInjectionParameters` requirement ([#44615](https://github.com/angular/angular/pull/44615)) |
|
||||
| [0072eb48ba](https://github.com/angular/angular/commit/0072eb48ba1c6f549703988b7fd7ba3e09058048) | feat | initial implementation of standalone components ([#44812](https://github.com/angular/angular/pull/44812)) |
|
||||
| [2142ffd295](https://github.com/angular/angular/commit/2142ffd295de491eb8582c2eb1712b5b48044f24) | feat | propagate `standalone` flag to runtime ([#44973](https://github.com/angular/angular/pull/44973)) |
|
||||
| [6f653e05f9](https://github.com/angular/angular/commit/6f653e05f985141ae4d2d90af78b2bc001595661) | feat | standalone types imported into NgModule scopes ([#44973](https://github.com/angular/angular/pull/44973)) |
|
||||
| [752ddbc165](https://github.com/angular/angular/commit/752ddbc165359c2ff987c24f715d0a36fd604ec0) | feat | Support template binding to protected component members ([#45823](https://github.com/angular/angular/pull/45823)) |
|
||||
| [3d13343975](https://github.com/angular/angular/commit/3d133439754cbf5d5a20bb3c714c8673c848e465) | fix | better error messages for NgModule structural issues ([#44973](https://github.com/angular/angular/pull/44973)) |
|
||||
| [046dad1a8d](https://github.com/angular/angular/commit/046dad1a8d878ea537a6e2ef5f5ef24a85a2cf02) | fix | fix issue with incremental tracking of APIs for pipes ([#45672](https://github.com/angular/angular/pull/45672)) |
|
||||
| [27b4af7240](https://github.com/angular/angular/commit/27b4af7240ea964d1e4d51b83118f10321163627) | fix | full side-effectful registration of NgModules with ids ([#45024](https://github.com/angular/angular/pull/45024)) |
|
||||
| [32c625d027](https://github.com/angular/angular/commit/32c625d0279d9fd55178ced47c45969da533eedc) | fix | handle forwardRef in imports of standalone component ([#45869](https://github.com/angular/angular/pull/45869)) |
|
||||
| [06050ac2b4](https://github.com/angular/angular/commit/06050ac2b4937836096fb331ec71bacb5a1fc231) | fix | handle inline type-check blocks in nullish coalescing extended check ([#45454](https://github.com/angular/angular/pull/45454)) |
|
||||
| [a524a50361](https://github.com/angular/angular/commit/a524a50361bb408cf8baf8ff209378a1db967545) | fix | handle standalone components with cycles ([#46029](https://github.com/angular/angular/pull/46029)) |
|
||||
| [724e88e042](https://github.com/angular/angular/commit/724e88e042b1a89691d48879673b5e1b161c3d9a) | fix | preserve `forwardRef` for component scopes ([#46139](https://github.com/angular/angular/pull/46139)) |
|
||||
| [9cfea3d522](https://github.com/angular/angular/commit/9cfea3d522c0e4a34bb3a5d0fa9d04f64de39b49) | fix | report invalid imports in standalone components during resolve phase ([#45827](https://github.com/angular/angular/pull/45827)) |
|
||||
| [c0778b4dfc](https://github.com/angular/angular/commit/c0778b4dfc1c107543b3db6f1958814a0812c7df) | fix | Support resolve animation name from the DTS ([#45107](https://github.com/angular/angular/pull/45107)) |
|
||||
| [f2e5234e07](https://github.com/angular/angular/commit/f2e5234e078b6d4771d07e8b28b8098f1faeb176) | fix | update unknown tag error for aot standalone components ([#45919](https://github.com/angular/angular/pull/45919)) |
|
||||
| [35f20afcac](https://github.com/angular/angular/commit/35f20afcac4877963e4a3e5d85b67ed8cb000af9) | fix | use existing imports for standalone dependencies ([#46029](https://github.com/angular/angular/pull/46029)) |
|
||||
| [8155428ba6](https://github.com/angular/angular/commit/8155428ba65c38c0c15f2666727202a7b360c1bd) | perf | ignore the module.id anti-pattern for NgModule ids ([#45024](https://github.com/angular/angular/pull/45024)) |
|
||||
### core
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [174ce7dd13](https://github.com/angular/angular/commit/174ce7dd13f6d8f941d3aa0b843559614cb68b0c) | feat | add `ApplicationRef.destroy` method ([#45624](https://github.com/angular/angular/pull/45624)) |
|
||||
| [5771b18a98](https://github.com/angular/angular/commit/5771b18a989c3c75d713ffb75cd7c047c63e4090) | feat | add the `bootstrapApplication` function ([#45674](https://github.com/angular/angular/pull/45674)) |
|
||||
| [69018c9f42](https://github.com/angular/angular/commit/69018c9f42d14c1c7aa8271b406d6d247de1c564) | feat | allow for injector to be specified when creating an embedded view ([#45156](https://github.com/angular/angular/pull/45156)) |
|
||||
| [94c949a60a](https://github.com/angular/angular/commit/94c949a60ad0de1f5385ec97f2c1933cb1d55f22) | feat | allow for injector to be specified when creating an embedded view ([#45156](https://github.com/angular/angular/pull/45156)) |
|
||||
| [e702cafcf2](https://github.com/angular/angular/commit/e702cafcf2fef53968ef52ce17cb343f3a12bb53) | feat | allow to throw on unknown elements in tests ([#45479](https://github.com/angular/angular/pull/45479)) |
|
||||
| [6662a97c61](https://github.com/angular/angular/commit/6662a97c61f133a14f2d117f71e4d2b8f4a83c32) | feat | allow to throw on unknown elements in tests ([#45479](https://github.com/angular/angular/pull/45479)) |
|
||||
| [a6675925b0](https://github.com/angular/angular/commit/a6675925b0b6f313d4932a753bd4aa4cb473b5f3) | feat | allow to throw on unknown properties in tests ([#45853](https://github.com/angular/angular/pull/45853)) |
|
||||
| [6eaaefd22e](https://github.com/angular/angular/commit/6eaaefd22ebb95ee896a62237832ff2f2defdee0) | feat | drop support for Node.js 12 ([#45286](https://github.com/angular/angular/pull/45286)) |
|
||||
| [c9d566ce4b](https://github.com/angular/angular/commit/c9d566ce4b6e9097d9eceb7ac3964a0b25c404ad) | feat | drop support for TypeScript 4.4 and 4.5 ([#45394](https://github.com/angular/angular/pull/45394)) |
|
||||
| [b568a5e708](https://github.com/angular/angular/commit/b568a5e708579f5035f40c7218fbba39ad6b3065) | feat | implement `importProvidersFrom` function ([#45626](https://github.com/angular/angular/pull/45626)) |
|
||||
| [d5a6cd1111](https://github.com/angular/angular/commit/d5a6cd11110c78762a8e6115a718f5851508dbf2) | feat | implement EnvironmentInjector with adapter to NgModuleRef ([#45626](https://github.com/angular/angular/pull/45626)) |
|
||||
| [5a10fc4f82](https://github.com/angular/angular/commit/5a10fc4f8287d448c88ce2f6c97c48307af34af1) | feat | implement standalone directives, components, and pipes ([#45687](https://github.com/angular/angular/pull/45687)) |
|
||||
| [e461f716d4](https://github.com/angular/angular/commit/e461f716d4b736829d53de26ba6dddb58f476746) | feat | move ANIMATION_MODULE_TYPE injection token into core ([#44970](https://github.com/angular/angular/pull/44970)) |
|
||||
| [94bba76a4a](https://github.com/angular/angular/commit/94bba76a4a9594a5eb90e581f407f1b70697e715) | feat | support TypeScript 4.6 ([#45190](https://github.com/angular/angular/pull/45190)) |
|
||||
| [29039fcdbc](https://github.com/angular/angular/commit/29039fcdbcb8cab040d88dabe2dcb1abae34cb4e) | feat | support TypeScript 4.7 ([#45749](https://github.com/angular/angular/pull/45749)) |
|
||||
| [225e4f2dbe](https://github.com/angular/angular/commit/225e4f2dbeac5982982fb94eac65ea931c44247a) | feat | triggerEventHandler accept optional eventObj ([#45279](https://github.com/angular/angular/pull/45279)) |
|
||||
| [401dec46eb](https://github.com/angular/angular/commit/401dec46eb71e33ae3ef185b8f92ed2b3b7661fd) | feat | update TestBed to recognize Standalone Components ([#45809](https://github.com/angular/angular/pull/45809)) |
|
||||
| [35653ce337](https://github.com/angular/angular/commit/35653ce337b59336ebb06f07dfed027e5037ed06) | fix | add more details to the MISSING_INJECTION_CONTEXT error ([#46166](https://github.com/angular/angular/pull/46166)) |
|
||||
| [d36fa111eb](https://github.com/angular/angular/commit/d36fa111eb677d504a9952d8b4ca53560cadd04d) | fix | avoid Closure Compiler error in restoreView ([#45445](https://github.com/angular/angular/pull/45445)) |
|
||||
| [0bc77f4cab](https://github.com/angular/angular/commit/0bc77f4cabb3fe76accb120f7aba57ebfee9262b) | fix | better error message when unknown property is present ([#46147](https://github.com/angular/angular/pull/46147)) |
|
||||
| [f3eb7d9ecb](https://github.com/angular/angular/commit/f3eb7d9ecb9a82e7396cca658e3250e47c853153) | fix | Ensure the `StandaloneService` is retained after closure minification ([#45783](https://github.com/angular/angular/pull/45783)) |
|
||||
| [701405fa71](https://github.com/angular/angular/commit/701405fa710177ed1c963f61686ebb910e5ec74e) | fix | handle AOT-compiled standalone components in TestBed correctly ([#46052](https://github.com/angular/angular/pull/46052)) |
|
||||
| [ddce357d1d](https://github.com/angular/angular/commit/ddce357d1db9694bc69c114deb96e26d2651fb8b) | fix | improve TestBed declarations standalone error message ([#45999](https://github.com/angular/angular/pull/45999)) |
|
||||
| [ba9f30c9a6](https://github.com/angular/angular/commit/ba9f30c9a6af19b34d13962303ec96997c7c3d56) | fix | include component name into unknown element/property error message ([#46160](https://github.com/angular/angular/pull/46160)) |
|
||||
| [9fa6f5a552](https://github.com/angular/angular/commit/9fa6f5a552b7780ce32aa5a60273d4008a7b6afb) | fix | incorrectly inserting elements inside `<template>` element ([#43429](https://github.com/angular/angular/pull/43429)) |
|
||||
| [d5719c2e0f](https://github.com/angular/angular/commit/d5719c2e0fb237be71d658444bbfe2410e05086a) | fix | input coercion ([#42803](https://github.com/angular/angular/pull/42803)) |
|
||||
| [be161bef79](https://github.com/angular/angular/commit/be161bef798ac5309df8390e3c2bba2cf84cb530) | fix | memory leak in event listeners inside embedded views ([#43075](https://github.com/angular/angular/pull/43075)) |
|
||||
| [fa755b2a54](https://github.com/angular/angular/commit/fa755b2a541274336541e8870852e73718ad62d4) | fix | prevent `BrowserModule` providers from being loaded twice ([#45826](https://github.com/angular/angular/pull/45826)) |
|
||||
| [3172b4cc99](https://github.com/angular/angular/commit/3172b4cc9972a7d347b3078484c9adcb15cf270c) | fix | produce proper error message for unknown props on `<ng-template>`s ([#46068](https://github.com/angular/angular/pull/46068)) |
|
||||
| [4f1a813596](https://github.com/angular/angular/commit/4f1a813596a40f7885d369eb8917aedcce9dd795) | fix | restore NgModule state correctly after TestBed overrides ([#46049](https://github.com/angular/angular/pull/46049)) |
|
||||
| [3f7ecec59b](https://github.com/angular/angular/commit/3f7ecec59b894d2a92f4b5643d2a23891938bde8) | fix | set correct context for inject() for component ctors ([#45991](https://github.com/angular/angular/pull/45991)) |
|
||||
| [4e413d9240](https://github.com/angular/angular/commit/4e413d9240259fca1b1e548c5c405e646f8e68c8) | fix | support nested arrays of providers in `EnvironmentInjector` ([#45789](https://github.com/angular/angular/pull/45789)) |
|
||||
| [fde4942cdf](https://github.com/angular/angular/commit/fde4942cdf5133119b13ed26ee2f6976b787d84c) | fix | throw if standalone components are present in `@NgModule.bootstrap` ([#45825](https://github.com/angular/angular/pull/45825)) |
|
||||
| [560188bf12](https://github.com/angular/angular/commit/560188bf126f259328477773cabb367587a257d5) | fix | update unknown property error to account for standalone components in AOT ([#46159](https://github.com/angular/angular/pull/46159)) |
|
||||
| [df339d8abf](https://github.com/angular/angular/commit/df339d8abf979c715c35db813c5a80252a92eba7) | fix | update unknown tag error for jit standalone components ([#45920](https://github.com/angular/angular/pull/45920)) |
|
||||
| [aafac7228f](https://github.com/angular/angular/commit/aafac7228f3d18bb720e85d2b889df446122f4c7) | fix | verify standalone component imports in JiT ([#45777](https://github.com/angular/angular/pull/45777)) |
|
||||
| [e9317aee71](https://github.com/angular/angular/commit/e9317aee71f64f4aadea0529601e589b37f6b46f) | perf | allow `checkNoChanges` mode to be tree-shaken in production ([#45913](https://github.com/angular/angular/pull/45913)) |
|
||||
| [071c8af8ba](https://github.com/angular/angular/commit/071c8af8ba89163270f17657c0c7f1c944f186f7) | perf | avoid storing LView in `__ngContext__` ([#45051](https://github.com/angular/angular/pull/45051)) |
|
||||
| [a96c4827c4](https://github.com/angular/angular/commit/a96c4827c4f62e1d665d8544a3e66c51d2644c88) | perf | make `Compiler`, `ApplicationRef` and `ApplicationInitStatus` tree-shakable ([#45102](https://github.com/angular/angular/pull/45102)) |
|
||||
| [45d98e7ca5](https://github.com/angular/angular/commit/45d98e7ca5accf918e2602cbc17805e11e18896b) | perf | make `IterableDiffers` and `KeyValueDiffers` tree-shakable ([#45094](https://github.com/angular/angular/pull/45094)) |
|
||||
| [1e60fe0a3e](https://github.com/angular/angular/commit/1e60fe0a3eeb6bd46997f7eeb4825351cfbe3c44) | perf | make `LOCALE_ID` and other tokens from `ApplicationModule` tree-shakable ([#45102](https://github.com/angular/angular/pull/45102)) |
|
||||
| [88f1168506](https://github.com/angular/angular/commit/88f1168506befd0665125da1fca2a719cb27dbb5) | perf | only track LViews that are referenced in `__ngContext__` ([#45172](https://github.com/angular/angular/pull/45172)) |
|
||||
| [9add714b13](https://github.com/angular/angular/commit/9add714b13740db621eb2b200d72be74cc7eb630) | refactor | remove deprecated `aotSummaries` fields in TestBed config ([#45487](https://github.com/angular/angular/pull/45487)) |
|
||||
### devtools tabs
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [6c284ef32e](https://github.com/angular/angular/commit/6c284ef32e53a8dabbc368b9cf69f1b6f1a377b7) | fix | stop scroll occuring at tabs level |
|
||||
### forms
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [2dbdebc646](https://github.com/angular/angular/commit/2dbdebc6467074c7005c09ef5c229029f8d3607c) | feat | Add `FormBuilder.nonNullable`. ([#45852](https://github.com/angular/angular/pull/45852)) |
|
||||
| [e0a2248b32](https://github.com/angular/angular/commit/e0a2248b3233b5d384f33859ef6207613cad909d) | feat | Add a FormRecord type. ([#45607](https://github.com/angular/angular/pull/45607)) |
|
||||
| [7ee121f595](https://github.com/angular/angular/commit/7ee121f595a47338b31098ae51a6f91d80c8fdce) | feat | Add untyped versions of the model classes for use in migration. ([#45205](https://github.com/angular/angular/pull/45205)) |
|
||||
| [89d299105a](https://github.com/angular/angular/commit/89d299105a4ae11985fb809f42cd0e34511207c9) | feat | Implement strict types for the Angular Forms package. ([#43834](https://github.com/angular/angular/pull/43834)) |
|
||||
| [f490c2de4e](https://github.com/angular/angular/commit/f490c2de4e1ada26e62e35c0d70cb90e55297658) | feat | support negative indices in FormArray methods. ([#44848](https://github.com/angular/angular/pull/44848)) |
|
||||
| [39be06037d](https://github.com/angular/angular/commit/39be06037daf65ae9298b5bdb4cd50785c16332f) | fix | Add a `nonNullable` option to `FormControl` for consistency. |
|
||||
| [4332897baa](https://github.com/angular/angular/commit/4332897baa2226ef246ee054fdd5254e3c129109) | fix | Add UntypedFormBuilder ([#45268](https://github.com/angular/angular/pull/45268)) |
|
||||
| [5d13e58aed](https://github.com/angular/angular/commit/5d13e58aed8b7641f0b2d4f0c9015cb90ff4edb5) | fix | Allow NonNullableFormBuilder to be injected. ([#45904](https://github.com/angular/angular/pull/45904)) |
|
||||
| [8dd3f82f94](https://github.com/angular/angular/commit/8dd3f82f946bae86dc6c678d8694ab73d915bbfa) | fix | Correct empty validator to handle objects with a property `length: 0`. ([#33729](https://github.com/angular/angular/pull/33729)) |
|
||||
| [ff3f5a8d12](https://github.com/angular/angular/commit/ff3f5a8d12e3243620e311b690a050e26493e539) | fix | Fix a typing bug in FormBuilder. ([#45684](https://github.com/angular/angular/pull/45684)) |
|
||||
| [fe0e42a996](https://github.com/angular/angular/commit/fe0e42a996d5b70f4c0923cb6570c9a8032b1298) | fix | Make UntypedFormBuilder assignable to FormBuilder, and vice versa. ([#45421](https://github.com/angular/angular/pull/45421)) |
|
||||
| [b36dec6b5b](https://github.com/angular/angular/commit/b36dec6b5b05de0d40581726c59db9f962a97124) | fix | not picking up disabled state if group is swapped out and disabled ([#43499](https://github.com/angular/angular/pull/43499)) |
|
||||
| [9f6fa5b746](https://github.com/angular/angular/commit/9f6fa5b746cbdd4a71f67728ff6b663ad26c6a29) | fix | Prevent FormBuilder from distributing unions to control types. ([#45942](https://github.com/angular/angular/pull/45942)) |
|
||||
| [aa7b857be8](https://github.com/angular/angular/commit/aa7b857be89a62e6dcfe62c418acf0823525d265) | fix | Property renaming safe code ([#45271](https://github.com/angular/angular/pull/45271)) |
|
||||
| [cae1e44608](https://github.com/angular/angular/commit/cae1e44608227ac80682401190c6d28ef39b3269) | fix | Update the typed forms migration to use `FormArray<T>` instead of `FormArray<T[]>`. ([#44933](https://github.com/angular/angular/pull/44933)) |
|
||||
| [d336ba96d9](https://github.com/angular/angular/commit/d336ba96d922363235688f54d8af108ef7ab01f0) | fix | Update the typed forms migration. ([#45281](https://github.com/angular/angular/pull/45281)) |
|
||||
| [018550ed50](https://github.com/angular/angular/commit/018550ed5025de9737794b683eb7c0697d83d430) | fix | Value and RawValue should be part of the public API. ([#45978](https://github.com/angular/angular/pull/45978)) |
|
||||
| [2e96cede3e](https://github.com/angular/angular/commit/2e96cede3e2d9ac399c10d3bfe25259b92a5276e) | fix | Warn on FormControls that are constructed with both options and asyncValidators. |
|
||||
### http
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [76a9a24cdc](https://github.com/angular/angular/commit/76a9a24cdcb87e36868e2e29d1831af2dec3a818) | fix | encode + signs in query params as %2B (angular[#11058](https://github.com/angular/angular/pull/11058)) ([#45111](https://github.com/angular/angular/pull/45111)) |
|
||||
| [d43c0e973f](https://github.com/angular/angular/commit/d43c0e973f4389c74eb19a7f6b667bd2e1d380fe) | fix | Throw error when headers are supplied in JSONP request ([#45210](https://github.com/angular/angular/pull/45210)) |
|
||||
### language-service
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [9d4af65e34](https://github.com/angular/angular/commit/9d4af65e343e41b48cb9afd12c2b61d8ca61a9ac) | feat | Provide plugin to delegate rename requests to Angular ([#44696](https://github.com/angular/angular/pull/44696)) |
|
||||
| [3ae133c69e](https://github.com/angular/angular/commit/3ae133c69e1c78d5ddf772cacf222f909faccd70) | fix | Fix detection of Angular for v14+ projects ([#45998](https://github.com/angular/angular/pull/45998)) |
|
||||
### localize
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [a50e2da64a](https://github.com/angular/angular/commit/a50e2da64a4297d1bc994a267203c2097da2efaf) | fix | ensure transitively loaded compiler code is tree-shakable ([#45405](https://github.com/angular/angular/pull/45405)) |
|
||||
### migrations
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [d56a537196](https://github.com/angular/angular/commit/d56a537196d2ab0f3f48821889020d2f5bc621ba) | feat | Add migration to add explicit `Route`/`Routes` type ([#45084](https://github.com/angular/angular/pull/45084)) |
|
||||
### ngcc
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [74a2e2e2ec](https://github.com/angular/angular/commit/74a2e2e2ecb81a31c7fbec42d7770937b09e33a4) | fix | cope with packages following APF v14+ ([#45833](https://github.com/angular/angular/pull/45833)) |
|
||||
### platform-browser
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [a01bcb8e7e](https://github.com/angular/angular/commit/a01bcb8e7eaf63ac9466a78dd4d15228241da900) | fix | do not run change detection when loading Hammer ([#44921](https://github.com/angular/angular/pull/44921)) |
|
||||
| [b32647dc68](https://github.com/angular/angular/commit/b32647dc68b055da0c49c86d6e7e2a7d2ec5954a) | fix | Make transfer state key typesafe. ([#23020](https://github.com/angular/angular/pull/23020)) |
|
||||
| [c7bf75dd5e](https://github.com/angular/angular/commit/c7bf75dd5e7f3f80a3c2afb8586ae46f7258f349) | fix | remove obsolete shim for Map comparison in Jasmine ([#45521](https://github.com/angular/angular/pull/45521)) |
|
||||
| [23c4c9601e](https://github.com/angular/angular/commit/23c4c9601e1bdd8bd2e37d9e0468440a44971b95) | perf | avoid including Testability by default in `bootstrapApplication` ([#45885](https://github.com/angular/angular/pull/45885)) |
|
||||
### platform-server
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [dff5586d52](https://github.com/angular/angular/commit/dff5586d5243f914b9054b5ab7ac71d57edfa973) | feat | implement `renderApplication` function ([#45785](https://github.com/angular/angular/pull/45785)) |
|
||||
| [22c71be94c](https://github.com/angular/angular/commit/22c71be94c3a695ca0cd0316065d807abb46355f) | fix | update `renderApplication` to move `appId` to options ([#45844](https://github.com/angular/angular/pull/45844)) |
|
||||
### router
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [f4fd1a8262](https://github.com/angular/angular/commit/f4fd1a82620b5b3899c5e8c89fa06b084ee5792e) | feat | Add `EnvironmentInjector` to `RouterOutlet.activateWith` ([#45597](https://github.com/angular/angular/pull/45597)) |
|
||||
| [910de8bc33](https://github.com/angular/angular/commit/910de8bc3379b1452c0bc23a866b5108d6d94e55) | feat | Add `Route.title` with a configurable `TitleStrategy` ([#43307](https://github.com/angular/angular/pull/43307)) |
|
||||
| [4e0957a4e1](https://github.com/angular/angular/commit/4e0957a4e10d1ae188faa843043a2314c9873c52) | feat | Add ability to specify providers on a Route ([#45673](https://github.com/angular/angular/pull/45673)) |
|
||||
| [dea8c86cd5](https://github.com/angular/angular/commit/dea8c86cd57f8e1720634928805f9ea0ba8f0baa) | feat | add ariaCurrentWhenActive input to RouterLinkActive directive ([#45167](https://github.com/angular/angular/pull/45167)) |
|
||||
| [41e2a68e30](https://github.com/angular/angular/commit/41e2a68e30c12e5ad3e26047c3a4032e9aa1a6e1) | feat | add type properties to all router events ([#44189](https://github.com/angular/angular/pull/44189)) |
|
||||
| [4962a4a332](https://github.com/angular/angular/commit/4962a4a3324d5972f364d768b38b21a33fdb7d7c) | feat | Allow `loadChildren` to return a `Route` array ([#45700](https://github.com/angular/angular/pull/45700)) |
|
||||
| [791bd31424](https://github.com/angular/angular/commit/791bd3142432ed7caf14a68a6e434b27004e634b) | feat | set stricter type for Route.title ([#44939](https://github.com/angular/angular/pull/44939)) |
|
||||
| [50004c143b](https://github.com/angular/angular/commit/50004c143ba9b1e041c7b30caf98f6582d3d26d4) | feat | Support lazy loading standalone components with `loadComponent` ([#45705](https://github.com/angular/angular/pull/45705)) |
|
||||
| [7fd416d060](https://github.com/angular/angular/commit/7fd416d060bd873bc88dffed41946c51aa649ec1) | fix | Fix type of Route.pathMatch to be more accurate ([#45176](https://github.com/angular/angular/pull/45176)) |
|
||||
| [1c11a57155](https://github.com/angular/angular/commit/1c11a5715576632a4fb7170202395cf95dfbce09) | fix | merge interited resolved data and static data ([#45276](https://github.com/angular/angular/pull/45276)) |
|
||||
| [f8f3ab377b](https://github.com/angular/angular/commit/f8f3ab377b8842c2bae0f215a0e90aaa3a1e070a) | fix | Remove `any` from `LoadChildrenCallback` type ([#45524](https://github.com/angular/angular/pull/45524)) |
|
||||
| [d4fc12fa19](https://github.com/angular/angular/commit/d4fc12fa19dbe5e89189bb52c37e5d37a00a663c) | fix | Remove deprecated initialNavigation option ([#45729](https://github.com/angular/angular/pull/45729)) |
|
||||
| [989e840cce](https://github.com/angular/angular/commit/989e840cce7ebe94311ae898786e09b1b41ce7f6) | fix | Remove unused string type for ActivatedRoute.component ([#45625](https://github.com/angular/angular/pull/45625)) |
|
||||
| [64f837d2c0](https://github.com/angular/angular/commit/64f837d2c0fbcf722d32b35a87e87220bfe61f65) | fix | Update `Navigation#initialUrl` to match documentation and reality ([#43863](https://github.com/angular/angular/pull/43863)) |
|
||||
| [96fd29c6d2](https://github.com/angular/angular/commit/96fd29c6d2d2abc5afee4d21a3e964a79aa39844) | fix | validate lazy loaded configs ([#45526](https://github.com/angular/angular/pull/45526)) |
|
||||
| [f13295f3a3](https://github.com/angular/angular/commit/f13295f3a3a1d622d15cf8339360d53feba824b5) | perf | cancel the navigation instantly if at least one resolver doesn't emit any value ([#45621](https://github.com/angular/angular/pull/45621)) |
|
||||
| [1d2f5c1101](https://github.com/angular/angular/commit/1d2f5c1101ccd182f5b528de52583a1b98dd6789) | refactor | deprecate no longer needed resolver fields ([#45597](https://github.com/angular/angular/pull/45597)) |
|
||||
| [7b367d9d90](https://github.com/angular/angular/commit/7b367d9d908e15222645a45e2ba133b9d9da76b7) | refactor | Remove unnecessary setTimeout in UrlTree redirects ([#45735](https://github.com/angular/angular/pull/45735)) |
|
||||
| [c9679760b2](https://github.com/angular/angular/commit/c9679760b2bf5c607c957c20482b9cea7a21702b) | refactor | take only the first emitted value of every resolver to make it consistent with guards ([#44573](https://github.com/angular/angular/pull/44573)) |
|
||||
### service-worker
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [ec0a0e0669](https://github.com/angular/angular/commit/ec0a0e0669ef5d2f524fbf9a540eb2b306b2e24a) | feat | add `cacheOpaqueResponses` option for data-groups ([#44723](https://github.com/angular/angular/pull/44723)) |
|
||||
| [bd04fbc05b](https://github.com/angular/angular/commit/bd04fbc05b0397de966a493a863e1054203c86d9) | feat | emit a notification when the service worker is already up to date after check ([#45216](https://github.com/angular/angular/pull/45216)) |
|
||||
## Special Thanks
|
||||
Alex Rickabaugh, Andrew Kushnir, Andrew Scott, George Kalpakas, Joey Perrott, Michal Materowski, Paul Gschwendtner, Pawel Kozlowski, Sumit Arora and dario-piotrowicz
|
||||
Adrian Kunz, Alan Agius, AleksanderBodurri, Alex Rickabaugh, AlirezaEbrahimkhani, Amir Rustamzadeh, Andrew Kushnir, Andrew Scott, Chabbey François, Charles Lyding, Cédric Exbrayat, Daan De Smedt, David Schmidt, Derek Cormier, Dmitrij Kuba, Doug Parker, Dylan Hunn, Emma Twersky, George Kalpakas, George Looshch, Jan Kuehle, Jessica Janiuk, JiaLiPassion, JimMorrison723, Joe Martin (Crowdstaffing), Joey Perrott, JoostK, Kristiyan Kostadinov, Krzysztof Platis, Leosvel Pérez Espinosa, Maddie Klein, Mark Whitfeld, Martin Sikora, Michael-Doner, Michal Materowski, Minko Gechev, Paul Gschwendtner, Pawel Kozlowski, Payam Shahidi, Pusztai Tibor, Ricardo Mattiazzi Baumgartner, Roy Dorombozi, Ruslan Lekhman, Samuel Littley, Sergej Grilborzer, Sumit Arora, Tobias Speicher, Virginia Dooley, Zack Elliott, alirezaghey, ananyahs96, arturovt, cexbrayat, dario-piotrowicz, ivanwonder, kamikopi, markostanimirovic, markwhitfeld, mgechev, renovate[bot], twerske and zverbeta
|
||||
|
||||
<!-- CHANGELOG SPLIT MARKER -->
|
||||
|
||||
<a name="13.3.10"></a>
|
||||
# 13.3.10 (2022-05-25)
|
||||
## Special Thanks
|
||||
A. J. Javier, Aristeidis Bampakos, J Rob Gant, Jerome Kruse, Joey Perrott, Nathan Nontell, Paul Gschwendtner, Roopesh Chinnakampalli, Thomas Mair, Tom Raithel, dario-piotrowicz and mgechev
|
||||
|
||||
<!-- CHANGELOG SPLIT MARKER -->
|
||||
|
||||
<a name="13.3.6"></a>
|
||||
# 13.3.6 (2022-05-04)
|
||||
## Special Thanks
|
||||
Andrew Kushnir, Andrew Scott, George Kalpakas, Paul Gschwendtner, Pawel Kozlowski, Ryan Day and dario-piotrowicz
|
||||
|
||||
<!-- CHANGELOG SPLIT MARKER -->
|
||||
|
||||
@@ -59,68 +350,6 @@ Andrew Kushnir, George Kalpakas, Joey Perrott and dario-piotrowicz
|
||||
|
||||
<!-- CHANGELOG SPLIT MARKER -->
|
||||
|
||||
<a name="14.0.0-next.14"></a>
|
||||
# 14.0.0-next.14 (2022-04-20)
|
||||
## Breaking Changes
|
||||
### http
|
||||
- Queries including + will now actually query for + instead of space.
|
||||
Most workarounds involving custom codecs will be unaffected.
|
||||
Possible server-side workarounds will need to be undone.
|
||||
### router
|
||||
- The type of `component` on `ActivatedRoute` and `ActivatedRouteSnapshot`
|
||||
includes `string`. In reality, this is not the case. The component
|
||||
cannot be anything other than a component class.
|
||||
|
||||
- Lazy loaded configs are now also validated once loaded like the
|
||||
initial set of routes are. Lazy loaded modules which have invalid Route
|
||||
configs will now error. Note that this is only done in dev mode so
|
||||
there is no production impact of this change.
|
||||
## Deprecations
|
||||
### router
|
||||
- The `resolver` argument of the `RouterOutletContract.activateWith` function and the `resolver` field of the `OutletContext` class are deprecated. Passing component factory resolvers are no longer needed. The `ComponentFactoryResolver`-related symbols were deprecated in `@angular/core` package since v13.
|
||||
### bazel
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [f8a1ea0c11](https://github.com/angular/angular/commit/f8a1ea0c117aa97f45b7696d2b0c9b5679e09b9f) | fix | do not error if files part of `srcs` are outside of package ([#45622](https://github.com/angular/angular/pull/45622)) |
|
||||
### compiler-cli
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [046dad1a8d](https://github.com/angular/angular/commit/046dad1a8d878ea537a6e2ef5f5ef24a85a2cf02) | fix | fix issue with incremental tracking of APIs for pipes ([#45672](https://github.com/angular/angular/pull/45672)) |
|
||||
### core
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [174ce7dd13](https://github.com/angular/angular/commit/174ce7dd13f6d8f941d3aa0b843559614cb68b0c) | feat | add `ApplicationRef.destroy` method ([#45624](https://github.com/angular/angular/pull/45624)) |
|
||||
| [b568a5e708](https://github.com/angular/angular/commit/b568a5e708579f5035f40c7218fbba39ad6b3065) | feat | implement `importProvidersFrom` function ([#45626](https://github.com/angular/angular/pull/45626)) |
|
||||
| [d5a6cd1111](https://github.com/angular/angular/commit/d5a6cd11110c78762a8e6115a718f5851508dbf2) | feat | implement EnvironmentInjector with adapter to NgModuleRef ([#45626](https://github.com/angular/angular/pull/45626)) |
|
||||
| [57f8ab2ed8](https://github.com/angular/angular/commit/57f8ab2ed81f9fa12ff84fabcd9771ab877809c5) | fix | better error message when directive extends a component ([#45658](https://github.com/angular/angular/pull/45658)) |
|
||||
| [c6e0e3f6d3](https://github.com/angular/angular/commit/c6e0e3f6d31a66dc3034da4dfddbffed4bec37d2) | fix | improve multiple components match error ([#45645](https://github.com/angular/angular/pull/45645)) |
|
||||
### forms
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [e0a2248b32](https://github.com/angular/angular/commit/e0a2248b3233b5d384f33859ef6207613cad909d) | feat | Add a FormRecord type. ([#45607](https://github.com/angular/angular/pull/45607)) |
|
||||
| [ff3f5a8d12](https://github.com/angular/angular/commit/ff3f5a8d12e3243620e311b690a050e26493e539) | fix | Fix a typing bug in FormBuilder. ([#45684](https://github.com/angular/angular/pull/45684)) |
|
||||
### http
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [76a9a24cdc](https://github.com/angular/angular/commit/76a9a24cdcb87e36868e2e29d1831af2dec3a818) | fix | encode + signs in query params as %2B (angular[#11058](https://github.com/angular/angular/pull/11058)) ([#45111](https://github.com/angular/angular/pull/45111)) |
|
||||
### language-service
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [f57e46c538](https://github.com/angular/angular/commit/f57e46c53881c46e712a64e23bffb1893dfe54fc) | fix | two-way binding completion should not remove the trailing quote ([#45582](https://github.com/angular/angular/pull/45582)) |
|
||||
### router
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [f4fd1a8262](https://github.com/angular/angular/commit/f4fd1a82620b5b3899c5e8c89fa06b084ee5792e) | feat | Add `EnvironmentInjector` to `RouterOutlet.activateWith` ([#45597](https://github.com/angular/angular/pull/45597)) |
|
||||
| [4e0957a4e1](https://github.com/angular/angular/commit/4e0957a4e10d1ae188faa843043a2314c9873c52) | feat | Add ability to specify providers on a Route ([#45673](https://github.com/angular/angular/pull/45673)) |
|
||||
| [989e840cce](https://github.com/angular/angular/commit/989e840cce7ebe94311ae898786e09b1b41ce7f6) | fix | Remove unused string type for ActivatedRoute.component ([#45625](https://github.com/angular/angular/pull/45625)) |
|
||||
| [96fd29c6d2](https://github.com/angular/angular/commit/96fd29c6d2d2abc5afee4d21a3e964a79aa39844) | fix | validate lazy loaded configs ([#45526](https://github.com/angular/angular/pull/45526)) |
|
||||
| [f13295f3a3](https://github.com/angular/angular/commit/f13295f3a3a1d622d15cf8339360d53feba824b5) | perf | cancel the navigation instantly if at least one resolver doesn't emit any value ([#45621](https://github.com/angular/angular/pull/45621)) |
|
||||
| [1d2f5c1101](https://github.com/angular/angular/commit/1d2f5c1101ccd182f5b528de52583a1b98dd6789) | refactor | deprecate no longer needed resolver fields ([#45597](https://github.com/angular/angular/pull/45597)) |
|
||||
## Special Thanks
|
||||
Adrian Kunz, Alex Rickabaugh, Andrew Kushnir, Andrew Scott, Dmitrij Kuba, Doug Parker, Dylan Hunn, George Kalpakas, Ilya Marchik, Jeremy Elbourn, Kristiyan Kostadinov, Louis Gombert, Mangalraj, Marko Kaznovac, Paul Gschwendtner, Saurabh Kamble, dario-piotrowicz and ivanwonder
|
||||
|
||||
<!-- CHANGELOG SPLIT MARKER -->
|
||||
|
||||
<a name="13.3.4"></a>
|
||||
# 13.3.4 (2022-04-20)
|
||||
### core
|
||||
@@ -137,24 +366,6 @@ Andrew Kushnir, Andrew Scott, George Kalpakas, Ilya Marchik, Jeremy Elbourn, Kri
|
||||
|
||||
<!-- CHANGELOG SPLIT MARKER -->
|
||||
|
||||
<a name="14.0.0-next.13"></a>
|
||||
# 14.0.0-next.13 (2022-04-13)
|
||||
## Breaking Changes
|
||||
### core
|
||||
- Since Ivy, TestBed doesn't use AOT summaries. The `aotSummaries` fields in TestBed APIs were present, but unused. The fields were deprecated in previous major version and in v14 those fields are removed. The `aotSummaries` fields were completely unused, so you can just drop them from the TestBed APIs usage.
|
||||
### compiler
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [598b759d33](https://github.com/angular/angular/commit/598b759d33221cc2bbfad4895ca0d2499a0547f8) | fix | avoid errors with extremely long instruction chains ([#45574](https://github.com/angular/angular/pull/45574)) |
|
||||
### core
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [9add714b13](https://github.com/angular/angular/commit/9add714b13740db621eb2b200d72be74cc7eb630) | refactor | remove deprecated `aotSummaries` fields in TestBed config ([#45487](https://github.com/angular/angular/pull/45487)) |
|
||||
## Special Thanks
|
||||
4javier, Andrew Kushnir, Andrew Scott, Dylan Hunn, Jason Hendee, Jessica Janiuk, Kristiyan Kostadinov, Michael-Doner, Michal Materowski and Virginia Dooley
|
||||
|
||||
<!-- CHANGELOG SPLIT MARKER -->
|
||||
|
||||
<a name="13.3.3"></a>
|
||||
# 13.3.3 (2022-04-13)
|
||||
### compiler
|
||||
@@ -166,63 +377,6 @@ Andrew Kushnir, Andrew Scott, George Kalpakas, Ilya Marchik, Jeremy Elbourn, Kri
|
||||
|
||||
<!-- CHANGELOG SPLIT MARKER -->
|
||||
|
||||
<a name="14.0.0-next.12"></a>
|
||||
# 14.0.0-next.12 (2022-04-12)
|
||||
## Breaking Changes
|
||||
### forms
|
||||
- Forms classes accept a generic.
|
||||
|
||||
Forms model classes now accept a generic type parameter. Untyped versions of these classes are available to opt-out of the new, stricter behavior.
|
||||
### router
|
||||
- When returning a `Promise` from the
|
||||
`LoadChildrenCallback`, the possible type is now restricted to
|
||||
`Type<any>|NgModuleFactory<any>` rather than `any`.
|
||||
### animations
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [a6fa37bc6e](https://github.com/angular/angular/commit/a6fa37bc6e705fee48f555d4b8022915094e8b16) | feat | make validateStyleProperty check dev-mode only ([#45570](https://github.com/angular/angular/pull/45570)) |
|
||||
### bazel
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [636909fba7](https://github.com/angular/angular/commit/636909fba73e25b75489c6e050bda1b880e62885) | feat | allow for generated `package.json` files in `ng_package` ([#45470](https://github.com/angular/angular/pull/45470)) |
|
||||
| [28e835b4bb](https://github.com/angular/angular/commit/28e835b4bbd9271cbc7abe2a0e42ff273b1e7e84) | feat | report error when dependency does not provide JS sources in `ng_package` ([#45470](https://github.com/angular/angular/pull/45470)) |
|
||||
| [4b2e98d55d](https://github.com/angular/angular/commit/4b2e98d55dde4f6040a021a6ebd35d29fb5f8fdf) | fix | remove unnecessary file extractions from `ng_package` ([#45470](https://github.com/angular/angular/pull/45470)) |
|
||||
### forms
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [89d299105a](https://github.com/angular/angular/commit/89d299105a4ae11985fb809f42cd0e34511207c9) | feat | Implement strict types for the Angular Forms package. ([#43834](https://github.com/angular/angular/pull/43834)) |
|
||||
### platform-browser
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [c7bf75dd5e](https://github.com/angular/angular/commit/c7bf75dd5e7f3f80a3c2afb8586ae46f7258f349) | fix | remove obsolete shim for Map comparison in Jasmine ([#45521](https://github.com/angular/angular/pull/45521)) |
|
||||
### router
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [f8f3ab377b](https://github.com/angular/angular/commit/f8f3ab377b8842c2bae0f215a0e90aaa3a1e070a) | fix | Remove `any` from `LoadChildrenCallback` type ([#45524](https://github.com/angular/angular/pull/45524)) |
|
||||
## Special Thanks
|
||||
Andrew Scott, Cédric Exbrayat, Dylan Hunn, George Kalpakas, Hossein Mousavi, Jessica Janiuk, Joe Martin (Crowdstaffing), Joey Perrott, Kristiyan Kostadinov, Leosvel Pérez Espinosa, Michael-Doner, Michal Materowski, Paul Gschwendtner, Samuel Littley, Sumit Arora and dario-piotrowicz
|
||||
|
||||
<!-- CHANGELOG SPLIT MARKER -->
|
||||
|
||||
<a name="14.0.0-next.11"></a>
|
||||
# 14.0.0-next.11 (2022-04-06)
|
||||
### animations
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [29d3891782](https://github.com/angular/angular/commit/29d38917825b9334a049799cdac60f0d39baf65a) | fix | handle structured AnimateTimings ([#31107](https://github.com/angular/angular/pull/31107)) |
|
||||
### migrations
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [d56a537196](https://github.com/angular/angular/commit/d56a537196d2ab0f3f48821889020d2f5bc621ba) | feat | Add migration to add explicit `Route`/`Routes` type ([#45084](https://github.com/angular/angular/pull/45084)) |
|
||||
### router
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [1c11a57155](https://github.com/angular/angular/commit/1c11a5715576632a4fb7170202395cf95dfbce09) | fix | merge interited resolved data and static data ([#45276](https://github.com/angular/angular/pull/45276)) |
|
||||
## Special Thanks
|
||||
Alan Agius, Andrew Kushnir, Andrew Scott, Chabbey François, Christoph Guttandin, Cédric Exbrayat, Dylan Hunn, George Kalpakas, Martin Sikora, mgechev and piyush132000
|
||||
|
||||
<!-- CHANGELOG SPLIT MARKER -->
|
||||
|
||||
<a name="13.3.2"></a>
|
||||
# 13.3.2 (2022-04-06)
|
||||
### animations
|
||||
@@ -253,114 +407,6 @@ AlirezaEbrahimkhani, Andrew Kushnir, Andrew Scott, Ben Brook, Dylan Hunn, George
|
||||
|
||||
<!-- CHANGELOG SPLIT MARKER -->
|
||||
|
||||
<a name="14.0.0-next.10"></a>
|
||||
# 14.0.0-next.10 (2022-03-30)
|
||||
## Breaking Changes
|
||||
### http
|
||||
- JSONP will throw an error when headers are set on a request
|
||||
|
||||
JSONP does not support headers being set on requests. Before when
|
||||
a request was sent to a JSONP backend that had headers set the headers
|
||||
were ignored. The JSONP backend will now throw an error if it
|
||||
receives a request that has any headers set. Any uses of JSONP
|
||||
on requests with headers set will need to remove the headers
|
||||
to avoid the error.
|
||||
### compiler-cli
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [2142ffd295](https://github.com/angular/angular/commit/2142ffd295de491eb8582c2eb1712b5b48044f24) | feat | propagate `standalone` flag to runtime ([#44973](https://github.com/angular/angular/pull/44973)) |
|
||||
| [6f653e05f9](https://github.com/angular/angular/commit/6f653e05f985141ae4d2d90af78b2bc001595661) | feat | standalone types imported into NgModule scopes ([#44973](https://github.com/angular/angular/pull/44973)) |
|
||||
| [3d13343975](https://github.com/angular/angular/commit/3d133439754cbf5d5a20bb3c714c8673c848e465) | fix | better error messages for NgModule structural issues ([#44973](https://github.com/angular/angular/pull/44973)) |
|
||||
| [06050ac2b4](https://github.com/angular/angular/commit/06050ac2b4937836096fb331ec71bacb5a1fc231) | fix | handle inline type-check blocks in nullish coalescing extended check ([#45454](https://github.com/angular/angular/pull/45454)) |
|
||||
### core
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [d36fa111eb](https://github.com/angular/angular/commit/d36fa111eb677d504a9952d8b4ca53560cadd04d) | fix | avoid Closure Compiler error in restoreView ([#45445](https://github.com/angular/angular/pull/45445)) |
|
||||
### forms
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [b36dec6b5b](https://github.com/angular/angular/commit/b36dec6b5b05de0d40581726c59db9f962a97124) | fix | not picking up disabled state if group is swapped out and disabled ([#43499](https://github.com/angular/angular/pull/43499)) |
|
||||
### http
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [d43c0e973f](https://github.com/angular/angular/commit/d43c0e973f4389c74eb19a7f6b667bd2e1d380fe) | fix | Throw error when headers are supplied in JSONP request ([#45210](https://github.com/angular/angular/pull/45210)) |
|
||||
## Special Thanks
|
||||
Alex Rickabaugh, AlirezaEbrahimkhani, Andrew Kushnir, Andrew Scott, Dylan Hunn, JiaLiPassion, JoostK, Kristiyan Kostadinov, Maddie Klein, Michael-Doner, Paul Gschwendtner, Willian Corrêa, arturovt, dario-piotrowicz and zverbeta
|
||||
|
||||
<!-- CHANGELOG SPLIT MARKER -->
|
||||
|
||||
<a name="14.0.0-next.9"></a>
|
||||
# 14.0.0-next.9 (2022-03-25)
|
||||
## Special Thanks
|
||||
Paul Gschwendtner
|
||||
|
||||
<!-- CHANGELOG SPLIT MARKER -->
|
||||
|
||||
<a name="14.0.0-next.8"></a>
|
||||
# 14.0.0-next.8 (2022-03-25)
|
||||
## Breaking Changes
|
||||
### core
|
||||
- TypeScript versions older than 4.6 are no longer supported.
|
||||
### zone.js
|
||||
- in TaskTrackingZoneSpec track a periodic task until it is cancelled
|
||||
|
||||
The breaking change is scoped only to the plugin
|
||||
`zone.js/plugins/task-tracking`. If you used `TaskTrackingZoneSpec` and
|
||||
checked the pending macroTasks e.g. using `(this.ngZone as any)._inner
|
||||
._parent._properties.TaskTrackingZone.getTasksFor('macroTask')`, then
|
||||
its behavior slightly changed for periodic macrotasks. For example,
|
||||
previously the `setInterval` macrotask was no longer tracked after its
|
||||
callback was executed for the first time. Now it's tracked until
|
||||
the task is explicitly cancelled, e.g with `clearInterval(id)`.
|
||||
|
||||
fixes 45350
|
||||
### animations
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [79d334b138](https://github.com/angular/angular/commit/79d334b1385685d0fd1451534df2ed3cbaefa0b6) | feat | provide warnings for non-animatable CSS properties ([#45212](https://github.com/angular/angular/pull/45212)) |
|
||||
| [2a75754ee8](https://github.com/angular/angular/commit/2a75754ee823032e651bc596887a2186dc6846ae) | fix | apply default params when resolved value is null or undefined ([#45339](https://github.com/angular/angular/pull/45339)) |
|
||||
### bazel
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [dc72f3007a](https://github.com/angular/angular/commit/dc72f3007ac74389ec02ea021a27f35770aa5a5d) | fix | ng module compilation workers are subject to linker race-conditions ([#45393](https://github.com/angular/angular/pull/45393)) |
|
||||
### compiler
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [fffa023803](https://github.com/angular/angular/commit/fffa023803e5b04a58f0bd0b8667ee8b686aa530) | fix | scope css rules within `@layer` blocks ([#45396](https://github.com/angular/angular/pull/45396)) |
|
||||
### compiler-cli
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [27b4af7240](https://github.com/angular/angular/commit/27b4af7240ea964d1e4d51b83118f10321163627) | fix | full side-effectful registration of NgModules with ids ([#45024](https://github.com/angular/angular/pull/45024)) |
|
||||
| [8155428ba6](https://github.com/angular/angular/commit/8155428ba65c38c0c15f2666727202a7b360c1bd) | perf | ignore the module.id anti-pattern for NgModule ids ([#45024](https://github.com/angular/angular/pull/45024)) |
|
||||
### core
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [c9d566ce4b](https://github.com/angular/angular/commit/c9d566ce4b6e9097d9eceb7ac3964a0b25c404ad) | feat | drop support for TypeScript 4.4 and 4.5 ([#45394](https://github.com/angular/angular/pull/45394)) |
|
||||
| [be161bef79](https://github.com/angular/angular/commit/be161bef798ac5309df8390e3c2bba2cf84cb530) | fix | memory leak in event listeners inside embedded views ([#43075](https://github.com/angular/angular/pull/43075)) |
|
||||
### forms
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [fe0e42a996](https://github.com/angular/angular/commit/fe0e42a996d5b70f4c0923cb6570c9a8032b1298) | fix | Make UntypedFormBuilder assignable to FormBuilder, and vice versa. ([#45421](https://github.com/angular/angular/pull/45421)) |
|
||||
## Special Thanks
|
||||
Alan Agius, Alex Rickabaugh, AlirezaEbrahimkhani, Ben Brook, Dylan Hunn, George Kalpakas, Joey Perrott, JoostK, Kristiyan Kostadinov, Krzysztof Platis, Mike, Paul Gschwendtner, Tobias Speicher, alirezaghey, arturovt, dario-piotrowicz, khai and mgechev
|
||||
|
||||
<!-- CHANGELOG SPLIT MARKER -->
|
||||
|
||||
<a name="14.0.0-next.7"></a>
|
||||
# 14.0.0-next.7 (2022-03-16)
|
||||
### core
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [225e4f2dbe](https://github.com/angular/angular/commit/225e4f2dbeac5982982fb94eac65ea931c44247a) | feat | triggerEventHandler accept optional eventObj ([#45279](https://github.com/angular/angular/pull/45279)) |
|
||||
### forms
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [9db9091349](https://github.com/angular/angular/commit/9db9091349e0e9761855d3c55729004674eec3c5) | fix | improve error message for invalid value accessors ([#45192](https://github.com/angular/angular/pull/45192)) |
|
||||
## Special Thanks
|
||||
Alan Agius, Alistair Kane, Amer Yousuf, Andrew Kushnir, Andrew Scott, Derek Cormier, Jessica Janiuk, JiaLiPassion, Lee Cooper, Paul Gschwendtner, alirezaghey and why520crazy
|
||||
|
||||
<!-- CHANGELOG SPLIT MARKER -->
|
||||
|
||||
<a name="13.3.0"></a>
|
||||
# 13.3.0-rc.0 (2022-03-16)
|
||||
### core
|
||||
@@ -394,41 +440,6 @@ Alistair Kane, Andrew Scott and Kristiyan Kostadinov
|
||||
|
||||
<!-- CHANGELOG SPLIT MARKER -->
|
||||
|
||||
<a name="14.0.0-next.6"></a>
|
||||
# 14.0.0-next.6 (2022-03-09)
|
||||
## Breaking Changes
|
||||
### core
|
||||
- Support for Node.js v12 has been removed as it will become EOL on 2022-04-30. Please use Node.js v14.15 or later.
|
||||
### compiler
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [2b7553db6f](https://github.com/angular/angular/commit/2b7553db6f59e64b73fc2e5601db278f815a67a2) | fix | compute correct offsets when interpolations have HTML entities ([#44811](https://github.com/angular/angular/pull/44811)) |
|
||||
### core
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [6eaaefd22e](https://github.com/angular/angular/commit/6eaaefd22ebb95ee896a62237832ff2f2defdee0) | feat | drop support for Node.js 12 ([#45286](https://github.com/angular/angular/pull/45286)) |
|
||||
| [94bba76a4a](https://github.com/angular/angular/commit/94bba76a4a9594a5eb90e581f407f1b70697e715) | feat | support TypeScript 4.6 ([#45190](https://github.com/angular/angular/pull/45190)) |
|
||||
| [9fa6f5a552](https://github.com/angular/angular/commit/9fa6f5a552b7780ce32aa5a60273d4008a7b6afb) | fix | incorrectly inserting elements inside `<template>` element ([#43429](https://github.com/angular/angular/pull/43429)) |
|
||||
### forms
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [4332897baa](https://github.com/angular/angular/commit/4332897baa2226ef246ee054fdd5254e3c129109) | fix | Add UntypedFormBuilder ([#45268](https://github.com/angular/angular/pull/45268)) |
|
||||
| [aa7b857be8](https://github.com/angular/angular/commit/aa7b857be89a62e6dcfe62c418acf0823525d265) | fix | Property renaming safe code ([#45271](https://github.com/angular/angular/pull/45271)) |
|
||||
| [d336ba96d9](https://github.com/angular/angular/commit/d336ba96d922363235688f54d8af108ef7ab01f0) | fix | Update the typed forms migration. ([#45281](https://github.com/angular/angular/pull/45281)) |
|
||||
### language-service
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [be220fa683](https://github.com/angular/angular/commit/be220fa683e636e5ec3e49f56859015ec66a71fb) | fix | Prioritize Angular-specific completions over DOM completions ([#45293](https://github.com/angular/angular/pull/45293)) |
|
||||
### service-worker
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [bd04fbc05b](https://github.com/angular/angular/commit/bd04fbc05b0397de966a493a863e1054203c86d9) | feat | emit a notification when the service worker is already up to date after check ([#45216](https://github.com/angular/angular/pull/45216)) |
|
||||
| [3ecf93020c](https://github.com/angular/angular/commit/3ecf93020ce06b9b8621f0c83126cb3d584d4181) | fix | file system hash in batch of 500 elements ([#45262](https://github.com/angular/angular/pull/45262)) |
|
||||
## Special Thanks
|
||||
Alan Agius, Andrew Kushnir, Andrew Scott, Anner Visser, Aristeidis Bampakos, Dylan Hunn, George Kalpakas, JiaLiPassion, Joey Perrott, JoostK, Kristiyan Kostadinov, Luca, Mladen Jakovljević, Paul Gschwendtner, Srikanth Kolli, ananyahs96 and nanda18
|
||||
|
||||
<!-- CHANGELOG SPLIT MARKER -->
|
||||
|
||||
<a name="13.2.6"></a>
|
||||
# 13.2.6 (2022-03-09)
|
||||
### compiler
|
||||
@@ -448,50 +459,6 @@ Andrew Scott, Anner Visser, Aristeidis Bampakos, JiaLiPassion, Joey Perrott, Kri
|
||||
|
||||
<!-- CHANGELOG SPLIT MARKER -->
|
||||
|
||||
<a name="14.0.0-next.5"></a>
|
||||
# 14.0.0-next.5 (2022-03-02)
|
||||
## Breaking Changes
|
||||
### router
|
||||
- The type of `Route.pathMatch` is now more strict. Places that use
|
||||
`pathMatch` will likely need to be updated to have an explicit
|
||||
`Route`/`Routes` type so that TypeScript does not infer the type as
|
||||
`string`.
|
||||
|
||||
- Previously, resolvers were waiting to be completed
|
||||
before proceeding with the navigation and the Router would take the last
|
||||
value emitted from the resolver.
|
||||
The router now takes only the first emitted value by the resolvers
|
||||
and then proceeds with navigation. This is now consistent with `Observables`
|
||||
returned by other guards: only the first value is used.
|
||||
### common
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [7671a1e40d](https://github.com/angular/angular/commit/7671a1e40d0f2abe1cb7e9d84d5ac47a5adb546a) | fix | canceled JSONP requests won't throw console error with missing callback function ([#36807](https://github.com/angular/angular/pull/36807)) |
|
||||
| [05d50b849b](https://github.com/angular/angular/commit/05d50b849bb891c37c3eefab81a45099057dfd78) | perf | make `NgLocalization` token tree-shakable ([#45118](https://github.com/angular/angular/pull/45118)) |
|
||||
### core
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [69018c9f42](https://github.com/angular/angular/commit/69018c9f42d14c1c7aa8271b406d6d247de1c564) | feat | allow for injector to be specified when creating an embedded view ([#45156](https://github.com/angular/angular/pull/45156)) |
|
||||
| [c5df474c7c](https://github.com/angular/angular/commit/c5df474c7c744305c9412090b5e19bb6d4f7128d) | fix | remove individual commands for updating gold files ([#45198](https://github.com/angular/angular/pull/45198)) |
|
||||
| [88f1168506](https://github.com/angular/angular/commit/88f1168506befd0665125da1fca2a719cb27dbb5) | perf | only track LViews that are referenced in __ngContext__ ([#45172](https://github.com/angular/angular/pull/45172)) |
|
||||
### forms
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [7ee121f595](https://github.com/angular/angular/commit/7ee121f595a47338b31098ae51a6f91d80c8fdce) | feat | Add untyped versions of the model classes for use in migration. ([#45205](https://github.com/angular/angular/pull/45205)) |
|
||||
### localize
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [ca5603b09b](https://github.com/angular/angular/commit/ca5603b09b06481529dce78ff995d674383e274f) | fix | avoid imports into `compiler-cli` package ([#45180](https://github.com/angular/angular/pull/45180)) |
|
||||
### router
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [7fd416d060](https://github.com/angular/angular/commit/7fd416d060bd873bc88dffed41946c51aa649ec1) | fix | Fix type of Route.pathMatch to be more accurate ([#45176](https://github.com/angular/angular/pull/45176)) |
|
||||
| [c9679760b2](https://github.com/angular/angular/commit/c9679760b2bf5c607c957c20482b9cea7a21702b) | refactor | take only the first emitted value of every resolver to make it consistent with guards ([#44573](https://github.com/angular/angular/pull/44573)) |
|
||||
## Special Thanks
|
||||
Andrew Kushnir, Andrew Scott, Charles Lyding, Dmitrij Kuba, Dylan Hunn, Guillaume Bonnet, Jessica Janiuk, JiaLiPassion, JoostK, Kristiyan Kostadinov, Martin Sikora, Paul Gschwendtner, Theodore Brown and dario-piotrowicz
|
||||
|
||||
<!-- CHANGELOG SPLIT MARKER -->
|
||||
|
||||
<a name="13.2.5"></a>
|
||||
# 13.2.5 (2022-03-02)
|
||||
### animations
|
||||
@@ -522,34 +489,6 @@ Andrew Kushnir, Andrew Scott, Charles Lyding, Guillaume Bonnet, Jessica Janiuk,
|
||||
|
||||
<!-- CHANGELOG SPLIT MARKER -->
|
||||
|
||||
<a name="14.0.0-next.4"></a>
|
||||
# 14.0.0-next.4 (2022-02-23)
|
||||
## Breaking Changes
|
||||
### animations
|
||||
- The `AnimationDriver.getParentElement` method has become required, so any
|
||||
implementors of this interface are now required to provide an implementation
|
||||
for this method. This breakage is unlikely to affect application developers,
|
||||
as `AnimationDriver` is not expected to be implemented in user code.
|
||||
### animations
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [f8dc660605](https://github.com/angular/angular/commit/f8dc66060591e1ce42265a7862c3df16e40b05e1) | fix | allow animations with unsupported CSS properties ([#44729](https://github.com/angular/angular/pull/44729)) |
|
||||
| [59559fdbac](https://github.com/angular/angular/commit/59559fdbacc00bee380b1e88c79d08b6e856ebbe) | refactor | make `AnimationDriver.getParentElement` required ([#45114](https://github.com/angular/angular/pull/45114)) |
|
||||
### compiler-cli
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [c0778b4dfc](https://github.com/angular/angular/commit/c0778b4dfc1c107543b3db6f1958814a0812c7df) | fix | Support resolve animation name from the DTS ([#45107](https://github.com/angular/angular/pull/45107)) |
|
||||
### core
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [071c8af8ba](https://github.com/angular/angular/commit/071c8af8ba89163270f17657c0c7f1c944f186f7) | perf | avoid storing LView in __ngContext__ ([#45051](https://github.com/angular/angular/pull/45051)) |
|
||||
| [a96c4827c4](https://github.com/angular/angular/commit/a96c4827c4f62e1d665d8544a3e66c51d2644c88) | perf | make `Compiler`, `ApplicationRef` and `ApplicationInitStatus` tree-shakable ([#45102](https://github.com/angular/angular/pull/45102)) |
|
||||
| [1e60fe0a3e](https://github.com/angular/angular/commit/1e60fe0a3eeb6bd46997f7eeb4825351cfbe3c44) | perf | make `LOCALE_ID` and other tokens from `ApplicationModule` tree-shakable ([#45102](https://github.com/angular/angular/pull/45102)) |
|
||||
## Special Thanks
|
||||
Alan Cohen, AlirezaEbrahimkhani, Andrew Kushnir, Andrew Scott, Daniele Maltese, David Wolf, JiaLiPassion, JoostK, Kristiyan Kostadinov, Paul Gschwendtner, arturovt, dario-piotrowicz, ivanwonder and manuelkroiss
|
||||
|
||||
<!-- CHANGELOG SPLIT MARKER -->
|
||||
|
||||
<a name="13.2.4"></a>
|
||||
# 13.2.4 (2022-02-23)
|
||||
### animations
|
||||
@@ -565,32 +504,6 @@ Alan Cohen, AlirezaEbrahimkhani, Andrew Kushnir, Daniele Maltese, David Wolf, Jo
|
||||
|
||||
<!-- CHANGELOG SPLIT MARKER -->
|
||||
|
||||
<a name="14.0.0-next.3"></a>
|
||||
# 14.0.0-next.3 (2022-02-17)
|
||||
### animations
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [5c7c56bc85](https://github.com/angular/angular/commit/5c7c56bc859b195bf6710f6c1479d9e18dde35b1) | perf | improve algorithm to balance animation namespaces ([#45057](https://github.com/angular/angular/pull/45057)) |
|
||||
| [4c778cdb28](https://github.com/angular/angular/commit/4c778cdb28de128c6ddecd2fd6cb6257d675500b) | perf | made errors in the animations package tree shakeable ([#45004](https://github.com/angular/angular/pull/45004)) |
|
||||
### common
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [bedb257afc](https://github.com/angular/angular/commit/bedb257afc1ca12eb221536ea44ade960e62cda0) | fix | cleanup URL change listeners when the root view is removed ([#44901](https://github.com/angular/angular/pull/44901)) |
|
||||
### compiler
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [1b91e1049e](https://github.com/angular/angular/commit/1b91e1049eedfb96febd9f6cc45fb7c0e1f154a7) | perf | chain element start/end instructions ([#44994](https://github.com/angular/angular/pull/44994)) |
|
||||
### core
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [e461f716d4](https://github.com/angular/angular/commit/e461f716d4b736829d53de26ba6dddb58f476746) | feat | move ANIMATION_MODULE_TYPE injection token into core ([#44970](https://github.com/angular/angular/pull/44970)) |
|
||||
| [822439fddd](https://github.com/angular/angular/commit/822439fdddfca316ac826569042efab460e67b50) | fix | `ViewContainerRef.createComponent` should consult module injector when custom one is provided ([#44966](https://github.com/angular/angular/pull/44966)) |
|
||||
| [45d98e7ca5](https://github.com/angular/angular/commit/45d98e7ca5accf918e2602cbc17805e11e18896b) | perf | make `IterableDiffers` and `KeyValueDiffers` tree-shakable ([#45094](https://github.com/angular/angular/pull/45094)) |
|
||||
## Special Thanks
|
||||
Alan Cohen, AlirezaEbrahimkhani, Amer Yousuf, Andrew Kushnir, Aristeidis Bampakos, Dario Piotrowicz, Dylan Hunn, Esteban Gehring, George Kalpakas, Jessica Janiuk, JiaLiPassion, Joey Perrott, JoostK, Kristiyan Kostadinov, Mina Hosseini Moghadam, Patrick Cameron, Srdjan Milic, Yousaf Nawaz, arturovt, dario-piotrowicz, markostanimirovic, mgechev and zuckjet
|
||||
|
||||
<!-- CHANGELOG SPLIT MARKER -->
|
||||
|
||||
<a name="13.2.3"></a>
|
||||
# 13.2.3 (2022-02-16)
|
||||
### animations
|
||||
@@ -633,118 +546,6 @@ Alan Agius, Alan Cohen, Andrew Kushnir, Andrew Scott, Daniel Díaz, Dario Piotro
|
||||
|
||||
<!-- CHANGELOG SPLIT MARKER -->
|
||||
|
||||
<a name="14.0.0-next.2"></a>
|
||||
# 14.0.0-next.2 (2022-02-07)
|
||||
## Breaking Changes
|
||||
|
||||
### forms
|
||||
- Forms [email] input coercion: forms [email] input value will be considered as true if it is defined with any value rather than false and 'false'.
|
||||
- Objects with a length key set to zero will no longer validate as empty. This is technically a breaking change, since objects with a key `length` and value `0` will no longer validate as empty. This is a very minor change, and any reliance on this behavior is probably a bug anyway.
|
||||
### platform-browser
|
||||
- This change may cause a breaking change in unit tests that are implicitly depending on a specific number and sequence of change detections in order for their assertions to pass.
|
||||
- This tightens parameter types of `TransferState` usage, and is a minor breaking change which may reveal existing problematic calls. This may break invalid calls to `TransferState` methods.
|
||||
### router
|
||||
- The type of `initialUrl` is set to `string|UrlTree` but in reality,
|
||||
the `Router` only sets it to a value that will always be `UrlTree`.
|
||||
`initialUrl` is documented as "The target URL passed into the
|
||||
`Router#navigateByUrl()` call before navigation" but the value
|
||||
actually gets set to something completely different. It's set to the
|
||||
current internal `UrlTree` of the Router at the time navigation
|
||||
occurs. With this change, there is no exact replacement for the old value of
|
||||
`initialUrl` because it was enver intended to be exposed.
|
||||
`Router.url` is likely the best replacement for this.
|
||||
In more specific use-cases, tracking the `finalUrl` between successful
|
||||
navigations can also be used as a replacement.
|
||||
### animations
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [e46b379204](https://github.com/angular/angular/commit/e46b37920438d84bff895498c0a102dd1ffba178) | fix | implement missing transition delay ([#44799](https://github.com/angular/angular/pull/44799)) |
|
||||
### common
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [38c03a2035](https://github.com/angular/angular/commit/38c03a20358db3f8621c023b98e627cd385731c0) | feat | support years greater than 9999 ([#43622](https://github.com/angular/angular/pull/43622)) |
|
||||
### compiler
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [db6cf7e7c1](https://github.com/angular/angular/commit/db6cf7e7c168f9a25550d7fa53840c08b50b889c) | fix | allow banana-in-a-box bindings to end with non-null assertion ([#37809](https://github.com/angular/angular/pull/37809)) |
|
||||
### compiler-cli
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [0072eb48ba](https://github.com/angular/angular/commit/0072eb48ba1c6f549703988b7fd7ba3e09058048) | feat | initial implementation of standalone components ([#44812](https://github.com/angular/angular/pull/44812)) |
|
||||
### core
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [d5719c2e0f](https://github.com/angular/angular/commit/d5719c2e0fb237be71d658444bbfe2410e05086a) | fix | input coercion ([#42803](https://github.com/angular/angular/pull/42803)) |
|
||||
### forms
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [8dd3f82f94](https://github.com/angular/angular/commit/8dd3f82f946bae86dc6c678d8694ab73d915bbfa) | fix | Correct empty validator to handle objects with a property `length: 0`. ([#33729](https://github.com/angular/angular/pull/33729)) |
|
||||
| [ebf2fc5224](https://github.com/angular/angular/commit/ebf2fc5224c9debfd839087c9d1d21fb49577d37) | fix | incorrectly keeping track of ngModel with ngFor inside a form ([#40459](https://github.com/angular/angular/pull/40459)) |
|
||||
### http
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [28393031b1](https://github.com/angular/angular/commit/28393031b10f4dbefab7587c19641e49cb820785) | perf | remove IE special status handling ([#44354](https://github.com/angular/angular/pull/44354)) |
|
||||
### platform-browser
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [a01bcb8e7e](https://github.com/angular/angular/commit/a01bcb8e7eaf63ac9466a78dd4d15228241da900) | fix | do not run change detection when loading Hammer ([#44921](https://github.com/angular/angular/pull/44921)) |
|
||||
| [b32647dc68](https://github.com/angular/angular/commit/b32647dc68b055da0c49c86d6e7e2a7d2ec5954a) | fix | Make transfer state key typesafe. ([#23020](https://github.com/angular/angular/pull/23020)) |
|
||||
### router
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [791bd31424](https://github.com/angular/angular/commit/791bd3142432ed7caf14a68a6e434b27004e634b) | feat | set stricter type for Route.title ([#44939](https://github.com/angular/angular/pull/44939)) |
|
||||
| [64f837d2c0](https://github.com/angular/angular/commit/64f837d2c0fbcf722d32b35a87e87220bfe61f65) | fix | Update `Navigation#initialUrl` to match documentation and reality ([#43863](https://github.com/angular/angular/pull/43863)) |
|
||||
### upgrade
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [202a1a5631](https://github.com/angular/angular/commit/202a1a56314af4ddb99c476f974536a10e390319) | fix | Do not trigger duplicate navigation events from Angular Router ([#43441](https://github.com/angular/angular/pull/43441)) |
|
||||
## Special Thanks
|
||||
Alan Agius, Alex Rickabaugh, Andrew Kushnir, Andrew Scott, Daniel Díaz, Dario Piotrowicz, Doug Parker, Jayson Acosta, Joey Perrott, JoostK, Kristiyan Kostadinov, Olivier Capuozzo, Payam Shahidi, Pusztai Tibor, Ramzan, Ruslan Lekhman, Sergej Grilborzer, Shai Reznik, TANMAY SRIVASTAVA, arturovt, dario-piotrowicz, iRealNirmal, jhonyeduardo, markostanimirovic, mgechev and zuckjet
|
||||
|
||||
<!-- CHANGELOG SPLIT MARKER -->
|
||||
|
||||
<a name="14.0.0-next.1"></a>
|
||||
# 14.0.0-next.1 (2022-02-02)
|
||||
## Deprecations
|
||||
### common
|
||||
- The `ngModuleFactory` input of the `NgComponentOutlet` directive is deprecated in favor of a newly added `ngModule` input. The `ngModule` input accepts references to the NgModule class directly, without the need to resolve module factory first.
|
||||
### animations
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [7a81481fb2](https://github.com/angular/angular/commit/7a81481fb29e94b550db7ad68270eccf26bb2743) | perf | Remove generic objects in favor of Maps ([#44482](https://github.com/angular/angular/pull/44482)) |
|
||||
| [6642e3c8fd](https://github.com/angular/angular/commit/6642e3c8fd7d3b5ed22710667b2c41a46f14eda8) | perf | remove no longer needed CssKeyframes classes ([#44903](https://github.com/angular/angular/pull/44903)) |
|
||||
### common
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [c89cf63059](https://github.com/angular/angular/commit/c89cf63059370bba43717483e3d9627499dfe815) | feat | support NgModule as an input to the NgComponentOutlet ([#44815](https://github.com/angular/angular/pull/44815)) |
|
||||
| [a2cbffc06f](https://github.com/angular/angular/commit/a2cbffc06f25ea1cb8f1c8aef658f7c213621a07) | fix | include query parameters for open HTTP requests in `verify` ([#44917](https://github.com/angular/angular/pull/44917)) |
|
||||
### compiler-cli
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [73c97aea9e](https://github.com/angular/angular/commit/73c97aea9e4a1cb94199272384d5e912d3b0ccd0) | fix | accept nullish coalescing operator for any and unknown types ([#44862](https://github.com/angular/angular/pull/44862)) |
|
||||
| [366e424a73](https://github.com/angular/angular/commit/366e424a7382cdbd72f5d1c3460fe2992fb72941) | fix | enable nullish coalescing check only with `strictNullChecks` ([#44862](https://github.com/angular/angular/pull/44862)) |
|
||||
| [d82c957a26](https://github.com/angular/angular/commit/d82c957a260f4e4a341e7036355a764a5e98d3ca) | fix | ensure casing of logical paths is preserved ([#44798](https://github.com/angular/angular/pull/44798)) |
|
||||
### core
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [5686f68fde](https://github.com/angular/angular/commit/5686f68fde514a7daf760aacc1fe52a7cb80b3af) | fix | Add back support for namespace URIs in createElement of dom renderer ([#44914](https://github.com/angular/angular/pull/44914)) |
|
||||
| [39c614c438](https://github.com/angular/angular/commit/39c614c4389fb48c8fc5a51329e9169a0139c4aa) | fix | flush delayed scoping queue while setting up TestBed ([#44814](https://github.com/angular/angular/pull/44814)) |
|
||||
### forms
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [f490c2de4e](https://github.com/angular/angular/commit/f490c2de4e1ada26e62e35c0d70cb90e55297658) | feat | support negative indices in FormArray methods. ([#44848](https://github.com/angular/angular/pull/44848)) |
|
||||
| [889de8276d](https://github.com/angular/angular/commit/889de8276d77eed3b65f8c2032f2f5c3c4ac9139) | fix | ensure OnPush ancestors are marked dirty when the promise resolves ([#44886](https://github.com/angular/angular/pull/44886)) |
|
||||
| [deb9531a31](https://github.com/angular/angular/commit/deb9531a31274fbedcbf468a33d80481328c55ce) | fix | Update the typed forms migration schematic to find all files. ([#44881](https://github.com/angular/angular/pull/44881)) |
|
||||
| [cae1e44608](https://github.com/angular/angular/commit/cae1e44608227ac80682401190c6d28ef39b3269) | fix | Update the typed forms migration to use FormArray<T> instead of FormArray<T[]>. ([#44933](https://github.com/angular/angular/pull/44933)) |
|
||||
### router
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [910de8bc33](https://github.com/angular/angular/commit/910de8bc3379b1452c0bc23a866b5108d6d94e55) | feat | Add `Route.title` with a configurable `TitleStrategy` ([#43307](https://github.com/angular/angular/pull/43307)) |
|
||||
## Special Thanks
|
||||
Alan, Alan Agius, AleksanderBodurri, Amir Rustamzadeh, Andrew Kushnir, Andrew Scott, Aristeidis Bampakos, Arjen, Daan De Smedt, Daniel Díaz, David Shevitz, Doug Parker, Dylan Hunn, Emma Twersky, Esteban Gehring, George Kalpakas, Jessica Janiuk, JimMorrison723, JoostK, Juri Strumpflohner, Lee Robinson, Maarten Tibau, Mark Whitfeld, Minko Gechev, Paul Gschwendtner, Ricardo Mattiazzi Baumgartner, Sumit Arora, Theodore Brown, arturovt, dario-piotrowicz, fru2, kamikopi, markostanimirovic, markwhitfeld, mgechev, renovate[bot] and twerske
|
||||
|
||||
<!-- CHANGELOG SPLIT MARKER -->
|
||||
|
||||
<a name="13.2.1"></a>
|
||||
# 13.2.1 (2022-02-02)
|
||||
### animations
|
||||
@@ -776,21 +577,6 @@ Alan, Andrew Kushnir, Andrew Scott, Aristeidis Bampakos, Arjen, Daniel Díaz, Da
|
||||
|
||||
<!-- CHANGELOG SPLIT MARKER -->
|
||||
|
||||
<a name="14.0.0-next.0"></a>
|
||||
# 14.0.0-next.0 (2022-01-26)
|
||||
### language-service
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [9d4af65e34](https://github.com/angular/angular/commit/9d4af65e343e41b48cb9afd12c2b61d8ca61a9ac) | feat | Provide plugin to delegate rename requests to Angular ([#44696](https://github.com/angular/angular/pull/44696)) |
|
||||
### service-worker
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [ec0a0e0669](https://github.com/angular/angular/commit/ec0a0e0669ef5d2f524fbf9a540eb2b306b2e24a) | feat | add `cacheOpaqueResponses` option for data-groups ([#44723](https://github.com/angular/angular/pull/44723)) |
|
||||
## Special Thanks
|
||||
Andrew Kushnir, Dylan Hunn, George Kalpakas, JiaLiPassion, Joey Perrott and ivanwonder
|
||||
|
||||
<!-- CHANGELOG SPLIT MARKER -->
|
||||
|
||||
<a name="13.2.0"></a>
|
||||
# 13.2.0 (2022-01-26)
|
||||
## Deprecations
|
||||
|
||||
+7
-7
@@ -94,7 +94,7 @@ Before you submit your Pull Request (PR) consider the following guidelines:
|
||||
5. In your forked repository, make your changes in a new git branch:
|
||||
|
||||
```shell
|
||||
git checkout -b my-fix-branch master
|
||||
git checkout -b my-fix-branch main
|
||||
```
|
||||
|
||||
6. Create your patch, **including appropriate test cases**.
|
||||
@@ -117,7 +117,7 @@ Before you submit your Pull Request (PR) consider the following guidelines:
|
||||
git push origin my-fix-branch
|
||||
```
|
||||
|
||||
11. In GitHub, send a pull request to `angular:master`.
|
||||
11. In GitHub, send a pull request to `angular:main`.
|
||||
|
||||
### Reviewing a Pull Request
|
||||
|
||||
@@ -181,10 +181,10 @@ After your pull request is merged, you can safely delete your branch and pull th
|
||||
git push origin --delete my-fix-branch
|
||||
```
|
||||
|
||||
* Check out the master branch:
|
||||
* Check out the main branch:
|
||||
|
||||
```shell
|
||||
git checkout master -f
|
||||
git checkout main -f
|
||||
```
|
||||
|
||||
* Delete the local branch:
|
||||
@@ -193,10 +193,10 @@ After your pull request is merged, you can safely delete your branch and pull th
|
||||
git branch -D my-fix-branch
|
||||
```
|
||||
|
||||
* Update your master with the latest upstream version:
|
||||
* Update your local `main` with the latest upstream version:
|
||||
|
||||
```shell
|
||||
git pull --ff upstream master
|
||||
git pull --ff upstream main
|
||||
```
|
||||
|
||||
|
||||
@@ -397,7 +397,7 @@ The following documents can help you sort out issues with GitHub accounts and mu
|
||||
[coc]: https://github.com/angular/code-of-conduct/blob/master/CODE_OF_CONDUCT.md
|
||||
[commit-message-format]: https://docs.google.com/document/d/1QrDFcIiPjSLDn3EL15IJygNPiHORgU1_OOAqWjiDU5Y/edit#
|
||||
[corporate-cla]: https://cla.developers.google.com/about/google-corporate
|
||||
[dev-doc]: https://github.com/angular/angular/blob/master/docs/DEVELOPER.md
|
||||
[dev-doc]: https://github.com/angular/angular/blob/main/docs/DEVELOPER.md
|
||||
[github]: https://github.com/angular/angular
|
||||
[discord]: https://discord.gg/angular
|
||||
[individual-cla]: https://cla.developers.google.com/about/google-individual
|
||||
|
||||
@@ -24,8 +24,8 @@
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://circleci.com/gh/angular/workflows/angular/tree/master">
|
||||
<img src="https://img.shields.io/circleci/build/github/angular/angular/master.svg?logo=circleci&logoColor=fff&label=CircleCI" alt="CI status" />
|
||||
<a href="https://circleci.com/gh/angular/workflows/angular/tree/main">
|
||||
<img src="https://img.shields.io/circleci/build/github/angular/angular/main.svg?logo=circleci&logoColor=fff&label=CircleCI" alt="CI status" />
|
||||
</a>
|
||||
<a href="https://www.npmjs.com/@angular/core">
|
||||
<img src="https://img.shields.io/npm/v/@angular/core.svg?logo=npm&logoColor=fff&label=NPM+package&color=limegreen" alt="Angular on npm" />
|
||||
|
||||
@@ -10,8 +10,8 @@ load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
|
||||
|
||||
http_archive(
|
||||
name = "build_bazel_rules_nodejs",
|
||||
sha256 = "2b2004784358655f334925e7eadc7ba80f701144363df949b3293e1ae7a2fb7b",
|
||||
urls = ["https://github.com/bazelbuild/rules_nodejs/releases/download/5.4.0/rules_nodejs-5.4.0.tar.gz"],
|
||||
sha256 = "e328cb2c9401be495fa7d79c306f5ee3040e8a03b2ebb79b022e15ca03770096",
|
||||
urls = ["https://github.com/bazelbuild/rules_nodejs/releases/download/5.4.2/rules_nodejs-5.4.2.tar.gz"],
|
||||
)
|
||||
|
||||
load("@build_bazel_rules_nodejs//:repositories.bzl", "build_bazel_rules_nodejs_dependencies")
|
||||
@@ -144,14 +144,16 @@ cldr_xml_data_repository(
|
||||
# sass rules
|
||||
http_archive(
|
||||
name = "io_bazel_rules_sass",
|
||||
sha256 = "b83d695bc8deb5ab5fb3a8e6919999eebf738a4a5aa57a43a63ee70109f80224",
|
||||
strip_prefix = "rules_sass-1.50.0",
|
||||
sha256 = "856080c608e51993ab3088c08c70361d3f1fce383e14499cc2279060b350898c",
|
||||
strip_prefix = "rules_sass-1.51.0",
|
||||
urls = [
|
||||
"https://github.com/bazelbuild/rules_sass/archive/1.50.0.zip",
|
||||
"https://github.com/bazelbuild/rules_sass/archive/1.51.0.zip",
|
||||
],
|
||||
)
|
||||
|
||||
# Setup the rules_sass toolchain
|
||||
load("@io_bazel_rules_sass//sass:sass_repositories.bzl", "sass_repositories")
|
||||
|
||||
sass_repositories()
|
||||
sass_repositories(
|
||||
yarn_script = "//:.yarn/releases/yarn-1.22.17.cjs",
|
||||
)
|
||||
|
||||
@@ -48,6 +48,7 @@
|
||||
"@typescript-eslint/naming-convention": "off",
|
||||
"no-console": ["error", {"allow": ["log", "warn", "error"]}],
|
||||
"no-empty-function": "off",
|
||||
"@angular-eslint/no-host-metadata-property": "off",
|
||||
"no-restricted-syntax": [
|
||||
"error",
|
||||
{
|
||||
|
||||
+14
-17
@@ -1,19 +1,19 @@
|
||||
# See https://help.github.com/ignore-files/ for more about ignoring files.
|
||||
|
||||
# compiled output
|
||||
# Compiled output
|
||||
/dist
|
||||
/out-tsc
|
||||
/src/generated
|
||||
/tmp
|
||||
/out-tsc
|
||||
/bazel-out
|
||||
/src/generated
|
||||
|
||||
# Cache folder for the Angular CLI.
|
||||
.angular/cache
|
||||
|
||||
# dependencies
|
||||
# Node
|
||||
/node_modules
|
||||
npm-debug.log
|
||||
yarn-error.log
|
||||
|
||||
# IDEs and editors
|
||||
/.idea
|
||||
.idea/
|
||||
.project
|
||||
.classpath
|
||||
.c9/
|
||||
@@ -21,31 +21,28 @@
|
||||
.settings/
|
||||
*.sublime-workspace
|
||||
|
||||
# IDE - VSCode
|
||||
# Visual Studio Code
|
||||
.vscode/*
|
||||
!.vscode/settings.json
|
||||
!.vscode/tasks.json
|
||||
!.vscode/launch.json
|
||||
!.vscode/extensions.json
|
||||
.history/*
|
||||
|
||||
# misc
|
||||
# Miscellaneous
|
||||
/.angular/cache
|
||||
/.firebase/
|
||||
/.sass-cache
|
||||
.sass-cache/
|
||||
/connect.lock
|
||||
/coverage
|
||||
/libpeerconnection.log
|
||||
debug.log
|
||||
firebase-debug.log
|
||||
npm-debug.log
|
||||
testem.log
|
||||
/typings
|
||||
yarn-error.log
|
||||
|
||||
# e2e
|
||||
/e2e/*.js
|
||||
/e2e/*.map
|
||||
protractor-results*.txt
|
||||
|
||||
# System Files
|
||||
# System files
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
load("@aio_npm//@angular-devkit/architect-cli:index.bzl", "architect", "architect_test")
|
||||
load("@build_bazel_rules_nodejs//:index.bzl", "npm_package_bin")
|
||||
load("//tools:defaults.bzl", "npm_package_bin")
|
||||
|
||||
# The write_source_files macro is used to write bazel outputs to the source tree and test that they are up to date.
|
||||
# See: https://docs.aspect.build/aspect-build/bazel-lib/v0.5.0/docs/docs/write_source_files-docgen.html
|
||||
|
||||
@@ -4,28 +4,69 @@ import fetch, {RequestInit} from 'node-fetch';
|
||||
import {assertNotMissingOrEmpty} from './utils';
|
||||
|
||||
// Constants
|
||||
const CIRCLE_CI_API_URL = 'https://circleci.com/api/v2/project/gh';
|
||||
const CIRCLE_CI_BASE_API_URL = 'https://circleci.com/api/v2';
|
||||
const CIRCLE_CI_BUILD_API_URL = `${CIRCLE_CI_BASE_API_URL}/project/gh`;
|
||||
const CIRCLE_CI_PIPELINE_API_URL = `${CIRCLE_CI_BASE_API_URL}/pipeline`;
|
||||
|
||||
// Interfaces - Types
|
||||
|
||||
// API docs: https://circleci.com/docs/api/v2#operation/getJobArtifacts
|
||||
// Example: https://circleci.com/api/v2/project/gh/angular/angular/1163941/artifacts
|
||||
export interface ArtifactInfo {
|
||||
/** The path of this build artifacts. */
|
||||
path: string;
|
||||
pretty_path: string;
|
||||
node_index: number;
|
||||
|
||||
/** The full URL where this artifact can be downloaded from. */
|
||||
url: string;
|
||||
|
||||
// There are other fields but they are not used in this code.
|
||||
}
|
||||
|
||||
export type ArtifactResponse = ArtifactInfo[];
|
||||
export type ArtifactResponse = {items: ArtifactInfo[]};
|
||||
|
||||
// API docs: https://circleci.com/docs/api/v2#operation/getJobDetails
|
||||
// Example: https://circleci.com/api/v2/project/gh/angular/angular/job/1163941
|
||||
export interface BuildInfo {
|
||||
reponame: string;
|
||||
failed: boolean;
|
||||
branch: string;
|
||||
username: string;
|
||||
build_num: number;
|
||||
has_artifacts: boolean;
|
||||
outcome: string; // e.g. 'success'
|
||||
vcs_revision: string; // HEAD SHA
|
||||
// there are other fields but they are not used in this code
|
||||
/** The job number. */
|
||||
number: number;
|
||||
|
||||
/** The job name (e.g. `'aio_preview'`). */
|
||||
name: string;
|
||||
|
||||
/** Info about the organization which the project related to this job belongs to. */
|
||||
organization: {
|
||||
name: string;
|
||||
};
|
||||
|
||||
/** Info about the project related to this job. */
|
||||
project: {
|
||||
name: string;
|
||||
};
|
||||
|
||||
/** Info about the [pipeline](https://circleci.com/docs/2.0/pipelines/) that this job is part of. */
|
||||
pipeline: {
|
||||
id: string;
|
||||
};
|
||||
|
||||
// There are other fields but they are not used in this code.
|
||||
}
|
||||
|
||||
// API docs: https://circleci.com/docs/api/v2#operation/getPipelineById
|
||||
// https://circleci.com/api/v2/pipeline/356227c0-32f6-4f99-bfc2-3938db90a147
|
||||
export interface PipelineInfo {
|
||||
/** The pipeline ID. */
|
||||
id: string;
|
||||
|
||||
/** Info related to/retrieved from the version control system provider (e.g. GitHub). */
|
||||
vcs: {
|
||||
/** The PR number. */
|
||||
review_id: string;
|
||||
|
||||
/** The HEAD SHA. */
|
||||
revision: string;
|
||||
};
|
||||
|
||||
// There are other fields but they are not used in this code.
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -59,13 +100,13 @@ export class CircleCiApi {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the info for a build from the CircleCI API
|
||||
* Get the info for a build (aka job) from the CircleCI API.
|
||||
* @param buildNumber The CircleCI build number that generated the artifact.
|
||||
* @returns A promise to the info about the build
|
||||
* @returns A promise to the info about the build.
|
||||
*/
|
||||
public async getBuildInfo(buildNumber: number): Promise<BuildInfo> {
|
||||
try {
|
||||
const url = `${CIRCLE_CI_API_URL}/${this.githubOrg}/${this.githubRepo}/job/${buildNumber}`;
|
||||
const url = `${CIRCLE_CI_BUILD_API_URL}/${this.githubOrg}/${this.githubRepo}/job/${buildNumber}`;
|
||||
const response = await this.fetchFromCircleCi(url);
|
||||
return response.json();
|
||||
} catch (error) {
|
||||
@@ -79,10 +120,10 @@ export class CircleCiApi {
|
||||
* @returns A promise to the URL that can be requested to download the actual build artifact file.
|
||||
*/
|
||||
public async getBuildArtifactUrl(buildNumber: number, artifactPath: string): Promise<string> {
|
||||
const baseUrl = `${CIRCLE_CI_API_URL}/${this.githubOrg}/${this.githubRepo}/${buildNumber}`;
|
||||
const baseUrl = `${CIRCLE_CI_BUILD_API_URL}/${this.githubOrg}/${this.githubRepo}/${buildNumber}`;
|
||||
try {
|
||||
const response = await this.fetchFromCircleCi(`${baseUrl}/artifacts`);
|
||||
const artifacts = await response.json() as {items: ArtifactResponse};
|
||||
const artifacts = await response.json() as ArtifactResponse;
|
||||
const artifact = artifacts.items.find(item => item.path === artifactPath);
|
||||
if (!artifact) {
|
||||
throw new Error(`Missing artifact (${artifactPath}) for CircleCI build: ${buildNumber}`);
|
||||
@@ -92,4 +133,19 @@ export class CircleCiApi {
|
||||
throw new Error(`CircleCI artifact URL request failed (${(error as Error).message})`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the info for a [pipeline](https://circleci.com/docs/2.0/pipelines/) from the CircleCI API.
|
||||
* @param pipelineId The CircleCI pipeline ID that generated the artifact.
|
||||
* @returns A promise to the info about the pipeline.
|
||||
*/
|
||||
public async getPipelineInfo(pipelineId: string): Promise<PipelineInfo> {
|
||||
try {
|
||||
const url = `${CIRCLE_CI_PIPELINE_API_URL}/${pipelineId}`;
|
||||
const response = await this.fetchFromCircleCi(url);
|
||||
return response.json();
|
||||
} catch (error) {
|
||||
throw new Error(`CircleCI pipeline info request failed (${(error as Error).message})`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,7 +13,6 @@ export interface GithubInfo {
|
||||
pr: number;
|
||||
repo: string;
|
||||
sha: string;
|
||||
success: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -36,12 +35,12 @@ export class BuildRetriever {
|
||||
*/
|
||||
public async getGithubInfo(buildNum: number): Promise<GithubInfo> {
|
||||
const buildInfo = await this.api.getBuildInfo(buildNum);
|
||||
const pipelineInfo = await this.api.getPipelineInfo(buildInfo.pipeline.id);
|
||||
const githubInfo: GithubInfo = {
|
||||
org: buildInfo.username,
|
||||
pr: getPrFromBranch(buildInfo.branch),
|
||||
repo: buildInfo.reponame,
|
||||
sha: buildInfo.vcs_revision,
|
||||
success: !buildInfo.failed,
|
||||
org: buildInfo.organization.name,
|
||||
pr: +pipelineInfo.vcs.review_id,
|
||||
repo: buildInfo.project.name,
|
||||
sha: pipelineInfo.vcs.revision,
|
||||
};
|
||||
return githubInfo;
|
||||
}
|
||||
@@ -76,12 +75,3 @@ export class BuildRetriever {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getPrFromBranch(branch: string): number {
|
||||
// CircleCI only exposes PR numbers via the `branch` field :-(
|
||||
const match = /^pull\/(\d+)$/.exec(branch);
|
||||
if (!match) {
|
||||
throw new Error(`No PR found in branch field: ${branch}`);
|
||||
}
|
||||
return +match[1];
|
||||
}
|
||||
|
||||
+1
-7
@@ -120,13 +120,7 @@ export class PreviewServerFactory {
|
||||
return;
|
||||
}
|
||||
|
||||
const { pr, sha, org, repo, success } = await buildRetriever.getGithubInfo(buildNum);
|
||||
|
||||
if (!success) {
|
||||
res.sendStatus(204);
|
||||
logger.log(`PR:${pr}, Build:${buildNum} - Skipping preview processing because this build did not succeed.`);
|
||||
return;
|
||||
}
|
||||
const { pr, sha, org, repo } = await buildRetriever.getGithubInfo(buildNum);
|
||||
|
||||
assert(cfg.githubOrg === org,
|
||||
`Invalid webhook: expected "githubOrg" property to equal "${cfg.githubOrg}" but got "${org}".`);
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
export const enum BuildNums {
|
||||
BUILD_INFO_ERROR = 1,
|
||||
BUILD_INFO_404,
|
||||
BUILD_INFO_BUILD_FAILED,
|
||||
BUILD_INFO_INVALID_GH_ORG,
|
||||
BUILD_INFO_INVALID_GH_REPO,
|
||||
PIPELINE_INFO_ERROR,
|
||||
PIPELINE_INFO_404,
|
||||
CHANGED_FILES_ERROR,
|
||||
CHANGED_FILES_404,
|
||||
CHANGED_FILES_NONE,
|
||||
@@ -21,6 +22,17 @@ export const enum BuildNums {
|
||||
TRUST_CHECK_INACTIVE_TRUSTED_USER,
|
||||
}
|
||||
|
||||
export const enum PipelineIds {
|
||||
PIPELINE_INFO_ERROR = 'pipeline-error',
|
||||
PIPELINE_INFO_404 = 'pipeline-404',
|
||||
CHANGED_FILES_ERROR = 'pipeline-cfe',
|
||||
CHANGED_FILES_404 = 'pipeline-cf404',
|
||||
CHANGED_FILES_NONE = 'pipeline-cfn',
|
||||
TRUST_CHECK_ERROR = 'pipeline-tce',
|
||||
TRUST_CHECK_UNTRUSTED = 'pipeline-tcu',
|
||||
PIPELINE_INFO_OK = 'pipeline-ok',
|
||||
}
|
||||
|
||||
export const enum PrNums {
|
||||
CHANGED_FILES_ERROR = 1,
|
||||
CHANGED_FILES_404,
|
||||
|
||||
+58
-35
@@ -3,7 +3,7 @@ import * as nock from 'nock';
|
||||
import * as tar from 'tar-stream';
|
||||
import {gzipSync} from 'zlib';
|
||||
import {getEnvVar, Logger} from '../common/utils';
|
||||
import {BuildNums, PrNums, SHA} from './constants';
|
||||
import {BuildNums, PipelineIds, PrNums, SHA} from './constants';
|
||||
|
||||
// We are using the `nock` library to fake responses from REST requests, when testing.
|
||||
// This is necessary, because the test preview-server runs as a separate node process to
|
||||
@@ -30,11 +30,17 @@ const INACTIVE_TRUSTED_USER = 'inactive-trusted-user';
|
||||
const UNTRUSTED_USER = 'untrusted-user';
|
||||
|
||||
const BASIC_BUILD_INFO = {
|
||||
branch: `pull/${PrNums.TRUST_CHECK_ACTIVE_TRUSTED_USER}`,
|
||||
failed: false,
|
||||
reponame: AIO_GITHUB_REPO,
|
||||
username: AIO_GITHUB_ORGANIZATION,
|
||||
vcs_revision: SHA,
|
||||
name: 'test_job',
|
||||
organization: {name: AIO_GITHUB_ORGANIZATION},
|
||||
project: {name: AIO_GITHUB_REPO},
|
||||
pipeline: {id: PipelineIds.PIPELINE_INFO_OK},
|
||||
};
|
||||
const BASIC_PIPELINE_INFO = {
|
||||
id: PipelineIds.PIPELINE_INFO_OK,
|
||||
vcs: {
|
||||
review_id: `${PrNums.TRUST_CHECK_ACTIVE_TRUSTED_USER}`,
|
||||
revision: SHA,
|
||||
},
|
||||
};
|
||||
|
||||
const ISSUE_INFO_TRUSTED_LABEL = { labels: [{ name: AIO_TRUSTED_PR_LABEL }], user: { login: UNTRUSTED_USER } };
|
||||
@@ -47,7 +53,6 @@ const INACTIVE_STATE = { state: 'inactive' };
|
||||
const TEST_TEAM_INFO = AIO_GITHUB_TEAM_SLUGS.map((slug, index) => ({ slug, id: index }));
|
||||
|
||||
const CIRCLE_CI_API_HOST = 'https://circleci.com';
|
||||
const CIRCLE_CI_TOKEN_PARAM = `circle-token=${AIO_CIRCLE_CI_TOKEN}`;
|
||||
const ARTIFACT_1 = { path: 'artifact-1', url: `${CIRCLE_CI_API_HOST}/artifacts/artifact-1`, _urlPath: '/artifacts/artifact-1' };
|
||||
const ARTIFACT_2 = { path: 'artifact-2', url: `${CIRCLE_CI_API_HOST}/artifacts/artifact-2`, _urlPath: '/artifacts/artifact-2' };
|
||||
const ARTIFACT_3 = { path: 'artifact-3', url: `${CIRCLE_CI_API_HOST}/artifacts/artifact-3`, _urlPath: '/artifacts/artifact-3' };
|
||||
@@ -57,11 +62,13 @@ const ARTIFACT_VALID_TRUSTED_USER = { path: AIO_ARTIFACT_PATH, url: `${CIRCLE_CI
|
||||
const ARTIFACT_VALID_TRUSTED_LABEL = { path: AIO_ARTIFACT_PATH, url: `${CIRCLE_CI_API_HOST}/artifacts/valid/label`, _urlPath: '/artifacts/valid/label' };
|
||||
const ARTIFACT_VALID_UNTRUSTED = { path: AIO_ARTIFACT_PATH, url: `${CIRCLE_CI_API_HOST}/artifacts/valid/untrusted`, _urlPath: '/artifacts/valid/untrusted' };
|
||||
|
||||
const CIRCLE_CI_BUILD_INFO_URL = `/api/v1.1/project/github/${AIO_GITHUB_ORGANIZATION}/${AIO_GITHUB_REPO}`;
|
||||
const CIRCLE_CI_BUILD_INFO_URL = `/api/v2/project/gh/${AIO_GITHUB_ORGANIZATION}/${AIO_GITHUB_REPO}`;
|
||||
const CIRCLE_CI_PIPELINE_INFO_URL = '/api/v2/pipeline';
|
||||
|
||||
const buildInfoUrl = (buildNum: number) => `${CIRCLE_CI_BUILD_INFO_URL}/${buildNum}?${CIRCLE_CI_TOKEN_PARAM}`;
|
||||
const buildArtifactsUrl = (buildNum: number) => `${CIRCLE_CI_BUILD_INFO_URL}/${buildNum}/artifacts?${CIRCLE_CI_TOKEN_PARAM}`;
|
||||
const buildInfo = (prNum: number) => ({ ...BASIC_BUILD_INFO, branch: `pull/${prNum}` });
|
||||
const buildInfoUrl = (buildNum: number) => `${CIRCLE_CI_BUILD_INFO_URL}/job/${buildNum}`;
|
||||
const buildArtifactsUrl = (buildNum: number) => `${CIRCLE_CI_BUILD_INFO_URL}/${buildNum}/artifacts`;
|
||||
const pipelineInfoUrl = (pipelineId: string) => `${CIRCLE_CI_PIPELINE_INFO_URL}/${pipelineId}`;
|
||||
const buildInfo = (pipelineId: string) => ({ ...BASIC_BUILD_INFO, pipeline: { id: pipelineId } });
|
||||
|
||||
const GITHUB_API_HOST = 'https://api.github.com';
|
||||
const GITHUB_ISSUES_URL = `/repos/${AIO_GITHUB_ORGANIZATION}/${AIO_GITHUB_REPO}/issues`;
|
||||
@@ -73,6 +80,15 @@ const getFilesUrl = (prNum: number, pageNum = 1) => `${GITHUB_PULLS_URL}/${prNum
|
||||
const getCommentUrl = (prNum: number) => `${getIssueUrl(prNum)}/comments`;
|
||||
const getTeamMembershipUrl = (teamId: number, username: string) => `/teams/${teamId}/memberships/${username}`;
|
||||
|
||||
const setUpCircleCiApiForBuild =
|
||||
(buildNum: number, pipelineId: string, prNum = PrNums.TRUST_CHECK_ACTIVE_TRUSTED_USER, sha = SHA) => {
|
||||
circleCiApi.get(buildInfoUrl(buildNum)).reply(200, buildInfo(pipelineId));
|
||||
circleCiApi.get(pipelineInfoUrl(pipelineId)).reply(200, {
|
||||
id: pipelineId,
|
||||
vcs: { review_id: `${prNum}`, revision: sha },
|
||||
});
|
||||
};
|
||||
|
||||
const createArchive = (buildNum: number, prNum: number, sha: string) => {
|
||||
logger.log('createArchive', buildNum, prNum, sha);
|
||||
const pack = tar.pack();
|
||||
@@ -84,7 +100,7 @@ const createArchive = (buildNum: number, prNum: number, sha: string) => {
|
||||
};
|
||||
|
||||
// Create request scopes
|
||||
const circleCiApi = nock(CIRCLE_CI_API_HOST).persist();
|
||||
const circleCiApi = nock(CIRCLE_CI_API_HOST).persist().matchHeader('Circle-Token', AIO_CIRCLE_CI_TOKEN);
|
||||
const githubApi = nock(GITHUB_API_HOST).persist().matchHeader('Authorization', `token ${AIO_GITHUB_TOKEN}`);
|
||||
|
||||
//////////////////////////////
|
||||
@@ -92,50 +108,57 @@ const githubApi = nock(GITHUB_API_HOST).persist().matchHeader('Authorization', `
|
||||
// GENERAL responses
|
||||
githubApi.get(GITHUB_TEAMS_URL + '?page=1&per_page=100').reply(200, TEST_TEAM_INFO);
|
||||
githubApi.post(getCommentUrl(PrNums.TRUST_CHECK_ACTIVE_TRUSTED_USER)).reply(200);
|
||||
circleCiApi.get(pipelineInfoUrl(PipelineIds.PIPELINE_INFO_OK)).reply(200, BASIC_PIPELINE_INFO);
|
||||
|
||||
// BUILD_INFO errors
|
||||
// BUILD INFO errors
|
||||
circleCiApi.get(buildInfoUrl(BuildNums.BUILD_INFO_ERROR)).replyWithError('BUILD_INFO_ERROR');
|
||||
circleCiApi.get(buildInfoUrl(BuildNums.BUILD_INFO_404)).reply(404, 'BUILD_INFO_404');
|
||||
circleCiApi.get(buildInfoUrl(BuildNums.BUILD_INFO_BUILD_FAILED)).reply(200, { ...BASIC_BUILD_INFO, failed: true });
|
||||
circleCiApi.get(buildInfoUrl(BuildNums.BUILD_INFO_INVALID_GH_ORG)).reply(200, { ...BASIC_BUILD_INFO, username: 'bad' });
|
||||
circleCiApi.get(buildInfoUrl(BuildNums.BUILD_INFO_INVALID_GH_REPO)).reply(200, { ...BASIC_BUILD_INFO, reponame: 'bad' });
|
||||
circleCiApi.get(buildInfoUrl(BuildNums.BUILD_INFO_INVALID_GH_ORG)).reply(200, { ...BASIC_BUILD_INFO, organization: { name: 'bad' } });
|
||||
circleCiApi.get(buildInfoUrl(BuildNums.BUILD_INFO_INVALID_GH_REPO)).reply(200, { ...BASIC_BUILD_INFO, project: { name: 'bad' } });
|
||||
|
||||
// PIPELINE INFO errors
|
||||
circleCiApi.get(buildInfoUrl(BuildNums.PIPELINE_INFO_ERROR)).reply(200, { ...BASIC_BUILD_INFO, pipeline: { id: PipelineIds.PIPELINE_INFO_ERROR } });
|
||||
circleCiApi.get(pipelineInfoUrl(PipelineIds.PIPELINE_INFO_ERROR)).replyWithError('PIPELINE_INFO_ERROR');
|
||||
|
||||
circleCiApi.get(buildInfoUrl(BuildNums.PIPELINE_INFO_404)).reply(200, { ...BASIC_BUILD_INFO, pipeline: { id: PipelineIds.PIPELINE_INFO_404 } });
|
||||
circleCiApi.get(pipelineInfoUrl(PipelineIds.PIPELINE_INFO_ERROR)).reply(404, 'PIPELINE_INFO_404');
|
||||
|
||||
// CHANGED FILE errors
|
||||
circleCiApi.get(buildInfoUrl(BuildNums.CHANGED_FILES_ERROR)).reply(200, buildInfo(PrNums.CHANGED_FILES_ERROR));
|
||||
setUpCircleCiApiForBuild(BuildNums.CHANGED_FILES_ERROR, PipelineIds.CHANGED_FILES_ERROR, PrNums.CHANGED_FILES_ERROR);
|
||||
githubApi.get(getFilesUrl(PrNums.CHANGED_FILES_ERROR)).replyWithError('CHANGED_FILES_ERROR');
|
||||
circleCiApi.get(buildInfoUrl(BuildNums.CHANGED_FILES_404)).reply(200, buildInfo(PrNums.CHANGED_FILES_404));
|
||||
setUpCircleCiApiForBuild(BuildNums.CHANGED_FILES_404, PipelineIds.CHANGED_FILES_404, PrNums.CHANGED_FILES_404);
|
||||
githubApi.get(getFilesUrl(PrNums.CHANGED_FILES_404)).reply(404, 'CHANGED_FILES_404');
|
||||
circleCiApi.get(buildInfoUrl(BuildNums.CHANGED_FILES_NONE)).reply(200, buildInfo(PrNums.CHANGED_FILES_NONE));
|
||||
setUpCircleCiApiForBuild(BuildNums.CHANGED_FILES_NONE, PipelineIds.CHANGED_FILES_NONE, PrNums.CHANGED_FILES_NONE);
|
||||
githubApi.get(getFilesUrl(PrNums.CHANGED_FILES_NONE)).reply(200, []);
|
||||
|
||||
// ARTIFACT URL errors
|
||||
circleCiApi.get(buildInfoUrl(BuildNums.BUILD_ARTIFACTS_ERROR)).reply(200, buildInfo(PrNums.TRUST_CHECK_ACTIVE_TRUSTED_USER));
|
||||
circleCiApi.get(buildInfoUrl(BuildNums.BUILD_ARTIFACTS_ERROR)).reply(200, BASIC_BUILD_INFO);
|
||||
circleCiApi.get(buildArtifactsUrl(BuildNums.BUILD_ARTIFACTS_ERROR)).replyWithError('BUILD_ARTIFACTS_ERROR');
|
||||
circleCiApi.get(buildInfoUrl(BuildNums.BUILD_ARTIFACTS_404)).reply(200, buildInfo(PrNums.TRUST_CHECK_ACTIVE_TRUSTED_USER));
|
||||
circleCiApi.get(buildInfoUrl(BuildNums.BUILD_ARTIFACTS_404)).reply(200, BASIC_BUILD_INFO);
|
||||
circleCiApi.get(buildArtifactsUrl(BuildNums.BUILD_ARTIFACTS_404)).reply(404, 'BUILD_ARTIFACTS_ERROR');
|
||||
circleCiApi.get(buildInfoUrl(BuildNums.BUILD_ARTIFACTS_EMPTY)).reply(200, buildInfo(PrNums.TRUST_CHECK_ACTIVE_TRUSTED_USER));
|
||||
circleCiApi.get(buildArtifactsUrl(BuildNums.BUILD_ARTIFACTS_EMPTY)).reply(200, []);
|
||||
circleCiApi.get(buildInfoUrl(BuildNums.BUILD_ARTIFACTS_MISSING)).reply(200, buildInfo(PrNums.TRUST_CHECK_ACTIVE_TRUSTED_USER));
|
||||
circleCiApi.get(buildArtifactsUrl(BuildNums.BUILD_ARTIFACTS_MISSING)).reply(200, [ARTIFACT_1, ARTIFACT_2, ARTIFACT_3]);
|
||||
circleCiApi.get(buildInfoUrl(BuildNums.BUILD_ARTIFACTS_EMPTY)).reply(200, BASIC_BUILD_INFO);
|
||||
circleCiApi.get(buildArtifactsUrl(BuildNums.BUILD_ARTIFACTS_EMPTY)).reply(200, { items: [] });
|
||||
circleCiApi.get(buildInfoUrl(BuildNums.BUILD_ARTIFACTS_MISSING)).reply(200, BASIC_BUILD_INFO);
|
||||
circleCiApi.get(buildArtifactsUrl(BuildNums.BUILD_ARTIFACTS_MISSING)).reply(200, { items: [ARTIFACT_1, ARTIFACT_2, ARTIFACT_3] });
|
||||
|
||||
// ARTIFACT DOWNLOAD errors
|
||||
circleCiApi.get(buildInfoUrl(BuildNums.DOWNLOAD_ARTIFACT_ERROR)).reply(200, buildInfo(PrNums.TRUST_CHECK_ACTIVE_TRUSTED_USER));
|
||||
circleCiApi.get(buildArtifactsUrl(BuildNums.DOWNLOAD_ARTIFACT_ERROR)).reply(200, [ARTIFACT_ERROR]);
|
||||
circleCiApi.get(buildInfoUrl(BuildNums.DOWNLOAD_ARTIFACT_ERROR)).reply(200, BASIC_BUILD_INFO);
|
||||
circleCiApi.get(buildArtifactsUrl(BuildNums.DOWNLOAD_ARTIFACT_ERROR)).reply(200, { items: [ARTIFACT_ERROR] });
|
||||
circleCiApi.get(ARTIFACT_ERROR._urlPath).replyWithError(ARTIFACT_ERROR._urlPath);
|
||||
circleCiApi.get(buildInfoUrl(BuildNums.DOWNLOAD_ARTIFACT_404)).reply(200, buildInfo(PrNums.TRUST_CHECK_ACTIVE_TRUSTED_USER));
|
||||
circleCiApi.get(buildInfoUrl(BuildNums.DOWNLOAD_ARTIFACT_404)).reply(200, BASIC_BUILD_INFO);
|
||||
circleCiApi.get(buildArtifactsUrl(BuildNums.DOWNLOAD_ARTIFACT_404)).reply(200, [ARTIFACT_404]);
|
||||
circleCiApi.get(ARTIFACT_ERROR._urlPath).reply(404, ARTIFACT_ERROR._urlPath);
|
||||
|
||||
// TRUST CHECK errors
|
||||
circleCiApi.get(buildInfoUrl(BuildNums.TRUST_CHECK_ERROR)).reply(200, buildInfo(PrNums.TRUST_CHECK_ERROR));
|
||||
setUpCircleCiApiForBuild(BuildNums.TRUST_CHECK_ERROR, PipelineIds.TRUST_CHECK_ERROR, PrNums.TRUST_CHECK_ERROR);
|
||||
githubApi.get(getFilesUrl(PrNums.TRUST_CHECK_ERROR)).reply(200, [{ filename: 'aio/a' }]);
|
||||
circleCiApi.get(buildArtifactsUrl(BuildNums.TRUST_CHECK_ERROR)).reply(200, [ARTIFACT_VALID_TRUSTED_USER]);
|
||||
circleCiApi.get(buildArtifactsUrl(BuildNums.TRUST_CHECK_ERROR)).reply(200, { items: [ARTIFACT_VALID_TRUSTED_USER] });
|
||||
githubApi.get(getIssueUrl(PrNums.TRUST_CHECK_ERROR)).replyWithError('TRUST_CHECK_ERROR');
|
||||
|
||||
// ACTIVE TRUSTED USER response
|
||||
circleCiApi.get(buildInfoUrl(BuildNums.TRUST_CHECK_ACTIVE_TRUSTED_USER)).reply(200, BASIC_BUILD_INFO);
|
||||
githubApi.get(getFilesUrl(PrNums.TRUST_CHECK_ACTIVE_TRUSTED_USER)).reply(200, [{ filename: 'aio/a' }]);
|
||||
circleCiApi.get(buildArtifactsUrl(BuildNums.TRUST_CHECK_ACTIVE_TRUSTED_USER)).reply(200, [ARTIFACT_VALID_TRUSTED_USER]);
|
||||
circleCiApi.get(buildArtifactsUrl(BuildNums.TRUST_CHECK_ACTIVE_TRUSTED_USER)).reply(200, { items: [ARTIFACT_VALID_TRUSTED_USER] });
|
||||
circleCiApi.get(ARTIFACT_VALID_TRUSTED_USER._urlPath).reply(200, createArchive(BuildNums.TRUST_CHECK_ACTIVE_TRUSTED_USER, PrNums.TRUST_CHECK_ACTIVE_TRUSTED_USER, SHA));
|
||||
githubApi.get(getIssueUrl(PrNums.TRUST_CHECK_ACTIVE_TRUSTED_USER)).reply(200, ISSUE_INFO_ACTIVE_TRUSTED_USER);
|
||||
githubApi.get(getTeamMembershipUrl(0, ACTIVE_TRUSTED_USER)).reply(200, ACTIVE_STATE);
|
||||
@@ -143,7 +166,7 @@ githubApi.get(getTeamMembershipUrl(0, ACTIVE_TRUSTED_USER)).reply(200, ACTIVE_ST
|
||||
// TRUSTED LABEL response
|
||||
circleCiApi.get(buildInfoUrl(BuildNums.TRUST_CHECK_TRUSTED_LABEL)).reply(200, BASIC_BUILD_INFO);
|
||||
githubApi.get(getFilesUrl(PrNums.TRUST_CHECK_TRUSTED_LABEL)).reply(200, [{ filename: 'aio/a' }]);
|
||||
circleCiApi.get(buildArtifactsUrl(BuildNums.TRUST_CHECK_TRUSTED_LABEL)).reply(200, [ARTIFACT_VALID_TRUSTED_LABEL]);
|
||||
circleCiApi.get(buildArtifactsUrl(BuildNums.TRUST_CHECK_TRUSTED_LABEL)).reply(200, { items: [ARTIFACT_VALID_TRUSTED_LABEL] });
|
||||
circleCiApi.get(ARTIFACT_VALID_TRUSTED_LABEL._urlPath).reply(200, createArchive(BuildNums.TRUST_CHECK_TRUSTED_LABEL, PrNums.TRUST_CHECK_TRUSTED_LABEL, SHA));
|
||||
githubApi.get(getIssueUrl(PrNums.TRUST_CHECK_TRUSTED_LABEL)).reply(200, ISSUE_INFO_TRUSTED_LABEL);
|
||||
githubApi.get(getTeamMembershipUrl(0, ACTIVE_TRUSTED_USER)).reply(200, ACTIVE_STATE);
|
||||
@@ -151,14 +174,14 @@ githubApi.get(getTeamMembershipUrl(0, ACTIVE_TRUSTED_USER)).reply(200, ACTIVE_ST
|
||||
// INACTIVE TRUSTED USER response
|
||||
circleCiApi.get(buildInfoUrl(BuildNums.TRUST_CHECK_INACTIVE_TRUSTED_USER)).reply(200, BASIC_BUILD_INFO);
|
||||
githubApi.get(getFilesUrl(PrNums.TRUST_CHECK_INACTIVE_TRUSTED_USER)).reply(200, [{ filename: 'aio/a' }]);
|
||||
circleCiApi.get(buildArtifactsUrl(BuildNums.TRUST_CHECK_INACTIVE_TRUSTED_USER)).reply(200, [ARTIFACT_VALID_TRUSTED_USER]);
|
||||
circleCiApi.get(buildArtifactsUrl(BuildNums.TRUST_CHECK_INACTIVE_TRUSTED_USER)).reply(200, { items: [ARTIFACT_VALID_TRUSTED_USER] });
|
||||
githubApi.get(getIssueUrl(PrNums.TRUST_CHECK_INACTIVE_TRUSTED_USER)).reply(200, ISSUE_INFO_INACTIVE_TRUSTED_USER);
|
||||
githubApi.get(getTeamMembershipUrl(0, INACTIVE_TRUSTED_USER)).reply(200, INACTIVE_STATE);
|
||||
|
||||
// UNTRUSTED reponse
|
||||
circleCiApi.get(buildInfoUrl(BuildNums.TRUST_CHECK_UNTRUSTED)).reply(200, buildInfo(PrNums.TRUST_CHECK_UNTRUSTED));
|
||||
setUpCircleCiApiForBuild(BuildNums.TRUST_CHECK_UNTRUSTED, PipelineIds.TRUST_CHECK_UNTRUSTED, PrNums.TRUST_CHECK_UNTRUSTED);
|
||||
githubApi.get(getFilesUrl(PrNums.TRUST_CHECK_UNTRUSTED)).reply(200, [{ filename: 'aio/a' }]);
|
||||
circleCiApi.get(buildArtifactsUrl(BuildNums.TRUST_CHECK_UNTRUSTED)).reply(200, [ARTIFACT_VALID_UNTRUSTED]);
|
||||
circleCiApi.get(buildArtifactsUrl(BuildNums.TRUST_CHECK_UNTRUSTED)).reply(200, { items: [ARTIFACT_VALID_UNTRUSTED] });
|
||||
circleCiApi.get(ARTIFACT_VALID_UNTRUSTED._urlPath).reply(200, createArchive(BuildNums.TRUST_CHECK_UNTRUSTED, PrNums.TRUST_CHECK_UNTRUSTED, SHA));
|
||||
githubApi.get(getIssueUrl(PrNums.TRUST_CHECK_UNTRUSTED)).reply(200, ISSUE_INFO_UNTRUSTED);
|
||||
githubApi.get(getTeamMembershipUrl(0, UNTRUSTED_USER)).reply(404);
|
||||
|
||||
@@ -145,13 +145,11 @@ describe('preview-server', () => {
|
||||
]);
|
||||
});
|
||||
|
||||
it('should respond with 500 if the CircleCI API request errors', async () => {
|
||||
it('should respond with 500 if any of the CircleCI API requests errors', async () => {
|
||||
await curl(payload(BuildNums.BUILD_INFO_ERROR)).then(h.verifyResponse(500));
|
||||
await curl(payload(BuildNums.BUILD_INFO_404)).then(h.verifyResponse(500));
|
||||
});
|
||||
|
||||
it('should respond with 204 if the build on CircleCI failed', async () => {
|
||||
await curl(payload(BuildNums.BUILD_INFO_BUILD_FAILED)).then(h.verifyResponse(204));
|
||||
await curl(payload(BuildNums.PIPELINE_INFO_ERROR)).then(h.verifyResponse(500));
|
||||
await curl(payload(BuildNums.PIPELINE_INFO_404)).then(h.verifyResponse(500));
|
||||
});
|
||||
|
||||
it('should respond with 500 if the github org from CircleCI does not match what is configured', async () => {
|
||||
|
||||
@@ -49,6 +49,6 @@
|
||||
"supertest": "^4.0.2",
|
||||
"tslint": "^6.1.3",
|
||||
"tslint-jasmine-noSkipOrFocus": "^1.0.9",
|
||||
"typescript": "~4.6.2"
|
||||
"typescript": "~4.7.2"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,8 @@ import {CircleCiApi} from '../../lib/common/circle-ci-api';
|
||||
const ORG = 'testorg';
|
||||
const REPO = 'testrepo';
|
||||
const TOKEN = 'xxxx';
|
||||
const BASE_URL = `https://circleci.com/api/v2/project/gh/${ORG}/${REPO}`;
|
||||
const BASE_BUILD_URL = `https://circleci.com/api/v2/project/gh/${ORG}/${REPO}`;
|
||||
const BASE_PIPELINE_URL = 'https://circleci.com/api/v2/pipeline';
|
||||
|
||||
describe('CircleCIApi', () => {
|
||||
describe('constructor()', () => {
|
||||
@@ -27,11 +28,11 @@ describe('CircleCIApi', () => {
|
||||
describe('fetchFromCircleCI', () => {
|
||||
it('should include the authentication token in the headers on every request', async () => {
|
||||
const api = new CircleCiApi(ORG, REPO, TOKEN);
|
||||
const request = nock(BASE_URL)
|
||||
const request = nock(BASE_BUILD_URL)
|
||||
.get('/')
|
||||
.matchHeader('Circle-Token', TOKEN)
|
||||
.reply(200);
|
||||
await api.fetchFromCircleCi(`${BASE_URL}/`);
|
||||
await api.fetchFromCircleCi(`${BASE_BUILD_URL}/`);
|
||||
request.done();
|
||||
})
|
||||
})
|
||||
@@ -42,7 +43,7 @@ describe('CircleCIApi', () => {
|
||||
const buildNum = 12345;
|
||||
const expectedBuildInfo: any = { org: ORG, repo: REPO, build_num: buildNum };
|
||||
|
||||
const request = nock(BASE_URL)
|
||||
const request = nock(BASE_BUILD_URL)
|
||||
.get(`/job/${buildNum}`)
|
||||
.reply(200, expectedBuildInfo);
|
||||
|
||||
@@ -55,17 +56,17 @@ describe('CircleCIApi', () => {
|
||||
const api = new CircleCiApi(ORG, REPO, TOKEN);
|
||||
const buildNum = 12345;
|
||||
const errorMessage = 'Invalid request';
|
||||
const request = nock(BASE_URL).get(`/job/${buildNum}`);
|
||||
const request = nock(BASE_BUILD_URL).get(`/job/${buildNum}`);
|
||||
|
||||
request.replyWithError(errorMessage);
|
||||
await expectAsync(api.getBuildInfo(buildNum)).toBeRejectedWithError(
|
||||
`CircleCI build info request failed ` +
|
||||
`(request to ${BASE_URL}/job/${buildNum} failed, reason: ${errorMessage})`);
|
||||
`(request to ${BASE_BUILD_URL}/job/${buildNum} failed, reason: ${errorMessage})`);
|
||||
|
||||
request.reply(404, errorMessage);
|
||||
await expectAsync(api.getBuildInfo(buildNum)).toBeRejectedWithError(
|
||||
`CircleCI build info request failed ` +
|
||||
`(request to ${BASE_URL}/job/${buildNum} failed, reason: ${errorMessage})`);
|
||||
`(request to ${BASE_BUILD_URL}/job/${buildNum} failed, reason: ${errorMessage})`);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -76,7 +77,7 @@ describe('CircleCIApi', () => {
|
||||
const artifact0: any = { path: 'some/path/0', url: 'https://url/0' };
|
||||
const artifact1: any = { path: 'some/path/1', url: 'https://url/1' };
|
||||
const artifact2: any = { path: 'some/path/2', url: 'https://url/2' };
|
||||
const request = nock(BASE_URL)
|
||||
const request = nock(BASE_BUILD_URL)
|
||||
.get(`/${buildNum}/artifacts`)
|
||||
.reply(200, {items: [artifact0, artifact1, artifact2]});
|
||||
|
||||
@@ -89,17 +90,17 @@ describe('CircleCIApi', () => {
|
||||
const api = new CircleCiApi(ORG, REPO, TOKEN);
|
||||
const buildNum = 12345;
|
||||
const errorMessage = 'Invalid request';
|
||||
const request = nock(BASE_URL).get(`/${buildNum}/artifacts`);
|
||||
const request = nock(BASE_BUILD_URL).get(`/${buildNum}/artifacts`);
|
||||
|
||||
request.replyWithError(errorMessage);
|
||||
await expectAsync(api.getBuildArtifactUrl(buildNum, 'some/path/1')).toBeRejectedWithError(
|
||||
`CircleCI artifact URL request failed ` +
|
||||
`(request to ${BASE_URL}/${buildNum}/artifacts failed, reason: ${errorMessage})`);
|
||||
`(request to ${BASE_BUILD_URL}/${buildNum}/artifacts failed, reason: ${errorMessage})`);
|
||||
|
||||
request.reply(404, errorMessage);
|
||||
await expectAsync(api.getBuildArtifactUrl(buildNum, 'some/path/1')).toBeRejectedWithError(
|
||||
`CircleCI artifact URL request failed ` +
|
||||
`(request to ${BASE_URL}/${buildNum}/artifacts failed, reason: ${errorMessage})`);
|
||||
`(request to ${BASE_BUILD_URL}/${buildNum}/artifacts failed, reason: ${errorMessage})`);
|
||||
});
|
||||
|
||||
it('should throw an error if the response does not contain the specified artifact', async () => {
|
||||
@@ -108,7 +109,7 @@ describe('CircleCIApi', () => {
|
||||
const artifact0: any = { path: 'some/path/0', url: 'https://url/0' };
|
||||
const artifact1: any = { path: 'some/path/1', url: 'https://url/1' };
|
||||
const artifact2: any = { path: 'some/path/2', url: 'https://url/2' };
|
||||
nock(BASE_URL)
|
||||
nock(BASE_BUILD_URL)
|
||||
.get(`/${buildNum}/artifacts`)
|
||||
.reply(200, {items: [artifact0, artifact1, artifact2]});
|
||||
|
||||
@@ -117,4 +118,37 @@ describe('CircleCIApi', () => {
|
||||
`(Missing artifact (some/path/3) for CircleCI build: ${buildNum})`);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getPipelineInfo', () => {
|
||||
it('should make a request to the CircleCI API for the given pipeline ID', async () => {
|
||||
const api = new CircleCiApi(ORG, REPO, TOKEN);
|
||||
const pipelineId = 'a1-b2-c3';
|
||||
const expectedPipelineInfo: any = { org: ORG, repo: REPO, pipeline_id: pipelineId };
|
||||
|
||||
const request = nock(BASE_PIPELINE_URL)
|
||||
.get(`/${pipelineId}`)
|
||||
.reply(200, expectedPipelineInfo);
|
||||
|
||||
const pipelineInfo = await api.getPipelineInfo(pipelineId);
|
||||
expect(pipelineInfo).toEqual(expectedPipelineInfo);
|
||||
request.done();
|
||||
});
|
||||
|
||||
it('should throw an error if the request fails', async () => {
|
||||
const api = new CircleCiApi(ORG, REPO, TOKEN);
|
||||
const pipelineId = 'a1-b2-c3';
|
||||
const errorMessage = 'Invalid request';
|
||||
const request = nock(BASE_PIPELINE_URL).get(`/${pipelineId}`);
|
||||
|
||||
request.replyWithError(errorMessage);
|
||||
await expectAsync(api.getPipelineInfo(pipelineId)).toBeRejectedWithError(
|
||||
`CircleCI pipeline info request failed ` +
|
||||
`(request to ${BASE_PIPELINE_URL}/${pipelineId} failed, reason: ${errorMessage})`);
|
||||
|
||||
request.reply(404, errorMessage);
|
||||
await expectAsync(api.getPipelineInfo(pipelineId)).toBeRejectedWithError(
|
||||
`CircleCI pipeline info request failed ` +
|
||||
`(request to ${BASE_PIPELINE_URL}/${pipelineId} failed, reason: ${errorMessage})`);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
+18
-18
@@ -1,7 +1,7 @@
|
||||
import * as fs from 'fs';
|
||||
import * as nock from 'nock';
|
||||
import {resolve as resolvePath} from 'path';
|
||||
import {BuildInfo, CircleCiApi} from '../../lib/common/circle-ci-api';
|
||||
import {BuildInfo, CircleCiApi, PipelineInfo} from '../../lib/common/circle-ci-api';
|
||||
import {Logger} from '../../lib/common/utils';
|
||||
import {BuildRetriever} from '../../lib/preview-server/build-retriever';
|
||||
|
||||
@@ -13,6 +13,7 @@ describe('BuildRetriever', () => {
|
||||
|
||||
let api: CircleCiApi;
|
||||
let BUILD_INFO: BuildInfo;
|
||||
let PIPELINE_INFO: PipelineInfo;
|
||||
let WRITEFILE_RESULT: any;
|
||||
let writeFileSpy: jasmine.Spy;
|
||||
let EXISTS_RESULT: boolean;
|
||||
@@ -21,18 +22,23 @@ describe('BuildRetriever', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
BUILD_INFO = {
|
||||
branch: 'pull/777',
|
||||
build_num: 12345,
|
||||
failed: false,
|
||||
has_artifacts: true,
|
||||
outcome: 'success',
|
||||
reponame: 'REPO',
|
||||
username: 'ORG',
|
||||
vcs_revision: 'COMMIT',
|
||||
number: 12345,
|
||||
name: 'test_job',
|
||||
organization: {name: 'ORG'},
|
||||
project: {name: 'REPO'},
|
||||
pipeline: {id: 'test_pipeline'},
|
||||
};
|
||||
PIPELINE_INFO = {
|
||||
id: 'test_pipeline',
|
||||
vcs: {
|
||||
review_id: '777',
|
||||
revision: 'COMMIT',
|
||||
},
|
||||
};
|
||||
|
||||
api = new CircleCiApi('ORG', 'REPO', 'TOKEN');
|
||||
spyOn(api, 'getBuildInfo').and.resolveTo(BUILD_INFO);
|
||||
spyOn(api, 'getPipelineInfo').and.resolveTo(PIPELINE_INFO);
|
||||
getBuildArtifactUrlSpy = spyOn(api, 'getBuildArtifactUrl').and.resolveTo(BASE_URL + ARTIFACT_PATH);
|
||||
|
||||
WRITEFILE_RESULT = undefined;
|
||||
@@ -65,18 +71,12 @@ describe('BuildRetriever', () => {
|
||||
|
||||
|
||||
describe('getGithubInfo', () => {
|
||||
it('should request the info from CircleCI', async () => {
|
||||
it('should request the build and pipeline info from CircleCI', async () => {
|
||||
const retriever = new BuildRetriever(api, MAX_DOWNLOAD_SIZE, DOWNLOAD_DIR);
|
||||
const info = await retriever.getGithubInfo(12345);
|
||||
expect(api.getBuildInfo).toHaveBeenCalledWith(12345);
|
||||
expect(info).toEqual({org: 'ORG', pr: 777, repo: 'REPO', sha: 'COMMIT', success: true});
|
||||
});
|
||||
|
||||
it('should error if it is not possible to extract the PR number from the branch', async () => {
|
||||
BUILD_INFO.branch = 'master';
|
||||
const retriever = new BuildRetriever(api, MAX_DOWNLOAD_SIZE, DOWNLOAD_DIR);
|
||||
|
||||
await expectAsync(retriever.getGithubInfo(12345)).toBeRejectedWithError('No PR found in branch field: master');
|
||||
expect(api.getPipelineInfo).toHaveBeenCalledWith('test_pipeline');
|
||||
expect(info).toEqual({org: 'ORG', pr: 777, repo: 'REPO', sha: 'COMMIT'});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
-10
@@ -410,7 +410,6 @@ describe('PreviewServerFactory', () => {
|
||||
pr: PR,
|
||||
repo: defaultConfig.githubRepo,
|
||||
sha: SHA,
|
||||
success: true,
|
||||
};
|
||||
BASIC_PAYLOAD = { payload: { build_num: BUILD_NUM, build_parameters: { CIRCLE_JOB: 'aio_preview' } } };
|
||||
AFFECTS_SIGNIFICANT_FILES = true;
|
||||
@@ -481,15 +480,6 @@ describe('PreviewServerFactory', () => {
|
||||
await agent.post(URL).send(BASIC_PAYLOAD).expect(202);
|
||||
});
|
||||
|
||||
it('should not create a preview if the build was not successful', async () => {
|
||||
BUILD_INFO.success = false;
|
||||
await agent.post(URL).send(BASIC_PAYLOAD).expect(204);
|
||||
expect(getGithubInfoSpy).toHaveBeenCalledWith(BUILD_NUM);
|
||||
expect(downloadBuildArtifactSpy).not.toHaveBeenCalled();
|
||||
expect(getPrIsTrustedSpy).not.toHaveBeenCalled();
|
||||
expect(createBuildSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should fail if the CircleCI request fails', async () => {
|
||||
// Note it is important to put the `reject` into `and.callFake`;
|
||||
// If you just `and.returnValue` the rejected promise
|
||||
|
||||
@@ -2563,10 +2563,10 @@ typedarray-to-buffer@^3.1.5:
|
||||
dependencies:
|
||||
is-typedarray "^1.0.0"
|
||||
|
||||
typescript@~4.6.2:
|
||||
version "4.6.2"
|
||||
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.6.2.tgz#fe12d2727b708f4eef40f51598b3398baa9611d4"
|
||||
integrity sha512-HM/hFigTBHZhLXshn9sN37H085+hQGeJHJ/X7LpBWLID/fbc2acUMfU+lGD98X81sKP+pFa9f0DZmCwB9GnbAg==
|
||||
typescript@~4.7.2:
|
||||
version "4.7.2"
|
||||
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.7.2.tgz#1f9aa2ceb9af87cca227813b4310fff0b51593c4"
|
||||
integrity sha512-Mamb1iX2FDUpcTRzltPxgWMKy3fhg0TN378ylbktPGPK/99KbDtMQ4W1hwgsbPAsG3a0xKa1vmw4VKZQbkvz5A==
|
||||
|
||||
undefsafe@^2.0.2:
|
||||
version "2.0.2"
|
||||
|
||||
@@ -6,7 +6,7 @@ Assuming you have cloned the repository containing the preview server code (as d
|
||||
[here](vm-setup--create-docker-image.md)), you can use the `update-preview-server.sh` script on the
|
||||
VM host to update the preview server based on changes in the source code.
|
||||
|
||||
The script will pull the latest changes from the origin's master branch and examine if there have
|
||||
The script will pull the latest changes from the origin's main branch and examine if there have
|
||||
been any changes in files inside the preview server source code directory (see below). If there are,
|
||||
it will create a new image and verify that it works as expected. Finally, it will stop and remove
|
||||
the old docker container and image, create a new container based on the new image and start it.
|
||||
|
||||
@@ -25,8 +25,8 @@ readonly CONTAINER_NAME=aio
|
||||
readonly lastDeployedCommit=$(git rev-parse HEAD)
|
||||
echo "Currently at commit $lastDeployedCommit."
|
||||
|
||||
# Pull latest master from origin.
|
||||
git pull origin master
|
||||
# Pull latest main from origin.
|
||||
git pull origin main
|
||||
|
||||
# Do not update the server unless files inside `aio-builds-setup/` have changed
|
||||
# or the last attempt failed (identified by the provisional image still being around).
|
||||
@@ -67,5 +67,9 @@ readonly CONTAINER_NAME=aio
|
||||
--volume $HOST_LOGS_DIR:/var/log/aio \
|
||||
"$LATEST_IMAGE_NAME"
|
||||
|
||||
# Clean up unused docker containers and images (to reclaim space).
|
||||
sudo docker container prune --force
|
||||
sudo docker image prune --all --force
|
||||
|
||||
echo "The new docker image has been successfully deployed."
|
||||
)
|
||||
|
||||
@@ -10,7 +10,7 @@ Install the CLI using the `npm` package manager:
|
||||
|
||||
<code-example format="shell" language="shell">
|
||||
|
||||
npm install -g @angular/cli
|
||||
npm install -g @angular/cli<aio-angular-dist-tag class="pln"></aio-angular-dist-tag>
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
@name `inject()` must be called from an injection context
|
||||
@category runtime
|
||||
@shortDescription `inject()` must be called from an injection context (a constructor, a factory function or a field initializer)
|
||||
|
||||
@description
|
||||
You see this error when you try to use the `inject()` function outside of the allowed injection context. The injection context is available during the class creation and initialization.
|
||||
|
||||
In practice the `inject()` calls are allowed in a constructor, a constructor parameter and a field initializer:
|
||||
|
||||
```typescript
|
||||
@Injectable({providedIn: 'root'})
|
||||
export class Car {
|
||||
radio: Radio|undefined;
|
||||
|
||||
// OK: field initializer
|
||||
spareTyre = inject(Tyre);
|
||||
|
||||
constructor() {
|
||||
// OK: constructor body
|
||||
this.radio = inject(Radio);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
It is also legal to call `inject` from a provider's factory:
|
||||
|
||||
```typescript
|
||||
providers: [
|
||||
{provide: Car, useFactory: () => {
|
||||
// OK: a class factory
|
||||
const engine = inject(Engine);
|
||||
return new Car(engine);
|
||||
}}
|
||||
]
|
||||
```
|
||||
|
||||
Calls to the `inject()` function outside of the class creation context will result in error. Most notably, calls to `inject()` are disallowed after a class instance was created, in methods (including lifecycle hooks):
|
||||
|
||||
```typescript
|
||||
@Component({ ... })
|
||||
export class CarComponent {
|
||||
ngOnInit() {
|
||||
// ERROR: too late, the component instance was already created
|
||||
const engine = inject(Engine);
|
||||
engine.start();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
@debugging
|
||||
|
||||
Work backwards from the stack trace of the error to identify a place where the disallowed call to `inject()` is located.
|
||||
|
||||
To fix the error move the `inject()` call to an allowed place (usually a class constructor or a field initializer).
|
||||
|
||||
<!-- links -->
|
||||
|
||||
<!-- external links -->
|
||||
|
||||
<!-- end links -->
|
||||
|
||||
@reviewed 2022-05-27
|
||||
@@ -7,7 +7,7 @@ One or more elements cannot be resolved during compilation because the element i
|
||||
|
||||
<div class="alert is-helpful">
|
||||
|
||||
This is the compiler equivalent of a common runtime error `NG0304: '${tagName}' is not a known element: …`.
|
||||
This is the compiler equivalent of a common runtime error `NG0304: '${tagName}' is not a known element: ...`.
|
||||
|
||||
</div>
|
||||
|
||||
@@ -15,7 +15,8 @@ This is the compiler equivalent of a common runtime error `NG0304: '${tagName}'
|
||||
Use the element name in the error to find the file(s) where the element is being used.
|
||||
|
||||
Check that the name and selector are correct.
|
||||
If the component is from a different module or import, check that the component is exported from its origin module and imported into the correct `*.modules.ts` file, and declared in the imports list.
|
||||
|
||||
Make sure that the component is correctly imported inside your NgModule or standalone component, by checking its presence in the `imports` field. If the component is declared in an NgModule (meaning that it is not standalone) make sure that it is exported correctly from it, by checking its presence in the `exports` field.
|
||||
|
||||
When using custom elements or web components, ensure that you add [`CUSTOM_ELEMENTS_SCHEMA`](api/core/CUSTOM_ELEMENTS_SCHEMA) to the application module.
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
# boilerplate files
|
||||
**/.vscode/
|
||||
**/src/environments/environment.prod.ts
|
||||
**/src/environments/environment.ts
|
||||
**/src/assets/.gitkeep
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
"experimentalDecorators": true,
|
||||
"moduleResolution": "node",
|
||||
"importHelpers": true,
|
||||
"target": "es2017",
|
||||
"target": "es2020",
|
||||
"module": "es2020",
|
||||
"lib": [
|
||||
"es2020",
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// #docplaster …
|
||||
// #docplaster ...
|
||||
// #docregion webpack-config
|
||||
import linkerPlugin from '@angular/compiler-cli/linker/babel';
|
||||
|
||||
|
||||
@@ -2,14 +2,13 @@
|
||||
import { Hero } from './hero';
|
||||
|
||||
export const HEROES: Hero[] = [
|
||||
{ id: 11, name: 'Dr Nice' },
|
||||
{ id: 12, name: 'Narco' },
|
||||
{ id: 12, name: 'Dr. Nice' },
|
||||
{ id: 13, name: 'Bombasto' },
|
||||
{ id: 14, name: 'Celeritas' },
|
||||
{ id: 15, name: 'Magneta' },
|
||||
{ id: 16, name: 'RubberMan' },
|
||||
{ id: 17, name: 'Dynama' },
|
||||
{ id: 18, name: 'Dr IQ' },
|
||||
{ id: 18, name: 'Dr. IQ' },
|
||||
{ id: 19, name: 'Magma' },
|
||||
{ id: 20, name: 'Tornado' }
|
||||
];
|
||||
|
||||
@@ -69,7 +69,4 @@
|
||||
<!-- #enddocregion style-delegation -->
|
||||
</div>
|
||||
|
||||
<!-- #docregion attribute-decorator -->
|
||||
<app-my-input-with-attribute-decorator type="number"></app-my-input-with-attribute-decorator>
|
||||
<!-- #enddocregion attribute-decorator -->
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ import { Component } from '@angular/core';
|
||||
export class VoteTakerComponent {
|
||||
agreed = 0;
|
||||
disagreed = 0;
|
||||
voters = ['Narco', 'Celeritas', 'Bombasto'];
|
||||
voters = ['Dr IQ', 'Celeritas', 'Bombasto'];
|
||||
|
||||
onVoted(agreed: boolean) {
|
||||
if (agreed) {
|
||||
|
||||
@@ -52,7 +52,7 @@ describe('Dependency Injection Tests', () => {
|
||||
});
|
||||
|
||||
it('Hero displays as expected', async () => {
|
||||
expectedMsg = 'Dr Nice';
|
||||
expectedMsg = 'Dr. Nice';
|
||||
expect(await element(by.css('#hero')).getText()).toEqual(expectedMsg);
|
||||
});
|
||||
|
||||
|
||||
@@ -2,8 +2,7 @@
|
||||
import { Hero } from './hero';
|
||||
|
||||
export const HEROES: Hero[] = [
|
||||
{ id: 11, isSecret: false, name: 'Dr Nice' },
|
||||
{ id: 12, isSecret: false, name: 'Narco' },
|
||||
{ id: 12, isSecret: false, name: 'Dr. Nice' },
|
||||
{ id: 13, isSecret: false, name: 'Bombasto' },
|
||||
{ id: 14, isSecret: false, name: 'Celeritas' },
|
||||
{ id: 15, isSecret: false, name: 'Magneta' },
|
||||
|
||||
@@ -4,8 +4,8 @@ export interface Hero {
|
||||
}
|
||||
|
||||
export const HEROES: Hero[] = [
|
||||
{ id: 11, name: 'Dr Nice' },
|
||||
{ id: 12, name: 'Narco' },
|
||||
{ id: 12, name: 'Dr. Nice' },
|
||||
{ id: 13, name: 'Bombasto' },
|
||||
{ id: 14, name: 'Celeritas' }
|
||||
{ id: 14, name: 'Celeritas' },
|
||||
{ id: 15, name: 'Magneta' }
|
||||
];
|
||||
|
||||
@@ -28,10 +28,7 @@
|
||||
<div class="group">
|
||||
<h3>Binding to a nested component</h3>
|
||||
<h4>Custom events with EventEmitter</h4>
|
||||
<!-- #docregion event-binding-to-component -->
|
||||
<app-item-detail (deleteRequest)="deleteItem($event)" [item]="currentItem"></app-item-detail>
|
||||
<!-- #enddocregion event-binding-to-component -->
|
||||
|
||||
|
||||
<h4>Click to see event target class:</h4>
|
||||
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
<div class="detail">
|
||||
<p>This is the ItemDetailComponent</p>
|
||||
<!-- #docregion line-through -->
|
||||
<img src="{{itemImageUrl}}" alt="{{item.name}}" [style.display]="displayNone">
|
||||
<span [style.text-decoration]="lineThrough">{{ item.name }}
|
||||
</span>
|
||||
<button type="button" (click)="delete()">Delete</button>
|
||||
<!-- #enddocregion line-through -->
|
||||
</div>
|
||||
|
||||
@@ -15,7 +15,6 @@ export class ItemDetailComponent {
|
||||
displayNone = '';
|
||||
@Input() prefix = '';
|
||||
|
||||
// #docregion deleteRequest
|
||||
// This component makes a request but it can't actually delete a hero.
|
||||
@Output() deleteRequest = new EventEmitter<Item>();
|
||||
|
||||
@@ -24,6 +23,5 @@ export class ItemDetailComponent {
|
||||
this.displayNone = this.displayNone ? '' : 'none';
|
||||
this.lineThrough = this.lineThrough ? '' : 'line-through';
|
||||
}
|
||||
// #enddocregion deleteRequest
|
||||
|
||||
}
|
||||
|
||||
@@ -54,7 +54,7 @@ describe('Http Tests', () => {
|
||||
describe('Heroes', () => {
|
||||
it('retrieves the list of heroes at startup', async () => {
|
||||
expect(await page.heroesListItems.count()).toBe(4);
|
||||
expect(await page.heroesListItems.get(0).getText()).toContain('Dr Nice');
|
||||
expect(await page.heroesListItems.get(0).getText()).toContain('Dr. Nice');
|
||||
await checkLogForMessage('GET "api/heroes"');
|
||||
});
|
||||
|
||||
|
||||
@@ -3,10 +3,10 @@ import { InMemoryDbService } from 'angular-in-memory-web-api';
|
||||
export class InMemoryDataService implements InMemoryDbService {
|
||||
createDb() {
|
||||
const heroes = [
|
||||
{ id: 11, name: 'Dr Nice' },
|
||||
{ id: 12, name: 'Narco' },
|
||||
{ id: 12, name: 'Dr. Nice' },
|
||||
{ id: 13, name: 'Bombasto' },
|
||||
{ id: 14, name: 'Celeritas' },
|
||||
{ id: 15, name: 'Magneta' },
|
||||
];
|
||||
const query = [
|
||||
{ name: '@angular/core', version: '20.1.0', description: 'angular core package' },
|
||||
|
||||
@@ -62,7 +62,7 @@ export class UploaderService {
|
||||
|
||||
case HttpEventType.UploadProgress:
|
||||
// Compute and show the % done:
|
||||
const percentDone = Math.round(100 * event.loaded / (event.total ?? 0));
|
||||
const percentDone = event.total ? Math.round(100 * event.loaded / event.total) : 0;
|
||||
return `File "${file.name}" is ${percentDone}% uploaded.`;
|
||||
|
||||
case HttpEventType.Response:
|
||||
|
||||
@@ -16,16 +16,12 @@
|
||||
|
||||
<h3>Evaluating template expressions </h3>
|
||||
<h4>Simple evaluation (to a string):</h4>
|
||||
<!-- #docregion convert-string -->
|
||||
<!-- "The sum of 1 + 1 is 2" -->
|
||||
<p>The sum of 1 + 1 is {{1 + 1}}.</p>
|
||||
<!-- #enddocregion convert-string -->
|
||||
|
||||
<h4>Evaluates using a method (also evaluates to a string):</h4>
|
||||
<!-- #docregion invoke-method -->
|
||||
<!-- "The sum of 1 + 1 is not 4" -->
|
||||
<p>The sum of 1 + 1 is not {{1 + 1 + getVal()}}.</p>
|
||||
<!-- #enddocregion invoke-method -->
|
||||
</div>
|
||||
|
||||
<hr />
|
||||
|
||||
@@ -6,8 +6,8 @@ export interface Hero {
|
||||
}
|
||||
|
||||
export const heroes: Hero[] = [
|
||||
{ id: 1, name: 'Dr Nice', emotion: 'happy' },
|
||||
{ id: 2, name: 'Narco', emotion: 'sad' },
|
||||
{ id: 1, name: 'Dr. Nice', emotion: 'happy' },
|
||||
{ id: 2, name: 'RubberMan', emotion: 'sad' },
|
||||
{ id: 3, name: 'Windstorm', emotion: 'confused' },
|
||||
{ id: 4, name: 'Magneta' }
|
||||
];
|
||||
|
||||
@@ -86,12 +86,14 @@ describe('typeahead', () => {
|
||||
static install(mockTime = 0): MockClock['tick'] {
|
||||
const mocked = new this(mockTime);
|
||||
|
||||
spyOn(global, 'clearInterval').and.callFake(id => mocked.clearTask(id));
|
||||
spyOn(global, 'clearTimeout').and.callFake(id => mocked.clearTask(id));
|
||||
spyOn(global, 'clearInterval').and.callFake(id => mocked.clearTask(id as MockTask['id']));
|
||||
spyOn(global, 'clearTimeout').and.callFake(id => mocked.clearTask(id as MockTask['id']));
|
||||
spyOn(global, 'setInterval').and.callFake(
|
||||
(fn, delay, ...args) => mocked.createTask(fn, delay, true, ...args));
|
||||
((fn: () => unknown, delay: number, ...args: any[]) =>
|
||||
mocked.createTask(fn, delay, true, ...args)) as typeof setInterval);
|
||||
spyOn(global, 'setTimeout').and.callFake(
|
||||
(fn, delay, ...args) => mocked.createTask(fn, delay, false, ...args));
|
||||
((fn: () => unknown, delay: number, ...args: any[]) =>
|
||||
mocked.createTask(fn, delay, false, ...args)) as typeof setTimeout);
|
||||
|
||||
spyOn(Date, 'now').and.callFake(() => mocked.now);
|
||||
|
||||
|
||||
@@ -46,27 +46,21 @@
|
||||
<hr />
|
||||
|
||||
<h2>Property binding and interpolation</h2>
|
||||
<!-- #docregion property-binding-interpolation -->
|
||||
<p><img alt="Interpolated item" src="{{itemImageUrl}}"> is the <i>interpolated</i> image.</p>
|
||||
<p><img alt="Property Bound item" [src]="itemImageUrl"> is the <i>property bound</i> image.</p>
|
||||
|
||||
<p><span>"{{interpolationTitle}}" is the <i>interpolated</i> title.</span></p>
|
||||
<p>"<span [innerHTML]="propertyTitle"></span>" is the <i>property bound</i> title.</p>
|
||||
<!-- #enddocregion property-binding-interpolation -->
|
||||
|
||||
<hr />
|
||||
|
||||
<h2>Malicious content</h2>
|
||||
|
||||
<!-- #docregion malicious-interpolated -->
|
||||
<p><span>"{{evilTitle}}" is the <i>interpolated</i> evil title.</span></p>
|
||||
<!-- #enddocregion malicious-interpolated -->
|
||||
|
||||
<!-- #docregion malicious-content -->
|
||||
<!--
|
||||
Angular generates a warning for the following line as it sanitizes them
|
||||
WARNING: sanitizing HTML stripped some content (see https://g.co/ng/security#xss).
|
||||
-->
|
||||
<p>"<span [innerHTML]="evilTitle"></span>" is the <i>property bound</i> evil title.</p>
|
||||
<!-- #enddocregion malicious-content -->
|
||||
</div>
|
||||
|
||||
@@ -13,9 +13,7 @@ export class AppComponent {
|
||||
// #docregion boolean
|
||||
isUnchanged = true;
|
||||
// #enddocregion boolean
|
||||
// #docregion directive-property
|
||||
classes = 'special';
|
||||
// #enddocregion directive-property
|
||||
// #docregion parent-data-type
|
||||
parentItem = 'lamp';
|
||||
// #enddocregion parent-data-type
|
||||
@@ -30,7 +28,5 @@ export class AppComponent {
|
||||
interpolationTitle = 'Interpolation';
|
||||
propertyTitle = 'Property binding';
|
||||
|
||||
// #docregion malicious-content
|
||||
evilTitle = 'Template <script>alert("evil never sleeps")</script> Syntax';
|
||||
// #enddocregion malicious-content
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ import { browser, element, by, ExpectedConditions as EC } from 'protractor';
|
||||
|
||||
const numDashboardTabs = 5;
|
||||
const numCrises = 4;
|
||||
const numHeroes = 10;
|
||||
const numHeroes = 9;
|
||||
|
||||
describe('Router', () => {
|
||||
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import { Hero } from './hero';
|
||||
|
||||
export const HEROES: Hero[] = [
|
||||
{ id: 11, name: 'Dr Nice' },
|
||||
{ id: 12, name: 'Narco' },
|
||||
{ id: 12, name: 'Dr. Nice' },
|
||||
{ id: 13, name: 'Bombasto' },
|
||||
{ id: 14, name: 'Celeritas' },
|
||||
{ id: 15, name: 'Magneta' },
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
import { browser, element, by } from 'protractor';
|
||||
|
||||
describe('Set Document Title', () => {
|
||||
|
||||
beforeEach(() => browser.get(''));
|
||||
|
||||
it('should set the document title', async () => {
|
||||
const elems = await element.all(by.css('ul li a'));
|
||||
const titles = [
|
||||
'Good morning!',
|
||||
'Good afternoon!',
|
||||
'Good evening!',
|
||||
];
|
||||
|
||||
for (let i = 0; i < elems.length; i++) {
|
||||
await elems[i].click();
|
||||
expect(await browser.getTitle()).toEqual(titles[i]);
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
@@ -1,29 +0,0 @@
|
||||
// #docplaster
|
||||
// #docregion
|
||||
// Import the native Angular services.
|
||||
import { Component } from '@angular/core';
|
||||
import { Title } from '@angular/platform-browser';
|
||||
|
||||
@Component({
|
||||
selector: 'app-root',
|
||||
template: `
|
||||
<p>
|
||||
Select a title to set on the current HTML document:
|
||||
</p>
|
||||
|
||||
<ul>
|
||||
<li><button type="button" (click)="setTitle('Good morning!')">Good morning</button></li>
|
||||
<li><button type="button" (click)="setTitle('Good afternoon!')">Good afternoon</button></li>
|
||||
<li><button type="button" (click)="setTitle('Good evening!')">Good evening</button></li>
|
||||
</ul>
|
||||
`,
|
||||
})
|
||||
// #docregion class
|
||||
export class AppComponent {
|
||||
public constructor(private titleService: Title) { }
|
||||
|
||||
public setTitle(newTitle: string) {
|
||||
this.titleService.setTitle(newTitle);
|
||||
}
|
||||
}
|
||||
// #enddocregion class
|
||||
@@ -1,19 +0,0 @@
|
||||
// #docregion
|
||||
import { NgModule } from '@angular/core';
|
||||
import { BrowserModule, Title } from '@angular/platform-browser';
|
||||
|
||||
import { AppComponent } from './app.component';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
BrowserModule
|
||||
],
|
||||
declarations: [
|
||||
AppComponent
|
||||
],
|
||||
providers: [
|
||||
Title
|
||||
],
|
||||
bootstrap: [ AppComponent ]
|
||||
})
|
||||
export class AppModule { }
|
||||
@@ -1,22 +0,0 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<base href="/">
|
||||
<link rel="icon" type="image/x-icon" href="favicon.ico">
|
||||
|
||||
<title>
|
||||
Setting The Document Title Using The Title Service
|
||||
</title>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<h1>
|
||||
Setting The Document Title Using The Title Service
|
||||
</h1>
|
||||
|
||||
<app-root></app-root>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,12 +0,0 @@
|
||||
// #docregion
|
||||
import { enableProdMode } from '@angular/core';
|
||||
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
|
||||
|
||||
import { AppModule } from './app/app.module';
|
||||
import { environment } from './environments/environment';
|
||||
|
||||
if (environment.production) {
|
||||
enableProdMode();
|
||||
}
|
||||
|
||||
platformBrowserDynamic().bootstrapModule(AppModule);
|
||||
@@ -1,10 +0,0 @@
|
||||
{
|
||||
"description": "Set The Document Title In Angular",
|
||||
"files": [
|
||||
"!**/*.d.ts",
|
||||
"!**/*.js",
|
||||
"!**/*.[1].*"
|
||||
],
|
||||
"file": "src/app/app.component.ts",
|
||||
"tags": [ "cookbook" ]
|
||||
}
|
||||
@@ -6,12 +6,12 @@ describe('Structural Directives', () => {
|
||||
|
||||
it('first div should show hero name with *ngIf', async () => {
|
||||
const allDivs = element.all(by.tagName('div'));
|
||||
expect(await allDivs.get(0).getText()).toEqual('Dr Nice');
|
||||
expect(await allDivs.get(0).getText()).toEqual('Dr. Nice');
|
||||
});
|
||||
|
||||
it('first li should show hero name with *ngFor', async () => {
|
||||
const allLis = element.all(by.tagName('li'));
|
||||
expect(await allLis.get(0).getText()).toEqual('Dr Nice');
|
||||
expect(await allLis.get(0).getText()).toEqual('Dr. Nice');
|
||||
});
|
||||
|
||||
it('ngSwitch have two <happy-hero> instances', async () => {
|
||||
@@ -27,11 +27,6 @@ describe('Structural Directives', () => {
|
||||
expect(await paragraph.get(0).getText()).not.toContain('I waved');
|
||||
});
|
||||
|
||||
it('should have only one "Hip!" (the other is erased)', async () => {
|
||||
const paragraph = element.all(by.cssContainingText('p', 'Hip!'));
|
||||
expect(await paragraph.count()).toEqual(1);
|
||||
});
|
||||
|
||||
it('appUnless should show 3 paragraph (A)s and (B)s at the start', async () => {
|
||||
const paragraph = element.all(by.css('p.unless'));
|
||||
expect(await paragraph.count()).toEqual(3);
|
||||
|
||||
@@ -110,15 +110,20 @@
|
||||
|
||||
<p class="code"><div *ngFor="let hero of heroes; let i=index; let odd=odd; trackBy: trackById" [class.odd]="odd"></p>
|
||||
<!--#docregion inside-ngfor -->
|
||||
<div *ngFor="let hero of heroes; let i=index; let odd=odd; trackBy: trackById" [class.odd]="odd">
|
||||
<div
|
||||
*ngFor="let hero of heroes; let i=index; let odd=odd; trackBy: trackById"
|
||||
[class.odd]="odd">
|
||||
({{i}}) {{hero.name}}
|
||||
</div>
|
||||
|
||||
<!--#enddocregion inside-ngfor -->
|
||||
<p class="code"><ng-template ngFor let-hero [ngForOf]="heroes" let-i="index" let-odd="odd" [ngForTrackBy]="trackById"/></p>
|
||||
<!--#docregion inside-ngfor -->
|
||||
<ng-template ngFor let-hero [ngForOf]="heroes" let-i="index" let-odd="odd" [ngForTrackBy]="trackById">
|
||||
<div [class.odd]="odd">({{i}}) {{hero.name}}</div>
|
||||
<ng-template ngFor let-hero [ngForOf]="heroes"
|
||||
let-i="index" let-odd="odd" [ngForTrackBy]="trackById">
|
||||
<div [class.odd]="odd">
|
||||
({{i}}) {{hero.name}}
|
||||
</div>
|
||||
</ng-template>
|
||||
<!--#enddocregion inside-ngfor -->
|
||||
|
||||
@@ -162,16 +167,6 @@
|
||||
|
||||
<hr>
|
||||
|
||||
<h2><ng-template></h2>
|
||||
<!-- #docregion template-tag -->
|
||||
<p>Hip!</p>
|
||||
<ng-template>
|
||||
<p>Hip!</p>
|
||||
</ng-template>
|
||||
<p>Hooray!</p>
|
||||
<!-- #enddocregion template-tag -->
|
||||
|
||||
<hr>
|
||||
|
||||
<h2 id="appUnless">UnlessDirective</h2>
|
||||
<!-- #docregion toggle-info -->
|
||||
|
||||
@@ -6,8 +6,8 @@ export interface Hero {
|
||||
}
|
||||
|
||||
export const heroes: Hero[] = [
|
||||
{ id: 1, name: 'Dr Nice', emotion: 'happy'},
|
||||
{ id: 2, name: 'Narco', emotion: 'sad' },
|
||||
{ id: 1, name: 'Dr. Nice', emotion: 'happy'},
|
||||
{ id: 2, name: 'RubberMan', emotion: 'sad' },
|
||||
{ id: 3, name: 'Windstorm', emotion: 'confused' },
|
||||
{ id: 4, name: 'Magneta'}
|
||||
];
|
||||
|
||||
@@ -97,7 +97,5 @@ value of the input will have no effect. -->
|
||||
|
||||
See the console output to see that when you declare the variable on an <code>ng-template</code>, the variable refers to a <code>TemplateRef</code> instance, which represents the template.
|
||||
|
||||
<!-- #docregion template-ref -->
|
||||
<ng-template #ref3></ng-template>
|
||||
<button type="button" (click)="log(ref3)">Log type of #ref</button>
|
||||
<!-- #enddocregion template-ref -->
|
||||
|
||||
@@ -11,8 +11,8 @@ export class Hero {
|
||||
325
|
||||
),
|
||||
new Hero(1, 'Dr Nice', 'happy'),
|
||||
new Hero(2, 'Narco', 'sad' ),
|
||||
new Hero(3, 'Windstorm', 'confused' ),
|
||||
new Hero(2, 'RubberMan', 'sad'),
|
||||
new Hero(3, 'Windstorm', 'confused'),
|
||||
new Hero(4, 'Magneta')
|
||||
];
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// For more examples:
|
||||
// https://github.com/angular/angular/blob/master/modules/@angular/router/test/integration.spec.ts
|
||||
// https://github.com/angular/angular/blob/main/packages/router/test/integration.spec.ts
|
||||
|
||||
import { waitForAsync, ComponentFixture, fakeAsync, TestBed, tick } from '@angular/core/testing';
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ describe('HeroDetailComponent - no TestBed', () => {
|
||||
let router: jasmine.SpyObj<Router>;
|
||||
|
||||
beforeEach((done: DoneFn) => {
|
||||
expectedHero = {id: 42, name: 'Bubba' };
|
||||
expectedHero = { id: 42, name: 'Bubba' };
|
||||
const activatedRoute = new ActivatedRouteStub({ id: expectedHero.id });
|
||||
router = jasmine.createSpyObj('Router', ['navigate']);
|
||||
|
||||
@@ -27,7 +27,7 @@ describe('HeroDetailComponent - no TestBed', () => {
|
||||
comp.ngOnInit();
|
||||
|
||||
// OnInit calls HDS.getHero; wait for it to get the fake hero
|
||||
hds.getHero.calls.first().returnValue.subscribe(done);
|
||||
hds.getHero.calls.first().returnValue.subscribe(() => done());
|
||||
});
|
||||
|
||||
it('should expose the hero retrieved from the service', () => {
|
||||
|
||||
@@ -8,14 +8,13 @@ const maxQuotes = Infinity; // 0;
|
||||
export class InMemoryDataService implements InMemoryDbService {
|
||||
createDb() {
|
||||
const heroes = [
|
||||
{ id: 11, name: 'Dr Nice' },
|
||||
{ id: 12, name: 'Narco' },
|
||||
{ id: 12, name: 'Dr. Nice' },
|
||||
{ id: 13, name: 'Bombasto' },
|
||||
{ id: 14, name: 'Celeritas' },
|
||||
{ id: 15, name: 'Magneta' },
|
||||
{ id: 16, name: 'RubberMan' },
|
||||
{ id: 17, name: 'Dynama' },
|
||||
{ id: 18, name: 'Dr IQ' },
|
||||
{ id: 18, name: 'Dr. IQ' },
|
||||
{ id: 19, name: 'Magma' },
|
||||
{ id: 20, name: 'Tornado' }
|
||||
];
|
||||
|
||||
@@ -54,7 +54,7 @@ function initialPageTests() {
|
||||
|
||||
it('has the right number of heroes', async () => {
|
||||
const page = getPageElts();
|
||||
expect(await page.heroes.count()).toEqual(10);
|
||||
expect(await page.heroes.count()).toEqual(9);
|
||||
});
|
||||
|
||||
it('has no selected hero and no hero details', async () => {
|
||||
|
||||
@@ -2,7 +2,10 @@
|
||||
<h2>My Heroes</h2>
|
||||
<ul class="heroes">
|
||||
<li>
|
||||
<span class="badge">{{hero.id}}</span> {{hero.name}}
|
||||
<button type="button">
|
||||
<span class="badge">{{hero.id}}</span>
|
||||
<span class="name">{{hero.name}}</span>
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
<!-- #enddocregion list -->
|
||||
|
||||
@@ -11,17 +11,13 @@
|
||||
<!-- #enddocregion li -->
|
||||
</ul>
|
||||
|
||||
<!-- #docregion ng-if -->
|
||||
<!-- #docregion selectedHero-details -->
|
||||
<div *ngIf="selectedHero">
|
||||
|
||||
<!-- #docregion selectedHero-details -->
|
||||
<h2>{{selectedHero.name | uppercase}} Details</h2>
|
||||
<div>id: {{selectedHero.id}}</div>
|
||||
<div>
|
||||
<label for="hero-name">Hero name: </label>
|
||||
<input id="hero-name" [(ngModel)]="selectedHero.name" placeholder="name">
|
||||
</div>
|
||||
<!-- #enddocregion selectedHero-details -->
|
||||
|
||||
</div>
|
||||
<!-- #enddocregion ng-if -->
|
||||
<!-- #enddocregion selectedHero-details -->
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
import { Hero } from './hero';
|
||||
|
||||
export const HEROES: Hero[] = [
|
||||
{ id: 11, name: 'Dr Nice' },
|
||||
{ id: 12, name: 'Narco' },
|
||||
{ id: 12, name: 'Dr. Nice' },
|
||||
{ id: 13, name: 'Bombasto' },
|
||||
{ id: 14, name: 'Celeritas' },
|
||||
{ id: 15, name: 'Magneta' },
|
||||
{ id: 16, name: 'RubberMan' },
|
||||
{ id: 17, name: 'Dynama' },
|
||||
{ id: 18, name: 'Dr IQ' },
|
||||
{ id: 18, name: 'Dr. IQ' },
|
||||
{ id: 19, name: 'Magma' },
|
||||
{ id: 20, name: 'Tornado' }
|
||||
];
|
||||
|
||||
@@ -54,7 +54,7 @@ function initialPageTests() {
|
||||
|
||||
it('has the right number of heroes', async () => {
|
||||
const page = getPageElts();
|
||||
expect(await page.heroes.count()).toEqual(10);
|
||||
expect(await page.heroes.count()).toEqual(9);
|
||||
});
|
||||
|
||||
it('has no selected hero and no hero details', async () => {
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
import { Hero } from './hero';
|
||||
|
||||
export const HEROES: Hero[] = [
|
||||
{ id: 11, name: 'Dr Nice' },
|
||||
{ id: 12, name: 'Narco' },
|
||||
{ id: 12, name: 'Dr. Nice' },
|
||||
{ id: 13, name: 'Bombasto' },
|
||||
{ id: 14, name: 'Celeritas' },
|
||||
{ id: 15, name: 'Magneta' },
|
||||
{ id: 16, name: 'RubberMan' },
|
||||
{ id: 17, name: 'Dynama' },
|
||||
{ id: 18, name: 'Dr IQ' },
|
||||
{ id: 18, name: 'Dr. IQ' },
|
||||
{ id: 19, name: 'Magma' },
|
||||
{ id: 20, name: 'Tornado' }
|
||||
];
|
||||
|
||||
@@ -54,7 +54,7 @@ function initialPageTests() {
|
||||
|
||||
it('has the right number of heroes', async () => {
|
||||
const page = getPageElts();
|
||||
expect(await page.heroes.count()).toEqual(10);
|
||||
expect(await page.heroes.count()).toEqual(9);
|
||||
});
|
||||
|
||||
it('has no selected hero and no hero details', async () => {
|
||||
|
||||
@@ -2,14 +2,13 @@
|
||||
import { Hero } from './hero';
|
||||
|
||||
export const HEROES: Hero[] = [
|
||||
{ id: 11, name: 'Dr Nice' },
|
||||
{ id: 12, name: 'Narco' },
|
||||
{ id: 12, name: 'Dr. Nice' },
|
||||
{ id: 13, name: 'Bombasto' },
|
||||
{ id: 14, name: 'Celeritas' },
|
||||
{ id: 15, name: 'Magneta' },
|
||||
{ id: 16, name: 'RubberMan' },
|
||||
{ id: 17, name: 'Dynama' },
|
||||
{ id: 18, name: 'Dr IQ' },
|
||||
{ id: 18, name: 'Dr. IQ' },
|
||||
{ id: 19, name: 'Magma' },
|
||||
{ id: 20, name: 'Tornado' }
|
||||
];
|
||||
|
||||
@@ -2,7 +2,7 @@ import { browser, element, by, ElementFinder } from 'protractor';
|
||||
|
||||
const expectedH1 = 'Tour of Heroes';
|
||||
const expectedTitle = `${expectedH1}`;
|
||||
const targetHero = { id: 15, name: 'Magneta' };
|
||||
const targetHero = { id: 16, name: 'RubberMan' };
|
||||
const targetHeroDashboardIndex = 3;
|
||||
const nameSuffix = 'X';
|
||||
const newHeroName = targetHero.name + nameSuffix;
|
||||
@@ -106,7 +106,7 @@ describe('Tutorial part 5', () => {
|
||||
await getPageElts().appHeroesHref.click();
|
||||
const page = getPageElts();
|
||||
expect(await page.appHeroes.isPresent()).toBeTruthy();
|
||||
expect(await page.allHeroes.count()).toEqual(10, 'number of heroes');
|
||||
expect(await page.allHeroes.count()).toEqual(9, 'number of heroes');
|
||||
});
|
||||
|
||||
it('can route to hero details', async () => {
|
||||
|
||||
@@ -2,14 +2,13 @@
|
||||
import { Hero } from './hero';
|
||||
|
||||
export const HEROES: Hero[] = [
|
||||
{ id: 11, name: 'Dr Nice' },
|
||||
{ id: 12, name: 'Narco' },
|
||||
{ id: 12, name: 'Dr. Nice' },
|
||||
{ id: 13, name: 'Bombasto' },
|
||||
{ id: 14, name: 'Celeritas' },
|
||||
{ id: 15, name: 'Magneta' },
|
||||
{ id: 16, name: 'RubberMan' },
|
||||
{ id: 17, name: 'Dynama' },
|
||||
{ id: 18, name: 'Dr IQ' },
|
||||
{ id: 18, name: 'Dr. IQ' },
|
||||
{ id: 19, name: 'Magma' },
|
||||
{ id: 20, name: 'Tornado' }
|
||||
];
|
||||
|
||||
@@ -3,7 +3,7 @@ import { browser, element, by, ElementFinder, ElementArrayFinder } from 'protrac
|
||||
const expectedH1 = 'Tour of Heroes';
|
||||
const expectedTitle = `${expectedH1}`;
|
||||
const targetHero = { id: 15, name: 'Magneta' };
|
||||
const targetHeroDashboardIndex = 3;
|
||||
const targetHeroDashboardIndex = 2;
|
||||
const nameSuffix = 'X';
|
||||
const newHeroName = targetHero.name + nameSuffix;
|
||||
|
||||
@@ -132,7 +132,7 @@ describe('Tutorial part 6', () => {
|
||||
await getPageElts().appHeroesHref.click();
|
||||
const page = getPageElts();
|
||||
expect(await page.appHeroes.isPresent()).toBeTruthy();
|
||||
expect(await page.allHeroes.count()).toEqual(10, 'number of heroes');
|
||||
expect(await page.allHeroes.count()).toEqual(9, 'number of heroes');
|
||||
});
|
||||
|
||||
it('can route to hero details', async () => {
|
||||
@@ -161,7 +161,7 @@ describe('Tutorial part 6', () => {
|
||||
|
||||
const page = getPageElts();
|
||||
expect(await page.appHeroes.isPresent()).toBeTruthy();
|
||||
expect(await page.allHeroes.count()).toEqual(9, 'number of heroes');
|
||||
expect(await page.allHeroes.count()).toEqual(8, 'number of heroes');
|
||||
const heroesAfter = await toHeroArray(page.allHeroes);
|
||||
// console.log(await Hero.fromLi(page.allHeroes[0]));
|
||||
const expectedHeroes = heroesBefore.filter(h => h.name !== newHeroName);
|
||||
@@ -228,7 +228,7 @@ describe('Tutorial part 6', () => {
|
||||
expect(await getPageElts().searchResults.count()).toBe(2);
|
||||
});
|
||||
|
||||
it(`continues search with 'e' and gets ${targetHero.name}`, async () => {
|
||||
it(`continues search with 'n' and gets ${targetHero.name}`, async () => {
|
||||
await getPageElts().searchBox.sendKeys('n');
|
||||
await browser.sleep(1000);
|
||||
const page = getPageElts();
|
||||
|
||||
@@ -24,13 +24,11 @@ describe('DashboardComponent', () => {
|
||||
providers: [{provide: HeroService, useValue: heroService}]
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(DashboardComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should be created', () => {
|
||||
expect(component).toBeTruthy();
|
||||
|
||||
@@ -9,14 +9,13 @@ import { Hero } from './hero';
|
||||
export class InMemoryDataService implements InMemoryDbService {
|
||||
createDb() {
|
||||
const heroes = [
|
||||
{ id: 11, name: 'Dr Nice' },
|
||||
{ id: 12, name: 'Narco' },
|
||||
{ id: 12, name: 'Dr. Nice' },
|
||||
{ id: 13, name: 'Bombasto' },
|
||||
{ id: 14, name: 'Celeritas' },
|
||||
{ id: 15, name: 'Magneta' },
|
||||
{ id: 16, name: 'RubberMan' },
|
||||
{ id: 17, name: 'Dynama' },
|
||||
{ id: 18, name: 'Dr IQ' },
|
||||
{ id: 18, name: 'Dr. IQ' },
|
||||
{ id: 19, name: 'Magma' },
|
||||
{ id: 20, name: 'Tornado' }
|
||||
];
|
||||
|
||||
@@ -2,14 +2,13 @@
|
||||
import { Hero } from './hero';
|
||||
|
||||
export const HEROES: Hero[] = [
|
||||
{ id: 11, name: 'Dr Nice' },
|
||||
{ id: 12, name: 'Narco' },
|
||||
{ id: 12, name: 'Dr. Nice' },
|
||||
{ id: 13, name: 'Bombasto' },
|
||||
{ id: 14, name: 'Celeritas' },
|
||||
{ id: 15, name: 'Magneta' },
|
||||
{ id: 16, name: 'RubberMan' },
|
||||
{ id: 17, name: 'Dynama' },
|
||||
{ id: 18, name: 'Dr IQ' },
|
||||
{ id: 18, name: 'Dr. IQ' },
|
||||
{ id: 19, name: 'Magma' },
|
||||
{ id: 20, name: 'Tornado' }
|
||||
];
|
||||
|
||||
@@ -37,7 +37,7 @@ describe('Universal', () => {
|
||||
const expectedH1 = 'Tour of Heroes';
|
||||
const expectedTitle = `${expectedH1}`;
|
||||
const targetHero = { id: 15, name: 'Magneta' };
|
||||
const targetHeroDashboardIndex = 3;
|
||||
const targetHeroDashboardIndex = 2;
|
||||
const nameSuffix = 'X';
|
||||
const newHeroName = targetHero.name + nameSuffix;
|
||||
|
||||
@@ -111,7 +111,7 @@ describe('Universal', () => {
|
||||
await getPageElts().appHeroesHref.click();
|
||||
const page = getPageElts();
|
||||
expect(await page.appHeroes.isPresent()).toBeTruthy();
|
||||
expect(await page.allHeroes.count()).toEqual(10, 'number of heroes');
|
||||
expect(await page.allHeroes.count()).toEqual(9, 'number of heroes');
|
||||
});
|
||||
|
||||
it('can route to hero details', async () => {
|
||||
@@ -140,7 +140,7 @@ describe('Universal', () => {
|
||||
|
||||
const page = getPageElts();
|
||||
expect(await page.appHeroes.isPresent()).toBeTruthy();
|
||||
expect(await page.allHeroes.count()).toEqual(9, 'number of heroes');
|
||||
expect(await page.allHeroes.count()).toEqual(8, 'number of heroes');
|
||||
const heroesAfter = await toHeroArray(page.allHeroes);
|
||||
// console.log(await Hero.fromLi(page.allHeroes[0]));
|
||||
const expectedHeroes = heroesBefore.filter(h => h.name !== newHeroName);
|
||||
@@ -206,7 +206,7 @@ describe('Universal', () => {
|
||||
expect(await getPageElts().searchResults.count()).toBe(2);
|
||||
});
|
||||
|
||||
it(`continues search with 'e' and gets ${targetHero.name}`, async () => {
|
||||
it(`continues search with 'n' and gets ${targetHero.name}`, async () => {
|
||||
await getPageElts().searchBox.sendKeys('n');
|
||||
await browser.sleep(1000);
|
||||
const page = getPageElts();
|
||||
|
||||
@@ -15,7 +15,7 @@ export function app() {
|
||||
const indexHtml = existsSync(join(distFolder, 'index.original.html')) ? 'index.original.html' : 'index';
|
||||
|
||||
// #docregion ngExpressEngine
|
||||
// Our Universal express-engine (found @ https://github.com/angular/universal/tree/master/modules/express-engine)
|
||||
// Our Universal express-engine (found @ https://github.com/angular/universal/tree/main/modules/express-engine)
|
||||
server.engine('html', ngExpressEngine({
|
||||
bootstrap: AppServerModule,
|
||||
}));
|
||||
|
||||
@@ -8,14 +8,13 @@ import { Hero } from './hero';
|
||||
export class InMemoryDataService implements InMemoryDbService {
|
||||
createDb() {
|
||||
const heroes = [
|
||||
{ id: 11, name: 'Dr Nice' },
|
||||
{ id: 12, name: 'Narco' },
|
||||
{ id: 12, name: 'Dr. Nice' },
|
||||
{ id: 13, name: 'Bombasto' },
|
||||
{ id: 14, name: 'Celeritas' },
|
||||
{ id: 15, name: 'Magneta' },
|
||||
{ id: 16, name: 'RubberMan' },
|
||||
{ id: 17, name: 'Dynama' },
|
||||
{ id: 18, name: 'Dr IQ' },
|
||||
{ id: 18, name: 'Dr. IQ' },
|
||||
{ id: 19, name: 'Magma' },
|
||||
{ id: 20, name: 'Tornado' }
|
||||
];
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
import { Hero } from './hero';
|
||||
|
||||
export const HEROES: Hero[] = [
|
||||
{ id: 11, name: 'Dr Nice' },
|
||||
{ id: 12, name: 'Narco' },
|
||||
{ id: 12, name: 'Dr. Nice' },
|
||||
{ id: 13, name: 'Bombasto' },
|
||||
{ id: 14, name: 'Celeritas' },
|
||||
{ id: 15, name: 'Magneta' },
|
||||
{ id: 16, name: 'RubberMan' },
|
||||
{ id: 17, name: 'Dynama' },
|
||||
{ id: 18, name: 'Dr IQ' },
|
||||
{ id: 18, name: 'Dr. IQ' },
|
||||
{ id: 19, name: 'Magma' },
|
||||
{ id: 20, name: 'Tornado' }
|
||||
];
|
||||
|
||||
@@ -238,7 +238,7 @@ But now you can also encapsulate a style sheet within a specific component.
|
||||
[AioGuideArchitectureServicesAndDependencyInjection]: guide/architecture#services-and-dependency-injection "Services and dependency injection - Introduction to Angular concepts | Angular"
|
||||
|
||||
[AioGuideAttributeBinding]: guide/attribute-binding "Attribute, class, and style bindings | Angular"
|
||||
[AioGuideAttributeBindingBindingToTheStyleAttribute]: guide/attribute-binding#binding-to-the-style-attribute "Binding to the style attribute - Attribute, class, and style bindings | Angular"
|
||||
[AioGuideAttributeBindingBindingToTheStyleAttribute]: guide/class-binding "Class and style binding | Angular"
|
||||
|
||||
[AioGuideBuiltInDirectives]: guide/built-in-directives "Built-in directives | Angular"
|
||||
[AioGuideBuiltInDirectivesDisplayingAndUpdatingPropertiesWithNgmodel]: guide/built-in-directives#displaying-and-updating-properties-with-ngmodel "Displaying and updating properties with ngModel - Built-in directives | Angular"
|
||||
|
||||
@@ -109,7 +109,7 @@ You can think of `.metadata.json` as a diagram of the overall structure of a dec
|
||||
|
||||
<div class="alert is-helpful">
|
||||
|
||||
Angular's [schema.ts](https://github.com/angular/angular/blob/master/packages/compiler-cli/src/metadata/schema.ts) describes the JSON format as a collection of TypeScript interfaces.
|
||||
Angular's [schema.ts](https://github.com/angular/angular/blob/main/packages/compiler-cli/src/metadata/schema.ts) describes the JSON format as a collection of TypeScript interfaces.
|
||||
|
||||
</div>
|
||||
|
||||
@@ -124,7 +124,7 @@ Define metadata objects with the following limited syntax:
|
||||
|:--- |:--- |
|
||||
| Literal object | `{cherry: true, apple: true, mincemeat: false}` |
|
||||
| Literal array | `['cherries', 'flour', 'sugar']` |
|
||||
| Spread in literal array | `['apples', 'flour', …the_rest]` |
|
||||
| Spread in literal array | `['apples', 'flour', ...]` |
|
||||
| Calls | `bake(ingredients)` |
|
||||
| New | `new Oven()` |
|
||||
| Property access | `pie.slice` |
|
||||
@@ -389,7 +389,7 @@ export class TypicalModule {}
|
||||
</code-example>
|
||||
|
||||
The Angular [`RouterModule`](api/router/RouterModule) exports two macro static methods, `forRoot` and `forChild`, to help declare root and child routes.
|
||||
Review the [source code](https://github.com/angular/angular/blob/master/packages/router/src/router_module.ts#L139 "RouterModule.forRoot source code")
|
||||
Review the [source code](https://github.com/angular/angular/blob/main/packages/router/src/router_module.ts#L139 "RouterModule.forRoot source code")
|
||||
for these methods to see how macros can simplify configuration of complex [NgModules](guide/ngmodules).
|
||||
|
||||
<a id="metadata-rewriting"></a>
|
||||
|
||||
@@ -80,7 +80,7 @@ Your template can use *data binding* to coordinate the application and DOM data,
|
||||
|
||||
For example, here is a template for the Tutorial's `HeroListComponent`.
|
||||
|
||||
<code-exampleheader="src/app/hero-list.component.html" path="architecture/src/app/hero-list.component.html" ></code-example>
|
||||
<code-example header="src/app/hero-list.component.html" path="architecture/src/app/hero-list.component.html" ></code-example>
|
||||
|
||||
This template uses typical HTML elements like `<h2>` and `<p>`, and also includes Angular template-syntax elements, `*ngFor`, `{{hero.name}}`, `(click)`, `[hero]`, and `<app-hero-detail>`.
|
||||
The template-syntax elements tell Angular how to render the HTML to the screen, using program logic and data.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Attribute, class, and style bindings
|
||||
# Attribute binding
|
||||
|
||||
Attribute binding in Angular helps you set values for attributes directly.
|
||||
With attribute binding, you can improve accessibility, style your application dynamically, and manage multiple CSS classes or styles simultaneously.
|
||||
@@ -9,15 +9,9 @@ See the <live-example></live-example> for a working example containing the code
|
||||
|
||||
</div>
|
||||
|
||||
## Binding to an attribute
|
||||
## Prerequisites
|
||||
|
||||
It is recommended that you set an element property with a [property binding](guide/property-binding) whenever possible.
|
||||
However, sometimes you don't have an element property to bind.
|
||||
In those situations, use attribute binding.
|
||||
|
||||
For example, [ARIA](https://developer.mozilla.org/docs/Web/Accessibility/ARIA) and [SVG](https://developer.mozilla.org/docs/Web/SVG) are purely attributes.
|
||||
Neither ARIA nor SVG correspond to element properties and don't set element properties.
|
||||
In these cases, you must use attribute binding because there are no corresponding property targets.
|
||||
* [Property Binding](guide/property-binding)
|
||||
|
||||
## Syntax
|
||||
|
||||
@@ -38,7 +32,9 @@ When the expression resolves to `null` or `undefined`, Angular removes the attri
|
||||
|
||||
## Binding ARIA attributes
|
||||
|
||||
One of the primary use cases for attribute binding is to set ARIA attributes, as in this example:
|
||||
One of the primary use cases for attribute binding is to set ARIA attributes.
|
||||
|
||||
To bind to an ARIA attribute, type the following:
|
||||
|
||||
<code-example header="src/app/app.component.html" path="attribute-binding/src/app/app.component.html" region="attrib-binding-aria"></code-example>
|
||||
|
||||
@@ -46,347 +42,31 @@ One of the primary use cases for attribute binding is to set ARIA attributes, as
|
||||
|
||||
## Binding to `colspan`
|
||||
|
||||
Another common use case for attribute binding is with the `colspan` attribute in tables.
|
||||
Binding to the `colspan` attribute helps you keep your tables programmatically dynamic.
|
||||
Depending on the amount of data that your application populates a table with, the number of columns that a row spans could change.
|
||||
Another common use case for attribute binding is with the `colspan` attribute in tables. Binding to the `colspan` attribute helps you to keep your tables programmatically dynamic. Depending on the amount of data that your application populates a table with, the number of columns that a row spans could change.
|
||||
|
||||
To use attribute binding with the `<td>` attribute `colspan`:
|
||||
|
||||
1. Specify the `colspan` attribute by using the following syntax:
|
||||
|
||||
<code-example format="html" hideCopy language="html">
|
||||
|
||||
[attr.colspan]
|
||||
|
||||
</code-example>
|
||||
|
||||
1. Set `[attr.colspan]` equal to an expression.
|
||||
To use attribute binding with the `<td>` attribute `colspan`
|
||||
1. Specify the `colspan` attribute by using the following syntax: `[attr.colspan]`.
|
||||
1. Set `[attr.colspan]` equal to an expression.
|
||||
|
||||
In the following example, you bind the `colspan` attribute to the expression `1 + 1`.
|
||||
|
||||
<code-example header="src/app/app.component.html" path="attribute-binding/src/app/app.component.html" region="colspan"></code-example>
|
||||
|
||||
This binding causes the `<td>` to span two columns.
|
||||
This binding causes the `<tr>` to span two columns.
|
||||
|
||||
<div class="alert is-helpful">
|
||||
|
||||
Sometimes there are differences between the name of property and an attribute.
|
||||
|
||||
`colspan` is an attribute of `<td>`, while `colSpan` with a capital "S" is a property.
|
||||
`colspan` is an attribute of `<tr>`, while `colSpan` with a capital "S" is a property.
|
||||
When using attribute binding, use `colspan` with a lowercase "s".
|
||||
|
||||
For more information on how to bind to the `colSpan` property, see the [`colspan` and `colSpan`](guide/property-binding#colspan) section of [Property Binding](guide/property-binding).
|
||||
|
||||
</div>
|
||||
|
||||
<a id="class-binding"></a>
|
||||
## What’s next
|
||||
|
||||
## Binding to the `class` attribute
|
||||
* [Class & Style Binding](guide/class-binding)
|
||||
|
||||
Use class binding to add and remove CSS class names from an element's `class` attribute.
|
||||
|
||||
### Binding to a single CSS `class`
|
||||
|
||||
To create a single class binding, use the prefix `class` followed by a dot and the name of the CSS class.
|
||||
For example,
|
||||
|
||||
<code-example format="typescript" language="typescript">
|
||||
|
||||
[class.sale]="onSale"
|
||||
|
||||
</code-example>
|
||||
|
||||
Angular adds the class when the bound expression, `onSale` is truthy, and it removes the class when the expression is falsy —with the exception of `undefined`.
|
||||
See [styling delegation](guide/style-precedence#styling-delegation) for more information.
|
||||
|
||||
### Binding to multiple CSS classes
|
||||
|
||||
To bind to multiple classes, use `[class]` set to an expression.
|
||||
For example,
|
||||
|
||||
<code-example format="typescript" language="typescript">
|
||||
|
||||
[class]="classExpression"
|
||||
|
||||
</code-example>
|
||||
|
||||
The expression can be one of:
|
||||
|
||||
* A space-delimited string of class names
|
||||
* An object with class names as the keys and truthy or falsy expressions as the values
|
||||
* An array of class names
|
||||
|
||||
With the object format, Angular adds a class only if its associated value is truthy.
|
||||
|
||||
<div class="alert is-important">
|
||||
|
||||
With any object-like expression —such as `object`, `Array`, `Map`, or `Set`— the identity of the object must change for Angular to update the class list.
|
||||
Updating the property without changing object identity has no effect.
|
||||
|
||||
</div>
|
||||
|
||||
If there are multiple bindings to the same class name, Angular uses [styling precedence](guide/style-precedence) to determine which binding to use.
|
||||
|
||||
The following table summarizes class binding syntax.
|
||||
|
||||
| Binding Type | Syntax | Input Type | Example Input Values |
|
||||
|:--- |:--- |:--- |:--- |
|
||||
| Single class binding | `[class.sale]="onSale"` | <code>boolean | undefined | null</code> | `true`, `false` |
|
||||
| Multi-class binding | `[class]="classExpression"` | `string` | `"my-class-1 my-class-2 my-class-3"` |
|
||||
| Multi-class binding | `[class]="classExpression"` | <code>Record<string, boolean | undefined | null></code> | `{foo: true, bar: false}` |
|
||||
| Multi-class binding | `[class]="classExpression"` | <code>Array<string></code> | `['foo', 'bar']` |
|
||||
|
||||
<a id="style-binding"></a>
|
||||
|
||||
## Binding to the style attribute
|
||||
|
||||
Use style binding to set styles dynamically.
|
||||
|
||||
### Binding to a single style
|
||||
|
||||
To create a single style binding, use the prefix `style` followed by a dot and the name of the CSS style property.
|
||||
For example,
|
||||
|
||||
<code-example format="typescript" language="typescript">
|
||||
|
||||
[style.width]="width"
|
||||
|
||||
</code-example>
|
||||
|
||||
Angular sets the property to the value of the bound expression, which is usually a string.
|
||||
Optionally, you can add a unit extension like `em` or `%`, which requires a number type.
|
||||
|
||||
<div class="alert is-helpful">
|
||||
|
||||
You can write a style property name in either [dash-case](guide/glossary#dash-case), or [camelCase](guide/glossary#camelcase).
|
||||
|
||||
<code-example format="html" language="html">
|
||||
|
||||
<nav [style.background-color]="expression"></nav>
|
||||
|
||||
<nav [style.backgroundColor]="expression"></nav>
|
||||
|
||||
</code-example>
|
||||
|
||||
</div>
|
||||
|
||||
### Binding to multiple styles
|
||||
|
||||
To toggle multiple styles, bind to the `[style]` attribute.
|
||||
For example,
|
||||
|
||||
<code-example format="typescript" language="typescript">
|
||||
|
||||
[style]="styleExpression"
|
||||
|
||||
</code-example>
|
||||
|
||||
The `styleExpression` can be one of:
|
||||
|
||||
* A string list of styles, such as
|
||||
|
||||
<code-example format="css" hideCopy language="css">
|
||||
|
||||
"width: 100px; height: 100px; background-color: cornflowerblue;"
|
||||
|
||||
</code-example>
|
||||
|
||||
* An object with style names as the keys and style values as the values, such as
|
||||
|
||||
<code-example format="typescript" hideCopy language="typescript">
|
||||
|
||||
{width: '100px', height: '100px', backgroundColor: 'cornflowerblue'}
|
||||
|
||||
</code-example>
|
||||
|
||||
<div class="alert is-helpful">
|
||||
|
||||
**NOTE**: <br />
|
||||
Binding an array to `[style]` is not supported.
|
||||
|
||||
</div>
|
||||
|
||||
<div class="alert is-important">
|
||||
|
||||
When binding `[style]` to an object expression, the identity of the object must change for Angular to update the class list.
|
||||
Updating the property without changing object identity has no effect.
|
||||
|
||||
</div>
|
||||
|
||||
#### Single and multiple-style binding example
|
||||
|
||||
<code-example header="nav-bar.component.ts" path="attribute-binding/src/app/single-and-multiple-style-binding.component.ts"></code-example>
|
||||
|
||||
If there are multiple bindings to the same style attribute, Angular uses [styling precedence](guide/style-precedence) to determine which binding to use.
|
||||
|
||||
The following table summarizes style binding syntax.
|
||||
|
||||
| Binding Type | Syntax | Input Type | Example Input Values |
|
||||
|:--- |:--- |:--- |:--- |
|
||||
| Single style binding | `[style.width]="width"` | <code>string | undefined | null</code> | `"100px"` |
|
||||
| Single style binding with units | `[style.width.px]="width"` | <code>number | undefined | null</code> | `100` |
|
||||
| Multi-style binding | `[style]="styleExpression"` | `string` | `"width: 100px; height: 100px"` |
|
||||
| Multi-style binding | `[style]="styleExpression"` | <code>Record<string, string | undefined | null></code> | `{width: '100px', height: '100px'}` |
|
||||
|
||||
<div class="alert is-helpful">
|
||||
|
||||
The [NgStyle](guide/built-in-directives/#ngstyle) directive can be used as an alternative to direct `[style]` bindings.
|
||||
However, using the preceding style binding syntax without `NgStyle` is preferred because due to improvements in style binding in Angular, `NgStyle` no longer provides significant value, and might eventually be removed in the future.
|
||||
|
||||
</div>
|
||||
|
||||
<a id="styling-precedence"></a>
|
||||
|
||||
## Styling Precedence
|
||||
|
||||
A single HTML element can have its CSS class list and style values bound to multiple sources \(for example, host bindings from multiple directives\).
|
||||
|
||||
When there are multiple bindings to the same class name or style property, Angular uses a set of precedence rules to resolve conflicts and determine which classes or styles are ultimately applied to the element.
|
||||
|
||||
<div class="alert is-helpful">
|
||||
|
||||
<header>Styling precedence (highest to lowest)</header>
|
||||
|
||||
1. Template bindings
|
||||
1. Property binding
|
||||
|
||||
<code-tabs>
|
||||
<code-pane format="html" header="class.foo" hideCopy language="html"> <div [class.foo]="hasFoo"> </code-pane>
|
||||
<code-pane format="html" header="style.color" hideCopy language="html"> <div [style.color]="color"> </code-pane>
|
||||
</code-tabs>
|
||||
|
||||
1. Map binding
|
||||
|
||||
<code-tabs>
|
||||
<code-pane format="html" header="class" hideCopy language="html"> <div [class]="classExpr"> </code-pane>
|
||||
<code-pane format="html" header="style" hideCopy language="html"> <div [style]="styleExpr"> </code-pane>
|
||||
</code-tabs>
|
||||
|
||||
1. Static value
|
||||
|
||||
<code-tabs>
|
||||
<code-pane format="html" header="class" hideCopy language="html"> <div class="foo"> </code-pane>
|
||||
<code-pane format="html" header="style" hideCopy language="html"> <div style="color: blue"> </code-pane>
|
||||
</code-tabs>
|
||||
|
||||
1. Directive host bindings
|
||||
1. Property binding
|
||||
|
||||
<code-tabs>
|
||||
<code-pane format="html" header="class" hideCopy language="html"> host: {'[class.foo]': 'hasFoo'} </code-pane>
|
||||
<code-pane format="html" header="style" hideCopy language="html"> host: {'[style.color]': 'color'} </code-pane>
|
||||
</code-tabs>
|
||||
|
||||
1. Map binding
|
||||
|
||||
<code-tabs>
|
||||
<code-pane format="html" header="class" hideCopy language="html"> host: {'[class]': 'classExpr'} </code-pane>
|
||||
<code-pane format="html" header="style" hideCopy language="html"> host: {'[style]': 'styleExpr'} </code-pane>
|
||||
</code-tabs>
|
||||
|
||||
1. Static value
|
||||
|
||||
<code-tabs>
|
||||
<code-pane format="html" header="class" hideCopy language="html"> host: host: {'class': 'foo'} </code-pane>
|
||||
<code-pane format="html" header="style" hideCopy language="html"> host: {'style': 'color: blue'} </code-pane>
|
||||
</code-tabs>
|
||||
|
||||
1. Component host bindings
|
||||
1. Property binding
|
||||
|
||||
<code-tabs>
|
||||
<code-pane format="html" header="class" hideCopy language="html"> host: {'[class.foo]': 'hasFoo'} </code-pane>
|
||||
<code-pane format="html" header="style" hideCopy language="html"> host: {'[style.color]': 'color'} </code-pane>
|
||||
</code-tabs>
|
||||
|
||||
1. Map binding
|
||||
|
||||
<code-tabs>
|
||||
<code-pane format="html" header="class" hideCopy language="html"> host: {'[class]': 'classExpr'} </code-pane>
|
||||
<code-pane format="html" header="style" hideCopy language="html"> host: {'[style]': 'styleExpr'} </code-pane>
|
||||
</code-tabs>
|
||||
|
||||
1. Static value
|
||||
|
||||
<code-tabs>
|
||||
<code-pane format="html" header="class" hideCopy language="html"> host: {'class': 'foo'} </code-pane>
|
||||
<code-pane format="html" header="style" hideCopy language="html"> host: {'style': 'color: blue'} </code-pane>
|
||||
</code-tabs>
|
||||
|
||||
</div>
|
||||
|
||||
The more specific a class or style binding is, the higher its precedence.
|
||||
|
||||
A binding to a specific class \(for example, `[class.foo]`\) takes precedence over a generic `[class]` binding, and a binding to a specific style \(for example, `[style.bar]`\) takes precedence over a generic `[style]` binding.
|
||||
|
||||
<code-example header="src/app/app.component.html" path="attribute-binding/src/app/app.component.html" region="basic-specificity"></code-example>
|
||||
|
||||
Specificity rules also apply when it comes to bindings that originate from different sources.
|
||||
It's possible for an element to have bindings in the template where it's declared, from host bindings on matched directives, and from host bindings on matched components.
|
||||
|
||||
Template bindings are the most specific because they apply to the element directly and exclusively, so they have the highest precedence.
|
||||
|
||||
Directive host bindings are considered less specific because directives can be used in multiple locations, so they have a lower precedence than template bindings.
|
||||
|
||||
Directives often augment component behavior, so host bindings from components have the lowest precedence.
|
||||
|
||||
<code-example header="src/app/app.component.html" path="attribute-binding/src/app/app.component.html" region="source-specificity"></code-example>
|
||||
|
||||
In addition, bindings take precedence over static attributes.
|
||||
|
||||
In the following case, `class` and `[class]` have similar specificity, but the `[class]` binding takes precedence because it is dynamic.
|
||||
|
||||
<code-example header="src/app/app.component.html" path="attribute-binding/src/app/app.component.html" region="dynamic-priority"></code-example>
|
||||
|
||||
<a id="styling-delegation"></a>
|
||||
|
||||
### Delegating to styles with lower precedence
|
||||
|
||||
It is possible for higher precedence styles to "delegate" to lower precedence styles using `undefined` values.
|
||||
Whereas setting a style property to `null` ensures the style is removed, setting it to `undefined` causes Angular to fall back to the next-highest precedence binding to that style.
|
||||
|
||||
For example, consider the following template:
|
||||
|
||||
<code-example header="src/app/app.component.html" path="attribute-binding/src/app/app.component.html" region="style-delegation"></code-example>
|
||||
|
||||
Imagine that the `dirWithHostBinding` directive and the `comp-with-host-binding` component both have a `[style.width]` host binding.
|
||||
In that case, if `dirWithHostBinding` sets its binding to `undefined`, the `width` property falls back to the value of the `comp-with-host-binding` host binding.
|
||||
However, if `dirWithHostBinding` sets its binding to `null`, the `width` property will be removed entirely.
|
||||
|
||||
## Injecting attribute values
|
||||
|
||||
There are cases where you need to differentiate the behavior of a [Component](api/core/Component) or [Directive](api/core/Directive) based on a static value set on the host element as an HTML attribute.
|
||||
For example, you might have a directive that needs to know the `type` of a `<button>` or `<input>` element.
|
||||
|
||||
The [Attribute](api/core/Attribute) parameter decorator is great for passing the value of an HTML attribute to a component/directive constructor using [dependency injection](guide/dependency-injection).
|
||||
|
||||
<div class="alert is-helpful">
|
||||
|
||||
The injected value captures the value of the specified HTML attribute at that moment.
|
||||
Future updates to the attribute value are not reflected in the injected value.
|
||||
|
||||
</div>
|
||||
|
||||
<code-example header="src/app/my-input-with-attribute-decorator.component.ts" path="attribute-binding/src/app/my-input-with-attribute-decorator.component.ts"></code-example>
|
||||
|
||||
<code-example header="src/app/app.component.html" path="attribute-binding/src/app/app.component.html" region="attribute-decorator"></code-example>
|
||||
|
||||
In the preceding example, the result of `app.component.html` is **The type of the input is: number**.
|
||||
|
||||
Another example is the [RouterOutlet](api/router/RouterOutlet) directive, which makes use of the [Attribute](api/core/Attribute) decorator to retrieve the unique [name](api/router/RouterOutlet#description) on each outlet.
|
||||
|
||||
<div class="callout is-helpful">
|
||||
|
||||
<header><code>@Attribute()</code> vs <code>@Input()</code></header>
|
||||
|
||||
Remember, use [@Input()](api/core/Input) when you want to keep track of the attribute value and update the associated property.
|
||||
Use [@Attribute()](api/core/Attribute) when you want to inject the value of an HTML attribute to a component or directive constructor.
|
||||
|
||||
</div>
|
||||
|
||||
<!-- links -->
|
||||
|
||||
<!-- external links -->
|
||||
|
||||
<!-- end links -->
|
||||
|
||||
@reviewed 2022-02-28
|
||||
@reviewed 2022-05-02
|
||||
|
||||
@@ -0,0 +1,109 @@
|
||||
# Understanding binding
|
||||
|
||||
In an Angular template, a binding creates a live connection between a part of the UI created from a template (a DOM element, directive, or component) and the model (the component instance to which the template belongs). This connection can be used to synchronize the view with the model, to notify the model when an event or user action takes place in the view, or both. Angular's [Change Detection](guide/change-detection) algorithm is responsible for keeping the view and the model in sync.
|
||||
|
||||
Examples of binding include:
|
||||
|
||||
* text interpolations
|
||||
* property binding
|
||||
* event binding
|
||||
* two-way binding
|
||||
|
||||
Bindings always have two parts: a _target_ which will receive the bound value, and a _template expression_ which produces a value from the model.
|
||||
|
||||
|
||||
## Syntax
|
||||
|
||||
Template expressions are similar to JavaScript expressions.
|
||||
Many JavaScript expressions are legal template expressions, with the following exceptions.
|
||||
|
||||
You can't use JavaScript expressions that have or promote side effects, including:
|
||||
|
||||
* Assignments (`=`, `+=`, `-=`, `...`)
|
||||
* Operators such as `new`, `typeof`, or `instanceof`
|
||||
* Chaining expressions with <code>;</code> or <code>,</code>
|
||||
* The increment and decrement operators `++` and `--`
|
||||
* Some of the ES2015+ operators
|
||||
|
||||
Other notable differences from JavaScript syntax include:
|
||||
|
||||
* No support for the bitwise operators such as `|` and `&`
|
||||
* New [template expression operators](guide/template-expression-operators), such as `|`
|
||||
|
||||
## Expression context
|
||||
|
||||
Interpolated expressions have a context—a particular part of the application to which the expression belongs. Typically, this context is the component instance.
|
||||
|
||||
In the following snippet, the expression `recommended` and the expression `itemImageUrl2` refer to properties of the `AppComponent`.
|
||||
|
||||
<code-example path="interpolation/src/app/app.component.html" region="component-context" header="src/app/app.component.html"></code-example>
|
||||
|
||||
An expression can also refer to properties of the _template's_ context such as a [template input variable](guide/structural-directives#shorthand) or a [template reference variable](guide/template-reference-variables).
|
||||
|
||||
The following example uses a template input variable of `customer`.
|
||||
|
||||
<code-example path="interpolation/src/app/app.component.html" region="template-input-variable" header="src/app/app.component.html (template input variable)"></code-example>
|
||||
|
||||
This next example features a template reference variable, `#customerInput`.
|
||||
|
||||
<code-example path="interpolation/src/app/app.component.html" region="template-reference-variable" header="src/app/app.component.html (template reference variable)"></code-example>
|
||||
|
||||
<div class="alert is-helpful">
|
||||
|
||||
Template expressions cannot refer to anything in the global namespace, except `undefined`. They can't refer to `window` or `document`. Additionally, they can't call `console.log()` or `Math.max()` and are restricted to referencing members of the expression context.
|
||||
|
||||
</div>
|
||||
|
||||
### Preventing name collisions
|
||||
|
||||
The context against which an expression evaluates is the union of the template variables, the directive's context object—if it has one—and the component's members.
|
||||
If you reference a name that belongs to more than one of these namespaces, Angular applies the following precedence logic to determine the context:
|
||||
|
||||
1. The template variable name.
|
||||
1. A name in the directive's context.
|
||||
1. The component's member names.
|
||||
|
||||
To avoid variables shadowing variables in another context, keep variable names unique.
|
||||
In the following example, the `AppComponent` template greets the `customer`, Padma.
|
||||
|
||||
An `ngFor` then lists each `customer` in the `customers` array.
|
||||
|
||||
<code-example path="interpolation/src/app/app.component.1.ts" region="var-collision" header="src/app/app.component.ts"></code-example>
|
||||
|
||||
The `customer` within the `ngFor` is in the context of an `<ng-template>` and so refers to the `customer` in the `customers` array, in this case Ebony and Chiho.
|
||||
This list does not feature Padma because `customer` outside of the `ngFor` is in a different context.
|
||||
Conversely, `customer` in the `<h1>` doesn't include Ebony or Chiho because the context for this `customer` is the class and the class value for `customer` is Padma.
|
||||
|
||||
## Expression best practices
|
||||
|
||||
When using template a expression, follow these best practices:
|
||||
|
||||
* **Use short expressions**
|
||||
|
||||
Use property names or method calls whenever possible. Keep application and business logic in the component, where it is accessible to develop and test.
|
||||
|
||||
* **Quick execution**
|
||||
|
||||
Angular executes a template expression after every [change detection](guide/glossary#change-detection) cycle. Many asynchronous activities trigger change detection cycles, such as promise resolutions, HTTP results, timer events, key presses and mouse moves.
|
||||
|
||||
An expression should finish quickly to keep the user experience as efficient as possible, especially on slower devices. Consider caching values when their computation requires greater resources.
|
||||
|
||||
## No visible side effects
|
||||
|
||||
According to Angular's [unidirectional data flow model](guide/glossary#unidirectional-data-flow), a template expression should not change any application state other than the value of the target property. Reading a component value should not change some other displayed value. The view should be stable throughout a single rendering pass.
|
||||
|
||||
<div class="callout is-important">
|
||||
<header>Idempotent expressions reduce side effects</header>
|
||||
|
||||
An [idempotent](https://en.wikipedia.org/wiki/Idempotence) expression is free of side effects and improves Angular's change detection performance. In Angular terms, an idempotent expression always returns *exactly the same thing* until one of its dependent values changes.
|
||||
|
||||
Dependent values should not change during a single turn of the event loop. If an idempotent expression returns a string or a number, it returns the same string or number if you call it twice consecutively. If the expression returns an object, including an `array`, it returns the same object *reference* if you call it twice consecutively.
|
||||
|
||||
</div>
|
||||
|
||||
## What's next
|
||||
|
||||
* [Property binding](guide/property-binding)
|
||||
* [Event binding](guide/event-binding)
|
||||
|
||||
@reviewed 2022-05-12
|
||||
@@ -6,7 +6,7 @@ This page discusses build-specific configuration options for Angular projects.
|
||||
|
||||
## Configuring application environments
|
||||
|
||||
You can define different named build configurations for your project, such as *stage* and *production*, with different defaults.
|
||||
You can define different named build configurations for your project, such as *staging* and *production*, with different defaults.
|
||||
|
||||
Each named configuration can have defaults for any of the options that apply to the various [builder targets](guide/glossary#target), such as `build`, `serve`, and `test`.
|
||||
The [Angular CLI](cli) `build`, `serve`, and `test` commands can then replace files with appropriate versions for your intended target environment.
|
||||
@@ -30,7 +30,7 @@ For example:
|
||||
environment.prod.ts
|
||||
</div>
|
||||
<div class="file">
|
||||
environment.stage.ts
|
||||
environment.staging.ts
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -60,7 +60,7 @@ export const environment = {
|
||||
</code-example>
|
||||
|
||||
You can add target-specific configuration files, such as `environment.prod.ts`.
|
||||
The following sets content sets default values for the production build target:
|
||||
The following content sets default values for the production build target:
|
||||
|
||||
<code-example format="typescript" language="typescript">
|
||||
|
||||
@@ -102,7 +102,7 @@ The following application structure configures build targets for production and
|
||||
environment.prod.ts
|
||||
</div>
|
||||
<div class="file">
|
||||
environment.prod.ts
|
||||
environment.staging.ts
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -50,7 +50,7 @@ Add or remove multiple CSS classes simultaneously with `ngClass`.
|
||||
|
||||
<div class="alert is-helpful">
|
||||
|
||||
To add or remove a *single* class, use [class binding](guide/attribute-binding#class-binding) rather than `NgClass`.
|
||||
To add or remove a *single* class, use [class binding](guide/class-binding) rather than `NgClass`.
|
||||
|
||||
</div>
|
||||
|
||||
@@ -251,16 +251,12 @@ then uses this template repeatedly to create a new set of elements and bindings
|
||||
in the list.
|
||||
For more information about shorthand, see the [Structural Directives](guide/structural-directives#shorthand) guide.
|
||||
|
||||
<a id="one-per-element"></a>
|
||||
|
||||
## Repeating elements when a condition is true
|
||||
|
||||
To repeat a block of HTML when a particular condition is true, put the `*ngIf` on a container element that wraps an `*ngFor` element.
|
||||
One or both elements can be an `<ng-container>` so you don't have to introduce extra levels of HTML.
|
||||
|
||||
Because structural directives add and remove nodes from the DOM, apply only one structural directive per element.
|
||||
|
||||
For more information about `NgFor` see the [NgForOf API reference](api/common/NgForOf).
|
||||
For more information see [one structural directive per element](guide/structural-directives#one-per-element).
|
||||
|
||||
<a id="ngfor-with-trackby"></a>
|
||||
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
# Skipping component subtrees
|
||||
|
||||
JavaScript, by default, uses mutable data structures that you can reference from multiple different components. Angular runs change detection over your entire component tree to make sure that the most up-to-date state of your data structures is reflected in the DOM.
|
||||
|
||||
Change detection is sufficiently fast for most applications. However, when an application has an especially large component tree, running change detection across the whole application can cause performance issues. You can address this by configuring change detection to only run on a subset of the component tree.
|
||||
|
||||
If you are confident that a part of the application is not affected by a state change, you can use [OnPush](https://angular.io/api/core/ChangeDetectionStrategy) to skip change detection in an entire component subtree.
|
||||
|
||||
|
||||
## Using OnPush
|
||||
|
||||
OnPush change detection instructs Angular that to only run change detection automatically for a component subtree when:
|
||||
* The root component of the subtree receives new inputs as the result of a template binding. Angular compares the current and past value of the input with `==`
|
||||
* Angular handles an event within the component with OnPush change detection
|
||||
|
||||
You can set the change detection strategy of a component to `OnPush` in the `@Component` decorator:
|
||||
|
||||
```ts
|
||||
import { ChangeDetectionStrategy, Component } from '@angular/core';
|
||||
@Component({
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class MyComponent {}
|
||||
```
|
||||
|
||||
## Common change detection scenarios
|
||||
|
||||
This section examines several common change detection scenarios to illustrate Angular's behavior.
|
||||
|
||||
## An event is handled by a component with default change detection
|
||||
|
||||
If Angular handles an event within a component without `OnPush` strategy, the framework executes change detection on the entire component tree. Angular will skip descendant component subtrees with roots using `OnPush`, which have not received new inputs.
|
||||
|
||||
As an example, if we set the change detection strategy of `MainComponent` to `OnPush` and the user interacts with a component outside the subtree with root `MainComponent`, Angular will check all the green components from the diagram below (`AppComponent`, `HeaderComponent`, `SearchComponent`, `ButtonComponent`) unless `MainComponent` receives new inputs:
|
||||
|
||||
<div class="lightbox">
|
||||
<img alt="Change detection propagation from non-OnPush component" src="generated/images/guide/change-detection/event-trigger.svg">
|
||||
</div>
|
||||
|
||||
## An event is handled by a component with OnPush
|
||||
|
||||
If Angular handles an event within a component with OnPush strategy, the framework will execute change detection within the entire component tree. Angular will ignore component subtrees with roots using OnPush, which have not received new inputs and are outside the component which handled the event.
|
||||
|
||||
As an example, if Angular handles an event within `MainComponent`, the framework will run change detection in the entire component tree. Angular will ignore the subtree with root `LoginComponent` because it has `OnPush` and the event happened outside of its scope.
|
||||
|
||||
<div class="lightbox">
|
||||
<img alt="Change detection propagation from OnPush component" src="generated/images/guide/change-detection/on-push-trigger.svg">
|
||||
</div>
|
||||
|
||||
## An event is handled by a descendant of a component with OnPush
|
||||
|
||||
If Angular handles an event in a component with OnPush, the framework will execute change detection in the entire component tree, including the component’s ancestors.
|
||||
|
||||
As an example, in the diagram below, Angular handles an event in `LoginComponent` which uses OnPush. Angular will invoke change detection in the entire component subtree including `MainComponent` (`LoginComponent`’s parent), even though `MainComponent` has `OnPush` as well. Angular checks `MainComponent` as well because `LoginComponent` is part of its view.
|
||||
|
||||
<div class="lightbox">
|
||||
<img alt="Change detection propagation from nested OnPush component" src="generated/images/guide/change-detection/leaf-trigger.svg">
|
||||
</div>
|
||||
|
||||
## New inputs to component with OnPush
|
||||
|
||||
Angular will run change detection within a child component with `OnPush` setting an input property as result of a template binding.
|
||||
|
||||
For example, in the diagram below, `AppComponent` passes a new input to `MainComponent`, which has `OnPush`. Angular will run change detection in `MainComponent` but will not run change detection in `LoginComponent`, which also has `OnPush`, unless it receives new inputs as well.
|
||||
|
||||
<div class="lightbox">
|
||||
<img alt="Change detection propagation with OnPush component that receives new inputs" src="generated/images/guide/change-detection/on-push-input.svg">
|
||||
</div>
|
||||
|
||||
## Edge cases
|
||||
|
||||
* **Modifying input properties in TypeScript code**. When you use an API like `@ViewChild` or `@ContentChild` to get a reference to a component in TypeScript and manually modify an `@Input` property, Angular will not automatically run change detection for OnPush components. If you need Angular to run change detection, you can inject `ChangeDetectorRef` in your component and call `changeDetectorRef.markForCheck()` to tell Angular to schedule a change detection.
|
||||
* **Modifying object references**. In case an input receives a mutable object as value and you modify the object but preserve the reference, Angular will not invoke change detection. That’s the expected behavior because the previous and the current value of the input point to the same reference.
|
||||
|
||||
@reviewed 2022-05-04
|
||||
@@ -0,0 +1,31 @@
|
||||
# Slow computations
|
||||
|
||||
On every change detection cycle, Angular synchronously:
|
||||
|
||||
* Evaluates all template expressions in all components, unless specified otherwise, based on that each component's detection strategy
|
||||
* Executes the `ngDoCheck`, `ngAfterContentChecked`, `ngAfterViewChecked`, and `ngOnChanges` lifecycle hooks.
|
||||
A single slow computation within a template or a lifecycle hook can slow down the entire change detection process because Angular runs the computations sequentially.
|
||||
|
||||
## Identifying slow computations
|
||||
|
||||
You can identify heavy computations with Angular DevTools’ profiler. In the performance timeline, click on a bar to preview a particular change detection cycle. This displays a bar chart, which shows how long the framework spent in change detection for each component. When you click on a component, you can preview how long Angular spent evaluating its template and lifecycle hooks.
|
||||
|
||||
<div class="lightbox">
|
||||
<img alt="Angular DevTools profiler preview showing slow computation" src="generated/images/guide/change-detection/slow-computations.png">
|
||||
</div>
|
||||
|
||||
For example, in the screenshot above, we selected the second change detection cycle after the profiler started where Angular spent over 573 ms. Angular spent most time in the `EmployeeListComponent`. In the details panel, we can see that we spent over 297ms in evaluating the template of the `EmployeeListComponent`.
|
||||
|
||||
|
||||
## Optimizing slow computations
|
||||
|
||||
There are several techniques to eliminate slow computations:
|
||||
|
||||
* **Optimizing the underlying algorithm**. This is the recommended approach; if you can speed up the algorithm that is causing the problem, you can speed up the entire change detection mechanism.
|
||||
* **Caching using pure pipes**. You can move the heavy computation to a pure [pipe](https://angular.io/guide/pipes). Angular will reevaluate a pure pipe only if it detects that its inputs changed, compared to the previous time Angular called it.
|
||||
* **Using memoization**. [Memoization](https://en.wikipedia.org/wiki/Memoization) is a similar technique to pure pipes, with the difference that pure pipes preserve only the last result from the computation where memoization could store multiple results.
|
||||
* **Avoid repaints/reflows in lifecycle hooks**. Certain [operations](https://web.dev/avoid-large-complex-layouts-and-layout-thrashing/) cause the browser to either synchronously recalculate the layout of the page or re-render it. Since reflows and repaints are generally slow, we want to avoid performing them in every change detection cycle.
|
||||
|
||||
Pure pipes and memoization have different trade-offs. Pure pipes are an Angular built-in concept compared to memoization, which is a general software engineering practice for caching function results. The memory overhead of memoization could be significant if you invoke the heavy computation frequently with different arguments.
|
||||
|
||||
@reviewed 2022-05-04
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user