mirror of
https://github.com/angular/angular.git
synced 2026-09-14 13:54:52 +08:00
Compare commits
128 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 77329b70a0 | |||
| 43ad12b793 | |||
| a752c31453 | |||
| 9cb228a955 | |||
| e43b03f4bf | |||
| e27b505149 | |||
| eff06ea8be | |||
| 4db321cf3f | |||
| 1aefc27f4b | |||
| 4799a3bd5d | |||
| 67bc88fa54 | |||
| b9a0d022e5 | |||
| ea9c10eabb | |||
| 3549b5e111 | |||
| 729b32ee01 | |||
| b0928f852a | |||
| fec9be1942 | |||
| 359408e0b1 | |||
| 4d708b213d | |||
| 50623ea1e1 | |||
| 443e5f1591 | |||
| c75c168ed5 | |||
| 3b50885691 | |||
| 6148d76e21 | |||
| aa0fce6531 | |||
| 43b027f36e | |||
| e7eadd9aff | |||
| badae7254e | |||
| 586cc24a10 | |||
| b558a01c84 | |||
| 2fc1971ac6 | |||
| 9637b5d03d | |||
| 7ead0fccb0 | |||
| 6ca8ecad54 | |||
| e3bdab12fa | |||
| 342737e01f | |||
| 0767adc603 | |||
| 37dcf8d658 | |||
| ffbafc7d4a | |||
| 917b9bdd2e | |||
| 7ca08aaa5b | |||
| c19c44d694 | |||
| b0f434a893 | |||
| c05bef0ee8 | |||
| 3f0e892638 | |||
| edc45d5b6f | |||
| 79a32816dc | |||
| 11a0a5d017 | |||
| 89db58691f | |||
| 624767c076 | |||
| 7b30a869e1 | |||
| 986b9c3bea | |||
| 69598c0c6c | |||
| ffe3aa1169 | |||
| 2aefed8763 | |||
| 377442d90f | |||
| 20a549f1de | |||
| daf7c611b2 | |||
| c1163535d1 | |||
| 57123524a2 | |||
| dcd6c8c79b | |||
| fbb0398795 | |||
| 56ad65afa7 | |||
| b1b7bbffb8 | |||
| 48aec63ee4 | |||
| d68cf175a4 | |||
| 89f8331b7e | |||
| 84ade02df6 | |||
| dbe673b027 | |||
| 1a526f2881 | |||
| 600a8cd59a | |||
| bb57d34110 | |||
| ef94bb6bf6 | |||
| e627c6eaef | |||
| 65c9148c71 | |||
| 0a3edfb543 | |||
| f00336c31f | |||
| 8997260e29 | |||
| 033608e17b | |||
| 926063eb0a | |||
| 187b46a3a0 | |||
| 6447c0eecc | |||
| ed8857e46f | |||
| 3e31f1a34e | |||
| d411ee735a | |||
| d34e3298db | |||
| db35266021 | |||
| 790f4f7c26 | |||
| 5e80e6db1c | |||
| 5597e5d172 | |||
| f8a5b40489 | |||
| 3bd5860c74 | |||
| b975ff1bc2 | |||
| ba686cc43f | |||
| a7e4b99515 | |||
| 02fcd928b2 | |||
| fba7fe175c | |||
| e5e8be4d72 | |||
| 8521188cd1 | |||
| 44a0ea4e2d | |||
| 3eda005680 | |||
| 7234824228 | |||
| 0b95d141ab | |||
| 78b48cc272 | |||
| 1c536250b6 | |||
| 674b6c36b0 | |||
| 35925263bd | |||
| 501c159e05 | |||
| 277540c4fc | |||
| 6d00115bf4 | |||
| ef965ac87c | |||
| 369e96c837 | |||
| 2363e77002 | |||
| 1581988d2e | |||
| 0ecc2d12f1 | |||
| 453e00c9b0 | |||
| 4d722a276f | |||
| 744cb1e561 | |||
| deb9ea07c5 | |||
| 62b87b4551 | |||
| 6c278a9069 | |||
| 5de985a565 | |||
| 85c05514be | |||
| 7aa92fa8fe | |||
| 4b96f370ee | |||
| 9e2c1405d1 | |||
| 9f05732656 | |||
| ed41bfcad7 |
@@ -1,5 +1,5 @@
|
||||
# Enable debugging tests with --config=debug
|
||||
test:debug --test_arg=--node_options=--inspect-brk --test_output=streamed --test_strategy=exclusive --test_timeout=9999 --nocache_test_results
|
||||
test:debug --test_arg=--node_options=--inspect-brk --test_output=streamed --test_strategy=exclusive --test_timeout=9999 --nocache_test_results --spawn_strategy=local
|
||||
|
||||
# Do not attempt to de-flake locally.
|
||||
# On CI we might set this to `3` to run with deflaking.
|
||||
@@ -177,6 +177,9 @@ test:saucelabs --flaky_test_attempts=1
|
||||
|
||||
# --ng_perf will ask the Ivy compiler to produce performance results for each build.
|
||||
build --flag_alias=ng_perf=//packages/compiler-cli:ng_perf
|
||||
# --adev_fast will run adev build/serve in a faster mode, skipping things like prerendering
|
||||
# for local development.
|
||||
build --flag_alias=fast_adev=//adev:fast_build_mode
|
||||
|
||||
####################################################
|
||||
# User bazel configuration
|
||||
|
||||
@@ -21,16 +21,16 @@ jobs:
|
||||
(github.event.action == 'synchronize' && contains(github.event.pull_request.labels.*.name, 'adev: preview'))
|
||||
steps:
|
||||
- name: Initialize environment
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@f89362d698858f9674463e48fde514b097a15838
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@c83e99a12397014162531ca125c94549db55dd84
|
||||
- name: Setup Bazel
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@f89362d698858f9674463e48fde514b097a15838
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@c83e99a12397014162531ca125c94549db55dd84
|
||||
- name: Setup Bazel RBE
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@f89362d698858f9674463e48fde514b097a15838
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@c83e99a12397014162531ca125c94549db55dd84
|
||||
- name: Install node modules
|
||||
run: yarn install --frozen-lockfile
|
||||
- name: Build adev to ensure it continues to work
|
||||
run: yarn bazel build --config=aio_local_deps //adev:build
|
||||
- uses: angular/dev-infra/github-actions/previews/pack-and-upload-artifact@f89362d698858f9674463e48fde514b097a15838
|
||||
- uses: angular/dev-infra/github-actions/previews/pack-and-upload-artifact@c83e99a12397014162531ca125c94549db55dd84
|
||||
with:
|
||||
workflow-artifact-name: 'adev-preview'
|
||||
pull-number: '${{github.event.pull_request.number}}'
|
||||
|
||||
@@ -40,7 +40,7 @@ jobs:
|
||||
npx -y firebase-tools@latest target:clear --config adev/firebase.json --project ${{env.PREVIEW_PROJECT}} hosting angular-docs
|
||||
npx -y firebase-tools@latest target:apply --config adev/firebase.json --project ${{env.PREVIEW_PROJECT}} hosting angular-docs ${{env.PREVIEW_SITE}}
|
||||
|
||||
- uses: angular/dev-infra/github-actions/previews/upload-artifacts-to-firebase@f89362d698858f9674463e48fde514b097a15838
|
||||
- uses: angular/dev-infra/github-actions/previews/upload-artifacts-to-firebase@c83e99a12397014162531ca125c94549db55dd84
|
||||
with:
|
||||
github-token: '${{secrets.GITHUB_TOKEN}}'
|
||||
workflow-artifact-name: 'adev-preview'
|
||||
|
||||
@@ -25,7 +25,7 @@ jobs:
|
||||
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||
- uses: ./.github/actions/yarn-install
|
||||
|
||||
- uses: angular/dev-infra/github-actions/bazel/configure-remote@f89362d698858f9674463e48fde514b097a15838
|
||||
- uses: angular/dev-infra/github-actions/bazel/configure-remote@c83e99a12397014162531ca125c94549db55dd84
|
||||
with:
|
||||
bazelrc: ./.bazelrc.user
|
||||
|
||||
@@ -34,7 +34,7 @@ jobs:
|
||||
# the number of concurrent actions is determined based on the host resources.
|
||||
- run: bazel build //aio:build --jobs=32 --announce_rc --verbose_failures
|
||||
|
||||
- uses: angular/dev-infra/github-actions/previews/pack-and-upload-artifact@f89362d698858f9674463e48fde514b097a15838
|
||||
- uses: angular/dev-infra/github-actions/previews/pack-and-upload-artifact@c83e99a12397014162531ca125c94549db55dd84
|
||||
with:
|
||||
workflow-artifact-name: 'aio'
|
||||
pull-number: '${{github.event.pull_request.number}}'
|
||||
|
||||
@@ -34,7 +34,7 @@ jobs:
|
||||
npx -y firebase-tools@latest target:clear --project ${{env.PREVIEW_PROJECT}} hosting aio
|
||||
npx -y firebase-tools@latest target:apply --project ${{env.PREVIEW_PROJECT}} hosting aio ${{env.PREVIEW_SITE}}
|
||||
|
||||
- uses: angular/dev-infra/github-actions/previews/upload-artifacts-to-firebase@f89362d698858f9674463e48fde514b097a15838
|
||||
- uses: angular/dev-infra/github-actions/previews/upload-artifacts-to-firebase@c83e99a12397014162531ca125c94549db55dd84
|
||||
with:
|
||||
github-token: '${{secrets.GITHUB_TOKEN}}'
|
||||
workflow-artifact-name: 'aio'
|
||||
|
||||
@@ -16,6 +16,6 @@ jobs:
|
||||
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: angular/dev-infra/github-actions/branch-manager@f89362d698858f9674463e48fde514b097a15838
|
||||
- uses: angular/dev-infra/github-actions/branch-manager@c83e99a12397014162531ca125c94549db55dd84
|
||||
with:
|
||||
angular-robot-key: ${{ secrets.ANGULAR_ROBOT_PRIVATE_KEY }}
|
||||
|
||||
@@ -38,7 +38,7 @@ jobs:
|
||||
|
||||
- uses: ./.github/actions/yarn-install
|
||||
|
||||
- uses: angular/dev-infra/github-actions/bazel/configure-remote@f89362d698858f9674463e48fde514b097a15838
|
||||
- uses: angular/dev-infra/github-actions/bazel/configure-remote@c83e99a12397014162531ca125c94549db55dd84
|
||||
with:
|
||||
bazelrc: ./.bazelrc.user
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ jobs:
|
||||
SAUCE_ACCESS_KEY: ${{ secrets.SAUCE_ACCESS_KEY }}
|
||||
steps:
|
||||
- name: Initialize environment
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@f89362d698858f9674463e48fde514b097a15838
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@c83e99a12397014162531ca125c94549db55dd84
|
||||
with:
|
||||
cache-node-modules: true
|
||||
# Checking out the pull request commit is intended here as we need to run the changed code tests.
|
||||
@@ -31,7 +31,7 @@ jobs:
|
||||
- name: Install node modules
|
||||
run: yarn install --frozen-lockfile
|
||||
- name: Setup Bazel
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@f89362d698858f9674463e48fde514b097a15838
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@c83e99a12397014162531ca125c94549db55dd84
|
||||
- name: Starting Saucelabs tunnel service
|
||||
run: ./tools/saucelabs/sauce-service.sh run &
|
||||
# Build test fixtures for a test that rely on Bazel-generated fixtures. Note that disabling
|
||||
|
||||
+28
-28
@@ -23,7 +23,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Initialize environment
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@f89362d698858f9674463e48fde514b097a15838
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@c83e99a12397014162531ca125c94549db55dd84
|
||||
with:
|
||||
cache-node-modules: true
|
||||
node-module-directories: |
|
||||
@@ -60,13 +60,13 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Initialize environment
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@f89362d698858f9674463e48fde514b097a15838
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@c83e99a12397014162531ca125c94549db55dd84
|
||||
with:
|
||||
cache-node-modules: true
|
||||
- name: Setup Bazel
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@f89362d698858f9674463e48fde514b097a15838
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@c83e99a12397014162531ca125c94549db55dd84
|
||||
- name: Setup Bazel RBE
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@f89362d698858f9674463e48fde514b097a15838
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@c83e99a12397014162531ca125c94549db55dd84
|
||||
- name: Install node modules
|
||||
run: yarn install --frozen-lockfile
|
||||
- name: Run unit tests
|
||||
@@ -78,13 +78,13 @@ jobs:
|
||||
runs-on: ubuntu-latest-4core
|
||||
steps:
|
||||
- name: Initialize environment
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@f89362d698858f9674463e48fde514b097a15838
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@c83e99a12397014162531ca125c94549db55dd84
|
||||
with:
|
||||
cache-node-modules: true
|
||||
- name: Setup Bazel
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@f89362d698858f9674463e48fde514b097a15838
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@c83e99a12397014162531ca125c94549db55dd84
|
||||
- name: Setup Bazel Remote Caching
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@f89362d698858f9674463e48fde514b097a15838
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@c83e99a12397014162531ca125c94549db55dd84
|
||||
- name: Install node modules
|
||||
run: yarn install --frozen-lockfile --network-timeout 100000
|
||||
- name: Run CI tests for framework
|
||||
@@ -94,15 +94,15 @@ jobs:
|
||||
runs-on: ubuntu-latest-4core
|
||||
steps:
|
||||
- name: Initialize environment
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@f89362d698858f9674463e48fde514b097a15838
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@c83e99a12397014162531ca125c94549db55dd84
|
||||
with:
|
||||
cache-node-modules: true
|
||||
node-module-directories: |
|
||||
./aio/node_modules
|
||||
- name: Setup Bazel
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@f89362d698858f9674463e48fde514b097a15838
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@c83e99a12397014162531ca125c94549db55dd84
|
||||
- name: Setup Bazel RBE
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@f89362d698858f9674463e48fde514b097a15838
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@c83e99a12397014162531ca125c94549db55dd84
|
||||
- name: Install node modules for aio
|
||||
run: yarn install --cwd aio --frozen-lockfile
|
||||
- name: Run AIO tests with upstream packages
|
||||
@@ -115,11 +115,11 @@ jobs:
|
||||
labels: ubuntu-latest-4core
|
||||
steps:
|
||||
- name: Initialize environment
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@f89362d698858f9674463e48fde514b097a15838
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@c83e99a12397014162531ca125c94549db55dd84
|
||||
- name: Setup Bazel
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@f89362d698858f9674463e48fde514b097a15838
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@c83e99a12397014162531ca125c94549db55dd84
|
||||
- name: Setup Bazel RBE
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@f89362d698858f9674463e48fde514b097a15838
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@c83e99a12397014162531ca125c94549db55dd84
|
||||
- name: Install node modules
|
||||
run: yarn install --frozen-lockfile
|
||||
- name: Build adev to ensure it continues to work
|
||||
@@ -130,15 +130,15 @@ jobs:
|
||||
labels: ubuntu-latest-4core
|
||||
steps:
|
||||
- name: Initialize environment
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@f89362d698858f9674463e48fde514b097a15838
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@c83e99a12397014162531ca125c94549db55dd84
|
||||
with:
|
||||
cache-node-modules: true
|
||||
node-module-directories: |
|
||||
./aio/node_modules
|
||||
- name: Setup Bazel
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@f89362d698858f9674463e48fde514b097a15838
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@c83e99a12397014162531ca125c94549db55dd84
|
||||
- name: Setup Bazel RBE
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@f89362d698858f9674463e48fde514b097a15838
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@c83e99a12397014162531ca125c94549db55dd84
|
||||
- name: Install node modules
|
||||
run: yarn install --cwd aio --frozen-lockfile
|
||||
- name: Run AIO tests with local packages
|
||||
@@ -153,15 +153,15 @@ jobs:
|
||||
labels: ubuntu-latest
|
||||
steps:
|
||||
- name: Initialize environment
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@f89362d698858f9674463e48fde514b097a15838
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@c83e99a12397014162531ca125c94549db55dd84
|
||||
with:
|
||||
cache-node-modules: true
|
||||
node-module-directories: |
|
||||
./aio/node_modules
|
||||
- name: Setup Bazel
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@f89362d698858f9674463e48fde514b097a15838
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@c83e99a12397014162531ca125c94549db55dd84
|
||||
- name: Setup Bazel RBE
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@f89362d698858f9674463e48fde514b097a15838
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@c83e99a12397014162531ca125c94549db55dd84
|
||||
- name: Install node modules for aio
|
||||
run: yarn install --cwd aio --frozen-lockfile
|
||||
- name: Set the stable branch environment variable
|
||||
@@ -197,13 +197,13 @@ jobs:
|
||||
labels: ubuntu-latest
|
||||
steps:
|
||||
- name: Initialize environment
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@f89362d698858f9674463e48fde514b097a15838
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@c83e99a12397014162531ca125c94549db55dd84
|
||||
with:
|
||||
cache-node-modules: true
|
||||
- name: Setup Bazel
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@f89362d698858f9674463e48fde514b097a15838
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@c83e99a12397014162531ca125c94549db55dd84
|
||||
- name: Setup Bazel RBE
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@f89362d698858f9674463e48fde514b097a15838
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@c83e99a12397014162531ca125c94549db55dd84
|
||||
- name: Install node modules
|
||||
run: yarn install --frozen-lockfile
|
||||
- run: echo "https://${{secrets.SNAPSHOT_BUILDS_GITHUB_TOKEN}}:@github.com" > ${HOME}/.git_credentials
|
||||
@@ -215,7 +215,7 @@ jobs:
|
||||
labels: ubuntu-latest-4core
|
||||
steps:
|
||||
- name: Initialize environment
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@f89362d698858f9674463e48fde514b097a15838
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@c83e99a12397014162531ca125c94549db55dd84
|
||||
with:
|
||||
cache-node-modules: true
|
||||
node-module-directories: |
|
||||
@@ -223,9 +223,9 @@ jobs:
|
||||
./packages/zone.js/node_modules
|
||||
./packages/zone.js/test/typings/node_modules
|
||||
- name: Setup Bazel
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@f89362d698858f9674463e48fde514b097a15838
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@c83e99a12397014162531ca125c94549db55dd84
|
||||
- name: Setup Bazel RBE
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@f89362d698858f9674463e48fde514b097a15838
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@c83e99a12397014162531ca125c94549db55dd84
|
||||
- name: Install node modules
|
||||
run: yarn install --frozen-lockfile
|
||||
- run: |
|
||||
@@ -264,7 +264,7 @@ jobs:
|
||||
JOBS: 2
|
||||
steps:
|
||||
- name: Initialize environment
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@f89362d698858f9674463e48fde514b097a15838
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@c83e99a12397014162531ca125c94549db55dd84
|
||||
with:
|
||||
cache-node-modules: true
|
||||
# Checking out the pull request commit is intended here as we need to run the changed code tests.
|
||||
@@ -272,9 +272,9 @@ jobs:
|
||||
- name: Install node modules
|
||||
run: yarn install --frozen-lockfile
|
||||
- name: Setup Bazel
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@f89362d698858f9674463e48fde514b097a15838
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@c83e99a12397014162531ca125c94549db55dd84
|
||||
- name: Setup Bazel Remote Caching
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@f89362d698858f9674463e48fde514b097a15838
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@c83e99a12397014162531ca125c94549db55dd84
|
||||
- name: Set up Sauce Tunnel Daemon
|
||||
run: yarn bazel run //tools/saucelabs-daemon/background-service -- $JOBS &
|
||||
env:
|
||||
|
||||
@@ -13,13 +13,13 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||
- uses: angular/dev-infra/github-actions/commit-message-based-labels@f89362d698858f9674463e48fde514b097a15838
|
||||
- uses: angular/dev-infra/github-actions/commit-message-based-labels@c83e99a12397014162531ca125c94549db55dd84
|
||||
with:
|
||||
angular-robot-key: ${{ secrets.ANGULAR_ROBOT_PRIVATE_KEY }}
|
||||
post_approval_changes:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||
- uses: angular/dev-infra/github-actions/post-approval-changes@f89362d698858f9674463e48fde514b097a15838
|
||||
- uses: angular/dev-infra/github-actions/post-approval-changes@c83e99a12397014162531ca125c94549db55dd84
|
||||
with:
|
||||
angular-robot-key: ${{ secrets.ANGULAR_ROBOT_PRIVATE_KEY }}
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
name: Feature request triage bot
|
||||
|
||||
on:
|
||||
schedule:
|
||||
# Run at 14:00 every day
|
||||
- cron: '0 14 * * *'
|
||||
|
||||
# Declare default permissions as read only.
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
feature_triage:
|
||||
if: github.repository == 'angular/angular'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: angular/dev-infra/github-actions/feature-request@f89362d698858f9674463e48fde514b097a15838
|
||||
with:
|
||||
angular-robot-key: ${{ secrets.ANGULAR_ROBOT_PRIVATE_KEY }}
|
||||
@@ -14,7 +14,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||
- uses: angular/dev-infra/github-actions/google-internal-tests@f89362d698858f9674463e48fde514b097a15838
|
||||
- uses: angular/dev-infra/github-actions/google-internal-tests@c83e99a12397014162531ca125c94549db55dd84
|
||||
with:
|
||||
run-tests-guide-url: http://go/angular-g3sync-start
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
@@ -9,6 +9,6 @@ jobs:
|
||||
status:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: angular/dev-infra/github-actions/unified-status-check@f89362d698858f9674463e48fde514b097a15838
|
||||
- uses: angular/dev-infra/github-actions/unified-status-check@c83e99a12397014162531ca125c94549db55dd84
|
||||
with:
|
||||
angular-robot-key: ${{ secrets.ANGULAR_ROBOT_PRIVATE_KEY }}
|
||||
|
||||
@@ -22,15 +22,15 @@ jobs:
|
||||
version: ['https://next.angular.io/', 'https://angular.io/']
|
||||
steps:
|
||||
- name: Initialize environment
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@f89362d698858f9674463e48fde514b097a15838
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@c83e99a12397014162531ca125c94549db55dd84
|
||||
with:
|
||||
cache-node-modules: true
|
||||
node-module-directories: |
|
||||
./aio/node_modules
|
||||
- name: Setup Bazel
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@f89362d698858f9674463e48fde514b097a15838
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@c83e99a12397014162531ca125c94549db55dd84
|
||||
- name: Setup Bazel RBE
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@f89362d698858f9674463e48fde514b097a15838
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@c83e99a12397014162531ca125c94549db55dd84
|
||||
- name: Install node modules in aio
|
||||
run: yarn install --frozen-lockfile --cwd aio
|
||||
- name: Run basic e2e and deployment config tests.
|
||||
@@ -44,7 +44,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Initialize environment
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@f89362d698858f9674463e48fde514b097a15838
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@c83e99a12397014162531ca125c94549db55dd84
|
||||
with:
|
||||
cache-node-modules: true
|
||||
node-module-directories: |
|
||||
|
||||
@@ -39,7 +39,7 @@ jobs:
|
||||
|
||||
# Upload the results as artifacts.
|
||||
- name: 'Upload artifact'
|
||||
uses: actions/upload-artifact@c7d193f32edcb7bfad88892161225aeda64e9392 # v4.0.0
|
||||
uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1
|
||||
with:
|
||||
name: SARIF file
|
||||
path: results.sarif
|
||||
@@ -47,6 +47,6 @@ jobs:
|
||||
|
||||
# Upload the results to GitHub's code scanning dashboard.
|
||||
- name: 'Upload to code-scanning'
|
||||
uses: github/codeql-action/upload-sarif@b374143c1149a9115d881581d29b8390bbcbb59c # v3.22.11
|
||||
uses: github/codeql-action/upload-sarif@47b3d888fe66b639e431abf22ebca059152f1eea # v3.24.5
|
||||
with:
|
||||
sarif_file: results.sarif
|
||||
|
||||
@@ -32,7 +32,7 @@ jobs:
|
||||
env:
|
||||
ANGULAR_CLI_BUILDS_READONLY_GITHUB_TOKEN: ${{ secrets.ANGULAR_CLI_BUILDS_READONLY_GITHUB_TOKEN }}
|
||||
- name: Create a PR (if necessary)
|
||||
uses: angular/dev-infra/github-actions/create-pr-for-changes@f89362d698858f9674463e48fde514b097a15838
|
||||
uses: angular/dev-infra/github-actions/create-pr-for-changes@c83e99a12397014162531ca125c94549db55dd84
|
||||
with:
|
||||
branch-prefix: update-cli-help
|
||||
pr-title: 'docs: update Angular CLI help [${{github.ref_name}}]'
|
||||
|
||||
@@ -35,7 +35,7 @@ jobs:
|
||||
- name: Generate `events.json`
|
||||
run: node aio/scripts/generate-events/index.mjs --ignore-invalid-dates
|
||||
- name: Create a PR (if necessary)
|
||||
uses: angular/dev-infra/github-actions/create-pr-for-changes@f89362d698858f9674463e48fde514b097a15838
|
||||
uses: angular/dev-infra/github-actions/create-pr-for-changes@c83e99a12397014162531ca125c94549db55dd84
|
||||
with:
|
||||
branch-prefix: docs-update-events
|
||||
pr-title: 'docs: update events'
|
||||
|
||||
+3
-1
@@ -20,6 +20,7 @@ export const format: FormatConfig = {
|
||||
'packages/examples/**/*.{js,ts}',
|
||||
'packages/misc/**/*.{js,ts}',
|
||||
'packages/private/**/*.{js,ts}',
|
||||
'packages/router/**/*.{js,ts}',
|
||||
'packages/service-worker/**/*.{js,ts}',
|
||||
'packages/upgrade/**/*.{js,ts}',
|
||||
|
||||
@@ -33,7 +34,7 @@ export const format: FormatConfig = {
|
||||
},
|
||||
'clang-format': {
|
||||
'matchers': [
|
||||
//'**/*.{js,ts}',
|
||||
'**/*.{js,ts}',
|
||||
// TODO: burn down format failures and remove aio and integration exceptions.
|
||||
'!aio/**',
|
||||
'!integration/**',
|
||||
@@ -70,6 +71,7 @@ export const format: FormatConfig = {
|
||||
'!packages/examples/**/*.{js,ts}',
|
||||
'!packages/misc/**/*.{js,ts}',
|
||||
'!packages/private/**/*.{js,ts}',
|
||||
'!packages/router/**/*.{js,ts}',
|
||||
'!packages/service-worker/**/*.{js,ts}',
|
||||
'!packages/upgrade/**/*.{js,ts}',
|
||||
],
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
{
|
||||
"syncedFilePatterns": ["LICENSE", "modules/benchmarks/**", "packages/**"],
|
||||
"syncedFilePatterns": [
|
||||
"LICENSE",
|
||||
"modules/benchmarks/**",
|
||||
"packages/**"
|
||||
],
|
||||
"separateFilePatterns": [
|
||||
"packages/core/primitives/**"
|
||||
],
|
||||
"alwaysExternalFilePatterns": [
|
||||
"packages/*",
|
||||
"packages/bazel/*",
|
||||
|
||||
@@ -17,4 +17,9 @@ export const pullRequest: PullRequestConfig = {
|
||||
// the `bazel` package is not considered part of the public API so that features
|
||||
// can land in patch branches.
|
||||
targetLabelExemptScopes: ['dev-infra', 'docs-infra', 'bazel'],
|
||||
// enables specific validations during the pull request merge process
|
||||
validators: {
|
||||
assertEnforceTested: true,
|
||||
assertIsolatedSeparateFiles: true,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
"printWidth": 100,
|
||||
"tabWidth": 2,
|
||||
"tabs": false,
|
||||
"embeddedLanguageFormatting": "off",
|
||||
"singleQuote": true,
|
||||
"semicolon": true,
|
||||
"quoteProps": "preserve",
|
||||
|
||||
@@ -322,7 +322,9 @@ groups:
|
||||
'aio/content/guide/providers.md',
|
||||
'aio/content/examples/providers/**/{*,.*}',
|
||||
'aio/content/images/guide/providers/**/{*,.*}',
|
||||
'aio/content/guide/signal-queries.md',
|
||||
'aio/content/guide/singleton-services.md',
|
||||
'aio/content/guide/signal-inputs.md',
|
||||
'aio/content/guide/sharing-ngmodules.md',
|
||||
'aio/content/guide/standalone-components.md',
|
||||
'aio/content/guide/standalone-migration.md',
|
||||
@@ -345,6 +347,7 @@ groups:
|
||||
'aio/content/guide/defer.md',
|
||||
'aio/content/guide/signals.md',
|
||||
'aio/content/guide/control_flow.md',
|
||||
'aio/content/guide/model-inputs.md',
|
||||
'aio/content/examples/injection-token/**/{*,.*}',
|
||||
])
|
||||
reviewers:
|
||||
@@ -936,6 +939,7 @@ groups:
|
||||
'aio/content/guide/update-to-version-14.md',
|
||||
'aio/content/guide/update-to-version-15.md',
|
||||
'aio/content/guide/update-to-version-16.md',
|
||||
'aio/content/guide/update-to-version-17.md',
|
||||
])
|
||||
reviewers:
|
||||
users:
|
||||
@@ -1081,6 +1085,7 @@ groups:
|
||||
- crisbeto
|
||||
- atscott
|
||||
- pkozlowski-opensource
|
||||
- ~jeanmeche
|
||||
|
||||
# =========================================================
|
||||
# Docs-infra
|
||||
|
||||
+189
-112
@@ -1,3 +1,130 @@
|
||||
<a name="17.2.4"></a>
|
||||
# 17.2.4 (2024-03-06)
|
||||
### compiler-cli
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [917b9bdd2e](https://github.com/angular/angular/commit/917b9bdd2eec9cd6395adb0cbde979490bcc3a88) | fix | unwrap expressions with type parameters in query read property ([#54647](https://github.com/angular/angular/pull/54647)) |
|
||||
### core
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [586cc24a10](https://github.com/angular/angular/commit/586cc24a102d51e7c42ecc839124353afe459073) | fix | apply TestBed provider overrides to `@defer` dependencies ([#54667](https://github.com/angular/angular/pull/54667)) |
|
||||
| [b558a01c84](https://github.com/angular/angular/commit/b558a01c84a1fe4f9e047e92d99d69f02f2ccc4f) | fix | generic inference for signal inputs may break with `--strictFunctionTypes` ([#54652](https://github.com/angular/angular/pull/54652)) |
|
||||
| [443e5f1591](https://github.com/angular/angular/commit/443e5f1591896909eab6ad157bb4b5db50a49f26) | fix | return a readonly signal on `asReadonly`. ([#54719](https://github.com/angular/angular/pull/54719)) |
|
||||
| [ffbafc7d4a](https://github.com/angular/angular/commit/ffbafc7d4a3c61226cb5c524b3ff42ed4f9f1d76) | fix | untrack various core operations ([#54614](https://github.com/angular/angular/pull/54614)) |
|
||||
|
||||
<!-- CHANGELOG SPLIT MARKER -->
|
||||
|
||||
<a name="17.2.3"></a>
|
||||
# 17.2.3 (2024-02-27)
|
||||
### common
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [1a526f2881](https://github.com/angular/angular/commit/1a526f28814429c05b003edbe40500b7069e1404) | perf | `AsyncPipe` should not call `markForCheck` on subscription ([#54554](https://github.com/angular/angular/pull/54554)) |
|
||||
### compiler-cli
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [2aefed8763](https://github.com/angular/angular/commit/2aefed87632293c5570e17653a7d270162ea35c3) | fix | catch function instance properties in interpolated signal diagnostic ([#54325](https://github.com/angular/angular/pull/54325)) |
|
||||
| [48aec63ee4](https://github.com/angular/angular/commit/48aec63ee48fe9d20c1f1565b044ec359100736d) | fix | identify aliased initializer functions ([#54480](https://github.com/angular/angular/pull/54480)) |
|
||||
| [daf7c611b2](https://github.com/angular/angular/commit/daf7c611b23797ba2dba24e20e26c344902e7bc7) | fix | identify aliased initializer functions ([#54609](https://github.com/angular/angular/pull/54609)) |
|
||||
### core
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [57123524a2](https://github.com/angular/angular/commit/57123524a2e1481987eaf239d2ae7f1216291864) | fix | collect providers from NgModules while rendering `@defer` block ([#52881](https://github.com/angular/angular/pull/52881)) |
|
||||
| [79a32816dc](https://github.com/angular/angular/commit/79a32816dcb57e9b02ba6e18261c6dccd14b0b23) | fix | fix typo in injectors.svg file ([#54596](https://github.com/angular/angular/pull/54596)) |
|
||||
### migrations
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [dbe673b027](https://github.com/angular/angular/commit/dbe673b02751b8bf12f6f6f79b843dd268d965e1) | fix | resolve infinite loop for a single line element with a long tag name and angle bracket on a new line ([#54588](https://github.com/angular/angular/pull/54588)) |
|
||||
|
||||
<!-- CHANGELOG SPLIT MARKER -->
|
||||
|
||||
<a name="17.2.2"></a>
|
||||
# 17.2.2 (2024-02-21)
|
||||
### common
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [d34e3298db](https://github.com/angular/angular/commit/d34e3298db2da9d3aa8f8e2c49e1d1aed849e7d2) | fix | image placeholder not removed in OnPush component ([#54515](https://github.com/angular/angular/pull/54515)) |
|
||||
### compiler
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [6447c0eecc](https://github.com/angular/angular/commit/6447c0eecc15800417bb5bde2d273865c559b7d4) | fix | adding the inert property to the "SCHEMA" array ([#53148](https://github.com/angular/angular/pull/53148)) |
|
||||
### compiler-cli
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [0a3edfb543](https://github.com/angular/angular/commit/0a3edfb5433e1b67f37d7b3f69a5c77b48eedce6) | fix | correctly detect deferred dependencies across scoped nodes ([#54499](https://github.com/angular/angular/pull/54499)) |
|
||||
| [790f4f7c26](https://github.com/angular/angular/commit/790f4f7c26474af2b1d81a4f3c761047bf920edf) | fix | use correct symbol name for default imported symbols in defer blocks ([#54495](https://github.com/angular/angular/pull/54495)) |
|
||||
### core
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [3bd5860c74](https://github.com/angular/angular/commit/3bd5860c749517c4d35850703c53a768138b7bde) | fix | properly execute content queries for root components ([#54457](https://github.com/angular/angular/pull/54457)) |
|
||||
### migrations
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [bb57d34110](https://github.com/angular/angular/commit/bb57d34110ea8796f5c5f522b98dadf078e8ad8a) | fix | Fix cf migration regular expression to include underscores ([#54533](https://github.com/angular/angular/pull/54533)) |
|
||||
### router
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [3e31f1a34e](https://github.com/angular/angular/commit/3e31f1a34ee97a802a4032cd9e7816d683a51d5e) | fix | Clear internal transition when navigation finalizes ([#54261](https://github.com/angular/angular/pull/54261)) |
|
||||
|
||||
<!-- CHANGELOG SPLIT MARKER -->
|
||||
|
||||
<a name="17.2.1"></a>
|
||||
# 17.2.1 (2024-02-14)
|
||||
### compiler-cli
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [7234824228](https://github.com/angular/angular/commit/7234824228df11249a5ebe01a6dee381be74e02e) | fix | fix broken version detection condition ([#54443](https://github.com/angular/angular/pull/54443)) |
|
||||
|
||||
<!-- CHANGELOG SPLIT MARKER -->
|
||||
|
||||
<a name="17.2.0"></a>
|
||||
# 17.2.0 (2024-02-14)
|
||||
### common
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [03c3b3eb79](https://github.com/angular/angular/commit/03c3b3eb79ec061b0031d6ad7ba386d185c87d8d) | feat | add Netlify image loader ([#54311](https://github.com/angular/angular/pull/54311)) |
|
||||
| [f5c520b836](https://github.com/angular/angular/commit/f5c520b836c4545c7043649f28b3a0369c168747) | feat | add placeholder to NgOptimizedImage ([#53783](https://github.com/angular/angular/pull/53783)) |
|
||||
### compiler
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [47e6e84101](https://github.com/angular/angular/commit/47e6e841016abfca0c1aa84051d82a04b3027617) | feat | Add a TSConfig option `useTemplatePipeline` ([#54057](https://github.com/angular/angular/pull/54057)) |
|
||||
| [66e940aebf](https://github.com/angular/angular/commit/66e940aebfd5a93944860a4e0dbd14e1072f80f2) | feat | scope selectors in @starting-style ([#53943](https://github.com/angular/angular/pull/53943)) |
|
||||
| [7b4d275f49](https://github.com/angular/angular/commit/7b4d275f494a64c38b61cea7045ba8b6e8447b78) | fix | Fix the template pipeline option ([#54148](https://github.com/angular/angular/pull/54148)) |
|
||||
### compiler-cli
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [7e861c640e](https://github.com/angular/angular/commit/7e861c640edf90c5f8d4f7e091861d3d98cd49c0) | feat | generate extra imports for component local dependencies in local mode ([#53543](https://github.com/angular/angular/pull/53543)) |
|
||||
| [3263df23f2](https://github.com/angular/angular/commit/3263df23f2f4da722ef2c1a1dacfb0866498dd60) | feat | generate global imports in local compilation mode ([#53543](https://github.com/angular/angular/pull/53543)) |
|
||||
| [b774e22d8e](https://github.com/angular/angular/commit/b774e22d8e384f43e9cd8f5c55475d06e7f66988) | feat | make it configurable to generate alias reexports ([#53937](https://github.com/angular/angular/pull/53937)) |
|
||||
| [3e1384048e](https://github.com/angular/angular/commit/3e1384048eb76c92532ae19aa2883318121c00e8) | feat | support host directives for local compilation mode ([#53877](https://github.com/angular/angular/pull/53877)) |
|
||||
| [a592904c69](https://github.com/angular/angular/commit/a592904c691844d2c1aed00bd914edabef49f9b1) | fix | allow custom/duplicate decorators for `@Injectable` classes in local compilation mode ([#54139](https://github.com/angular/angular/pull/54139)) |
|
||||
| [4b1d948b36](https://github.com/angular/angular/commit/4b1d948b36285ec6d80dbe93e0b92133f9d4be94) | fix | consider the case of duplicate Angular decorators in local compilation diagnostics ([#54139](https://github.com/angular/angular/pull/54139)) |
|
||||
| [96bcf4fb12](https://github.com/angular/angular/commit/96bcf4fb1208d1f073784a2cde4a03553e905807) | fix | forbid custom/duplicate decorator when option `forbidOrphanComponents` is set ([#54139](https://github.com/angular/angular/pull/54139)) |
|
||||
| [64fa5715c6](https://github.com/angular/angular/commit/64fa5715c696101fba0b4f8623eaec0eadc5b159) | fix | generating extra imports in local compilation mode when cycle is introduced ([#53543](https://github.com/angular/angular/pull/53543)) |
|
||||
| [6c8b09468a](https://github.com/angular/angular/commit/6c8b09468a05a80cba3960861f0ab8d3bae80415) | fix | highlight the unresolved element in the @Component.styles array for the error LOCAL_COMPILATION_UNRESOLVED_CONST ([#54230](https://github.com/angular/angular/pull/54230)) |
|
||||
| [0970129e20](https://github.com/angular/angular/commit/0970129e20f77dc309f2b4f76f961b310124778c) | fix | show proper error for custom decorators in local compilation mode ([#53983](https://github.com/angular/angular/pull/53983)) |
|
||||
| [f39cb06418](https://github.com/angular/angular/commit/f39cb064183d984254bdf4e41b61d3dc9379738a) | fix | show specific error for unresolved @Directive.exportAs in local compilation mode ([#54230](https://github.com/angular/angular/pull/54230)) |
|
||||
| [f3851b5945](https://github.com/angular/angular/commit/f3851b59459a1d9c214ace3db5a716d51c1f93c7) | fix | show specific error for unresolved @HostBinding's argument in local compilation mode ([#54230](https://github.com/angular/angular/pull/54230)) |
|
||||
| [39ddd884e8](https://github.com/angular/angular/commit/39ddd884e826cc0be63fd0f7d7de20d642877ef9) | fix | show specific error for unresolved @HostListener's event name in local compilation mode ([#54230](https://github.com/angular/angular/pull/54230)) |
|
||||
| [5d633240fd](https://github.com/angular/angular/commit/5d633240fd5927c4318a9240e60c3a30b2333cee) | fix | show the correct message for the error LOCAL_COMPILATION_UNRESOLVED_CONST when an unresolved symbol used for @Component.styles ([#54230](https://github.com/angular/angular/pull/54230)) |
|
||||
| [58b8a232d6](https://github.com/angular/angular/commit/58b8a232d64f5fe3207c90c8145cab36e7e192c2) | fix | support jumping to definitions of signal-based inputs ([#54053](https://github.com/angular/angular/pull/54053)) |
|
||||
### core
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [702ab28b4c](https://github.com/angular/angular/commit/702ab28b4c07a903c403a20af2ca287348b6afd0) | feat | add support for model inputs ([#54252](https://github.com/angular/angular/pull/54252)) |
|
||||
| [e95ef2cbc6](https://github.com/angular/angular/commit/e95ef2cbc6f850d8fe96218b74cff76cea947674) | feat | expose queries as signals ([#54283](https://github.com/angular/angular/pull/54283)) |
|
||||
| [656bc282e3](https://github.com/angular/angular/commit/656bc282e345c5e37a9189a0a4daa631e02c31bf) | fix | add toString implementation to signals ([#54002](https://github.com/angular/angular/pull/54002)) |
|
||||
| [62b87b4551](https://github.com/angular/angular/commit/62b87b4551d77815f58af152d1921de3733621ba) | fix | do not crash for signal query that does not have any matches ([#54353](https://github.com/angular/angular/pull/54353)) |
|
||||
| [4b96f370ee](https://github.com/angular/angular/commit/4b96f370eea08d2531cc54f65a651f94b504692d) | fix | expose model signal subcribe for type checking purposes ([#54357](https://github.com/angular/angular/pull/54357)) |
|
||||
| [744cb1e561](https://github.com/angular/angular/commit/744cb1e561d9b0cbffd20ad612d5ea9e2cf6b2e7) | fix | return the same children query results if there are no changes ([#54392](https://github.com/angular/angular/pull/54392)) |
|
||||
| [6d00115bf4](https://github.com/angular/angular/commit/6d00115bf45731b80178e11921df6ec1dc89efaf) | fix | show placeholder block on the server with immediate trigger ([#54394](https://github.com/angular/angular/pull/54394)) |
|
||||
### http
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [1c536250b6](https://github.com/angular/angular/commit/1c536250b6112c67a68159de50c4afe0dbc440f8) | fix | Use string body to generate transfer cache key. ([#54379](https://github.com/angular/angular/pull/54379)) |
|
||||
|
||||
<!-- CHANGELOG SPLIT MARKER -->
|
||||
|
||||
<a name="17.1.3"></a>
|
||||
# 17.1.3 (2024-02-08)
|
||||
### compiler-cli
|
||||
@@ -21,50 +148,9 @@
|
||||
|
||||
<!-- CHANGELOG SPLIT MARKER -->
|
||||
|
||||
<a name="17.2.0-next.1"></a>
|
||||
# 17.2.0-next.1 (2024-01-31)
|
||||
###
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [dfc6c8d0c7](https://github.com/angular/angular/commit/dfc6c8d0c76380ee7beb6d904261e40857b375ed) | fix | cta clickability issue in adev homepage. ([#52905](https://github.com/angular/angular/pull/52905)) |
|
||||
### animations
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [75aeae42b7](https://github.com/angular/angular/commit/75aeae42b7f512553262d515966f43d11e34d228) | fix | cleanup DOM elements when root view is removed with async animations ([#53033](https://github.com/angular/angular/pull/53033)) |
|
||||
### common
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [f5c520b836](https://github.com/angular/angular/commit/f5c520b836c4545c7043649f28b3a0369c168747) | feat | add placeholder to NgOptimizedImage ([#53783](https://github.com/angular/angular/pull/53783)) |
|
||||
| [122213d37d](https://github.com/angular/angular/commit/122213d37d3e73fc0dcfd5a10a2c388dc573b6cf) | fix | The date pipe should return ISO format for week and week-year as intended in the unit test. ([#53879](https://github.com/angular/angular/pull/53879)) |
|
||||
### compiler
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [47e6e84101](https://github.com/angular/angular/commit/47e6e841016abfca0c1aa84051d82a04b3027617) | feat | Add a TSConfig option `useTemplatePipeline` ([#54057](https://github.com/angular/angular/pull/54057)) |
|
||||
| [7b4d275f49](https://github.com/angular/angular/commit/7b4d275f494a64c38b61cea7045ba8b6e8447b78) | fix | Fix the template pipeline option ([#54148](https://github.com/angular/angular/pull/54148)) |
|
||||
| [eddf5dae5e](https://github.com/angular/angular/commit/eddf5dae5eb9e1aa3ca4c276c2cb2b897b73a9e0) | fix | Update type check block to fix control flow source mappings ([#53980](https://github.com/angular/angular/pull/53980)) |
|
||||
### compiler-cli
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [7e861c640e](https://github.com/angular/angular/commit/7e861c640edf90c5f8d4f7e091861d3d98cd49c0) | feat | generate extra imports for component local dependencies in local mode ([#53543](https://github.com/angular/angular/pull/53543)) |
|
||||
| [3263df23f2](https://github.com/angular/angular/commit/3263df23f2f4da722ef2c1a1dacfb0866498dd60) | feat | generate global imports in local compilation mode ([#53543](https://github.com/angular/angular/pull/53543)) |
|
||||
| [64fa5715c6](https://github.com/angular/angular/commit/64fa5715c696101fba0b4f8623eaec0eadc5b159) | fix | generating extra imports in local compilation mode when cycle is introduced ([#53543](https://github.com/angular/angular/pull/53543)) |
|
||||
| [0970129e20](https://github.com/angular/angular/commit/0970129e20f77dc309f2b4f76f961b310124778c) | fix | show proper error for custom decorators in local compilation mode ([#53983](https://github.com/angular/angular/pull/53983)) |
|
||||
| [58b8a232d6](https://github.com/angular/angular/commit/58b8a232d64f5fe3207c90c8145cab36e7e192c2) | fix | support jumping to definitions of signal-based inputs ([#54053](https://github.com/angular/angular/pull/54053)) |
|
||||
### core
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [656bc282e3](https://github.com/angular/angular/commit/656bc282e345c5e37a9189a0a4daa631e02c31bf) | fix | add toString implementation to signals ([#54002](https://github.com/angular/angular/pull/54002)) |
|
||||
| [037b79b72e](https://github.com/angular/angular/commit/037b79b72ea18f08b3a74f9ad541bbdca183b1aa) | fix | change defer block fixture default behavior to playthrough ([#54088](https://github.com/angular/angular/pull/54088)) |
|
||||
### migrations
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [28ad6fc4ad](https://github.com/angular/angular/commit/28ad6fc4ad518884bb5777fd20e9075d8969b27a) | fix | error in standalone migration when non-array value is used as declarations in TestBed ([#54122](https://github.com/angular/angular/pull/54122)) |
|
||||
|
||||
<!-- CHANGELOG SPLIT MARKER -->
|
||||
|
||||
<a name="17.1.2"></a>
|
||||
# 17.1.2 (2024-01-31)
|
||||
###
|
||||
###
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [ccddacf11d](https://github.com/angular/angular/commit/ccddacf11deaebeda12e1bdb6e93ec401397d352) | fix | cta clickability issue in adev homepage. ([#52905](https://github.com/angular/angular/pull/52905)) |
|
||||
@@ -91,15 +177,6 @@
|
||||
|
||||
<!-- CHANGELOG SPLIT MARKER -->
|
||||
|
||||
<a name="17.2.0-next.0"></a>
|
||||
# 17.2.0-next.0 (2024-01-24)
|
||||
### compiler
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [66e940aebf](https://github.com/angular/angular/commit/66e940aebfd5a93944860a4e0dbd14e1072f80f2) | feat | scope selectors in @starting-style ([#53943](https://github.com/angular/angular/pull/53943)) |
|
||||
|
||||
<!-- CHANGELOG SPLIT MARKER -->
|
||||
|
||||
<a name="17.1.1"></a>
|
||||
# 17.1.1 (2024-01-24)
|
||||
### router
|
||||
@@ -435,9 +512,9 @@ Live long and prosper 🖖🏻
|
||||
[Blog post "Angular v17 is now available"](http://goo.gle/angular-v17).
|
||||
|
||||
## Breaking Changes
|
||||
###
|
||||
###
|
||||
- Node.js v16 support has been removed and the minimum support version has been bumped to 18.13.0.
|
||||
|
||||
|
||||
Node.js v16 is planned to be End-of-Life on 2023-09-11. Angular will stop supporting Node.js v16 in Angular v17. For Node.js release schedule details, please see: https://github.com/nodejs/release#release-schedule
|
||||
### common
|
||||
- the NgSwitch directive now defaults to the === equality operator,
|
||||
@@ -451,30 +528,30 @@ Live long and prosper 🖖🏻
|
||||
- The `mutate` method was removed from the `WritableSignal` interface and completely
|
||||
dropped from the public API surface. As an alternative, please use the `update` method and
|
||||
make immutable changes to the object.
|
||||
|
||||
|
||||
Example before:
|
||||
|
||||
|
||||
```typescript
|
||||
items.mutate(itemsArray => itemsArray.push(newItem));
|
||||
```
|
||||
|
||||
|
||||
Example after:
|
||||
|
||||
|
||||
```typescript
|
||||
items.update(itemsArray => [itemsArray, …newItem]);
|
||||
```
|
||||
- The `mutate` method was removed from the `WritableSignal` interface and completely
|
||||
dropped from the public API surface. As an alternative please use the update method and
|
||||
make immutable changes to the object.
|
||||
|
||||
|
||||
Example before:
|
||||
|
||||
|
||||
```typescript
|
||||
items.mutate(itemsArray => itemsArray.push(newItem));
|
||||
```
|
||||
|
||||
|
||||
Example after:
|
||||
|
||||
|
||||
```typescript
|
||||
items.update(itemsArray => [itemsArray, …newItem]);
|
||||
```
|
||||
@@ -490,7 +567,7 @@ Live long and prosper 🖖🏻
|
||||
`detectChanges` on that component's `ChangeDetectorRef`.
|
||||
### platform-browser
|
||||
- `REMOVE_STYLES_ON_COMPONENT_DESTROY` default value is now `true`. This causes CSS of components to be removed from the DOM when destroyed. You retain the previous behaviour by providing the `REMOVE_STYLES_ON_COMPONENT_DESTROY` injection token.
|
||||
|
||||
|
||||
```ts
|
||||
import {REMOVE_STYLES_ON_COMPONENT_DESTROY} from '@angular/platform-browser';
|
||||
...
|
||||
@@ -513,13 +590,13 @@ Live long and prosper 🖖🏻
|
||||
This should instead be configured through the provideRouter or RouterModule.forRoot APIs.
|
||||
- The following Router properties have been removed from
|
||||
the public API:
|
||||
|
||||
|
||||
- canceledNavigationResolution
|
||||
- paramsInheritanceStrategy
|
||||
- titleStrategy
|
||||
- urlUpdateStrategy
|
||||
- malformedUriErrorHandler
|
||||
|
||||
|
||||
These should instead be configured through the `provideRouter` or
|
||||
`RouterModule.forRoot` APIs.
|
||||
- The `setupTestingRouter` function has been removed. Use
|
||||
@@ -530,7 +607,7 @@ Live long and prosper 🖖🏻
|
||||
handled in the `UrlSerializer.parse` method.
|
||||
### zone.js
|
||||
- Deep and legacy `dist/` imports like `zone.js/bundles/zone-testing.js` and `zone.js/dist/zone` are no longer allowed. `zone-testing-bundle` and `zone-testing-node-bundle` are also no longer part of the package.
|
||||
|
||||
|
||||
The proper way to import `zone.js` and `zone.js/testing` is:
|
||||
```js
|
||||
import 'zone.js';
|
||||
@@ -541,7 +618,7 @@ Live long and prosper 🖖🏻
|
||||
- The `AnimationDriver.NOOP` symbol is deprecated, use `NoopAnimationDriver` instead.
|
||||
### core
|
||||
- `ChangeDetectorRef.checkNoChanges` is deprecated.
|
||||
|
||||
|
||||
Test code should use `ComponentFixture` instead of `ChangeDetectorRef`.
|
||||
Application code should not call `ChangeDetectorRef.checkNoChanges` directly.
|
||||
- Swapping out the context object for `EmbeddedViewRef`
|
||||
@@ -550,12 +627,12 @@ Live long and prosper 🖖🏻
|
||||
simple assignments in most cases, or `Object.assign` , or alternatively
|
||||
still replace the full object by using a `Proxy` (see `NgTemplateOutlet`
|
||||
as an example).
|
||||
|
||||
|
||||
Also adds a warning if the deprecated
|
||||
- NgProbeToken
|
||||
|
||||
|
||||
The `NgProbeToken` is not used internally since the transition from View Engine to Ivy. The token has no utility and can be removed from applications and libraries.
|
||||
###
|
||||
###
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [59aa0634f4](https://github.com/angular/angular/commit/59aa0634f4d4694203f2a69c40017fe5a3962514) | build | remove support for Node.js v16 ([#51755](https://github.com/angular/angular/pull/51755)) |
|
||||
@@ -1169,7 +1246,7 @@ Live long and prosper 🖖🏻
|
||||
[Blog post "Angular v16 is now available"](https://goo.gle/angular-v16).
|
||||
|
||||
## Breaking Changes
|
||||
###
|
||||
###
|
||||
- Angular Compatibility Compiler (ngcc) has been removed and as a result Angular View Engine libraries will no longer work
|
||||
- Deprecated `EventManager` method `addGlobalEventListener` has been removed as it is not used by Ivy.
|
||||
### bazel
|
||||
@@ -1192,14 +1269,14 @@ Live long and prosper 🖖🏻
|
||||
The ideal fix would be to update the code to instead be compatible with
|
||||
`MockPlatformLocation` instead.
|
||||
- If the 'ngTemplateOutletContext' is different from the context, it will result in a compile-time error.
|
||||
|
||||
|
||||
Before the change, the following template was compiling:
|
||||
|
||||
|
||||
```typescript
|
||||
interface MyContext {
|
||||
$implicit: string;
|
||||
}
|
||||
|
||||
|
||||
@Component({
|
||||
standalone: true,
|
||||
imports: [NgTemplateOutlet],
|
||||
@@ -1217,7 +1294,7 @@ Live long and prosper 🖖🏻
|
||||
}
|
||||
```
|
||||
However, it does not compile now because the 'xxx' property does not exist in 'MyContext', resulting in the error: 'Type '{ $implicit: string; xxx: string; }' is not assignable to type 'MyContext'.'
|
||||
|
||||
|
||||
The solution is either:
|
||||
- add the 'xxx' property to 'MyContext' with the correct type or
|
||||
- add '$any(...)' inside the template to make the error disappear. However, adding '$any(...)' does not correct the error but only preserves the previous behavior of the code.
|
||||
@@ -1236,7 +1313,7 @@ Live long and prosper 🖖🏻
|
||||
value differs from the previous call to `setInput`.
|
||||
- `RendererType2.styles` no longer accepts a nested arrays.
|
||||
- The `APP_ID` token value is no longer randomly generated. If you are bootstrapping multiple application on the same page you will need to set to provide the `APP_ID` yourself.
|
||||
|
||||
|
||||
```ts
|
||||
bootstrapApplication(ComponentA, {
|
||||
providers: [
|
||||
@@ -1247,21 +1324,21 @@ Live long and prosper 🖖🏻
|
||||
```
|
||||
- The `ReflectiveInjector` and related symbols were removed. Please update the code to avoid references to the `ReflectiveInjector` symbol. Use `Injector.create` as a replacement to create an injector instead.
|
||||
- Node.js v14 support has been removed
|
||||
|
||||
|
||||
Node.js v14 is planned to be End-of-Life on 2023-04-30. Angular will stop supporting Node.js v14 in Angular v16. Angular v16 will continue to officially support Node.js versions v16 and v18.
|
||||
### platform-browser
|
||||
- The deprecated `BrowserTransferStateModule` was removed, since it's no longer needed. The `TransferState` class can be injected without providing the module. The `BrowserTransferStateModule` was empty starting from v14 and you can just remove the reference to that module from your applications.
|
||||
### platform-server
|
||||
- Users that are using SSR with JIT mode will now need to add `import to @angular/compiler` before bootstrapping the application.
|
||||
|
||||
|
||||
**NOTE:** this does not effect users using the Angular CLI.
|
||||
- `renderApplication` method no longer accepts a root component as first argument. Instead, provide a bootstrapping function that returns a `Promise<ApplicationRef>`.
|
||||
|
||||
|
||||
Before
|
||||
```ts
|
||||
const output: string = await renderApplication(RootComponent, options);
|
||||
```
|
||||
|
||||
|
||||
Now
|
||||
```ts
|
||||
const bootstrap = () => bootstrapApplication(RootComponent, appConfig);
|
||||
@@ -1296,14 +1373,14 @@ Live long and prosper 🖖🏻
|
||||
## Deprecations
|
||||
### core
|
||||
- `makeStateKey`, `StateKey` and `TransferState` exports have been moved from `@angular/platform-browser` to `@angular/core`. Please update the imports.
|
||||
|
||||
|
||||
```diff
|
||||
- import {makeStateKey, StateKey, TransferState} from '@angular/platform-browser';
|
||||
+ import {makeStateKey, StateKey, TransferState} from '@angular/core';
|
||||
```
|
||||
- `EnvironmentInjector.runInContext` is now deprecated, with
|
||||
`runInInjectionContext` functioning as a direct replacement:
|
||||
|
||||
|
||||
```typescript
|
||||
// Previous method version (deprecated):
|
||||
envInjector.runInContext(fn);
|
||||
@@ -1316,7 +1393,7 @@ Live long and prosper 🖖🏻
|
||||
### platform-browser
|
||||
- `BrowserModule.withServerTransition` has been deprecated. `APP_ID` should be used instead to set the application ID.
|
||||
NB: Unless, you render multiple Angular applications on the same page, setting an application ID is not necessary.
|
||||
|
||||
|
||||
Before:
|
||||
```ts
|
||||
imports: [
|
||||
@@ -1324,7 +1401,7 @@ Live long and prosper 🖖🏻
|
||||
...
|
||||
]
|
||||
```
|
||||
|
||||
|
||||
After:
|
||||
```ts
|
||||
imports: [
|
||||
@@ -1336,7 +1413,7 @@ Live long and prosper 🖖🏻
|
||||
- `ApplicationConfig` has moved, please import `ApplicationConfig` from `@angular/core` instead.
|
||||
### platform-server
|
||||
- `PlatformConfig.baseUrl` and `PlatformConfig.useAbsoluteUrl` platform-server config options are deprecated as these were not used.
|
||||
###
|
||||
###
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [48aa96ea13](https://github.com/angular/angular/commit/48aa96ea13ebfadf2f6b13516c7702dae740a7be) | refactor | remove Angular Compatibility Compiler (ngcc) ([#49101](https://github.com/angular/angular/pull/49101)) |
|
||||
@@ -1645,11 +1722,11 @@ Alan Agius, Andrew Kushnir, Aristeidis Bampakos, Craig Spence, Doug Parker, Ivá
|
||||
<a name="15.2.0"></a>
|
||||
# 15.2.0 (2023-02-22)
|
||||
## Deprecations
|
||||
###
|
||||
###
|
||||
- Class and `InjectionToken` guards and resolvers are
|
||||
deprecated. Instead, write guards as plain JavaScript functions and
|
||||
inject dependencies with `inject` from `@angular/core`.
|
||||
###
|
||||
###
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [926c35f4ac](https://github.com/angular/angular/commit/926c35f4ac70f5e4d142e545d6d056dd67aac97b) | docs | Deprecate class and InjectionToken and resolvers ([#47924](https://github.com/angular/angular/pull/47924)) |
|
||||
@@ -1814,21 +1891,21 @@ Alan Agius, Alex Rickabaugh, Andrew Scott, Aristeidis Bampakos, Bob Watson, Jens
|
||||
- CanLoad guards in the Router are deprecated. Use CanMatch
|
||||
instead.
|
||||
- router writable properties
|
||||
|
||||
|
||||
The following strategies are meant to be configured by registering the
|
||||
application strategy in DI via the `providers` in the root `NgModule` or
|
||||
`bootstrapApplication`:
|
||||
* `routeReuseStrategy`
|
||||
* `titleStrategy`
|
||||
* `urlHandlingStrategy`
|
||||
|
||||
|
||||
The following options are meant to be configured using the options
|
||||
available in `RouterModule.forRoot` or `provideRouter`.
|
||||
* `onSameUrlNavigation`
|
||||
* `paramsInheritanceStrategy`
|
||||
* `urlUpdateStrategy`
|
||||
* `canceledNavigationResolution`
|
||||
|
||||
|
||||
The following options are available in `RouterModule.forRoot` but not
|
||||
available in `provideRouter`:
|
||||
* `malformedUriErrorHandler` - This was found to not be used anywhere
|
||||
@@ -2023,23 +2100,23 @@ Andrew Kushnir
|
||||
- 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.
|
||||
### compiler-cli
|
||||
- Invalid constructors for DI may now report compilation errors
|
||||
|
||||
|
||||
When a class inherits its constructor from a base class, the compiler may now
|
||||
report an error when that constructor cannot be used for DI purposes. This may
|
||||
either be because the base class is missing an Angular decorator such as
|
||||
@@ -2048,7 +2125,7 @@ Andrew Kushnir
|
||||
These situations used to behave unexpectedly at runtime, where the class may be
|
||||
constructed without any of its constructor parameters, so this is now reported
|
||||
as an error during compilation.
|
||||
|
||||
|
||||
Any new errors that may be reported because of this change can be resolved either
|
||||
by decorating the base class from which the constructor is inherited, or by adding
|
||||
an explicit constructor to the class for which the error is reported.
|
||||
@@ -2082,16 +2159,16 @@ Andrew Kushnir
|
||||
### core
|
||||
- - The ability to pass an `NgModule` to the `providedIn` option for
|
||||
`@Injectable` and `InjectionToken` is now deprecated.
|
||||
|
||||
|
||||
`providedIn: NgModule` was intended to be a tree-shakable alternative to
|
||||
NgModule providers. It does not have wide usage, and in most cases is used
|
||||
incorrectly, in circumstances where `providedIn: 'root'` should be
|
||||
preferred. If providers should truly be scoped to a specific NgModule, use
|
||||
`NgModule.providers` instead.
|
||||
|
||||
|
||||
- The ability to set `providedIn: 'any'` for an `@Injectable` or
|
||||
`InjectionToken` is now deprecated.
|
||||
|
||||
|
||||
`providedIn: 'any'` is an option with confusing semantics and is almost
|
||||
never used apart from a handful of esoteric cases internal to the framework.
|
||||
- The bit field signature of `Injector.get()` has been deprecated, in favor of the new options object.
|
||||
@@ -2694,16 +2771,16 @@ Adrien Crivelli, Alan Agius, Alex Rickabaugh, Andrew Kushnir, Andrew Scott, Dyla
|
||||
- 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)
|
||||
@@ -2713,23 +2790,23 @@ Adrien Crivelli, Alan Agius, Alex Rickabaugh, Andrew Kushnir, Andrew Scott, Dyla
|
||||
- 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
|
||||
@@ -2740,7 +2817,7 @@ Adrien Crivelli, Alan Agius, Alex Rickabaugh, Andrew Kushnir, Andrew Scott, Dyla
|
||||
- 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 stricter. Places that use
|
||||
@@ -2762,7 +2839,7 @@ Adrien Crivelli, Alan Agius, Alex Rickabaugh, Andrew Kushnir, Andrew Scott, Dyla
|
||||
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 never intended to be exposed.
|
||||
`Router.url` is likely the best replacement for this.
|
||||
@@ -2786,7 +2863,7 @@ Adrien Crivelli, Alan Agius, Alex Rickabaugh, Andrew Kushnir, Andrew Scott, Dyla
|
||||
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
|
||||
@@ -2795,7 +2872,7 @@ Adrien Crivelli, Alan Agius, Alex Rickabaugh, Andrew Kushnir, Andrew Scott, Dyla
|
||||
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)`.
|
||||
|
||||
|
||||
## 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.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
The MIT License
|
||||
|
||||
Copyright (c) 2010-2023 Google LLC. https://angular.io/license
|
||||
Copyright (c) 2010-2024 Google LLC. https://angular.io/license
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
||||
@@ -64,7 +64,17 @@ load("@rules_nodejs//nodejs:repositories.bzl", "nodejs_register_toolchains")
|
||||
|
||||
nodejs_register_toolchains(
|
||||
name = "nodejs",
|
||||
node_version = "18.13.0",
|
||||
node_repositories = {
|
||||
"18.18.2-darwin_arm64": ("node-v18.18.2-darwin-arm64.tar.gz", "node-v18.18.2-darwin-arm64", "9f982cc91b28778dd8638e4f94563b0c2a1da7aba62beb72bd427721035ab553"),
|
||||
"18.18.2-darwin_amd64": ("node-v18.18.2-darwin-x64.tar.gz", "node-v18.18.2-darwin-x64", "5bb8da908ed590e256a69bf2862238c8a67bc4600119f2f7721ca18a7c810c0f"),
|
||||
"18.18.2-linux_arm64": ("node-v18.18.2-linux-arm64.tar.xz", "node-v18.18.2-linux-arm64", "2e630e18548627f61eaf573233da7949dc0a1df5eef3f486fa9820c5f6c121aa"),
|
||||
"18.18.2-linux_ppc64le": ("node-v18.18.2-linux-ppc64le.tar.xz", "node-v18.18.2-linux-ppc64le", "b0adff5cf5938266b711d6c724fb134d802e0dee40b3a3f73d162de1b3d11880"),
|
||||
"18.18.2-linux_s390x": ("node-v18.18.2-linux-s390x.tar.xz", "node-v18.18.2-linux-s390x", "c70ec2074b5e2b42c55bb4b8105418b67bf8a61c500d9376a07430dfcc341fdb"),
|
||||
"18.18.2-linux_amd64": ("node-v18.18.2-linux-x64.tar.xz", "node-v18.18.2-linux-x64", "75aba25ae76999309fc6c598efe56ce53fbfc221381a44a840864276264ab8ac"),
|
||||
"18.18.2-windows_amd64": ("node-v18.18.2-win-x64.zip", "node-v18.18.2-win-x64", "3bb0e51e579a41a22b3bf6cb2f3e79c03801aa17acbe0ca00fc555d1282e7acd"),
|
||||
},
|
||||
# We need at least Node 18.17 due to some transitive dependencies.
|
||||
node_version = "18.18.2",
|
||||
)
|
||||
|
||||
# Download npm dependencies.
|
||||
@@ -196,10 +206,10 @@ cldr_xml_data_repository(
|
||||
# sass rules
|
||||
http_archive(
|
||||
name = "io_bazel_rules_sass",
|
||||
sha256 = "386ef2b97bd4342e45db450ce7eb5acb343d22f7580c0bbae89186dc009fa14e",
|
||||
strip_prefix = "rules_sass-d970cb53ad159e7fd1d3a99a00a530cc2c83eff9",
|
||||
sha256 = "974f5e14c54d300ca63a67910ebfcea6d838d8811fefc61184f2872107152705",
|
||||
strip_prefix = "rules_sass-888dd9c02709e228b60a8b3e51e2b16b2ca5279d",
|
||||
urls = [
|
||||
"https://github.com/bazelbuild/rules_sass/archive/d970cb53ad159e7fd1d3a99a00a530cc2c83eff9.zip",
|
||||
"https://github.com/bazelbuild/rules_sass/archive/888dd9c02709e228b60a8b3e51e2b16b2ca5279d.zip",
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
+26
-1
@@ -1,4 +1,5 @@
|
||||
load("//:packages.bzl", "link_packages")
|
||||
load("@bazel_skylib//rules:common_settings.bzl", "bool_flag")
|
||||
load("@build_bazel_rules_nodejs//:index.bzl", "copy_to_bin")
|
||||
load("@npm//@angular-devkit/architect-cli:index.bzl", "architect", "architect_test")
|
||||
|
||||
@@ -96,12 +97,36 @@ copy_to_bin(
|
||||
srcs = APPLICATION_FILES,
|
||||
)
|
||||
|
||||
bool_flag(
|
||||
name = "fast_build_mode",
|
||||
build_setting_default = False,
|
||||
)
|
||||
|
||||
config_setting(
|
||||
name = "fast",
|
||||
flag_values = {
|
||||
":fast_build_mode": "true",
|
||||
},
|
||||
)
|
||||
|
||||
config_setting(
|
||||
name = "full",
|
||||
flag_values = {
|
||||
":fast_build_mode": "false",
|
||||
},
|
||||
)
|
||||
|
||||
config_based_architect_flags = select({
|
||||
":fast": ["--no-prerender"],
|
||||
":full": ["--prerender"],
|
||||
})
|
||||
|
||||
architect(
|
||||
name = "build",
|
||||
args = [
|
||||
"angular-dev:build",
|
||||
"--output-path=build",
|
||||
],
|
||||
] + config_based_architect_flags,
|
||||
chdir = "$(RULEDIR)",
|
||||
data = APPLICATION_DEPS + [
|
||||
":application_files_bin",
|
||||
|
||||
@@ -26,7 +26,6 @@
|
||||
"index": "src/index.html",
|
||||
"browser": "src/main.ts",
|
||||
"server": "src/main.server.ts",
|
||||
"prerender": true,
|
||||
"polyfills": ["src/polyfills.ts", "zone.js"],
|
||||
"tsConfig": "tsconfig.app.json",
|
||||
"inlineStyleLanguage": "scss",
|
||||
|
||||
@@ -1,135 +0,0 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright Google LLC All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.dev/license
|
||||
*/
|
||||
|
||||
import {input, confirm} from '@inquirer/prompts';
|
||||
import {existsSync} from 'fs';
|
||||
import {copyFile, mkdir, writeFile} from 'fs/promises';
|
||||
import {dirname, join} from 'path';
|
||||
import {fileURLToPath} from 'url';
|
||||
import {copyFolder} from './utils/fs.mjs';
|
||||
|
||||
const EXAMPLES_BASE = join(
|
||||
dirname(fileURLToPath(import.meta.url)),
|
||||
'../../src/content/examples',
|
||||
);
|
||||
const STACKBLITZ_TEMPLATE = join(
|
||||
dirname(fileURLToPath(import.meta.url)),
|
||||
'../../src/content/stackblitz-template',
|
||||
);
|
||||
|
||||
const MAIN_TS_FILENAME = 'main.ts';
|
||||
const STACKBLITZ_CONFIG_FILENAME = 'stackblitz.json';
|
||||
|
||||
createExample();
|
||||
|
||||
async function createExample(): Promise<void> {
|
||||
console.info('Create example');
|
||||
const name = await input({message: 'What is the example name?'});
|
||||
const description = await input({message: 'Could you provide the description of the example?'});
|
||||
const tags: string[] = [];
|
||||
let addMoreTags = true;
|
||||
|
||||
while (addMoreTags) {
|
||||
const tag = await input({message: 'Could you provide the tag of the example?'});
|
||||
|
||||
if (!!tag) {
|
||||
tags.push(tag);
|
||||
}
|
||||
|
||||
console.info();
|
||||
|
||||
addMoreTags = await confirm({message: 'Would you like to add another tag?', default: true});
|
||||
}
|
||||
|
||||
const shouldCopySrcAppFolderFromTemplate = await confirm({
|
||||
message: 'Would you like to copy /src/app/**.* from template?',
|
||||
default: true,
|
||||
});
|
||||
const shouldCopyMainTsFromTemplate = await confirm({
|
||||
message: 'Would you like to copy /src/main.ts from template?',
|
||||
default: false,
|
||||
});
|
||||
|
||||
const examplePath = join(EXAMPLES_BASE, name);
|
||||
|
||||
await createEmptyExample(examplePath, name, description, tags);
|
||||
await copyFilesFromTemplate(
|
||||
examplePath,
|
||||
shouldCopySrcAppFolderFromTemplate,
|
||||
shouldCopyMainTsFromTemplate,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the directory and marker files for the new example.
|
||||
*/
|
||||
async function createEmptyExample(
|
||||
examplePath: string,
|
||||
exampleName: string,
|
||||
description: string,
|
||||
tags: string[],
|
||||
): Promise<void> {
|
||||
validateExampleName(exampleName);
|
||||
|
||||
await ensureExamplePath(examplePath);
|
||||
await writeStackBlitzFile(examplePath, description, tags);
|
||||
}
|
||||
|
||||
function validateExampleName(exampleName: string): void {
|
||||
if (/\s/.test(exampleName)) {
|
||||
throw new Error(`Unable to create example. The example name contains spaces: '${exampleName}'`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure that the new example directory exists.
|
||||
*/
|
||||
async function ensureExamplePath(examplePath: string): Promise<void> {
|
||||
if (existsSync(examplePath)) {
|
||||
throw new Error(
|
||||
`Unable to create example. The path to the new example already exists: ${examplePath}`,
|
||||
);
|
||||
}
|
||||
await mkdir(examplePath);
|
||||
}
|
||||
|
||||
/**
|
||||
* Write the `stackblitz.json` file into the new example.
|
||||
*/
|
||||
async function writeStackBlitzFile(
|
||||
examplePath: string,
|
||||
description: string,
|
||||
tags: string[],
|
||||
): Promise<void> {
|
||||
const config = {
|
||||
description,
|
||||
ignore: ['**/*.d.ts', '**/*.js', '**/*.[1,2].*'],
|
||||
tags,
|
||||
};
|
||||
await writeFile(
|
||||
join(examplePath, STACKBLITZ_CONFIG_FILENAME),
|
||||
JSON.stringify(config, null, 2) + '\n',
|
||||
);
|
||||
}
|
||||
|
||||
async function copyFilesFromTemplate(
|
||||
examplePath: string,
|
||||
shouldCopySrcAppFolderFromTemplate: boolean,
|
||||
shouldCopyMainTsFromTemplate: boolean,
|
||||
): Promise<void> {
|
||||
if (shouldCopySrcAppFolderFromTemplate) {
|
||||
const appTemplateFolderPath = join(STACKBLITZ_TEMPLATE, 'src/app');
|
||||
const exampleAppFolderPath = join(examplePath, 'src/app');
|
||||
await copyFolder(appTemplateFolderPath, exampleAppFolderPath);
|
||||
}
|
||||
if (shouldCopyMainTsFromTemplate) {
|
||||
const mainTsTemplatePath = join(STACKBLITZ_TEMPLATE, 'src', MAIN_TS_FILENAME);
|
||||
const exampleMainTsPath = join(examplePath, 'src', MAIN_TS_FILENAME);
|
||||
await copyFile(mainTsTemplatePath, exampleMainTsPath);
|
||||
}
|
||||
}
|
||||
@@ -1,158 +0,0 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright Google LLC All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.dev/license
|
||||
*/
|
||||
|
||||
import ts from 'typescript';
|
||||
import * as fs from 'fs';
|
||||
import {dirname, join, relative} from 'path';
|
||||
import {fileURLToPath} from 'url';
|
||||
import {readFile, writeFile} from 'fs/promises';
|
||||
|
||||
const TYPESCRIPT_EXTENSION = '.ts';
|
||||
const SKIP_FILES_WITH_EXTENSIONS = ['.e2e-spec.ts', '.spec.ts', '.po.ts'];
|
||||
const EXAMPLES_PATH = `../../content/examples`;
|
||||
|
||||
interface File {
|
||||
path: string;
|
||||
content: string;
|
||||
}
|
||||
|
||||
interface AnalyzedFiles {
|
||||
path: string;
|
||||
componentNames: string[];
|
||||
}
|
||||
|
||||
/** Absolute disk path to the project directory. */
|
||||
const examplesDir = join(
|
||||
dirname(fileURLToPath(import.meta.url)),
|
||||
'../../src/content/examples',
|
||||
);
|
||||
const projectDir = join(dirname(fileURLToPath(import.meta.url)), '../../src');
|
||||
const previewsComponentMapFileName = 'previews.ts';
|
||||
|
||||
main();
|
||||
|
||||
async function main() {
|
||||
const files = await retrieveAllTypescriptFiles(
|
||||
examplesDir,
|
||||
(path) => !SKIP_FILES_WITH_EXTENSIONS.some((extensionToSkip) => path.endsWith(extensionToSkip)),
|
||||
);
|
||||
|
||||
const filesWithComponent = files
|
||||
.map((file) => ({
|
||||
componentNames: analyzeFile(file),
|
||||
path: file.path,
|
||||
}))
|
||||
.filter((result) => result.componentNames.length > 0);
|
||||
|
||||
const previewsComponentMap = generatePreviewsComponentMap(filesWithComponent);
|
||||
|
||||
fs.mkdirSync(join(projectDir, 'assets/previews'), {recursive: true});
|
||||
|
||||
await writeFile(
|
||||
join(projectDir, 'assets/previews', previewsComponentMapFileName),
|
||||
previewsComponentMap,
|
||||
);
|
||||
}
|
||||
|
||||
/** Recursively search the provided directory for all typescript files and asynchronously load them. */
|
||||
function retrieveAllTypescriptFiles(
|
||||
baseDir: string,
|
||||
predicateFn: (path: string) => boolean,
|
||||
): Promise<File[]> {
|
||||
const typescriptFiles: Promise<File>[] = [];
|
||||
|
||||
const checkFilesInDirectory = (dir: string) => {
|
||||
const files = fs.readdirSync(dir, {withFileTypes: true});
|
||||
for (const file of files) {
|
||||
const fullPathToFile = join(dir, file.name);
|
||||
const relativeFilePath = relative(baseDir, fullPathToFile);
|
||||
|
||||
if (
|
||||
file.isFile() &&
|
||||
file.name.endsWith(TYPESCRIPT_EXTENSION) &&
|
||||
predicateFn(relativeFilePath)
|
||||
) {
|
||||
typescriptFiles.push(
|
||||
readFile(fullPathToFile, {encoding: 'utf-8'}).then((fileContent) => {
|
||||
return {
|
||||
path: relativeFilePath,
|
||||
content: fileContent,
|
||||
};
|
||||
}),
|
||||
);
|
||||
} else if (file.isDirectory()) {
|
||||
checkFilesInDirectory(fullPathToFile);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
checkFilesInDirectory(baseDir);
|
||||
|
||||
return Promise.all(typescriptFiles);
|
||||
}
|
||||
|
||||
/** Returns list of the `Standalone` @Component class names for given file */
|
||||
function analyzeFile(file: File): string[] {
|
||||
const componentClassNames: string[] = [];
|
||||
const sourceFile = ts.createSourceFile(file.path, file.content, ts.ScriptTarget.Latest, false);
|
||||
|
||||
const visitNode = (node: ts.Node): void => {
|
||||
if (ts.isClassDeclaration(node)) {
|
||||
const decorators = ts.getDecorators(node);
|
||||
const componentName = node.name ? node.name.text : null;
|
||||
|
||||
if (decorators && decorators.length) {
|
||||
for (const decorator of decorators) {
|
||||
const call = decorator.expression;
|
||||
|
||||
if (
|
||||
ts.isCallExpression(call) &&
|
||||
ts.isIdentifier(call.expression) &&
|
||||
call.expression.text === 'Component' &&
|
||||
call.arguments.length > 0 &&
|
||||
ts.isObjectLiteralExpression(call.arguments[0])
|
||||
) {
|
||||
const hasStandaloneProperty = call.arguments[0].properties.some(
|
||||
(property) =>
|
||||
property.name &&
|
||||
ts.isIdentifier(property.name) &&
|
||||
property.name.text === 'standalone',
|
||||
);
|
||||
|
||||
if (hasStandaloneProperty && componentName) {
|
||||
componentClassNames.push(componentName);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ts.forEachChild(node, visitNode);
|
||||
};
|
||||
|
||||
visitNode(sourceFile);
|
||||
|
||||
return componentClassNames;
|
||||
}
|
||||
|
||||
function generatePreviewsComponentMap(data: AnalyzedFiles[]): string {
|
||||
let result = '';
|
||||
for (const fileData of data) {
|
||||
for (const componentName of fileData.componentNames) {
|
||||
const key = `${fileData.path}${
|
||||
fileData.componentNames.length > 1 ? '_' + componentName : ''
|
||||
}`.replace(/\\/g, '/');
|
||||
result += `['${key}']: () => import('${EXAMPLES_PATH}/${fileData.path
|
||||
.replace(/\\/g, '/')
|
||||
.replace('.ts', '')}').then(c => c.${componentName}),\n`;
|
||||
}
|
||||
}
|
||||
return fs
|
||||
.readFileSync('scripts/examples/previews-components.template', 'utf8')
|
||||
.replace(/\${previewsComponents}/g, result);
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright Google LLC All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.dev/license
|
||||
*/
|
||||
|
||||
/**
|
||||
******************************************************************************
|
||||
* DO NOT MANUALLY EDIT THIS FILE. THIS FILE IS AUTOMATICALLY GENERATED.
|
||||
******************************************************************************
|
||||
*/
|
||||
|
||||
import {Type} from '@angular/core';
|
||||
|
||||
/**
|
||||
* Map of the previews components, values are functions which returns the promise of the component type, which will be displayed as preview in the ExampleViewer component.
|
||||
* Keys has to be equal to paths written down in the docs markdown files.
|
||||
*/
|
||||
export const PREVIEWS_COMPONENTS_MAP: Record<string, () => Promise<Type<unknown>>> = {
|
||||
${previewsComponents}
|
||||
};
|
||||
@@ -1,221 +0,0 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright Google LLC All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.dev/license
|
||||
*/
|
||||
|
||||
import {dirname, join} from 'path';
|
||||
import {readFile, writeFile} from 'fs/promises';
|
||||
import {
|
||||
ASSETS_EXAMPLE_PATH,
|
||||
CSS_TS_COPYRIGHT,
|
||||
EXAMPLES_PATH,
|
||||
HTML_COPYRIGHT,
|
||||
STACKBLITZ_TEMPLATE_PATH,
|
||||
STACKBLITZ_CONFIG_FILENAME,
|
||||
TEMPORARY_EXAMPLES_PATH,
|
||||
EXCLUDE_FILES_FOR_STACKBLITZ,
|
||||
} from './utils/examples-constants.mjs';
|
||||
import {copyFolder, createFolder, removeFolder} from './utils/fs.mjs';
|
||||
import jsdom from 'jsdom';
|
||||
import {glob} from 'glob';
|
||||
import {regionParser} from './../../prerender/markdown-pipeline/regions/region-parser.mjs';
|
||||
|
||||
interface StackblitzConfig {
|
||||
ignore: string[];
|
||||
file: string;
|
||||
tags: string[];
|
||||
description: string;
|
||||
}
|
||||
|
||||
export async function generateStackblitzExample(
|
||||
exampleFolderName: string,
|
||||
primaryFilePath: string,
|
||||
title: string,
|
||||
) {
|
||||
const exampleDir = join(EXAMPLES_PATH, exampleFolderName);
|
||||
const temporaryExampleDir = join(TEMPORARY_EXAMPLES_PATH, exampleFolderName);
|
||||
const config = await readFile(join(exampleDir, STACKBLITZ_CONFIG_FILENAME), 'utf-8');
|
||||
const stackblitzConfig: StackblitzConfig = JSON.parse(config);
|
||||
primaryFilePath = join(...primaryFilePath.split('/'));
|
||||
|
||||
await createFolder(temporaryExampleDir);
|
||||
await combineTemplateWithExample(exampleDir, temporaryExampleDir);
|
||||
await generateStackblitzHtml(
|
||||
temporaryExampleDir,
|
||||
stackblitzConfig,
|
||||
exampleFolderName,
|
||||
primaryFilePath,
|
||||
title,
|
||||
);
|
||||
await removeFolder(temporaryExampleDir);
|
||||
}
|
||||
|
||||
async function combineTemplateWithExample(
|
||||
exampleDir: string,
|
||||
temporaryExampleDir: string,
|
||||
): Promise<void> {
|
||||
// Copy template files to TEMP folder
|
||||
await copyFolder(STACKBLITZ_TEMPLATE_PATH, temporaryExampleDir);
|
||||
|
||||
// Copy example files to TEMP folder
|
||||
await copyFolder(exampleDir, temporaryExampleDir);
|
||||
}
|
||||
|
||||
async function generateStackblitzHtml(
|
||||
temporaryExampleDir: string,
|
||||
stackBlitzConfig: StackblitzConfig,
|
||||
exampleFolderName: string,
|
||||
primaryFilePath: string,
|
||||
title: string,
|
||||
): Promise<void> {
|
||||
const defaultIncludes = [
|
||||
'**/*.ts',
|
||||
'**/*.js',
|
||||
'**/*.css',
|
||||
'**/*.html',
|
||||
'**/*.md',
|
||||
'**/*.json',
|
||||
'**/*.svg',
|
||||
];
|
||||
const exampleFilePaths = await glob(defaultIncludes, {
|
||||
cwd: temporaryExampleDir,
|
||||
nodir: true,
|
||||
dot: true,
|
||||
ignore: stackBlitzConfig.ignore,
|
||||
});
|
||||
|
||||
const postData = await createPostData(
|
||||
temporaryExampleDir,
|
||||
stackBlitzConfig,
|
||||
exampleFilePaths,
|
||||
title,
|
||||
);
|
||||
const primaryFile = getPrimaryFile(primaryFilePath ?? stackBlitzConfig.file, exampleFilePaths);
|
||||
const html = createStackblitzHtml(postData, primaryFile);
|
||||
|
||||
const stackblitzHtmlPath = join(
|
||||
join(ASSETS_EXAMPLE_PATH, exampleFolderName),
|
||||
`${primaryFile}.html`,
|
||||
);
|
||||
await createFolder(dirname(stackblitzHtmlPath));
|
||||
await writeFile(stackblitzHtmlPath, html, 'utf-8');
|
||||
}
|
||||
|
||||
function getPrimaryFile(primaryFilePath: string, exampleFilePaths: string[]): string {
|
||||
if (primaryFilePath) {
|
||||
if (!exampleFilePaths.some((filePath) => filePath === primaryFilePath)) {
|
||||
throw new Error(`The specified primary file (${primaryFilePath}) does not exist!`);
|
||||
}
|
||||
return primaryFilePath;
|
||||
} else {
|
||||
const defaultPrimaryFilePaths = [
|
||||
'src/app/app.component.html',
|
||||
'src/app/app.component.ts',
|
||||
'src/app/main.ts',
|
||||
];
|
||||
const primaryFile = defaultPrimaryFilePaths.find((path) =>
|
||||
exampleFilePaths.some((filePath) => filePath === path),
|
||||
);
|
||||
|
||||
if (!primaryFile) {
|
||||
throw new Error(
|
||||
`None of the default primary files (${defaultPrimaryFilePaths.join(', ')}) exists.`,
|
||||
);
|
||||
}
|
||||
|
||||
return primaryFile;
|
||||
}
|
||||
}
|
||||
|
||||
async function createPostData(
|
||||
exampleDir: string,
|
||||
config: StackblitzConfig,
|
||||
exampleFilePaths: string[],
|
||||
title: string,
|
||||
): Promise<Record<string, string>> {
|
||||
const postData: Record<string, string> = {};
|
||||
|
||||
for (const filePath of exampleFilePaths) {
|
||||
if (EXCLUDE_FILES_FOR_STACKBLITZ.some((excludedFile) => filePath.endsWith(excludedFile))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let content = await readFile(join(exampleDir, filePath), 'utf-8');
|
||||
content = appendCopyright(filePath, content);
|
||||
content = extractRegions(filePath, content);
|
||||
|
||||
postData[`project[files][${filePath}]`] = content;
|
||||
}
|
||||
|
||||
const tags = ['angular', 'example', ...(config.tags || [])];
|
||||
tags.forEach((tag, index) => (postData[`project[tags][${index}]`] = tag));
|
||||
|
||||
postData['project[description]'] = `Angular Example - ${config.description}`;
|
||||
postData['project[template]'] = 'node';
|
||||
postData['project[title]'] = title ?? 'Angular Example';
|
||||
|
||||
return postData;
|
||||
}
|
||||
|
||||
function createStackblitzHtml(postData: Record<string, string>, primaryFile: string): string {
|
||||
const baseHtml = createBaseStackblitzHtml(primaryFile);
|
||||
const doc = new jsdom.JSDOM(baseHtml).window.document;
|
||||
const form = doc.querySelector('form');
|
||||
|
||||
for (const [key, value] of Object.entries(postData)) {
|
||||
const element = htmlToElement(doc, `<input type="hidden" name="${key}">`);
|
||||
if (element && form) {
|
||||
element.setAttribute('value', value as string);
|
||||
form.appendChild(element);
|
||||
}
|
||||
}
|
||||
|
||||
return doc.documentElement.outerHTML;
|
||||
}
|
||||
|
||||
function createBaseStackblitzHtml(primaryFile: string) {
|
||||
const file = `?file=${primaryFile}`;
|
||||
const action = `https://stackblitz.com/run${file}`;
|
||||
|
||||
return `
|
||||
<!DOCTYPE html><html lang="en"><body>
|
||||
<form id="mainForm" method="post" action="${action}" target="_self"></form>
|
||||
<script>
|
||||
var embedded = 'ctl=1';
|
||||
var isEmbedded = window.location.search.indexOf(embedded) > -1;
|
||||
|
||||
if (isEmbedded) {
|
||||
var form = document.getElementById('mainForm');
|
||||
var action = form.action;
|
||||
var actionHasParams = action.indexOf('?') > -1;
|
||||
var symbol = actionHasParams ? '&' : '?'
|
||||
form.action = form.action + symbol + embedded;
|
||||
}
|
||||
document.getElementById("mainForm").submit();
|
||||
</script>
|
||||
</body></html>
|
||||
`.trim();
|
||||
}
|
||||
|
||||
function appendCopyright(filename: string, content: string): string {
|
||||
if (filename.endsWith('.html')) {
|
||||
return `${HTML_COPYRIGHT}${content}`;
|
||||
} else if (filename.endsWith('.ts') || filename.endsWith('.css')) {
|
||||
return `${CSS_TS_COPYRIGHT}${content}`;
|
||||
}
|
||||
return content;
|
||||
}
|
||||
|
||||
function htmlToElement(document: Document, html: string) {
|
||||
const div = document.createElement('div');
|
||||
div.innerHTML = html;
|
||||
return div.firstElementChild;
|
||||
}
|
||||
|
||||
function extractRegions(path: string, contents: string): string {
|
||||
const regionParserResult = regionParser(contents, path);
|
||||
return regionParserResult.contents;
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
{
|
||||
"extends": "../../tsconfig.json",
|
||||
"ts-node": {
|
||||
"compilerOptions": {
|
||||
"module": "es2020"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,55 +0,0 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright Google LLC All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.dev/license
|
||||
*/
|
||||
|
||||
import {dirname, join} from 'path';
|
||||
import {fileURLToPath} from 'url';
|
||||
|
||||
const BASE = dirname(fileURLToPath(import.meta.url));
|
||||
|
||||
// Directory paths
|
||||
export const ASSETS_EXAMPLE_PATH = join(
|
||||
BASE,
|
||||
'../../../src/assets/content/examples',
|
||||
);
|
||||
export const EXAMPLES_PATH = join(BASE, '../../../src/content/examples');
|
||||
export const STACKBLITZ_TEMPLATE_PATH = join(
|
||||
BASE,
|
||||
'../../../src/content/stackblitz-template',
|
||||
);
|
||||
|
||||
export const TEMPORARY_EXAMPLES_PATH = join(fileURLToPath(import.meta.url), '../../../../TEMP');
|
||||
|
||||
// Extensions
|
||||
export const TEST_FILES_EXTENSION_SUFFIX = '.spec.ts';
|
||||
export const TEST_FILES_E2E_EXTENSION_SUFFIX = '.e2e-spec.ts';
|
||||
|
||||
// Filename
|
||||
// TODO: BUILD.bazel and example-config will be deprecated when migration to standalone examples will be done.
|
||||
export const BUILD_BAZEL_FILENAME = 'BUILD.bazel';
|
||||
export const EXAMPLE_CONFIG_FILENAME = 'example-config.json';
|
||||
export const STACKBLITZ_CONFIG_FILENAME = 'stackblitz.json';
|
||||
|
||||
// Copyright
|
||||
const PAD = '\n\n';
|
||||
const COPYRIGHT =
|
||||
'@license\n' +
|
||||
'Copyright Google LLC All Rights Reserved.\n' +
|
||||
'\n' +
|
||||
'Use of this source code is governed by an MIT-style license that can be\n' +
|
||||
'found in the LICENSE file at https://angular.dev/license\n';
|
||||
export const CSS_TS_COPYRIGHT = `/*\n${COPYRIGHT}\n*/${PAD}`;
|
||||
export const HTML_COPYRIGHT = `<!-- \n${COPYRIGHT}\n-->${PAD}`;
|
||||
|
||||
// Exclude
|
||||
export const EXCLUDE_FILES_FOR_STACKBLITZ = [
|
||||
STACKBLITZ_CONFIG_FILENAME,
|
||||
BUILD_BAZEL_FILENAME,
|
||||
EXAMPLE_CONFIG_FILENAME,
|
||||
TEST_FILES_EXTENSION_SUFFIX,
|
||||
TEST_FILES_E2E_EXTENSION_SUFFIX,
|
||||
];
|
||||
@@ -1,77 +0,0 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright Google LLC All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.dev/license
|
||||
*/
|
||||
|
||||
import {Dirent, existsSync, readdirSync} from 'fs';
|
||||
import {copyFile, mkdir, readFile, readdir, rm, stat} from 'fs/promises';
|
||||
import {join, relative} from 'path';
|
||||
|
||||
export async function copyFolder(source: string, destination: string) {
|
||||
if (!existsSync(destination)) {
|
||||
await mkdir(destination, {recursive: true});
|
||||
}
|
||||
|
||||
const files = await readdir(source);
|
||||
|
||||
for (const file of files) {
|
||||
const sourcePath = join(source, file);
|
||||
const destPath = join(destination, file);
|
||||
|
||||
const stats = await stat(sourcePath);
|
||||
const isDirectory = await stats.isDirectory();
|
||||
|
||||
if (isDirectory) {
|
||||
await copyFolder(sourcePath, destPath);
|
||||
} else {
|
||||
await copyFile(sourcePath, destPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Recursively search the provided directory for all files which satisfy predicate and asynchronously load them. */
|
||||
export function retrieveFiles(
|
||||
baseDir: string,
|
||||
predicate?: (file: Dirent) => boolean,
|
||||
): Promise<{path: string; content: string}[]> {
|
||||
const filesWithContent: Promise<{path: string; content: string}>[] = [];
|
||||
|
||||
const checkFilesInDirectory = (dir: string) => {
|
||||
const files = readdirSync(dir, {withFileTypes: true});
|
||||
for (const file of files) {
|
||||
const fullPathToFile = join(dir, file.name);
|
||||
|
||||
if (file.isFile() && (predicate ? predicate(file) : true)) {
|
||||
filesWithContent.push(
|
||||
readFile(fullPathToFile, {encoding: 'utf-8'}).then((fileContent) => {
|
||||
return {
|
||||
path: relative(baseDir, fullPathToFile),
|
||||
content: fileContent,
|
||||
};
|
||||
}),
|
||||
);
|
||||
} else if (file.isDirectory()) {
|
||||
checkFilesInDirectory(fullPathToFile);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
checkFilesInDirectory(baseDir);
|
||||
|
||||
return Promise.all(filesWithContent);
|
||||
}
|
||||
|
||||
export async function createFolder(path: string): Promise<void> {
|
||||
if (!existsSync(path)) {
|
||||
await mkdir(path, {recursive: true});
|
||||
}
|
||||
}
|
||||
|
||||
export async function removeFolder(path: string): Promise<void> {
|
||||
if (existsSync(path)) {
|
||||
await rm(path, {recursive: true});
|
||||
}
|
||||
}
|
||||
@@ -116,7 +116,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<p class="docs-license">
|
||||
Super-powered by Google ©2010-2023. Code licensed under an
|
||||
Super-powered by Google ©2010-2024. Code licensed under an
|
||||
<a routerLink="/license" title="License text">MIT-style License</a>
|
||||
. Documentation licensed under
|
||||
<a href="https://creativecommons.org/licenses/by/4.0/">CC BY 4.0</a>
|
||||
|
||||
@@ -0,0 +1,207 @@
|
||||
# EmbeddedEditor components, services and functionality
|
||||
|
||||
- [Scenarios](#scenarios)
|
||||
- [Loading a project](#loading-a-project)
|
||||
- [Updating the code](#updating-the-code)
|
||||
- [Creating a new file](#creating-a-new-file)
|
||||
- [Deleting a file](#deleting-a-file)
|
||||
- [Switching a project](#switching-a-project)
|
||||
- [Components and services](#components-and-services)
|
||||
|
||||
- [EmbeddedEditor](#EmbeddedEditor)
|
||||
- [CodeEditor](#CodeEditor)
|
||||
- [CodeMirrorEditor](#CodeMirrorEditor)
|
||||
- [TypeScript Web Worker](#typescript-web-worker)
|
||||
- [Preview](#Preview)
|
||||
- [Terminal](#Terminal)
|
||||
- [InteractiveTerminal](#InteractiveTerminal)
|
||||
- [Console](#Console)
|
||||
- [NodeRuntimeSandbox](#NodeRuntimeSandbox)
|
||||
- [NodeRuntimeState](#NodeRuntimeState)
|
||||
- [EmbeddedTutorialManager](#EmbeddedTutorialManager)
|
||||
- [EditorUiState](#EditorUiState)
|
||||
- [DownloadManager](#DownloadManager)
|
||||
- [AlertManager](#AlertManager)
|
||||
- [TypingsLoader](#TypingsLoader)
|
||||
|
||||
## External libraries
|
||||
|
||||
- [WebContainers API](https://webcontainers.io/)
|
||||
- [CodeMirror](https://codemirror.net/)
|
||||
- [@typescript/vfs](https://www.npmjs.com/package/@typescript/vfs)
|
||||
- [Xterm.js](https://xtermjs.org/)
|
||||
|
||||
## Notes
|
||||
|
||||
- See [scripts/tutorials/README.md](/scripts/tutorials/README.md) for more information about the tutorials script.
|
||||
- See [adev/src/content/tutorials/README.md](/adev/src/content/tutorials/README.md) for more information about the tutorials content.
|
||||
|
||||
---
|
||||
|
||||
## Scenarios
|
||||
|
||||
### Loading a project
|
||||
|
||||
1. The page responsible for the embedded editor lazy loads the [`EmbeddedEditor`](./embedded-editor.component.ts) component and the [`NodeRuntimeSandbox`](./node-runtime-sandbox.service.ts), then triggers the initialization of all components and services. The embedded editor is available in the following pages:
|
||||
|
||||
- homepage: https://angular.dev
|
||||
- playground: https://angular.dev/playground
|
||||
- tutorial pages: https://angular.dev/tutorials
|
||||
|
||||
2. The project assets are fetched by the [`EmbeddedTutorialManager`](./embedded-tutorial-manager.service.ts). Meanwhile:
|
||||
|
||||
- The code editor is initialized
|
||||
- The code editor initializes the TypeScript Web Worker, which initializes the "default file system map" using TypeScript's CDN.
|
||||
- The WebContainer is initialized
|
||||
- The terminal is initialized
|
||||
|
||||
3. The tutorial source code is mounted in the `WebContainer`'s filesystem by the [`NodeRuntimeSandbox`](./node-runtime-sandbox.service.ts)
|
||||
4. The tutorial project dependencies are installed by the [`NodeRuntimeSandbox`](./node-runtime-sandbox.service.ts).
|
||||
5. The development server is started by the [`NodeRuntimeSandbox`](./node-runtime-sandbox.service.ts) and the types are loaded by the [`TypingsLoader`](./typings-loader.service.ts) service.
|
||||
6. The preview is loaded with the URL provided by the WebContainer API after the development server is started.
|
||||
7. The project is ready.
|
||||
|
||||
### Updating the code
|
||||
|
||||
1. The user update the code in the code editor.
|
||||
2. The code editor state is updated on real time, without debouncing so that the user can see the changes in the code editor and CodeMirror can handle the changes accordingly.
|
||||
3. At the same time, the changes are sent to the TypeScript web worker to provide diagnostics, autocomplete and type features as soon as possible.
|
||||
4. The code changes are debounced to be written in the WebContainer filesystem by the [`NodeRuntimeSandbox`](./node-runtime-sandbox.service.ts).
|
||||
5. After the debounce time is reached, the code changes are written in the WebContainer filesystem by the [`NodeRuntimeSandbox`](./node-runtime-sandbox.service.ts), then the user can see the changes in the preview.
|
||||
|
||||
### Creating a new file
|
||||
|
||||
1. The user clicks on the new file button.
|
||||
2. The new file tab is opened.
|
||||
3. The user types the new file name.
|
||||
4. If the file name is valid, the file is created in the WebContainer filesystem by the [`NodeRuntimeSandbox`](./node-runtime-sandbox.service.ts).
|
||||
|
||||
- `..` is disallowed in the file name to prevent users to create files outside the `src` directory.
|
||||
|
||||
5. The file is added to the TypeScript virtual file system, allowing the TypeScript web worker to provide diagnostics, autocomplete and type features for the new file. Also, exports from the new file are available in other files.
|
||||
6. The new file is added as the last tab in the code editor and the new file can be edited.
|
||||
|
||||
Note: If the new file name matches a file that already exists but is hidden in the code editor, the content for that file will show up in the created file. An example for a file that always exists is `index.html`.
|
||||
|
||||
### Deleting a file
|
||||
|
||||
1. The user clicks on the delete file button.
|
||||
2. The file is deleted from the WebContainer filesystem by the [`NodeRuntimeSandbox`](./node-runtime-sandbox.service.ts).
|
||||
3. The file is removed from the TypeScript virtual file system.
|
||||
4. The file is removed from the code editor tabs.
|
||||
|
||||
Note: Some files can't be deleted to prevent users to break the app, being `src/main.ts`and `src/index.html`
|
||||
|
||||
### Switching a project
|
||||
|
||||
The embedded editor considers a project change when the embedded editor was already initialized and the user changes the page in the following scenarios:
|
||||
|
||||
- Navigating through tutorial steps
|
||||
- Going from the homepage after the embedded editor is initialized to the playground
|
||||
- Going from a tutorial page to the playground
|
||||
- Going from a tutorial page to the homepage
|
||||
- Going from the playground to the homepage
|
||||
|
||||
When a project change is detected, the [`EmbeddedTutorialManager`](./embedded-tutorial-manager.service.ts) emits the `tutorialChanged` observable, which is listened in multiple sub-components and services, then each component/service performs the necessary operations to switch the project.
|
||||
|
||||
The following steps are executed on project change:
|
||||
|
||||
1. The new project files are fetched by the [`EmbeddedTutorialManager`](./embedded-tutorial-manager.service.ts).
|
||||
2. The new project files are mounted in the WebContainer filesystem.
|
||||
3. The TypeScript virtual filesystem is updated with the new files and contents.
|
||||
4. The previous project and new project files are compared.
|
||||
1. Files that are not available in the new project are deleted from the WebContainer filesystem.
|
||||
2. Files that have the same path and name have their content replaced on the previous step when the files are mounted.
|
||||
5. The previous project dependencies are compared with the new project dependencies.
|
||||
1. If there are differences, a `npm install` is triggered, hiding the preview and going to the install loading step.
|
||||
2. If there are no differences, the project is ready.
|
||||
6. Some states are resetted, for example the "reveal answer" state if the previous project was in the "reveal answer" state.
|
||||
|
||||
## Components and services
|
||||
|
||||
### [`EmbeddedEditor`](./embedded-editor.component.ts)
|
||||
|
||||
The embedded editor is the parent component that holds all the components and services that compose the embedded editor.
|
||||
|
||||
#### [`CodeEditor`](./code-editor/code-editor.component.ts)
|
||||
|
||||
The component that holds the code editor view and the code editor state.
|
||||
|
||||
##### [`CodeMirrorEditor`](./code-editor/code-mirror-editor.service.ts)
|
||||
|
||||
[CodeMirror](https://codemirror.net/) is the library used to handle the code editor.
|
||||
|
||||
The `CodeMirrorEditor` service manages the CodeMirror instance and all the interactions with the library used to handle the code editor.
|
||||
|
||||
- handle the file edits and the CodeMirror view and state
|
||||
- handle the current project files in the code editor
|
||||
- handle the file creations and deletions
|
||||
- handle the file changes
|
||||
- handle all the CodeMirror specific events and extensions
|
||||
|
||||
###### [TypeScript Web Worker](./code-editor/workers/typescript-vfs.worker.ts)
|
||||
|
||||
The TypeScript features are provided by the TypeScript web worker, that is initialized by the `CodeMirrorEditor` service.
|
||||
|
||||
The TypeScript web worker uses `@typescript/vfs` and the TypeScript language service to provide diagnostics, autocomplete and type features.
|
||||
|
||||
#### [`Preview`](./preview/preview.component.ts)
|
||||
|
||||
The preview component manages the `iframe` responsible for displaying the tutorial project preview, with the URL provided by the WebContainer API after the development server is started.
|
||||
|
||||
While the project is being initialized, the preview displays the loading state.
|
||||
|
||||
#### [`Terminal`](./terminal/terminal.component.ts)
|
||||
|
||||
[Xterm.js](https://xtermjs.org/) is the library used to handle the terminals.
|
||||
|
||||
The terminal component handles the Xterm.js instance for the console and for the interactive terminal.
|
||||
|
||||
##### [`InteractiveTerminal`](./terminal/interactive-terminal.ts)
|
||||
|
||||
The interactive terminal is the terminal where the user can interact with the terminal and run commands, supporting only commands for the Angular CLI.
|
||||
|
||||
##### Console
|
||||
|
||||
The console displays the output for `npm install` and `ng serve`.
|
||||
|
||||
#### [`NodeRuntimeSandbox`](./node-runtime-sandbox.service.ts)
|
||||
|
||||
Responsible for managing the WebContainer instance and all communication with its API. This service handles:
|
||||
|
||||
- the WebContainer instance
|
||||
- all Node.js scripts
|
||||
- the WebContainer filesystem, mounting the tutorial project files, writing new content, deleting and creating files.
|
||||
- the terminal session, reading and processing user inputs.
|
||||
- the tutorial project dependencies, installing the dependencies.
|
||||
- the processes running inside the WebContainer, being the npm scripts to install the dependencies, run the development server and the user inputs for the `ng` CLI.
|
||||
|
||||
##### [`NodeRuntimeState`](./node-runtime-state.service.ts)
|
||||
|
||||
Manages the [`NodeRuntimeSandbox`](./node-runtime-sandbox.service.ts) loading and error state.
|
||||
|
||||
#### [`EmbeddedTutorialManager`](./embedded-tutorial-manager.service.ts)
|
||||
|
||||
Manages the tutorial assets, being responsible for fetching the tutorial source code and metadata.
|
||||
|
||||
The source code is mounted in the WebContainer filesystem by the [`NodeRuntimeSandbox`](./node-runtime-sandbox.service.ts).
|
||||
|
||||
The metadata is used to manage the project, handle the project changes and the user interactivity with the app.
|
||||
|
||||
This service also handles the reveal answer and reset reveal answer feature.
|
||||
|
||||
#### [`EditorUiState`](./editor-ui-state.service.ts)
|
||||
|
||||
Manages the editor UI state, being responsible for handling the user interactions with the editor tabs, switching between the preview, the terminal and the console.
|
||||
|
||||
#### [`DownloadManager`](./download-manager.service.ts)
|
||||
|
||||
Responsible for handling the download button in the embedded editor, fetching the tutorial project files and generating a zip file with the project content.
|
||||
|
||||
#### [`AlertManager`](./alert-manager.service.ts)
|
||||
|
||||
Manage the alerts displayed in the embedded editor, being the out of memory alert when multiple tabs are opened, and unsupported enviroments alerts.
|
||||
|
||||
#### [`TypingsLoader`](./typings-loader.service.ts)
|
||||
|
||||
Manages the types definitions for the code editor.
|
||||
@@ -145,7 +145,9 @@ $width-breakpoint: 950px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background-color: var(--octonary-contrast);
|
||||
transition: background-color 0.3s ease, border-color 0.3s ease;
|
||||
transition:
|
||||
background-color 0.3s ease,
|
||||
border-color 0.3s ease;
|
||||
|
||||
i {
|
||||
color: var(--bright-blue);
|
||||
@@ -192,3 +194,11 @@ $width-breakpoint: 950px;
|
||||
padding: 1.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
::ng-deep mat-tab-group {
|
||||
.mat-mdc-tab-body-wrapper,
|
||||
.mat-mdc-tab-body,
|
||||
.mat-mdc-tab-body-content {
|
||||
display: contents;
|
||||
}
|
||||
}
|
||||
|
||||
+5
-1
@@ -51,7 +51,8 @@
|
||||
&:has(.docs-reference-api-tab) {
|
||||
width: 60%;
|
||||
}
|
||||
&:not(.docs-reference-api-tab) {
|
||||
&:not(:has(.docs-reference-api-tab)) {
|
||||
width: 100%;
|
||||
max-width: var(--page-width);
|
||||
}
|
||||
}
|
||||
@@ -98,11 +99,14 @@
|
||||
display: flex;
|
||||
gap: 1.81rem;
|
||||
align-items: flex-start;
|
||||
margin-bottom: 1px;
|
||||
|
||||
@include mq.for-desktop-down {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
& > .docs-code {
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
max-height: 93vh;
|
||||
overflow: hidden;
|
||||
|
||||
+187
-167
@@ -157,6 +157,11 @@ const DOCS_SUB_NAVIGATION_DATA: NavigationItem[] = [
|
||||
path: 'guide/components/advanced-configuration',
|
||||
contentPath: 'guide/components/advanced-configuration',
|
||||
},
|
||||
{
|
||||
label: 'Custom Elements',
|
||||
path: 'guide/elements',
|
||||
contentPath: 'guide/elements',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
@@ -218,9 +223,39 @@ const DOCS_SUB_NAVIGATION_DATA: NavigationItem[] = [
|
||||
contentPath: 'guide/templates/control-flow',
|
||||
},
|
||||
{
|
||||
label: 'Deferrable views',
|
||||
path: 'guide/defer',
|
||||
contentPath: 'guide/defer',
|
||||
label: 'Pipes',
|
||||
children: [
|
||||
{
|
||||
label: 'Overview',
|
||||
path: 'guide/pipes',
|
||||
contentPath: 'guide/pipes/overview',
|
||||
},
|
||||
{
|
||||
label: 'Using a pipe in a template',
|
||||
path: 'guide/pipes/template',
|
||||
contentPath: 'guide/pipes/template',
|
||||
},
|
||||
{
|
||||
label: 'Custom pipes',
|
||||
path: 'guide/pipes/transform-data',
|
||||
contentPath: 'guide/pipes/transform-data',
|
||||
},
|
||||
{
|
||||
label: 'Pipe precedence in expressions',
|
||||
path: 'guide/pipes/precedence',
|
||||
contentPath: 'guide/pipes/precedence',
|
||||
},
|
||||
{
|
||||
label: 'Change detection with pipes',
|
||||
path: 'guide/pipes/change-detection',
|
||||
contentPath: 'guide/pipes/change-detection',
|
||||
},
|
||||
{
|
||||
label: 'Unwrapping data from an observable',
|
||||
path: 'guide/pipes/unwrapping-data-observables',
|
||||
contentPath: 'guide/pipes/unwrapping-data-observables',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: 'Template reference variables',
|
||||
@@ -317,70 +352,20 @@ const DOCS_SUB_NAVIGATION_DATA: NavigationItem[] = [
|
||||
path: 'guide/signals/rxjs-interop',
|
||||
contentPath: 'guide/signals/rxjs-interop',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: 'NgModule',
|
||||
children: [
|
||||
{
|
||||
label: 'Overview',
|
||||
path: 'guide/ngmodules',
|
||||
contentPath: 'guide/ngmodules/overview',
|
||||
label: 'Inputs as signals',
|
||||
path: 'guide/signals/inputs',
|
||||
contentPath: 'guide/signals/inputs',
|
||||
},
|
||||
{
|
||||
label: 'JS Modules vs NgModules',
|
||||
path: 'guide/ngmodules/vs-jsmodule',
|
||||
contentPath: 'guide/ngmodules/vs-jsmodule',
|
||||
label: 'Model inputs',
|
||||
path: 'guide/signals/model',
|
||||
contentPath: 'guide/signals/model',
|
||||
},
|
||||
{
|
||||
label: 'Launching your app with a root module',
|
||||
path: 'guide/ngmodules/bootstrapping',
|
||||
contentPath: 'guide/ngmodules/bootstrapping',
|
||||
},
|
||||
{
|
||||
label: 'Sharing NgModules',
|
||||
path: 'guide/ngmodules/sharing',
|
||||
contentPath: 'guide/ngmodules/sharing',
|
||||
},
|
||||
{
|
||||
label: 'Frequently used NgModules',
|
||||
path: 'guide/ngmodules/frequent',
|
||||
contentPath: 'guide/ngmodules/frequent',
|
||||
},
|
||||
{
|
||||
label: 'Feature modules',
|
||||
path: 'guide/ngmodules/feature-modules',
|
||||
contentPath: 'guide/ngmodules/feature-modules',
|
||||
},
|
||||
{
|
||||
label: 'Types of feature modules',
|
||||
path: 'guide/ngmodules/module-types',
|
||||
contentPath: 'guide/ngmodules/module-types',
|
||||
},
|
||||
{
|
||||
label: 'Providing dependencies',
|
||||
path: 'guide/ngmodules/providers',
|
||||
contentPath: 'guide/ngmodules/providers',
|
||||
},
|
||||
{
|
||||
label: 'Singleton services',
|
||||
path: 'guide/ngmodules/singleton-services',
|
||||
contentPath: 'guide/ngmodules/singleton-services',
|
||||
},
|
||||
{
|
||||
label: 'Lazy-loading feature modules',
|
||||
path: 'guide/ngmodules/lazy-loading',
|
||||
contentPath: 'guide/ngmodules/lazy-loading',
|
||||
},
|
||||
{
|
||||
label: 'NgModule API',
|
||||
path: 'guide/ngmodules/api',
|
||||
contentPath: 'guide/ngmodules/api',
|
||||
},
|
||||
{
|
||||
label: 'NgModule FAQs',
|
||||
path: 'guide/ngmodules/faq',
|
||||
contentPath: 'guide/ngmodules/faq',
|
||||
label: 'Queries as signals',
|
||||
path: 'guide/signals/queries',
|
||||
contentPath: 'guide/signals/queries',
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -450,29 +435,64 @@ const DOCS_SUB_NAVIGATION_DATA: NavigationItem[] = [
|
||||
],
|
||||
},
|
||||
{
|
||||
label: 'Server-side Rendering',
|
||||
path: 'guide/ssr',
|
||||
contentPath: 'guide/ssr',
|
||||
label: 'HTTP Client',
|
||||
children: [
|
||||
{
|
||||
label: 'Overview',
|
||||
path: 'guide/http',
|
||||
contentPath: 'guide/http/overview',
|
||||
},
|
||||
{
|
||||
label: 'Setting up HttpClient',
|
||||
path: 'guide/http/setup',
|
||||
contentPath: 'guide/http/setup',
|
||||
},
|
||||
{
|
||||
label: 'Making requests',
|
||||
path: 'guide/http/making-requests',
|
||||
contentPath: 'guide/http/making-requests',
|
||||
},
|
||||
{
|
||||
label: 'Intercepting requests and responses',
|
||||
path: 'guide/http/interceptors',
|
||||
contentPath: 'guide/http/interceptors',
|
||||
},
|
||||
{
|
||||
label: 'Testing',
|
||||
path: 'guide/http/testing',
|
||||
contentPath: 'guide/http/testing',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: 'Build-time prerendering',
|
||||
path: 'guide/prerendering',
|
||||
contentPath: 'guide/prerendering',
|
||||
},
|
||||
{
|
||||
label: 'Hydration',
|
||||
path: 'guide/hydration',
|
||||
contentPath: 'guide/hydration',
|
||||
},
|
||||
{
|
||||
label: 'Deferrable views',
|
||||
path: 'guide/defer',
|
||||
contentPath: 'guide/defer',
|
||||
},
|
||||
{
|
||||
label: 'Image Optimization',
|
||||
path: 'guide/image-optimization',
|
||||
contentPath: 'guide/image-optimization',
|
||||
label: 'Performance',
|
||||
children: [
|
||||
{
|
||||
label: 'Deferrable views',
|
||||
path: 'guide/defer',
|
||||
contentPath: 'guide/defer',
|
||||
},
|
||||
{
|
||||
label: 'Image Optimization',
|
||||
path: 'guide/image-optimization',
|
||||
contentPath: 'guide/image-optimization',
|
||||
},
|
||||
{
|
||||
label: 'Server-side Rendering',
|
||||
path: 'guide/ssr',
|
||||
contentPath: 'guide/ssr',
|
||||
},
|
||||
{
|
||||
label: 'Build-time prerendering',
|
||||
path: 'guide/prerendering',
|
||||
contentPath: 'guide/prerendering',
|
||||
},
|
||||
{
|
||||
label: 'Hydration',
|
||||
path: 'guide/hydration',
|
||||
contentPath: 'guide/hydration',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: 'Testing',
|
||||
@@ -584,81 +604,6 @@ const DOCS_SUB_NAVIGATION_DATA: NavigationItem[] = [
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: 'Security',
|
||||
path: 'guide/security',
|
||||
contentPath: 'guide/security',
|
||||
},
|
||||
{
|
||||
label: 'Pipes',
|
||||
children: [
|
||||
{
|
||||
label: 'Overview',
|
||||
path: 'guide/pipes',
|
||||
contentPath: 'guide/pipes/overview',
|
||||
},
|
||||
{
|
||||
label: 'Using a pipe in a template',
|
||||
path: 'guide/pipes/template',
|
||||
contentPath: 'guide/pipes/template',
|
||||
},
|
||||
{
|
||||
label: 'Custom pipes',
|
||||
path: 'guide/pipes/transform-data',
|
||||
contentPath: 'guide/pipes/transform-data',
|
||||
},
|
||||
{
|
||||
label: 'Pipe precedence in expressions',
|
||||
path: 'guide/pipes/precedence',
|
||||
contentPath: 'guide/pipes/precedence',
|
||||
},
|
||||
{
|
||||
label: 'Change detection with pipes',
|
||||
path: 'guide/pipes/change-detection',
|
||||
contentPath: 'guide/pipes/change-detection',
|
||||
},
|
||||
{
|
||||
label: 'Unwrapping data from an observable',
|
||||
path: 'guide/pipes/unwrapping-data-observables',
|
||||
contentPath: 'guide/pipes/unwrapping-data-observables',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: 'Http Client',
|
||||
children: [
|
||||
{
|
||||
label: 'Overview',
|
||||
path: 'guide/http',
|
||||
contentPath: 'guide/http/overview',
|
||||
},
|
||||
{
|
||||
label: 'Setting up HttpClient',
|
||||
path: 'guide/http/setup',
|
||||
contentPath: 'guide/http/setup',
|
||||
},
|
||||
{
|
||||
label: 'Making requests',
|
||||
path: 'guide/http/making-requests',
|
||||
contentPath: 'guide/http/making-requests',
|
||||
},
|
||||
{
|
||||
label: 'Intercepting requests and responses',
|
||||
path: 'guide/http/interceptors',
|
||||
contentPath: 'guide/http/interceptors',
|
||||
},
|
||||
{
|
||||
label: 'Security',
|
||||
path: 'guide/http/security',
|
||||
contentPath: 'guide/http/security',
|
||||
},
|
||||
{
|
||||
label: 'Testing',
|
||||
path: 'guide/http/testing',
|
||||
contentPath: 'guide/http/testing',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: 'Animations',
|
||||
children: [
|
||||
@@ -689,11 +634,6 @@ const DOCS_SUB_NAVIGATION_DATA: NavigationItem[] = [
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: 'Custom Elements',
|
||||
path: 'guide/elements',
|
||||
contentPath: 'guide/elements',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
@@ -825,9 +765,9 @@ const DOCS_SUB_NAVIGATION_DATA: NavigationItem[] = [
|
||||
contentPath: 'best-practices/style-guide',
|
||||
},
|
||||
{
|
||||
label: 'Keeping up-to-date',
|
||||
path: 'update',
|
||||
contentPath: 'best-practices/update',
|
||||
label: 'Security',
|
||||
path: 'best-practices/security',
|
||||
contentPath: 'guide/security', // Have not refactored due to build issues
|
||||
},
|
||||
{
|
||||
label: 'Accessibility',
|
||||
@@ -859,6 +799,11 @@ const DOCS_SUB_NAVIGATION_DATA: NavigationItem[] = [
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: 'Keeping up-to-date',
|
||||
path: 'update',
|
||||
contentPath: 'best-practices/update',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
@@ -943,7 +888,7 @@ export const TUTORIALS_SUB_NAVIGATION_DATA: NavigationItem[] = [
|
||||
{
|
||||
path: DefaultPage.TUTORIALS,
|
||||
contentPath: 'tutorials/home',
|
||||
label: 'Tutorials'
|
||||
label: 'Tutorials',
|
||||
},
|
||||
];
|
||||
|
||||
@@ -1344,6 +1289,81 @@ const REFERENCE_SUB_NAVIGATION_DATA: NavigationItem[] = [
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: 'Concepts',
|
||||
children: [
|
||||
{
|
||||
label: 'Overview',
|
||||
path: 'reference/concepts',
|
||||
contentPath: 'reference/concepts/overview',
|
||||
},
|
||||
{
|
||||
label: 'NgModule',
|
||||
children: [
|
||||
{
|
||||
label: 'Overview',
|
||||
path: 'guide/ngmodules',
|
||||
contentPath: 'guide/ngmodules/overview',
|
||||
},
|
||||
{
|
||||
label: 'JS Modules vs NgModules',
|
||||
path: 'guide/ngmodules/vs-jsmodule',
|
||||
contentPath: 'guide/ngmodules/vs-jsmodule',
|
||||
},
|
||||
{
|
||||
label: 'Launching your app with a root module',
|
||||
path: 'guide/ngmodules/bootstrapping',
|
||||
contentPath: 'guide/ngmodules/bootstrapping',
|
||||
},
|
||||
{
|
||||
label: 'Sharing NgModules',
|
||||
path: 'guide/ngmodules/sharing',
|
||||
contentPath: 'guide/ngmodules/sharing',
|
||||
},
|
||||
{
|
||||
label: 'Frequently used NgModules',
|
||||
path: 'guide/ngmodules/frequent',
|
||||
contentPath: 'guide/ngmodules/frequent',
|
||||
},
|
||||
{
|
||||
label: 'Feature modules',
|
||||
path: 'guide/ngmodules/feature-modules',
|
||||
contentPath: 'guide/ngmodules/feature-modules',
|
||||
},
|
||||
{
|
||||
label: 'Types of feature modules',
|
||||
path: 'guide/ngmodules/module-types',
|
||||
contentPath: 'guide/ngmodules/module-types',
|
||||
},
|
||||
{
|
||||
label: 'Providing dependencies',
|
||||
path: 'guide/ngmodules/providers',
|
||||
contentPath: 'guide/ngmodules/providers',
|
||||
},
|
||||
{
|
||||
label: 'Singleton services',
|
||||
path: 'guide/ngmodules/singleton-services',
|
||||
contentPath: 'guide/ngmodules/singleton-services',
|
||||
},
|
||||
{
|
||||
label: 'Lazy-loading feature modules',
|
||||
path: 'guide/ngmodules/lazy-loading',
|
||||
contentPath: 'guide/ngmodules/lazy-loading',
|
||||
},
|
||||
{
|
||||
label: 'NgModule API',
|
||||
path: 'guide/ngmodules/api',
|
||||
contentPath: 'guide/ngmodules/api',
|
||||
},
|
||||
{
|
||||
label: 'NgModule FAQs',
|
||||
path: 'guide/ngmodules/faq',
|
||||
contentPath: 'guide/ngmodules/faq',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
const FOOTER_NAVIGATION_DATA: NavigationItem[] = [
|
||||
|
||||
@@ -26,6 +26,7 @@ copy_to_directory(
|
||||
"//adev/src/content/introduction",
|
||||
"//adev/src/content/introduction/essentials",
|
||||
"//adev/src/content/reference",
|
||||
"//adev/src/content/reference/concepts",
|
||||
"//adev/src/content/reference/configs",
|
||||
"//adev/src/content/reference/errors",
|
||||
"//adev/src/content/reference/extended-diagnostics",
|
||||
@@ -65,10 +66,13 @@ copy_to_directory(
|
||||
"//packages/upgrade:upgrade_docs",
|
||||
"//packages/upgrade/static:upgrade_static_docs",
|
||||
"//packages/upgrade/static/testing:upgrade_static_testing_docs",
|
||||
"//tools/manual_api_docs/blocks:blocks_docs",
|
||||
"//tools/manual_api_docs/elements:elements_docs",
|
||||
],
|
||||
replace_prefixes = {
|
||||
"adev/src/content": "",
|
||||
"packages/**/": "api/",
|
||||
"tools/**/": "api/",
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@@ -43,7 +43,7 @@ It also includes troubleshooting information and any recommended manual changes
|
||||
For simple updates, the CLI command [`ng update`](cli/update) is all you need.
|
||||
Without additional arguments, [`ng update`](cli/update) lists the updates that are available to you and provides recommended steps to update your application to the most current version.
|
||||
|
||||
[Angular Versioning and Releases](reference/releases#versioning "Angular Release Practices, Versioning") describes the level of change that you can expect based a release's version number.
|
||||
[Angular Versioning and Releases](reference/releases#versioning "Angular Release Practices, Versioning") describes the level of change that you can expect based on a release's version number.
|
||||
It also describes supported update paths.
|
||||
|
||||
## Resource summary
|
||||
|
||||
@@ -126,8 +126,7 @@ export class SpecializedMenuWithTooltip { }
|
||||
### Directive execution order
|
||||
|
||||
Host directives go through the same lifecycle as components and directives used directly in a
|
||||
template. However, host directives always execute their constructor, lifecycle hooks, and bindings _
|
||||
before_ the component or directive on which they are applied.
|
||||
template. However, host directives always execute their constructor, lifecycle hooks, and bindings _before_ the component or directive on which they are applied.
|
||||
|
||||
The following example shows minimal use of a host directive:
|
||||
|
||||
|
||||
@@ -79,7 +79,7 @@ If you look at the template for the `name` input again, it is fairly similar to
|
||||
|
||||
This form differs from the template-driven version in that it no longer exports any directives. Instead, it uses the `name` getter defined in the component class.
|
||||
|
||||
Notice that the `required` attribute is still present in the template. Although it's not necessary for validation, it should be retained to for accessibility purposes.
|
||||
Notice that the `required` attribute is still present in the template. Although it's not necessary for validation, it should be retained for accessibility purposes.
|
||||
|
||||
## Defining custom validators
|
||||
|
||||
|
||||
@@ -151,7 +151,7 @@ Just as a form group contains a group of controls, the *profileForm* `FormGroup`
|
||||
</docs-step>
|
||||
|
||||
<docs-step title="Save form data">
|
||||
The `ProfileEditor` component accepts input from the user, but in a real scenario you want to capture the form value and make available for further processing outside the component. The `FormGroup` directive listens for the `submit` event emitted by the `form` element and emits an `ngSubmit` event that you can bind to a callback function. Add an `ngSubmit` event listener to the `form` tag with the `onSubmit()` callback method.
|
||||
The `ProfileEditor` component accepts input from the user, but in a real scenario you want to capture the form value and make it available for further processing outside the component. The `FormGroup` directive listens for the `submit` event emitted by the `form` element and emits an `ngSubmit` event that you can bind to a callback function. Add an `ngSubmit` event listener to the `form` tag with the `onSubmit()` callback method.
|
||||
|
||||
<docs-code header="src/app/profile-editor/profile-editor.component.html (submit event)" path="adev/src/content/examples/reactive-forms/src/app/profile-editor/profile-editor.component.html" visibleRegion="ng-submit"/>
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ This tutorial shows you how to create a template-driven form. The control elemen
|
||||
Template-driven forms use [two-way data binding](guide/templates/two-way-binding) to update the data model in the component as changes are made in the template and vice versa.
|
||||
|
||||
<docs-callout helpful title="Template vs Reactive forms">
|
||||
Angular supports two design approaches for interactive forms. Template-driven forms allow you to use form-specific directives in your Angular template.Reactive forms provide a model-driven approach to building forms.
|
||||
Angular supports two design approaches for interactive forms. Template-driven forms allow you to use form-specific directives in your Angular template. Reactive forms provide a model-driven approach to building forms.
|
||||
|
||||
Template-driven forms are a great choice for small or simple forms, while reactive forms are more scalable and suitable for complex forms. For a comparison of the two approaches, see [Choosing an approach](guide/forms#choosing-an-approach)
|
||||
</docs-callout>
|
||||
@@ -294,7 +294,7 @@ To let form users add a new actor, you will add a **New Actor** button that resp
|
||||
|
||||
<docs-code header="src/app/actor-form/actor-form.component.ts (New Actor method)" path="adev/src/content/examples/forms/src/app/actor-form/actor-form.component.ts" visibleRegion="new-actor"/>
|
||||
|
||||
1. Bind the button's click event to a actor-creation method, `newActor()`.
|
||||
1. Bind the button's click event to an actor-creation method, `newActor()`.
|
||||
|
||||
<docs-code header="src/app/actor-form/actor-form.component.html (New Actor button)" path="adev/src/content/examples/forms/src/app/actor-form/actor-form.component.html" visibleRegion="new-actor-button-no-reset"/>
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ The localization process includes the following actions.
|
||||
* Format data for a specific locale
|
||||
|
||||
A *locale* identifies a region in which people speak a particular language or language variant.
|
||||
Possible regions includes countries and geographical regions.
|
||||
Possible regions include countries and geographical regions.
|
||||
A locale determines the formatting and parsing of the following details.
|
||||
|
||||
* Measurement units including date and time, numbers, and currencies
|
||||
|
||||
@@ -345,7 +345,7 @@ Note: even though the `width` property may not always be present, a custom loade
|
||||
|
||||
### The `loaderParams` Property
|
||||
|
||||
There is an additional attribute supported by the `NgOptimizedImage` directive, called `loaderParams`, which is specifically designed to support the use of custom loaders. The `loaderParams` attribute take an object with any properties as a value, and does not do anything on its own. The data in `loaderParams` is added to the `ImageLoaderConfig` object passed to your custom loader, and can be used to control the behavior of the loader.
|
||||
There is an additional attribute supported by the `NgOptimizedImage` directive, called `loaderParams`, which is specifically designed to support the use of custom loaders. The `loaderParams` attribute takes an object with any properties as a value, and does not do anything on its own. The data in `loaderParams` is added to the `ImageLoaderConfig` object passed to your custom loader, and can be used to control the behavior of the loader.
|
||||
|
||||
A common use for `loaderParams` is controlling advanced image CDN features.
|
||||
|
||||
@@ -400,3 +400,7 @@ The `ngSrc` attribute was chosen as the trigger for NgOptimizedImage due to tech
|
||||
The [image loaders](#configuring-an-image-loader-for-ngoptimizedimage) provider pattern is designed to be as simple as possible for the common use case of having only a single image CDN used within a component. However, it's still very possible to manage multiple image CDNs using a single provider.
|
||||
|
||||
To do this, we recommend writing a [custom image loader](#custom-loaders) which uses the [`loaderParams` property](#the-loaderparams-property) to pass a flag that specifies which image CDN should be used, and then invokes the appropriate loader based on that flag.
|
||||
|
||||
### Can you add a new built-in loader for my preferred CDN?
|
||||
|
||||
For maintenance reasons, we don't currently plan to support additional built-in loaders in the Angular repository. Instead, we encourage developers to publish any additional image loaders as third-party packages.
|
||||
|
||||
@@ -0,0 +1,148 @@
|
||||
# Signal inputs
|
||||
|
||||
Signal inputs allow values to be bound from parent components.
|
||||
Those values are exposed using a `Signal` and can change during the lifecycle of your component.
|
||||
|
||||
HELPFUL: Signal inputs are currently in [developer preview](/guide/releases#developer-preview).
|
||||
|
||||
Angular supports two variants of inputs:
|
||||
|
||||
**Optional inputs**
|
||||
Inputs are optional by default, unless you use `input.required`.
|
||||
You can specify an explicit initial value, or Angular will use `undefined` implicitly.
|
||||
|
||||
**Required inputs**
|
||||
Required inputs always have a value of the given input type.
|
||||
They are declared using the `input.required` function.
|
||||
|
||||
```typescript
|
||||
import {Component, input} from '@angular/core';
|
||||
|
||||
@Component({...})
|
||||
export class MyComp {
|
||||
// optional
|
||||
firstName = input<string>(); // InputSignal<string|undefined>
|
||||
age = input(0); // InputSignal<number>
|
||||
|
||||
// required
|
||||
lastName = input.required<string>(); // InputSignal<string>
|
||||
}
|
||||
```
|
||||
|
||||
An input is automatically recognized by Angular whenever you use the `input` or `input.required` functions as initializer of class members.
|
||||
|
||||
## Aliasing an input
|
||||
|
||||
Angular uses the class member name as the name of the input.
|
||||
You can alias inputs to change their public name to be different.
|
||||
|
||||
```typescript
|
||||
class StudentDirective {
|
||||
age = input(0, {alias: 'studentAge'});
|
||||
}
|
||||
```
|
||||
|
||||
This allows users to bind to your input using `[studentAge]`, while inside your component you can access the input values using `this.age`.
|
||||
|
||||
## Using in templates
|
||||
|
||||
Signal inputs are read-only signals.
|
||||
As with signals declared via `signal()`, you access the current value of the input by calling the input signal.
|
||||
|
||||
```html
|
||||
<p>First name: {{firstName()}}</p>
|
||||
<p>Last name: {{lastName()}}</p>
|
||||
```
|
||||
|
||||
This access to the value is captured in reactive contexts and can notify active consumers, like Angular itself, whenever the input value changes.
|
||||
|
||||
An input signal in practice is a trivial extension of signals that you know from [the signals guide](/guide/signals).
|
||||
|
||||
```typescript
|
||||
export class InputSignal<T> extends Signal<T> { ... }`.
|
||||
```
|
||||
|
||||
## Deriving values
|
||||
|
||||
As with signals, you can derive values from inputs using `computed`.
|
||||
|
||||
```typescript
|
||||
import {Component, input, computed} from '@angular/core';
|
||||
|
||||
@Component({...})
|
||||
export class MyComp {
|
||||
age = input(0);
|
||||
|
||||
// age multiplied by two.
|
||||
ageMultiplied = computed(() => this.age() * 2);
|
||||
}
|
||||
```
|
||||
|
||||
Computed signals memoize values.
|
||||
See more details in the [dedicated section for computed](/guide/signals#computed-signals).
|
||||
|
||||
## Monitoring changes
|
||||
|
||||
With signal inputs, users can leverage the `effect` function.
|
||||
The function will execute whenever the input changes.
|
||||
|
||||
Consider the following example.
|
||||
The new value is printed to the console whenever the `firstName` input changes.
|
||||
|
||||
```typescript
|
||||
import {input, effect} from '@angular/core';
|
||||
|
||||
class MyComp {
|
||||
firstName = input.required<string>();
|
||||
|
||||
constructor() {
|
||||
effect(() => {
|
||||
console.log(this.firstName());
|
||||
});
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The `console.log` function is invoked every time the `firstName` input changes.
|
||||
This will happen as soon as `firstName` is available, and for subsequent changes during the lifetime of `MyComp`.
|
||||
|
||||
## Value transforms
|
||||
|
||||
You may want to coerce or parse input values without changing the meaning of the input.
|
||||
Transforms convert the raw value from parent templates to the expected input type.
|
||||
Transforms should be [pure functions](https://en.wikipedia.org/wiki/Pure_function).
|
||||
|
||||
```typescript
|
||||
class MyComp {
|
||||
disabled = input(false, {
|
||||
transform: (value: boolean|string) => typeof value === 'string' ? value === '' : value,
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
In the example above, you are declaring an input named `disabled` that is accepting values of type `boolean` and `string`.
|
||||
This is captured by the explicit parameter type of `value` in the `transform` option.
|
||||
These values are then parsed to a `boolean` with the transform, resulting in booleans.
|
||||
|
||||
That way, you are only dealing with `boolean` inside your component when calling `this.disabled()`, while users of your component can pass an empty string as a shorthand to mark your component as disabled.
|
||||
|
||||
```html
|
||||
<my-custom-comp disabled>
|
||||
```
|
||||
|
||||
|
||||
IMPORTANT:Do not use transforms if they change the meaning of the input, or if they are [impure](https://en.wikipedia.org/wiki/Pure_function#Impure_functions).
|
||||
Instead, use `computed` for transformations with different meaning, or an `effect` for impure code that should run whenever the input changes.
|
||||
|
||||
## Why should we use signal inputs and not `@Input()`?
|
||||
|
||||
Signal inputs are a reactive alternative to decorator-based `@Input()`.
|
||||
|
||||
In comparison to decorator-based `@Input`, signal inputs provide numerous benefits:
|
||||
|
||||
1. Signal inputs are more **type safe**:
|
||||
<br/>• Required inputs do not require initial values, or tricks to tell TypeScript that an input _always_ has a value.
|
||||
<br/>• Transforms are automatically checked to match the accepted input values.
|
||||
2. Signal inputs, when used in templates, will **automatically** mark `OnPush` components as dirty.
|
||||
3. Values can be easily **derived** whenever an input changes using `computed`.
|
||||
4. Easier and more local monitoring of inputs using `effect` instead of `ngOnChanges` or setters.
|
||||
@@ -0,0 +1,138 @@
|
||||
# Model inputs
|
||||
|
||||
**Model inputs** are a special type of input that enable a component to propagate new values
|
||||
back to another component.
|
||||
|
||||
HELPFUL: Model inputs are currently in [developer preview](/guide/releases#developer-preview).
|
||||
|
||||
When creating a component, you can define a model input similarly to how you create a standard
|
||||
input.
|
||||
|
||||
```typescript
|
||||
import {Component, model, input} from '@angular/core';
|
||||
|
||||
@Component({...})
|
||||
export class CustomCheckbox {
|
||||
// This is a model input.
|
||||
checked = model(false);
|
||||
|
||||
// This is a standard input.
|
||||
disabled = input(false);
|
||||
}
|
||||
```
|
||||
|
||||
Both types of input allow someone to bind a value into the property. However, **model inputs allow
|
||||
the component author to write values into the property**.
|
||||
|
||||
In other respects, you can use model inputs the same way you use standard inputs. You can read the
|
||||
value by calling the signal function, including in reactive contexts like `computed` and `effect`.
|
||||
|
||||
```typescript
|
||||
import {Component, model, input} from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'custom-checkbox',
|
||||
template: '<div (click)="toggle()"> ... </div>',
|
||||
})
|
||||
export class CustomCheckbox {
|
||||
checked = model(false);
|
||||
disabled = input(false);
|
||||
|
||||
toggle() {
|
||||
// While standard inputs are read-only, you can write directly to model inputs.
|
||||
this.checked.set(!this.checked());
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
When a component writes a new value into a model input, Angular can propagate the new value back
|
||||
to the component that is binding a value into that input. This is called **two-way binding** because
|
||||
values can flow in both directions.
|
||||
|
||||
## Two-way binding with signals
|
||||
|
||||
You can bind a writable signal to a model input.
|
||||
|
||||
```typescript
|
||||
@Component({
|
||||
...,
|
||||
// `checked` is a model input.
|
||||
// The parenthesis-inside-square-brackets syntax (aka "banana-in-a-box") creates a two-way binding
|
||||
template: '<custom-checkbox [(checked)]="isAdmin" />',
|
||||
})
|
||||
export class UserProfile {
|
||||
protected isAdmin = signal(false);
|
||||
}
|
||||
```
|
||||
|
||||
In the above example, the `CustomCheckbox` can write values into its `checked` model input, which
|
||||
then propagates those values back to the `isAdmin` signal in `UserProfile`. This binding keeps that
|
||||
values of `checked` and `isAdmin` in sync. Notice that the binding passes the `isAdmin` signal
|
||||
itself, not the _value_ of the signal.
|
||||
|
||||
## Two-way binding with plain properties
|
||||
|
||||
You can bind a plain JavaScript property to a model input.
|
||||
|
||||
```typescript
|
||||
@Component({
|
||||
...,
|
||||
// `checked` is a model input.
|
||||
// The parenthesis-inside-square-brackets syntax (aka "banana-in-a-box") creates a two-way binding
|
||||
template: '<custom-checkbox [(checked)]="isAdmin" />',
|
||||
})
|
||||
export class UserProfile {
|
||||
protected isAdmin = false;
|
||||
}
|
||||
```
|
||||
|
||||
In the example above, the `CustomCheckbox` can write values into its `checked` model input, which
|
||||
then propagates those values back to the `isAdmin` property in `UserProfile`. This binding keeps
|
||||
that values of `checked` and `isAdmin` in sync.
|
||||
|
||||
## Implicit `change` events
|
||||
|
||||
When you declare a model input in a component or directive, Angular automatically creates a
|
||||
corresponding [output](/guide/inputs-outputs) for that model. The output's name is the model
|
||||
input's name suffixed with "Change".
|
||||
|
||||
```typescript
|
||||
@Directive({...})
|
||||
export class CustomCheckbox {
|
||||
// This automatically creates an output named "checkedChange".
|
||||
// Can be subscribed to using `(checkedChange)="handler()"` in the template.
|
||||
checked = model(false);
|
||||
}
|
||||
```
|
||||
|
||||
Angular emits this change event whenever you write a new value into the model input by calling
|
||||
its `set` or `update` methods.
|
||||
|
||||
## Customizing model inputs
|
||||
|
||||
You can mark a model input as required or provide an alias in the same way as a
|
||||
[standard input](/guide/signal-inputs).
|
||||
|
||||
Model inputs do not support input transforms.
|
||||
|
||||
## Differences between `model()` and `input()`
|
||||
|
||||
Both `input()` and `model()` functions are ways to define signal-based inputs in Angular, but they
|
||||
differ in a few ways:
|
||||
1. `model()` defines **both** an input and an output. The output's name is always the name of the
|
||||
input suffixed with `Change` to support two-way bindings. It will be up to the consumer of your
|
||||
directive to decide if they want to use just the input, just the output, or both.
|
||||
2. `ModelSignal` is a `WritableSignal` which means that its value can be changed from anywhere
|
||||
using the `set` and `update` methods. When a new value is assigned, the `ModelSignal` will emit
|
||||
to its output. This is different from `InputSignal` which is read-only and can only be changed
|
||||
through the template.
|
||||
3. Model inputs do not support input transforms while signal inputs do.
|
||||
|
||||
## When to use model inputs
|
||||
|
||||
Use model inputs in components that exist to modify a value based on user interaction.
|
||||
Custom form controls, such as a date picker or combobox, should use model inputs for their
|
||||
primary value.
|
||||
|
||||
Avoid using model inputs as a convenience to avoid introducing an additional class property for
|
||||
containing local state.
|
||||
@@ -0,0 +1,191 @@
|
||||
# Signal queries
|
||||
|
||||
A component or directive can define queries that find child elements and read values from their injectors.
|
||||
|
||||
Developers most commonly use queries to retrieve references to components, directives, DOM elements, and more.
|
||||
|
||||
There are two categories of query: view queries and content queries.
|
||||
|
||||
Signal queries supply query results as a reactive signal primitive. You can use query results in `computed` and `effect`, composing these results with other signals.
|
||||
|
||||
IMPORTANT: Signal queries are in [developer preview](reference/releases#developer-preview). APIs may change based on feedback without going through Angular's deprecation cycle.
|
||||
|
||||
If you're already familiar with Angular queries, you can jump straight to [Comparing signal-based queries to decorator-based queries](#comparing-signal-based-queries-to-decorator-based-queries)
|
||||
|
||||
## View queries
|
||||
|
||||
View queries retrieve results from the elements in the component's own template (view).
|
||||
|
||||
### `viewChild`
|
||||
|
||||
You can declare a query targeting a single result with the `viewChild` function.
|
||||
|
||||
```ts
|
||||
@Component({
|
||||
template: `
|
||||
<div #el></div>
|
||||
<my-component />
|
||||
`
|
||||
})
|
||||
export class TestComponent {
|
||||
// query for a single result by a string predicate
|
||||
divEl = viewChild<ElementRef>('el'); // Signal<ElementRef|undefined>
|
||||
// query for a single result by a type predicate
|
||||
cmp = viewChild(MyComponent); // Signal<MyComponent|undefined>
|
||||
}
|
||||
```
|
||||
|
||||
### `viewChildren`
|
||||
|
||||
You can also query for multiple results with the `viewChildren` function.
|
||||
|
||||
```ts
|
||||
@Component({
|
||||
template: `
|
||||
<div #el></div>
|
||||
@if (show) {
|
||||
<div #el></div>
|
||||
}
|
||||
`
|
||||
})
|
||||
export class TestComponent {
|
||||
show = true;
|
||||
|
||||
// query for multiple results
|
||||
divEls = viewChildren<ElementRef>('el'); // Signal<ReadonlyArray<ElementRef>>
|
||||
}
|
||||
```
|
||||
|
||||
### View query options
|
||||
|
||||
The `viewChild` and the `viewChildren` query declaration functions have a similar signature accepting two arguments:
|
||||
|
||||
* a **locator** to specify the query target - it can be either a `string` or any injectable token
|
||||
* a set of **options** to adjust behavior of a given query.
|
||||
|
||||
Signal-based view queries accept only one option: `read`. The `read` option indicates the type of result to inject from the matched nodes and return in the final results.
|
||||
|
||||
```ts
|
||||
@Component({
|
||||
template: `<my-component/>`
|
||||
})
|
||||
export class TestComponent {
|
||||
// query for a single result with options
|
||||
cmp = viewChild(MyComponent, {read: ElementRef}); // Signal<ElementRef|undefined>
|
||||
}
|
||||
```
|
||||
|
||||
## Content queries
|
||||
|
||||
Content queries retrieve results from the elements in the component's content — the elements nested inside the component tag in the template where it's used.
|
||||
|
||||
### `contentChild`
|
||||
|
||||
You can query for a single result with the `contentChild` function.
|
||||
|
||||
```ts
|
||||
@Component({...})
|
||||
export class TestComponent {
|
||||
// query by a string predicate
|
||||
headerEl = contentChild<ElementRef>('h'); // Signal<ElementRef|undefined>
|
||||
|
||||
// query by a type predicate
|
||||
header = contentChild(MyHeader); // Signal<MyHeader|undefined>
|
||||
}
|
||||
```
|
||||
|
||||
### `contentChildren`
|
||||
|
||||
You can also query for multiple results with the `contentChildren` function.
|
||||
|
||||
```ts
|
||||
@Component({...})
|
||||
export class TestComponent {
|
||||
// query for multiple results
|
||||
divEls = contentChildren<ElementRef>('h'); // Signal<ReadonlyArray<ElementRef>>
|
||||
}
|
||||
```
|
||||
|
||||
### Content query options
|
||||
|
||||
The `contentChild` and the `contentChildren` query declaration functions have a similar signature accepting two arguments:
|
||||
|
||||
* a **locator** to specify the query target - it can be either a `string` or any injectable token
|
||||
* a set of **options** to adjust behavior of a given query.
|
||||
|
||||
Content queries accept the following options:
|
||||
|
||||
* `descendants` By default, content queries find only direct children of the component and do not traverse into descendants. If this option is changed to `true`, query results will include all descendants of the element. Even when `true`, however, queries _never_ descend into components.
|
||||
* `read` indicates the type of result to retrieve from the matched nodes and return in the final results.
|
||||
|
||||
### Required child queries
|
||||
|
||||
If a child query (`viewChild` or `contentChild`) does not find a result, its value is `undefined`. This may occur if the target element is hidden by a control flow statement like`@if` or `@for`.
|
||||
|
||||
Because of this, the child queries return a signal that potentially have the `undefined` value. Most of the time, and especially for the view child queries, developers author their code such that:
|
||||
* there is at least one matching result;
|
||||
* results are accessed when the template was processed and query results are available.
|
||||
|
||||
For such cases, you can mark child queries as `required` to enforce presence of at least one matching result. This eliminates `undefined` from the result type signature. If a `required` query does not find any results, Angular throws an error.
|
||||
|
||||
```ts
|
||||
@Component({
|
||||
selector: 'app-root',
|
||||
standalone: true,
|
||||
template: `
|
||||
<div #requiredEl></div>
|
||||
`,
|
||||
})
|
||||
export class App {
|
||||
existingEl = viewChild.required('requiredEl'); // required and existing result
|
||||
missingEl = viewChild.required('notInATemplate'); // required but NOT existing result
|
||||
|
||||
ngAfterViewInit() {
|
||||
console.log(this.existingEl()); // OK :-)
|
||||
console.log(this.missingEl()); // Runtime error: result marked as required by not available!
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Results availability timing
|
||||
|
||||
A signal query authoring functions will be executed as part of the directive instance construction. This happens before we could create a query instance and execute the template’s creation mode to collect any matches. As a consequence, there is a period of time where the signal instance was created (and can be read) but no query results could have been collected. By default Angular will return `undefined` (for child queries) or an empty array (for children queries) before results are available. Required queries will throw if accessed at this point.
|
||||
|
||||
Angular computes signal-based query results lazily, on demand. This means that query results are not collected unless there is a code path that reads the signal.
|
||||
|
||||
Query results can change over time due to the view manipulation - either through the Angular's control flow (`@if`, `@for` etc.) or by the direct calls to the `ViewContainerRef` API. When you read the value from the query result signal, you can receive different values over time.
|
||||
|
||||
Note: to avoid returning incomplete query results while a template is rendered, Angular delays query resolution until it finishes rendering a given template.
|
||||
|
||||
## Query declarations functions and the associated rules
|
||||
|
||||
The `viewChild`, `contentChild`, `viewChildren` and `contentChildren` functions are special function recognized by the Angular compiler. You can use those functions to declare queries by initializing a component or a directive property. You can never call these functions outside of component and directive property initializers.
|
||||
|
||||
```ts
|
||||
@Component({
|
||||
selector: 'app-root',
|
||||
standalone: true,
|
||||
template: `
|
||||
<div #el></div>
|
||||
`,
|
||||
})
|
||||
export class App {
|
||||
el = viewChild('el'); // all good!
|
||||
|
||||
constructor() {
|
||||
const myConst = viewChild('el'); // NOT SUPPORTED
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Comparing signal-based queries to decorator-based queries
|
||||
|
||||
Signal queries are an alternative approach to the queries declared using the `@ContentChild`, `@ContentChildren`, `@ViewChild` or `@ViewChildren` decorators. The new approach exposes query results as signals which means that query results can be composed with other signals (using `computed` or `effect`) and drive change detection. Additionally, the signal-based query system offers other benefits:
|
||||
|
||||
* **More predictable timing.** You can access query results as soon as they're available.
|
||||
* **Simpler API surface.** All queries return a signal, and queries with more than one result let you work with a standard array.
|
||||
* **Improved type safety.** Fewer query use cases include `undefined` in the possible results.
|
||||
* **More accurate type inference.** TypeScript can infer more accurate types when you use a type predicate or when you specify an explicit `read` option.
|
||||
* **Lazier updates.** - Angular updates signal-based query results lazily; the framework does no work unless your code explicitly reads the query results.
|
||||
|
||||
The underlying query mechanism doesn't change much - conceptually Angular still creates singular "child" or plural "children" queries that target elements in a template (view) or content. The difference is in type of results and the exact timing of the results availability. The authoring format for declaring signal-based queries changed as well: the `viewChild`, `viewChildren`, `contentChild` and `contentChildren` functions used as initializer of class members are automatically recognized by Angular.
|
||||
@@ -2,7 +2,8 @@
|
||||
|
||||
Angular templates support *control flow blocks* that let you conditionally show, hide, and repeat elements.
|
||||
|
||||
IMPORTANT: Angular built-in control flow is in [developer preview](reference/releases#developer-preview). It is ready to try, but may change before becoming stable.
|
||||
IMPORTANT: Angular built-in control flow is in [developer preview](reference/releases#developer-preview). It is ready to
|
||||
try, but may change before becoming stable.
|
||||
|
||||
## `@if` block conditionals
|
||||
|
||||
@@ -10,66 +11,80 @@ The `@if` block conditionally displays its content when its condition expression
|
||||
|
||||
```html
|
||||
@if (a > b) {
|
||||
{{a}} is greater than {{b}}
|
||||
{{a}} is greater than {{b}}
|
||||
}
|
||||
```
|
||||
|
||||
The `@if` block might have one or more associated `@else` blocks. Immediately after an `@if` block, you can optionally specify any number of `@else if` blocks and one `@else` block:
|
||||
The `@if` block might have one or more associated `@else` blocks. Immediately after an `@if` block, you can optionally
|
||||
specify any number of `@else if` blocks and one `@else` block:
|
||||
|
||||
```html
|
||||
@if (a > b) {
|
||||
{{a}} is greater than {{b}}
|
||||
{{a}} is greater than {{b}}
|
||||
} @else if (b > a) {
|
||||
{{a}} is less than {{b}}
|
||||
{{a}} is less than {{b}}
|
||||
} @else {
|
||||
{{a}} is equal to {{b}}
|
||||
{{a}} is equal to {{b}}
|
||||
}
|
||||
```
|
||||
|
||||
### Referencing the conditional expression's result
|
||||
|
||||
The new built-in `@if` conditional supports referencing of expression results to keep a solution for common coding patterns:
|
||||
The new built-in `@if` conditional supports referencing of expression results to keep a solution for common coding
|
||||
patterns:
|
||||
|
||||
```html
|
||||
@if (users$ | async; as users) {
|
||||
{{ users.length }}
|
||||
{{ users.length }}
|
||||
}
|
||||
```
|
||||
|
||||
## `@for` block - repeaters
|
||||
|
||||
The `@for` repeatedly renders content of a block for each item in a collection. The collection can be represented as any JavaScript [iterable](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols) but there are performance advantages of using a regular `Array`. A basic `@for` loop looks like:
|
||||
The `@for` repeatedly renders content of a block for each item in a collection. The collection can be represented as any
|
||||
JavaScript [iterable](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols) but there
|
||||
are performance advantages of using a regular `Array`. A basic `@for` loop looks like:
|
||||
|
||||
```html
|
||||
@for (item of items; track item.id) {
|
||||
{{ item.name }}
|
||||
{{ item.name }}
|
||||
}
|
||||
```
|
||||
|
||||
### `track` for calculating difference of two collections
|
||||
|
||||
The value of the `track` expression determines a key used to associate array items with the views in the DOM. Having clear indication of the item identity allows Angular to execute a minimal set of DOM operations as items are added, removed or moved in a collection.
|
||||
The value of the `track` expression determines a key used to associate array items with the views in the DOM. Having
|
||||
clear indication of the item identity allows Angular to execute a minimal set of DOM operations as items are added,
|
||||
removed or moved in a collection.
|
||||
|
||||
Loops over immutable data without `trackBy` are one of the most common causes for performance issues across Angular applications. Because of the potential for poor performance, the `track` expression is required for the `@for` loops. When in doubt, using `track $index` is a good default.
|
||||
Using track effectively can significantly enhance your application's performance, especially in loops over data
|
||||
collections.
|
||||
|
||||
For collections that do not undergo modifications (no items are moved, added, or deleted), using `track $index` is an
|
||||
efficient strategy. For collections with mutable data or frequent changes, select a property that uniquely identifies
|
||||
each item to use as your track expression.
|
||||
|
||||
Be aware of the potential for increased DOM re-creation when using object identity as a track key with immutable data
|
||||
structures, as this can lead to unnecessary performance costs.
|
||||
|
||||
### `$index` and other contextual variables
|
||||
|
||||
Inside `@for` contents, several implicit variables are always available:
|
||||
|
||||
| Variable | Meaning |
|
||||
| -------- | ------- |
|
||||
| Variable | Meaning |
|
||||
|----------|-----------------------------------------------|
|
||||
| `$count` | Number of items in a collection iterated over |
|
||||
| `$index` | Index of the current row |
|
||||
| `$first` | Whether the current row is the first row |
|
||||
| `$last` | Whether the current row is the last row |
|
||||
| `$even` | Whether the current row index is even |
|
||||
| `$odd` | Whether the current row index is odd |
|
||||
| `$index` | Index of the current row |
|
||||
| `$first` | Whether the current row is the first row |
|
||||
| `$last` | Whether the current row is the last row |
|
||||
| `$even` | Whether the current row index is even |
|
||||
| `$odd` | Whether the current row index is odd |
|
||||
|
||||
These variables are always available with these names, but can be aliased via a `let` segment:
|
||||
|
||||
```html
|
||||
@for (item of items; track item.id; let idx = $index, e = $even) {
|
||||
Item #{{ idx }}: {{ item.name }}
|
||||
Item #{{ idx }}: {{ item.name }}
|
||||
}
|
||||
```
|
||||
|
||||
@@ -77,13 +92,14 @@ The aliasing is especially useful in case of using nested `@for` blocks where co
|
||||
|
||||
### `empty` block
|
||||
|
||||
You can optionally include an `@empty` section immediately after the `@for` block content. The content of the `@empty` block displays when there are no items:
|
||||
You can optionally include an `@empty` section immediately after the `@for` block content. The content of the `@empty`
|
||||
block displays when there are no items:
|
||||
|
||||
```html
|
||||
@for (item of items; track item.name) {
|
||||
<li> {{ item.name }} </li>
|
||||
<li> {{ item.name }}</li>
|
||||
} @empty {
|
||||
<li> There are no items. </li>
|
||||
<li> There are no items.</li>
|
||||
}
|
||||
```
|
||||
|
||||
@@ -93,15 +109,15 @@ The syntax for `switch` is very similar to `if`, and is inspired by the JavaScri
|
||||
|
||||
```html
|
||||
@switch (condition) {
|
||||
@case (caseA) {
|
||||
Case A.
|
||||
}
|
||||
@case (caseB) {
|
||||
Case B.
|
||||
}
|
||||
@default {
|
||||
Default case.
|
||||
}
|
||||
@case (caseA) {
|
||||
Case A.
|
||||
}
|
||||
@case (caseB) {
|
||||
Case B.
|
||||
}
|
||||
@default {
|
||||
Default case.
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
@@ -109,7 +125,8 @@ The value of the conditional expression is compared to the case expression using
|
||||
|
||||
**`@switch` does not have fallthrough**, so you do not need an equivalent to a `break` or `return` statement.
|
||||
|
||||
The `@default` block is optional and can be omitted. If no `@case` matches the expression and there is no `@default` block, nothing is shown.
|
||||
The `@default` block is optional and can be omitted. If no `@case` matches the expression and there is no `@default`
|
||||
block, nothing is shown.
|
||||
|
||||
## Built-in control flow and the `NgIf`, `NgSwitch` and `NgFor` structural directives
|
||||
|
||||
@@ -120,16 +137,21 @@ The `@switch` block replaces `ngSwitch` with major benefits:
|
||||
* it does not require a container element to hold the condition expression or each conditional template;
|
||||
* it supports template type-checking, including type narrowing within each branch.
|
||||
|
||||
The `@for` block replaces `*ngFor` for iteration, and has several differences compared to its structural directive `NgFor` predecessor:
|
||||
The `@for` block replaces `*ngFor` for iteration, and has several differences compared to its structural
|
||||
directive `NgFor` predecessor:
|
||||
|
||||
* tracking expression (calculating keys corresponding to object identities) is mandatory but has better ergonomics (it is enough to write an expression instead of creating the `trackBy` method);
|
||||
* uses a new optimized algorithm for calculating a minimal number of DOM operations to be performed in response to changes in a collection, instead of Angular’s customizable diffing implementation (`IterableDiffer`);
|
||||
* tracking expression (calculating keys corresponding to object identities) is mandatory but has better ergonomics (it
|
||||
is enough to write an expression instead of creating the `trackBy` method);
|
||||
* uses a new optimized algorithm for calculating a minimal number of DOM operations to be performed in response to
|
||||
changes in a collection, instead of Angular’s customizable diffing implementation (`IterableDiffer`);
|
||||
* has support for `@empty` blocks.
|
||||
|
||||
The `track` setting replaces `NgFor`'s concept of a `trackBy` function. Because `@for` is built-in, we can provide a better experience than passing a `trackBy` function, and directly use an expression representing the key instead. Migrating from `trackBy` to `track` is possible by invoking the `trackBy` function:
|
||||
The `track` setting replaces `NgFor`'s concept of a `trackBy` function. Because `@for` is built-in, we can provide a
|
||||
better experience than passing a `trackBy` function, and directly use an expression representing the key instead.
|
||||
Migrating from `trackBy` to `track` is possible by invoking the `trackBy` function:
|
||||
|
||||
```html
|
||||
@for (item of items; track itemId($index, item)) {
|
||||
{{ item.name }}
|
||||
{{ item.name }}
|
||||
}
|
||||
```
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
load("@npm//@angular/docs:index.bzl", "generate_guides")
|
||||
|
||||
generate_guides(
|
||||
name = "concepts",
|
||||
srcs = glob([
|
||||
"*.md",
|
||||
]),
|
||||
visibility = ["//adev:__subpackages__"],
|
||||
)
|
||||
@@ -0,0 +1,7 @@
|
||||
# Concepts
|
||||
|
||||
<docs-card-container>
|
||||
<docs-card title="NgModules" link="Learn more" href="guide/ngmodules">
|
||||
NgModules is a concept that commonly used in architecture v16 and earlier to help configure the injector and the compiler and help organize related things together.
|
||||
</docs-card>
|
||||
</docs-card-container>
|
||||
@@ -1,6 +1,6 @@
|
||||
# The MIT License
|
||||
|
||||
Copyright (c) 2010-2023 Google LLC. https://angular.dev/license
|
||||
Copyright (c) 2010-2024 Google LLC. https://angular.dev/license
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ Angular CLI includes four builders typically used as `build` targets:
|
||||
| `@angular-devkit/build-angular:application` | Builds an application with a client-side bundle, a Node server, and build-time prerendered routes with [esbuild](https://esbuild.github.io/). |
|
||||
| `@angular-devkit/build-angular:ng-packagr` | Builds an Angular library adhering to [Angular Package Format](tools/libraries/angular-package-format). |
|
||||
|
||||
Applications generated by `ng new` use `@angular-devkit/build-angular:browser` by default.
|
||||
Applications generated by `ng new` use `@angular-devkit/build-angular:application` by default.
|
||||
Libraries generated by `ng generate library` use `@angular-devkit/build-angular:ng-packagr` by default.
|
||||
|
||||
You can determine which builder is being used for a particular project by looking up the `build` target for that project.
|
||||
@@ -26,7 +26,7 @@ You can determine which builder is being used for a particular project by lookin
|
||||
"architect": {
|
||||
// `ng build` invokes the Architect target named `build`.
|
||||
"build": {
|
||||
"builder": "@angular-devkit/build-angular:browser",
|
||||
"builder": "@angular-devkit/build-angular:application",
|
||||
…
|
||||
},
|
||||
"serve": { … }
|
||||
@@ -39,7 +39,7 @@ You can determine which builder is being used for a particular project by lookin
|
||||
|
||||
</docs-code>
|
||||
|
||||
This page discusses usage and options of `@angular-devkit/build-angular:browser`.
|
||||
This page discusses usage and options of `@angular-devkit/build-angular:application`.
|
||||
|
||||
## Output directory
|
||||
|
||||
|
||||
+1259
-922
File diff suppressed because it is too large
Load Diff
@@ -9,21 +9,21 @@
|
||||
},
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@angular/animations": "^17.2.0-next",
|
||||
"@angular/common": "^17.2.0-next",
|
||||
"@angular/compiler": "^17.2.0-next",
|
||||
"@angular/core": "^17.2.0-next",
|
||||
"@angular/forms": "^17.2.0-next",
|
||||
"@angular/platform-browser": "^17.2.0-next",
|
||||
"@angular/router": "^17.2.0-next",
|
||||
"@angular/animations": "^17.2.0",
|
||||
"@angular/common": "^17.2.0",
|
||||
"@angular/compiler": "^17.2.0",
|
||||
"@angular/core": "^17.2.0",
|
||||
"@angular/forms": "^17.2.0",
|
||||
"@angular/platform-browser": "^17.2.0",
|
||||
"@angular/router": "^17.2.0",
|
||||
"rxjs": "~7.8.0",
|
||||
"tslib": "^2.3.0",
|
||||
"zone.js": "~0.14.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@angular-devkit/build-angular": "^17.2.0-next",
|
||||
"@angular/cli": "^17.2.0-next",
|
||||
"@angular/compiler-cli": "^17.2.0-next",
|
||||
"@angular-devkit/build-angular": "^17.2.0",
|
||||
"@angular/cli": "^17.2.0",
|
||||
"@angular/compiler-cli": "^17.2.0",
|
||||
"@types/jasmine": "~5.1.0",
|
||||
"@types/node": "^16.11.35",
|
||||
"copyfiles": "^2.4.1",
|
||||
|
||||
+927
-529
File diff suppressed because it is too large
Load Diff
@@ -9,19 +9,19 @@
|
||||
},
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@angular/common": "^17.2.0-next",
|
||||
"@angular/compiler": "^17.2.0-next",
|
||||
"@angular/core": "^17.2.0-next",
|
||||
"@angular/forms": "^17.2.0-next",
|
||||
"@angular/platform-browser": "^17.2.0-next",
|
||||
"@angular/common": "^17.2.0",
|
||||
"@angular/compiler": "^17.2.0",
|
||||
"@angular/core": "^17.2.0",
|
||||
"@angular/forms": "^17.2.0",
|
||||
"@angular/platform-browser": "^17.2.0",
|
||||
"rxjs": "~7.8.0",
|
||||
"tslib": "^2.3.0",
|
||||
"zone.js": "~0.14.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@angular-devkit/build-angular": "^17.2.0-next",
|
||||
"@angular/cli": "^17.2.0-next",
|
||||
"@angular/compiler-cli": "^17.2.0-next",
|
||||
"@angular-devkit/build-angular": "^17.2.0",
|
||||
"@angular/cli": "^17.2.0",
|
||||
"@angular/compiler-cli": "^17.2.0",
|
||||
"typescript": "~5.2.0"
|
||||
}
|
||||
}
|
||||
|
||||
+934
-536
File diff suppressed because it is too large
Load Diff
@@ -9,20 +9,20 @@
|
||||
},
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@angular/common": "^17.2.0-next",
|
||||
"@angular/compiler": "^17.2.0-next",
|
||||
"@angular/core": "^17.2.0-next",
|
||||
"@angular/forms": "^17.2.0-next",
|
||||
"@angular/platform-browser": "^17.2.0-next",
|
||||
"@angular/router": "^17.2.0-next",
|
||||
"@angular/common": "^17.2.0",
|
||||
"@angular/compiler": "^17.2.0",
|
||||
"@angular/core": "^17.2.0",
|
||||
"@angular/forms": "^17.2.0",
|
||||
"@angular/platform-browser": "^17.2.0",
|
||||
"@angular/router": "^17.2.0",
|
||||
"rxjs": "~7.8.0",
|
||||
"tslib": "^2.3.0",
|
||||
"zone.js": "~0.14.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@angular-devkit/build-angular": "^17.2.0-next",
|
||||
"@angular/cli": "^17.2.0-next",
|
||||
"@angular/compiler-cli": "^17.2.0-next",
|
||||
"@angular-devkit/build-angular": "^17.2.0",
|
||||
"@angular/cli": "^17.2.0",
|
||||
"@angular/compiler-cli": "^17.2.0",
|
||||
"typescript": "~5.2.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,4 +29,4 @@ If you get stuck, click "Reveal answer" at the top.
|
||||
</docs-card>
|
||||
</docs-card-container>
|
||||
|
||||
Alright, let's [get started](/tutorials/learn-angular/components-in-angular).
|
||||
Alright, let's [get started](/tutorials/learn-angular/1-components-in-angular).
|
||||
|
||||
@@ -8,7 +8,7 @@ In this activity you will learn to use constructor-based dependency injection.
|
||||
|
||||
<hr>
|
||||
|
||||
To inject a service or some other injectable resource into your componenet use the following syntax:
|
||||
To inject a service or some other injectable resource into your component use the following syntax:
|
||||
|
||||
<docs-code language="ts" highlight="[3]">
|
||||
@Component({...})
|
||||
|
||||
+948
-550
File diff suppressed because it is too large
Load Diff
@@ -9,22 +9,22 @@
|
||||
},
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@angular/animations": "^17.2.0-next",
|
||||
"@angular/cdk": "^17.2.0-next",
|
||||
"@angular/common": "^17.2.0-next",
|
||||
"@angular/compiler": "^17.2.0-next",
|
||||
"@angular/core": "^17.2.0-next",
|
||||
"@angular/forms": "^17.2.0-next",
|
||||
"@angular/material": "^17.2.0-next",
|
||||
"@angular/platform-browser": "^17.2.0-next",
|
||||
"@angular/animations": "^17.2.0",
|
||||
"@angular/cdk": "^17.2.0",
|
||||
"@angular/common": "^17.2.0",
|
||||
"@angular/compiler": "^17.2.0",
|
||||
"@angular/core": "^17.2.0",
|
||||
"@angular/forms": "^17.2.0",
|
||||
"@angular/material": "^17.2.0",
|
||||
"@angular/platform-browser": "^17.2.0",
|
||||
"rxjs": "~7.8.0",
|
||||
"tslib": "^2.3.0",
|
||||
"zone.js": "~0.14.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@angular-devkit/build-angular": "^17.2.0-next",
|
||||
"@angular/cli": "^17.2.0-next",
|
||||
"@angular/compiler-cli": "^17.2.0-next",
|
||||
"@angular-devkit/build-angular": "^17.2.0",
|
||||
"@angular/cli": "^17.2.0",
|
||||
"@angular/compiler-cli": "^17.2.0",
|
||||
"typescript": "~5.2.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,43 +4,55 @@ The `@for` block repeatedly renders content of a block for each item in a collec
|
||||
|
||||
```html
|
||||
@for (item of items; track item.name) {
|
||||
<li> {{ item.name }} </li>
|
||||
<li>{{ item.name }}</li>
|
||||
} @empty {
|
||||
<li> There are no items. </li>
|
||||
<li>There are no items.</li>
|
||||
}
|
||||
```
|
||||
|
||||
@description
|
||||
|
||||
The `@for` block renders its content in response to changes in a collection. Collections can be any JavaScript [iterable](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols), but there are performance advantages of using a regular `Array`.
|
||||
The `@for` block renders its content in response to changes in a collection. Collections can be any
|
||||
JavaScript [iterable](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols),
|
||||
but there are performance advantages of using a regular `Array`.
|
||||
|
||||
You can optionally include an `@empty` section immediately after the `@for` block content. The content of the `@empty` block displays when there are no items.
|
||||
You can optionally include an `@empty` section immediately after the `@for` block content. The
|
||||
content of the `@empty` block displays when there are no items.
|
||||
|
||||
<h3> track and objects identity </h3>
|
||||
|
||||
The value of the `track` expression determines a key used to associate array items with the views in the DOM. Having clear indication of the item identity allows Angular to execute a minimal set of DOM operations as items are added, removed or moved in a collection.
|
||||
The value of the `track` expression determines a key used to associate array items with the views in
|
||||
the DOM. Having clear indication of the item identity allows Angular to execute a minimal set of DOM
|
||||
operations as items are added, removed or moved in a collection.
|
||||
|
||||
Loops over immutable data without `trackBy` as one of the most common causes for performance issues across Angular applications. Because of the potential for poor performance, the `track` expression is required for the `@for` loops. When in doubt, using `track $index` is a good default.
|
||||
To optimize performance, especially in loops over immutable data, ensure the track expression is effectively used to
|
||||
identify each item uniquely. Because of the potential for poor performance, the `track` expression
|
||||
is required for the `@for` loops.
|
||||
|
||||
For collections that remain static , `track $index` provides a straightforward tracking mechanism. For dynamic
|
||||
collections experiencing additions, deletions, or reordering, opt for a
|
||||
unique property of each item as the tracking key.
|
||||
|
||||
<h3> `$index` and other contextual variables </h3>
|
||||
|
||||
Inside `@for` contents, several implicit variables are always available:
|
||||
Inside `@for` contents, several implicit variables are always available:
|
||||
|
||||
| Variable | Meaning |
|
||||
| -------- | ------- |
|
||||
| Variable | Meaning |
|
||||
|----------|-----------------------------------------------|
|
||||
| `$count` | Number of items in a collection iterated over |
|
||||
| `$index` | Index of the current row |
|
||||
| `$first` | Whether the current row is the first row |
|
||||
| `$last` | Whether the current row is the last row |
|
||||
| `$even` | Whether the current row index is even |
|
||||
| `$odd` | Whether the current row index is odd |
|
||||
| `$index` | Index of the current row |
|
||||
| `$first` | Whether the current row is the first row |
|
||||
| `$last` | Whether the current row is the last row |
|
||||
| `$even` | Whether the current row index is even |
|
||||
| `$odd` | Whether the current row index is odd |
|
||||
|
||||
These variables are always available with these names, but can be aliased via a `let` segment:
|
||||
|
||||
```html
|
||||
@for (item of items; track item.id; let idx = $index, e = $even) {
|
||||
Item #{{ idx }}: {{ item.name }}
|
||||
Item #{{ idx }}: {{ item.name }}
|
||||
}
|
||||
```
|
||||
|
||||
The aliasing is especially useful in case of using nested `@for` blocks where contextual variable names could collide.
|
||||
The aliasing is especially useful in case of using nested `@for` blocks where contextual variable
|
||||
names could collide.
|
||||
|
||||
@@ -13,7 +13,7 @@ comprehensive details on deprecations and breaking changes.
|
||||
<div class="alert is-helpful">
|
||||
|
||||
Features and APIs that were deprecated in v6 or earlier are candidates for removal in version 9 or any later major version.
|
||||
For information about Angular's deprecation and removal practices, see [Angular Release Practices](guide/releases#deprecation-practices "Angular Release Practices: Deprecation practices").
|
||||
For information about Angular's deprecation and removal practices, see [Angular Release Practices](guide/releases#deprecation-policy "Angular Release Practices: Deprecation policy").
|
||||
|
||||
For step-by-step instructions on how to update to the latest Angular release, use the interactive update guide at [update.angular.io](https://update.angular.io).
|
||||
|
||||
|
||||
@@ -124,8 +124,7 @@ export class SpecializedMenuWithTooltip { }
|
||||
### Directive execution order
|
||||
|
||||
Host directives go through the same lifecycle as components and directives used directly in a
|
||||
template. However, host directives always execute their constructor, lifecycle hooks, and bindings _
|
||||
before_ the component or directive on which they are applied.
|
||||
template. However, host directives always execute their constructor, lifecycle hooks, and bindings _before_ the component or directive on which they are applied.
|
||||
|
||||
The following example shows minimal use of a host directive:
|
||||
|
||||
|
||||
@@ -110,7 +110,7 @@ This form differs from the template-driven version in that it no longer exports
|
||||
Instead, it uses the `name` getter defined in the component class.
|
||||
|
||||
Notice that the `required` attribute is still present in the template.
|
||||
Although it's not necessary for validation, it should be retained to for accessibility purposes.
|
||||
Although it's not necessary for validation, it should be retained for accessibility purposes.
|
||||
|
||||
<a id="custom-validators"></a>
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ The localization process includes the following actions.
|
||||
* Format data for a specific locale
|
||||
|
||||
A *locale* identifies a region in which people speak a particular language or language variant.
|
||||
Possible regions includes countries and geographical regions.
|
||||
Possible regions include countries and geographical regions.
|
||||
A locale determines the formatting and parsing of the following details.
|
||||
|
||||
* Measurement units including date and time, numbers, and currencies
|
||||
|
||||
@@ -338,7 +338,7 @@ Note: even though the `width` property may not always be present, a custom loade
|
||||
|
||||
### The `loaderParams` Property
|
||||
|
||||
There is an additional attribute supported by the `NgOptimizedImage` directive, called `loaderParams`, which is specifically designed to support the use of custom loaders. The `loaderParams` attribute take an object with any properties as a value, and does not do anything on its own. The data in `loaderParams` is added to the `ImageLoaderConfig` object passed to your custom loader, and can be used to control the behavior of the loader.
|
||||
There is an additional attribute supported by the `NgOptimizedImage` directive, called `loaderParams`, which is specifically designed to support the use of custom loaders. The `loaderParams` attribute takes an object with any properties as a value, and does not do anything on its own. The data in `loaderParams` is added to the `ImageLoaderConfig` object passed to your custom loader, and can be used to control the behavior of the loader.
|
||||
|
||||
A common use for `loaderParams` is controlling advanced image CDN features.
|
||||
|
||||
@@ -402,6 +402,10 @@ providers: [
|
||||
|
||||
If you use a variable to pass the domain string to the loader, or you're not using a loader, the static analysis will not be able to identify the domain, and no preconnect link will be generated. In this case you should manually add a preconnect link to the document head, as [described above.](#resource-hints).
|
||||
|
||||
### Can you add a new built-in loader for my preferred CDN?
|
||||
|
||||
For maintenance reasons, we don't currently plan to support additional built-in loaders in the Angular repository. Instead, we encourage developers to publish any additional image loaders as third-party packages.
|
||||
|
||||
<!-- links -->
|
||||
|
||||
<!-- external links -->
|
||||
|
||||
@@ -25,6 +25,13 @@ The `<parent-component>` serves as the context for the `<child-component>`.
|
||||
`@Input()` lets a parent component update data in the child component.
|
||||
Conversely, `@Output()` lets the child send data to a parent component.
|
||||
|
||||
<div class="alert is-helpful">
|
||||
|
||||
Signal-based inputs are available in developer preview and may be a better fit for some use cases as an alternative to `@Input`.
|
||||
Learn more in the [signal inputs guide](/guide/signal-inputs).
|
||||
|
||||
</div>
|
||||
|
||||
<a id="input"></a>
|
||||
|
||||
## Sending data to a child component
|
||||
@@ -214,11 +221,11 @@ To make `Input` property as required for a child component while passing values
|
||||
|
||||
<code-example header="src/app/item-details-metadata.component.ts" path="inputs-outputs/src/app/item-details-metadata.component.ts" region="use-input-metadata-required"></code-example>
|
||||
|
||||
Next, in the parent template add the following:
|
||||
Next, in the parent template add the following:
|
||||
|
||||
<code-example header="src/app/app.component.html" path="inputs-outputs/src/app/app.component.html" region="input-parent-metadata"></code-example>
|
||||
|
||||
If required inputs in a child component are not specified in the parent component template will result a compile time error:
|
||||
If required inputs in a child component are not specified in the parent component template will result a compile time error:
|
||||
|
||||
<div class="alert is-helpful">
|
||||
NG8008: Required input item from component ItemDetailMetadataComponent must be specified.
|
||||
|
||||
@@ -0,0 +1,142 @@
|
||||
# Model inputs
|
||||
|
||||
**Model inputs** are a special type of input that enable a component to propagate new values
|
||||
back to another component.
|
||||
|
||||
<div class="alert is-helpful">
|
||||
|
||||
Model inputs are currently in [developer preview](/guide/releases#developer-preview).
|
||||
|
||||
</div>
|
||||
|
||||
When creating a component, you can define a model input similarly to how you create a standard
|
||||
input.
|
||||
|
||||
```typescript
|
||||
import {Component, model, input} from '@angular/core';
|
||||
|
||||
@Component({...})
|
||||
export class CustomCheckbox {
|
||||
// This is a model input.
|
||||
checked = model(false);
|
||||
|
||||
// This is a standard input.
|
||||
disabled = input(false);
|
||||
}
|
||||
```
|
||||
|
||||
Both types of input allow someone to bind a value into the property. However, **model inputs allow
|
||||
the component author to write values into the property**.
|
||||
|
||||
In other respects, you can use model inputs the same way you use standard inputs. You can read the
|
||||
value by calling the signal function, including in reactive contexts like `computed` and `effect`.
|
||||
|
||||
```typescript
|
||||
import {Component, model, input} from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'custom-checkbox',
|
||||
template: '<div (click)="toggle()"> ... </div>',
|
||||
})
|
||||
export class CustomCheckbox {
|
||||
checked = model(false);
|
||||
disabled = input(false);
|
||||
|
||||
toggle() {
|
||||
// While standard inputs are read-only, you can write directly to model inputs.
|
||||
this.checked.set(!this.checked());
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
When a component writes a new value into a model input, Angular can propagate the new value back
|
||||
to the component that is binding a value into that input. This is called **two-way binding** because
|
||||
values can flow in both directions.
|
||||
|
||||
## Two-way binding with signals
|
||||
|
||||
You can bind a writable signal to a model input.
|
||||
|
||||
```typescript
|
||||
@Component({
|
||||
...,
|
||||
// `checked` is a model input.
|
||||
// The parenthesis-inside-square-brackets syntax (aka "banana-in-a-box") creates a two-way binding
|
||||
template: '<custom-checkbox [(checked)]="isAdmin" />',
|
||||
})
|
||||
export class UserProfile {
|
||||
protected isAdmin = signal(false);
|
||||
}
|
||||
```
|
||||
|
||||
In the above example, the `CustomCheckbox` can write values into its `checked` model input, which
|
||||
then propagates those values back to the `isAdmin` signal in `UserProfile`. This binding keeps that
|
||||
values of `checked` and `isAdmin` in sync. Notice that the binding passes the `isAdmin` signal
|
||||
itself, not the _value_ of the signal.
|
||||
|
||||
## Two-way binding with plain properties
|
||||
|
||||
You can bind a plain JavaScript property to a model input.
|
||||
|
||||
```typescript
|
||||
@Component({
|
||||
...,
|
||||
// `checked` is a model input.
|
||||
// The parenthesis-inside-square-brackets syntax (aka "banana-in-a-box") creates a two-way binding
|
||||
template: '<custom-checkbox [(checked)]="isAdmin" />',
|
||||
})
|
||||
export class UserProfile {
|
||||
protected isAdmin = false;
|
||||
}
|
||||
```
|
||||
|
||||
In the example above, the `CustomCheckbox` can write values into its `checked` model input, which
|
||||
then propagates those values back to the `isAdmin` property in `UserProfile`. This binding keeps
|
||||
that values of `checked` and `isAdmin` in sync.
|
||||
|
||||
## Implicit `change` events
|
||||
|
||||
When you declare a model input in a component or directive, Angular automatically creates a
|
||||
corresponding [output](/guide/inputs-outputs) for that model. The output's name is the model
|
||||
input's name suffixed with "Change".
|
||||
|
||||
```typescript
|
||||
@Directive({...})
|
||||
export class CustomCheckbox {
|
||||
// This automatically creates an output named "checkedChange".
|
||||
// Can be subscribed to using `(checkedChange)="handler()"` in the template.
|
||||
checked = model(false);
|
||||
}
|
||||
```
|
||||
|
||||
Angular emits this change event whenever you write a new value into the model input by calling
|
||||
its `set` or `update` methods.
|
||||
|
||||
## Customizing model inputs
|
||||
|
||||
You can mark a model input as required or provide an alias in the same way as a
|
||||
[standard input](/guide/signal-inputs).
|
||||
|
||||
Model inputs do not support input transforms.
|
||||
|
||||
## Differences between `model()` and `input()`
|
||||
|
||||
Both `input()` and `model()` functions are ways to define signal-based inputs in Angular, but they
|
||||
differ in a few ways:
|
||||
1. `model()` defines **both** an input and an output. The output's name is always the name of the
|
||||
input suffixed with `Change` to support two-way bindings. It will be up to the consumer of your
|
||||
directive to decide if they want to use just the input, just the output, or both.
|
||||
2. `ModelSignal` is a `WritableSignal` which means that its value can be changed from anywhere
|
||||
using the `set` and `update` methods. When a new value is assigned, the `ModelSignal` will emit
|
||||
to its output. This is different from `InputSignal` which is read-only and can only be changed
|
||||
through the template.
|
||||
3. Model inputs do not support input transforms while signal inputs do.
|
||||
|
||||
## When to use model inputs
|
||||
|
||||
Use model inputs in components that exist to modify a value based on user interaction.
|
||||
Custom form controls, such as a date picker or combobox, should use model inputs for their
|
||||
primary value.
|
||||
|
||||
Avoid using model inputs as a convenience to avoid introducing an additional class property for
|
||||
containing local state.
|
||||
@@ -137,7 +137,7 @@ To add a form group to this component, take the following steps.
|
||||
|:--- |:--- |
|
||||
| Create a `FormGroup` instance | Create a property in the component class named `profileForm` and set the property to a new form group instance. To initialize the form group, provide the constructor with an object of named keys mapped to their control. <br /> For the profile form, add two form control instances with the names `firstName` and `lastName`. <code-example header="src/app/profile-editor/profile-editor.component.ts (form group)" path="reactive-forms/src/app/profile-editor/profile-editor.component.1.ts" region="formgroup"></code-example> The individual form controls are now collected within a group. A `FormGroup` instance provides its model value as an object reduced from the values of each control in the group. A form group instance has the same properties \(such as `value` and `untouched`\) and methods \(such as `setValue()`\) as a form control instance. |
|
||||
| Associate the `FormGroup` model and view | A form group tracks the status and changes for each of its controls, so if one of the controls changes, the parent control also emits a new status or value change. The model for the group is maintained from its members. After you define the model, you must update the template to reflect the model in the view. <code-example header="src/app/profile-editor/profile-editor.component.html (template form group)" path="reactive-forms/src/app/profile-editor/profile-editor.component.1.html" region="formgroup"></code-example> <div class="alert is-helpful"> **NOTE**: <br /> Just as a form group contains a group of controls, the *profileForm* `FormGroup` is bound to the `form` element with the `FormGroup` directive, creating a communication layer between the model and the form containing the inputs. </div> The `formControlName` input provided by the `FormControlName` directive binds each individual input to the form control defined in `FormGroup`. The form controls communicate with their respective elements. They also communicate changes to the form group instance, which provides the source of truth for the model value. |
|
||||
| Save form data | The `ProfileEditor` component accepts input from the user, but in a real scenario you want to capture the form value and make available for further processing outside the component. The `FormGroup` directive listens for the `submit` event emitted by the `form` element and emits an `ngSubmit` event that you can bind to a callback function. Add an `ngSubmit` event listener to the `form` tag with the `onSubmit()` callback method. <code-example header="src/app/profile-editor/profile-editor.component.html (submit event)" path="reactive-forms/src/app/profile-editor/profile-editor.component.html" region="ng-submit"></code-example> The `onSubmit()` method in the `ProfileEditor` component captures the current value of `profileForm`. Use `EventEmitter` to keep the form encapsulated and to provide the form value outside the component. The following example uses `console.warn` to log a message to the browser console. <code-example header="src/app/profile-editor/profile-editor.component.ts (submit method)" path="reactive-forms/src/app/profile-editor/profile-editor.component.ts" region="on-submit"></code-example> The `submit` event is emitted by the `form` tag using the built-in DOM event. You trigger the event by clicking a button with `submit` type. This lets the user press the **Enter** key to submit the completed form. <br /> Use a `button` element to add a button to the bottom of the form to trigger the form submission. <code-example header="src/app/profile-editor/profile-editor.component.html (submit button)" path="reactive-forms/src/app/profile-editor/profile-editor.component.html" region="submit-button"></code-example> <div class="alert is-helpful"> **NOTE**: <br /> The button in the preceding snippet also has a `disabled` binding attached to it to disable the button when `profileForm` is invalid. You aren't performing any validation yet, so the button is always enabled. Basic form validation is covered in the [Validating form input](#basic-form-validation "Basic form validation.") section. </div> |
|
||||
| Save form data | The `ProfileEditor` component accepts input from the user, but in a real scenario you want to capture the form value and make it available for further processing outside the component. The `FormGroup` directive listens for the `submit` event emitted by the `form` element and emits an `ngSubmit` event that you can bind to a callback function. Add an `ngSubmit` event listener to the `form` tag with the `onSubmit()` callback method. <code-example header="src/app/profile-editor/profile-editor.component.html (submit event)" path="reactive-forms/src/app/profile-editor/profile-editor.component.html" region="ng-submit"></code-example> The `onSubmit()` method in the `ProfileEditor` component captures the current value of `profileForm`. Use `EventEmitter` to keep the form encapsulated and to provide the form value outside the component. The following example uses `console.warn` to log a message to the browser console. <code-example header="src/app/profile-editor/profile-editor.component.ts (submit method)" path="reactive-forms/src/app/profile-editor/profile-editor.component.ts" region="on-submit"></code-example> The `submit` event is emitted by the `form` tag using the built-in DOM event. You trigger the event by clicking a button with `submit` type. This lets the user press the **Enter** key to submit the completed form. <br /> Use a `button` element to add a button to the bottom of the form to trigger the form submission. <code-example header="src/app/profile-editor/profile-editor.component.html (submit button)" path="reactive-forms/src/app/profile-editor/profile-editor.component.html" region="submit-button"></code-example> <div class="alert is-helpful"> **NOTE**: <br /> The button in the preceding snippet also has a `disabled` binding attached to it to disable the button when `profileForm` is invalid. You aren't performing any validation yet, so the button is always enabled. Basic form validation is covered in the [Validating form input](#basic-form-validation "Basic form validation.") section. </div> |
|
||||
| Display the component | To display the `ProfileEditor` component that contains the form, add it to a component template. <code-example header="src/app/app.component.html (profile editor)" path="reactive-forms/src/app/app.component.1.html" region="app-profile-editor"></code-example> `ProfileEditor` lets you manage the form control instances for the `firstName` and `lastName` controls within the form group instance. <div class="lightbox"> <img alt="Profile Editor with labels and inputs for first and last name as well as a submit button" src="generated/images/guide/reactive-forms/profile-editor-1.gif"> </div> |
|
||||
|
||||
<a id="nested-groups"></a>
|
||||
|
||||
@@ -11,6 +11,13 @@ This document contains the practices that we follow to provide you with a leadin
|
||||
We strive to ensure that future changes are always introduced in a predictable way.
|
||||
We want everyone who depends on Angular to know when and how new features are added, and to be well-prepared when obsolete ones are removed.
|
||||
|
||||
Sometimes *breaking changes*, such as the removal of APIs or features, are necessary to innovate and stay current with evolving best practices, changing dependencies, or shifts in the web platform. These breaking changes go through a deprecation process explained in our [deprecation policy](#deprecation-policy).
|
||||
|
||||
To make these transitions as straightforward as possible, the Angular team makes these commitments:
|
||||
|
||||
* We work hard to minimize the number of breaking changes and to provide migration tools when possible
|
||||
* We follow the deprecation policy described here, so you have time to update your applications to the latest APIs and best practices
|
||||
|
||||
<div class="alert is-helpful">
|
||||
|
||||
The practices described in this document apply to Angular 2.0 and later.
|
||||
@@ -45,25 +52,6 @@ This means that in order to use the CLI as you develop an Angular app, the versi
|
||||
|
||||
</div>
|
||||
|
||||
<a id="updating"></a>
|
||||
|
||||
### Supported update paths
|
||||
|
||||
You can `ng update` to any version of Angular, provided that the following criteria are met:
|
||||
|
||||
* The version you want to update *to* is supported.
|
||||
* The version you want to update *from* is within one major version of the version you want to
|
||||
upgrade to.
|
||||
|
||||
For example, you can update from version 11 to version 12, provided that version 12 is still supported.
|
||||
If you want to update across multiple major versions, perform each update one major version at a time.
|
||||
For example, to update from version 10 to version 12:
|
||||
|
||||
1. Update from version 10 to version 11.
|
||||
1. Update from version 11 to version 12.
|
||||
|
||||
See [Keeping Up-to-Date](guide/updating "Updating your projects") for more information about updating your Angular projects to the most recent version.
|
||||
|
||||
<a id="previews"></a>
|
||||
|
||||
### Preview releases
|
||||
@@ -147,32 +135,57 @@ As a general rule, a fix is considered for an LTS version if it resolves one of:
|
||||
|
||||
<a id="deprecation"></a>
|
||||
|
||||
## Deprecation practices
|
||||
## Deprecation policy
|
||||
|
||||
Sometimes "breaking changes", such as the removal of support for select APIs and features, are necessary to innovate and stay current with new best practices, changing dependencies, or changes in the \(web\) platform itself.
|
||||
|
||||
To make these transitions as straightforward as possible, we make these commitments to you:
|
||||
|
||||
* We work hard to minimize the number of breaking changes and to provide migration tools when possible
|
||||
* We follow the deprecation policy described here, so you have time to update your applications to the latest APIs and best practices
|
||||
When the Angular team intends to remove an API or feature, it will be marked as *deprecated*. This occurs when when an API is obsolete, superseded by another API, or otherwise discontinued. Deprecated API remain available through their deprecated phase, which lasts a minimum two major versions (approximately one year).
|
||||
|
||||
To help ensure that you have sufficient time and a clear path to update, this is our deprecation policy:
|
||||
|
||||
| Deprecation stages | Details |
|
||||
|:--- |:--- |
|
||||
| Announcement | We announce deprecated APIs and features in the [change log](https://github.com/angular/angular/blob/main/CHANGELOG.md "Angular change log"). Deprecated APIs appear in the [documentation](api?status=deprecated) with ~~strikethrough~~. When we announce a deprecation, we also announce a recommended update path. For convenience, [Deprecations](guide/deprecations) contains a summary of deprecated APIs and features. |
|
||||
| Deprecation period | When an API or a feature is deprecated, it is still present in the next two major releases. After that, deprecated APIs and features are candidates for removal. A deprecation can be announced in any release, but the removal of a deprecated API or feature happens only in major release. Until a deprecated API or feature is removed, it is maintained according to the LTS support policy, meaning that only critical and security issues are fixed. |
|
||||
| Announcement | We announce deprecated APIs and features in the [change log](https://github.com/angular/angular/blob/main/CHANGELOG.md "Angular change log"). Deprecated APIs appear in the [documentation](api?status=deprecated) with ~~strikethrough~~. When we announce a deprecation, we also announce a recommended update path. For convenience, [Deprecations](guide/deprecations) contains a summary of deprecated APIs, and features. Additionally, all deprecated APIs are annotated with `@deprecated` in the corresponding documentation, which enables text editors and IDEs to provide hints if your project depends on them. |
|
||||
| Deprecation period | When an API or a feature is deprecated, it will remain in a supported release for a period of at least 12 months. A deprecation can be announced in any release, but the removal of a deprecated API or feature happens only in major release. Until a deprecated API or feature is removed, it is maintained according to the LTS support policy, meaning that only critical and security issues are fixed. |
|
||||
| npm dependencies | We only make npm dependency updates that require changes to your applications in a major release. In minor releases, we update peer dependencies by expanding the supported versions, but we do not require projects to update these dependencies until a future major version. This means that during minor Angular releases, npm dependency updates within Angular applications and libraries are optional. |
|
||||
|
||||
<a id="public-api"></a>
|
||||
|
||||
## Public API surface
|
||||
## Compatibility policy
|
||||
|
||||
Angular is a collection of many packages, subprojects, and tools.
|
||||
To prevent accidental use of private APIs and so that you can clearly understand what is covered by the practices described here — we document what is and is not considered our public API surface.
|
||||
For details, see [Supported Public API Surface of Angular](https://github.com/angular/angular/blob/main/docs/PUBLIC_API.md "Supported Public API Surface of Angular").
|
||||
|
||||
Any changes to the public API surface are done using the versioning, support, and depreciation policies previously described.
|
||||
To guarantee backward compatibility of Angular we run a series of checks before we merge any change:
|
||||
|
||||
* Unit tests and integration tests
|
||||
* Comparing the type definitions of the public API surface before and after the change
|
||||
* Running the tests of all the applications at Google that depend on Angular
|
||||
|
||||
Any changes to the public API surface are made in accordance with the versioning, support, and depreciation policies previously described. In exceptional cases, such as critical security patches, fixes may introduce backwards incompatible changes. Such exceptional cases are accompanied by explicit notice on the framework's official communication channels.
|
||||
<a id="updating"></a>
|
||||
|
||||
## Breaking change policy and update paths
|
||||
|
||||
Breaking change requires you to do work because the state after it is not backward compatible with the state before it. You can find the rare exceptions from this rule in the [Compatibility policy](#compatibility-policy). Examples of breaking changes are the removal of public APIs or other changes of the type definition of Angular, changing the timing of calls, or updating to a new version of a dependency of Angular, which includes breaking changes itself.
|
||||
|
||||
To support you in case of breaking changes in Angular:
|
||||
|
||||
* We follow our [deprecation policy](#deprecation-policy) before we remove a public API
|
||||
* Support update automation via the `ng update` command. It provides code transformations which we often have tested ahead of time over hundreds of thousands of projects at Google
|
||||
* Step by step instructions how to update from one major version to another at the ["Angular Update Guide"](https://update.angular.io/)
|
||||
|
||||
You can `ng update` to any version of Angular, provided that the following criteria are met:
|
||||
|
||||
* The version you want to update *to* is supported.
|
||||
* The version you want to update *from* is within one major version of the version you want to
|
||||
upgrade to.
|
||||
|
||||
For example, you can update from version 11 to version 12, provided that version 12 is still supported.
|
||||
If you want to update across multiple major versions, perform each update one major version at a time.
|
||||
For example, to update from version 10 to version 12:
|
||||
|
||||
1. Update from version 10 to version 11.
|
||||
1. Update from version 11 to version 12.
|
||||
|
||||
<a id="developer-preview"></a>
|
||||
|
||||
@@ -196,4 +209,4 @@ The policies and practices that are described in this document do not apply to A
|
||||
|
||||
<!-- end links -->
|
||||
|
||||
@reviewed 2023-12-13
|
||||
@reviewed 2024-02-08
|
||||
|
||||
@@ -184,7 +184,7 @@ The Angular router selects this route any time the requested URL doesn't match a
|
||||
To set up a wildcard route, add the following code to your `routes` definition.
|
||||
|
||||
```
|
||||
{ path: '**', component: <component-name> }
|
||||
{ path: '**', component: PageNotFoundComponent }
|
||||
```
|
||||
|
||||
The two asterisks, `**`, indicate to Angular that this `routes` definition is a wildcard route.
|
||||
|
||||
@@ -0,0 +1,157 @@
|
||||
# Signal inputs
|
||||
|
||||
Signal inputs allow values to be bound from parent components.
|
||||
Those values are exposed using a `Signal` and can change during the lifecycle of your component.
|
||||
|
||||
<div class="alert is-helpful">
|
||||
|
||||
Signal inputs are currently in [developer preview](/guide/releases#developer-preview).
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
Angular supports two variants of inputs:
|
||||
|
||||
**Optional inputs**
|
||||
Inputs are optional by default, unless you use `input.required`.
|
||||
You can specify an explicit initial value, or Angular will use `undefined` implicitly.
|
||||
|
||||
**Required inputs**
|
||||
Required inputs always have a value of the given input type.
|
||||
They are declared using the `input.required` function.
|
||||
|
||||
```typescript
|
||||
import {Component, input} from '@angular/core';
|
||||
|
||||
@Component({...})
|
||||
export class MyComp {
|
||||
// optional
|
||||
firstName = input<string>(); // InputSignal<string|undefined>
|
||||
age = input(0); // InputSignal<number>
|
||||
|
||||
// required
|
||||
lastName = input.required<string>(); // InputSignal<string>
|
||||
}
|
||||
```
|
||||
|
||||
An input is automatically recognized by Angular whenever you use the `input` or `input.required` functions as initializer of class members.
|
||||
|
||||
## Aliasing an input
|
||||
|
||||
Angular uses the class member name as the name of the input.
|
||||
You can alias inputs to change their public name to be different.
|
||||
|
||||
```typescript
|
||||
class StudentDirective {
|
||||
age = input(0, {alias: 'studentAge'});
|
||||
}
|
||||
```
|
||||
|
||||
This allows users to bind to your input using `[studentAge]`, while inside your component you can access the input values using `this.age`.
|
||||
|
||||
## Using in templates
|
||||
|
||||
Signal inputs are read-only signals.
|
||||
As with signals declared via `signal()`, you access the current value of the input by calling the input signal.
|
||||
|
||||
```html
|
||||
<p>First name: {{firstName()}}</p>
|
||||
<p>Last name: {{lastName()}}</p>
|
||||
```
|
||||
|
||||
This access to the value is captured in reactive contexts and can notify active consumers, like Angular itself, whenever the input value changes.
|
||||
|
||||
An input signal in practice is a trivial extension of signals that you know from [the signals guide](/guide/signals).
|
||||
|
||||
```typescript
|
||||
export class InputSignal<T> extends Signal<T> { ... }`.
|
||||
```
|
||||
|
||||
## Deriving values
|
||||
|
||||
As with signals, you can derive values from inputs using `computed`.
|
||||
|
||||
```typescript
|
||||
import {Component, input, computed} from '@angular/core';
|
||||
|
||||
@Component({...})
|
||||
export class MyComp {
|
||||
age = input(0);
|
||||
|
||||
// age multiplied by two.
|
||||
ageMultiplied = computed(() => this.age() * 2);
|
||||
}
|
||||
```
|
||||
|
||||
Computed signals memoize values.
|
||||
See more details in the [dedicated section for computed](/guide/signals#computed-signals).
|
||||
|
||||
## Monitoring changes
|
||||
|
||||
With signal inputs, users can leverage the `effect` function.
|
||||
The function will execute whenever the input changes.
|
||||
|
||||
Consider the following example.
|
||||
The new value is printed to the console whenever the `firstName` input changes.
|
||||
|
||||
```typescript
|
||||
import {input, effect} from '@angular/core';
|
||||
|
||||
class MyComp {
|
||||
firstName = input.required<string>();
|
||||
|
||||
constructor() {
|
||||
effect(() => {
|
||||
console.log(this.firstName());
|
||||
});
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The `console.log` function is invoked every time the `firstName` input changes.
|
||||
This will happen as soon as `firstName` is available, and for subsequent changes during the lifetime of `MyComp`.
|
||||
|
||||
## Value transforms
|
||||
|
||||
You may want to coerce or parse input values without changing the meaning of the input.
|
||||
Transforms convert the raw value from parent templates to the expected input type.
|
||||
Transforms should be [pure functions](https://en.wikipedia.org/wiki/Pure_function).
|
||||
|
||||
```typescript
|
||||
class MyComp {
|
||||
disabled = input(false, {
|
||||
transform: (value: boolean|string) => typeof value === 'string' ? value === '' : value,
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
In the example above, you are declaring an input named `disabled` that is accepting values of type `boolean` and `string`.
|
||||
This is captured by the explicit parameter type of `value` in the `transform` option.
|
||||
These values are then parsed to a `boolean` with the transform, resulting in booleans.
|
||||
|
||||
That way, you are only dealing with `boolean` inside your component when calling `this.disabled()`, while users of your component can pass an empty string as a shorthand to mark your component as disabled.
|
||||
|
||||
```html
|
||||
<my-custom-comp disabled>
|
||||
```
|
||||
|
||||
<div class="alert is-important">
|
||||
|
||||
Do not use transforms if they change the meaning of the input, or if they are [impure](https://en.wikipedia.org/wiki/Pure_function#Impure_functions).
|
||||
|
||||
Instead, use `computed` for transformations with different meaning, or an `effect` for impure code that should run whenever the input changes.
|
||||
|
||||
</div>
|
||||
|
||||
## Why should we use signal inputs and not `@Input()`?
|
||||
|
||||
Signal inputs are a reactive alternative to decorator-based `@Input()`.
|
||||
|
||||
In comparison to decorator-based `@Input`, signal inputs provide numerous benefits:
|
||||
|
||||
1. Signal inputs are more **type safe**:
|
||||
<br/>• Required inputs do not require initial values, or tricks to tell TypeScript that an input _always_ has a value.
|
||||
<br/>• Transforms are automatically checked to match the accepted input values.
|
||||
2. Signal inputs, when used in templates, will **automatically** mark `OnPush` components as dirty.
|
||||
3. Values can be easily **derived** whenever an input changes using `computed`.
|
||||
4. Easier and more local monitoring of inputs using `effect` instead of `ngOnChanges` or setters.
|
||||
@@ -0,0 +1,196 @@
|
||||
# Signal queries
|
||||
|
||||
A component or directive can define queries that find child elements and read values from their injectors.
|
||||
|
||||
Developers most commonly use queries to retrieve references to components, directives, DOM elements, and more.
|
||||
|
||||
There are two categories of query: view queries and content queries.
|
||||
|
||||
Signal queries supply query results as a reactive signal primitive. You can use query results in `computed` and `effect`, composing these results with other signals.
|
||||
|
||||
<div class="alert is-helpful">
|
||||
|
||||
Signal queries are in [developer preview](/guide/releases#developer-preview).
|
||||
APIs may change based on feedback without going through Angular's deprecation cycle.
|
||||
|
||||
</div>
|
||||
|
||||
If you're already familiar with Angular queries, you can jump straight to [Comparing signal-based queries to decorator-based queries](#comparing-signal-based-queries-to-decorator-based-queries)
|
||||
|
||||
## View queries
|
||||
|
||||
View queries retrieve results from the elements in the component's own template (view).
|
||||
|
||||
### `viewChild`
|
||||
|
||||
You can declare a query targeting a single result with the `viewChild` function.
|
||||
|
||||
```ts
|
||||
@Component({
|
||||
template: `
|
||||
<div #el></div>
|
||||
<my-component />
|
||||
`
|
||||
})
|
||||
export class TestComponent {
|
||||
// query for a single result by a string predicate
|
||||
divEl = viewChild<ElementRef>('el'); // Signal<ElementRef|undefined>
|
||||
// query for a single result by a type predicate
|
||||
cmp = viewChild(MyComponent); // Signal<MyComponent|undefined>
|
||||
}
|
||||
```
|
||||
|
||||
### `viewChildren`
|
||||
|
||||
You can also query for multiple results with the `viewChildren` function.
|
||||
|
||||
```ts
|
||||
@Component({
|
||||
template: `
|
||||
<div #el></div>
|
||||
@if (show) {
|
||||
<div #el></div>
|
||||
}
|
||||
`
|
||||
})
|
||||
export class TestComponent {
|
||||
show = true;
|
||||
|
||||
// query for multiple results
|
||||
divEls = viewChildren<ElementRef>('el'); // Signal<ReadonlyArray<ElementRef>>
|
||||
}
|
||||
```
|
||||
|
||||
### View query options
|
||||
|
||||
The `viewChild` and the `viewChildren` query declaration functions have a similar signature accepting two arguments:
|
||||
|
||||
* a **locator** to specify the query target - it can be either a `string` or any injectable token
|
||||
* a set of **options** to adjust behavior of a given query.
|
||||
|
||||
Signal-based view queries accept only one option: `read`. The `read` option indicates the type of result to inject from the matched nodes and return in the final results.
|
||||
|
||||
```ts
|
||||
@Component({
|
||||
template: `<my-component/>`
|
||||
})
|
||||
export class TestComponent {
|
||||
// query for a single result with options
|
||||
cmp = viewChild(MyComponent, {read: ElementRef}); // Signal<ElementRef|undefined>
|
||||
}
|
||||
```
|
||||
|
||||
## Content queries
|
||||
|
||||
Content queries retrieve results from the elements in the component's content — the elements nested inside the component tag in the template where it's used.
|
||||
|
||||
### `contentChild`
|
||||
|
||||
You can query for a single result with the `contentChild` function.
|
||||
|
||||
```ts
|
||||
@Component({...})
|
||||
export class TestComponent {
|
||||
// query by a string predicate
|
||||
headerEl = contentChild<ElementRef>('h'); // Signal<ElementRef|undefined>
|
||||
|
||||
// query by a type predicate
|
||||
header = contentChild(MyHeader); // Signal<MyHeader|undefined>
|
||||
}
|
||||
```
|
||||
|
||||
### `contentChildren`
|
||||
|
||||
You can also query for multiple results with the `contentChildren` function.
|
||||
|
||||
```ts
|
||||
@Component({...})
|
||||
export class TestComponent {
|
||||
// query for multiple results
|
||||
divEls = contentChildren<ElementRef>('h'); // Signal<ReadonlyArray<ElementRef>>
|
||||
}
|
||||
```
|
||||
|
||||
### Content query options
|
||||
|
||||
The `contentChild` and the `contentChildren` query declaration functions have a similar signature accepting two arguments:
|
||||
|
||||
* a **locator** to specify the query target - it can be either a `string` or any injectable token
|
||||
* a set of **options** to adjust behavior of a given query.
|
||||
|
||||
Content queries accept the following options:
|
||||
|
||||
* `descendants` By default, content queries find only direct children of the component and do not traverse into descendants. If this option is changed to `true`, query results will include all descendants of the element. Even when `true`, however, queries _never_ descend into components.
|
||||
* `read` indicates the type of result to retrieve from the matched nodes and return in the final results.
|
||||
|
||||
### Required child queries
|
||||
|
||||
If a child query (`viewChild` or `contentChild`) does not find a result, its value is `undefined`. This may occur if the target element is hidden by a control flow statement like `@if` or `@for`.
|
||||
|
||||
Because of this, the child queries return a signal that potentially have the `undefined` value. Most of the time, and especially for the view child queries, developers author their code such that:
|
||||
* there is at least one matching result;
|
||||
* results are accessed when the template was processed and query results are available.
|
||||
|
||||
For such cases, you can mark child queries as `required` to enforce presence of at least one matching result. This eliminates `undefined` from the result type signature. If a `required` query does not find any results, Angular throws an error.
|
||||
|
||||
```ts
|
||||
@Component({
|
||||
selector: 'app-root',
|
||||
standalone: true,
|
||||
template: `
|
||||
<div #requiredEl></div>
|
||||
`,
|
||||
})
|
||||
export class App {
|
||||
existingEl = viewChild.required('requiredEl'); // required and existing result
|
||||
missingEl = viewChild.required('notInATemplate'); // required but NOT existing result
|
||||
|
||||
ngAfterViewInit() {
|
||||
console.log(this.existingEl()); // OK :-)
|
||||
console.log(this.missingEl()); // Runtime error: result marked as required but not available!
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Results availability timing
|
||||
|
||||
A signal query authoring functions will be executed as part of the directive instance construction. This happens before we could create a query instance and execute the template’s creation mode to collect any matches. As a consequence, there is a period of time where the signal instance was created (and can be read) but no query results could have been collected. By default Angular will return `undefined` (for child queries) or an empty array (for children queries) before results are available. Required queries will throw if accessed at this point.
|
||||
|
||||
Angular computes signal-based query results lazily, on demand. This means that query results are not collected unless there is a code path that reads the signal.
|
||||
|
||||
Query results can change over time due to the view manipulation - either through the Angular's control flow (`@if`, `@for` etc.) or by the direct calls to the `ViewContainerRef` API. When you read the value from the query result signal, you can receive different values over time.
|
||||
|
||||
Note: to avoid returning incomplete query results while a template is rendered, Angular delays query resolution until it finishes rendering a given template.
|
||||
|
||||
## Query declarations functions and the associated rules
|
||||
|
||||
The `viewChild`, `contentChild`, `viewChildren` and `contentChildren` functions are special function recognized by the Angular compiler. You can use those functions to declare queries by initializing a component or a directive property. You can never call these functions outside of component and directive property initializers.
|
||||
|
||||
```ts
|
||||
@Component({
|
||||
selector: 'app-root',
|
||||
standalone: true,
|
||||
template: `
|
||||
<div #el></div>
|
||||
`,
|
||||
})
|
||||
export class App {
|
||||
el = viewChild('el'); // all good!
|
||||
|
||||
constructor() {
|
||||
const myConst = viewChild('el'); // NOT SUPPORTED
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Comparing signal-based queries to decorator-based queries
|
||||
|
||||
Signal queries are an alternative approach to the queries declared using the `@ContentChild`, `@ContentChildren`, `@ViewChild` or `@ViewChildren` decorators. The new approach exposes query results as signals which means that query results can be composed with other signals (using `computed` or `effect`) and drive change detection. Additionally, the signal-based query system offers other benefits:
|
||||
|
||||
* **More predictable timing.** You can access query results as soon as they're available.
|
||||
* **Simpler API surface.** All queries return a signal, and queries with more than one result let you work with a standard array.
|
||||
* **Improved type safety.** Fewer query use cases include `undefined` in the possible results.
|
||||
* **More accurate type inference.** TypeScript can infer more accurate types when you use a type predicate or when you specify an explicit `read` option.
|
||||
* **Lazier updates.** - Angular updates signal-based query results lazily; the framework does no work unless your code explicitly reads the query results.
|
||||
|
||||
The underlying query mechanism doesn't change much - conceptually Angular still creates singular "child" or plural "children" queries that target elements in a template (view) or content. The difference is in type of results and the exact timing of the results availability. The authoring format for declaring signal-based queries changed as well: the `viewChild`, `viewChildren`, `contentChild` and `contentChildren` functions used as initializer of class members are automatically recognized by Angular.
|
||||
@@ -104,7 +104,7 @@ Template input variables can be seen in action in the long-form usage of `NgFor`
|
||||
```html
|
||||
<ul>
|
||||
<ng-template ngFor let-hero [ngForOf]="heroes">
|
||||
<li>{{hero.name}}
|
||||
<li>{{hero.name}}</li>
|
||||
</ng-template>
|
||||
</ul>
|
||||
```
|
||||
@@ -118,7 +118,7 @@ When an `<ng-template>` is instantiated, multiple named values can be passed whi
|
||||
```html
|
||||
<ul>
|
||||
<ng-template ngFor let-hero let-i="index" [ngForOf]="heroes">
|
||||
<li>Hero number {{i}}: {{hero.name}}
|
||||
<li>Hero number {{i}}: {{hero.name}}</li>
|
||||
</ng-template>
|
||||
</ul>
|
||||
```
|
||||
|
||||
@@ -79,6 +79,21 @@ For more information on how to use two-way binding in forms, see Angular [NgMode
|
||||
|
||||
</div>
|
||||
|
||||
## Two-way binding to signals
|
||||
|
||||
Angular supports binding both plain values and signals to two-way bindings. If you pass a writable signal to a two-way binding, Angular will automatically keep it in sync with the underlying input and output.
|
||||
|
||||
```typescript
|
||||
@Component({
|
||||
template: '<input [(ngModel)]="name">',
|
||||
imports: [FormsModule]
|
||||
})
|
||||
export class MyComp {
|
||||
name = signal('Bob');
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
<!-- links -->
|
||||
|
||||
<!-- external links -->
|
||||
|
||||
@@ -36,7 +36,7 @@ To get the most out of these developer guides, you should review the following t
|
||||
</a>
|
||||
<a href="guide/dependency-injection" class="docs-card" title="Dependency injection">
|
||||
<section>Dependency injection</section>
|
||||
<p>Learn about dependency injection. Dependency injection refers to services or objects that a class needs to perform a specific function.</p>
|
||||
<p>Learn about dependency injection. Dependency injection refers to the pattern Angular uses to create and deliver services or objects that a class needs to perform a specific function.</p>
|
||||
<p class="card-footer">Dependency injection</p>
|
||||
</a>
|
||||
<!-- <a href="guide/rendering-overview" class="docs-card" title="Angular service worker developer guide">
|
||||
|
||||
@@ -10,7 +10,7 @@ For step-by-step instructions on how to update to the latest Angular release and
|
||||
|
||||
<div class="alert is-helpful">
|
||||
|
||||
For information about the deprecation and removal practices of Angular, see [Angular Release Practices](guide/releases#deprecation-practices).
|
||||
For information about the deprecation and removal practices of Angular, see [Angular Release Practices](guide/releases#deprecation-policy).
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
@@ -197,7 +197,7 @@ For more information, see [the error page](/errors/NG0910).
|
||||
## Deprecations in Angular v15
|
||||
|
||||
These are the aspects of Angular that are being phased out.
|
||||
They are still available in v15, but they can be removed in future versions as Angular's [deprecation practices](/guide/releases#deprecation-practices) describe.
|
||||
They are still available in v15, but they can be removed in future versions as Angular's [deprecation policy](/guide/releases#deprecation-policy) describe.
|
||||
|
||||
To maintain the reliability of your Angular application, review these notes and update your application as soon as practicable.
|
||||
|
||||
|
||||
@@ -158,7 +158,7 @@ the `relativeTo` property should be removed.
|
||||
## Deprecations highlights in Angular v16
|
||||
|
||||
These APIs remain available in v16, but may be removed in future versions as described by Angular's
|
||||
[deprecation practices](/guide/releases#deprecation-practices).
|
||||
[deprecation policy](/guide/releases#deprecation-policy).
|
||||
|
||||
To maintain the reliability of your Angular application, always update your application as soon as
|
||||
practicable.
|
||||
|
||||
@@ -0,0 +1,262 @@
|
||||
# Update Angular to v17
|
||||
|
||||
<!-- NOTE to writers: When creating the topic for the next version, -->
|
||||
<!-- remember to update the redirect link in angular/aio/firebase.json -->
|
||||
<!-- To update the redirect link in angular/aio/firebase.json: -->
|
||||
<!-- 1. Search for the entry in firebase.json with "source": "guide/update-to-latest-version" -->
|
||||
<!-- 2, Update the destination value to refer to the new guide's URL -->
|
||||
<!-- -->
|
||||
|
||||
This topic provides information about updating your Angular applications to Angular version 17.
|
||||
|
||||
For a summary of this information and the step-by-step procedure to update your Angular application
|
||||
to v17, see the [Angular Update Guide](https://update.angular.io).
|
||||
|
||||
The information in the [Angular Update Guide](https://update.angular.io) and this topic is
|
||||
summarized from these changelogs:
|
||||
|
||||
- [angular/angular changelog](https://github.com/angular/angular/blob/main/CHANGELOG.md)
|
||||
- [angular/angular-cli changelog](https://github.com/angular/angular-cli/blob/main/CHANGELOG.md)
|
||||
- [angular/components changelog](https://github.com/angular/components/blob/main/CHANGELOG.md)
|
||||
|
||||
Information about updating Angular applications to v16 is archived at
|
||||
[Update to version 16](/guide/update-to-version-16).
|
||||
|
||||
<a id="new-features"></a>
|
||||
|
||||
## Feature highlights in Angular v17
|
||||
|
||||
For a more comprehensive list of new features, see the
|
||||
[Angular blog post on the update to v17](https://blog.angular.io).
|
||||
|
||||
<!-- markdownLint-disable MD001 -->
|
||||
|
||||
#### Deferrable views
|
||||
|
||||
The new deferrable views allow you to lazily load all the components, directives, and pipes in a a section of your template:
|
||||
|
||||
```html
|
||||
@defer (on viewport) {
|
||||
<comment-list />
|
||||
} @placeholder {
|
||||
<!-- A placeholder content to show until the comments load -->
|
||||
<img src="comments-placeholder.png" />
|
||||
}
|
||||
```
|
||||
|
||||
In the example above, Angular first renders the contents of the placeholder block. When it becomes visible in the viewport, the loading of the `<comment-list />` component starts. Once the loading is completed, Angular removes the placeholder and renders the component.
|
||||
|
||||
#### Built-in control flow
|
||||
|
||||
We used a new block syntax for an optimized, built-in control flow. After running user studies we identified that a lot of developers struggle with `*ngIf`, `*ngSwitch`, and `*ngFor`.
|
||||
|
||||
It includes conditional statements:
|
||||
|
||||
```text
|
||||
@if (loggedIn) {
|
||||
The user is logged in
|
||||
} @else {
|
||||
The user is not logged in
|
||||
}
|
||||
```
|
||||
|
||||
Switch statements:
|
||||
|
||||
```text
|
||||
@switch (accessLevel) {
|
||||
@case ('admin') { <admin-dashboard/> }
|
||||
@case ('moderator') { <moderator-dashboard/> }
|
||||
@default { <user-dashboard/> }
|
||||
}
|
||||
```
|
||||
|
||||
And for loop:
|
||||
|
||||
```text
|
||||
@for (user of users; track user.id) {
|
||||
{{ user.name }}
|
||||
} @empty {
|
||||
Empty list of users
|
||||
}
|
||||
```
|
||||
|
||||
In addition to the improved ergonomics, the control flow is also up to 90% faster for certain performance benchmarks.
|
||||
|
||||
As of v17, the built-in control flow is in developer preview. To update your projects to using it run the schematic:
|
||||
|
||||
```sh
|
||||
ng generate @angular/core:control-flow
|
||||
```
|
||||
|
||||
#### New `@angular/ssr` package
|
||||
|
||||
We moved the code powering Angular server-side rendering from `angular/universal` to `angular/angular-cli`, integrating SSR more directly into our tooling.
|
||||
|
||||
Starting today, to add SSR support to your existing application run:
|
||||
|
||||
```sh
|
||||
ng add @angular/ssr
|
||||
```
|
||||
|
||||
This command generates the server entry point, adds SSR and SSG build capabilities, and enables hydration by default. `@angular/ssr` provides equivalent functionality to` @nguniversal/express-engine` which is currently in maintenance mode. The Angular CLI will automatically update your code to `@angular/ssr` with `ng update`!
|
||||
|
||||
#### New lifecycle hooks
|
||||
|
||||
To improve the performance of Angular’s SSR and SSG, in the long-term we’d like to move away from DOM emulation and direct DOM manipulations. At the same time, throughout most applications’ lifecycle they need to interact with elements to instantiate third-party libraries, measure element size, etc.
|
||||
|
||||
To enable this, we developed a set of new lifecycle hooks:
|
||||
|
||||
- `afterRender` - register a callback to be invoked each time the application finishes rendering
|
||||
- `afterNextRender` - register a callback to be invoked the next time the application finishes rendering
|
||||
|
||||
Only the browser will invoke these hooks, which enables you to plug custom DOM logic safely directly inside your components. For example, if you’d like to instantiate a charting library you can use afterNextRender:
|
||||
|
||||
```typescript
|
||||
@Component({
|
||||
selector: 'my-chart-cmp',
|
||||
template: `<div #chart>{{ ... }}</div>`,
|
||||
})
|
||||
export class MyChartCmp {
|
||||
@ViewChild('chart') chartRef: ElementRef;
|
||||
chart: MyChart | null;
|
||||
|
||||
constructor() {
|
||||
afterNextRender(() => {
|
||||
this.chart = new MyChart(this.chartRef.nativeElement);
|
||||
});
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Vite and esbuild the default for new projects
|
||||
|
||||
In v16 we introduced developer preview of the esbuild plus Vite powered build experience. Since then a lot of developers experimented with it and enterprises! Today, we’re happy to announce that the new application builder graduates from developer preview and is enabled by default for all new applications!
|
||||
|
||||
We updated the build pipeline when using hybrid rendering. With SSR & SSG you can observe up to 87% speed improvement in ng build and 80% faster edit-refresh loop in for `ng serve`. We'll be working on schematics to switch existing applications using hybrid rendering (SSR and SSG) to the new builder.
|
||||
|
||||
#### Dependency injection debugging in DevTools
|
||||
|
||||
Over the past few months, we implemented brand new debugging APIs that allow us to plug into the framework’s runtime and inspect the injector tree.
|
||||
|
||||
Based on these APIs we built an inspection user interface that allows you to preview the:
|
||||
|
||||
- Dependencies of your components in the component inspector
|
||||
- Injector tree and dependency resolution path
|
||||
- Providers declared within the individual injectors
|
||||
|
||||
#### Experimental view transitions support
|
||||
|
||||
The View Transitions API enables smooth transitions when changing the DOM. In the Angular router we now provide direct support for this API via the `withViewTransitions` feature. Using this, you can use the browser's native capabilities for creating animated transitions between routes.
|
||||
|
||||
You can add this feature to your app today by configuring it in the router’s provider declaration during bootstrap:
|
||||
|
||||
```typescript
|
||||
bootstrapApplication(App, {
|
||||
providers: [provideRouter(routes, withViewTransitions())],
|
||||
});
|
||||
```
|
||||
|
||||
`withViewTransitions` accepts an optional configuration object with property `onViewTransitionCreated`, which is a callback that provides you some extra control:
|
||||
|
||||
- Decide if you’d like to skip particular animations
|
||||
- Add classes to the document to customize the animation and remove these classes when the animation completes
|
||||
- etc.
|
||||
|
||||
<a id="breaking-changes"></a>
|
||||
|
||||
## Highlighted breaking changes in Angular v17
|
||||
|
||||
For a comprehensive list of breaking changes, see the full changelogs on GitHub.
|
||||
|
||||
<a id="v17-bc-01"></a>
|
||||
|
||||
### Angular v17 requires node.js version v18.13 or newer
|
||||
|
||||
Angular requires node.js v18.13 or newer.
|
||||
|
||||
See [Version compatibility](/guide/versions) for full version compatibility details.
|
||||
|
||||
<a id="v17-bc-02"></a>
|
||||
|
||||
### Angular v17 requires TypeScript version 5.2 or later
|
||||
|
||||
Angular v17 no longer supports TypeScript versions older than 5.2.
|
||||
|
||||
See [Version compatibility](/guide/versions) for full version compatibility details.
|
||||
|
||||
<a id="v17-bc-03"></a>
|
||||
|
||||
### Angular v17 requires zone.js v0.14.0
|
||||
|
||||
Angular v17 no longer supports zone.js older than v0.14.0.
|
||||
|
||||
<a id="v17-bc-04"></a>
|
||||
|
||||
#### Strict NgSwitch check
|
||||
|
||||
The `NgSwitch` directive now defaults to the `===` equality operator, migrating from the previously used `==`. `NgSwitch` expressions or individual condition values need adjusting to this stricter equality check. You'll see a warning message in the console where you need to adjust the check.
|
||||
|
||||
<a id="v17-bc-06"></a>
|
||||
|
||||
#### Routes with `loadComponent` data inheritance
|
||||
|
||||
Child routes with `loadComponent` no longer automatically inherit their data from their parent by default. In v17 the default `paramsInheritanceStrategy` is `emptyOnly`. If you want to inherit parent data in child routes update the strategy to `always`.
|
||||
|
||||
<a id="v17-bc-07"></a>
|
||||
|
||||
#### Router absolute redirect behavior change
|
||||
|
||||
Absolute redirects no longer prevent further redirects. You may need to adjust route configurations to prevent infinite redirects. Previously the router was ignoring all redirects after an absolute redirect.
|
||||
|
||||
<a id="v17-bc-08"></a>
|
||||
|
||||
#### Removal of `setupTestingRouter`
|
||||
|
||||
Use `RouterModule.forRoot` or `provideRouter` to setup the Router for tests instead of `setupTestingRouter`.
|
||||
|
||||
<a id="v17-bc-09"></a>
|
||||
|
||||
#### Removal of `malformedUriErrorHandler`
|
||||
|
||||
We removed `malformedUriErrorHandler` from the `RouterModule.forRoot` options. To handle URL parsing errors use the `UrlSerializer.parse` method.
|
||||
|
||||
<a id="v17-bc-10"></a>
|
||||
|
||||
#### Removal of zone.js bundles
|
||||
|
||||
In v17 we removed `zone-testing-bundle` and `zone-testing-node-bundle` which now prevents you from using `dist/` imports like `zone.js/bundles/zone-testing.js` and `zone.js/dist/zone`.
|
||||
|
||||
<a id="v17-bc-11"></a>
|
||||
|
||||
#### `OnPush` dynamically instantiated components
|
||||
|
||||
For dynamically instantiated components we now execute `ngDoCheck` during change detection if the component is marked as dirty. You may need to update your tests or logic within `ngDoCheck` for dynamically instantiated components.
|
||||
|
||||
<a id="v17-bc-12"></a>
|
||||
|
||||
#### Relocating Router public methods to `provideRouter` and `RouterModule.forRoot`
|
||||
|
||||
Make sure you configure `setupTestingRouter`, `canceledNavigationResolution`, `paramsInheritanceStrategy`, `titleStrategy`, `urlUpdateStrategy`, `urlHandlingStrategy`, and `malformedUriErrorHandler` in `provideRouter` or `RouterModule.forRoot` since these properties are now not part of the `Router`'s public API
|
||||
|
||||
<a id="v17-bc-13"></a>
|
||||
|
||||
#### `REMOVE_STYLES_ON_COMPONENT_DESTROY` now defaults to `true`
|
||||
|
||||
Angular now automatically removes styles of destroyed components, which may impact your existing apps in cases you rely on leaked styles. To change this update the value of the `REMOVE_STYLES_ON_COMPONENT_DESTROY` provider to `false`.
|
||||
|
||||
<a id="deprecations"></a>
|
||||
|
||||
## Deprecations highlights in Angular v17
|
||||
|
||||
These APIs remain available in v17, but may be removed in future versions as described by Angular's
|
||||
[deprecation policy](/guide/releases#deprecation-policy).
|
||||
|
||||
To maintain the reliability of your Angular application, always update your application as soon as
|
||||
practicable.
|
||||
|
||||
| Removed | Replacement | Details |
|
||||
| :------------------------------------------- | :-------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| <a id="v17-dp-01"></a>`NgProbeToken` | None | The `NgProbeToken` is not used internally since the transition from View Engine to Ivy. The token has no utility and can be removed from applications and libraries. |
|
||||
| <a id="v17-dp-02"></a>`AnimationDriver.NOOP` | `NoopAnimationDriver` | `The AnimationDriver.NOOP` symbol is deprecated, use `NoopAnimationDriver` instead. |
|
||||
|
||||
@reviewed 2023-10-26
|
||||
@@ -58,7 +58,7 @@ It also includes troubleshooting information and any recommended manual changes
|
||||
For simple updates, the CLI command [`ng update`](cli/update) is all you need.
|
||||
Without additional arguments, [`ng update`](cli/update) lists the updates that are available to you and provides recommended steps to update your application to the most current version.
|
||||
|
||||
[Angular Versioning and Releases](guide/releases#versioning "Angular Release Practices, Versioning") describes the level of change that you can expect based a release's version number.
|
||||
[Angular Versioning and Releases](guide/releases#versioning "Angular Release Practices, Versioning") describes the level of change that you can expect based on a release's version number.
|
||||
It also describes supported update paths.
|
||||
|
||||
<a id="resources"></a>
|
||||
|
||||
+23
-15
@@ -407,18 +407,17 @@ export class AppComponent implements OnInit {
|
||||
<!-- vale Angular.Google_Headings = YES -->
|
||||
|
||||
To make Zone.js available in Angular, you need to import the `zone.js` package.
|
||||
If you are using the Angular CLI, this step is done automatically, and you can see the following line in the `src/polyfills.ts`:
|
||||
If you are using the Angular CLI, this step is done automatically, and you can see the following line in the `angular.json`:
|
||||
|
||||
<code-example format="typescript" language="typescript">
|
||||
<code-example format="json" language="json">
|
||||
|
||||
/***************************************************************************************************
|
||||
* Zone JS is required by default for Angular itself.
|
||||
*/
|
||||
import 'zone.js'; // Included with Angular CLI.
|
||||
"polyfills": [
|
||||
"zone.js"
|
||||
]
|
||||
|
||||
</code-example>
|
||||
|
||||
Before importing the `zone.js` package, you can set the following configurations:
|
||||
Several `zone.js` settings can be changed like:
|
||||
|
||||
* Disabling some asynchronous API monkey patching for better performance.
|
||||
For example, disabling the `requestAnimationFrame()` monkey patch, so the callback of `requestAnimationFrame()` does not trigger change detection.
|
||||
@@ -426,7 +425,6 @@ Before importing the `zone.js` package, you can set the following configurations
|
||||
|
||||
* Specify that certain DOM events do not run inside the Angular zone. For example, to prevent a `mousemove` or `scroll` event to trigger change detection
|
||||
|
||||
Several other settings can be changed.
|
||||
To make these changes, you need to create a `zone-flags.ts` file, such as the following.
|
||||
|
||||
<code-example format="typescript" language="typescript">
|
||||
@@ -439,15 +437,25 @@ To make these changes, you need to create a `zone-flags.ts` file, such as the fo
|
||||
|
||||
</code-example>
|
||||
|
||||
Next, import `zone-flags` before you import `zone.js` in the `polyfills.ts`:
|
||||
Next, add `zone-flags` before `zone.js` in the `angular.json` file:
|
||||
|
||||
<code-example format="typescript" language="typescript">
|
||||
<code-example format="json" language="json">
|
||||
|
||||
/***************************************************************************************************
|
||||
* Zone JS is required by default for Angular.
|
||||
*/
|
||||
import `./zone-flags`;
|
||||
import 'zone.js'; // Included with Angular CLI.
|
||||
"polyfills": [
|
||||
"src/zone-flags.ts",
|
||||
"zone.js"
|
||||
]
|
||||
|
||||
</code-example>
|
||||
|
||||
Lastly, add `zone-flags` to the `include` array in the `tsconfig.app.json` file:
|
||||
|
||||
<code-example format="json" language="json">
|
||||
|
||||
"include": [
|
||||
"src/**/*.d.ts",
|
||||
"src/zone-flags.ts"
|
||||
]
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
File diff suppressed because one or more lines are too long
|
Before Width: | Height: | Size: 7.1 KiB After Width: | Height: | Size: 7.1 KiB |
@@ -2,7 +2,7 @@
|
||||
@description
|
||||
The MIT License
|
||||
|
||||
Copyright (c) 2010-2023 Google LLC. https://angular.io/license
|
||||
Copyright (c) 2010-2024 Google LLC. https://angular.io/license
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files \(the "Software"\), to deal
|
||||
|
||||
@@ -1336,5 +1336,11 @@
|
||||
"start": "2024-06-13"
|
||||
},
|
||||
"name": "JSNation"
|
||||
},
|
||||
{
|
||||
"date": {
|
||||
"start": "2024-06-27"
|
||||
},
|
||||
"name": "dotJS"
|
||||
}
|
||||
]
|
||||
@@ -787,6 +787,21 @@
|
||||
"title": "Signals",
|
||||
"tooltip": "Angular signals for optimized change detection"
|
||||
},
|
||||
{
|
||||
"url": "guide/signal-inputs",
|
||||
"title": "Signal inputs",
|
||||
"tooltip": "Signal inputs to optimize change detection and developer experience."
|
||||
},
|
||||
{
|
||||
"url": "guide/model-inputs",
|
||||
"title": "Model inputs",
|
||||
"tooltip": "Model inputs define inputs that emit a changed event when updated."
|
||||
},
|
||||
{
|
||||
"url": "guide/signal-queries",
|
||||
"title": "Signal queries",
|
||||
"tooltip": "Signal queries exposing results as a reactive signal primitive."
|
||||
},
|
||||
{
|
||||
"url": "guide/rxjs-interop",
|
||||
"title": "RxJS Interop for Signals",
|
||||
|
||||
+1
-1
@@ -88,7 +88,7 @@
|
||||
// The destination value must be updated with each new major version.
|
||||
// When changing this value, be sure to update or add the corresponding
|
||||
// test in angular/aio/tests/deployment/shared/URLS_TO_REDIRECT.txt
|
||||
{"type": 301, "source": "/guide/update-to-latest-version", "destination": "/guide/update-to-version-16"},
|
||||
{"type": 301, "source": "/guide/update-to-latest-version", "destination": "/guide/update-to-version-17"},
|
||||
|
||||
// Renaming of Getting Started topics
|
||||
{"type": 301, "source": "/start/data", "destination": "/start/start-data"},
|
||||
|
||||
+18
-18
@@ -61,33 +61,33 @@
|
||||
},
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@angular/animations": "17.2.0-next.0",
|
||||
"@angular/cdk": "17.2.0-next.0",
|
||||
"@angular/common": "17.2.0-next.0",
|
||||
"@angular/compiler": "17.2.0-next.0",
|
||||
"@angular/core": "17.2.0-next.0",
|
||||
"@angular/elements": "17.2.0-next.0",
|
||||
"@angular/forms": "17.2.0-next.0",
|
||||
"@angular/material": "17.2.0-next.0",
|
||||
"@angular/platform-browser": "17.2.0-next.0",
|
||||
"@angular/platform-browser-dynamic": "17.2.0-next.0",
|
||||
"@angular/router": "17.2.0-next.0",
|
||||
"@angular/service-worker": "17.2.0-next.0",
|
||||
"@angular/animations": "17.2.0-rc.1",
|
||||
"@angular/cdk": "17.2.0-rc.0",
|
||||
"@angular/common": "17.2.0-rc.1",
|
||||
"@angular/compiler": "17.2.0-rc.1",
|
||||
"@angular/core": "17.2.0-rc.1",
|
||||
"@angular/elements": "17.2.0-rc.1",
|
||||
"@angular/forms": "17.2.0-rc.1",
|
||||
"@angular/material": "17.2.0-rc.0",
|
||||
"@angular/platform-browser": "17.2.0-rc.1",
|
||||
"@angular/platform-browser-dynamic": "17.2.0-rc.1",
|
||||
"@angular/router": "17.2.0-rc.1",
|
||||
"@angular/service-worker": "17.2.0-rc.1",
|
||||
"rxjs": "~7.8.0",
|
||||
"safevalues": "^0.5.0",
|
||||
"tslib": "^2.3.0",
|
||||
"zone.js": "~0.14.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@angular-devkit/architect-cli": "0.1702.0-next.0",
|
||||
"@angular-devkit/build-angular": "17.2.0-next.0",
|
||||
"@angular-devkit/architect-cli": "0.1702.0-rc.0",
|
||||
"@angular-devkit/build-angular": "17.2.0-rc.0",
|
||||
"@angular-eslint/builder": "16.0.3",
|
||||
"@angular-eslint/eslint-plugin": "16.0.3",
|
||||
"@angular-eslint/eslint-plugin-template": "16.0.3",
|
||||
"@angular-eslint/template-parser": "^16.0.3",
|
||||
"@angular/build-tooling": "https://github.com/angular/dev-infra-private-build-tooling-builds.git#07da06414a4ce04a8f9710f2bc9346c2c753a0d2",
|
||||
"@angular/cli": "17.2.0-next.0",
|
||||
"@angular/compiler-cli": "17.2.0-next.0",
|
||||
"@angular/build-tooling": "https://github.com/angular/dev-infra-private-build-tooling-builds.git#7c4cf003cb4ac849986beaa243d7e85a893612f2",
|
||||
"@angular/cli": "17.2.0-rc.0",
|
||||
"@angular/compiler-cli": "17.2.0-rc.1",
|
||||
"@bazel/bazelisk": "^1.7.5",
|
||||
"@bazel/buildozer": "^6.0.0",
|
||||
"@bazel/ibazel": "^0.16.2",
|
||||
@@ -113,7 +113,7 @@
|
||||
"eslint": "8.56.0",
|
||||
"eslint-plugin-import": "2.29.1",
|
||||
"eslint-plugin-jasmine": "4.1.3",
|
||||
"eslint-plugin-jsdoc": "48.0.5",
|
||||
"eslint-plugin-jsdoc": "48.0.6",
|
||||
"eslint-plugin-prefer-arrow": "1.2.3",
|
||||
"find-free-port": "^2.0.0",
|
||||
"firebase-tools": "^13.0.0",
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import {execSync} from 'node:child_process';
|
||||
import {readFile, writeFile, mkdtemp, realpath, rm, rename} from 'node:fs/promises';
|
||||
import {tmpdir} from 'os';
|
||||
import {readFile, writeFile, readdir, mkdtemp, realpath, copyFile, unlink} from 'node:fs/promises';
|
||||
import {tmpdir} from 'node:os';
|
||||
import {get} from 'node:https';
|
||||
import {dirname, resolve as resolvePath, posix, join} from 'node:path';
|
||||
import {fileURLToPath} from 'node:url';
|
||||
import {existsSync} from 'node:fs';
|
||||
import {existsSync, constants as fsConstants} from 'node:fs';
|
||||
|
||||
const GITHUB_API = 'https://api.github.com/repos/';
|
||||
const CLI_BUILDS_REPO = 'angular/cli-builds';
|
||||
@@ -35,7 +35,7 @@ async function main() {
|
||||
|
||||
console.log(
|
||||
`The below help files changed between ${currentSha} and ${latestSha}:\n` +
|
||||
changedHelpFiles.map((f) => '* ' + f).join('\n')
|
||||
changedHelpFiles.map((f) => '* ' + f).join('\n'),
|
||||
);
|
||||
|
||||
const temporaryDir = await realpath(await mkdtemp(join(tmpdir(), 'cli-src-')));
|
||||
@@ -53,11 +53,23 @@ async function main() {
|
||||
stdio: ['ignore', 'pipe', 'ignore'],
|
||||
}).trim();
|
||||
|
||||
// Delete current contents
|
||||
await rm(CLI_HELP_CONTENT_PATH, {recursive: true, force: true});
|
||||
// Delete existing JSON help files.
|
||||
const helpFilesUnlink = (await readdir(CLI_HELP_CONTENT_PATH))
|
||||
.filter((f) => f.endsWith('.json'))
|
||||
.map((f) => unlink(join(CLI_HELP_CONTENT_PATH, f)));
|
||||
|
||||
// Move Help contents
|
||||
await rename(join(temporaryDir, 'help'), CLI_HELP_CONTENT_PATH);
|
||||
await Promise.allSettled(helpFilesUnlink);
|
||||
|
||||
// Copy new help files
|
||||
const tempHelpDir = join(temporaryDir, 'help');
|
||||
const helpFilesCopy = (await readdir(tempHelpDir)).map((f) => {
|
||||
const src = join(tempHelpDir, f);
|
||||
const dest = join(CLI_HELP_CONTENT_PATH, f);
|
||||
|
||||
return copyFile(src, dest, fsConstants.COPYFILE_FICLONE);
|
||||
});
|
||||
|
||||
await Promise.allSettled(helpFilesCopy);
|
||||
|
||||
// Write SHA to file.
|
||||
await writeFile(
|
||||
@@ -68,8 +80,8 @@ async function main() {
|
||||
sha: shaWhenFilesChanged,
|
||||
},
|
||||
undefined,
|
||||
2
|
||||
)
|
||||
2,
|
||||
),
|
||||
);
|
||||
|
||||
console.log('\nChanges: ');
|
||||
@@ -106,7 +118,7 @@ async function getShaFromCliBuilds(branch) {
|
||||
*/
|
||||
async function getAffectedFiles(baseSha, headSha) {
|
||||
const {files} = JSON.parse(
|
||||
await httpGet(`${GITHUB_API_CLI_BUILDS}/compare/${baseSha}...${headSha}`)
|
||||
await httpGet(`${GITHUB_API_CLI_BUILDS}/compare/${baseSha}...${headSha}`),
|
||||
);
|
||||
return files.map((f) => f.filename);
|
||||
}
|
||||
|
||||
@@ -94,6 +94,10 @@ aio-api-list {
|
||||
h2 {
|
||||
margin-top: 16px;
|
||||
margin-bottom: 16px;
|
||||
|
||||
& .deprecated-api-item {
|
||||
text-decoration: line-through;
|
||||
}
|
||||
}
|
||||
|
||||
/* API CLASS LIST */
|
||||
|
||||
@@ -213,7 +213,7 @@
|
||||
/guide/service-worker-configref --> /guide/service-worker-config
|
||||
/guide/setup --> /guide/setup-local
|
||||
/guide/setup-systemjs-anatomy --> /guide/file-structure
|
||||
/guide/update-to-latest-version --> /guide/update-to-version-16
|
||||
/guide/update-to-latest-version --> /guide/update-to-version-17
|
||||
/guide/updating-to-version-10 --> https://v10.angular.io/guide/updating-to-version-10
|
||||
/guide/updating-to-version-11 --> https://v11.angular.io/guide/updating-to-version-11
|
||||
/guide/updating-to-version-12 --> https://v12.angular.io/guide/updating-to-version-12
|
||||
|
||||
@@ -13,14 +13,14 @@
|
||||
},
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@angular/animations": "17.1.1",
|
||||
"@angular/common": "17.1.1",
|
||||
"@angular/compiler": "17.1.1",
|
||||
"@angular/core": "17.1.1",
|
||||
"@angular/forms": "17.1.1",
|
||||
"@angular/platform-browser": "17.1.1",
|
||||
"@angular/platform-browser-dynamic": "17.1.1",
|
||||
"@angular/router": "17.1.1",
|
||||
"@angular/animations": "17.1.3",
|
||||
"@angular/common": "17.1.3",
|
||||
"@angular/compiler": "17.1.3",
|
||||
"@angular/core": "17.1.3",
|
||||
"@angular/forms": "17.1.3",
|
||||
"@angular/platform-browser": "17.1.3",
|
||||
"@angular/platform-browser-dynamic": "17.1.3",
|
||||
"@angular/router": "17.1.3",
|
||||
"angular": "^1.8.3",
|
||||
"angular-in-memory-web-api": "~0.17.0",
|
||||
"angular-route": "1.8.3",
|
||||
@@ -29,9 +29,9 @@
|
||||
"zone.js": "~0.14.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@angular-devkit/build-angular": "17.1.1",
|
||||
"@angular/cli": "17.1.1",
|
||||
"@angular/compiler-cli": "17.1.1",
|
||||
"@angular-devkit/build-angular": "17.1.3",
|
||||
"@angular/cli": "17.1.3",
|
||||
"@angular/compiler-cli": "17.1.3",
|
||||
"@types/angular": "^1.8.4",
|
||||
"@types/angular-route": "^1.7.2",
|
||||
"@types/jasmine": "~5.1.0",
|
||||
|
||||
@@ -13,23 +13,23 @@
|
||||
},
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@angular/animations": "17.1.1",
|
||||
"@angular/common": "17.1.1",
|
||||
"@angular/compiler": "17.1.1",
|
||||
"@angular/core": "17.1.1",
|
||||
"@angular/forms": "17.1.1",
|
||||
"@angular/platform-browser": "17.1.1",
|
||||
"@angular/platform-browser-dynamic": "17.1.1",
|
||||
"@angular/router": "17.1.1",
|
||||
"@angular/animations": "17.1.3",
|
||||
"@angular/common": "17.1.3",
|
||||
"@angular/compiler": "17.1.3",
|
||||
"@angular/core": "17.1.3",
|
||||
"@angular/forms": "17.1.3",
|
||||
"@angular/platform-browser": "17.1.3",
|
||||
"@angular/platform-browser-dynamic": "17.1.3",
|
||||
"@angular/router": "17.1.3",
|
||||
"angular-in-memory-web-api": "~0.17.0",
|
||||
"rxjs": "~7.8.0",
|
||||
"tslib": "^2.3.0",
|
||||
"zone.js": "~0.14.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@angular-devkit/build-angular": "17.1.1",
|
||||
"@angular/cli": "17.1.1",
|
||||
"@angular/compiler-cli": "17.1.1",
|
||||
"@angular-devkit/build-angular": "17.1.3",
|
||||
"@angular/cli": "17.1.3",
|
||||
"@angular/compiler-cli": "17.1.3",
|
||||
"@types/jasmine": "~5.1.0",
|
||||
"@types/node": "^16.11.35",
|
||||
"copyfiles": "^2.4.1",
|
||||
|
||||
@@ -13,24 +13,24 @@
|
||||
},
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@angular/animations": "17.1.1",
|
||||
"@angular/common": "17.1.1",
|
||||
"@angular/compiler": "17.1.1",
|
||||
"@angular/core": "17.1.1",
|
||||
"@angular/elements": "17.1.1",
|
||||
"@angular/forms": "17.1.1",
|
||||
"@angular/platform-browser": "17.1.1",
|
||||
"@angular/platform-browser-dynamic": "17.1.1",
|
||||
"@angular/router": "17.1.1",
|
||||
"@angular/animations": "17.1.3",
|
||||
"@angular/common": "17.1.3",
|
||||
"@angular/compiler": "17.1.3",
|
||||
"@angular/core": "17.1.3",
|
||||
"@angular/elements": "17.1.3",
|
||||
"@angular/forms": "17.1.3",
|
||||
"@angular/platform-browser": "17.1.3",
|
||||
"@angular/platform-browser-dynamic": "17.1.3",
|
||||
"@angular/router": "17.1.3",
|
||||
"angular-in-memory-web-api": "~0.17.0",
|
||||
"rxjs": "~7.8.0",
|
||||
"tslib": "^2.3.0",
|
||||
"zone.js": "~0.14.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@angular-devkit/build-angular": "17.1.1",
|
||||
"@angular/cli": "17.1.1",
|
||||
"@angular/compiler-cli": "17.1.1",
|
||||
"@angular-devkit/build-angular": "17.1.3",
|
||||
"@angular/cli": "17.1.3",
|
||||
"@angular/compiler-cli": "17.1.3",
|
||||
"@types/jasmine": "~5.1.0",
|
||||
"@types/node": "^16.11.35",
|
||||
"jasmine-core": "~5.1.0",
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user