mirror of
https://github.com/angular/angular.git
synced 2026-09-14 13:54:52 +08:00
Compare commits
110 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 98de185e53 | |||
| 0fb455f72e | |||
| 67bb310376 | |||
| a9460d08a0 | |||
| afc057ee92 | |||
| 0fffe5389f | |||
| 77ae5a391b | |||
| 76f2c4fe1a | |||
| 9366e170e9 | |||
| 879537466e | |||
| 7330b6944d | |||
| 3e1d6e9d6e | |||
| 9d3cd9bc83 | |||
| b3e61ba63f | |||
| 15e0600521 | |||
| cf84acf44c | |||
| 7898957b9d | |||
| 73a17904aa | |||
| 9ddb003b6c | |||
| 7a2efd442d | |||
| 464dae94ee | |||
| 8f273ce7a8 | |||
| 1872fcd8e0 | |||
| 6e1e70d3f1 | |||
| 554a1be51b | |||
| b80b46215c | |||
| b5ab2ef269 | |||
| 46acb55e76 | |||
| aeb3fe26cf | |||
| 14d5094177 | |||
| e5331107fd | |||
| 3a930a59f6 | |||
| 72108bad4d | |||
| 9e07b621ea | |||
| 58a8f124b0 | |||
| 76e7aa8add | |||
| 7645325d46 | |||
| b44e35f472 | |||
| afd87acde4 | |||
| c320aa6705 | |||
| 2e891ad72a | |||
| 8b5921547c | |||
| 45b58059f0 | |||
| 95bf0c85f8 | |||
| 8cabb7a85d | |||
| 7c1b4a49ae | |||
| 1f2e791c57 | |||
| a0ec2d8915 | |||
| fc9602087a | |||
| 7ff7dfaee7 | |||
| a33fdfd8da | |||
| bf25dfae58 | |||
| b9a997c467 | |||
| 5903c3752b | |||
| cda8bfa8be | |||
| 379242c910 | |||
| 874d33286e | |||
| 7cea897132 | |||
| 024e9bf54d | |||
| 3312727aec | |||
| 9f777b692c | |||
| 2ac54f8327 | |||
| 99cf450d38 | |||
| 73d999f0c7 | |||
| 7eaa407c22 | |||
| ff656691c3 | |||
| 0a77825042 | |||
| a4a82af865 | |||
| 375e9a7c78 | |||
| 66ffeca2de | |||
| fd544159e3 | |||
| 49d3062c0e | |||
| 9160a21624 | |||
| 0650981a54 | |||
| b87a4c40bf | |||
| 4f9084e514 | |||
| 292c987791 | |||
| 0d78a92431 | |||
| f307e95459 | |||
| 31fdf0fbea | |||
| be17de53d4 | |||
| 91b1f24d03 | |||
| 4a7402f75d | |||
| f7233b0589 | |||
| f6e11e67f2 | |||
| 5559b7fe63 | |||
| 5b4970bbaa | |||
| 96972b45ec | |||
| e0096ef945 | |||
| 54de0f3f0b | |||
| 8a6027de86 | |||
| 356ec6508b | |||
| bf8814c6c3 | |||
| 0dc4df8296 | |||
| 2bd166537b | |||
| f4f1d32f7e | |||
| 5926bf452d | |||
| ae7af8d23b | |||
| 5a52facef7 | |||
| f1d6f867b9 | |||
| 7e89753eef | |||
| 97eea8d50e | |||
| e54c950e92 | |||
| 03934f674e | |||
| e723635058 | |||
| a45a640474 | |||
| 0b53fdb3b4 | |||
| 90389add7a | |||
| 3667352ce5 | |||
| 4cec9fc67a |
@@ -8,7 +8,6 @@
|
||||
"devondcarew.bazel-code",
|
||||
"gkalpak.aio-docs-utils",
|
||||
"ms-vscode.vscode-typescript-tslint-plugin",
|
||||
"xaver.clang-format",
|
||||
// The following extensions are useful when working on angular.io (i.e. inside the `aio/` directory).
|
||||
//"angular.ng-template",
|
||||
//"dbaeumer.vscode-eslint",
|
||||
|
||||
@@ -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@f122b83d2a49e24eb9dfcd54fca9fc97612b832d
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@e6e98d32d7d4d350ecf4ec10ad9c7e75198daabd
|
||||
- name: Setup Bazel
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@f122b83d2a49e24eb9dfcd54fca9fc97612b832d
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@e6e98d32d7d4d350ecf4ec10ad9c7e75198daabd
|
||||
- name: Setup Bazel RBE
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@f122b83d2a49e24eb9dfcd54fca9fc97612b832d
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@e6e98d32d7d4d350ecf4ec10ad9c7e75198daabd
|
||||
- name: Install node modules
|
||||
run: yarn install --frozen-lockfile
|
||||
- name: Build adev to ensure it continues to work
|
||||
run: yarn bazel build //adev:build
|
||||
- uses: angular/dev-infra/github-actions/previews/pack-and-upload-artifact@f122b83d2a49e24eb9dfcd54fca9fc97612b832d
|
||||
- uses: angular/dev-infra/github-actions/previews/pack-and-upload-artifact@e6e98d32d7d4d350ecf4ec10ad9c7e75198daabd
|
||||
with:
|
||||
workflow-artifact-name: 'adev-preview'
|
||||
pull-number: '${{github.event.pull_request.number}}'
|
||||
|
||||
@@ -29,7 +29,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ github.event.workflow_run.conclusion == 'success' }}
|
||||
steps:
|
||||
- uses: actions/checkout@1d96c772d19495a3b5c517cd2bc0cb401ea0529f # v4
|
||||
- uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4
|
||||
with:
|
||||
token: '${{secrets.GITHUB_TOKEN}}'
|
||||
|
||||
@@ -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@f122b83d2a49e24eb9dfcd54fca9fc97612b832d
|
||||
- uses: angular/dev-infra/github-actions/previews/upload-artifacts-to-firebase@e6e98d32d7d4d350ecf4ec10ad9c7e75198daabd
|
||||
with:
|
||||
github-token: '${{secrets.GITHUB_TOKEN}}'
|
||||
workflow-artifact-name: 'adev-preview'
|
||||
|
||||
@@ -13,9 +13,9 @@ jobs:
|
||||
assistant_to_the_branch_manager:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@1d96c772d19495a3b5c517cd2bc0cb401ea0529f # v4.1.3
|
||||
- uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: angular/dev-infra/github-actions/branch-manager@f122b83d2a49e24eb9dfcd54fca9fc97612b832d
|
||||
- uses: angular/dev-infra/github-actions/branch-manager@e6e98d32d7d4d350ecf4ec10ad9c7e75198daabd
|
||||
with:
|
||||
angular-robot-key: ${{ secrets.ANGULAR_ROBOT_PRIVATE_KEY }}
|
||||
|
||||
@@ -28,7 +28,7 @@ jobs:
|
||||
- uses: alessbell/pull-request-comment-branch@aad01d65d6982b8eacabed5e9a684cd8ceb98da6 # v1.1
|
||||
id: comment-branch
|
||||
|
||||
- uses: actions/checkout@1d96c772d19495a3b5c517cd2bc0cb401ea0529f # v4.1.3
|
||||
- uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4
|
||||
with:
|
||||
# Specify repository as the PR branch might be from a fork.
|
||||
repository: ${{steps.comment-branch.outputs.head_owner}}/${{steps.comment-branch.outputs.head_repo}}
|
||||
@@ -38,7 +38,7 @@ jobs:
|
||||
|
||||
- uses: ./.github/actions/yarn-install
|
||||
|
||||
- uses: angular/dev-infra/github-actions/bazel/configure-remote@f122b83d2a49e24eb9dfcd54fca9fc97612b832d
|
||||
- uses: angular/dev-infra/github-actions/bazel/configure-remote@e6e98d32d7d4d350ecf4ec10ad9c7e75198daabd
|
||||
with:
|
||||
bazelrc: ./.bazelrc.user
|
||||
|
||||
|
||||
+9
-188
@@ -5,7 +5,7 @@ on:
|
||||
branches:
|
||||
- main
|
||||
- '[0-9]+.[0-9]+.x'
|
||||
- 'remove-aio-stuff'
|
||||
- 'browser-update-test'
|
||||
pull_request:
|
||||
types: [opened, synchronize, reopened]
|
||||
|
||||
@@ -20,155 +20,6 @@ defaults:
|
||||
shell: bash
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Initialize environment
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@f122b83d2a49e24eb9dfcd54fca9fc97612b832d
|
||||
with:
|
||||
cache-node-modules: true
|
||||
node-module-directories: |
|
||||
./node_modules
|
||||
./aio/node_modules
|
||||
- name: Install node modules
|
||||
run: yarn install --frozen-lockfile
|
||||
- name: Install node modules in aio
|
||||
run: yarn install --frozen-lockfile --cwd aio
|
||||
- name: Check code lint
|
||||
run: yarn -s tslint
|
||||
- name: Check for circular dependencies
|
||||
run: yarn -s ts-circular-deps:check
|
||||
- name: Validate pull approve configuration
|
||||
run: yarn -s ng-dev pullapprove verify
|
||||
- name: Validate angular robot configuration
|
||||
run: yarn -s ng-dev ngbot verify
|
||||
- name: Confirm code builds with typescript as expected
|
||||
run: yarn -s check-tooling-setup
|
||||
- name: Check commit message
|
||||
# Commit message validation is only done on pull requests as its too late to validate once
|
||||
# it has been merged.
|
||||
if: github.event_name == 'pull_request'
|
||||
run: yarn ng-dev commit-message validate-range ${{ github.event.pull_request.base.sha }} ${{ github.event.pull_request.head.sha }}
|
||||
- name: Check code format
|
||||
# Code formatting checks are only done on pull requests as its too late to validate once
|
||||
# it has been merged.
|
||||
if: github.event_name == 'pull_request'
|
||||
run: yarn ng-dev format changed --check ${{ github.event.pull_request.base.sha }}
|
||||
|
||||
devtools:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Initialize environment
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@f122b83d2a49e24eb9dfcd54fca9fc97612b832d
|
||||
with:
|
||||
cache-node-modules: true
|
||||
- name: Setup Bazel
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@f122b83d2a49e24eb9dfcd54fca9fc97612b832d
|
||||
- name: Setup Bazel RBE
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@f122b83d2a49e24eb9dfcd54fca9fc97612b832d
|
||||
- name: Install node modules
|
||||
run: yarn install --frozen-lockfile
|
||||
- name: Run unit tests
|
||||
run: yarn devtools:test
|
||||
- name: Test build
|
||||
run: yarn devtools:build:chrome
|
||||
|
||||
test:
|
||||
runs-on: ubuntu-latest-4core
|
||||
steps:
|
||||
- name: Initialize environment
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@f122b83d2a49e24eb9dfcd54fca9fc97612b832d
|
||||
with:
|
||||
cache-node-modules: true
|
||||
- name: Setup Bazel
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@f122b83d2a49e24eb9dfcd54fca9fc97612b832d
|
||||
- name: Setup Bazel Remote Caching
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@f122b83d2a49e24eb9dfcd54fca9fc97612b832d
|
||||
- name: Install node modules
|
||||
run: yarn install --frozen-lockfile --network-timeout 100000
|
||||
- name: Run CI tests for framework
|
||||
run: yarn test:ci
|
||||
|
||||
adev:
|
||||
runs-on:
|
||||
labels: ubuntu-latest-4core
|
||||
steps:
|
||||
- name: Initialize environment
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@f122b83d2a49e24eb9dfcd54fca9fc97612b832d
|
||||
- name: Setup Bazel
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@f122b83d2a49e24eb9dfcd54fca9fc97612b832d
|
||||
- name: Setup Bazel RBE
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@f122b83d2a49e24eb9dfcd54fca9fc97612b832d
|
||||
- name: Install node modules
|
||||
run: yarn install --frozen-lockfile
|
||||
- name: Build adev to ensure it continues to work
|
||||
run: yarn bazel test //adev:test
|
||||
|
||||
publish-snapshots:
|
||||
if: github.event_name == 'push'
|
||||
runs-on:
|
||||
labels: ubuntu-latest
|
||||
steps:
|
||||
- name: Initialize environment
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@f122b83d2a49e24eb9dfcd54fca9fc97612b832d
|
||||
with:
|
||||
cache-node-modules: true
|
||||
- name: Setup Bazel
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@f122b83d2a49e24eb9dfcd54fca9fc97612b832d
|
||||
- name: Setup Bazel RBE
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@f122b83d2a49e24eb9dfcd54fca9fc97612b832d
|
||||
- name: Install node modules
|
||||
run: yarn install --frozen-lockfile
|
||||
- run: echo "https://${{secrets.SNAPSHOT_BUILDS_GITHUB_TOKEN}}:@github.com" > ${HOME}/.git_credentials
|
||||
- run: yarn build
|
||||
- run: ./scripts/ci/publish-build-artifacts.sh
|
||||
|
||||
zone-js:
|
||||
runs-on:
|
||||
labels: ubuntu-latest-4core
|
||||
steps:
|
||||
- name: Initialize environment
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@f122b83d2a49e24eb9dfcd54fca9fc97612b832d
|
||||
with:
|
||||
cache-node-modules: true
|
||||
node-module-directories: |
|
||||
./node_modules
|
||||
./packages/zone.js/node_modules
|
||||
./packages/zone.js/test/typings/node_modules
|
||||
- name: Setup Bazel
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@f122b83d2a49e24eb9dfcd54fca9fc97612b832d
|
||||
- name: Setup Bazel RBE
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@f122b83d2a49e24eb9dfcd54fca9fc97612b832d
|
||||
- name: Install node modules
|
||||
run: yarn install --frozen-lockfile
|
||||
- run: |
|
||||
yarn bazel build \
|
||||
//packages/zone.js/bundles:zone.umd.js \
|
||||
//packages/zone.js:npm_package \
|
||||
//packages/zone.js/test/closure:closure_js \
|
||||
|
||||
- run: |
|
||||
rm -Rf packages/zone.js/build
|
||||
rm -Rf packages/zone.js/test/extra/*.umd.js
|
||||
|
||||
mkdir -p packages/zone.js/build/
|
||||
mkdir -p packages/zone.js/build/test/
|
||||
|
||||
cp dist/bin/packages/zone.js/bundles/zone.umd.js packages/zone.js/build/zone.umd.js
|
||||
cp dist/bin/packages/zone.js/npm_package/bundles/zone-mix.umd.js ./packages/zone.js/test/extra/
|
||||
cp dist/bin/packages/zone.js/npm_package/bundles/zone-patch-electron.umd.js ./packages/zone.js/test/extra/
|
||||
cp dist/bin/packages/zone.js/test/closure/zone.closure.mjs ./packages/zone.js/build/test/zone.closure.mjs
|
||||
|
||||
# Install
|
||||
- run: yarn --cwd packages/zone.js install --frozen-lockfile --non-interactive
|
||||
# Run zone.js tools tests
|
||||
- run: yarn --cwd packages/zone.js promisefinallytest
|
||||
- run: yarn --cwd packages/zone.js jest:test
|
||||
- run: yarn --cwd packages/zone.js jest:nodetest
|
||||
- run: yarn --cwd packages/zone.js electrontest
|
||||
- run: yarn --cwd packages/zone.js/test/typings install --frozen-lockfile --non-interactive
|
||||
- run: yarn --cwd packages/zone.js/test/typings test
|
||||
|
||||
bazel-saucelabs:
|
||||
# TODO: Replace legacy job with Bazel saucelabs job.
|
||||
if: github.event_name == 'push'
|
||||
@@ -177,7 +28,7 @@ jobs:
|
||||
JOBS: 2
|
||||
steps:
|
||||
- name: Initialize environment
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@f122b83d2a49e24eb9dfcd54fca9fc97612b832d
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@e6e98d32d7d4d350ecf4ec10ad9c7e75198daabd
|
||||
with:
|
||||
cache-node-modules: true
|
||||
# Checking out the pull request commit is intended here as we need to run the changed code tests.
|
||||
@@ -185,11 +36,11 @@ jobs:
|
||||
- name: Install node modules
|
||||
run: yarn install --frozen-lockfile
|
||||
- name: Setup Bazel
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@f122b83d2a49e24eb9dfcd54fca9fc97612b832d
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@e6e98d32d7d4d350ecf4ec10ad9c7e75198daabd
|
||||
- name: Setup Bazel Remote Caching
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@f122b83d2a49e24eb9dfcd54fca9fc97612b832d
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@e6e98d32d7d4d350ecf4ec10ad9c7e75198daabd
|
||||
- name: Setup Saucelabs Variables
|
||||
uses: angular/dev-infra/github-actions/saucelabs@f122b83d2a49e24eb9dfcd54fca9fc97612b832d
|
||||
uses: angular/dev-infra/github-actions/saucelabs@e6e98d32d7d4d350ecf4ec10ad9c7e75198daabd
|
||||
- name: Set up Sauce Tunnel Daemon
|
||||
run: yarn bazel run //tools/saucelabs-daemon/background-service -- $JOBS &
|
||||
env:
|
||||
@@ -205,15 +56,15 @@ jobs:
|
||||
SAUCE_TUNNEL_IDENTIFIER: angular-framework-${{ github.run_number }}
|
||||
steps:
|
||||
- name: Initialize environment
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@f122b83d2a49e24eb9dfcd54fca9fc97612b832d
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@e6e98d32d7d4d350ecf4ec10ad9c7e75198daabd
|
||||
with:
|
||||
cache-node-modules: true
|
||||
- name: Install node modules
|
||||
run: yarn install --frozen-lockfile
|
||||
- name: Setup Bazel
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@f122b83d2a49e24eb9dfcd54fca9fc97612b832d
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@e6e98d32d7d4d350ecf4ec10ad9c7e75198daabd
|
||||
- name: Setup Saucelabs Variables
|
||||
uses: angular/dev-infra/github-actions/saucelabs@f122b83d2a49e24eb9dfcd54fca9fc97612b832d
|
||||
uses: angular/dev-infra/github-actions/saucelabs@e6e98d32d7d4d350ecf4ec10ad9c7e75198daabd
|
||||
- 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
|
||||
@@ -245,34 +96,4 @@ jobs:
|
||||
- name: Stop Saucelabs tunnel service
|
||||
run: ./tools/saucelabs/sauce-service.sh stop
|
||||
|
||||
adev-deploy:
|
||||
needs: [adev]
|
||||
if: needs.adev.result == 'success' && github.event_name == 'push' && github.ref_name == 'main'
|
||||
env:
|
||||
DEPLOY_PROJECT: angular-dev-site
|
||||
DEPLOY_SITE: angular-dev-site
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Initialize environment
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@f122b83d2a49e24eb9dfcd54fca9fc97612b832d
|
||||
- name: Setup Bazel
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@f122b83d2a49e24eb9dfcd54fca9fc97612b832d
|
||||
- name: Setup Bazel RBE
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@f122b83d2a49e24eb9dfcd54fca9fc97612b832d
|
||||
- name: Install node modules
|
||||
run: yarn install --frozen-lockfile
|
||||
- name: Build adev to ensure it continues to work
|
||||
run: yarn bazel build //adev:build
|
||||
- name: Move generated project files into deployment directory
|
||||
run: cp -r dist/bin/adev/build adev/build
|
||||
- name: Setup firebase context
|
||||
run: |
|
||||
npx -y firebase-tools@latest target:clear --config adev/firebase.json --project ${{ env.DEPLOY_PROJECT }} hosting angular-docs
|
||||
npx -y firebase-tools@latest target:apply --config adev/firebase.json --project ${{ env.DEPLOY_PROJECT }} hosting angular-docs ${{ env.DEPLOY_SITE }}
|
||||
- name: Deploy to firebase
|
||||
uses: FirebaseExtended/action-hosting-deploy@120e124148ab7016bec2374e5050f15051255ba2 # v0.7.1
|
||||
with:
|
||||
entryPoint: './adev'
|
||||
firebaseServiceAccount: ${{ secrets.ANGULAR_DEV_SITE_DEPLOY }}
|
||||
projectId: ${{ env.DEPLOY_PROJECT }}
|
||||
channelId: live
|
||||
|
||||
@@ -12,14 +12,14 @@ jobs:
|
||||
labels:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@1d96c772d19495a3b5c517cd2bc0cb401ea0529f # v4.1.3
|
||||
- uses: angular/dev-infra/github-actions/commit-message-based-labels@f122b83d2a49e24eb9dfcd54fca9fc97612b832d
|
||||
- uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4
|
||||
- uses: angular/dev-infra/github-actions/commit-message-based-labels@e6e98d32d7d4d350ecf4ec10ad9c7e75198daabd
|
||||
with:
|
||||
angular-robot-key: ${{ secrets.ANGULAR_ROBOT_PRIVATE_KEY }}
|
||||
post_approval_changes:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@1d96c772d19495a3b5c517cd2bc0cb401ea0529f # v4.1.3
|
||||
- uses: angular/dev-infra/github-actions/post-approval-changes@f122b83d2a49e24eb9dfcd54fca9fc97612b832d
|
||||
- uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4
|
||||
- uses: angular/dev-infra/github-actions/post-approval-changes@e6e98d32d7d4d350ecf4ec10ad9c7e75198daabd
|
||||
with:
|
||||
angular-robot-key: ${{ secrets.ANGULAR_ROBOT_PRIVATE_KEY }}
|
||||
|
||||
@@ -13,8 +13,8 @@ jobs:
|
||||
statuses: write
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@1d96c772d19495a3b5c517cd2bc0cb401ea0529f # v4.1.3
|
||||
- uses: angular/dev-infra/github-actions/google-internal-tests@f122b83d2a49e24eb9dfcd54fca9fc97612b832d
|
||||
- uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4
|
||||
- uses: angular/dev-infra/github-actions/google-internal-tests@e6e98d32d7d4d350ecf4ec10ad9c7e75198daabd
|
||||
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@f122b83d2a49e24eb9dfcd54fca9fc97612b832d
|
||||
- uses: angular/dev-infra/github-actions/unified-status-check@e6e98d32d7d4d350ecf4ec10ad9c7e75198daabd
|
||||
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@f122b83d2a49e24eb9dfcd54fca9fc97612b832d
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@e6e98d32d7d4d350ecf4ec10ad9c7e75198daabd
|
||||
with:
|
||||
cache-node-modules: true
|
||||
node-module-directories: |
|
||||
./aio/node_modules
|
||||
- name: Setup Bazel
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@f122b83d2a49e24eb9dfcd54fca9fc97612b832d
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@e6e98d32d7d4d350ecf4ec10ad9c7e75198daabd
|
||||
- name: Setup Bazel RBE
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@f122b83d2a49e24eb9dfcd54fca9fc97612b832d
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@e6e98d32d7d4d350ecf4ec10ad9c7e75198daabd
|
||||
- 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@f122b83d2a49e24eb9dfcd54fca9fc97612b832d
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@e6e98d32d7d4d350ecf4ec10ad9c7e75198daabd
|
||||
with:
|
||||
cache-node-modules: true
|
||||
node-module-directories: |
|
||||
|
||||
@@ -25,7 +25,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: 'Checkout code'
|
||||
uses: actions/checkout@1d96c772d19495a3b5c517cd2bc0cb401ea0529f # v4.1.3
|
||||
uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
@@ -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@8f596b4ae3cb3c588a5c46780b86dd53fef16c52 # v3.25.2
|
||||
uses: github/codeql-action/upload-sarif@d39d31e687223d841ef683f52467bd88e9b21c14 # v3.25.3
|
||||
with:
|
||||
sarif_file: results.sarif
|
||||
|
||||
@@ -19,7 +19,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout the repository
|
||||
uses: actions/checkout@1d96c772d19495a3b5c517cd2bc0cb401ea0529f # v4.1.3
|
||||
uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4
|
||||
with:
|
||||
# Setting `persist-credentials: false` prevents the github-action account from being the
|
||||
# account that is attempted to be used for authentication, instead the remote is set to
|
||||
@@ -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@f122b83d2a49e24eb9dfcd54fca9fc97612b832d
|
||||
uses: angular/dev-infra/github-actions/create-pr-for-changes@e6e98d32d7d4d350ecf4ec10ad9c7e75198daabd
|
||||
with:
|
||||
branch-prefix: update-cli-help
|
||||
pr-title: 'docs: update Angular CLI help [${{github.ref_name}}]'
|
||||
|
||||
+5
-73
@@ -7,32 +7,7 @@ export const format: FormatConfig = {
|
||||
'prettier': {
|
||||
'matchers': [
|
||||
'**/*.{yaml,yml}',
|
||||
'adev/**/*.{js,ts}',
|
||||
'devtools/**/*.{js,ts}',
|
||||
'integration/**/*.{js,ts}',
|
||||
'tools/**/*.{js,ts}',
|
||||
'modules/**/*.{js,ts}',
|
||||
'scripts/**/*.{js,ts}',
|
||||
'packages/animations/**/*.{js,ts}',
|
||||
'packages/bazel/**/*.{js,ts}',
|
||||
'packages/benchpress/**/*.{js,ts}',
|
||||
'packages/common/**/*.{js,ts}',
|
||||
'packages/compiler/**/*.{js,ts}',
|
||||
'packages/core/primitives/**/*.{js,ts}',
|
||||
'packages/docs/**/*.{js,ts}',
|
||||
'packages/elements/**/*.{js,ts}',
|
||||
'packages/examples/**/*.{js,ts}',
|
||||
'packages/forms/**/*.{js,ts}',
|
||||
'packages/language-service/**/*.{js,ts}',
|
||||
'packages/localize/**/*.{js,ts}',
|
||||
'packages/platform-browser/**/*.{js,ts}',
|
||||
'packages/platform-browser-dynamic/**/*.{js,ts}',
|
||||
'packages/platform-server/**/*.{js,ts}',
|
||||
'packages/misc/**/*.{js,ts}',
|
||||
'packages/private/**/*.{js,ts}',
|
||||
'packages/router/**/*.{js,ts}',
|
||||
'packages/service-worker/**/*.{js,ts}',
|
||||
'packages/upgrade/**/*.{js,ts}',
|
||||
'**/*.{js,ts}',
|
||||
|
||||
// Do not format d.ts files as they are generated
|
||||
'!**/*.d.ts',
|
||||
@@ -40,58 +15,15 @@ export const format: FormatConfig = {
|
||||
// not be modified.
|
||||
'!third_party/**',
|
||||
'!.yarn/**',
|
||||
],
|
||||
},
|
||||
'clang-format': {
|
||||
'matchers': [
|
||||
'**/*.{js,ts}',
|
||||
// TODO: burn down format failures and remove aio and integration exceptions.
|
||||
'!aio/**',
|
||||
'!integration/**',
|
||||
// Both third_party and .yarn are directories containing copied code which should
|
||||
// not be modified.
|
||||
'!third_party/**',
|
||||
'!.yarn/**',
|
||||
// Do not format d.ts files as they are generated
|
||||
'!**/*.d.ts',
|
||||
// Do not format generated ng-dev script
|
||||
'!dev-infra/ng-dev.js',
|
||||
'!dev-infra/build-worker.js',
|
||||
// Do not format compliance test-cases since they must match generated code
|
||||
'!packages/compiler-cli/test/compliance/test_cases/**/*.js',
|
||||
// Do not format the locale files which are checked-in for Google3, but generated using
|
||||
// the `generate-locales-tool` from `packages/common/locales`.
|
||||
'!packages/core/src/i18n/locale_en.ts',
|
||||
'!packages/common/locales/closure-locale.ts',
|
||||
'!packages/common/src/i18n/currencies.ts',
|
||||
// Temporarily disable formatting for adev
|
||||
'!adev/**',
|
||||
|
||||
// Migrated to prettier
|
||||
'!devtools/**/*.{js,ts}',
|
||||
'!tools/**/*.{js,ts}',
|
||||
'!modules/**/*.{js,ts}',
|
||||
'!scripts/**/*.{js,ts}',
|
||||
'!packages/animations/**/*.{js,ts}',
|
||||
'!packages/bazel/**/*.{js,ts}',
|
||||
'!packages/benchpress/**/*.{js,ts}',
|
||||
'!packages/common/**/*.{js,ts}',
|
||||
'!packages/compiler/**/*.{js,ts}',
|
||||
'!packages/core/primitives/**/*.{js,ts}',
|
||||
'!packages/docs/**/*.{js,ts}',
|
||||
'!packages/elements/**/*.{js,ts}',
|
||||
'!packages/examples/**/*.{js,ts}',
|
||||
'!packages/forms/**/*.{js,ts}',
|
||||
'!packages/language-service/**/*.{js,ts}',
|
||||
'!packages/localize/**/*.{js,ts}',
|
||||
'!packages/platform-browser/**/*.{js,ts}',
|
||||
'!packages/platform-browser-dynamic/**/*.{js,ts}',
|
||||
'!packages/platform-server/**/*.{js,ts}',
|
||||
'!packages/misc/**/*.{js,ts}',
|
||||
'!packages/private/**/*.{js,ts}',
|
||||
'!packages/router/**/*.{js,ts}',
|
||||
'!packages/service-worker/**/*.{js,ts}',
|
||||
'!packages/upgrade/**/*.{js,ts}',
|
||||
// Test cases contain non valid code.
|
||||
'!packages/compiler-cli/test/compliance/test_cases/**/*.{js,ts}',
|
||||
// Ignore AIO as its pending removal.
|
||||
'!aio/**/*.{js,ts}',
|
||||
],
|
||||
},
|
||||
'buildifier': true,
|
||||
|
||||
Vendored
-1
@@ -6,6 +6,5 @@
|
||||
"recommendations": [
|
||||
"BazelBuild.vscode-bazel",
|
||||
"ms-vscode.vscode-typescript-tslint-plugin",
|
||||
"xaver.clang-format",
|
||||
],
|
||||
}
|
||||
|
||||
Vendored
-6
@@ -1,16 +1,10 @@
|
||||
{
|
||||
// Format js and ts files on save with `clang-format.executable`
|
||||
// If `clang-format.executable` is not being used, these two settings should be removed otherwise it will break existing formatting.
|
||||
// You can instead run `yarn gulp format` to manually format your code.
|
||||
"[javascript]": {
|
||||
"editor.formatOnSave": true,
|
||||
},
|
||||
"[typescript]": {
|
||||
"editor.formatOnSave": true,
|
||||
},
|
||||
// Please install https://marketplace.visualstudio.com/items?itemName=xaver.clang-format to take advantage of `clang-format` in VSCode.
|
||||
// (See https://clang.llvm.org/docs/ClangFormat.html for more info `clang-format`.)
|
||||
"clang-format.executable": "${workspaceRoot}/node_modules/.bin/clang-format",
|
||||
// Exclude third party modules and build artifacts from the editor watchers/searches.
|
||||
"files.watcherExclude": {
|
||||
"**/.git/objects/**": true,
|
||||
|
||||
+108
-15
@@ -1,3 +1,111 @@
|
||||
<a name="18.0.0-rc.0"></a>
|
||||
# 18.0.0-rc.0 (2024-05-01)
|
||||
### core
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [44c0ed83a6](https://github.com/angular/angular/commit/44c0ed83a6499fa96f65a27bc5c926579c06b6d2) | fix | hide implementation details of ExperimentalPendingTasks ([#55516](https://github.com/angular/angular/pull/55516)) |
|
||||
| [a177abef1b](https://github.com/angular/angular/commit/a177abef1b0a0edc46dcca2d8c5c67669c55ff8f) | fix | render hooks should not specifically run outside the Angular zone ([#55399](https://github.com/angular/angular/pull/55399)) |
|
||||
| [a5c57c7484](https://github.com/angular/angular/commit/a5c57c7484f1dc3afab4ece4e969a4a7308cdeca) | fix | resolve error for multiple component instances that use fallback content ([#55478](https://github.com/angular/angular/pull/55478)) |
|
||||
### migrations
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [f93e5180be](https://github.com/angular/angular/commit/f93e5180be1e20a59ff68f12853653f4f3282846) | fix | resolve multiple structural issues with HttpClient migration ([#55557](https://github.com/angular/angular/pull/55557)) |
|
||||
### router
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [aefee87074](https://github.com/angular/angular/commit/aefee87074f4a5b2531f989a1876ace6e6616da4) | fix | Scroller should scroll as soon as change detection completes ([#55105](https://github.com/angular/angular/pull/55105)) |
|
||||
|
||||
<!-- CHANGELOG SPLIT MARKER -->
|
||||
|
||||
<a name="17.3.7"></a>
|
||||
# 17.3.7 (2024-05-01)
|
||||
### compiler-cli
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [51ac883167](https://github.com/angular/angular/commit/51ac8831670637b562dfa135bec1e27a0b49f21d) | fix | don't type check the bodies of control flow nodes in basic mode ([#55558](https://github.com/angular/angular/pull/55558)) |
|
||||
### core
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [af0eb846a5](https://github.com/angular/angular/commit/af0eb846a572d0e9de2c6bb7016ddd339473dc00) | fix | render hooks should not specifically run outside the Angular zone ([#55399](https://github.com/angular/angular/pull/55399)) |
|
||||
### router
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [3eea50da64](https://github.com/angular/angular/commit/3eea50da644f6023e7f001b8738c54cb89aece4c) | fix | Scroller should scroll as soon as change detection completes ([#55105](https://github.com/angular/angular/pull/55105)) |
|
||||
|
||||
<!-- CHANGELOG SPLIT MARKER -->
|
||||
|
||||
<a name="18.0.0-next.6"></a>
|
||||
# 18.0.0-next.6 (2024-04-25)
|
||||
## Breaking Changes
|
||||
### animations
|
||||
- Deprecated `matchesElement` method has been removed from `AnimationDriver` as it is unused.
|
||||
### core
|
||||
- `async` has been removed, use `waitForAsync` instead.
|
||||
### http
|
||||
- By default we now prevent caching of HTTP requests that require authorization . To opt-out from this behaviour use the `includeRequestsWithAuthHeaders` option in `withHttpTransferCache`.
|
||||
|
||||
Example:
|
||||
```ts
|
||||
withHttpTransferCache({
|
||||
includeRequestsWithAuthHeaders: true,
|
||||
})
|
||||
```
|
||||
### platform-browser
|
||||
- Deprecated `StateKey`, `TransferState` and `makeStateKey` have been removed from `@angular/platform-browser`, use the same APIs from `@angular/core`.
|
||||
## Deprecations
|
||||
### common
|
||||
- `getCurrencySymbol`, `getLocaleCurrencyCode`, `getLocaleCurrencyName`, `getLocaleCurrencySymbol`, `getLocaleDateFormat`, `getLocaleDateTimeFormat`, `getLocaleDayNames`, `getLocaleDayPeriods`, `getLocaleDirection`, `getLocaleEraNames`, `getLocaleExtraDayPeriodRules`, `getLocaleExtraDayPeriods`, `getLocaleFirstDayOfWeek`, `getLocaleId`, `getLocaleMonthNames`, `getLocaleNumberFormat`, `getLocaleNumberSymbol`, `getLocalePluralCase`, `getLocaleTimeFormat`, `getLocaleWeekEndRange`, `getNumberOfCurrencyDigits`
|
||||
### http
|
||||
- `HttpClientModule`, `HttpClientXsrfModule` and `HttpClientJsonpModule`
|
||||
|
||||
As mentionned, those modules can be replaced by provider function only.
|
||||
### animations
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [bcce85af72](https://github.com/angular/angular/commit/bcce85af72a82634f60b31d66a5ef42ecd844ce8) | refactor | remove deprecated `matchesElement` from `AnimationDriver` ([#55479](https://github.com/angular/angular/pull/55479)) |
|
||||
### common
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [d34c033902](https://github.com/angular/angular/commit/d34c033902b4e7543796de6c57d0cfd09093eb43) | refactor | Deprecate Local Data API functions ([#54483](https://github.com/angular/angular/pull/54483)) |
|
||||
### compiler-cli
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [7a16d7e969](https://github.com/angular/angular/commit/7a16d7e969eaf5a9475ffdd21a4bf637ce523856) | fix | don't type check the bodies of control flow nodes in basic mode ([#55360](https://github.com/angular/angular/pull/55360)) |
|
||||
### core
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [a730f09ae9](https://github.com/angular/angular/commit/a730f09ae9e729da79a3e0951e15e0139ef67713) | feat | Add a public API to establish events to be replayed and an attribute to mark an element with an event handler. ([#55356](https://github.com/angular/angular/pull/55356)) |
|
||||
| [5f06ca8f55](https://github.com/angular/angular/commit/5f06ca8f5539ed208bae0b110887b5538ac4041f) | feat | add HOST_TAG_NAME token ([#54751](https://github.com/angular/angular/pull/54751)) |
|
||||
| [ac863ded48](https://github.com/angular/angular/commit/ac863ded4818af3426ef5888c706a2bd8c79c0be) | feat | provide ExperimentalPendingTasks API ([#55487](https://github.com/angular/angular/pull/55487)) |
|
||||
| [5a10f405d3](https://github.com/angular/angular/commit/5a10f405d315a28b9a000c669e9b1cb3fa24a7f1) | fix | complete the removal of deprecation `async` function ([#55491](https://github.com/angular/angular/pull/55491)) |
|
||||
| [c175bca364](https://github.com/angular/angular/commit/c175bca36432b26851adb4ea9d850c2d07fba864) | fix | DeferBlockFixture.render should not wait for stability ([#55271](https://github.com/angular/angular/pull/55271)) |
|
||||
| [9894278e71](https://github.com/angular/angular/commit/9894278e712a50079af87898a63e1d19a462d015) | fix | make `ActivatedRoute` inject correct instance inside `@defer` blocks ([#55374](https://github.com/angular/angular/pull/55374)) |
|
||||
| [5948193e13](https://github.com/angular/angular/commit/5948193e1368eec86647b4c1c0b337a387b27087) | fix | skip defer timers on the server ([#55480](https://github.com/angular/angular/pull/55480)) |
|
||||
### http
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [6f88d80758](https://github.com/angular/angular/commit/6f88d8075895bd80592b1b7e0fba8202a58a5417) | feat | allow caching requests with different origins between server and client ([#55274](https://github.com/angular/angular/pull/55274)) |
|
||||
| [8eacb6e4b9](https://github.com/angular/angular/commit/8eacb6e4b982a5aa23cfbf9078dc4e19d9466d73) | feat | exclude caching for authenticated HTTP requests ([#55034](https://github.com/angular/angular/pull/55034)) |
|
||||
| [ef665a40a5](https://github.com/angular/angular/commit/ef665a40a580ff4df79617084ac83738f28ae924) | refactor | Deprecate `HttpClientModule` & related modules ([#54020](https://github.com/angular/angular/pull/54020)) |
|
||||
### migrations
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [f914f6a362](https://github.com/angular/angular/commit/f914f6a3628847c06cbdde9c90cd417fb2f4c61f) | feat | Migration schematics for `HttpClientModule` ([#54020](https://github.com/angular/angular/pull/54020)) |
|
||||
### platform-browser
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [cba336d4f1](https://github.com/angular/angular/commit/cba336d4f1badd601b24a58fc51bde995f45682d) | refactor | remove deprecated transfer state APIs ([#55474](https://github.com/angular/angular/pull/55474)) |
|
||||
### platform-server
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [5674c644ab](https://github.com/angular/angular/commit/5674c644abf51ae8764befd3011742ff1febdf29) | fix | add `nonce` attribute to event record script ([#55495](https://github.com/angular/angular/pull/55495)) |
|
||||
### router
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [4a42961393](https://github.com/angular/angular/commit/4a42961393b3abf40f34374df059d3959dadecc0) | feat | `withNavigationErrorHandler` can convert errors to redirects ([#55370](https://github.com/angular/angular/pull/55370)) |
|
||||
|
||||
<!-- CHANGELOG SPLIT MARKER -->
|
||||
|
||||
<a name="17.3.6"></a>
|
||||
# 17.3.6 (2024-04-25)
|
||||
### core
|
||||
@@ -970,21 +1078,6 @@ Live long and prosper 🖖🏻
|
||||
|
||||
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]);
|
||||
```
|
||||
|
||||
+1
-1
@@ -207,7 +207,7 @@ To ensure consistency throughout the source code, keep these rules in mind as yo
|
||||
* All public API methods **must be documented**.
|
||||
* We follow [Google's JavaScript Style Guide][js-style-guide], but wrap all code at **100 characters**.
|
||||
|
||||
An automated formatter is available, see [DEVELOPER.md](docs/DEVELOPER.md#clang-format).
|
||||
An automated formatter is available, see [DEVELOPER.md](docs/DEVELOPER.md#formatting-your-source-code).
|
||||
|
||||
|
||||
## <a name="commit"></a> Commit Message Format
|
||||
|
||||
@@ -63,22 +63,7 @@ Get started with Angular, learn the fundamentals and explore advanced topics on
|
||||
|
||||
### Local Development
|
||||
|
||||
To contribute to Angular docs, you can setup a local environment with the following commands:
|
||||
|
||||
```bash
|
||||
# Clone Angular repo
|
||||
git clone https://github.com/angular/angular.git
|
||||
|
||||
# Navigate to project directory
|
||||
cd angular
|
||||
|
||||
# Install dependencies
|
||||
yarn
|
||||
|
||||
# Build and run local dev server
|
||||
# Note: Initial build will take some time
|
||||
yarn docs
|
||||
```
|
||||
To contribute to the Angular Docs, check out the [Angular.dev README](adev/README.md)
|
||||
|
||||
## Development Setup
|
||||
|
||||
|
||||
@@ -75,7 +75,7 @@ yarn_install(
|
||||
"//:.yarnrc",
|
||||
"//:tools/npm-patches/@bazel+jasmine+5.8.1.patch",
|
||||
"//tools:postinstall-patches.js",
|
||||
"//tools/esm-interop:patches/npm/@angular+build-tooling+0.0.0-e0ec7b60641d7f6369be45d8d02663fd50f320be.patch",
|
||||
"//tools/esm-interop:patches/npm/@angular+build-tooling+0.0.0-56d0b1f597b9e4b5d99f289f6904a220802164c1.patch",
|
||||
"//tools/esm-interop:patches/npm/@bazel+concatjs+5.8.1.patch",
|
||||
"//tools/esm-interop:patches/npm/@bazel+esbuild+5.7.1.patch",
|
||||
"//tools/esm-interop:patches/npm/@bazel+protractor+5.7.1.patch",
|
||||
@@ -167,10 +167,10 @@ cldr_xml_data_repository(
|
||||
# sass rules
|
||||
http_archive(
|
||||
name = "io_bazel_rules_sass",
|
||||
sha256 = "ca27b3dcd294b134ccc9d0a1c6b63b810e115204e87a2b3d4cc247f5676d0a85",
|
||||
strip_prefix = "rules_sass-c3f163fd9f570846c2aae4bf16ac69b4217e6f3b",
|
||||
sha256 = "845061fad5cb42a353ac394fe6450ec9f21d337acf5b98011ef29ddcb33e3671",
|
||||
strip_prefix = "rules_sass-9f8be59f5175d0dd3438d7da994b2782299e0c2c",
|
||||
urls = [
|
||||
"https://github.com/bazelbuild/rules_sass/archive/c3f163fd9f570846c2aae4bf16ac69b4217e6f3b.zip",
|
||||
"https://github.com/bazelbuild/rules_sass/archive/9f8be59f5175d0dd3438d7da994b2782299e0c2c.zip",
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
@@ -42,6 +42,7 @@ APPLICATION_ASSETS = [
|
||||
]
|
||||
|
||||
APPLICATION_DEPS = [
|
||||
"@npm//@angular/build",
|
||||
"@npm//@angular-devkit/build-angular",
|
||||
"@npm//@angular/animations",
|
||||
"@npm//@angular/cdk",
|
||||
@@ -78,8 +79,11 @@ APPLICATION_DEPS = [
|
||||
"@npm//@lezer/javascript",
|
||||
"@npm//@lezer/common",
|
||||
"@npm//@xterm/xterm",
|
||||
"@npm//algoliasearch",
|
||||
"@npm//xterm",
|
||||
"@npm//xterm-addon-fit",
|
||||
"@npm//angular-split",
|
||||
"@npm//zone.js",
|
||||
]
|
||||
|
||||
TEST_DEPS = APPLICATION_DEPS + [
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
# [Angular.dev](https://www.angular.dev)
|
||||
|
||||
This site is built with Angular.
|
||||
|
||||
The content is written primarly in Markdown format located in `src/content`. For simple edits, you can directly edit the file on GitHub and generate a Pull Request.
|
||||
|
||||
## Local Development
|
||||
|
||||
For local development, [yarn](https://yarnpkg.com/) is the preferred package manager. You can setup a local environment with the following commands
|
||||
:
|
||||
|
||||
```bash
|
||||
# Clone Angular repo
|
||||
git clone https://github.com/angular/angular.git
|
||||
|
||||
# Navigate to project directory
|
||||
cd angular
|
||||
|
||||
# Install dependencies
|
||||
yarn
|
||||
|
||||
# Build and run local dev server
|
||||
# Note: Initial build will take some time
|
||||
yarn docs
|
||||
```
|
||||
|
||||
## Contributing
|
||||
|
||||
Want to report a bug, contribute some code, or improve the documentation? Excellent!
|
||||
|
||||
Read through our [contributing guidelines](CONTRIBUTING.md) to learn about our submission process, coding rules, and more.
|
||||
|
||||
And if you're new, check out one of our issues labeled as <kbd>[help wanted](https://github.com/angular/angular/labels/help%20wanted)</kbd> or <kbd>[good first issue](https://github.com/angular/angular/labels/good%20first%20issue)</kbd>.
|
||||
|
||||
### Code of Conduct
|
||||
|
||||
Help us keep Angular open and inclusive. Please read and follow our [Code of Conduct](CODE_OF_CONDUCT.md).
|
||||
+1
-1
@@ -17,7 +17,7 @@
|
||||
"prefix": "adev",
|
||||
"architect": {
|
||||
"build": {
|
||||
"builder": "@angular-devkit/build-angular:application",
|
||||
"builder": "@angular/build:application",
|
||||
"options": {
|
||||
"externalDependencies": ["path"],
|
||||
"outputPath": "dist/angular-dev",
|
||||
|
||||
@@ -61,6 +61,9 @@
|
||||
<nav
|
||||
class="adev-nav-primary docs-scroll-hide"
|
||||
[class.adev-nav-primary--open]="isMobileNavigationOpened()"
|
||||
[class.adev-nav-primary--rc]="currentDocsVersionMode === 'rc'"
|
||||
[class.adev-nav-primary--next]="currentDocsVersionMode === 'next'"
|
||||
[class.adev-nav-primary--deprecated]="currentDocsVersionMode === 'deprecated'"
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
@@ -122,7 +125,6 @@
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
<div class="adev-beta-version">Beta Docs</div>
|
||||
</a>
|
||||
|
||||
<!-- Version picker for v18+ -->
|
||||
|
||||
@@ -69,6 +69,27 @@
|
||||
padding-block-end: 2rem;
|
||||
box-sizing: border-box;
|
||||
|
||||
&.adev-nav-primary--rc {
|
||||
// change nav background to indicate this is the rc docs
|
||||
background: linear-gradient(
|
||||
140deg,
|
||||
color-mix(in srgb, var(--orange-red), transparent 60%) 0%,
|
||||
color-mix(in srgb, var(--vivid-pink), transparent 40%) 15%,
|
||||
color-mix(in srgb, var(--electric-violet), transparent 70%) 25%,
|
||||
color-mix(in srgb, var(--bright-blue), transparent 60%) 90%,
|
||||
);
|
||||
}
|
||||
|
||||
&.adev-nav-primary--next {
|
||||
// change nav background to indicate this is the rc docs
|
||||
background-color: color-mix(in srgb, var(--orange-red), transparent 60%);
|
||||
}
|
||||
|
||||
&.adev-nav-primary--deprecated {
|
||||
// change nav background to indicate this is an outdated version of the docs
|
||||
background-color: color-mix(in srgb, var(--symbolic-gray), transparent 30%);
|
||||
}
|
||||
|
||||
// div containing mobile close button and adev-nav_top
|
||||
> div {
|
||||
display: flex;
|
||||
@@ -129,13 +150,6 @@
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.adev-beta-version {
|
||||
font-family: 'Material Symbols Outlined';
|
||||
text-transform: uppercase;
|
||||
text-align: center;
|
||||
line-height: normal;
|
||||
}
|
||||
|
||||
// version dropdown button
|
||||
.adev-version-button {
|
||||
border: 1px solid var(--senary-contrast);
|
||||
|
||||
@@ -102,6 +102,8 @@ export class Navigation implements OnInit {
|
||||
openedMenu: MenuType | null = null;
|
||||
|
||||
currentDocsVersion = this.versionManager.currentDocsVersion;
|
||||
currentDocsVersionMode = this.versionManager.currentDocsVersion()?.version;
|
||||
|
||||
// Set the values of the search label and title only on the client, because the label is user-agent specific.
|
||||
searchLabel = this.isBrowser
|
||||
? isApple
|
||||
|
||||
@@ -7,8 +7,9 @@
|
||||
*/
|
||||
|
||||
import {Injector} from '@angular/core';
|
||||
import {ENVIRONMENT, WINDOW} from '@angular/docs';
|
||||
import {ENVIRONMENT, WINDOW, LOCAL_STORAGE} from '@angular/docs';
|
||||
import {AnalyticsService} from './analytics.service';
|
||||
import {MockLocalStorage} from '@angular/docs/testing';
|
||||
|
||||
describe('AnalyticsService', () => {
|
||||
let service: AnalyticsService;
|
||||
@@ -18,6 +19,7 @@ describe('AnalyticsService', () => {
|
||||
let windowOnErrorHandler: (event: ErrorEvent) => void;
|
||||
|
||||
let mockWindow: any;
|
||||
let mockLocalStorage = new MockLocalStorage();
|
||||
|
||||
beforeEach(() => {
|
||||
gtagSpy = jasmine.createSpy('gtag');
|
||||
@@ -39,6 +41,7 @@ describe('AnalyticsService', () => {
|
||||
{provide: ENVIRONMENT, useValue: {}},
|
||||
{provide: AnalyticsService, deps: [WINDOW]},
|
||||
{provide: WINDOW, useFactory: () => mockWindow, deps: []},
|
||||
{provide: LOCAL_STORAGE, useValue: mockLocalStorage},
|
||||
],
|
||||
});
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
import {inject, Injectable} from '@angular/core';
|
||||
|
||||
import {WINDOW, ENVIRONMENT} from '@angular/docs';
|
||||
import {WINDOW, ENVIRONMENT, LOCAL_STORAGE, STORAGE_KEY, setCookieConsent} from '@angular/docs';
|
||||
|
||||
import {formatErrorEventForAnalytics} from './analytics-format-error';
|
||||
|
||||
@@ -28,6 +28,7 @@ interface WindowWithAnalytics extends Window {
|
||||
export class AnalyticsService {
|
||||
private environment = inject(ENVIRONMENT);
|
||||
private window: WindowWithAnalytics = inject(WINDOW);
|
||||
private readonly localStorage = inject(LOCAL_STORAGE);
|
||||
|
||||
constructor() {
|
||||
this._installGlobalSiteTag();
|
||||
@@ -64,6 +65,21 @@ export class AnalyticsService {
|
||||
window.gtag = function () {
|
||||
window.dataLayer?.push(arguments);
|
||||
};
|
||||
|
||||
// Cookie banner consent initial state
|
||||
// This code is modified in the @angular/docs package in the cookie-popup component.
|
||||
// Docs: https://developers.google.com/tag-platform/security/guides/consent
|
||||
if (this.localStorage) {
|
||||
if (this.localStorage.getItem(STORAGE_KEY) === 'true') {
|
||||
setCookieConsent('granted');
|
||||
} else {
|
||||
setCookieConsent('denied');
|
||||
}
|
||||
} else {
|
||||
// In case localStorage is not available, we default to denying cookies.
|
||||
setCookieConsent('denied');
|
||||
}
|
||||
|
||||
window.gtag('js', new Date());
|
||||
|
||||
// Configure properties before loading the script. This is necessary to avoid
|
||||
|
||||
@@ -15,7 +15,7 @@ export interface Version {
|
||||
url: string;
|
||||
}
|
||||
|
||||
export type VersionMode = 'stable' | 'rc' | 'next' | number;
|
||||
export type VersionMode = 'stable' | 'deprecated' | 'rc' | 'next' | number;
|
||||
|
||||
export const INITIAL_DOCS_VERSION = 17;
|
||||
export const VERSION_PATTERN_PLACEHOLDER = '{{version}}';
|
||||
|
||||
@@ -10,7 +10,29 @@
|
||||
@for (file of files(); track file) {
|
||||
<mat-tab #tab>
|
||||
<ng-template mat-tab-label>
|
||||
@if (tab.isActive && isRenamingFile()) {
|
||||
<form
|
||||
(submit)="renameFile($event, file.filename)"
|
||||
(docsClickOutside)="closeRenameFile()">
|
||||
<input
|
||||
name="rename-file"
|
||||
class="adev-rename-file-input"
|
||||
#renameFileInput
|
||||
(keydown)="$event.stopPropagation()"
|
||||
/>
|
||||
</form>
|
||||
} @else {
|
||||
{{ file.filename.replace('src/', '') }}
|
||||
}
|
||||
@if (tab.isActive && canRenameFile(file.filename)) {
|
||||
<button
|
||||
class="docs-rename-file"
|
||||
aria-label="rename file"
|
||||
(click)="onRenameButtonClick()"
|
||||
>
|
||||
<docs-icon>edit</docs-icon>
|
||||
</button>
|
||||
}
|
||||
@if (tab.isActive && canDeleteFile(file.filename)) {
|
||||
<button
|
||||
class="docs-delete-file"
|
||||
|
||||
@@ -79,7 +79,8 @@
|
||||
}
|
||||
|
||||
.adev-add-file,
|
||||
.docs-delete-file {
|
||||
.docs-delete-file,
|
||||
.docs-rename-file {
|
||||
docs-icon {
|
||||
color: var(--gray-400);
|
||||
transition: color 0.3s ease;
|
||||
@@ -92,7 +93,8 @@
|
||||
}
|
||||
}
|
||||
|
||||
.docs-delete-file {
|
||||
.docs-delete-file,
|
||||
.docs-rename-file {
|
||||
padding-inline-start: 0.1rem;
|
||||
padding-inline-end: 0;
|
||||
margin-block-start: 0.2rem;
|
||||
@@ -101,7 +103,8 @@
|
||||
}
|
||||
}
|
||||
|
||||
.adev-new-file-input {
|
||||
.adev-new-file-input,
|
||||
.adev-rename-file-input {
|
||||
color: var(--primary-contrast);
|
||||
border: none;
|
||||
border-radius: 0;
|
||||
|
||||
@@ -107,7 +107,7 @@ describe('CodeEditor', () => {
|
||||
const tabs = await matTabGroup.getTabs();
|
||||
const expectedLabels = files.map((file, index) => {
|
||||
const label = file.filename.replace('src/', '');
|
||||
if (index === 0) return `${label} delete`;
|
||||
if (index === 0) return `${label} editdelete`;
|
||||
return label;
|
||||
});
|
||||
|
||||
|
||||
@@ -28,9 +28,13 @@ import {EmbeddedTutorialManager} from '../embedded-tutorial-manager.service';
|
||||
import {CodeMirrorEditor} from './code-mirror-editor.service';
|
||||
import {DiagnosticWithLocation, DiagnosticsState} from './services/diagnostics-state.service';
|
||||
import {DownloadManager} from '../download-manager.service';
|
||||
import {IconComponent} from '@angular/docs';
|
||||
import {ClickOutside, IconComponent} from '@angular/docs';
|
||||
|
||||
export const REQUIRED_FILES = new Set(['src/main.ts', 'src/index.html']);
|
||||
export const REQUIRED_FILES = new Set([
|
||||
'src/main.ts',
|
||||
'src/index.html',
|
||||
'src/app/app.component.ts',
|
||||
]);
|
||||
|
||||
@Component({
|
||||
selector: 'docs-tutorial-code-editor',
|
||||
@@ -38,7 +42,7 @@ export const REQUIRED_FILES = new Set(['src/main.ts', 'src/index.html']);
|
||||
templateUrl: './code-editor.component.html',
|
||||
styleUrls: ['./code-editor.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
imports: [NgIf, NgFor, MatTabsModule, IconComponent],
|
||||
imports: [NgIf, NgFor, MatTabsModule, IconComponent, ClickOutside],
|
||||
})
|
||||
export class CodeEditor implements AfterViewInit, OnDestroy {
|
||||
@ViewChild('codeEditorWrapper') private codeEditorWrapperRef!: ElementRef<HTMLDivElement>;
|
||||
@@ -54,6 +58,16 @@ export class CodeEditor implements AfterViewInit, OnDestroy {
|
||||
}
|
||||
}
|
||||
|
||||
private renameFileInputRef?: ElementRef<HTMLInputElement>;
|
||||
@ViewChild('renameFileInput') protected set setRenameFileInputRef(
|
||||
element: ElementRef<HTMLInputElement>,
|
||||
) {
|
||||
if (element) {
|
||||
element.nativeElement.focus();
|
||||
this.renameFileInputRef = element;
|
||||
}
|
||||
}
|
||||
|
||||
private readonly destroyRef = inject(DestroyRef);
|
||||
|
||||
private readonly codeMirrorEditor = inject(CodeMirrorEditor);
|
||||
@@ -82,6 +96,7 @@ export class CodeEditor implements AfterViewInit, OnDestroy {
|
||||
readonly errors = signal<DiagnosticWithLocation[]>([]);
|
||||
readonly files = this.codeMirrorEditor.openFiles;
|
||||
readonly isCreatingFile = signal<boolean>(false);
|
||||
readonly isRenamingFile = signal<boolean>(false);
|
||||
|
||||
ngAfterViewInit() {
|
||||
this.codeMirrorEditor.init(this.codeEditorWrapperRef.nativeElement);
|
||||
@@ -104,6 +119,12 @@ export class CodeEditor implements AfterViewInit, OnDestroy {
|
||||
this.displayErrorsBox.set(false);
|
||||
}
|
||||
|
||||
closeRenameFile(): void {
|
||||
this.isRenamingFile.set(false);
|
||||
}
|
||||
|
||||
canRenameFile = (filename: string) => this.canDeleteFile(filename);
|
||||
|
||||
canDeleteFile(filename: string) {
|
||||
return !REQUIRED_FILES.has(filename);
|
||||
}
|
||||
@@ -118,6 +139,37 @@ export class CodeEditor implements AfterViewInit, OnDestroy {
|
||||
this.matTabGroup.selectedIndex = this.files().length;
|
||||
}
|
||||
|
||||
onRenameButtonClick() {
|
||||
this.isRenamingFile.set(true);
|
||||
}
|
||||
|
||||
async renameFile(event: SubmitEvent, oldPath: string) {
|
||||
if (!this.renameFileInputRef) return;
|
||||
|
||||
event.preventDefault();
|
||||
|
||||
const renameFileInputValue = this.renameFileInputRef.nativeElement.value;
|
||||
|
||||
if (renameFileInputValue) {
|
||||
if (renameFileInputValue.includes('..')) {
|
||||
alert('File name can not contain ".."');
|
||||
return;
|
||||
}
|
||||
|
||||
// src is hidden from users, here we manually add it to the new filename
|
||||
const newFile = 'src/' + renameFileInputValue;
|
||||
|
||||
if (this.files().find(({filename}) => filename.includes(newFile))) {
|
||||
alert('File name already exists');
|
||||
return;
|
||||
}
|
||||
|
||||
await this.codeMirrorEditor.renameFile(oldPath, newFile);
|
||||
}
|
||||
|
||||
this.isRenamingFile.set(false);
|
||||
}
|
||||
|
||||
async createFile(event: SubmitEvent) {
|
||||
if (!this.createFileInputRef) return;
|
||||
|
||||
|
||||
@@ -340,6 +340,34 @@ export class CodeMirrorEditor {
|
||||
this.changeCurrentFile(filename);
|
||||
}
|
||||
|
||||
async renameFile(oldPath: string, newPath: string) {
|
||||
const content = await this.nodeRuntimeSandbox.readFile(oldPath).catch((error) => {
|
||||
// empty content if file does not exist
|
||||
if (error.message.includes('ENOENT')) return '';
|
||||
else throw error;
|
||||
});
|
||||
|
||||
await this.nodeRuntimeSandbox.renameFile(oldPath, newPath).catch((error) => {
|
||||
throw error;
|
||||
});
|
||||
|
||||
this.embeddedTutorialManager.tutorialFiles.update((files) => {
|
||||
delete files[oldPath];
|
||||
files[newPath] = content;
|
||||
return files;
|
||||
});
|
||||
|
||||
this.embeddedTutorialManager.openFiles.update((files) => [
|
||||
...files.filter((file) => file !== oldPath),
|
||||
newPath,
|
||||
]);
|
||||
|
||||
this.setProjectFiles();
|
||||
this.updateVfsEnv();
|
||||
this.saveLibrariesTypes();
|
||||
this.changeCurrentFile(newPath);
|
||||
}
|
||||
|
||||
async deleteFile(deletedFile: string): Promise<void> {
|
||||
await this.nodeRuntimeSandbox.deleteFile(deletedFile);
|
||||
|
||||
|
||||
@@ -177,6 +177,20 @@ describe('NodeRuntimeSandbox', () => {
|
||||
expect(writeFileSpy).toHaveBeenCalledOnceWith(path, content);
|
||||
});
|
||||
|
||||
it('should call renameFile with proper parameters', async () => {
|
||||
setValuesToInitializeProject();
|
||||
|
||||
const fakeWebContainer = new FakeWebContainer();
|
||||
service['webContainerPromise'] = Promise.resolve(fakeWebContainer);
|
||||
const renameFileSpy = spyOn(fakeWebContainer.fs, 'rename');
|
||||
|
||||
const oldPath = 'oldPath';
|
||||
const newPath = 'newPath';
|
||||
|
||||
await service.renameFile(oldPath, newPath);
|
||||
expect(renameFileSpy).toHaveBeenCalledOnceWith(oldPath, newPath);
|
||||
});
|
||||
|
||||
it('should initialize the Angular CLI based on the tutorial config', async () => {
|
||||
setValuesToInitializeAngularCLI();
|
||||
|
||||
|
||||
@@ -270,6 +270,16 @@ export class NodeRuntimeSandbox {
|
||||
}
|
||||
}
|
||||
|
||||
async renameFile(oldPath: string, newPath: string): Promise<void> {
|
||||
const webContainer = await this.webContainerPromise!;
|
||||
|
||||
try {
|
||||
await webContainer.fs.rename(oldPath, newPath);
|
||||
} catch (err: any) {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
async readFile(filePath: string): Promise<string> {
|
||||
const webContainer = await this.webContainerPromise!;
|
||||
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
<div class="adev-top-content">
|
||||
<!-- Banner(s) can be edited here! -->
|
||||
<a href="https://goo.gle/angular-dot-dev" class="adev-banner" target="_blank">
|
||||
<h1 tabindex="-1">Welcome to Angular.dev — the future home for Angular!</h1>
|
||||
<p class="adev-banner-cta">Read about our beta launch</p>
|
||||
<h1 tabindex="-1">Welcome to Angular.dev — the new home for Angular!</h1>
|
||||
<p class="adev-banner-cta">Read about our launch</p>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
+25
-4
@@ -16,12 +16,12 @@ import {
|
||||
inject,
|
||||
signal,
|
||||
} from '@angular/core';
|
||||
import {DOCUMENT, NgFor, NgIf} from '@angular/common';
|
||||
import {DOCUMENT, NgFor, NgIf, ViewportScroller} from '@angular/common';
|
||||
import {MatTabGroup, MatTabsModule} from '@angular/material/tabs';
|
||||
import {takeUntilDestroyed} from '@angular/core/rxjs-interop';
|
||||
import {distinctUntilChanged, map} from 'rxjs/operators';
|
||||
import {takeUntilDestroyed, toSignal} from '@angular/core/rxjs-interop';
|
||||
import {distinctUntilChanged, filter, map, take} from 'rxjs/operators';
|
||||
import {DocContent, DocViewer} from '@angular/docs';
|
||||
import {ActivatedRoute, Router, RouterLink} from '@angular/router';
|
||||
import {ActivatedRoute, Router, RouterLink, Scroll} from '@angular/router';
|
||||
import {ApiItemType} from './../interfaces/api-item-type';
|
||||
import {ReferenceScrollHandler} from '../services/reference-scroll-handler.service';
|
||||
import {
|
||||
@@ -51,6 +51,15 @@ export default class ApiReferenceDetailsPage implements OnInit, AfterViewInit {
|
||||
private readonly document = inject(DOCUMENT);
|
||||
private readonly router = inject(Router);
|
||||
private readonly scrollHandler = inject(ReferenceScrollHandler);
|
||||
private readonly scroller = inject(ViewportScroller);
|
||||
private scrollEvent = toSignal(
|
||||
this.router.events.pipe(
|
||||
filter((e): e is Scroll => e instanceof Scroll),
|
||||
take(1),
|
||||
),
|
||||
{initialValue: null},
|
||||
);
|
||||
private cardsDisplayedOnInit = false;
|
||||
|
||||
ApiItemType = ApiItemType;
|
||||
|
||||
@@ -67,10 +76,22 @@ export default class ApiReferenceDetailsPage implements OnInit, AfterViewInit {
|
||||
|
||||
ngAfterViewInit(): void {
|
||||
this.setActiveTab();
|
||||
if (this.canDisplayCards()) {
|
||||
this.cardsDisplayedOnInit = true;
|
||||
}
|
||||
this.listenToTabChange();
|
||||
}
|
||||
|
||||
membersCardsLoaded(): void {
|
||||
const event = this.scrollEvent();
|
||||
if (this.cardsDisplayedOnInit && event) {
|
||||
// Cards loaded after scroll event happened. We have to handle the scroll event manually
|
||||
if (event.position) {
|
||||
this.scroller.scrollToPosition(event.position);
|
||||
} else if (event.anchor) {
|
||||
this.scroller.scrollToAnchor(event.anchor);
|
||||
}
|
||||
}
|
||||
this.scrollHandler.setupListeners(API_TAB_CLASS_NAME);
|
||||
}
|
||||
|
||||
|
||||
@@ -1248,6 +1248,16 @@ const REFERENCE_SUB_NAVIGATION_DATA: NavigationItem[] = [
|
||||
path: 'errors/NG0912',
|
||||
contentPath: 'reference/errors/NG0912',
|
||||
},
|
||||
{
|
||||
label: 'NG0955: Track expression resulted in duplicated keys for a given collection',
|
||||
path: 'errors/NG0955',
|
||||
contentPath: 'reference/errors/NG0955',
|
||||
},
|
||||
{
|
||||
label: 'NG0956: Tracking expression caused re-creation of the DOM structure',
|
||||
path: 'errors/NG0956',
|
||||
contentPath: 'reference/errors/NG0956',
|
||||
},
|
||||
{
|
||||
label: 'NG1001: Argument Not Literal',
|
||||
path: 'errors/NG1001',
|
||||
@@ -1269,9 +1279,9 @@ const REFERENCE_SUB_NAVIGATION_DATA: NavigationItem[] = [
|
||||
contentPath: 'reference/errors/NG3003',
|
||||
},
|
||||
{
|
||||
label: 'NG5000: Hydration with unsupported Zone.js instance.',
|
||||
path: 'errors/NG5000',
|
||||
contentPath: 'reference/errors/NG5000',
|
||||
label: 'NG05000: Hydration with unsupported Zone.js instance.',
|
||||
path: 'errors/NG05000',
|
||||
contentPath: 'reference/errors/NG05000',
|
||||
},
|
||||
{
|
||||
label: 'NG6100: NgModule.id Set to module.id anti-pattern',
|
||||
|
||||
@@ -76,15 +76,15 @@ The following example shows how to make a progress bar accessible by using host
|
||||
The ARIA attribute `aria-valuenow` is bound to the user's input.
|
||||
* In the template, the `aria-label` attribute ensures that the control is accessible to screen readers.
|
||||
|
||||
<docs-code-multifile
|
||||
path="adev/src/content/examples/accessibility/src/app/app.component.ts"
|
||||
preview>
|
||||
<docs-code-multifile>
|
||||
<docs-code
|
||||
path="adev/src/content/examples/accessibility/src/app/progress-bar.component.ts"
|
||||
language="ts"
|
||||
linenums
|
||||
highlight="[12, 20]"/>
|
||||
<docs-code
|
||||
path="adev/src/content/examples/accessibility/src/app/app.component.html"
|
||||
language="html"
|
||||
linenums
|
||||
highlight="[8, 9]"/>
|
||||
</docs-code-multifile>
|
||||
|
||||
@@ -59,8 +59,6 @@ If Angular handles an event within a component with OnPush strategy, the framewo
|
||||
|
||||
As an example, if Angular handles an event within `MainComponent`, the framework will run change detection in the entire component tree. Angular will ignore the subtree with root `LoginComponent` because it has `OnPush` and the event happened outside of its scope.
|
||||
|
||||
<img alt="Change detection propagation from OnPush component" src="assets/images/best-practices/runtime-performance/on-push-trigger.svg">
|
||||
|
||||
```mermaid
|
||||
graph TD;
|
||||
app[AppComponent] --- header[HeaderComponent];
|
||||
|
||||
@@ -5,6 +5,8 @@ It can improve the user experience by quickly launching a static rendered page (
|
||||
|
||||
This gives users a meaningful first paint of your application that appears quickly because the browser can render the HTML and CSS without the need to initialize any JavaScript.
|
||||
|
||||
Learn more in [The App Shell Model](https://developers.google.com/web/fundamentals/architecture/app-shell).
|
||||
|
||||
<docs-workflow>
|
||||
<docs-step title="Prepare the application">
|
||||
Do this with the following Angular CLI command:
|
||||
@@ -15,7 +17,7 @@ ng new my-app --routing
|
||||
|
||||
</docs-code>
|
||||
|
||||
For an existing application, you have to manually add the `RouterModule` and defining a `<router-outlet>` within your application.
|
||||
For an existing application, you have to manually add the `Router` and defining a `<router-outlet>` within your application.
|
||||
</docs-step>
|
||||
<docs-step title="Create the application shell">
|
||||
Use the Angular CLI to automatically create the application shell.
|
||||
@@ -28,57 +30,24 @@ ng generate app-shell
|
||||
|
||||
For more information about this command, see [App shell command](cli/generate#app-shell-command).
|
||||
|
||||
After running this command you can see that the `angular.json` configuration file has been updated to add two new targets, with a few other changes.
|
||||
The command updates the application code and adds extra files to the project structure.
|
||||
|
||||
<docs-code language="json">
|
||||
<code-example language="text">
|
||||
|
||||
src
|
||||
├── app
|
||||
│ ├── app.config.server.ts # server application configuration
|
||||
│ └── app-shell # app-shell component
|
||||
│ ├── app-shell.component.html
|
||||
│ ├── app-shell.component.scss
|
||||
│ ├── app-shell.component.spec.ts
|
||||
│ └── app-shell.component.ts
|
||||
└── main.server.ts # main server application bootstrapping
|
||||
|
||||
</code-example>
|
||||
|
||||
"server": {
|
||||
"builder": "@angular-devkit/build-angular:server",
|
||||
"defaultConfiguration": "production",
|
||||
"options": {
|
||||
"outputPath": "dist/my-app/server",
|
||||
"main": "src/main.server.ts",
|
||||
"tsConfig": "tsconfig.server.json"
|
||||
},
|
||||
"configurations": {
|
||||
"development": {
|
||||
"outputHashing": "none",
|
||||
},
|
||||
"production": {
|
||||
"outputHashing": "media",
|
||||
"fileReplacements": [
|
||||
{
|
||||
"replace": "src/environments/environment.ts",
|
||||
"with": "src/environments/environment.prod.ts"
|
||||
}
|
||||
],
|
||||
"sourceMap": false,
|
||||
"optimization": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"app-shell": {
|
||||
"builder": "@angular-devkit/build-angular:app-shell",
|
||||
"defaultConfiguration": "production",
|
||||
"options": {
|
||||
"route": "shell"
|
||||
},
|
||||
"configurations": {
|
||||
"development": {
|
||||
"browserTarget": "my-app:build:development",
|
||||
"serverTarget": "my-app:server:development",
|
||||
},
|
||||
"production": {
|
||||
"browserTarget": "my-app:build:production",
|
||||
"serverTarget": "my-app:server:production"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</docs-code>
|
||||
</docs-step>
|
||||
<docs-step title="Verify the application is built with the shell content">
|
||||
Use the Angular CLI to build the `app-shell` target.
|
||||
|
||||
<docs-code language="shell">
|
||||
|
||||
|
||||
+7
-7
@@ -7,7 +7,7 @@
|
||||
|
||||
<div [hidden]="formDir.submitted">
|
||||
|
||||
<div class="cross-validation" [class.cross-validation-error]="actorForm.errors?.['unambiguousRole'] && (actorForm.touched || actorForm.dirty)">
|
||||
<div class="cross-validation" [class.cross-validation-error]="actorForm.hasError('unambiguousRole') && (actorForm.touched || actorForm.dirty)">
|
||||
<div class="form-group">
|
||||
|
||||
<label for="name">Name</label>
|
||||
@@ -18,13 +18,13 @@
|
||||
<div *ngIf="name.invalid && (name.dirty || name.touched)"
|
||||
class="alert alert-danger">
|
||||
|
||||
<div *ngIf="name.errors?.['required']">
|
||||
<div *ngIf="name.hasError('required')">
|
||||
Name is required.
|
||||
</div>
|
||||
<div *ngIf="name.errors?.['minlength']">
|
||||
<div *ngIf="name.hasError('minlength')">
|
||||
Name must be at least 4 characters long.
|
||||
</div>
|
||||
<div *ngIf="name.errors?.['forbiddenName']">
|
||||
<div *ngIf="name.hasError('forbiddenName')">
|
||||
Name cannot be Bob.
|
||||
</div>
|
||||
</div>
|
||||
@@ -38,14 +38,14 @@
|
||||
|
||||
<div *ngIf="role.pending">Validating...</div>
|
||||
<div *ngIf="role.invalid" class="alert alert-danger role-errors">
|
||||
<div *ngIf="role.errors?.['uniqueRole']">
|
||||
<div *ngIf="role.hasError('uniqueRole')">
|
||||
Role is already taken.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- #docregion cross-validation-error-message -->
|
||||
<div *ngIf="actorForm.errors?.['unambiguousRole'] && (actorForm.touched || actorForm.dirty)" class="cross-validation-error-message alert alert-danger">
|
||||
<div *ngIf="actorForm.hasError('unambiguousRole') && (actorForm.touched || actorForm.dirty)" class="cross-validation-error-message alert alert-danger">
|
||||
Name cannot match role or audiences will be confused.
|
||||
</div>
|
||||
<!-- #enddocregion cross-validation-error-message -->
|
||||
@@ -59,7 +59,7 @@
|
||||
</select>
|
||||
|
||||
<div *ngIf="skill.invalid && skill.touched" class="alert alert-danger">
|
||||
<div *ngIf="skill.errors?.['required']">Skill is required.</div>
|
||||
<div *ngIf="skill.hasError('required')">Skill is required.</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
+6
-6
@@ -6,7 +6,7 @@
|
||||
<form #actorForm="ngForm" appUnambiguousRole>
|
||||
<!-- #enddocregion cross-validation-register-validator -->
|
||||
<div [hidden]="actorForm.submitted">
|
||||
<div class="cross-validation" [class.cross-validation-error]="actorForm.errors?.['unambiguousRole'] && (actorForm.touched || actorForm.dirty)">
|
||||
<div class="cross-validation" [class.cross-validation-error]="actorForm.hasError('unambiguousRole') && (actorForm.touched || actorForm.dirty)">
|
||||
<div class="form-group">
|
||||
<label for="name">Name</label>
|
||||
<!-- #docregion name-with-error-msg -->
|
||||
@@ -19,13 +19,13 @@
|
||||
<div *ngIf="name.invalid && (name.dirty || name.touched)"
|
||||
class="alert">
|
||||
|
||||
<div *ngIf="name.errors?.['required']">
|
||||
<div *ngIf="name.hasError('required')">
|
||||
Name is required.
|
||||
</div>
|
||||
<div *ngIf="name.errors?.['minlength']">
|
||||
<div *ngIf="name.hasError('minlength')">
|
||||
Name must be at least 4 characters long.
|
||||
</div>
|
||||
<div *ngIf="name.errors?.['forbiddenName']">
|
||||
<div *ngIf="name.hasError('forbiddenName')">
|
||||
Name cannot be Bob.
|
||||
</div>
|
||||
|
||||
@@ -46,14 +46,14 @@
|
||||
<!-- #enddocregion role-input -->
|
||||
<div *ngIf="role.pending">Validating...</div>
|
||||
<div *ngIf="role.invalid" class="alert role-errors">
|
||||
<div *ngIf="role.errors?.['uniqueRole']">
|
||||
<div *ngIf="role.hasError('uniqueRole')">
|
||||
Role is already taken.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- #docregion cross-validation-error-message -->
|
||||
<div *ngIf="actorForm.errors?.['unambiguousRole'] && (actorForm.touched || actorForm.dirty)" class="cross-validation-error-message alert">
|
||||
<div *ngIf="actorForm.hasError('unambiguousRole') && (actorForm.touched || actorForm.dirty)" class="cross-validation-error-message alert">
|
||||
Name cannot match role.
|
||||
</div>
|
||||
<!-- #enddocregion cross-validation-error-message -->
|
||||
|
||||
@@ -14,8 +14,7 @@ import {InMemoryDataService} from './in-memory-data.service';
|
||||
export const appConfig: ApplicationConfig = {
|
||||
providers: [
|
||||
provideRouter(routes),
|
||||
// TODO: Enable using Fetch API when disabling `HttpClientInMemoryWebApiModule`.
|
||||
provideHttpClient(/* withFetch()*/),
|
||||
provideHttpClient(withFetch()),
|
||||
provideClientHydration(),
|
||||
provideProtractorTestingSupport(), // essential for e2e testing
|
||||
|
||||
|
||||
@@ -80,4 +80,4 @@ Avoid inserting, removing, and modifying DOM elements. In particular, **never di
|
||||
element's `innerHTML` property**, which can make your application vulnerable
|
||||
to [cross-site scripting (XSS) exploits](https://developer.mozilla.org/en-US/docs/Glossary/Cross-site_scripting).
|
||||
Angular's template bindings, including bindings for `innerHTML`, include safeguards that help
|
||||
protect against XSS attacks. See the [Security guide](guide/security) for details.
|
||||
protect against XSS attacks. See the [Security guide](best-practices/security) for details.
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
The `output()` function declares an output in a directive or component.
|
||||
Outputs allow you to emit values to parent components.
|
||||
|
||||
HELPFUL: The `output()` function is currently in [developer preview](/guide/releases#developer-preview).
|
||||
HELPFUL: The `output()` function is currently in [developer preview](/reference/releases#developer-preview).
|
||||
|
||||
<docs-code language="ts" highlight="[[5], [8]]">
|
||||
import {Component, output} from '@angular/core';
|
||||
|
||||
@@ -60,10 +60,11 @@ emulated encapsulation.
|
||||
|
||||
In emulated mode, Angular supports
|
||||
the [`:host`](https://developer.mozilla.org/en-US/docs/Web/CSS/:host)
|
||||
and [`:host-context`](https://developer.mozilla.org/en-US/docs/Web/CSS/:host-context) pseudo
|
||||
and [`:host-context()`](https://developer.mozilla.org/en-US/docs/Web/CSS/:host-context) pseudo
|
||||
classes without
|
||||
using [Shadow DOM](https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_shadow_DOM).
|
||||
During compilation, the framework transforms these pseudo classes into attributes. Angular's
|
||||
During compilation, the framework transforms these pseudo classes into attributes so it doesn't
|
||||
comply with these native pseudo classes' rules at runtime (e.g. browser compatibility, specificity). Angular's
|
||||
emulated encapsulation mode does not support any other pseudo classes related to Shadow DOM, such
|
||||
as `::shadow` or `::part`.
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ This guide explores additional features of dependency injection in Angular.
|
||||
## Custom providers with `@Inject`
|
||||
|
||||
Using a custom provider allows you to provide a concrete implementation for implicit dependencies, such as built-in browser APIs.
|
||||
The following example uses an `InjectionToken` to provide the [localStorage](https://developer.mozilla.org/docs/Web/API/Window/localStorage) browser API as a dependency in the `BrowserStorageService`.
|
||||
The following example uses an `InjectionToken` to provide the [localStorage](https://developer.mozilla.org/docs/Web/API/Window/localStorage) browser API as a dependency in the `BrowserStorageService`:
|
||||
|
||||
<docs-code header="src/app/storage.service.ts" language="typescript"
|
||||
highlight="[[3,6],[12]]">
|
||||
@@ -32,8 +32,8 @@ export class BrowserStorageService {
|
||||
}
|
||||
</docs-code>
|
||||
|
||||
The `factory` function returns the `localStorage` property that is attached to the browser window object.
|
||||
The `Inject` decorator is a constructor parameter used to specify a custom provider of a dependency.
|
||||
The `factory` function returns the `localStorage` property that is attached to the browser's window object.
|
||||
The `Inject` decorator is applied to the `storage` constructor parameter and specifies a custom provider of the dependency.
|
||||
|
||||
This custom provider can now be overridden during testing with a mock API of `localStorage` instead of interacting with real browser APIs.
|
||||
|
||||
@@ -42,7 +42,7 @@ This custom provider can now be overridden during testing with a mock API of `lo
|
||||
Although developers strive to avoid it, some visual effects and third-party tools require direct DOM access.
|
||||
As a result, you might need to access a component's DOM element.
|
||||
|
||||
Angular exposes the underlying element of a `@Component` or `@Directive` via injection using the `ElementRef` injection token.
|
||||
Angular exposes the underlying element of a `@Component` or `@Directive` via injection using the `ElementRef` injection token:
|
||||
|
||||
<docs-code language="typescript" highlight="[7]">
|
||||
import { Directive, ElementRef } from '@angular/core';
|
||||
@@ -66,14 +66,13 @@ You can't refer directly to a class until it's been defined.
|
||||
|
||||
This isn't usually a problem, especially if you adhere to the recommended *one class per file* rule.
|
||||
But sometimes circular references are unavoidable.
|
||||
For example, when class 'A' refers to class 'B' and 'B' refers to 'A'.
|
||||
One of them has to be defined first.
|
||||
For example, when class 'A' refers to class 'B' and 'B' refers to 'A', one of them has to be defined first.
|
||||
|
||||
The Angular `forwardRef()` function creates an *indirect* reference that Angular can resolve later.
|
||||
|
||||
You face a similar problem when a class makes *a reference to itself*.
|
||||
For example in its `providers` array.
|
||||
The `providers` array is a property of the `@Component()` decorator function which must appear before the class definition.
|
||||
For example, in its `providers` array.
|
||||
The `providers` array is a property of the `@Component()` decorator function, which must appear before the class definition.
|
||||
You can break such circular references by using `forwardRef`.
|
||||
|
||||
<docs-code header="app.component.ts" language="typescript" highlight="[4]">
|
||||
|
||||
@@ -7,7 +7,7 @@ You can manage the dependency structure among your components and injectable ser
|
||||
This normally ensures that if a provided component or service is never actually used by the application, the compiler can remove its code from the bundle.
|
||||
|
||||
Due to the way Angular stores injection tokens, it is possible that such an unused component or service can end up in the bundle anyway.
|
||||
This page describes a dependency-injection design pattern that supports proper tree-shaking by using lightweight injection tokens.
|
||||
This page describes a dependency injection design pattern that supports proper tree-shaking by using lightweight injection tokens.
|
||||
|
||||
The lightweight injection token design pattern is especially important for library developers.
|
||||
It ensures that when an application uses only some of your library's capabilities, the unused code can be eliminated from the client's application bundle.
|
||||
@@ -20,7 +20,7 @@ To prevent the retention of unused components, your library should use the light
|
||||
## When tokens are retained
|
||||
|
||||
To better explain the condition under which token retention occurs, consider a library that provides a library-card component.
|
||||
This component contains a body and can contain an optional header.
|
||||
This component contains a body and can contain an optional header:
|
||||
|
||||
<docs-code language="html">
|
||||
|
||||
@@ -30,7 +30,7 @@ This component contains a body and can contain an optional header.
|
||||
|
||||
</docs-code>
|
||||
|
||||
In a likely implementation, the `<lib-card>` component uses `@ContentChild()` or `@ContentChildren()` to get `<lib-header>` and `<lib-body>`, as in the following.
|
||||
In a likely implementation, the `<lib-card>` component uses `@ContentChild()` or `@ContentChildren()` to get `<lib-header>` and `<lib-body>`, as in the following:
|
||||
|
||||
<docs-code language="typescript" highlight="[12]">
|
||||
@Component({
|
||||
@@ -51,7 +51,7 @@ class LibCardComponent {
|
||||
|
||||
Because `<lib-header>` is optional, the element can appear in the template in its minimal form, `<lib-card></lib-card>`.
|
||||
In this case, `<lib-header>` is not used and you would expect it to be tree-shaken, but that is not what happens.
|
||||
This is because `LibCardComponent` actually contains two references to the `LibHeaderComponent`.
|
||||
This is because `LibCardComponent` actually contains two references to the `LibHeaderComponent`:
|
||||
|
||||
<docs-code language="typescript">
|
||||
@ContentChild(LibHeaderComponent) header: LibHeaderComponent;
|
||||
@@ -60,24 +60,24 @@ This is because `LibCardComponent` actually contains two references to the `LibH
|
||||
* One of these reference is in the *type position*-- that is, it specifies `LibHeaderComponent` as a type: `header: LibHeaderComponent;`.
|
||||
* The other reference is in the *value position*-- that is, LibHeaderComponent is the value of the `@ContentChild()` parameter decorator: `@ContentChild(LibHeaderComponent)`.
|
||||
|
||||
The compiler handles token references in these positions differently.
|
||||
The compiler handles token references in these positions differently:
|
||||
|
||||
* The compiler erases *type position* references after conversion from TypeScript, so they have no impact on tree-shaking.
|
||||
* The compiler must keep *value position* references at runtime, which **prevents** the component from being tree-shaken.
|
||||
|
||||
In the example, the compiler retains the `LibHeaderComponent` token that occurs in the value position.
|
||||
This prevents the referenced component from being tree-shaken, even if the application does not actually use `<lib-header>` anywhere.
|
||||
If `LibHeaderComponent` 's code, template, and styles combined becomes too large, including it unnecessarily can significantly increase the size of the client application.
|
||||
If `LibHeaderComponent` 's code, template, and styles combine to become too large, including it unnecessarily can significantly increase the size of the client application.
|
||||
|
||||
## When to use the lightweight injection token pattern
|
||||
|
||||
The tree-shaking problem arises when a component is used as an injection token.
|
||||
There are two cases when that can happen.
|
||||
There are two cases when that can happen:
|
||||
|
||||
* The token is used in the value position of a [content query](guide/components/queries#content-queries).
|
||||
* The token is used as a type specifier for constructor injection.
|
||||
|
||||
In the following example, both uses of the `OtherComponent` token cause retention of `OtherComponent`, preventing it from being tree-shaken when it is not used.
|
||||
In the following example, both uses of the `OtherComponent` token cause retention of `OtherComponent`, preventing it from being tree-shaken when it is not used:
|
||||
|
||||
<docs-code language="typescript" highlight="[[2],[4]]">
|
||||
class MyComponent {
|
||||
@@ -89,16 +89,16 @@ class MyComponent {
|
||||
|
||||
Although tokens used only as type specifiers are removed when converted to JavaScript, all tokens used for dependency injection are needed at runtime.
|
||||
These effectively change `constructor(@Optional() other: OtherComponent)` to `constructor(@Optional() @Inject(OtherComponent) other)`.
|
||||
The token is now in a value position, and causes the tree shaker to keep the reference.
|
||||
The token is now in a value position, which causes the tree-shaker to keep the reference.
|
||||
|
||||
HELPFUL: For all services, a library should use [tree-shakable providers](guide/di/dependency-injection#providing-dependency), providing dependencies at the root level rather than in components or modules.
|
||||
HELPFUL: Libraries should use [tree-shakable providers](guide/di/dependency-injection#providing-dependency) for all services, providing dependencies at the root level rather than in components or modules.
|
||||
|
||||
## Using lightweight injection tokens
|
||||
|
||||
The lightweight injection token design pattern consists of using a small abstract class as an injection token, and providing the actual implementation at a later stage.
|
||||
The abstract class is retained, not tree-shaken, but it is small and has no material impact on the application size.
|
||||
|
||||
The following example shows how this works for the `LibHeaderComponent`.
|
||||
The following example shows how this works for the `LibHeaderComponent`:
|
||||
|
||||
<docs-code language="typescript" language="[[1],[6],[17]]">
|
||||
abstract class LibHeaderToken {}
|
||||
@@ -122,14 +122,14 @@ class LibCardComponent {
|
||||
</docs-code>
|
||||
|
||||
In this example, the `LibCardComponent` implementation no longer refers to `LibHeaderComponent` in either the type position or the value position.
|
||||
This lets full tree shaking of `LibHeaderComponent` take place.
|
||||
This lets full tree-shaking of `LibHeaderComponent` take place.
|
||||
The `LibHeaderToken` is retained, but it is only a class declaration, with no concrete implementation.
|
||||
It is small and does not materially impact the application size when retained after compilation.
|
||||
|
||||
Instead, `LibHeaderComponent` itself implements the abstract `LibHeaderToken` class.
|
||||
You can safely use that token as the provider in the component definition, allowing Angular to correctly inject the concrete type.
|
||||
|
||||
To summarize, the lightweight injection token pattern consists of the following.
|
||||
To summarize, the lightweight injection token pattern consists of the following:
|
||||
|
||||
1. A lightweight injection token that is represented as an abstract class.
|
||||
1. A component definition that implements the abstract class.
|
||||
@@ -144,7 +144,7 @@ The implementation of the method, with all its code overhead, resides in the inj
|
||||
This lets the parent communicate with the child, if it is present, in a type-safe manner.
|
||||
|
||||
For example, the `LibCardComponent` now queries `LibHeaderToken` rather than `LibHeaderComponent`.
|
||||
The following example shows how the pattern lets `LibCardComponent` communicate with the `LibHeaderComponent` without actually referring to `LibHeaderComponent`.
|
||||
The following example shows how the pattern lets `LibCardComponent` communicate with the `LibHeaderComponent` without actually referring to `LibHeaderComponent`:
|
||||
|
||||
<docs-code language="typescript" highlight="[[3],[13,16],[27]]">
|
||||
abstract class LibHeaderToken {
|
||||
@@ -179,7 +179,7 @@ class LibCardComponent implement AfterContentInit {
|
||||
}
|
||||
</docs-code>
|
||||
|
||||
In this example the parent queries the token to get the child component, and stores the resulting component reference if it is present.
|
||||
In this example, the parent queries the token to get the child component, and stores the resulting component reference if it is present.
|
||||
Before calling a method in the child, the parent component checks to see if the child component is present.
|
||||
If the child component has been tree-shaken, there is no runtime reference to it, and no call to its method.
|
||||
|
||||
|
||||
@@ -63,8 +63,8 @@ Because `isSpecial` is true, `ngClass` applies the class of `special` to the `<d
|
||||
|
||||
<docs-code header="src/app/app.component.html" path="adev/src/content/examples/built-in-directives/src/app/app.component.html" visibleRegion="NgClass-1"/>
|
||||
|
||||
For this use case, Angular applies the classes on initialization and in case of changes.
|
||||
The full example calls `setCurrentClasses()` initially with `ngOnInit()` and when the dependent properties change through a button click.
|
||||
For this use case, Angular applies the classes on initialization and in case of changes caused by reassigning the `currentClasses` object.
|
||||
The full example calls `setCurrentClasses()` initially with `ngOnInit()` when the user clicks on the `Refresh currentClasses` button.
|
||||
These steps are not necessary to implement `ngClass`.
|
||||
|
||||
## Setting inline styles with `NgStyle`
|
||||
|
||||
@@ -60,7 +60,7 @@ The tabs for the example show the following:
|
||||
| flying-heroes.component.html | Template with the new pipe used. |
|
||||
| flying-heroes.pipe.ts | File with custom pipe that filters flying heroes. |
|
||||
|
||||
<docs-code-multifile path="adev/src/content/examples/pipes/src/app/flying-heroes.component.ts_FlyingHeroesComponent" preview>
|
||||
<docs-code-multifile>
|
||||
<docs-code header="src/app/flying-heroes.component.html" path="adev/src/content/examples/pipes/src/app/flying-heroes.component.html" visibleRegion="template-flying-heroes"/>
|
||||
<docs-code header="src/app/flying-heroes.pipe.ts" path="adev/src/content/examples/pipes/src/app/flying-heroes.pipe.ts" visibleRegion="pure"/>
|
||||
</docs-code-multifile>
|
||||
|
||||
@@ -57,7 +57,7 @@ The following code example shows two component definitions:
|
||||
| `exponential-strength.pipe.ts` | Defines a custom pipe named `exponentialStrength` with the `transform` method that performs the transformation. It defines an argument to the `transform` method \(`exponent`\) for a parameter passed to the pipe. |
|
||||
| `power-booster.component.ts` | Demonstrates how to use the pipe, specifying a value \(`2`\) and the exponent parameter \(`10`\). |
|
||||
|
||||
<docs-code-multifile preview path="adev/src/content/examples/pipes/src/app/power-booster.component.ts">
|
||||
<docs-code header="src/app/exponential-strength.pipe.ts" path="adev/src/content/examples/pipes/src/app/exponential-strength.pipe.ts"/>
|
||||
<docs-code header="src/app/power-booster.component.ts" path="adev/src/content/examples/pipes/src/app/power-booster.component.ts"/>
|
||||
<docs-code-multifile>
|
||||
<docs-code header="src/app/exponential-strength.pipe.ts" language="ts" path="adev/src/content/examples/pipes/src/app/exponential-strength.pipe.ts"/>
|
||||
<docs-code header="src/app/power-booster.component.ts" language="ts" path="adev/src/content/examples/pipes/src/app/power-booster.component.ts"/>
|
||||
</docs-code-multifile>
|
||||
|
||||
@@ -63,7 +63,7 @@ Angular defines the following security contexts:
|
||||
| URL | Used for URL properties, such as `<a href>`. |
|
||||
| Resource URL | A URL that is loaded and executed as code, for example, in `<script src>`. |
|
||||
|
||||
Angular sanitizes untrusted values for HTML, styles, and URLs. Sanitizing resource URLs isn't possible because they contain arbitrary code.
|
||||
Angular sanitizes untrusted values for HTML and URLs. Sanitizing resource URLs isn't possible because they contain arbitrary code.
|
||||
In development mode, Angular prints a console warning when it has to change a value during sanitization.
|
||||
|
||||
### Sanitization example
|
||||
|
||||
@@ -130,8 +130,7 @@ That way, you are only dealing with `boolean` inside your component when calling
|
||||
<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).
|
||||
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()`?
|
||||
|
||||
@@ -26,7 +26,7 @@ However, because an Angular template is part of an overall webpage, and not the
|
||||
|
||||
IMPORTANT: To eliminate the risk of script injection attacks, Angular does not support the `<script>` element in templates.
|
||||
Angular ignores the `<script>` tag and outputs a warning to the browser console.
|
||||
For more information, see the [Security](guide/security) page.
|
||||
For more information, see the [Security](best-practices/security) page.
|
||||
|
||||
## More on template syntax
|
||||
|
||||
|
||||
@@ -0,0 +1,108 @@
|
||||
# Angular without ZoneJS (Zoneless)
|
||||
|
||||
## Why use Zoneless?
|
||||
|
||||
The main advantages to removing ZoneJS as a dependency are:
|
||||
|
||||
- **Improved performance**: ZoneJS uses DOM events and async tasks as indicators of when application state _might_ have updated and subsequently triggers application synchronization to run change detection on the application's views. ZoneJS does not have any insight into whether application state actually changed and so this synchronization is triggered more frequently than necessary.
|
||||
- **Improved Core Web Vitals**: ZoneJS brings a fair amount of overhead, both in payload size and in startup time cost.
|
||||
- **Improved debugging experience**: ZoneJS makes debugging code more difficult. Stack traces are harder to understand with ZoneJS. It's also difficult to understand when code breaks as a result of being outside the Angular Zone.
|
||||
- **Better ecosystem compatibility**: ZoneJS works by patching browser APIs but does not automatically have patches for every new browser API. Some APIs simply cannot be patched effectively, such as `async`/`await`, and have to be downleveled to work with ZoneJS. Sometimes libraries in the ecosystem are also incompatible with the way ZoneJS patches the native APIs. Removing ZoneJS as a dependency ensures better long-term compatibility by removing a source of complexity, monkey patching, and ongoing maintenance.
|
||||
|
||||
## Enabling Zoneless in an application
|
||||
|
||||
The API for enabling Zoneless is currently experimental. Neither the shape, nor the underlying behavior is stable and can change
|
||||
in patch versions. There are known feature gaps, including the lack of an ergonomic API which prevents the application from serializing too early with Server Side Rendering.
|
||||
|
||||
```typescript
|
||||
// standalone bootstrap
|
||||
bootstrapApplication(MyApp, {providers: [
|
||||
provideExperimentalZonelessChangeDetection(),
|
||||
]});
|
||||
|
||||
// NgModule bootstrap requires the provider and `ngZone: 'noop'`
|
||||
platformBrowser().bootstrapModule(AppModule, {ngZone: 'noop'});
|
||||
@NgModule({
|
||||
providers: [provideExperimentalZonelessChangeDetection()]
|
||||
})
|
||||
export class AppModule {}
|
||||
```
|
||||
|
||||
## Testing
|
||||
|
||||
The zoneless provider function can also be used with `TestBed` to help
|
||||
ensure the components under test are compatible with a Zoneless
|
||||
Angular application.
|
||||
|
||||
```typescript
|
||||
TestBed.configureTestingModule({
|
||||
providers: [provideExperimentalZonelessChangeDetection()]
|
||||
});
|
||||
|
||||
const fixture = TestBed.createComponent(MyComponent);
|
||||
await fixture.whenStable();
|
||||
```
|
||||
|
||||
To ensure tests have the most similar behavior to production code,
|
||||
avoid using `fixture.detectChanges()` when possibe. This forces
|
||||
change detection to run when Angular might otherwise have not
|
||||
scheduled change detection. Tests should ensure these notifications
|
||||
are happening and allow Angular to handle when to synchronize
|
||||
state rather than manually forcing it to happen in the test.
|
||||
|
||||
## Requirements for Zoneless compatibility
|
||||
|
||||
Angular relies on notifications from core APIs in order to determine when to run change detection and on which views.
|
||||
These notifications include:
|
||||
|
||||
- `ChangeDetectorRef.markForCheck` (called automatically by `AsyncPipe`)
|
||||
- `ComponentRef.setInput`
|
||||
- Updating a signal that's read in a template
|
||||
- Bound host or template listeners callbacks
|
||||
- Attaching a view that was marked dirty by one of the above
|
||||
|
||||
### `OnPush`-compatible components
|
||||
|
||||
One way to ensure that a component is using the correct notification mechanisms from above is to
|
||||
use [ChangeDetectionStrategy.OnPush](../best-practices/skipping-subtrees#using-onpush).
|
||||
|
||||
The `OnPush` change detection strategy is not required, but it is a recommended step towards zoneless compatibility for application components. It is not always possible for library components to use `ChangeDetectionStrategy.OnPush`.
|
||||
When a library component is a host for user-components which might use `ChangeDetectionStratey.Default`, it cannot use `OnPush` because that would prevent the child component from being refreshed if it is not `OnPush` compatible and relies on ZoneJS to trigger change detection. Components can use the `Default` strategy as long as they notify Angular when change detection needs to run (calling `markForCheck`, using signals, `AsyncPipe`, etc.).
|
||||
|
||||
### Remove `NgZone.onMicrotaskEmpty`, `NgZone.onUnstable`, `NgZone.isStable`, or `NgZone.onStable`
|
||||
|
||||
Applications and libraries need to remove uses of `NgZone.onMicrotaskEmpty`, `NgZone.onUnstable` and `NgZone.onStable`.
|
||||
These observables will never emit when an Application enables zoneless change detection.
|
||||
Similarly, `NgZone.isStable` will always be `true` and should not be used as a condition for code execution.
|
||||
|
||||
The `NgZone.onMicrotaskEmpty` and `NgZone.onStable` observables are most often used to wait for Angular to
|
||||
complete change detection before performing a task. Instead, these can be replaced by `afterNextRender`
|
||||
if they need to wait for a single change detection or `afterRender` if there is some condition that might span
|
||||
several change detection rounds. In other cases, these observables were used because they happened to be
|
||||
familiar and have similar timing to what was needed. More straightforward or direct DOM APIs can be used instead,
|
||||
such as `MutationObserver` when code needs to wait for certain DOM state (rather than waiting for it indirectly
|
||||
through Angular's render hooks).
|
||||
|
||||
<docs-callout title="NgZone.run and NgZone.runOutsideAngular are compatible with Zoneless">
|
||||
`NgZone.run` and `NgZone.runOutsideAngular` do not need to be removed in order for code to be compatible with
|
||||
Zoneless applications. In fact, removing these calls can lead to performance regressions for libraries that
|
||||
are used in applications that still rely on ZoneJS.
|
||||
</docs-callout>
|
||||
|
||||
### `ExperimentalPendingTasks` for Server Side Rendering (SSR)
|
||||
|
||||
If you are using SSR with Angular, you may know that it relies on ZoneJS to help determine when the application
|
||||
is "stable" and can be serialized. If there are asynchronous tasks that should prevent serialization, an application
|
||||
not using ZoneJS will need to make Angular aware of these with the `ExperimentalPendingTasks` service. Serialization
|
||||
will wait for the first moment that all pending tasks have been removed.
|
||||
|
||||
```typescript
|
||||
const taskService = inject(ExperimentalPendingTasks);
|
||||
const taskCleanup = taskService.add();
|
||||
await doSomeWorkThatNeedsToBeRendered();
|
||||
taskCleanup();
|
||||
```
|
||||
|
||||
The framework uses this service internally as well to prevent serialization until asynchronous tasks are complete. These include, but are not limited to,
|
||||
an ongoing Router navigation and an incomplete `HttpClient` request.
|
||||
|
||||
@@ -25,8 +25,7 @@ system" href="guide/components" link="Get started with Components">
|
||||
testable.
|
||||
</docs-card>
|
||||
<docs-card title="Get fast state updates with fine-grained reactivity based on Signals" href="guide/signals" link="Explore Angular Signals">
|
||||
Our fine-grained reactivity model, combined with compile-time optimizations, simplify development,
|
||||
and helps build faster apps by default.
|
||||
Our fine-grained reactivity model, combined with compile-time optimizations, simplifies development and helps build faster apps by default.
|
||||
|
||||
Granularly track how and where state is used throughout an application, giving the framework the power to render fast updates via highly optimized instructions.
|
||||
</docs-card>
|
||||
@@ -81,7 +80,7 @@ more">
|
||||
<docs-card title="Clear support policies and predictable release schedule" href="reference/releases" link="Versioning & releasing">
|
||||
Angular's predictable, time-based release schedule gives your organization confidence in the
|
||||
stability and backwards compatibility of the framework. Long Term Support (LTS) windows make sure
|
||||
you get critical security fixes when you need them. First party update tools, guides and automated
|
||||
you get critical security fixes when you need them. First-party update tools, guides and automated
|
||||
migration schematics help keep your apps up-to-date with the latest advancements to the framework
|
||||
and the web platform.
|
||||
</docs-card>
|
||||
@@ -91,10 +90,10 @@ more">
|
||||
|
||||
<docs-card-container>
|
||||
<docs-card title="Reach users everywhere with internationalization support" href="guide/i18n" link="Internationalization">
|
||||
Angular's internationalization features handles message translations and formatting, including
|
||||
Angular's internationalization features handle message translations and formatting, including
|
||||
support for unicode standard ICU syntax.
|
||||
</docs-card>
|
||||
<docs-card title="Protect your users with security by default" href="guide/security" link="Security">
|
||||
<docs-card title="Protect your users with security by default" href="best-practices/security" link="Security">
|
||||
In collaboration with Google's world-class security engineers, Angular aims to make development
|
||||
safe by default. Built-in security features, including HTML sanitization and
|
||||
trusted type support, help protect your users from common vulnerabilities like
|
||||
@@ -125,20 +124,20 @@ more">
|
||||
|
||||
<docs-card-container>
|
||||
<docs-card title="Courses, blogs and resources" href="https://devlibrary.withgoogle.com/products/angular?sort=added" link="Check out DevLibrary">
|
||||
Our community is composed of talented developers, writers, instructors, podcasters and more. The Google for Developers library is just a sample of the high quality resources available for new and experienced developers to continue developing.
|
||||
Our community is composed of talented developers, writers, instructors, podcasters, and more. The Google for Developers library is just a sample of the high quality resources available for new and experienced developers to continue developing.
|
||||
</docs-card>
|
||||
<docs-card title="Open Source" href="https://github.com/angular/angular/blob/main/CONTRIBUTING.md" link="Contribute to Angular">
|
||||
We are thankful for the open source contributors who make Angular a better framework for everyone. From fixing a typo in the docs, to adding major features, we encourage anyone interested to get started on our GitHub.
|
||||
</docs-card>
|
||||
<docs-card title="Community partnerships" href="https://developers.google.com/community/experts/directory?specialization=angular" link="Meet the Angular GDEs">
|
||||
Our team partners with individuals, educators and enterprises to ensure we consistently are supporting developers. Angular Google Developer Experts (GDEs) represent community leaders around the world educating, organizing and developing with Angular. Enterprise partnerships help ensure that Angular scales well for technology industry leaders.
|
||||
Our team partners with individuals, educators, and enterprises to ensure we consistently are supporting developers. Angular Google Developer Experts (GDEs) represent community leaders around the world educating, organizing, and developing with Angular. Enterprise partnerships help ensure that Angular scales well for technology industry leaders.
|
||||
</docs-card>
|
||||
<docs-card title="Partnering with other Google technologies">
|
||||
Angular partners closely with other Google technologies and teams to improve the web.
|
||||
|
||||
Our ongoing partnership with Chrome’s Aurora actively explores improvements to user experience across the web developing built-in performance optimizations like NgOptimizedImage and improvements to Angular’s Core Web Vitals.
|
||||
Our ongoing partnership with Chrome’s Aurora actively explores improvements to user experience across the web, developing built-in performance optimizations like NgOptimizedImage and improvements to Angular’s Core Web Vitals.
|
||||
|
||||
We are also working with [Firebase](https://firebase.google.com/), [Tensorflow](https://www.tensorflow.org/), [Flutter](https://flutter.dev/), [Material Design](https://m3.material.io/) and [Google Cloud](https://cloud.google.com/) to ensure we provide meaningful integrations across the developer workflow.
|
||||
We are also working with [Firebase](https://firebase.google.com/), [Tensorflow](https://www.tensorflow.org/), [Flutter](https://flutter.dev/), [Material Design](https://m3.material.io/), and [Google Cloud](https://cloud.google.com/) to ensure we provide meaningful integrations across the developer workflow.
|
||||
</docs-card>
|
||||
</docs-card-container>
|
||||
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
@name Track expression resulted in duplicated keys for a given collection.
|
||||
@category runtime
|
||||
@shortDescription A track expression specified in the `@for` loop evaluated to duplicated keys for a given collection. Duplicate keys are problematic from the correctness point of view and the tracking expression should be updated.
|
||||
|
||||
@description
|
||||
A track expression specified in the `@for` loop evaluated to duplicated keys for a given collection, ex.:
|
||||
|
||||
```typescript
|
||||
@Component({
|
||||
template: `@for (item of items; track item.value) {{{item.value}}}`,
|
||||
})
|
||||
class TestComponent {
|
||||
items = [{key: 1, value: 'a'}, {key: 2, value: 'b'}, {key: 3, value: 'a'}];
|
||||
}
|
||||
```
|
||||
|
||||
In the provided example the `item.key` tracking expression will find two duplicate keys `a` (at index 0 and 2).
|
||||
|
||||
Duplicate keys are problematic from the correctness point of view: since the `@for` loop can't uniquely identify items it might choose DOM nodes corresponding to _another_ item (with the same key) when performing DOM moves or destroy.
|
||||
|
||||
There is also performance penalty associated with duplicated keys - internally Angular must use more sophisticated and slower data structures while repeating over collections with duplicated keys.
|
||||
|
||||
## Fixing the error
|
||||
|
||||
Change the tracking expression such that it uniquely identifies an item in a collection. In the discussed example the correct track expression would use the unique `key` property (`item.key`):
|
||||
|
||||
```typescript
|
||||
@Component({
|
||||
template: `@for (item of items; track item.key) {{{item.value}}}`,
|
||||
})
|
||||
class TestComponent {
|
||||
items = [{key: 1, value: 'a'}, {key: 2, value: 'b'}, {key: 3, value: 'a'}];
|
||||
}
|
||||
```
|
||||
@@ -0,0 +1,60 @@
|
||||
@name Tracking expression caused re-creation of the DOM structure.
|
||||
@category runtime
|
||||
@shortDescription The configured tracking expression (track by identity) caused re-creation of the DOM structure for the entire collection, which is problematic from the performance and correctness point of view.
|
||||
|
||||
@description
|
||||
The identity track expression specified in the `@for` loop caused re-creation of the DOM corresponding to _all_ items. This is very expensive operation that commonly occurs when working with immutable data structures, Example:
|
||||
|
||||
```typescript
|
||||
@Component({
|
||||
template: `
|
||||
<button (click)="toggleAllDone()">All done!</button>
|
||||
<ul>
|
||||
@for (todo of todos; track todo) {
|
||||
<li>{{todo.task}}</li>
|
||||
}
|
||||
</ul>
|
||||
`,
|
||||
})
|
||||
export class App {
|
||||
todos = [
|
||||
{ id: 0, task: 'understand trackBy', done: false },
|
||||
{ id: 1, task: 'use proper tracking expression', done: false },
|
||||
];
|
||||
|
||||
toggleAllDone() {
|
||||
this.todos = this.todos.map(todo => ({ ...todo, done: true }));
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
In the provided example the entire list, with all the views (DOM nodes, Angular directives, components, queries etc.) are re-created (!) after toggling the "done" status of items. Here a relatively inexpensive binding update to the `done` property would suffice.
|
||||
|
||||
Apart from having high performance penalty, re-creating the DOM tree results in loss of state in the DOM elements (ex.: focus, text selection, sites loaded in an iframe etc.).
|
||||
|
||||
## Fixing the error
|
||||
|
||||
Change the tracking expression such that it uniquely identifies an item in a collection, regardless of its object identity. In the discussed example the correct track expression would use the unique `id` property (`item.id`):
|
||||
|
||||
```typescript
|
||||
@Component({
|
||||
template: `
|
||||
<button (click)="toggleAllDone()">All done!</button>
|
||||
<ul>
|
||||
@for (todo of todos; track todo.id) {
|
||||
<li>{{todo.task}}</li>
|
||||
}
|
||||
</ul>
|
||||
`,
|
||||
})
|
||||
export class App {
|
||||
todos = [
|
||||
{ id: 0, task: 'understand trackBy', done: false },
|
||||
{ id: 1, task: 'use proper tracking expression', done: false },
|
||||
];
|
||||
|
||||
toggleAllDone() {
|
||||
this.todos = this.todos.map(todo => ({ ...todo, done: true }));
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -23,11 +23,13 @@
|
||||
| `NG0507` | [HTML content was altered after SSR](errors/NG0507) |
|
||||
| `NG0910` | [Unsafe bindings on an iframe element](errors/NG0910) |
|
||||
| `NG0912` | [Component ID generation collision](errors/NG0912) |
|
||||
| `NG0955` | [Track expression resulted in duplicated keys for a given collection](errors/NG0955) |
|
||||
| `NG0956` | [Tracking expression caused re-creation of the DOM structure](errors/NG0956) |
|
||||
| `NG01101` | [Wrong Async Validator Return Type](errors/NG01101) |
|
||||
| `NG01203` | [Missing value accessor](errors/NG01203) |
|
||||
| `NG02200` | [Missing Iterable Differ](errors/NG02200) |
|
||||
| `NG02800` | [JSONP support in HttpClient configuration](errors/NG02800) |
|
||||
| `NG5000` | [Hydration with unsupported Zone.js instance.](errors/NG5000) |
|
||||
| `NG05000` | [Hydration with unsupported Zone.js instance.](errors/NG05000) |
|
||||
| `NG05104` | [Root element was not found.](errors/NG05104) |
|
||||
|
||||
## Compiler errors
|
||||
|
||||
@@ -46,7 +46,10 @@ In this example, we’ve adapted the shield to create an Angular Signals logo va
|
||||

|
||||
</docs-step>
|
||||
|
||||
<docs-step title="Do’s and don’ts of adapting the brand">
|
||||
<docs-step title="Do’s and don’ts of using Angular's brand">
|
||||
If you are creating your own logo, we encourage differentiating your logo from ours to not cause confusion. When adapting the logo, you are free to change and adapt the colors and shape to make it your own.
|
||||
|
||||
When representing Angular with the original logo, please follow these guidelines:
|
||||

|
||||
</docs-step>
|
||||
|
||||
|
||||
@@ -222,7 +222,7 @@ This section represents explorations and prototyping of potential future project
|
||||
<docs-card title="Transition the Angular language service to Ivy" link="Completed Q2 2021" href="tools/language-service">
|
||||
The goal of this project is to improve the experience and remove legacy dependency by transitioning the language service to Ivy. Today the language service still uses the View Engine compiler and type checking, even for Ivy apps. We want to use the Ivy template parser and improved type checking for the Angular Language service to match app behavior. This migration is also a step towards unblocking the removal of View Engine, which will simplify Angular, reduce the npm package size, and improve the maintainability of the framework.
|
||||
</docs-card>
|
||||
<docs-card title="Increased security with native Trusted Types in Angular" link="Completed Q2 2021" href="guide/security">
|
||||
<docs-card title="Increased security with native Trusted Types in Angular" link="Completed Q2 2021" href="best-practices/security">
|
||||
In collaboration with the Google security team, we are adding support for the new Trusted Types API. This web platform API helps developers build more secure web apps.
|
||||
</docs-card>
|
||||
<docs-card title="Optimized build speed and bundle sizes with Angular CLI webpack 5" link="Completed Q2 2021" href="tools/cli/build">
|
||||
|
||||
+398
-340
File diff suppressed because it is too large
Load Diff
@@ -87,18 +87,18 @@ In your IDE:
|
||||
|
||||
1. In `index.html`, replace the `<title>` element with this code to update the title of the app.
|
||||
|
||||
<docs-code header="Replace in src/index.html" path="adev/src/content/tutorials/first-app/steps/01-hello-world/src/index.html" visibleLines="[5]"/>
|
||||
<docs-code header="Replace in src/index.html" path="adev/src/content/tutorials/first-app/steps/02-HomeComponent/src/index.html" visibleLines="[5]"/>
|
||||
|
||||
Then, save the changes you just made to `index.html`.
|
||||
|
||||
1. Next, open `first-app/src/app/app.component.ts`.
|
||||
1. In `app.component.ts`, in the `@Component` definition, replace the `template` line with this code to change the text in the app component.
|
||||
|
||||
<docs-code header="Replace in src/app/app.component.ts" path="adev/src/content/tutorials/first-app/steps/01-hello-world/src/app/app.component.ts" visibleLines="[7]"/>
|
||||
<docs-code header="Replace in src/app/app.component.ts" path="adev/src/content/tutorials/first-app/steps/02-HomeComponent/src/app/app.component.ts" visibleLines="[7,9]"/>
|
||||
|
||||
1. In `app.component.ts`, in the `AppComponent` class definition, replace the `title` line with this code to change the component title.
|
||||
|
||||
<docs-code header="Replace in src/app/app.component.ts" path="adev/src/content/tutorials/first-app/steps/01-hello-world/src/app/app.component.ts" visibleLines="[11]"/>
|
||||
<docs-code header="Replace in src/app/app.component.ts" path="adev/src/content/tutorials/first-app/steps/02-HomeComponent/src/app/app.component.ts" visibleLines="[12,14]"/>
|
||||
|
||||
Then, save the changes you made to `app.component.ts`.
|
||||
|
||||
|
||||
@@ -196,6 +196,8 @@ The server is now reading data from the HTTP request but the components that rel
|
||||
|
||||
</docs-workflow>
|
||||
|
||||
Note: This lesson relies on the `fetch` browser API. For the support of interceptors, please refer to the [Http Client documentation](/guide/http)
|
||||
|
||||
Summary: In this lesson, you updated your app to use a local web server (`json-server`), and use asynchronous service methods to retrieve data.
|
||||
|
||||
Congratulations! You've successfully completed this tutorial and are ready to continue your journey with building even more complex Angular Apps.
|
||||
|
||||
+2
-2
@@ -42,8 +42,8 @@ export class HomeComponent {
|
||||
return;
|
||||
}
|
||||
|
||||
this.filteredLocationList = this.housingLocationList.filter(
|
||||
(housingLocation) => housingLocation?.city.toLowerCase().includes(text.toLowerCase()),
|
||||
this.filteredLocationList = this.housingLocationList.filter((housingLocation) =>
|
||||
housingLocation?.city.toLowerCase().includes(text.toLowerCase()),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,8 +38,8 @@ export class HomeComponent {
|
||||
return;
|
||||
}
|
||||
|
||||
this.filteredLocationList = this.housingLocationList.filter(
|
||||
(housingLocation) => housingLocation?.city.toLowerCase().includes(text.toLowerCase()),
|
||||
this.filteredLocationList = this.housingLocationList.filter((housingLocation) =>
|
||||
housingLocation?.city.toLowerCase().includes(text.toLowerCase()),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
+385
-327
File diff suppressed because it is too large
Load Diff
+391
-333
File diff suppressed because it is too large
Load Diff
+400
-342
File diff suppressed because it is too large
Load Diff
@@ -25,6 +25,6 @@
|
||||
"@angular-devkit/build-angular": "^17.3.0-rc",
|
||||
"@angular/cli": "^17.3.0-rc",
|
||||
"@angular/compiler-cli": "^17.3.0-rc",
|
||||
"typescript": "~5.2.0"
|
||||
"typescript": "~5.4.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ load("@build_bazel_rules_nodejs//:providers.bzl", "ExternalNpmPackageInfo", "Lin
|
||||
def _filter_external_npm_deps_impl(ctx):
|
||||
problematic_paths = ["external/npm/node_modules/%s" % pkg for pkg in ctx.attr.angular_packages]
|
||||
package_name = ctx.attr.target.label.package
|
||||
has_problematic_transitive_dep = package_name.startswith("@angular-devkit/")
|
||||
has_problematic_transitive_dep = package_name.startswith("@angular-devkit/") or package_name.startswith("@angular/build")
|
||||
filtered_deps = []
|
||||
|
||||
# Note: to_list() is expensive; we need to invoke it here to get the path
|
||||
|
||||
+7
-7
@@ -7,7 +7,7 @@
|
||||
<div
|
||||
class="cross-validation"
|
||||
[class.cross-validation-error]="
|
||||
heroForm.errors?.['identityRevealed'] &&
|
||||
heroForm.hasError('identityRevealed') &&
|
||||
(heroForm.touched || heroForm.dirty)
|
||||
"
|
||||
>
|
||||
@@ -26,11 +26,11 @@
|
||||
*ngIf="name.invalid && (name.dirty || name.touched)"
|
||||
class="alert alert-danger"
|
||||
>
|
||||
<div *ngIf="name.errors?.['required']">Name is required.</div>
|
||||
<div *ngIf="name.errors?.['minlength']">
|
||||
<div *ngIf="name.hasError('required')">Name is required.</div>
|
||||
<div *ngIf="name.hasError('minlength')">
|
||||
Name must be at least 4 characters long.
|
||||
</div>
|
||||
<div *ngIf="name.errors?.['forbiddenName']">
|
||||
<div *ngIf="name.hasError('forbiddenName')">
|
||||
Name cannot be Bob.
|
||||
</div>
|
||||
</div>
|
||||
@@ -51,7 +51,7 @@
|
||||
*ngIf="alterEgo.invalid"
|
||||
class="alert alert-danger alter-ego-errors"
|
||||
>
|
||||
<div *ngIf="alterEgo.errors?.['uniqueAlterEgo']">
|
||||
<div *ngIf="alterEgo.hasError('uniqueAlterEgo')">
|
||||
Alter ego is already taken.
|
||||
</div>
|
||||
</div>
|
||||
@@ -60,7 +60,7 @@
|
||||
<!-- #docregion cross-validation-error-message -->
|
||||
<div
|
||||
*ngIf="
|
||||
heroForm.errors?.['identityRevealed'] &&
|
||||
heroForm.hasError('identityRevealed') &&
|
||||
(heroForm.touched || heroForm.dirty)
|
||||
"
|
||||
class="cross-validation-error-message alert alert-danger"
|
||||
@@ -82,7 +82,7 @@
|
||||
</select>
|
||||
|
||||
<div *ngIf="power.invalid && power.touched" class="alert alert-danger">
|
||||
<div *ngIf="power.errors?.['required']">Power is required.</div>
|
||||
<div *ngIf="power.hasError('required')">Power is required.</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
+6
-6
@@ -8,7 +8,7 @@
|
||||
<div
|
||||
class="cross-validation"
|
||||
[class.cross-validation-error]="
|
||||
heroForm.errors?.['identityRevealed'] &&
|
||||
heroForm.hasError('identityRevealed') &&
|
||||
(heroForm.touched || heroForm.dirty)
|
||||
"
|
||||
>
|
||||
@@ -33,11 +33,11 @@
|
||||
*ngIf="name.invalid && (name.dirty || name.touched)"
|
||||
class="alert"
|
||||
>
|
||||
<div *ngIf="name.errors?.['required']">Name is required.</div>
|
||||
<div *ngIf="name.errors?.['minlength']">
|
||||
<div *ngIf="name.hasError('required')">Name is required.</div>
|
||||
<div *ngIf="name.hasError('minlength')">
|
||||
Name must be at least 4 characters long.
|
||||
</div>
|
||||
<div *ngIf="name.errors?.['forbiddenName']">
|
||||
<div *ngIf="name.hasError('forbiddenName')">
|
||||
Name cannot be Bob.
|
||||
</div>
|
||||
</div>
|
||||
@@ -59,7 +59,7 @@
|
||||
<!-- #enddocregion alterEgo-input -->
|
||||
<div *ngIf="alterEgo.pending">Validating...</div>
|
||||
<div *ngIf="alterEgo.invalid" class="alert alter-ego-errors">
|
||||
<div *ngIf="alterEgo.errors?.['uniqueAlterEgo']">
|
||||
<div *ngIf="alterEgo.hasError('uniqueAlterEgo')">
|
||||
Alter ego is already taken.
|
||||
</div>
|
||||
</div>
|
||||
@@ -68,7 +68,7 @@
|
||||
<!-- #docregion cross-validation-error-message -->
|
||||
<div
|
||||
*ngIf="
|
||||
heroForm.errors?.['identityRevealed'] &&
|
||||
heroForm.hasError('identityRevealed') &&
|
||||
(heroForm.touched || heroForm.dirty)
|
||||
"
|
||||
class="cross-validation-error-message alert"
|
||||
|
||||
@@ -13,14 +13,14 @@
|
||||
},
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@angular/animations": "17.3.5",
|
||||
"@angular/common": "17.3.5",
|
||||
"@angular/compiler": "17.3.5",
|
||||
"@angular/core": "17.3.5",
|
||||
"@angular/forms": "17.3.5",
|
||||
"@angular/platform-browser": "17.3.5",
|
||||
"@angular/platform-browser-dynamic": "17.3.5",
|
||||
"@angular/router": "17.3.5",
|
||||
"@angular/animations": "17.3.7",
|
||||
"@angular/common": "17.3.7",
|
||||
"@angular/compiler": "17.3.7",
|
||||
"@angular/core": "17.3.7",
|
||||
"@angular/forms": "17.3.7",
|
||||
"@angular/platform-browser": "17.3.7",
|
||||
"@angular/platform-browser-dynamic": "17.3.7",
|
||||
"@angular/router": "17.3.7",
|
||||
"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.3.5",
|
||||
"@angular/cli": "17.3.5",
|
||||
"@angular/compiler-cli": "17.3.5",
|
||||
"@angular-devkit/build-angular": "17.3.6",
|
||||
"@angular/cli": "17.3.6",
|
||||
"@angular/compiler-cli": "17.3.7",
|
||||
"@types/angular": "^1.8.4",
|
||||
"@types/angular-route": "^1.7.2",
|
||||
"@types/jasmine": "~5.1.0",
|
||||
@@ -46,6 +46,6 @@
|
||||
"karma-jasmine-html-reporter": "~2.1.0",
|
||||
"protractor": "~7.0.0",
|
||||
"ts-node": "~10.9.0",
|
||||
"typescript": "~4.9.3"
|
||||
"typescript": "~5.4.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,23 +13,23 @@
|
||||
},
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@angular/animations": "17.3.5",
|
||||
"@angular/common": "17.3.5",
|
||||
"@angular/compiler": "17.3.5",
|
||||
"@angular/core": "17.3.5",
|
||||
"@angular/forms": "17.3.5",
|
||||
"@angular/platform-browser": "17.3.5",
|
||||
"@angular/platform-browser-dynamic": "17.3.5",
|
||||
"@angular/router": "17.3.5",
|
||||
"@angular/animations": "17.3.7",
|
||||
"@angular/common": "17.3.7",
|
||||
"@angular/compiler": "17.3.7",
|
||||
"@angular/core": "17.3.7",
|
||||
"@angular/forms": "17.3.7",
|
||||
"@angular/platform-browser": "17.3.7",
|
||||
"@angular/platform-browser-dynamic": "17.3.7",
|
||||
"@angular/router": "17.3.7",
|
||||
"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.3.5",
|
||||
"@angular/cli": "17.3.5",
|
||||
"@angular/compiler-cli": "17.3.5",
|
||||
"@angular-devkit/build-angular": "17.3.6",
|
||||
"@angular/cli": "17.3.6",
|
||||
"@angular/compiler-cli": "17.3.7",
|
||||
"@types/jasmine": "~5.1.0",
|
||||
"@types/node": "^16.11.35",
|
||||
"copyfiles": "^2.4.1",
|
||||
@@ -43,6 +43,6 @@
|
||||
"karma-jasmine-html-reporter": "~2.1.0",
|
||||
"protractor": "~7.0.0",
|
||||
"ts-node": "~10.9.0",
|
||||
"typescript": "~4.9.3"
|
||||
"typescript": "~5.4.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,24 +13,24 @@
|
||||
},
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@angular/animations": "17.3.5",
|
||||
"@angular/common": "17.3.5",
|
||||
"@angular/compiler": "17.3.5",
|
||||
"@angular/core": "17.3.5",
|
||||
"@angular/elements": "17.3.5",
|
||||
"@angular/forms": "17.3.5",
|
||||
"@angular/platform-browser": "17.3.5",
|
||||
"@angular/platform-browser-dynamic": "17.3.5",
|
||||
"@angular/router": "17.3.5",
|
||||
"@angular/animations": "17.3.7",
|
||||
"@angular/common": "17.3.7",
|
||||
"@angular/compiler": "17.3.7",
|
||||
"@angular/core": "17.3.7",
|
||||
"@angular/elements": "17.3.7",
|
||||
"@angular/forms": "17.3.7",
|
||||
"@angular/platform-browser": "17.3.7",
|
||||
"@angular/platform-browser-dynamic": "17.3.7",
|
||||
"@angular/router": "17.3.7",
|
||||
"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.3.5",
|
||||
"@angular/cli": "17.3.5",
|
||||
"@angular/compiler-cli": "17.3.5",
|
||||
"@angular-devkit/build-angular": "17.3.6",
|
||||
"@angular/cli": "17.3.6",
|
||||
"@angular/compiler-cli": "17.3.7",
|
||||
"@types/jasmine": "~5.1.0",
|
||||
"@types/node": "^16.11.35",
|
||||
"jasmine-core": "~5.1.0",
|
||||
@@ -43,6 +43,6 @@
|
||||
"karma-jasmine-html-reporter": "~2.1.0",
|
||||
"protractor": "~7.0.0",
|
||||
"ts-node": "~10.9.0",
|
||||
"typescript": "~4.9.3"
|
||||
"typescript": "~5.4.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,24 +15,24 @@
|
||||
},
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@angular/animations": "17.3.5",
|
||||
"@angular/common": "17.3.5",
|
||||
"@angular/compiler": "17.3.5",
|
||||
"@angular/core": "17.3.5",
|
||||
"@angular/forms": "17.3.5",
|
||||
"@angular/localize": "17.3.5",
|
||||
"@angular/platform-browser": "17.3.5",
|
||||
"@angular/platform-browser-dynamic": "17.3.5",
|
||||
"@angular/router": "17.3.5",
|
||||
"@angular/animations": "17.3.7",
|
||||
"@angular/common": "17.3.7",
|
||||
"@angular/compiler": "17.3.7",
|
||||
"@angular/core": "17.3.7",
|
||||
"@angular/forms": "17.3.7",
|
||||
"@angular/localize": "17.3.7",
|
||||
"@angular/platform-browser": "17.3.7",
|
||||
"@angular/platform-browser-dynamic": "17.3.7",
|
||||
"@angular/router": "17.3.7",
|
||||
"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.3.5",
|
||||
"@angular/cli": "17.3.5",
|
||||
"@angular/compiler-cli": "17.3.5",
|
||||
"@angular-devkit/build-angular": "17.3.6",
|
||||
"@angular/cli": "17.3.6",
|
||||
"@angular/compiler-cli": "17.3.7",
|
||||
"@types/jasmine": "~5.1.0",
|
||||
"@types/node": "^16.11.35",
|
||||
"jasmine-core": "~5.1.0",
|
||||
@@ -45,6 +45,6 @@
|
||||
"karma-jasmine-html-reporter": "~2.1.0",
|
||||
"protractor": "~7.0.0",
|
||||
"ts-node": "~10.9.0",
|
||||
"typescript": "~4.9.3"
|
||||
"typescript": "~5.4.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,24 +13,24 @@
|
||||
},
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@angular/animations": "17.3.5",
|
||||
"@angular/common": "17.3.5",
|
||||
"@angular/compiler": "17.3.5",
|
||||
"@angular/core": "17.3.5",
|
||||
"@angular/forms": "17.3.5",
|
||||
"@angular/platform-browser": "17.3.5",
|
||||
"@angular/platform-browser-dynamic": "17.3.5",
|
||||
"@angular/router": "17.3.5",
|
||||
"@angular/service-worker": "17.3.5",
|
||||
"@angular/animations": "17.3.7",
|
||||
"@angular/common": "17.3.7",
|
||||
"@angular/compiler": "17.3.7",
|
||||
"@angular/core": "17.3.7",
|
||||
"@angular/forms": "17.3.7",
|
||||
"@angular/platform-browser": "17.3.7",
|
||||
"@angular/platform-browser-dynamic": "17.3.7",
|
||||
"@angular/router": "17.3.7",
|
||||
"@angular/service-worker": "17.3.7",
|
||||
"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.3.5",
|
||||
"@angular/cli": "17.3.5",
|
||||
"@angular/compiler-cli": "17.3.5",
|
||||
"@angular-devkit/build-angular": "17.3.6",
|
||||
"@angular/cli": "17.3.6",
|
||||
"@angular/compiler-cli": "17.3.7",
|
||||
"@types/jasmine": "~5.1.0",
|
||||
"@types/node": "^16.11.35",
|
||||
"jasmine-core": "~5.1.0",
|
||||
@@ -43,6 +43,6 @@
|
||||
"karma-jasmine-html-reporter": "~2.1.0",
|
||||
"protractor": "~7.0.0",
|
||||
"ts-node": "~10.9.0",
|
||||
"typescript": "~4.9.3"
|
||||
"typescript": "~5.4.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,15 +25,15 @@
|
||||
},
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@angular/animations": "17.3.5",
|
||||
"@angular/common": "17.3.5",
|
||||
"@angular/compiler": "17.3.5",
|
||||
"@angular/core": "17.3.5",
|
||||
"@angular/forms": "17.3.5",
|
||||
"@angular/platform-browser": "17.3.5",
|
||||
"@angular/platform-browser-dynamic": "17.3.5",
|
||||
"@angular/router": "17.3.5",
|
||||
"@angular/upgrade": "17.3.5",
|
||||
"@angular/animations": "17.3.7",
|
||||
"@angular/common": "17.3.7",
|
||||
"@angular/compiler": "17.3.7",
|
||||
"@angular/core": "17.3.7",
|
||||
"@angular/forms": "17.3.7",
|
||||
"@angular/platform-browser": "17.3.7",
|
||||
"@angular/platform-browser-dynamic": "17.3.7",
|
||||
"@angular/router": "17.3.7",
|
||||
"@angular/upgrade": "17.3.7",
|
||||
"core-js": "^3.0.0",
|
||||
"rxjs": "~7.8.0",
|
||||
"systemjs": "~0.19.39",
|
||||
@@ -42,7 +42,7 @@
|
||||
"zone.js": "~0.14.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@angular/compiler-cli": "17.3.5",
|
||||
"@angular/compiler-cli": "17.3.7",
|
||||
"@rollup/plugin-commonjs": "^25.0.0",
|
||||
"@rollup/plugin-node-resolve": "^13.2.0",
|
||||
"@types/angular": "^1.8.4",
|
||||
@@ -63,6 +63,6 @@
|
||||
"protractor": "~7.0.0",
|
||||
"rollup": "^2.70.1",
|
||||
"rollup-plugin-terser": "^7.0.2",
|
||||
"typescript": "~4.9.3"
|
||||
"typescript": "~5.4.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,16 +13,16 @@
|
||||
},
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@angular/animations": "17.3.5",
|
||||
"@angular/common": "17.3.5",
|
||||
"@angular/compiler": "17.3.5",
|
||||
"@angular/core": "17.3.5",
|
||||
"@angular/forms": "17.3.5",
|
||||
"@angular/platform-browser": "17.3.5",
|
||||
"@angular/platform-browser-dynamic": "17.3.5",
|
||||
"@angular/platform-server": "17.3.5",
|
||||
"@angular/router": "17.3.5",
|
||||
"@angular/ssr": "17.3.5",
|
||||
"@angular/animations": "17.3.7",
|
||||
"@angular/common": "17.3.7",
|
||||
"@angular/compiler": "17.3.7",
|
||||
"@angular/core": "17.3.7",
|
||||
"@angular/forms": "17.3.7",
|
||||
"@angular/platform-browser": "17.3.7",
|
||||
"@angular/platform-browser-dynamic": "17.3.7",
|
||||
"@angular/platform-server": "17.3.7",
|
||||
"@angular/router": "17.3.7",
|
||||
"@angular/ssr": "17.3.6",
|
||||
"angular-in-memory-web-api": "~0.17.0",
|
||||
"express": "^4.18.1",
|
||||
"rxjs": "~7.8.0",
|
||||
@@ -30,9 +30,9 @@
|
||||
"zone.js": "~0.14.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@angular-devkit/build-angular": "17.3.5",
|
||||
"@angular/cli": "17.3.5",
|
||||
"@angular/compiler-cli": "17.3.5",
|
||||
"@angular-devkit/build-angular": "17.3.6",
|
||||
"@angular/cli": "17.3.6",
|
||||
"@angular/compiler-cli": "17.3.7",
|
||||
"@types/express": "^4.17.13",
|
||||
"@types/jasmine": "~5.1.0",
|
||||
"@types/node": "^16.11.35",
|
||||
@@ -46,6 +46,6 @@
|
||||
"karma-jasmine-html-reporter": "~2.1.0",
|
||||
"protractor": "~7.0.0",
|
||||
"ts-node": "~10.9.0",
|
||||
"typescript": "~4.9.3"
|
||||
"typescript": "~5.4.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,20 +19,20 @@
|
||||
"license": "MIT",
|
||||
"repository": {},
|
||||
"dependencies": {
|
||||
"@angular/animations": "17.3.5",
|
||||
"@angular/common": "17.3.5",
|
||||
"@angular/compiler": "17.3.5",
|
||||
"@angular/core": "17.3.5",
|
||||
"@angular/elements": "17.3.5",
|
||||
"@angular/forms": "17.3.5",
|
||||
"@angular/localize": "17.3.5",
|
||||
"@angular/platform-browser": "17.3.5",
|
||||
"@angular/platform-browser-dynamic": "17.3.5",
|
||||
"@angular/platform-server": "17.3.5",
|
||||
"@angular/router": "17.3.5",
|
||||
"@angular/service-worker": "17.3.5",
|
||||
"@angular/ssr": "17.3.5",
|
||||
"@angular/upgrade": "17.3.5",
|
||||
"@angular/animations": "17.3.7",
|
||||
"@angular/common": "17.3.7",
|
||||
"@angular/compiler": "17.3.7",
|
||||
"@angular/core": "17.3.7",
|
||||
"@angular/elements": "17.3.7",
|
||||
"@angular/forms": "17.3.7",
|
||||
"@angular/localize": "17.3.7",
|
||||
"@angular/platform-browser": "17.3.7",
|
||||
"@angular/platform-browser-dynamic": "17.3.7",
|
||||
"@angular/platform-server": "17.3.7",
|
||||
"@angular/router": "17.3.7",
|
||||
"@angular/service-worker": "17.3.7",
|
||||
"@angular/ssr": "17.3.6",
|
||||
"@angular/upgrade": "17.3.7",
|
||||
"angular": "^1.8.3",
|
||||
"angular-in-memory-web-api": "~0.17.0",
|
||||
"angular-route": "1.8.3",
|
||||
@@ -45,9 +45,9 @@
|
||||
"zone.js": "~0.14.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@angular-devkit/build-angular": "17.3.5",
|
||||
"@angular/cli": "17.3.5",
|
||||
"@angular/compiler-cli": "17.3.5",
|
||||
"@angular-devkit/build-angular": "17.3.6",
|
||||
"@angular/cli": "17.3.6",
|
||||
"@angular/compiler-cli": "17.3.7",
|
||||
"@rollup/plugin-commonjs": "^25.0.0",
|
||||
"@rollup/plugin-node-resolve": "^13.2.0",
|
||||
"@types/angular": "^1.8.4",
|
||||
@@ -79,6 +79,6 @@
|
||||
"rollup-plugin-terser": "^7.0.2",
|
||||
"source-map-explorer": "^2.0.0",
|
||||
"ts-node": "~10.9.0",
|
||||
"typescript": "~4.9.3"
|
||||
"typescript": "~5.4.0"
|
||||
}
|
||||
}
|
||||
|
||||
+12
-23
@@ -22,40 +22,29 @@ const debugMode = false;
|
||||
// https://saucelabs.com/platform/platform-configurator
|
||||
const customLaunchers = {
|
||||
'SL_ANDROID11': {
|
||||
base: 'SauceLabs',
|
||||
browserName: 'Chrome',
|
||||
platformName: 'Android',
|
||||
platformVersion: '11.0',
|
||||
deviceName: 'Google Pixel 3a GoogleAPI Emulator',
|
||||
appiumVersion: '1.20.2',
|
||||
extendedDebugging: debugMode,
|
||||
},
|
||||
'SL_ANDROID12': {
|
||||
base: 'SauceLabs',
|
||||
browserName: 'Chrome',
|
||||
platformName: 'Android',
|
||||
platformVersion: '12.0',
|
||||
deviceName: 'Google Pixel 4a (5G) GoogleAPI Emulator',
|
||||
appiumVersion: '1.22.1',
|
||||
extendedDebugging: debugMode,
|
||||
browserName: 'Browser',
|
||||
'appium:deviceName': 'Android GoogleAPI Emulator',
|
||||
'appium:platformVersion': 'current_major',
|
||||
'appium:automationName': 'UiAutomator2',
|
||||
},
|
||||
};
|
||||
|
||||
const sauceAliases = {
|
||||
'CI_REQUIRED': buildConfiguration('unitTest', 'SL', true),
|
||||
'CI_OPTIONAL': buildConfiguration('unitTest', 'SL', false)
|
||||
'CI_OPTIONAL': buildConfiguration('unitTest', 'SL', false),
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
customLaunchers : customLaunchers,
|
||||
sauceAliases : sauceAliases,
|
||||
customLaunchers: customLaunchers,
|
||||
sauceAliases: sauceAliases,
|
||||
};
|
||||
|
||||
function buildConfiguration(type, target, required) {
|
||||
return Object.keys(config)
|
||||
.filter((item) => {
|
||||
const conf = config[item][type];
|
||||
return conf.required === required && conf.target === target;
|
||||
})
|
||||
.map((item) => target + '_' + item.toUpperCase());
|
||||
.filter((item) => {
|
||||
const conf = config[item][type];
|
||||
return conf.required === required && conf.target === target;
|
||||
})
|
||||
.map((item) => target + '_' + item.toUpperCase());
|
||||
}
|
||||
|
||||
@@ -70,13 +70,6 @@ pkg_web(
|
||||
":bundle",
|
||||
"@npm//:node_modules/tslib/tslib.js",
|
||||
],
|
||||
# Currently, ibazel doesn't allow passing in flags to bazel run.
|
||||
# This means we are not able to use --config snapshot-build
|
||||
# to access the current commit SHA.
|
||||
# Since we still want to be able to use ibazel to speed up
|
||||
# local development, we supply an empty string for the SHA substitution.
|
||||
# This does not effect production builds.
|
||||
substitutions = {"BUILD_SCM_COMMIT_SHA": ""},
|
||||
)
|
||||
|
||||
http_server(
|
||||
|
||||
@@ -8,5 +8,4 @@
|
||||
|
||||
export const environment = {
|
||||
production: false,
|
||||
LATEST_SHA: 'BUILD_SCM_COMMIT_SHA', // Stamped at build time by bazel
|
||||
};
|
||||
|
||||
@@ -51,7 +51,7 @@ import {ComponentTreeNode} from './interfaces';
|
||||
import {ngDebugDependencyInjectionApiIsSupported} from './ng-debug-api/ng-debug-api';
|
||||
import {setConsoleReference} from './set-console-reference';
|
||||
import {serializeDirectiveState} from './state-serializer/state-serializer';
|
||||
import {runOutsideAngular} from './utils';
|
||||
import {runOutsideAngular, unwrapSignal} from './utils';
|
||||
import {DirectiveForestHooks} from './hooks/hooks';
|
||||
|
||||
export const subscribeToClientEvents = (
|
||||
@@ -194,7 +194,7 @@ const getNestedPropertiesCallback =
|
||||
}
|
||||
let data = current.instance;
|
||||
for (const prop of propPath) {
|
||||
data = data[prop];
|
||||
data = unwrapSignal(data[prop]);
|
||||
if (!data) {
|
||||
console.error('Cannot access the properties', propPath, 'of', node);
|
||||
}
|
||||
|
||||
+2
-2
@@ -325,11 +325,11 @@ function getLevelDescriptorValue(
|
||||
const isReadonly = isSignal(prop);
|
||||
switch (type) {
|
||||
case PropType.Array:
|
||||
return prop.map((_: any, idx: number) =>
|
||||
return value.map((_: any, idx: number) =>
|
||||
continuation(value, idx, isReadonly, currentLevel + 1, level),
|
||||
);
|
||||
case PropType.Object:
|
||||
return getKeys(prop).reduce(
|
||||
return getKeys(value).reduce(
|
||||
(accumulator, propName) => {
|
||||
if (!ignoreList.has(propName)) {
|
||||
accumulator[propName] = continuation(
|
||||
|
||||
@@ -6,13 +6,8 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
interface Env {
|
||||
LATEST_SHA: string;
|
||||
}
|
||||
|
||||
export interface Environment {
|
||||
production: boolean;
|
||||
LATEST_SHA: string;
|
||||
}
|
||||
|
||||
export const TOP_LEVEL_FRAME_ID = 0;
|
||||
|
||||
@@ -51,9 +51,7 @@
|
||||
</span>
|
||||
}
|
||||
|
||||
@if (latestSHA) {
|
||||
| DevTools SHA: {{ latestSHA }}
|
||||
}
|
||||
| DevTools: {{ extensionVersion }}
|
||||
</section>
|
||||
}
|
||||
</nav>
|
||||
|
||||
@@ -90,6 +90,8 @@ export class DevToolsTabsComponent implements OnInit, AfterViewInit {
|
||||
return parseInt(version.toString().split('.')[0], 10);
|
||||
});
|
||||
|
||||
extensionVersion = 'Development Build';
|
||||
|
||||
constructor(
|
||||
public tabUpdate: TabUpdate,
|
||||
public themeService: ThemeService,
|
||||
@@ -111,6 +113,10 @@ export class DevToolsTabsComponent implements OnInit, AfterViewInit {
|
||||
|
||||
ngOnInit(): void {
|
||||
this.navbar.stretchTabs = false;
|
||||
|
||||
if (chrome !== undefined && chrome.runtime !== undefined) {
|
||||
this.extensionVersion = chrome.runtime.getManifest().version;
|
||||
}
|
||||
}
|
||||
|
||||
get tabs(): Tabs[] {
|
||||
@@ -122,10 +128,6 @@ export class DevToolsTabsComponent implements OnInit, AfterViewInit {
|
||||
this.navbar.disablePagination = true;
|
||||
}
|
||||
|
||||
get latestSHA(): string {
|
||||
return this.applicationEnvironment.environment.LATEST_SHA.slice(0, 8);
|
||||
}
|
||||
|
||||
changeTab(tab: Tabs): void {
|
||||
this.activeTab = tab;
|
||||
this.tabUpdate.notify();
|
||||
|
||||
+47
-43
@@ -21,53 +21,57 @@
|
||||
(mouseleave)="removeHighlight()"
|
||||
[style.padding-left]="15 + 15 * node.level + 'px'"
|
||||
>
|
||||
<div class="tree-node-info">
|
||||
@if (node.expandable) {
|
||||
<button
|
||||
[style.left]="15 * node.level + 'px'"
|
||||
(click)="treeControl.toggle(node)"
|
||||
[attr.aria-label]="'toggle ' + node.name"
|
||||
>
|
||||
<mat-icon class="mat-icon-rtl-mirror">
|
||||
{{ treeControl.isExpanded(node) ? 'expand_more' : 'chevron_right' }}
|
||||
</mat-icon>
|
||||
</button>
|
||||
}
|
||||
<span class="element-name" [class.angular-element]="isElement(node)">{{ node.name }}</span>
|
||||
<div class="tree-node-info">
|
||||
@if (node.expandable) {
|
||||
<!-- We stop propagration on dblClick to prevent to show the element panel -->
|
||||
<button
|
||||
[style.left]="15 * node.level + 'px'"
|
||||
(click)="treeControl.toggle(node)"
|
||||
(dblclick)="stopPropagation($event)"
|
||||
[attr.aria-label]="'toggle ' + node.name"
|
||||
>
|
||||
<mat-icon class="mat-icon-rtl-mirror">
|
||||
{{ treeControl.isExpanded(node) ? 'expand_more' : 'chevron_right' }}
|
||||
</mat-icon>
|
||||
</button>
|
||||
}
|
||||
<span class="element-name" [class.angular-element]="isElement(node)">{{ node.name }}</span>
|
||||
|
||||
@if (node.directives) {
|
||||
<span class="dir-names">[{{ node.directives }}]</span>
|
||||
}
|
||||
@if (isSelected(node)) {
|
||||
<span class="console-reference"> == $ng0 </span>
|
||||
}
|
||||
@if (node.directives) {
|
||||
<span class="dir-names">[{{ node.directives }}]</span>
|
||||
}
|
||||
@if (isSelected(node)) {
|
||||
<span class="console-reference"> == $ng0 </span>
|
||||
}
|
||||
|
||||
@switch(node.hydration?.status) {
|
||||
@case('hydrated') {
|
||||
<mat-icon matTooltip="Hydrated" class="hydration">water_drop</mat-icon>
|
||||
}
|
||||
@case('skipped') {
|
||||
<mat-icon matTooltip="Skipped" class="hydration skipped">invert_colors_off</mat-icon>
|
||||
}
|
||||
@case('mismatched') {
|
||||
<mat-icon matTooltip="Mismatch" class="hydration mismatched">error_outline</mat-icon>
|
||||
}
|
||||
}
|
||||
</div>
|
||||
@if(node.hydration?.status === 'mismatched' && (node.hydration.expectedNodeDetails || node.hydration.actualNodeDetails)) {
|
||||
<div class="hydration-error">
|
||||
@if(node.hydration.expectedNodeDetails) {
|
||||
<div>Expected Dom:</div>
|
||||
<pre>{{node.hydration.expectedNodeDetails}}</pre>
|
||||
}
|
||||
@if(node.hydration.actualNodeDetails) {
|
||||
<div>Actual Dom:</div>
|
||||
<pre>{{node.hydration.actualNodeDetails}}</pre>
|
||||
@switch (node.hydration?.status) {
|
||||
@case ('hydrated') {
|
||||
<mat-icon matTooltip="Hydrated" class="hydration">water_drop</mat-icon>
|
||||
}
|
||||
@case ('skipped') {
|
||||
<mat-icon matTooltip="Hydration skipped" class="hydration skipped">invert_colors_off</mat-icon>
|
||||
}
|
||||
@case ('mismatched') {
|
||||
<mat-icon matTooltip="Hydration mismatch" class="hydration mismatched">error_outline</mat-icon>
|
||||
}
|
||||
}
|
||||
</div>
|
||||
}
|
||||
@if (
|
||||
treeControl.isExpanded(node) &&
|
||||
node.hydration?.status === 'mismatched' &&
|
||||
(node.hydration.expectedNodeDetails || node.hydration.actualNodeDetails)
|
||||
) {
|
||||
<div class="hydration-error">
|
||||
@if (node.hydration.expectedNodeDetails) {
|
||||
<div>Expected Dom:</div>
|
||||
<pre>{{node.hydration.expectedNodeDetails}}</pre>
|
||||
}
|
||||
@if (node.hydration.actualNodeDetails) {
|
||||
<div>Actual Dom:</div>
|
||||
<pre>{{node.hydration.actualNodeDetails}}</pre>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
</ng-container>
|
||||
|
||||
</cdk-virtual-scroll-viewport>
|
||||
|
||||
+18
@@ -16,6 +16,7 @@ import {
|
||||
ChangeDetectionStrategy,
|
||||
ChangeDetectorRef,
|
||||
Component,
|
||||
ElementRef,
|
||||
EventEmitter,
|
||||
HostListener,
|
||||
Input,
|
||||
@@ -99,11 +100,13 @@ export class DirectiveForestComponent {
|
||||
readonly itemHeight = 18;
|
||||
|
||||
private _initialized = false;
|
||||
private resizeObserver: ResizeObserver;
|
||||
|
||||
constructor(
|
||||
private _tabUpdate: TabUpdate,
|
||||
private _messageBus: MessageBus<Events>,
|
||||
private _cdr: ChangeDetectorRef,
|
||||
private elementRef: ElementRef,
|
||||
) {
|
||||
this.subscribeToInspectorEvents();
|
||||
this._tabUpdate.tabUpdate$.pipe(takeUntilDestroyed()).subscribe(() => {
|
||||
@@ -114,6 +117,17 @@ export class DirectiveForestComponent {
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// In some cases there a height changes, we need to recalculate the viewport size.
|
||||
this.resizeObserver = new ResizeObserver(() => {
|
||||
this.viewport.scrollToIndex(0);
|
||||
this.viewport.checkViewportSize();
|
||||
});
|
||||
this.resizeObserver.observe(this.elementRef.nativeElement);
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this.resizeObserver.disconnect();
|
||||
}
|
||||
|
||||
subscribeToInspectorEvents(): void {
|
||||
@@ -330,6 +344,10 @@ export class DirectiveForestComponent {
|
||||
}
|
||||
}
|
||||
|
||||
stopPropagation(event: Event): void {
|
||||
event.stopPropagation();
|
||||
}
|
||||
|
||||
private _findMatchedNodes(): number[] {
|
||||
const indexesOfMatchedNodes: number[] = [];
|
||||
for (let i = 0; i < this.dataSource.data.length; i++) {
|
||||
|
||||
+2
-2
@@ -157,8 +157,8 @@ export class DirectivePropertyResolver {
|
||||
propPointer = inputLabels.has(propName)
|
||||
? inputProps
|
||||
: outputLabels.has(propName)
|
||||
? outputProps
|
||||
: stateProps;
|
||||
? outputProps
|
||||
: stateProps;
|
||||
propPointer[propName] = this.directiveProperties[propName];
|
||||
});
|
||||
|
||||
|
||||
@@ -151,5 +151,4 @@ pkg_web(
|
||||
additional_root_paths = [
|
||||
"projects/ng-devtools/src/lib",
|
||||
],
|
||||
substitutions = {"BUILD_SCM_COMMIT_SHA": "{BUILD_SCM_COMMIT_SHA}"},
|
||||
)
|
||||
|
||||
@@ -8,5 +8,4 @@
|
||||
|
||||
export const environment = {
|
||||
production: true,
|
||||
LATEST_SHA: 'BUILD_SCM_COMMIT_SHA', // Stamped at build time by bazel
|
||||
};
|
||||
|
||||
@@ -3,12 +3,13 @@
|
||||
"short_name": "Angular DevTools",
|
||||
"name": "Angular DevTools",
|
||||
"description": "Angular DevTools extends Chrome DevTools adding Angular specific debugging and profiling capabilities.",
|
||||
"version": "1.0.13",
|
||||
"version_name": "1.0.13",
|
||||
"version": "1.0.15",
|
||||
"version_name": "1.0.15",
|
||||
"minimum_chrome_version": "102",
|
||||
"content_security_policy": {
|
||||
"extension_pages": "script-src 'self'; object-src 'self'"
|
||||
},
|
||||
"key": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAlm3JzpfmggVNDXe1uSVKq1btwWec4pOYefbB36CYj41bqUyw3xQqiAOCIlSAhl5GkQA/9PTixCNd14ix6RbI2yV8OeNt6oc0ca5BmEdGVj27lu32CgH5KbOc+AhyOAikoMq4IlgaWvqglZiyzd9KnL1wO/DqLDsZS0PV9oRwZk/8Eno2giZ1El4YQ5cNZ125JJR5z9KYAvxUs6bnDXAM71gW5sJwoUdgXQZhEIT18uSz0p2RiC0Ajjh4kx9c5Ll9naByXd3g5hjS8Ro5WbENRhxZtx1eEcgwC79l0pbJjI71i0QjbfLZn6syfHGMWsLyMc/ucv6d86tCPO8J1mU8iQIDAQAB",
|
||||
"icons": {
|
||||
"16": "assets/icon16.png",
|
||||
"48": "assets/icon48.png",
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
"short_name": "Angular DevTools",
|
||||
"name": "Angular DevTools",
|
||||
"description": "Angular DevTools extends Firefox DevTools adding Angular specific debugging and profiling capabilities.",
|
||||
"version": "1.0.13",
|
||||
"version": "1.0.15",
|
||||
"content_security_policy": "script-src 'self' 'unsafe-eval'; object-src 'self'",
|
||||
"icons": {
|
||||
"16": "assets/icon16.png",
|
||||
|
||||
@@ -73,13 +73,6 @@ pkg_web(
|
||||
":bundle",
|
||||
"@npm//:node_modules/tslib/tslib.js",
|
||||
],
|
||||
# Currently, ibazel doesn't allow passing in flags to bazel run.
|
||||
# This means we are not able to use --config snapshot-build
|
||||
# to access the current commit SHA.
|
||||
# Since we still want to be able to use ibazel to speed up
|
||||
# local development, we supply an empty string for the SHA substitution.
|
||||
# This does not effect production builds.
|
||||
substitutions = {"BUILD_SCM_COMMIT_SHA": ""},
|
||||
)
|
||||
|
||||
http_server(
|
||||
|
||||
@@ -8,5 +8,4 @@
|
||||
|
||||
export const environment = {
|
||||
production: false,
|
||||
LATEST_SHA: 'BUILD_SCM_COMMIT_SHA', // Stamped at build time by bazel
|
||||
};
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user