mirror of
https://github.com/angular/angular.git
synced 2026-09-14 13:54:52 +08:00
Compare commits
225 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 9c486c9682 | |||
| 3fe21333ce | |||
| 97e8265570 | |||
| da93046920 | |||
| 017825b0f4 | |||
| 58365e04a2 | |||
| 2435291dda | |||
| 5cb44aa2fd | |||
| 375e60610e | |||
| ca39926de2 | |||
| dd8490f384 | |||
| 2e9a21a6ad | |||
| 744e20641a | |||
| 63a9027720 | |||
| ef55d48dee | |||
| 8d4b4ff7bf | |||
| 2e3fa28b93 | |||
| eeaec924ef | |||
| 40e1edc977 | |||
| 4482cbd22c | |||
| d695398414 | |||
| f3f1f85bed | |||
| c6896a30c0 | |||
| db78666196 | |||
| e847487d4f | |||
| 238f2a8bc9 | |||
| 2de7679b0b | |||
| 06391d1eb8 | |||
| 0cd9538f24 | |||
| 241858d20b | |||
| f303838d95 | |||
| bc4a6a9715 | |||
| 429700590c | |||
| ca459177d6 | |||
| 04a1d50bc8 | |||
| fc4d8bb78c | |||
| 756aa8f600 | |||
| 4586d2341d | |||
| f466c89d1f | |||
| 8876283f47 | |||
| e74ea10f5d | |||
| b74c02a6b8 | |||
| 9f6605d11b | |||
| a42f0ba65e | |||
| ea2fd1b66a | |||
| a6f09adb55 | |||
| 47c712e51e | |||
| 6f637718d1 | |||
| 54deaae3ba | |||
| cd88f1486e | |||
| 37a6da30ca | |||
| afc72b06fe | |||
| 0fb114274c | |||
| 86086169e7 | |||
| 99a0b90073 | |||
| 52a31d0ed8 | |||
| 4b6deb5ac2 | |||
| f933a38e3d | |||
| 46a2c5f8cc | |||
| 9e3d54cf2d | |||
| a997e08c6f | |||
| 7d59eb0a96 | |||
| 924a2768df | |||
| 7df133dcc2 | |||
| 2a0ac20824 | |||
| 67eb402024 | |||
| 4883ae686c | |||
| 57cb448de5 | |||
| 0bd668c943 | |||
| 2c0bb7fb0e | |||
| 735cdf3761 | |||
| 948fb5f937 | |||
| 9f1a5fa5da | |||
| cab06bd125 | |||
| cdc5e39532 | |||
| ed8e7c727b | |||
| e08cc50f3c | |||
| ca7456e722 | |||
| b353ad5bac | |||
| bd6d18b977 | |||
| 3b218a72ce | |||
| 4367c44f7f | |||
| b69bf37841 | |||
| 05a05b628b | |||
| c21f41d6d4 | |||
| 413db303f4 | |||
| 752ebd62c9 | |||
| 7001b5d633 | |||
| fccf375df1 | |||
| 8d85de7fe1 | |||
| 4c2b0d94e2 | |||
| 57174bb60b | |||
| 7a4a5253ea | |||
| c5a0e909ac | |||
| d07ce6156e | |||
| e3b874b4fb | |||
| 1ac37f932f | |||
| 8650f0dee2 | |||
| 030d77010d | |||
| 96ca49d145 | |||
| 8264382a6b | |||
| f5a5215166 | |||
| 5cd00f167c | |||
| 22f89c0a2d | |||
| c442912532 | |||
| 9f9dd901ae | |||
| 30e42df518 | |||
| 566499af1d | |||
| 4a5f37d216 | |||
| 74567f1c8a | |||
| 64399b26c0 | |||
| 1b296dbb80 | |||
| cddc426e18 | |||
| 8109013b0f | |||
| f25d7e880f | |||
| f240c70971 | |||
| 0172477ff2 | |||
| 0702d4a560 | |||
| 798aee7a83 | |||
| c477e876e3 | |||
| ecd0daf8fd | |||
| 6eca767811 | |||
| d8d6478d0e | |||
| 0c4d7e061e | |||
| 293dc3b046 | |||
| 8dca5a11f5 | |||
| 45fca0bc95 | |||
| 85819b9cdb | |||
| 87378a773d | |||
| 96dd0c715e | |||
| ccddacf11d | |||
| b216e76838 | |||
| 0d31904a96 | |||
| aa3df2a7d4 | |||
| 50b7901169 | |||
| 1439f059a3 | |||
| ac211d46b0 | |||
| 356984d9ba | |||
| 7ac94dd664 | |||
| 49aff2726f | |||
| 6c14c9324f | |||
| c0c7216c5f | |||
| 25e4f04d9f | |||
| 259707e31c | |||
| d17ba9bc87 | |||
| a4923f1f17 | |||
| e603450592 | |||
| c6861eefa4 | |||
| 910742389f | |||
| 98d545fafa | |||
| 7786e35faa | |||
| 49101330f8 | |||
| 4507704a7e | |||
| 854b839531 | |||
| 89f7b00e59 | |||
| f12b01ec88 | |||
| 0c8aaf082e | |||
| a4f024fb07 | |||
| e2e0f08d99 | |||
| e194fbfbaa | |||
| 8ad1ee764b | |||
| f222bee8fa | |||
| 9a9a00e0f9 | |||
| 5f9cac037b | |||
| 9778326f10 | |||
| fac5a7cf4e | |||
| ae145d19c6 | |||
| 30b6d34a49 | |||
| 3e0b7c1bdc | |||
| a58a35dfe4 | |||
| a7c9fbb087 | |||
| 20811aad27 | |||
| df453c3549 | |||
| 1f6c96a1dc | |||
| 656549d88a | |||
| c0f0baac58 | |||
| d38aa4f103 | |||
| 7397b6648a | |||
| 0fe9331958 | |||
| 7620f506c0 | |||
| 62ad2f0c71 | |||
| 424a26b3c7 | |||
| 18bd1a5ccb | |||
| 8922a11693 | |||
| 4e0341050b | |||
| 0616dc2839 | |||
| dc228c6fc1 | |||
| b1a7249c24 | |||
| 9ff4893bc0 | |||
| 025f7af694 | |||
| 1c4a99a6d1 | |||
| 51d53fc728 | |||
| be882c3efb | |||
| 18fa6d7556 | |||
| bb8a5a4c70 | |||
| 9c00506a26 | |||
| 79e8539ce2 | |||
| 6f79507ea7 | |||
| 6616019735 | |||
| 4d4858fa28 | |||
| 538fe47d9d | |||
| b07c549802 | |||
| 6f6ad0279b | |||
| d08f91f861 | |||
| 28aa7b3188 | |||
| 6ac0a68d73 | |||
| ea19bff302 | |||
| 1112e9196a | |||
| 0d8a5c4665 | |||
| 76ceebad04 | |||
| 99c2d76651 | |||
| 246cb9c381 | |||
| d7c75c6edb | |||
| 0daca457bb | |||
| 12f9ed01cc | |||
| 69713413f7 | |||
| 43d6816f9e | |||
| 4c4fea1304 | |||
| c6df89c827 | |||
| 9cae9b4a8c | |||
| 29b959ce31 | |||
| a24b24e8a4 | |||
| f51c008744 | |||
| 3dedd6a07a | |||
| 881376eb42 |
@@ -1,5 +1,5 @@
|
||||
# Enable debugging tests with --config=debug
|
||||
test:debug --test_arg=--node_options=--inspect-brk --test_output=streamed --test_strategy=exclusive --test_timeout=9999 --nocache_test_results --spawn_strategy=local
|
||||
test:debug --test_arg=--node_options=--inspect-brk --test_output=streamed --test_strategy=exclusive --test_timeout=9999 --nocache_test_results
|
||||
|
||||
# Do not attempt to de-flake locally.
|
||||
# On CI we might set this to `3` to run with deflaking.
|
||||
|
||||
@@ -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@c83e99a12397014162531ca125c94549db55dd84
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@e52eb8237f2ed71195f87ce8046467a176568e58
|
||||
- name: Setup Bazel
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@c83e99a12397014162531ca125c94549db55dd84
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@e52eb8237f2ed71195f87ce8046467a176568e58
|
||||
- name: Setup Bazel RBE
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@c83e99a12397014162531ca125c94549db55dd84
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@e52eb8237f2ed71195f87ce8046467a176568e58
|
||||
- name: Install node modules
|
||||
run: yarn install --frozen-lockfile
|
||||
- name: Build adev to ensure it continues to work
|
||||
run: yarn bazel build --config=aio_local_deps //adev:build
|
||||
- uses: angular/dev-infra/github-actions/previews/pack-and-upload-artifact@c83e99a12397014162531ca125c94549db55dd84
|
||||
- uses: angular/dev-infra/github-actions/previews/pack-and-upload-artifact@73777547f819464aa5f48071b966eed34fd2c402
|
||||
with:
|
||||
workflow-artifact-name: 'adev-preview'
|
||||
pull-number: '${{github.event.pull_request.number}}'
|
||||
|
||||
@@ -40,7 +40,7 @@ jobs:
|
||||
npx -y firebase-tools@latest target:clear --config adev/firebase.json --project ${{env.PREVIEW_PROJECT}} hosting angular-docs
|
||||
npx -y firebase-tools@latest target:apply --config adev/firebase.json --project ${{env.PREVIEW_PROJECT}} hosting angular-docs ${{env.PREVIEW_SITE}}
|
||||
|
||||
- uses: angular/dev-infra/github-actions/previews/upload-artifacts-to-firebase@c83e99a12397014162531ca125c94549db55dd84
|
||||
- uses: angular/dev-infra/github-actions/previews/upload-artifacts-to-firebase@73777547f819464aa5f48071b966eed34fd2c402
|
||||
with:
|
||||
github-token: '${{secrets.GITHUB_TOKEN}}'
|
||||
workflow-artifact-name: 'adev-preview'
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
# This workflow builds the AIO previews for pull requests when a certain label is applied.
|
||||
# The actual deployment happens as part of a dedicated second workflow to avoid security
|
||||
# issues where the building would otherwise occur in an authorized context where secrets
|
||||
# could be leaked. More details can be found here:
|
||||
|
||||
# https://securitylab.github.com/research/github-actions-preventing-pwn-requests/.
|
||||
|
||||
name: Build AIO app for preview deployment
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [synchronize, labeled]
|
||||
|
||||
permissions: read-all
|
||||
|
||||
jobs:
|
||||
aio-build:
|
||||
runs-on: ubuntu-latest
|
||||
# We only want to build and deploy the AIO app if the `aio: preview` label has been
|
||||
# added, or if the label is already applied and new changes have been made in the PR.
|
||||
if: |
|
||||
(github.event.action == 'labeled' && github.event.label.name == 'aio: preview') ||
|
||||
(github.event.action == 'synchronize' && contains(github.event.pull_request.labels.*.name, 'aio: preview'))
|
||||
steps:
|
||||
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||
- uses: ./.github/actions/yarn-install
|
||||
|
||||
- uses: angular/dev-infra/github-actions/bazel/configure-remote@7bd2697b40203c84826129b611e539fa663881f8
|
||||
with:
|
||||
bazelrc: ./.bazelrc.user
|
||||
|
||||
# Build the web package. Note: We run Bazel from a low-resource Github action container,
|
||||
# so we manually need to instruct Bazel to run more actions concurrently as by default
|
||||
# the number of concurrent actions is determined based on the host resources.
|
||||
- run: bazel build //aio:build --jobs=32 --announce_rc --verbose_failures
|
||||
|
||||
- uses: angular/dev-infra/github-actions/previews/pack-and-upload-artifact@7bd2697b40203c84826129b611e539fa663881f8
|
||||
with:
|
||||
workflow-artifact-name: 'aio'
|
||||
pull-number: '${{github.event.pull_request.number}}'
|
||||
artifact-build-revision: '${{github.event.pull_request.head.sha}}'
|
||||
deploy-directory: './dist/bin/aio/build'
|
||||
@@ -0,0 +1,44 @@
|
||||
# This workflow runs whenever the AIO build workflow has completed. Deployment happens
|
||||
# as part of a dedicated second workflow to avoid security issues where the building would
|
||||
# otherwise occur in an authorized context where secrets could be leaked.
|
||||
#
|
||||
# More details can be found here:
|
||||
# https://securitylab.github.com/research/github-actions-preventing-pwn-requests/.
|
||||
|
||||
name: Deploying AIO preview to Firebase
|
||||
|
||||
on:
|
||||
workflow_run:
|
||||
workflows: ['Build AIO app for preview deployment']
|
||||
types: [completed]
|
||||
|
||||
permissions:
|
||||
# Needed in order to be able to comment on the pull request.
|
||||
pull-requests: write
|
||||
|
||||
env:
|
||||
PREVIEW_PROJECT: ng-dev-previews
|
||||
PREVIEW_SITE: ng-dev-previews-fw
|
||||
|
||||
jobs:
|
||||
aio-deploy:
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ github.event.workflow_run.conclusion == 'success' }}
|
||||
steps:
|
||||
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4
|
||||
|
||||
- name: Configure Firebase deploy target
|
||||
working-directory: aio/
|
||||
run: |
|
||||
# We can use `npx` as the Firebase deploy actions uses it too.
|
||||
npx -y firebase-tools@latest target:clear --project ${{env.PREVIEW_PROJECT}} hosting aio
|
||||
npx -y firebase-tools@latest target:apply --project ${{env.PREVIEW_PROJECT}} hosting aio ${{env.PREVIEW_SITE}}
|
||||
|
||||
- uses: angular/dev-infra/github-actions/previews/upload-artifacts-to-firebase@7bd2697b40203c84826129b611e539fa663881f8
|
||||
with:
|
||||
github-token: '${{secrets.GITHUB_TOKEN}}'
|
||||
workflow-artifact-name: 'aio'
|
||||
firebase-config-dir: './aio/'
|
||||
firebase-public-dir: './aio/dist'
|
||||
firebase-project-id: '${{env.PREVIEW_PROJECT}}'
|
||||
firebase-service-key: '${{secrets.FIREBASE_PREVIEW_SERVICE_TOKEN}}'
|
||||
@@ -16,6 +16,6 @@ jobs:
|
||||
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: angular/dev-infra/github-actions/branch-manager@c83e99a12397014162531ca125c94549db55dd84
|
||||
- uses: angular/dev-infra/github-actions/branch-manager@7bd2697b40203c84826129b611e539fa663881f8
|
||||
with:
|
||||
angular-robot-key: ${{ secrets.ANGULAR_ROBOT_PRIVATE_KEY }}
|
||||
|
||||
@@ -38,7 +38,7 @@ jobs:
|
||||
|
||||
- uses: ./.github/actions/yarn-install
|
||||
|
||||
- uses: angular/dev-infra/github-actions/bazel/configure-remote@c83e99a12397014162531ca125c94549db55dd84
|
||||
- uses: angular/dev-infra/github-actions/bazel/configure-remote@7bd2697b40203c84826129b611e539fa663881f8
|
||||
with:
|
||||
bazelrc: ./.bazelrc.user
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ jobs:
|
||||
SAUCE_ACCESS_KEY: ${{ secrets.SAUCE_ACCESS_KEY }}
|
||||
steps:
|
||||
- name: Initialize environment
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@c83e99a12397014162531ca125c94549db55dd84
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@7bd2697b40203c84826129b611e539fa663881f8
|
||||
with:
|
||||
cache-node-modules: true
|
||||
# Checking out the pull request commit is intended here as we need to run the changed code tests.
|
||||
@@ -31,7 +31,7 @@ jobs:
|
||||
- name: Install node modules
|
||||
run: yarn install --frozen-lockfile
|
||||
- name: Setup Bazel
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@c83e99a12397014162531ca125c94549db55dd84
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@7bd2697b40203c84826129b611e539fa663881f8
|
||||
- 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
|
||||
|
||||
+107
-51
@@ -23,7 +23,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Initialize environment
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@c83e99a12397014162531ca125c94549db55dd84
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@7bd2697b40203c84826129b611e539fa663881f8
|
||||
with:
|
||||
cache-node-modules: true
|
||||
node-module-directories: |
|
||||
@@ -35,6 +35,8 @@ jobs:
|
||||
run: yarn install --frozen-lockfile --cwd aio
|
||||
- name: Check code lint
|
||||
run: yarn -s tslint
|
||||
- name: Check code lint in aio
|
||||
run: yarn -s --cwd aio lint
|
||||
- name: Check for circular dependencies
|
||||
run: yarn -s ts-circular-deps:check
|
||||
- name: Validate pull approve configuration
|
||||
@@ -58,13 +60,13 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Initialize environment
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@c83e99a12397014162531ca125c94549db55dd84
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@7bd2697b40203c84826129b611e539fa663881f8
|
||||
with:
|
||||
cache-node-modules: true
|
||||
- name: Setup Bazel
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@c83e99a12397014162531ca125c94549db55dd84
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@7bd2697b40203c84826129b611e539fa663881f8
|
||||
- name: Setup Bazel RBE
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@c83e99a12397014162531ca125c94549db55dd84
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@7bd2697b40203c84826129b611e539fa663881f8
|
||||
- name: Install node modules
|
||||
run: yarn install --frozen-lockfile
|
||||
- name: Run unit tests
|
||||
@@ -76,46 +78,132 @@ jobs:
|
||||
runs-on: ubuntu-latest-4core
|
||||
steps:
|
||||
- name: Initialize environment
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@c83e99a12397014162531ca125c94549db55dd84
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@7bd2697b40203c84826129b611e539fa663881f8
|
||||
with:
|
||||
cache-node-modules: true
|
||||
- name: Setup Bazel
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@c83e99a12397014162531ca125c94549db55dd84
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@7bd2697b40203c84826129b611e539fa663881f8
|
||||
- name: Setup Bazel Remote Caching
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@c83e99a12397014162531ca125c94549db55dd84
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@7bd2697b40203c84826129b611e539fa663881f8
|
||||
- name: Install node modules
|
||||
run: yarn install --frozen-lockfile --network-timeout 100000
|
||||
- name: Run CI tests for framework
|
||||
run: yarn test:ci
|
||||
|
||||
aio:
|
||||
runs-on: ubuntu-latest-4core
|
||||
steps:
|
||||
- name: Initialize environment
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@7bd2697b40203c84826129b611e539fa663881f8
|
||||
with:
|
||||
cache-node-modules: true
|
||||
node-module-directories: |
|
||||
./aio/node_modules
|
||||
- name: Setup Bazel
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@7bd2697b40203c84826129b611e539fa663881f8
|
||||
- name: Setup Bazel RBE
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@7bd2697b40203c84826129b611e539fa663881f8
|
||||
- name: Install node modules for aio
|
||||
run: yarn install --cwd aio --frozen-lockfile
|
||||
- name: Run AIO tests with upstream packages
|
||||
run: yarn --cwd aio test:ci
|
||||
- name: Check generated bundle sizes
|
||||
run: yarn --cwd aio payload-size
|
||||
|
||||
adev:
|
||||
runs-on:
|
||||
labels: ubuntu-latest-4core
|
||||
steps:
|
||||
- name: Initialize environment
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@c83e99a12397014162531ca125c94549db55dd84
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@e52eb8237f2ed71195f87ce8046467a176568e58
|
||||
- name: Setup Bazel
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@c83e99a12397014162531ca125c94549db55dd84
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@e52eb8237f2ed71195f87ce8046467a176568e58
|
||||
- name: Setup Bazel RBE
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@c83e99a12397014162531ca125c94549db55dd84
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@e52eb8237f2ed71195f87ce8046467a176568e58
|
||||
- name: Install node modules
|
||||
run: yarn install --frozen-lockfile
|
||||
- name: Build adev to ensure it continues to work
|
||||
run: yarn bazel test --config=aio_local_deps //adev:test
|
||||
|
||||
aio-local:
|
||||
runs-on:
|
||||
labels: ubuntu-latest-4core
|
||||
steps:
|
||||
- name: Initialize environment
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@7bd2697b40203c84826129b611e539fa663881f8
|
||||
with:
|
||||
cache-node-modules: true
|
||||
node-module-directories: |
|
||||
./aio/node_modules
|
||||
- name: Setup Bazel
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@7bd2697b40203c84826129b611e539fa663881f8
|
||||
- name: Setup Bazel RBE
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@7bd2697b40203c84826129b611e539fa663881f8
|
||||
- name: Install node modules
|
||||
run: yarn install --cwd aio --frozen-lockfile
|
||||
- name: Run AIO tests with local packages
|
||||
run: yarn --cwd aio test-local:ci
|
||||
- name: Check generated bundle sizes
|
||||
run: yarn --cwd aio payload-size aio-local
|
||||
|
||||
aio-deploy:
|
||||
needs: [aio]
|
||||
if: needs.aio.result == 'success' && github.event_name == 'push'
|
||||
runs-on:
|
||||
labels: ubuntu-latest
|
||||
steps:
|
||||
- name: Initialize environment
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@7bd2697b40203c84826129b611e539fa663881f8
|
||||
with:
|
||||
cache-node-modules: true
|
||||
node-module-directories: |
|
||||
./aio/node_modules
|
||||
- name: Setup Bazel
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@7bd2697b40203c84826129b611e539fa663881f8
|
||||
- name: Setup Bazel RBE
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@7bd2697b40203c84826129b611e539fa663881f8
|
||||
- name: Install node modules for aio
|
||||
run: yarn install --cwd aio --frozen-lockfile
|
||||
- name: Set the stable branch environment variable
|
||||
id: latest_version
|
||||
run: |
|
||||
echo "CI_STABLE_BRANCH=$(npm info @angular/core dist-tags.latest | sed -r 's/^\s*([0-9]+\.[0-9]+)\.[0-9]+.*$/\1.x/')" >> $GITHUB_OUTPUT
|
||||
echo "CI_BRANCH=$(echo ${{ github.event.ref }} | cut -d '/' -f 3)" >> $GITHUB_OUTPUT
|
||||
- name: Deploy aio to production
|
||||
env:
|
||||
CI_AIO_MIN_PWA_SCORE: '95'
|
||||
CI_COMMIT: ${{ github.event.after }}
|
||||
CI_REPO_NAME: 'angular'
|
||||
CI_PULL_REQUEST: 'false'
|
||||
CI_REPO_OWNER: 'angular'
|
||||
CI_BUILD_URL: '${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}'
|
||||
CI_STABLE_BRANCH: ${{ steps.latest_version.outputs.CI_STABLE_BRANCH }}
|
||||
CI_BRANCH: ${{ steps.latest_version.outputs.CI_BRANCH }}
|
||||
CI_SECRET_AIO_DEPLOY_FIREBASE_TOKEN: ${{ secrets.AIO_DEPLOY_TOKEN }}
|
||||
CI_SECRET_PAYLOAD_FIREBASE_TOKEN: ${{ secrets.AIO_PAYLOAD_TOKEN }}
|
||||
run: yarn --cwd aio deploy-production
|
||||
- name: Notify about failed deployment
|
||||
if: ${{ failure() }}
|
||||
uses: slackapi/slack-github-action@2a8087d4af6f83146a87539a70defe909fe6dbe6 # v=v1.24.0
|
||||
with:
|
||||
channel-id: 'C07DT5M6V,CKV1F72BG'
|
||||
slack-message: 'Deploy to aio job failed for ${{ github.base_ref }} branch failed on build ${{ github.event.after }}: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}'
|
||||
env:
|
||||
SLACK_BOT_TOKEN: ${{ secrets.ANGULAR_ROBOT_SLACK_TOKEN }}
|
||||
|
||||
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@c83e99a12397014162531ca125c94549db55dd84
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@7bd2697b40203c84826129b611e539fa663881f8
|
||||
with:
|
||||
cache-node-modules: true
|
||||
- name: Setup Bazel
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@c83e99a12397014162531ca125c94549db55dd84
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@7bd2697b40203c84826129b611e539fa663881f8
|
||||
- name: Setup Bazel RBE
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@c83e99a12397014162531ca125c94549db55dd84
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@7bd2697b40203c84826129b611e539fa663881f8
|
||||
- name: Install node modules
|
||||
run: yarn install --frozen-lockfile
|
||||
- run: echo "https://${{secrets.SNAPSHOT_BUILDS_GITHUB_TOKEN}}:@github.com" > ${HOME}/.git_credentials
|
||||
@@ -127,7 +215,7 @@ jobs:
|
||||
labels: ubuntu-latest-4core
|
||||
steps:
|
||||
- name: Initialize environment
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@c83e99a12397014162531ca125c94549db55dd84
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@7bd2697b40203c84826129b611e539fa663881f8
|
||||
with:
|
||||
cache-node-modules: true
|
||||
node-module-directories: |
|
||||
@@ -135,9 +223,9 @@ jobs:
|
||||
./packages/zone.js/node_modules
|
||||
./packages/zone.js/test/typings/node_modules
|
||||
- name: Setup Bazel
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@c83e99a12397014162531ca125c94549db55dd84
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@7bd2697b40203c84826129b611e539fa663881f8
|
||||
- name: Setup Bazel RBE
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@c83e99a12397014162531ca125c94549db55dd84
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@7bd2697b40203c84826129b611e539fa663881f8
|
||||
- name: Install node modules
|
||||
run: yarn install --frozen-lockfile
|
||||
- run: |
|
||||
@@ -176,7 +264,7 @@ jobs:
|
||||
JOBS: 2
|
||||
steps:
|
||||
- name: Initialize environment
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@c83e99a12397014162531ca125c94549db55dd84
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@7bd2697b40203c84826129b611e539fa663881f8
|
||||
with:
|
||||
cache-node-modules: true
|
||||
# Checking out the pull request commit is intended here as we need to run the changed code tests.
|
||||
@@ -184,9 +272,9 @@ jobs:
|
||||
- name: Install node modules
|
||||
run: yarn install --frozen-lockfile
|
||||
- name: Setup Bazel
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@c83e99a12397014162531ca125c94549db55dd84
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@7bd2697b40203c84826129b611e539fa663881f8
|
||||
- name: Setup Bazel Remote Caching
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@c83e99a12397014162531ca125c94549db55dd84
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@7bd2697b40203c84826129b611e539fa663881f8
|
||||
- name: Set up Sauce Tunnel Daemon
|
||||
run: yarn bazel run //tools/saucelabs-daemon/background-service -- $JOBS &
|
||||
env:
|
||||
@@ -197,35 +285,3 @@ jobs:
|
||||
run: |
|
||||
TESTS=$(./node_modules/.bin/bazelisk query --output label '(kind(karma_web_test, ...) intersect attr("tags", "saucelabs", ...)) except attr("tags", "fixme-saucelabs", ...)')
|
||||
yarn bazel test --config=saucelabs --jobs=$JOBS ${TESTS}
|
||||
|
||||
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@c83e99a12397014162531ca125c94549db55dd84
|
||||
- name: Setup Bazel
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@c83e99a12397014162531ca125c94549db55dd84
|
||||
- name: Setup Bazel RBE
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@c83e99a12397014162531ca125c94549db55dd84
|
||||
- name: Install node modules
|
||||
run: yarn install --frozen-lockfile
|
||||
- name: Build adev to ensure it continues to work
|
||||
run: yarn bazel build --config=aio_local_deps //adev:build
|
||||
- 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
|
||||
|
||||
@@ -13,13 +13,13 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||
- uses: angular/dev-infra/github-actions/commit-message-based-labels@c83e99a12397014162531ca125c94549db55dd84
|
||||
- uses: angular/dev-infra/github-actions/commit-message-based-labels@7bd2697b40203c84826129b611e539fa663881f8
|
||||
with:
|
||||
angular-robot-key: ${{ secrets.ANGULAR_ROBOT_PRIVATE_KEY }}
|
||||
post_approval_changes:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||
- uses: angular/dev-infra/github-actions/post-approval-changes@c83e99a12397014162531ca125c94549db55dd84
|
||||
- uses: angular/dev-infra/github-actions/post-approval-changes@7bd2697b40203c84826129b611e539fa663881f8
|
||||
with:
|
||||
angular-robot-key: ${{ secrets.ANGULAR_ROBOT_PRIVATE_KEY }}
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
name: Feature request triage bot
|
||||
|
||||
on:
|
||||
schedule:
|
||||
# Run at 14:00 every day
|
||||
- cron: '0 14 * * *'
|
||||
|
||||
# Declare default permissions as read only.
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
feature_triage:
|
||||
if: github.repository == 'angular/angular'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: angular/dev-infra/github-actions/feature-request@7bd2697b40203c84826129b611e539fa663881f8
|
||||
with:
|
||||
angular-robot-key: ${{ secrets.ANGULAR_ROBOT_PRIVATE_KEY }}
|
||||
@@ -14,7 +14,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||
- uses: angular/dev-infra/github-actions/google-internal-tests@c83e99a12397014162531ca125c94549db55dd84
|
||||
- uses: angular/dev-infra/github-actions/google-internal-tests@7bd2697b40203c84826129b611e539fa663881f8
|
||||
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@c83e99a12397014162531ca125c94549db55dd84
|
||||
- uses: angular/dev-infra/github-actions/unified-status-check@7bd2697b40203c84826129b611e539fa663881f8
|
||||
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@c83e99a12397014162531ca125c94549db55dd84
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@7bd2697b40203c84826129b611e539fa663881f8
|
||||
with:
|
||||
cache-node-modules: true
|
||||
node-module-directories: |
|
||||
./aio/node_modules
|
||||
- name: Setup Bazel
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@c83e99a12397014162531ca125c94549db55dd84
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@7bd2697b40203c84826129b611e539fa663881f8
|
||||
- name: Setup Bazel RBE
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@c83e99a12397014162531ca125c94549db55dd84
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@7bd2697b40203c84826129b611e539fa663881f8
|
||||
- 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@c83e99a12397014162531ca125c94549db55dd84
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@7bd2697b40203c84826129b611e539fa663881f8
|
||||
with:
|
||||
cache-node-modules: true
|
||||
node-module-directories: |
|
||||
|
||||
@@ -39,7 +39,7 @@ jobs:
|
||||
|
||||
# Upload the results as artifacts.
|
||||
- name: 'Upload artifact'
|
||||
uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1
|
||||
uses: actions/upload-artifact@c7d193f32edcb7bfad88892161225aeda64e9392 # v4.0.0
|
||||
with:
|
||||
name: SARIF file
|
||||
path: results.sarif
|
||||
@@ -47,6 +47,6 @@ jobs:
|
||||
|
||||
# Upload the results to GitHub's code scanning dashboard.
|
||||
- name: 'Upload to code-scanning'
|
||||
uses: github/codeql-action/upload-sarif@47b3d888fe66b639e431abf22ebca059152f1eea # v3.24.5
|
||||
uses: github/codeql-action/upload-sarif@b374143c1149a9115d881581d29b8390bbcbb59c # v3.22.11
|
||||
with:
|
||||
sarif_file: results.sarif
|
||||
|
||||
@@ -32,7 +32,7 @@ jobs:
|
||||
env:
|
||||
ANGULAR_CLI_BUILDS_READONLY_GITHUB_TOKEN: ${{ secrets.ANGULAR_CLI_BUILDS_READONLY_GITHUB_TOKEN }}
|
||||
- name: Create a PR (if necessary)
|
||||
uses: angular/dev-infra/github-actions/create-pr-for-changes@c83e99a12397014162531ca125c94549db55dd84
|
||||
uses: angular/dev-infra/github-actions/create-pr-for-changes@7bd2697b40203c84826129b611e539fa663881f8
|
||||
with:
|
||||
branch-prefix: update-cli-help
|
||||
pr-title: 'docs: update Angular CLI help [${{github.ref_name}}]'
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
# Update the `events.json` file that powers the [AIO events page](https://angular.io/events) (if
|
||||
# necessary) and create a pull request.
|
||||
#
|
||||
# For more details on the overall process, see
|
||||
# [aio/scripts/generate-events/README.md](../../aio/scripts/generate-events/README.md).
|
||||
|
||||
name: Update AIO events
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs: {}
|
||||
schedule:
|
||||
# Run every day at 15:00.
|
||||
- cron: '0 15 * * *'
|
||||
|
||||
# Declare default permissions as read only.
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
update_events:
|
||||
name: Update `events.json` (if necessary)
|
||||
if: github.repository == 'angular/angular'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout the repository
|
||||
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||
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
|
||||
# an authenticated URL.
|
||||
persist-credentials: false
|
||||
# This is needed as otherwise the PR creation will fail with `shallow update not allowed` when the forked branch is not in sync.
|
||||
fetch-depth: 0
|
||||
- name: Generate `events.json`
|
||||
run: node aio/scripts/generate-events/index.mjs --ignore-invalid-dates
|
||||
- name: Create a PR (if necessary)
|
||||
uses: angular/dev-infra/github-actions/create-pr-for-changes@7bd2697b40203c84826129b611e539fa663881f8
|
||||
with:
|
||||
branch-prefix: docs-update-events
|
||||
pr-title: 'docs: update events'
|
||||
pr-description: |
|
||||
Generated `events.json` with the latest events retrieved from the Firebase DB.
|
||||
pr-labels: |
|
||||
action: review
|
||||
area: docs
|
||||
target: patch
|
||||
angular-robot-token: ${{ secrets.ANGULAR_ROBOT_ACCESS_TOKEN }}
|
||||
@@ -7,7 +7,6 @@ export const format: FormatConfig = {
|
||||
'prettier': {
|
||||
'matchers': [
|
||||
'**/*.{yaml,yml}',
|
||||
'adev/**/*.{js,ts}',
|
||||
'devtools/**/*.{js,ts}',
|
||||
'tools/**/*.{js,ts}',
|
||||
'modules/**/*.{js,ts}',
|
||||
|
||||
@@ -1,12 +1,5 @@
|
||||
{
|
||||
"syncedFilePatterns": [
|
||||
"LICENSE",
|
||||
"modules/benchmarks/**",
|
||||
"packages/**"
|
||||
],
|
||||
"separateFilePatterns": [
|
||||
"packages/core/primitives/**"
|
||||
],
|
||||
"syncedFilePatterns": ["LICENSE", "modules/benchmarks/**", "packages/**"],
|
||||
"alwaysExternalFilePatterns": [
|
||||
"packages/*",
|
||||
"packages/bazel/*",
|
||||
@@ -29,6 +22,7 @@
|
||||
"packages/docs/**",
|
||||
"packages/elements/schematics/**",
|
||||
"packages/examples/**",
|
||||
"packages/language-service/**",
|
||||
"packages/localize/**",
|
||||
"packages/private/**",
|
||||
"packages/service-worker/**",
|
||||
|
||||
@@ -17,9 +17,4 @@ export const pullRequest: PullRequestConfig = {
|
||||
// the `bazel` package is not considered part of the public API so that features
|
||||
// can land in patch branches.
|
||||
targetLabelExemptScopes: ['dev-infra', 'docs-infra', 'bazel'],
|
||||
// enables specific validations during the pull request merge process
|
||||
validators: {
|
||||
assertEnforceTested: true,
|
||||
assertIsolatedSeparateFiles: true,
|
||||
},
|
||||
};
|
||||
|
||||
+25
-9
@@ -322,9 +322,7 @@ groups:
|
||||
'aio/content/guide/providers.md',
|
||||
'aio/content/examples/providers/**/{*,.*}',
|
||||
'aio/content/images/guide/providers/**/{*,.*}',
|
||||
'aio/content/guide/signal-queries.md',
|
||||
'aio/content/guide/singleton-services.md',
|
||||
'aio/content/guide/signal-inputs.md',
|
||||
'aio/content/guide/sharing-ngmodules.md',
|
||||
'aio/content/guide/standalone-components.md',
|
||||
'aio/content/guide/standalone-migration.md',
|
||||
@@ -347,7 +345,6 @@ groups:
|
||||
'aio/content/guide/defer.md',
|
||||
'aio/content/guide/signals.md',
|
||||
'aio/content/guide/control_flow.md',
|
||||
'aio/content/guide/model-inputs.md',
|
||||
'aio/content/examples/injection-token/**/{*,.*}',
|
||||
])
|
||||
reviewers:
|
||||
@@ -939,7 +936,6 @@ groups:
|
||||
'aio/content/guide/update-to-version-14.md',
|
||||
'aio/content/guide/update-to-version-15.md',
|
||||
'aio/content/guide/update-to-version-16.md',
|
||||
'aio/content/guide/update-to-version-17.md',
|
||||
])
|
||||
reviewers:
|
||||
users:
|
||||
@@ -1085,7 +1081,6 @@ groups:
|
||||
- crisbeto
|
||||
- atscott
|
||||
- pkozlowski-opensource
|
||||
- ~JeanMeche
|
||||
|
||||
# =========================================================
|
||||
# Docs-infra
|
||||
@@ -1164,7 +1159,7 @@ groups:
|
||||
- josephperrott
|
||||
- mgechev
|
||||
- twerske
|
||||
- ~JeanMeche
|
||||
- ~jeanmeche
|
||||
|
||||
# =========================================================
|
||||
# Dev-infra
|
||||
@@ -1397,6 +1392,27 @@ groups:
|
||||
author_value: 0 # The author of the PR cannot provide an approval for themself
|
||||
reviewed_for: ignored # All reviews apply to this group whether noted via Reviewed-for or not
|
||||
reviewers:
|
||||
teams:
|
||||
# Any member of the team can provide a review to perform the minimum review required
|
||||
- team
|
||||
users:
|
||||
- alan-agius4 # Alan Agius
|
||||
- AleksanderBodurri # Aleksander Bodurri
|
||||
- alxhub # Alex Rickabaugh
|
||||
- AndrewKushnir # Andrew Kushnir
|
||||
- andrewseguin # Andrew Seguin
|
||||
- atscott # Andrew Scott
|
||||
- clydin # Charles Lyding
|
||||
- crisbeto # Kristiyan Kostadinov
|
||||
- devversion # Paul Gschwendtner
|
||||
- dgp1130 # Doug Parker
|
||||
- dylhunn # Dylan Hunn
|
||||
- jeanmeche # Matthieu Riegler
|
||||
- jelbourn # Jeremy Elbourn
|
||||
- jessicajaniuk # Jessica Janiuk
|
||||
- JiaLiPassion # Jia Li
|
||||
- JoostK # Joost Koehoorn
|
||||
- josephperrott # Joey Perrott
|
||||
- MarkTechson # Mark Thompson (Techson)
|
||||
- mgechev # Minko Gechev
|
||||
- mmalerba # Miles Malerba
|
||||
- pkozlowski-opensource # Pawel Kozlowski
|
||||
- twerske # Emma Twersky
|
||||
- zarend # Zach Arend
|
||||
|
||||
Vendored
+5
@@ -5,7 +5,12 @@
|
||||
// List of extensions which should be recommended for users of this workspace.
|
||||
"recommendations": [
|
||||
"BazelBuild.vscode-bazel",
|
||||
"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",
|
||||
//"errata-ai.vale-server",
|
||||
],
|
||||
}
|
||||
|
||||
-192
@@ -1,195 +1,3 @@
|
||||
<a name="17.3.0-rc.0"></a>
|
||||
# 17.3.0-rc.0 (2024-03-06)
|
||||
### compiler-cli
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [5afa4f0ec1](https://github.com/angular/angular/commit/5afa4f0ec1b64b88ef875d48bd143e0f36e0a955) | fix | support `ModuleWithProviders` literal detection with `typeof` ([#54650](https://github.com/angular/angular/pull/54650)) |
|
||||
| [ae7dbe42de](https://github.com/angular/angular/commit/ae7dbe42de5779e578fb636938699b8ba85b33f6) | fix | unwrap expressions with type parameters in query read property ([#54647](https://github.com/angular/angular/pull/54647)) |
|
||||
### core
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [fb540e169a](https://github.com/angular/angular/commit/fb540e169a78a61f38d611f538eea8fdb0971f1d) | feat | add migration for invalid two-way bindings ([#54630](https://github.com/angular/angular/pull/54630)) |
|
||||
| [c687b8f453](https://github.com/angular/angular/commit/c687b8f4531252cd1c3dfbb9a7bd42bdbe666a36) | feat | expose new `output()` API ([#54650](https://github.com/angular/angular/pull/54650)) |
|
||||
| [c809069f21](https://github.com/angular/angular/commit/c809069f213244afd0e2d803a6a43510b218e6f5) | feat | introduce `outputFromObservable()` interop function ([#54650](https://github.com/angular/angular/pull/54650)) |
|
||||
| [aff65fd1f4](https://github.com/angular/angular/commit/aff65fd1f4a61ed76a6f9b623852f197eb3500e4) | feat | introduce `outputToObservable` interop helper ([#54650](https://github.com/angular/angular/pull/54650)) |
|
||||
| [33a6fab094](https://github.com/angular/angular/commit/33a6fab094205cc74aceb916e45c8afa22293cf4) | fix | apply TestBed provider overrides to `@defer` dependencies ([#54667](https://github.com/angular/angular/pull/54667)) |
|
||||
| [78e69117f0](https://github.com/angular/angular/commit/78e69117f07e8ae11aab3abe0343df815b3649be) | fix | generic inference for signal inputs may break with `--strictFunctionTypes` ([#54652](https://github.com/angular/angular/pull/54652)) |
|
||||
| [7243c704cf](https://github.com/angular/angular/commit/7243c704cf8a4986fae419793027458e142658f0) | fix | return a readonly signal on `asReadonly`. ([#54706](https://github.com/angular/angular/pull/54706)) |
|
||||
| [ffad7b8ea9](https://github.com/angular/angular/commit/ffad7b8ea9d1286ddb3ee7a2dac7dd33fd76b3aa) | fix | untrack various core operations ([#54614](https://github.com/angular/angular/pull/54614)) |
|
||||
### router
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [c1c7384e02](https://github.com/angular/angular/commit/c1c7384e02becc623c6a42985f7178ca98137264) | feat | Add reusable types for router guards ([#54580](https://github.com/angular/angular/pull/54580)) |
|
||||
|
||||
<!-- CHANGELOG SPLIT MARKER -->
|
||||
|
||||
<a name="17.2.4"></a>
|
||||
# 17.2.4 (2024-03-06)
|
||||
### compiler-cli
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [917b9bdd2e](https://github.com/angular/angular/commit/917b9bdd2eec9cd6395adb0cbde979490bcc3a88) | fix | unwrap expressions with type parameters in query read property ([#54647](https://github.com/angular/angular/pull/54647)) |
|
||||
### core
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [586cc24a10](https://github.com/angular/angular/commit/586cc24a102d51e7c42ecc839124353afe459073) | fix | apply TestBed provider overrides to `@defer` dependencies ([#54667](https://github.com/angular/angular/pull/54667)) |
|
||||
| [b558a01c84](https://github.com/angular/angular/commit/b558a01c84a1fe4f9e047e92d99d69f02f2ccc4f) | fix | generic inference for signal inputs may break with `--strictFunctionTypes` ([#54652](https://github.com/angular/angular/pull/54652)) |
|
||||
| [443e5f1591](https://github.com/angular/angular/commit/443e5f1591896909eab6ad157bb4b5db50a49f26) | fix | return a readonly signal on `asReadonly`. ([#54719](https://github.com/angular/angular/pull/54719)) |
|
||||
| [ffbafc7d4a](https://github.com/angular/angular/commit/ffbafc7d4a3c61226cb5c524b3ff42ed4f9f1d76) | fix | untrack various core operations ([#54614](https://github.com/angular/angular/pull/54614)) |
|
||||
|
||||
<!-- CHANGELOG SPLIT MARKER -->
|
||||
|
||||
<a name="17.2.3"></a>
|
||||
# 17.2.3 (2024-02-27)
|
||||
### common
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [1a526f2881](https://github.com/angular/angular/commit/1a526f28814429c05b003edbe40500b7069e1404) | perf | `AsyncPipe` should not call `markForCheck` on subscription ([#54554](https://github.com/angular/angular/pull/54554)) |
|
||||
### compiler-cli
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [2aefed8763](https://github.com/angular/angular/commit/2aefed87632293c5570e17653a7d270162ea35c3) | fix | catch function instance properties in interpolated signal diagnostic ([#54325](https://github.com/angular/angular/pull/54325)) |
|
||||
| [48aec63ee4](https://github.com/angular/angular/commit/48aec63ee48fe9d20c1f1565b044ec359100736d) | fix | identify aliased initializer functions ([#54480](https://github.com/angular/angular/pull/54480)) |
|
||||
| [daf7c611b2](https://github.com/angular/angular/commit/daf7c611b23797ba2dba24e20e26c344902e7bc7) | fix | identify aliased initializer functions ([#54609](https://github.com/angular/angular/pull/54609)) |
|
||||
### core
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [57123524a2](https://github.com/angular/angular/commit/57123524a2e1481987eaf239d2ae7f1216291864) | fix | collect providers from NgModules while rendering `@defer` block ([#52881](https://github.com/angular/angular/pull/52881)) |
|
||||
| [79a32816dc](https://github.com/angular/angular/commit/79a32816dcb57e9b02ba6e18261c6dccd14b0b23) | fix | fix typo in injectors.svg file ([#54596](https://github.com/angular/angular/pull/54596)) |
|
||||
### migrations
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [dbe673b027](https://github.com/angular/angular/commit/dbe673b02751b8bf12f6f6f79b843dd268d965e1) | fix | resolve infinite loop for a single line element with a long tag name and angle bracket on a new line ([#54588](https://github.com/angular/angular/pull/54588)) |
|
||||
|
||||
<!-- CHANGELOG SPLIT MARKER -->
|
||||
|
||||
<a name="17.3.0-next.1"></a>
|
||||
# 17.3.0-next.1 (2024-02-27)
|
||||
### common
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [707bfc9b32](https://github.com/angular/angular/commit/707bfc9b326f321f302dccfdfebef7380c914eb5) | perf | `AsyncPipe` should not call `markForCheck` on subscription ([#54554](https://github.com/angular/angular/pull/54554)) |
|
||||
### compiler
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [1a6beae8a2](https://github.com/angular/angular/commit/1a6beae8a2bdcff27d4c1e402f98246a52247906) | feat | Enable template pipeline by default. ([#54571](https://github.com/angular/angular/pull/54571)) |
|
||||
### compiler-cli
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [12dc4d074e](https://github.com/angular/angular/commit/12dc4d074e63edaff626003ad6136a8d122b2ba6) | fix | account for as expression in docs extraction ([#54414](https://github.com/angular/angular/pull/54414)) |
|
||||
| [f578889ca2](https://github.com/angular/angular/commit/f578889ca2dbb22f2e050d4c4af6aa2d597bf70d) | fix | catch function instance properties in interpolated signal diagnostic ([#54325](https://github.com/angular/angular/pull/54325)) |
|
||||
| [f04ecc0cda](https://github.com/angular/angular/commit/f04ecc0cdaeb3a292a748b1ccc94ce70a573fc79) | fix | identify aliased initializer functions ([#54480](https://github.com/angular/angular/pull/54480)) |
|
||||
| [f5c566c079](https://github.com/angular/angular/commit/f5c566c0793eacf9ca146c8a6b8da15b0e8f4c4d) | fix | identify aliased initializer functions ([#54609](https://github.com/angular/angular/pull/54609)) |
|
||||
### core
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [331b16efd2](https://github.com/angular/angular/commit/331b16efd2f5af876e6dc0ad2474ee7a87b00de5) | feat | add API to inject attributes on the host node ([#54604](https://github.com/angular/angular/pull/54604)) |
|
||||
| [974958913c](https://github.com/angular/angular/commit/974958913ca632971f878a045537472f2c99c665) | feat | support TypeScript 5.4 ([#54414](https://github.com/angular/angular/pull/54414)) |
|
||||
| [dcb9deb363](https://github.com/angular/angular/commit/dcb9deb3631ff4e839f738f2fb97ca6b894256ef) | fix | collect providers from NgModules while rendering `@defer` block ([#52881](https://github.com/angular/angular/pull/52881)) |
|
||||
| [ff40c9f762](https://github.com/angular/angular/commit/ff40c9f762b2c14870ac2859201ece9660087e79) | fix | fix typo in injectors.svg file ([#54596](https://github.com/angular/angular/pull/54596)) |
|
||||
### migrations
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [71e0c7df69](https://github.com/angular/angular/commit/71e0c7df695f274a54d5ffb9dfc0587d06d3a953) | fix | resolve infinite loop for a single line element with a long tag name and angle bracket on a new line ([#54588](https://github.com/angular/angular/pull/54588)) |
|
||||
|
||||
<!-- CHANGELOG SPLIT MARKER -->
|
||||
|
||||
<a name="17.3.0-next.0"></a>
|
||||
# 17.3.0-next.0 (2024-02-21)
|
||||
### compiler-cli
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [da7fbb40f0](https://github.com/angular/angular/commit/da7fbb40f06e6e37504f69e7b335f8219f424de2) | fix | detect when the linker is working in unpublished angular and widen supported versions ([#54439](https://github.com/angular/angular/pull/54439)) |
|
||||
|
||||
<!-- CHANGELOG SPLIT MARKER -->
|
||||
|
||||
<a name="17.2.2"></a>
|
||||
# 17.2.2 (2024-02-21)
|
||||
### common
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [d34e3298db](https://github.com/angular/angular/commit/d34e3298db2da9d3aa8f8e2c49e1d1aed849e7d2) | fix | image placeholder not removed in OnPush component ([#54515](https://github.com/angular/angular/pull/54515)) |
|
||||
### compiler
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [6447c0eecc](https://github.com/angular/angular/commit/6447c0eecc15800417bb5bde2d273865c559b7d4) | fix | adding the inert property to the "SCHEMA" array ([#53148](https://github.com/angular/angular/pull/53148)) |
|
||||
### compiler-cli
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [0a3edfb543](https://github.com/angular/angular/commit/0a3edfb5433e1b67f37d7b3f69a5c77b48eedce6) | fix | correctly detect deferred dependencies across scoped nodes ([#54499](https://github.com/angular/angular/pull/54499)) |
|
||||
| [790f4f7c26](https://github.com/angular/angular/commit/790f4f7c26474af2b1d81a4f3c761047bf920edf) | fix | use correct symbol name for default imported symbols in defer blocks ([#54495](https://github.com/angular/angular/pull/54495)) |
|
||||
### core
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [3bd5860c74](https://github.com/angular/angular/commit/3bd5860c749517c4d35850703c53a768138b7bde) | fix | properly execute content queries for root components ([#54457](https://github.com/angular/angular/pull/54457)) |
|
||||
### migrations
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [bb57d34110](https://github.com/angular/angular/commit/bb57d34110ea8796f5c5f522b98dadf078e8ad8a) | fix | Fix cf migration regular expression to include underscores ([#54533](https://github.com/angular/angular/pull/54533)) |
|
||||
### router
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [3e31f1a34e](https://github.com/angular/angular/commit/3e31f1a34ee97a802a4032cd9e7816d683a51d5e) | fix | Clear internal transition when navigation finalizes ([#54261](https://github.com/angular/angular/pull/54261)) |
|
||||
|
||||
<!-- CHANGELOG SPLIT MARKER -->
|
||||
|
||||
<a name="17.2.1"></a>
|
||||
# 17.2.1 (2024-02-14)
|
||||
### compiler-cli
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [7234824228](https://github.com/angular/angular/commit/7234824228df11249a5ebe01a6dee381be74e02e) | fix | fix broken version detection condition ([#54443](https://github.com/angular/angular/pull/54443)) |
|
||||
|
||||
<!-- CHANGELOG SPLIT MARKER -->
|
||||
|
||||
<a name="17.2.0"></a>
|
||||
# 17.2.0 (2024-02-14)
|
||||
### common
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [03c3b3eb79](https://github.com/angular/angular/commit/03c3b3eb79ec061b0031d6ad7ba386d185c87d8d) | feat | add Netlify image loader ([#54311](https://github.com/angular/angular/pull/54311)) |
|
||||
| [f5c520b836](https://github.com/angular/angular/commit/f5c520b836c4545c7043649f28b3a0369c168747) | feat | add placeholder to NgOptimizedImage ([#53783](https://github.com/angular/angular/pull/53783)) |
|
||||
### compiler
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [47e6e84101](https://github.com/angular/angular/commit/47e6e841016abfca0c1aa84051d82a04b3027617) | feat | Add a TSConfig option `useTemplatePipeline` ([#54057](https://github.com/angular/angular/pull/54057)) |
|
||||
| [66e940aebf](https://github.com/angular/angular/commit/66e940aebfd5a93944860a4e0dbd14e1072f80f2) | feat | scope selectors in @starting-style ([#53943](https://github.com/angular/angular/pull/53943)) |
|
||||
| [7b4d275f49](https://github.com/angular/angular/commit/7b4d275f494a64c38b61cea7045ba8b6e8447b78) | fix | Fix the template pipeline option ([#54148](https://github.com/angular/angular/pull/54148)) |
|
||||
### compiler-cli
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [7e861c640e](https://github.com/angular/angular/commit/7e861c640edf90c5f8d4f7e091861d3d98cd49c0) | feat | generate extra imports for component local dependencies in local mode ([#53543](https://github.com/angular/angular/pull/53543)) |
|
||||
| [3263df23f2](https://github.com/angular/angular/commit/3263df23f2f4da722ef2c1a1dacfb0866498dd60) | feat | generate global imports in local compilation mode ([#53543](https://github.com/angular/angular/pull/53543)) |
|
||||
| [b774e22d8e](https://github.com/angular/angular/commit/b774e22d8e384f43e9cd8f5c55475d06e7f66988) | feat | make it configurable to generate alias reexports ([#53937](https://github.com/angular/angular/pull/53937)) |
|
||||
| [3e1384048e](https://github.com/angular/angular/commit/3e1384048eb76c92532ae19aa2883318121c00e8) | feat | support host directives for local compilation mode ([#53877](https://github.com/angular/angular/pull/53877)) |
|
||||
| [a592904c69](https://github.com/angular/angular/commit/a592904c691844d2c1aed00bd914edabef49f9b1) | fix | allow custom/duplicate decorators for `@Injectable` classes in local compilation mode ([#54139](https://github.com/angular/angular/pull/54139)) |
|
||||
| [4b1d948b36](https://github.com/angular/angular/commit/4b1d948b36285ec6d80dbe93e0b92133f9d4be94) | fix | consider the case of duplicate Angular decorators in local compilation diagnostics ([#54139](https://github.com/angular/angular/pull/54139)) |
|
||||
| [96bcf4fb12](https://github.com/angular/angular/commit/96bcf4fb1208d1f073784a2cde4a03553e905807) | fix | forbid custom/duplicate decorator when option `forbidOrphanComponents` is set ([#54139](https://github.com/angular/angular/pull/54139)) |
|
||||
| [64fa5715c6](https://github.com/angular/angular/commit/64fa5715c696101fba0b4f8623eaec0eadc5b159) | fix | generating extra imports in local compilation mode when cycle is introduced ([#53543](https://github.com/angular/angular/pull/53543)) |
|
||||
| [6c8b09468a](https://github.com/angular/angular/commit/6c8b09468a05a80cba3960861f0ab8d3bae80415) | fix | highlight the unresolved element in the @Component.styles array for the error LOCAL_COMPILATION_UNRESOLVED_CONST ([#54230](https://github.com/angular/angular/pull/54230)) |
|
||||
| [0970129e20](https://github.com/angular/angular/commit/0970129e20f77dc309f2b4f76f961b310124778c) | fix | show proper error for custom decorators in local compilation mode ([#53983](https://github.com/angular/angular/pull/53983)) |
|
||||
| [f39cb06418](https://github.com/angular/angular/commit/f39cb064183d984254bdf4e41b61d3dc9379738a) | fix | show specific error for unresolved @Directive.exportAs in local compilation mode ([#54230](https://github.com/angular/angular/pull/54230)) |
|
||||
| [f3851b5945](https://github.com/angular/angular/commit/f3851b59459a1d9c214ace3db5a716d51c1f93c7) | fix | show specific error for unresolved @HostBinding's argument in local compilation mode ([#54230](https://github.com/angular/angular/pull/54230)) |
|
||||
| [39ddd884e8](https://github.com/angular/angular/commit/39ddd884e826cc0be63fd0f7d7de20d642877ef9) | fix | show specific error for unresolved @HostListener's event name in local compilation mode ([#54230](https://github.com/angular/angular/pull/54230)) |
|
||||
| [5d633240fd](https://github.com/angular/angular/commit/5d633240fd5927c4318a9240e60c3a30b2333cee) | fix | show the correct message for the error LOCAL_COMPILATION_UNRESOLVED_CONST when an unresolved symbol used for @Component.styles ([#54230](https://github.com/angular/angular/pull/54230)) |
|
||||
| [58b8a232d6](https://github.com/angular/angular/commit/58b8a232d64f5fe3207c90c8145cab36e7e192c2) | fix | support jumping to definitions of signal-based inputs ([#54053](https://github.com/angular/angular/pull/54053)) |
|
||||
### core
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [702ab28b4c](https://github.com/angular/angular/commit/702ab28b4c07a903c403a20af2ca287348b6afd0) | feat | add support for model inputs ([#54252](https://github.com/angular/angular/pull/54252)) |
|
||||
| [e95ef2cbc6](https://github.com/angular/angular/commit/e95ef2cbc6f850d8fe96218b74cff76cea947674) | feat | expose queries as signals ([#54283](https://github.com/angular/angular/pull/54283)) |
|
||||
| [656bc282e3](https://github.com/angular/angular/commit/656bc282e345c5e37a9189a0a4daa631e02c31bf) | fix | add toString implementation to signals ([#54002](https://github.com/angular/angular/pull/54002)) |
|
||||
| [62b87b4551](https://github.com/angular/angular/commit/62b87b4551d77815f58af152d1921de3733621ba) | fix | do not crash for signal query that does not have any matches ([#54353](https://github.com/angular/angular/pull/54353)) |
|
||||
| [4b96f370ee](https://github.com/angular/angular/commit/4b96f370eea08d2531cc54f65a651f94b504692d) | fix | expose model signal subcribe for type checking purposes ([#54357](https://github.com/angular/angular/pull/54357)) |
|
||||
| [744cb1e561](https://github.com/angular/angular/commit/744cb1e561d9b0cbffd20ad612d5ea9e2cf6b2e7) | fix | return the same children query results if there are no changes ([#54392](https://github.com/angular/angular/pull/54392)) |
|
||||
| [6d00115bf4](https://github.com/angular/angular/commit/6d00115bf45731b80178e11921df6ec1dc89efaf) | fix | show placeholder block on the server with immediate trigger ([#54394](https://github.com/angular/angular/pull/54394)) |
|
||||
### http
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [1c536250b6](https://github.com/angular/angular/commit/1c536250b6112c67a68159de50c4afe0dbc440f8) | fix | Use string body to generate transfer cache key. ([#54379](https://github.com/angular/angular/pull/54379)) |
|
||||
|
||||
<!-- CHANGELOG SPLIT MARKER -->
|
||||
|
||||
<a name="17.1.3"></a>
|
||||
# 17.1.3 (2024-02-08)
|
||||
### compiler-cli
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
The MIT License
|
||||
|
||||
Copyright (c) 2010-2024 Google LLC. https://angular.io/license
|
||||
Copyright (c) 2010-2023 Google LLC. https://angular.io/license
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
||||
@@ -64,17 +64,7 @@ load("@rules_nodejs//nodejs:repositories.bzl", "nodejs_register_toolchains")
|
||||
|
||||
nodejs_register_toolchains(
|
||||
name = "nodejs",
|
||||
node_repositories = {
|
||||
"18.18.2-darwin_arm64": ("node-v18.18.2-darwin-arm64.tar.gz", "node-v18.18.2-darwin-arm64", "9f982cc91b28778dd8638e4f94563b0c2a1da7aba62beb72bd427721035ab553"),
|
||||
"18.18.2-darwin_amd64": ("node-v18.18.2-darwin-x64.tar.gz", "node-v18.18.2-darwin-x64", "5bb8da908ed590e256a69bf2862238c8a67bc4600119f2f7721ca18a7c810c0f"),
|
||||
"18.18.2-linux_arm64": ("node-v18.18.2-linux-arm64.tar.xz", "node-v18.18.2-linux-arm64", "2e630e18548627f61eaf573233da7949dc0a1df5eef3f486fa9820c5f6c121aa"),
|
||||
"18.18.2-linux_ppc64le": ("node-v18.18.2-linux-ppc64le.tar.xz", "node-v18.18.2-linux-ppc64le", "b0adff5cf5938266b711d6c724fb134d802e0dee40b3a3f73d162de1b3d11880"),
|
||||
"18.18.2-linux_s390x": ("node-v18.18.2-linux-s390x.tar.xz", "node-v18.18.2-linux-s390x", "c70ec2074b5e2b42c55bb4b8105418b67bf8a61c500d9376a07430dfcc341fdb"),
|
||||
"18.18.2-linux_amd64": ("node-v18.18.2-linux-x64.tar.xz", "node-v18.18.2-linux-x64", "75aba25ae76999309fc6c598efe56ce53fbfc221381a44a840864276264ab8ac"),
|
||||
"18.18.2-windows_amd64": ("node-v18.18.2-win-x64.zip", "node-v18.18.2-win-x64", "3bb0e51e579a41a22b3bf6cb2f3e79c03801aa17acbe0ca00fc555d1282e7acd"),
|
||||
},
|
||||
# We need at least Node 18.17 due to some transitive dependencies.
|
||||
node_version = "18.18.2",
|
||||
node_version = "18.13.0",
|
||||
)
|
||||
|
||||
# Download npm dependencies.
|
||||
@@ -206,10 +196,10 @@ cldr_xml_data_repository(
|
||||
# sass rules
|
||||
http_archive(
|
||||
name = "io_bazel_rules_sass",
|
||||
sha256 = "974f5e14c54d300ca63a67910ebfcea6d838d8811fefc61184f2872107152705",
|
||||
strip_prefix = "rules_sass-888dd9c02709e228b60a8b3e51e2b16b2ca5279d",
|
||||
sha256 = "386ef2b97bd4342e45db450ce7eb5acb343d22f7580c0bbae89186dc009fa14e",
|
||||
strip_prefix = "rules_sass-d970cb53ad159e7fd1d3a99a00a530cc2c83eff9",
|
||||
urls = [
|
||||
"https://github.com/bazelbuild/rules_sass/archive/888dd9c02709e228b60a8b3e51e2b16b2ca5279d.zip",
|
||||
"https://github.com/bazelbuild/rules_sass/archive/d970cb53ad159e7fd1d3a99a00a530cc2c83eff9.zip",
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
load("//:packages.bzl", "link_packages")
|
||||
load("@aio_npm//@angular/build-tooling/bazel/remote-execution:index.bzl", "ENABLE_NETWORK")
|
||||
load("@bazel_skylib//rules:common_settings.bzl", "bool_flag")
|
||||
load("@build_bazel_rules_nodejs//:index.bzl", "copy_to_bin")
|
||||
load("@npm//@angular-devkit/architect-cli:index.bzl", "architect", "architect_test")
|
||||
@@ -132,12 +131,7 @@ architect(
|
||||
data = APPLICATION_DEPS + [
|
||||
":application_files_bin",
|
||||
],
|
||||
# Network is required to inline fonts.
|
||||
exec_properties = ENABLE_NETWORK,
|
||||
output_dir = True,
|
||||
tags = [
|
||||
"no-remote-exec",
|
||||
],
|
||||
)
|
||||
|
||||
architect(
|
||||
@@ -152,9 +146,6 @@ architect(
|
||||
data = APPLICATION_DEPS + [
|
||||
":application_files_bin",
|
||||
],
|
||||
tags = [
|
||||
"no-remote-exec",
|
||||
],
|
||||
)
|
||||
|
||||
architect_test(
|
||||
|
||||
+7
-11
@@ -21,7 +21,7 @@ module.exports = function (config) {
|
||||
'/dummy/image': 'src/assets/images/logos/angular/angular.png',
|
||||
},
|
||||
client: {
|
||||
clearContext: false, // leave Jasmine Spec Runner output visible in browser
|
||||
clearContext: false, // leave Jasmine Spec Runner output visible in browser
|
||||
jasmine: {
|
||||
// you can add configuration options for Jasmine here
|
||||
// the possible options are listed at https://jasmine.github.io/api/edge/Configuration.html
|
||||
@@ -32,12 +32,15 @@ module.exports = function (config) {
|
||||
},
|
||||
},
|
||||
jasmineHtmlReporter: {
|
||||
suppressAll: true, // removes the duplicated traces
|
||||
suppressAll: true // removes the duplicated traces
|
||||
},
|
||||
coverageReporter: {
|
||||
dir: require('path').join(__dirname, './coverage/site'),
|
||||
subdir: '.',
|
||||
reporters: [{type: 'html'}, {type: 'text-summary'}],
|
||||
reporters: [
|
||||
{ type: 'html' },
|
||||
{ type: 'text-summary' }
|
||||
],
|
||||
},
|
||||
reporters: ['progress', 'kjhtml', 'jasmine-seed'],
|
||||
port: 9876,
|
||||
@@ -48,14 +51,7 @@ module.exports = function (config) {
|
||||
ChromeHeadlessNoSandbox: {
|
||||
base: 'ChromeHeadless',
|
||||
// See /integration/README.md#browser-tests for more info on these args
|
||||
flags: [
|
||||
'--no-sandbox',
|
||||
'--headless',
|
||||
'--disable-gpu',
|
||||
'--disable-dev-shm-usage',
|
||||
'--hide-scrollbars',
|
||||
'--mute-audio',
|
||||
],
|
||||
flags: ['--no-sandbox', '--headless', '--disable-gpu', '--disable-dev-shm-usage', '--hide-scrollbars', '--mute-audio'],
|
||||
},
|
||||
},
|
||||
browsers: ['ChromeHeadlessNoSandbox'],
|
||||
|
||||
@@ -0,0 +1,135 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright Google LLC All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.dev/license
|
||||
*/
|
||||
|
||||
import {input, confirm} from '@inquirer/prompts';
|
||||
import {existsSync} from 'fs';
|
||||
import {copyFile, mkdir, writeFile} from 'fs/promises';
|
||||
import {dirname, join} from 'path';
|
||||
import {fileURLToPath} from 'url';
|
||||
import {copyFolder} from './utils/fs.mjs';
|
||||
|
||||
const EXAMPLES_BASE = join(
|
||||
dirname(fileURLToPath(import.meta.url)),
|
||||
'../../src/content/examples',
|
||||
);
|
||||
const STACKBLITZ_TEMPLATE = join(
|
||||
dirname(fileURLToPath(import.meta.url)),
|
||||
'../../src/content/stackblitz-template',
|
||||
);
|
||||
|
||||
const MAIN_TS_FILENAME = 'main.ts';
|
||||
const STACKBLITZ_CONFIG_FILENAME = 'stackblitz.json';
|
||||
|
||||
createExample();
|
||||
|
||||
async function createExample(): Promise<void> {
|
||||
console.info('Create example');
|
||||
const name = await input({message: 'What is the example name?'});
|
||||
const description = await input({message: 'Could you provide the description of the example?'});
|
||||
const tags: string[] = [];
|
||||
let addMoreTags = true;
|
||||
|
||||
while (addMoreTags) {
|
||||
const tag = await input({message: 'Could you provide the tag of the example?'});
|
||||
|
||||
if (!!tag) {
|
||||
tags.push(tag);
|
||||
}
|
||||
|
||||
console.info();
|
||||
|
||||
addMoreTags = await confirm({message: 'Would you like to add another tag?', default: true});
|
||||
}
|
||||
|
||||
const shouldCopySrcAppFolderFromTemplate = await confirm({
|
||||
message: 'Would you like to copy /src/app/**.* from template?',
|
||||
default: true,
|
||||
});
|
||||
const shouldCopyMainTsFromTemplate = await confirm({
|
||||
message: 'Would you like to copy /src/main.ts from template?',
|
||||
default: false,
|
||||
});
|
||||
|
||||
const examplePath = join(EXAMPLES_BASE, name);
|
||||
|
||||
await createEmptyExample(examplePath, name, description, tags);
|
||||
await copyFilesFromTemplate(
|
||||
examplePath,
|
||||
shouldCopySrcAppFolderFromTemplate,
|
||||
shouldCopyMainTsFromTemplate,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the directory and marker files for the new example.
|
||||
*/
|
||||
async function createEmptyExample(
|
||||
examplePath: string,
|
||||
exampleName: string,
|
||||
description: string,
|
||||
tags: string[],
|
||||
): Promise<void> {
|
||||
validateExampleName(exampleName);
|
||||
|
||||
await ensureExamplePath(examplePath);
|
||||
await writeStackBlitzFile(examplePath, description, tags);
|
||||
}
|
||||
|
||||
function validateExampleName(exampleName: string): void {
|
||||
if (/\s/.test(exampleName)) {
|
||||
throw new Error(`Unable to create example. The example name contains spaces: '${exampleName}'`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure that the new example directory exists.
|
||||
*/
|
||||
async function ensureExamplePath(examplePath: string): Promise<void> {
|
||||
if (existsSync(examplePath)) {
|
||||
throw new Error(
|
||||
`Unable to create example. The path to the new example already exists: ${examplePath}`,
|
||||
);
|
||||
}
|
||||
await mkdir(examplePath);
|
||||
}
|
||||
|
||||
/**
|
||||
* Write the `stackblitz.json` file into the new example.
|
||||
*/
|
||||
async function writeStackBlitzFile(
|
||||
examplePath: string,
|
||||
description: string,
|
||||
tags: string[],
|
||||
): Promise<void> {
|
||||
const config = {
|
||||
description,
|
||||
ignore: ['**/*.d.ts', '**/*.js', '**/*.[1,2].*'],
|
||||
tags,
|
||||
};
|
||||
await writeFile(
|
||||
join(examplePath, STACKBLITZ_CONFIG_FILENAME),
|
||||
JSON.stringify(config, null, 2) + '\n',
|
||||
);
|
||||
}
|
||||
|
||||
async function copyFilesFromTemplate(
|
||||
examplePath: string,
|
||||
shouldCopySrcAppFolderFromTemplate: boolean,
|
||||
shouldCopyMainTsFromTemplate: boolean,
|
||||
): Promise<void> {
|
||||
if (shouldCopySrcAppFolderFromTemplate) {
|
||||
const appTemplateFolderPath = join(STACKBLITZ_TEMPLATE, 'src/app');
|
||||
const exampleAppFolderPath = join(examplePath, 'src/app');
|
||||
await copyFolder(appTemplateFolderPath, exampleAppFolderPath);
|
||||
}
|
||||
if (shouldCopyMainTsFromTemplate) {
|
||||
const mainTsTemplatePath = join(STACKBLITZ_TEMPLATE, 'src', MAIN_TS_FILENAME);
|
||||
const exampleMainTsPath = join(examplePath, 'src', MAIN_TS_FILENAME);
|
||||
await copyFile(mainTsTemplatePath, exampleMainTsPath);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,158 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright Google LLC All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.dev/license
|
||||
*/
|
||||
|
||||
import ts from 'typescript';
|
||||
import * as fs from 'fs';
|
||||
import {dirname, join, relative} from 'path';
|
||||
import {fileURLToPath} from 'url';
|
||||
import {readFile, writeFile} from 'fs/promises';
|
||||
|
||||
const TYPESCRIPT_EXTENSION = '.ts';
|
||||
const SKIP_FILES_WITH_EXTENSIONS = ['.e2e-spec.ts', '.spec.ts', '.po.ts'];
|
||||
const EXAMPLES_PATH = `../../content/examples`;
|
||||
|
||||
interface File {
|
||||
path: string;
|
||||
content: string;
|
||||
}
|
||||
|
||||
interface AnalyzedFiles {
|
||||
path: string;
|
||||
componentNames: string[];
|
||||
}
|
||||
|
||||
/** Absolute disk path to the project directory. */
|
||||
const examplesDir = join(
|
||||
dirname(fileURLToPath(import.meta.url)),
|
||||
'../../src/content/examples',
|
||||
);
|
||||
const projectDir = join(dirname(fileURLToPath(import.meta.url)), '../../src');
|
||||
const previewsComponentMapFileName = 'previews.ts';
|
||||
|
||||
main();
|
||||
|
||||
async function main() {
|
||||
const files = await retrieveAllTypescriptFiles(
|
||||
examplesDir,
|
||||
(path) => !SKIP_FILES_WITH_EXTENSIONS.some((extensionToSkip) => path.endsWith(extensionToSkip)),
|
||||
);
|
||||
|
||||
const filesWithComponent = files
|
||||
.map((file) => ({
|
||||
componentNames: analyzeFile(file),
|
||||
path: file.path,
|
||||
}))
|
||||
.filter((result) => result.componentNames.length > 0);
|
||||
|
||||
const previewsComponentMap = generatePreviewsComponentMap(filesWithComponent);
|
||||
|
||||
fs.mkdirSync(join(projectDir, 'assets/previews'), {recursive: true});
|
||||
|
||||
await writeFile(
|
||||
join(projectDir, 'assets/previews', previewsComponentMapFileName),
|
||||
previewsComponentMap,
|
||||
);
|
||||
}
|
||||
|
||||
/** Recursively search the provided directory for all typescript files and asynchronously load them. */
|
||||
function retrieveAllTypescriptFiles(
|
||||
baseDir: string,
|
||||
predicateFn: (path: string) => boolean,
|
||||
): Promise<File[]> {
|
||||
const typescriptFiles: Promise<File>[] = [];
|
||||
|
||||
const checkFilesInDirectory = (dir: string) => {
|
||||
const files = fs.readdirSync(dir, {withFileTypes: true});
|
||||
for (const file of files) {
|
||||
const fullPathToFile = join(dir, file.name);
|
||||
const relativeFilePath = relative(baseDir, fullPathToFile);
|
||||
|
||||
if (
|
||||
file.isFile() &&
|
||||
file.name.endsWith(TYPESCRIPT_EXTENSION) &&
|
||||
predicateFn(relativeFilePath)
|
||||
) {
|
||||
typescriptFiles.push(
|
||||
readFile(fullPathToFile, {encoding: 'utf-8'}).then((fileContent) => {
|
||||
return {
|
||||
path: relativeFilePath,
|
||||
content: fileContent,
|
||||
};
|
||||
}),
|
||||
);
|
||||
} else if (file.isDirectory()) {
|
||||
checkFilesInDirectory(fullPathToFile);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
checkFilesInDirectory(baseDir);
|
||||
|
||||
return Promise.all(typescriptFiles);
|
||||
}
|
||||
|
||||
/** Returns list of the `Standalone` @Component class names for given file */
|
||||
function analyzeFile(file: File): string[] {
|
||||
const componentClassNames: string[] = [];
|
||||
const sourceFile = ts.createSourceFile(file.path, file.content, ts.ScriptTarget.Latest, false);
|
||||
|
||||
const visitNode = (node: ts.Node): void => {
|
||||
if (ts.isClassDeclaration(node)) {
|
||||
const decorators = ts.getDecorators(node);
|
||||
const componentName = node.name ? node.name.text : null;
|
||||
|
||||
if (decorators && decorators.length) {
|
||||
for (const decorator of decorators) {
|
||||
const call = decorator.expression;
|
||||
|
||||
if (
|
||||
ts.isCallExpression(call) &&
|
||||
ts.isIdentifier(call.expression) &&
|
||||
call.expression.text === 'Component' &&
|
||||
call.arguments.length > 0 &&
|
||||
ts.isObjectLiteralExpression(call.arguments[0])
|
||||
) {
|
||||
const hasStandaloneProperty = call.arguments[0].properties.some(
|
||||
(property) =>
|
||||
property.name &&
|
||||
ts.isIdentifier(property.name) &&
|
||||
property.name.text === 'standalone',
|
||||
);
|
||||
|
||||
if (hasStandaloneProperty && componentName) {
|
||||
componentClassNames.push(componentName);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ts.forEachChild(node, visitNode);
|
||||
};
|
||||
|
||||
visitNode(sourceFile);
|
||||
|
||||
return componentClassNames;
|
||||
}
|
||||
|
||||
function generatePreviewsComponentMap(data: AnalyzedFiles[]): string {
|
||||
let result = '';
|
||||
for (const fileData of data) {
|
||||
for (const componentName of fileData.componentNames) {
|
||||
const key = `${fileData.path}${
|
||||
fileData.componentNames.length > 1 ? '_' + componentName : ''
|
||||
}`.replace(/\\/g, '/');
|
||||
result += `['${key}']: () => import('${EXAMPLES_PATH}/${fileData.path
|
||||
.replace(/\\/g, '/')
|
||||
.replace('.ts', '')}').then(c => c.${componentName}),\n`;
|
||||
}
|
||||
}
|
||||
return fs
|
||||
.readFileSync('scripts/examples/previews-components.template', 'utf8')
|
||||
.replace(/\${previewsComponents}/g, result);
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright Google LLC All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.dev/license
|
||||
*/
|
||||
|
||||
/**
|
||||
******************************************************************************
|
||||
* DO NOT MANUALLY EDIT THIS FILE. THIS FILE IS AUTOMATICALLY GENERATED.
|
||||
******************************************************************************
|
||||
*/
|
||||
|
||||
import {Type} from '@angular/core';
|
||||
|
||||
/**
|
||||
* Map of the previews components, values are functions which returns the promise of the component type, which will be displayed as preview in the ExampleViewer component.
|
||||
* Keys has to be equal to paths written down in the docs markdown files.
|
||||
*/
|
||||
export const PREVIEWS_COMPONENTS_MAP: Record<string, () => Promise<Type<unknown>>> = {
|
||||
${previewsComponents}
|
||||
};
|
||||
@@ -0,0 +1,221 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright Google LLC All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.dev/license
|
||||
*/
|
||||
|
||||
import {dirname, join} from 'path';
|
||||
import {readFile, writeFile} from 'fs/promises';
|
||||
import {
|
||||
ASSETS_EXAMPLE_PATH,
|
||||
CSS_TS_COPYRIGHT,
|
||||
EXAMPLES_PATH,
|
||||
HTML_COPYRIGHT,
|
||||
STACKBLITZ_TEMPLATE_PATH,
|
||||
STACKBLITZ_CONFIG_FILENAME,
|
||||
TEMPORARY_EXAMPLES_PATH,
|
||||
EXCLUDE_FILES_FOR_STACKBLITZ,
|
||||
} from './utils/examples-constants.mjs';
|
||||
import {copyFolder, createFolder, removeFolder} from './utils/fs.mjs';
|
||||
import jsdom from 'jsdom';
|
||||
import {glob} from 'glob';
|
||||
import {regionParser} from './../../prerender/markdown-pipeline/regions/region-parser.mjs';
|
||||
|
||||
interface StackblitzConfig {
|
||||
ignore: string[];
|
||||
file: string;
|
||||
tags: string[];
|
||||
description: string;
|
||||
}
|
||||
|
||||
export async function generateStackblitzExample(
|
||||
exampleFolderName: string,
|
||||
primaryFilePath: string,
|
||||
title: string,
|
||||
) {
|
||||
const exampleDir = join(EXAMPLES_PATH, exampleFolderName);
|
||||
const temporaryExampleDir = join(TEMPORARY_EXAMPLES_PATH, exampleFolderName);
|
||||
const config = await readFile(join(exampleDir, STACKBLITZ_CONFIG_FILENAME), 'utf-8');
|
||||
const stackblitzConfig: StackblitzConfig = JSON.parse(config);
|
||||
primaryFilePath = join(...primaryFilePath.split('/'));
|
||||
|
||||
await createFolder(temporaryExampleDir);
|
||||
await combineTemplateWithExample(exampleDir, temporaryExampleDir);
|
||||
await generateStackblitzHtml(
|
||||
temporaryExampleDir,
|
||||
stackblitzConfig,
|
||||
exampleFolderName,
|
||||
primaryFilePath,
|
||||
title,
|
||||
);
|
||||
await removeFolder(temporaryExampleDir);
|
||||
}
|
||||
|
||||
async function combineTemplateWithExample(
|
||||
exampleDir: string,
|
||||
temporaryExampleDir: string,
|
||||
): Promise<void> {
|
||||
// Copy template files to TEMP folder
|
||||
await copyFolder(STACKBLITZ_TEMPLATE_PATH, temporaryExampleDir);
|
||||
|
||||
// Copy example files to TEMP folder
|
||||
await copyFolder(exampleDir, temporaryExampleDir);
|
||||
}
|
||||
|
||||
async function generateStackblitzHtml(
|
||||
temporaryExampleDir: string,
|
||||
stackBlitzConfig: StackblitzConfig,
|
||||
exampleFolderName: string,
|
||||
primaryFilePath: string,
|
||||
title: string,
|
||||
): Promise<void> {
|
||||
const defaultIncludes = [
|
||||
'**/*.ts',
|
||||
'**/*.js',
|
||||
'**/*.css',
|
||||
'**/*.html',
|
||||
'**/*.md',
|
||||
'**/*.json',
|
||||
'**/*.svg',
|
||||
];
|
||||
const exampleFilePaths = await glob(defaultIncludes, {
|
||||
cwd: temporaryExampleDir,
|
||||
nodir: true,
|
||||
dot: true,
|
||||
ignore: stackBlitzConfig.ignore,
|
||||
});
|
||||
|
||||
const postData = await createPostData(
|
||||
temporaryExampleDir,
|
||||
stackBlitzConfig,
|
||||
exampleFilePaths,
|
||||
title,
|
||||
);
|
||||
const primaryFile = getPrimaryFile(primaryFilePath ?? stackBlitzConfig.file, exampleFilePaths);
|
||||
const html = createStackblitzHtml(postData, primaryFile);
|
||||
|
||||
const stackblitzHtmlPath = join(
|
||||
join(ASSETS_EXAMPLE_PATH, exampleFolderName),
|
||||
`${primaryFile}.html`,
|
||||
);
|
||||
await createFolder(dirname(stackblitzHtmlPath));
|
||||
await writeFile(stackblitzHtmlPath, html, 'utf-8');
|
||||
}
|
||||
|
||||
function getPrimaryFile(primaryFilePath: string, exampleFilePaths: string[]): string {
|
||||
if (primaryFilePath) {
|
||||
if (!exampleFilePaths.some((filePath) => filePath === primaryFilePath)) {
|
||||
throw new Error(`The specified primary file (${primaryFilePath}) does not exist!`);
|
||||
}
|
||||
return primaryFilePath;
|
||||
} else {
|
||||
const defaultPrimaryFilePaths = [
|
||||
'src/app/app.component.html',
|
||||
'src/app/app.component.ts',
|
||||
'src/app/main.ts',
|
||||
];
|
||||
const primaryFile = defaultPrimaryFilePaths.find((path) =>
|
||||
exampleFilePaths.some((filePath) => filePath === path),
|
||||
);
|
||||
|
||||
if (!primaryFile) {
|
||||
throw new Error(
|
||||
`None of the default primary files (${defaultPrimaryFilePaths.join(', ')}) exists.`,
|
||||
);
|
||||
}
|
||||
|
||||
return primaryFile;
|
||||
}
|
||||
}
|
||||
|
||||
async function createPostData(
|
||||
exampleDir: string,
|
||||
config: StackblitzConfig,
|
||||
exampleFilePaths: string[],
|
||||
title: string,
|
||||
): Promise<Record<string, string>> {
|
||||
const postData: Record<string, string> = {};
|
||||
|
||||
for (const filePath of exampleFilePaths) {
|
||||
if (EXCLUDE_FILES_FOR_STACKBLITZ.some((excludedFile) => filePath.endsWith(excludedFile))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let content = await readFile(join(exampleDir, filePath), 'utf-8');
|
||||
content = appendCopyright(filePath, content);
|
||||
content = extractRegions(filePath, content);
|
||||
|
||||
postData[`project[files][${filePath}]`] = content;
|
||||
}
|
||||
|
||||
const tags = ['angular', 'example', ...(config.tags || [])];
|
||||
tags.forEach((tag, index) => (postData[`project[tags][${index}]`] = tag));
|
||||
|
||||
postData['project[description]'] = `Angular Example - ${config.description}`;
|
||||
postData['project[template]'] = 'node';
|
||||
postData['project[title]'] = title ?? 'Angular Example';
|
||||
|
||||
return postData;
|
||||
}
|
||||
|
||||
function createStackblitzHtml(postData: Record<string, string>, primaryFile: string): string {
|
||||
const baseHtml = createBaseStackblitzHtml(primaryFile);
|
||||
const doc = new jsdom.JSDOM(baseHtml).window.document;
|
||||
const form = doc.querySelector('form');
|
||||
|
||||
for (const [key, value] of Object.entries(postData)) {
|
||||
const element = htmlToElement(doc, `<input type="hidden" name="${key}">`);
|
||||
if (element && form) {
|
||||
element.setAttribute('value', value as string);
|
||||
form.appendChild(element);
|
||||
}
|
||||
}
|
||||
|
||||
return doc.documentElement.outerHTML;
|
||||
}
|
||||
|
||||
function createBaseStackblitzHtml(primaryFile: string) {
|
||||
const file = `?file=${primaryFile}`;
|
||||
const action = `https://stackblitz.com/run${file}`;
|
||||
|
||||
return `
|
||||
<!DOCTYPE html><html lang="en"><body>
|
||||
<form id="mainForm" method="post" action="${action}" target="_self"></form>
|
||||
<script>
|
||||
var embedded = 'ctl=1';
|
||||
var isEmbedded = window.location.search.indexOf(embedded) > -1;
|
||||
|
||||
if (isEmbedded) {
|
||||
var form = document.getElementById('mainForm');
|
||||
var action = form.action;
|
||||
var actionHasParams = action.indexOf('?') > -1;
|
||||
var symbol = actionHasParams ? '&' : '?'
|
||||
form.action = form.action + symbol + embedded;
|
||||
}
|
||||
document.getElementById("mainForm").submit();
|
||||
</script>
|
||||
</body></html>
|
||||
`.trim();
|
||||
}
|
||||
|
||||
function appendCopyright(filename: string, content: string): string {
|
||||
if (filename.endsWith('.html')) {
|
||||
return `${HTML_COPYRIGHT}${content}`;
|
||||
} else if (filename.endsWith('.ts') || filename.endsWith('.css')) {
|
||||
return `${CSS_TS_COPYRIGHT}${content}`;
|
||||
}
|
||||
return content;
|
||||
}
|
||||
|
||||
function htmlToElement(document: Document, html: string) {
|
||||
const div = document.createElement('div');
|
||||
div.innerHTML = html;
|
||||
return div.firstElementChild;
|
||||
}
|
||||
|
||||
function extractRegions(path: string, contents: string): string {
|
||||
const regionParserResult = regionParser(contents, path);
|
||||
return regionParserResult.contents;
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"extends": "../../tsconfig.json",
|
||||
"ts-node": {
|
||||
"compilerOptions": {
|
||||
"module": "es2020"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright Google LLC All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.dev/license
|
||||
*/
|
||||
|
||||
import {dirname, join} from 'path';
|
||||
import {fileURLToPath} from 'url';
|
||||
|
||||
const BASE = dirname(fileURLToPath(import.meta.url));
|
||||
|
||||
// Directory paths
|
||||
export const ASSETS_EXAMPLE_PATH = join(
|
||||
BASE,
|
||||
'../../../src/assets/content/examples',
|
||||
);
|
||||
export const EXAMPLES_PATH = join(BASE, '../../../src/content/examples');
|
||||
export const STACKBLITZ_TEMPLATE_PATH = join(
|
||||
BASE,
|
||||
'../../../src/content/stackblitz-template',
|
||||
);
|
||||
|
||||
export const TEMPORARY_EXAMPLES_PATH = join(fileURLToPath(import.meta.url), '../../../../TEMP');
|
||||
|
||||
// Extensions
|
||||
export const TEST_FILES_EXTENSION_SUFFIX = '.spec.ts';
|
||||
export const TEST_FILES_E2E_EXTENSION_SUFFIX = '.e2e-spec.ts';
|
||||
|
||||
// Filename
|
||||
// TODO: BUILD.bazel and example-config will be deprecated when migration to standalone examples will be done.
|
||||
export const BUILD_BAZEL_FILENAME = 'BUILD.bazel';
|
||||
export const EXAMPLE_CONFIG_FILENAME = 'example-config.json';
|
||||
export const STACKBLITZ_CONFIG_FILENAME = 'stackblitz.json';
|
||||
|
||||
// Copyright
|
||||
const PAD = '\n\n';
|
||||
const COPYRIGHT =
|
||||
'@license\n' +
|
||||
'Copyright Google LLC All Rights Reserved.\n' +
|
||||
'\n' +
|
||||
'Use of this source code is governed by an MIT-style license that can be\n' +
|
||||
'found in the LICENSE file at https://angular.dev/license\n';
|
||||
export const CSS_TS_COPYRIGHT = `/*\n${COPYRIGHT}\n*/${PAD}`;
|
||||
export const HTML_COPYRIGHT = `<!-- \n${COPYRIGHT}\n-->${PAD}`;
|
||||
|
||||
// Exclude
|
||||
export const EXCLUDE_FILES_FOR_STACKBLITZ = [
|
||||
STACKBLITZ_CONFIG_FILENAME,
|
||||
BUILD_BAZEL_FILENAME,
|
||||
EXAMPLE_CONFIG_FILENAME,
|
||||
TEST_FILES_EXTENSION_SUFFIX,
|
||||
TEST_FILES_E2E_EXTENSION_SUFFIX,
|
||||
];
|
||||
@@ -0,0 +1,77 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright Google LLC All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.dev/license
|
||||
*/
|
||||
|
||||
import {Dirent, existsSync, readdirSync} from 'fs';
|
||||
import {copyFile, mkdir, readFile, readdir, rm, stat} from 'fs/promises';
|
||||
import {join, relative} from 'path';
|
||||
|
||||
export async function copyFolder(source: string, destination: string) {
|
||||
if (!existsSync(destination)) {
|
||||
await mkdir(destination, {recursive: true});
|
||||
}
|
||||
|
||||
const files = await readdir(source);
|
||||
|
||||
for (const file of files) {
|
||||
const sourcePath = join(source, file);
|
||||
const destPath = join(destination, file);
|
||||
|
||||
const stats = await stat(sourcePath);
|
||||
const isDirectory = await stats.isDirectory();
|
||||
|
||||
if (isDirectory) {
|
||||
await copyFolder(sourcePath, destPath);
|
||||
} else {
|
||||
await copyFile(sourcePath, destPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Recursively search the provided directory for all files which satisfy predicate and asynchronously load them. */
|
||||
export function retrieveFiles(
|
||||
baseDir: string,
|
||||
predicate?: (file: Dirent) => boolean,
|
||||
): Promise<{path: string; content: string}[]> {
|
||||
const filesWithContent: Promise<{path: string; content: string}>[] = [];
|
||||
|
||||
const checkFilesInDirectory = (dir: string) => {
|
||||
const files = readdirSync(dir, {withFileTypes: true});
|
||||
for (const file of files) {
|
||||
const fullPathToFile = join(dir, file.name);
|
||||
|
||||
if (file.isFile() && (predicate ? predicate(file) : true)) {
|
||||
filesWithContent.push(
|
||||
readFile(fullPathToFile, {encoding: 'utf-8'}).then((fileContent) => {
|
||||
return {
|
||||
path: relative(baseDir, fullPathToFile),
|
||||
content: fileContent,
|
||||
};
|
||||
}),
|
||||
);
|
||||
} else if (file.isDirectory()) {
|
||||
checkFilesInDirectory(fullPathToFile);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
checkFilesInDirectory(baseDir);
|
||||
|
||||
return Promise.all(filesWithContent);
|
||||
}
|
||||
|
||||
export async function createFolder(path: string): Promise<void> {
|
||||
if (!existsSync(path)) {
|
||||
await mkdir(path, {recursive: true});
|
||||
}
|
||||
}
|
||||
|
||||
export async function removeFolder(path: string): Promise<void> {
|
||||
if (existsSync(path)) {
|
||||
await rm(path, {recursive: true});
|
||||
}
|
||||
}
|
||||
@@ -7,15 +7,7 @@
|
||||
*/
|
||||
|
||||
import {DOCUMENT, isPlatformBrowser} from '@angular/common';
|
||||
import {
|
||||
Component,
|
||||
inject,
|
||||
NgZone,
|
||||
OnInit,
|
||||
PLATFORM_ID,
|
||||
signal,
|
||||
WritableSignal,
|
||||
} from '@angular/core';
|
||||
import {Component, inject, NgZone, OnInit, PLATFORM_ID, signal, WritableSignal} from '@angular/core';
|
||||
import {NavigationEnd, NavigationSkipped, Router, RouterLink, RouterOutlet} from '@angular/router';
|
||||
import {filter, map, skip} from 'rxjs/operators';
|
||||
import {
|
||||
|
||||
@@ -116,7 +116,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<p class="docs-license">
|
||||
Super-powered by Google ©2010-2024. Code licensed under an
|
||||
Super-powered by Google ©2010-2023. Code licensed under an
|
||||
<a routerLink="/license" title="License text">MIT-style License</a>
|
||||
. Documentation licensed under
|
||||
<a href="https://creativecommons.org/licenses/by/4.0/">CC BY 4.0</a>
|
||||
|
||||
@@ -28,12 +28,12 @@ export class ContentLoader implements DocsContentLoader {
|
||||
this.cache.set(
|
||||
path,
|
||||
this.httpClient
|
||||
.get(`assets/content/${path}`, {
|
||||
responseType: 'text',
|
||||
})
|
||||
.pipe(map((contents) => ({contents, id: path})))
|
||||
.toPromise(),
|
||||
);
|
||||
.get(`assets/content/${path}`, {
|
||||
responseType: 'text',
|
||||
})
|
||||
.pipe(map((contents) => ({contents, id: path})))
|
||||
.toPromise()
|
||||
);
|
||||
} catch {
|
||||
this.router.navigateByUrl('/404');
|
||||
}
|
||||
|
||||
@@ -1,207 +0,0 @@
|
||||
# EmbeddedEditor components, services and functionality
|
||||
|
||||
- [Scenarios](#scenarios)
|
||||
- [Loading a project](#loading-a-project)
|
||||
- [Updating the code](#updating-the-code)
|
||||
- [Creating a new file](#creating-a-new-file)
|
||||
- [Deleting a file](#deleting-a-file)
|
||||
- [Switching a project](#switching-a-project)
|
||||
- [Components and services](#components-and-services)
|
||||
|
||||
- [EmbeddedEditor](#EmbeddedEditor)
|
||||
- [CodeEditor](#CodeEditor)
|
||||
- [CodeMirrorEditor](#CodeMirrorEditor)
|
||||
- [TypeScript Web Worker](#typescript-web-worker)
|
||||
- [Preview](#Preview)
|
||||
- [Terminal](#Terminal)
|
||||
- [InteractiveTerminal](#InteractiveTerminal)
|
||||
- [Console](#Console)
|
||||
- [NodeRuntimeSandbox](#NodeRuntimeSandbox)
|
||||
- [NodeRuntimeState](#NodeRuntimeState)
|
||||
- [EmbeddedTutorialManager](#EmbeddedTutorialManager)
|
||||
- [EditorUiState](#EditorUiState)
|
||||
- [DownloadManager](#DownloadManager)
|
||||
- [AlertManager](#AlertManager)
|
||||
- [TypingsLoader](#TypingsLoader)
|
||||
|
||||
## External libraries
|
||||
|
||||
- [WebContainers API](https://webcontainers.io/)
|
||||
- [CodeMirror](https://codemirror.net/)
|
||||
- [@typescript/vfs](https://www.npmjs.com/package/@typescript/vfs)
|
||||
- [Xterm.js](https://xtermjs.org/)
|
||||
|
||||
## Notes
|
||||
|
||||
- See [scripts/tutorials/README.md](/scripts/tutorials/README.md) for more information about the tutorials script.
|
||||
- See [adev/src/content/tutorials/README.md](/adev/src/content/tutorials/README.md) for more information about the tutorials content.
|
||||
|
||||
---
|
||||
|
||||
## Scenarios
|
||||
|
||||
### Loading a project
|
||||
|
||||
1. The page responsible for the embedded editor lazy loads the [`EmbeddedEditor`](./embedded-editor.component.ts) component and the [`NodeRuntimeSandbox`](./node-runtime-sandbox.service.ts), then triggers the initialization of all components and services. The embedded editor is available in the following pages:
|
||||
|
||||
- homepage: https://angular.dev
|
||||
- playground: https://angular.dev/playground
|
||||
- tutorial pages: https://angular.dev/tutorials
|
||||
|
||||
2. The project assets are fetched by the [`EmbeddedTutorialManager`](./embedded-tutorial-manager.service.ts). Meanwhile:
|
||||
|
||||
- The code editor is initialized
|
||||
- The code editor initializes the TypeScript Web Worker, which initializes the "default file system map" using TypeScript's CDN.
|
||||
- The WebContainer is initialized
|
||||
- The terminal is initialized
|
||||
|
||||
3. The tutorial source code is mounted in the `WebContainer`'s filesystem by the [`NodeRuntimeSandbox`](./node-runtime-sandbox.service.ts)
|
||||
4. The tutorial project dependencies are installed by the [`NodeRuntimeSandbox`](./node-runtime-sandbox.service.ts).
|
||||
5. The development server is started by the [`NodeRuntimeSandbox`](./node-runtime-sandbox.service.ts) and the types are loaded by the [`TypingsLoader`](./typings-loader.service.ts) service.
|
||||
6. The preview is loaded with the URL provided by the WebContainer API after the development server is started.
|
||||
7. The project is ready.
|
||||
|
||||
### Updating the code
|
||||
|
||||
1. The user update the code in the code editor.
|
||||
2. The code editor state is updated on real time, without debouncing so that the user can see the changes in the code editor and CodeMirror can handle the changes accordingly.
|
||||
3. At the same time, the changes are sent to the TypeScript web worker to provide diagnostics, autocomplete and type features as soon as possible.
|
||||
4. The code changes are debounced to be written in the WebContainer filesystem by the [`NodeRuntimeSandbox`](./node-runtime-sandbox.service.ts).
|
||||
5. After the debounce time is reached, the code changes are written in the WebContainer filesystem by the [`NodeRuntimeSandbox`](./node-runtime-sandbox.service.ts), then the user can see the changes in the preview.
|
||||
|
||||
### Creating a new file
|
||||
|
||||
1. The user clicks on the new file button.
|
||||
2. The new file tab is opened.
|
||||
3. The user types the new file name.
|
||||
4. If the file name is valid, the file is created in the WebContainer filesystem by the [`NodeRuntimeSandbox`](./node-runtime-sandbox.service.ts).
|
||||
|
||||
- `..` is disallowed in the file name to prevent users to create files outside the `src` directory.
|
||||
|
||||
5. The file is added to the TypeScript virtual file system, allowing the TypeScript web worker to provide diagnostics, autocomplete and type features for the new file. Also, exports from the new file are available in other files.
|
||||
6. The new file is added as the last tab in the code editor and the new file can be edited.
|
||||
|
||||
Note: If the new file name matches a file that already exists but is hidden in the code editor, the content for that file will show up in the created file. An example for a file that always exists is `index.html`.
|
||||
|
||||
### Deleting a file
|
||||
|
||||
1. The user clicks on the delete file button.
|
||||
2. The file is deleted from the WebContainer filesystem by the [`NodeRuntimeSandbox`](./node-runtime-sandbox.service.ts).
|
||||
3. The file is removed from the TypeScript virtual file system.
|
||||
4. The file is removed from the code editor tabs.
|
||||
|
||||
Note: Some files can't be deleted to prevent users to break the app, being `src/main.ts`and `src/index.html`
|
||||
|
||||
### Switching a project
|
||||
|
||||
The embedded editor considers a project change when the embedded editor was already initialized and the user changes the page in the following scenarios:
|
||||
|
||||
- Navigating through tutorial steps
|
||||
- Going from the homepage after the embedded editor is initialized to the playground
|
||||
- Going from a tutorial page to the playground
|
||||
- Going from a tutorial page to the homepage
|
||||
- Going from the playground to the homepage
|
||||
|
||||
When a project change is detected, the [`EmbeddedTutorialManager`](./embedded-tutorial-manager.service.ts) emits the `tutorialChanged` observable, which is listened in multiple sub-components and services, then each component/service performs the necessary operations to switch the project.
|
||||
|
||||
The following steps are executed on project change:
|
||||
|
||||
1. The new project files are fetched by the [`EmbeddedTutorialManager`](./embedded-tutorial-manager.service.ts).
|
||||
2. The new project files are mounted in the WebContainer filesystem.
|
||||
3. The TypeScript virtual filesystem is updated with the new files and contents.
|
||||
4. The previous project and new project files are compared.
|
||||
1. Files that are not available in the new project are deleted from the WebContainer filesystem.
|
||||
2. Files that have the same path and name have their content replaced on the previous step when the files are mounted.
|
||||
5. The previous project dependencies are compared with the new project dependencies.
|
||||
1. If there are differences, a `npm install` is triggered, hiding the preview and going to the install loading step.
|
||||
2. If there are no differences, the project is ready.
|
||||
6. Some states are resetted, for example the "reveal answer" state if the previous project was in the "reveal answer" state.
|
||||
|
||||
## Components and services
|
||||
|
||||
### [`EmbeddedEditor`](./embedded-editor.component.ts)
|
||||
|
||||
The embedded editor is the parent component that holds all the components and services that compose the embedded editor.
|
||||
|
||||
#### [`CodeEditor`](./code-editor/code-editor.component.ts)
|
||||
|
||||
The component that holds the code editor view and the code editor state.
|
||||
|
||||
##### [`CodeMirrorEditor`](./code-editor/code-mirror-editor.service.ts)
|
||||
|
||||
[CodeMirror](https://codemirror.net/) is the library used to handle the code editor.
|
||||
|
||||
The `CodeMirrorEditor` service manages the CodeMirror instance and all the interactions with the library used to handle the code editor.
|
||||
|
||||
- handle the file edits and the CodeMirror view and state
|
||||
- handle the current project files in the code editor
|
||||
- handle the file creations and deletions
|
||||
- handle the file changes
|
||||
- handle all the CodeMirror specific events and extensions
|
||||
|
||||
###### [TypeScript Web Worker](./code-editor/workers/typescript-vfs.worker.ts)
|
||||
|
||||
The TypeScript features are provided by the TypeScript web worker, that is initialized by the `CodeMirrorEditor` service.
|
||||
|
||||
The TypeScript web worker uses `@typescript/vfs` and the TypeScript language service to provide diagnostics, autocomplete and type features.
|
||||
|
||||
#### [`Preview`](./preview/preview.component.ts)
|
||||
|
||||
The preview component manages the `iframe` responsible for displaying the tutorial project preview, with the URL provided by the WebContainer API after the development server is started.
|
||||
|
||||
While the project is being initialized, the preview displays the loading state.
|
||||
|
||||
#### [`Terminal`](./terminal/terminal.component.ts)
|
||||
|
||||
[Xterm.js](https://xtermjs.org/) is the library used to handle the terminals.
|
||||
|
||||
The terminal component handles the Xterm.js instance for the console and for the interactive terminal.
|
||||
|
||||
##### [`InteractiveTerminal`](./terminal/interactive-terminal.ts)
|
||||
|
||||
The interactive terminal is the terminal where the user can interact with the terminal and run commands, supporting only commands for the Angular CLI.
|
||||
|
||||
##### Console
|
||||
|
||||
The console displays the output for `npm install` and `ng serve`.
|
||||
|
||||
#### [`NodeRuntimeSandbox`](./node-runtime-sandbox.service.ts)
|
||||
|
||||
Responsible for managing the WebContainer instance and all communication with its API. This service handles:
|
||||
|
||||
- the WebContainer instance
|
||||
- all Node.js scripts
|
||||
- the WebContainer filesystem, mounting the tutorial project files, writing new content, deleting and creating files.
|
||||
- the terminal session, reading and processing user inputs.
|
||||
- the tutorial project dependencies, installing the dependencies.
|
||||
- the processes running inside the WebContainer, being the npm scripts to install the dependencies, run the development server and the user inputs for the `ng` CLI.
|
||||
|
||||
##### [`NodeRuntimeState`](./node-runtime-state.service.ts)
|
||||
|
||||
Manages the [`NodeRuntimeSandbox`](./node-runtime-sandbox.service.ts) loading and error state.
|
||||
|
||||
#### [`EmbeddedTutorialManager`](./embedded-tutorial-manager.service.ts)
|
||||
|
||||
Manages the tutorial assets, being responsible for fetching the tutorial source code and metadata.
|
||||
|
||||
The source code is mounted in the WebContainer filesystem by the [`NodeRuntimeSandbox`](./node-runtime-sandbox.service.ts).
|
||||
|
||||
The metadata is used to manage the project, handle the project changes and the user interactivity with the app.
|
||||
|
||||
This service also handles the reveal answer and reset reveal answer feature.
|
||||
|
||||
#### [`EditorUiState`](./editor-ui-state.service.ts)
|
||||
|
||||
Manages the editor UI state, being responsible for handling the user interactions with the editor tabs, switching between the preview, the terminal and the console.
|
||||
|
||||
#### [`DownloadManager`](./download-manager.service.ts)
|
||||
|
||||
Responsible for handling the download button in the embedded editor, fetching the tutorial project files and generating a zip file with the project content.
|
||||
|
||||
#### [`AlertManager`](./alert-manager.service.ts)
|
||||
|
||||
Manage the alerts displayed in the embedded editor, being the out of memory alert when multiple tabs are opened, and unsupported enviroments alerts.
|
||||
|
||||
#### [`TypingsLoader`](./typings-loader.service.ts)
|
||||
|
||||
Manages the types definitions for the code editor.
|
||||
@@ -16,7 +16,10 @@ import {NodeRuntimeSandbox} from '../node-runtime-sandbox.service';
|
||||
import {EmbeddedTutorialManager} from '../embedded-tutorial-manager.service';
|
||||
|
||||
import {CodeMirrorEditor, EDITOR_CONTENT_CHANGE_DELAY_MILLIES} from './code-mirror-editor.service';
|
||||
import {TutorialConfig, TutorialMetadata} from '@angular/docs';
|
||||
import {
|
||||
TutorialConfig,
|
||||
TutorialMetadata,
|
||||
} from '@angular/docs';
|
||||
|
||||
class FakeNodeRuntimeSandbox {
|
||||
async writeFile(path: string, content: string) {}
|
||||
|
||||
@@ -13,4 +13,5 @@ export const TUTORIALS_ASSETS_METADATA_DIRECTORY = 'metadata';
|
||||
export const TUTORIALS_SOURCE_CODE_WEB_PATH = `${TUTORIALS_ASSETS_WEB_PATH}/${TUTORIALS_ASSETS_SOURCE_CODE_DIRECTORY}`;
|
||||
export const TUTORIALS_METADATA_WEB_PATH = `${TUTORIALS_ASSETS_WEB_PATH}/${TUTORIALS_ASSETS_METADATA_DIRECTORY}`;
|
||||
|
||||
export const TUTORIALS_COMMON_DIRECTORY = 'common';
|
||||
|
||||
export const TUTORIALS_COMMON_DIRECTORY = 'common';
|
||||
@@ -145,9 +145,7 @@ $width-breakpoint: 950px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background-color: var(--octonary-contrast);
|
||||
transition:
|
||||
background-color 0.3s ease,
|
||||
border-color 0.3s ease;
|
||||
transition: background-color 0.3s ease, border-color 0.3s ease;
|
||||
|
||||
i {
|
||||
color: var(--bright-blue);
|
||||
@@ -194,11 +192,3 @@ $width-breakpoint: 950px;
|
||||
padding: 1.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
::ng-deep mat-tab-group {
|
||||
.mat-mdc-tab-body-wrapper,
|
||||
.mat-mdc-tab-body,
|
||||
.mat-mdc-tab-body-content {
|
||||
display: contents;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -245,7 +245,7 @@ describe('EmbeddedTutorialManager', () => {
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
const metadata: TutorialMetadata = {
|
||||
tutorialFiles: {'app.js': ''},
|
||||
openFiles: ['app.js'],
|
||||
@@ -295,6 +295,7 @@ describe('EmbeddedTutorialManager', () => {
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
it('should not set shouldReinstallDependencies if project did not change', async () => {
|
||||
const fetchMock = spyOn(window, 'fetch');
|
||||
|
||||
@@ -324,6 +325,7 @@ describe('EmbeddedTutorialManager', () => {
|
||||
it('should trigger shouldReInstallDependencies if new metadata has different dependencies', async () => {
|
||||
const fetchMock = spyOn(window, 'fetch');
|
||||
|
||||
|
||||
fetchMock
|
||||
.withArgs(`${TUTORIALS_ASSETS_WEB_PATH}/${tutorial}/source-code.json`)
|
||||
.and.returnValues(Promise.resolve(new Response('{}', {status: 200})));
|
||||
|
||||
@@ -11,7 +11,10 @@ import {FileSystemTree} from '@webcontainer/api';
|
||||
import {BehaviorSubject} from 'rxjs';
|
||||
|
||||
import {TutorialMetadata} from '@angular/docs';
|
||||
import {TUTORIALS_COMMON_DIRECTORY, TUTORIALS_ASSETS_WEB_PATH} from './constants';
|
||||
import {
|
||||
TUTORIALS_COMMON_DIRECTORY,
|
||||
TUTORIALS_ASSETS_WEB_PATH
|
||||
} from './constants';
|
||||
|
||||
/**
|
||||
* A service responsible for the current tutorial, retrieving and providing
|
||||
@@ -137,9 +140,7 @@ export class EmbeddedTutorialManager {
|
||||
}
|
||||
|
||||
private async fetchTutorialSourceCode(tutorial: string): Promise<FileSystemTree> {
|
||||
const tutorialSourceCode = await fetch(
|
||||
`${TUTORIALS_ASSETS_WEB_PATH}/${tutorial}/source-code.json`,
|
||||
);
|
||||
const tutorialSourceCode = await fetch(`${TUTORIALS_ASSETS_WEB_PATH}/${tutorial}/source-code.json`);
|
||||
|
||||
if (!tutorialSourceCode.ok) throw new Error(`Missing source code for tutorial ${tutorial}`);
|
||||
|
||||
@@ -147,9 +148,7 @@ export class EmbeddedTutorialManager {
|
||||
}
|
||||
|
||||
private async fetchTutorialMetadata(tutorial: string): Promise<TutorialMetadata> {
|
||||
const tutorialSourceCode = await fetch(
|
||||
`${TUTORIALS_ASSETS_WEB_PATH}/${tutorial}/metadata.json`,
|
||||
);
|
||||
const tutorialSourceCode = await fetch(`${TUTORIALS_ASSETS_WEB_PATH}/${tutorial}/metadata.json`);
|
||||
|
||||
if (!tutorialSourceCode.ok) throw new Error(`Missing metadata for ${tutorial}`);
|
||||
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
export {EmbeddedTutorialManager} from './embedded-tutorial-manager.service';
|
||||
export {LoadingStep} from './enums/loading-steps';
|
||||
export {NodeRuntimeState} from './node-runtime-state.service';
|
||||
export { EmbeddedTutorialManager } from './embedded-tutorial-manager.service'
|
||||
export { LoadingStep } from './enums/loading-steps'
|
||||
export { NodeRuntimeState } from './node-runtime-state.service'
|
||||
|
||||
export {NodeRuntimeSandbox} from './node-runtime-sandbox.service';
|
||||
export { NodeRuntimeSandbox } from './node-runtime-sandbox.service'
|
||||
|
||||
export {EmbeddedEditor, EMBEDDED_EDITOR_SELECTOR} from './embedded-editor.component'
|
||||
|
||||
export {EmbeddedEditor, EMBEDDED_EDITOR_SELECTOR} from './embedded-editor.component';
|
||||
|
||||
@@ -12,7 +12,10 @@ import {BehaviorSubject, of as observableOf} from 'rxjs';
|
||||
import {signal} from '@angular/core';
|
||||
import {WebContainer} from '@webcontainer/api';
|
||||
import {TutorialType} from '@angular/docs';
|
||||
import {FakeWebContainer, FakeWebContainerProcess} from '@angular/docs/testing';
|
||||
import {
|
||||
FakeWebContainer,
|
||||
FakeWebContainerProcess,
|
||||
} from '@angular/docs/testing';
|
||||
import {AlertManager} from './alert-manager.service';
|
||||
import {EmbeddedTutorialManager} from './embedded-tutorial-manager.service';
|
||||
import {LoadingStep} from './enums/loading-steps';
|
||||
|
||||
@@ -77,7 +77,7 @@ export class NodeRuntimeState {
|
||||
* recommend using desktop.
|
||||
*/
|
||||
private checkUnsupportedEnvironment(): void {
|
||||
if (isIos) {
|
||||
if (isIos || isFirefox) {
|
||||
this.setError({
|
||||
message: 'Unsupported environment',
|
||||
type: ErrorType.UNSUPPORTED_BROWSER_ENVIRONMENT,
|
||||
|
||||
@@ -16,7 +16,7 @@ import {
|
||||
} from '@angular/core';
|
||||
|
||||
import {injectAsync} from '../../../core/services/inject-async';
|
||||
import {EmbeddedEditor, EmbeddedTutorialManager} from '../../../editor';
|
||||
import { EmbeddedEditor, EmbeddedTutorialManager } from '../../../editor';
|
||||
|
||||
@Component({
|
||||
selector: 'adev-code-editor',
|
||||
@@ -39,7 +39,9 @@ export class CodeEditorComponent implements OnInit {
|
||||
|
||||
private async loadEmbeddedEditor() {
|
||||
const nodeRuntimeSandbox = await injectAsync(this.environmentInjector, () =>
|
||||
import('../../../editor/index').then((c) => c.NodeRuntimeSandbox),
|
||||
import('../../../editor/index').then(
|
||||
(c) => c.NodeRuntimeSandbox,
|
||||
),
|
||||
);
|
||||
|
||||
await this.embeddedTutorialManager.fetchAndSetTutorialFiles(this.tutorialFiles);
|
||||
|
||||
@@ -31,10 +31,7 @@ export class BuildText extends View {
|
||||
/**
|
||||
* Create the build text.
|
||||
*/
|
||||
constructor(
|
||||
private readonly gl: OGLRenderingContext,
|
||||
private readonly document: Document,
|
||||
) {
|
||||
constructor(private readonly gl: OGLRenderingContext, private readonly document: Document) {
|
||||
super();
|
||||
|
||||
this.userData['x'] = 0;
|
||||
|
||||
@@ -27,10 +27,7 @@ export class BuildView extends View {
|
||||
/**
|
||||
* Create the build view.
|
||||
*/
|
||||
constructor(
|
||||
private readonly gl: OGLRenderingContext,
|
||||
private readonly document: Document,
|
||||
) {
|
||||
constructor(private readonly gl: OGLRenderingContext, private readonly document: Document) {
|
||||
super();
|
||||
|
||||
this.visible = false;
|
||||
|
||||
@@ -34,10 +34,7 @@ export class Lines extends View {
|
||||
/**
|
||||
* Create the container view.
|
||||
*/
|
||||
constructor(
|
||||
private readonly gl: OGLRenderingContext,
|
||||
private readonly divisions: number,
|
||||
) {
|
||||
constructor(private readonly gl: OGLRenderingContext, private readonly divisions: number) {
|
||||
super();
|
||||
|
||||
this.userData['x'] = 0;
|
||||
|
||||
@@ -23,7 +23,7 @@ import {RouterLink} from '@angular/router';
|
||||
import {PlaygroundTemplate} from '@angular/docs';
|
||||
import {ClickOutside, NavigationList} from '@angular/docs';
|
||||
import {injectAsync} from '../../core/services/inject-async';
|
||||
import {EmbeddedTutorialManager} from '../../editor/index';
|
||||
import {EmbeddedTutorialManager} from '../../editor/index';
|
||||
|
||||
import PLAYGROUND_ROUTE_DATA_JSON from '../../../../src/assets/tutorials/playground/routes.json';
|
||||
import {CdkMenu, CdkMenuItem, CdkMenuTrigger} from '@angular/cdk/menu';
|
||||
@@ -69,7 +69,9 @@ export default class PlaygroundComponent implements AfterViewInit {
|
||||
const [embeddedEditorComponent, nodeRuntimeSandbox] = await Promise.all([
|
||||
import('../../editor/index').then((c) => c.EmbeddedEditor),
|
||||
injectAsync(this.environmentInjector, () =>
|
||||
import('../../editor/index').then((c) => c.NodeRuntimeSandbox),
|
||||
import('../../editor/index').then(
|
||||
(c) => c.NodeRuntimeSandbox,
|
||||
),
|
||||
),
|
||||
]);
|
||||
|
||||
|
||||
+1
-5
@@ -51,8 +51,7 @@
|
||||
&:has(.docs-reference-api-tab) {
|
||||
width: 60%;
|
||||
}
|
||||
&:not(:has(.docs-reference-api-tab)) {
|
||||
width: 100%;
|
||||
&:not(.docs-reference-api-tab) {
|
||||
max-width: var(--page-width);
|
||||
}
|
||||
}
|
||||
@@ -99,14 +98,11 @@
|
||||
display: flex;
|
||||
gap: 1.81rem;
|
||||
align-items: flex-start;
|
||||
margin-bottom: 1px;
|
||||
|
||||
@include mq.for-desktop-down {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
& > .docs-code {
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
max-height: 93vh;
|
||||
overflow: hidden;
|
||||
|
||||
+1
-1
@@ -59,7 +59,7 @@ export default class CliReferenceDetailsPage implements OnInit {
|
||||
private setPageContent(): void {
|
||||
this.activatedRoute.data
|
||||
.pipe(
|
||||
map((data) => data['docContent']),
|
||||
map(data => data['docContent']),
|
||||
takeUntilDestroyed(this.destroyRef),
|
||||
)
|
||||
.subscribe((doc: DocContent | undefined) => {
|
||||
|
||||
@@ -18,12 +18,12 @@ import {
|
||||
EMBEDDED_EDITOR_SELECTOR,
|
||||
EmbeddedEditor,
|
||||
EmbeddedTutorialManager,
|
||||
NodeRuntimeSandbox,
|
||||
NodeRuntimeSandbox
|
||||
} from '../../editor';
|
||||
|
||||
import {mockAsyncProvider} from '../../core/services/inject-async';
|
||||
import Tutorial from './tutorial.component';
|
||||
import {TutorialConfig, TutorialType} from '@angular/docs';
|
||||
import {TutorialConfig, TutorialType} from '@angular/docs'
|
||||
|
||||
@Component({
|
||||
selector: EMBEDDED_EDITOR_SELECTOR,
|
||||
|
||||
@@ -38,11 +38,11 @@ import {
|
||||
EmbeddedTutorialManager,
|
||||
LoadingStep,
|
||||
NodeRuntimeState,
|
||||
EmbeddedEditor,
|
||||
EmbeddedEditor
|
||||
} from '../../editor/index';
|
||||
import {SplitResizerHandler} from './split-resizer-handler.service';
|
||||
import {TutorialType} from '@angular/docs';
|
||||
import {TutorialNavigationData, TutorialNavigationItem} from '@angular/docs';
|
||||
import { TutorialType } from '@angular/docs';
|
||||
import { TutorialNavigationData, TutorialNavigationItem, } from '@angular/docs'
|
||||
|
||||
const INTRODUCTION_LABEL = 'Introduction';
|
||||
|
||||
@@ -138,7 +138,9 @@ export default class Tutorial implements AfterViewInit {
|
||||
this.embeddedTutorialManager.revealAnswer();
|
||||
|
||||
const nodeRuntimeSandbox = await injectAsync(this.environmentInjector, () =>
|
||||
import('../../editor/index').then((s) => s.NodeRuntimeSandbox),
|
||||
import('../../editor/index').then(
|
||||
(s) => s.NodeRuntimeSandbox,
|
||||
),
|
||||
);
|
||||
|
||||
await Promise.all(
|
||||
@@ -211,10 +213,16 @@ export default class Tutorial implements AfterViewInit {
|
||||
|
||||
this.shouldRenderContent.set(routeData.type !== TutorialType.EDITOR_ONLY);
|
||||
|
||||
this.nextStepPath = routeData.nextStep ? `/${routeData.nextStep}` : undefined;
|
||||
this.previousStepPath = routeData.previousStep ? `/${routeData.previousStep}` : undefined;
|
||||
this.nextStepPath = routeData.nextStep
|
||||
? `/${routeData.nextStep}`
|
||||
: undefined;
|
||||
this.previousStepPath = routeData.previousStep
|
||||
? `/${routeData.previousStep}`
|
||||
: undefined;
|
||||
|
||||
this.nextTutorialPath.set(routeData.nextTutorial ? `/${routeData.nextTutorial}` : null);
|
||||
this.nextTutorialPath.set(
|
||||
routeData.nextTutorial ? `/${routeData.nextTutorial}` : null,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -6,7 +6,11 @@
|
||||
* found in the LICENSE file at https://angular.dev/license
|
||||
*/
|
||||
|
||||
import {contentResolver, flatNavigationData, mapNavigationItemsToRoutes} from '@angular/docs';
|
||||
import {
|
||||
contentResolver,
|
||||
flatNavigationData,
|
||||
mapNavigationItemsToRoutes,
|
||||
} from '@angular/docs';
|
||||
import {Route} from '@angular/router';
|
||||
|
||||
import {DefaultPage, PagePrefix} from './core/enums/pages';
|
||||
@@ -121,7 +125,7 @@ export const routes: Route[] = [
|
||||
{
|
||||
path: '',
|
||||
loadComponent: () => import('./features/home/home.component'),
|
||||
data: {label: 'Home'},
|
||||
data: {label: 'Home'}
|
||||
},
|
||||
{
|
||||
path: PagePrefix.DOCS,
|
||||
|
||||
+167
-187
@@ -157,11 +157,6 @@ const DOCS_SUB_NAVIGATION_DATA: NavigationItem[] = [
|
||||
path: 'guide/components/advanced-configuration',
|
||||
contentPath: 'guide/components/advanced-configuration',
|
||||
},
|
||||
{
|
||||
label: 'Custom Elements',
|
||||
path: 'guide/elements',
|
||||
contentPath: 'guide/elements',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
@@ -223,39 +218,9 @@ const DOCS_SUB_NAVIGATION_DATA: NavigationItem[] = [
|
||||
contentPath: 'guide/templates/control-flow',
|
||||
},
|
||||
{
|
||||
label: 'Pipes',
|
||||
children: [
|
||||
{
|
||||
label: 'Overview',
|
||||
path: 'guide/pipes',
|
||||
contentPath: 'guide/pipes/overview',
|
||||
},
|
||||
{
|
||||
label: 'Using a pipe in a template',
|
||||
path: 'guide/pipes/template',
|
||||
contentPath: 'guide/pipes/template',
|
||||
},
|
||||
{
|
||||
label: 'Custom pipes',
|
||||
path: 'guide/pipes/transform-data',
|
||||
contentPath: 'guide/pipes/transform-data',
|
||||
},
|
||||
{
|
||||
label: 'Pipe precedence in expressions',
|
||||
path: 'guide/pipes/precedence',
|
||||
contentPath: 'guide/pipes/precedence',
|
||||
},
|
||||
{
|
||||
label: 'Change detection with pipes',
|
||||
path: 'guide/pipes/change-detection',
|
||||
contentPath: 'guide/pipes/change-detection',
|
||||
},
|
||||
{
|
||||
label: 'Unwrapping data from an observable',
|
||||
path: 'guide/pipes/unwrapping-data-observables',
|
||||
contentPath: 'guide/pipes/unwrapping-data-observables',
|
||||
},
|
||||
],
|
||||
label: 'Deferrable views',
|
||||
path: 'guide/defer',
|
||||
contentPath: 'guide/defer',
|
||||
},
|
||||
{
|
||||
label: 'Template reference variables',
|
||||
@@ -352,20 +317,70 @@ const DOCS_SUB_NAVIGATION_DATA: NavigationItem[] = [
|
||||
path: 'guide/signals/rxjs-interop',
|
||||
contentPath: 'guide/signals/rxjs-interop',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: 'NgModule',
|
||||
children: [
|
||||
{
|
||||
label: 'Inputs as signals',
|
||||
path: 'guide/signals/inputs',
|
||||
contentPath: 'guide/signals/inputs',
|
||||
label: 'Overview',
|
||||
path: 'guide/ngmodules',
|
||||
contentPath: 'guide/ngmodules/overview',
|
||||
},
|
||||
{
|
||||
label: 'Model inputs',
|
||||
path: 'guide/signals/model',
|
||||
contentPath: 'guide/signals/model',
|
||||
label: 'JS Modules vs NgModules',
|
||||
path: 'guide/ngmodules/vs-jsmodule',
|
||||
contentPath: 'guide/ngmodules/vs-jsmodule',
|
||||
},
|
||||
{
|
||||
label: 'Queries as signals',
|
||||
path: 'guide/signals/queries',
|
||||
contentPath: 'guide/signals/queries',
|
||||
label: 'Launching your app with a root module',
|
||||
path: 'guide/ngmodules/bootstrapping',
|
||||
contentPath: 'guide/ngmodules/bootstrapping',
|
||||
},
|
||||
{
|
||||
label: 'Sharing NgModules',
|
||||
path: 'guide/ngmodules/sharing',
|
||||
contentPath: 'guide/ngmodules/sharing',
|
||||
},
|
||||
{
|
||||
label: 'Frequently used NgModules',
|
||||
path: 'guide/ngmodules/frequent',
|
||||
contentPath: 'guide/ngmodules/frequent',
|
||||
},
|
||||
{
|
||||
label: 'Feature modules',
|
||||
path: 'guide/ngmodules/feature-modules',
|
||||
contentPath: 'guide/ngmodules/feature-modules',
|
||||
},
|
||||
{
|
||||
label: 'Types of feature modules',
|
||||
path: 'guide/ngmodules/module-types',
|
||||
contentPath: 'guide/ngmodules/module-types',
|
||||
},
|
||||
{
|
||||
label: 'Providing dependencies',
|
||||
path: 'guide/ngmodules/providers',
|
||||
contentPath: 'guide/ngmodules/providers',
|
||||
},
|
||||
{
|
||||
label: 'Singleton services',
|
||||
path: 'guide/ngmodules/singleton-services',
|
||||
contentPath: 'guide/ngmodules/singleton-services',
|
||||
},
|
||||
{
|
||||
label: 'Lazy-loading feature modules',
|
||||
path: 'guide/ngmodules/lazy-loading',
|
||||
contentPath: 'guide/ngmodules/lazy-loading',
|
||||
},
|
||||
{
|
||||
label: 'NgModule API',
|
||||
path: 'guide/ngmodules/api',
|
||||
contentPath: 'guide/ngmodules/api',
|
||||
},
|
||||
{
|
||||
label: 'NgModule FAQs',
|
||||
path: 'guide/ngmodules/faq',
|
||||
contentPath: 'guide/ngmodules/faq',
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -435,64 +450,29 @@ const DOCS_SUB_NAVIGATION_DATA: NavigationItem[] = [
|
||||
],
|
||||
},
|
||||
{
|
||||
label: 'HTTP Client',
|
||||
children: [
|
||||
{
|
||||
label: 'Overview',
|
||||
path: 'guide/http',
|
||||
contentPath: 'guide/http/overview',
|
||||
},
|
||||
{
|
||||
label: 'Setting up HttpClient',
|
||||
path: 'guide/http/setup',
|
||||
contentPath: 'guide/http/setup',
|
||||
},
|
||||
{
|
||||
label: 'Making requests',
|
||||
path: 'guide/http/making-requests',
|
||||
contentPath: 'guide/http/making-requests',
|
||||
},
|
||||
{
|
||||
label: 'Intercepting requests and responses',
|
||||
path: 'guide/http/interceptors',
|
||||
contentPath: 'guide/http/interceptors',
|
||||
},
|
||||
{
|
||||
label: 'Testing',
|
||||
path: 'guide/http/testing',
|
||||
contentPath: 'guide/http/testing',
|
||||
},
|
||||
],
|
||||
label: 'Server-side Rendering',
|
||||
path: 'guide/ssr',
|
||||
contentPath: 'guide/ssr',
|
||||
},
|
||||
{
|
||||
label: 'Performance',
|
||||
children: [
|
||||
{
|
||||
label: 'Deferrable views',
|
||||
path: 'guide/defer',
|
||||
contentPath: 'guide/defer',
|
||||
},
|
||||
{
|
||||
label: 'Image Optimization',
|
||||
path: 'guide/image-optimization',
|
||||
contentPath: 'guide/image-optimization',
|
||||
},
|
||||
{
|
||||
label: 'Server-side Rendering',
|
||||
path: 'guide/ssr',
|
||||
contentPath: 'guide/ssr',
|
||||
},
|
||||
{
|
||||
label: 'Build-time prerendering',
|
||||
path: 'guide/prerendering',
|
||||
contentPath: 'guide/prerendering',
|
||||
},
|
||||
{
|
||||
label: 'Hydration',
|
||||
path: 'guide/hydration',
|
||||
contentPath: 'guide/hydration',
|
||||
},
|
||||
],
|
||||
label: 'Build-time prerendering',
|
||||
path: 'guide/prerendering',
|
||||
contentPath: 'guide/prerendering',
|
||||
},
|
||||
{
|
||||
label: 'Hydration',
|
||||
path: 'guide/hydration',
|
||||
contentPath: 'guide/hydration',
|
||||
},
|
||||
{
|
||||
label: 'Deferrable views',
|
||||
path: 'guide/defer',
|
||||
contentPath: 'guide/defer',
|
||||
},
|
||||
{
|
||||
label: 'Image Optimization',
|
||||
path: 'guide/image-optimization',
|
||||
contentPath: 'guide/image-optimization',
|
||||
},
|
||||
{
|
||||
label: 'Testing',
|
||||
@@ -604,6 +584,81 @@ const DOCS_SUB_NAVIGATION_DATA: NavigationItem[] = [
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: 'Security',
|
||||
path: 'guide/security',
|
||||
contentPath: 'guide/security',
|
||||
},
|
||||
{
|
||||
label: 'Pipes',
|
||||
children: [
|
||||
{
|
||||
label: 'Overview',
|
||||
path: 'guide/pipes',
|
||||
contentPath: 'guide/pipes/overview',
|
||||
},
|
||||
{
|
||||
label: 'Using a pipe in a template',
|
||||
path: 'guide/pipes/template',
|
||||
contentPath: 'guide/pipes/template',
|
||||
},
|
||||
{
|
||||
label: 'Custom pipes',
|
||||
path: 'guide/pipes/transform-data',
|
||||
contentPath: 'guide/pipes/transform-data',
|
||||
},
|
||||
{
|
||||
label: 'Pipe precedence in expressions',
|
||||
path: 'guide/pipes/precedence',
|
||||
contentPath: 'guide/pipes/precedence',
|
||||
},
|
||||
{
|
||||
label: 'Change detection with pipes',
|
||||
path: 'guide/pipes/change-detection',
|
||||
contentPath: 'guide/pipes/change-detection',
|
||||
},
|
||||
{
|
||||
label: 'Unwrapping data from an observable',
|
||||
path: 'guide/pipes/unwrapping-data-observables',
|
||||
contentPath: 'guide/pipes/unwrapping-data-observables',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: 'Http Client',
|
||||
children: [
|
||||
{
|
||||
label: 'Overview',
|
||||
path: 'guide/http',
|
||||
contentPath: 'guide/http/overview',
|
||||
},
|
||||
{
|
||||
label: 'Setting up HttpClient',
|
||||
path: 'guide/http/setup',
|
||||
contentPath: 'guide/http/setup',
|
||||
},
|
||||
{
|
||||
label: 'Making requests',
|
||||
path: 'guide/http/making-requests',
|
||||
contentPath: 'guide/http/making-requests',
|
||||
},
|
||||
{
|
||||
label: 'Intercepting requests and responses',
|
||||
path: 'guide/http/interceptors',
|
||||
contentPath: 'guide/http/interceptors',
|
||||
},
|
||||
{
|
||||
label: 'Security',
|
||||
path: 'guide/http/security',
|
||||
contentPath: 'guide/http/security',
|
||||
},
|
||||
{
|
||||
label: 'Testing',
|
||||
path: 'guide/http/testing',
|
||||
contentPath: 'guide/http/testing',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: 'Animations',
|
||||
children: [
|
||||
@@ -634,6 +689,11 @@ const DOCS_SUB_NAVIGATION_DATA: NavigationItem[] = [
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: 'Custom Elements',
|
||||
path: 'guide/elements',
|
||||
contentPath: 'guide/elements',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
@@ -765,9 +825,9 @@ const DOCS_SUB_NAVIGATION_DATA: NavigationItem[] = [
|
||||
contentPath: 'best-practices/style-guide',
|
||||
},
|
||||
{
|
||||
label: 'Security',
|
||||
path: 'best-practices/security',
|
||||
contentPath: 'guide/security', // Have not refactored due to build issues
|
||||
label: 'Keeping up-to-date',
|
||||
path: 'update',
|
||||
contentPath: 'best-practices/update',
|
||||
},
|
||||
{
|
||||
label: 'Accessibility',
|
||||
@@ -799,11 +859,6 @@ const DOCS_SUB_NAVIGATION_DATA: NavigationItem[] = [
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: 'Keeping up-to-date',
|
||||
path: 'update',
|
||||
contentPath: 'best-practices/update',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
@@ -888,7 +943,7 @@ export const TUTORIALS_SUB_NAVIGATION_DATA: NavigationItem[] = [
|
||||
{
|
||||
path: DefaultPage.TUTORIALS,
|
||||
contentPath: 'tutorials/home',
|
||||
label: 'Tutorials',
|
||||
label: 'Tutorials'
|
||||
},
|
||||
];
|
||||
|
||||
@@ -1289,81 +1344,6 @@ const REFERENCE_SUB_NAVIGATION_DATA: NavigationItem[] = [
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: 'Concepts',
|
||||
children: [
|
||||
{
|
||||
label: 'Overview',
|
||||
path: 'reference/concepts',
|
||||
contentPath: 'reference/concepts/overview',
|
||||
},
|
||||
{
|
||||
label: 'NgModule',
|
||||
children: [
|
||||
{
|
||||
label: 'Overview',
|
||||
path: 'guide/ngmodules',
|
||||
contentPath: 'guide/ngmodules/overview',
|
||||
},
|
||||
{
|
||||
label: 'JS Modules vs NgModules',
|
||||
path: 'guide/ngmodules/vs-jsmodule',
|
||||
contentPath: 'guide/ngmodules/vs-jsmodule',
|
||||
},
|
||||
{
|
||||
label: 'Launching your app with a root module',
|
||||
path: 'guide/ngmodules/bootstrapping',
|
||||
contentPath: 'guide/ngmodules/bootstrapping',
|
||||
},
|
||||
{
|
||||
label: 'Sharing NgModules',
|
||||
path: 'guide/ngmodules/sharing',
|
||||
contentPath: 'guide/ngmodules/sharing',
|
||||
},
|
||||
{
|
||||
label: 'Frequently used NgModules',
|
||||
path: 'guide/ngmodules/frequent',
|
||||
contentPath: 'guide/ngmodules/frequent',
|
||||
},
|
||||
{
|
||||
label: 'Feature modules',
|
||||
path: 'guide/ngmodules/feature-modules',
|
||||
contentPath: 'guide/ngmodules/feature-modules',
|
||||
},
|
||||
{
|
||||
label: 'Types of feature modules',
|
||||
path: 'guide/ngmodules/module-types',
|
||||
contentPath: 'guide/ngmodules/module-types',
|
||||
},
|
||||
{
|
||||
label: 'Providing dependencies',
|
||||
path: 'guide/ngmodules/providers',
|
||||
contentPath: 'guide/ngmodules/providers',
|
||||
},
|
||||
{
|
||||
label: 'Singleton services',
|
||||
path: 'guide/ngmodules/singleton-services',
|
||||
contentPath: 'guide/ngmodules/singleton-services',
|
||||
},
|
||||
{
|
||||
label: 'Lazy-loading feature modules',
|
||||
path: 'guide/ngmodules/lazy-loading',
|
||||
contentPath: 'guide/ngmodules/lazy-loading',
|
||||
},
|
||||
{
|
||||
label: 'NgModule API',
|
||||
path: 'guide/ngmodules/api',
|
||||
contentPath: 'guide/ngmodules/api',
|
||||
},
|
||||
{
|
||||
label: 'NgModule FAQs',
|
||||
path: 'guide/ngmodules/faq',
|
||||
contentPath: 'guide/ngmodules/faq',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
const FOOTER_NAVIGATION_DATA: NavigationItem[] = [
|
||||
|
||||
@@ -5,7 +5,6 @@ package(default_visibility = ["//adev:__subpackages__"])
|
||||
copy_to_directory(
|
||||
name = "content",
|
||||
srcs = [
|
||||
"//adev/src/content",
|
||||
"//adev/src/content/best-practices",
|
||||
"//adev/src/content/best-practices/runtime-performance",
|
||||
"//adev/src/content/ecosystem",
|
||||
@@ -27,7 +26,6 @@ copy_to_directory(
|
||||
"//adev/src/content/introduction",
|
||||
"//adev/src/content/introduction/essentials",
|
||||
"//adev/src/content/reference",
|
||||
"//adev/src/content/reference/concepts",
|
||||
"//adev/src/content/reference/configs",
|
||||
"//adev/src/content/reference/errors",
|
||||
"//adev/src/content/reference/extended-diagnostics",
|
||||
@@ -38,7 +36,6 @@ copy_to_directory(
|
||||
"//adev/src/content/tutorials",
|
||||
"//adev/src/content/tutorials/first-app:first-app-guides",
|
||||
"//adev/src/content/tutorials/learn-angular:learn-angular-guides",
|
||||
"//aio/content/cli/help:cli_docs",
|
||||
"//packages/animations:animations_docs",
|
||||
"//packages/animations/browser:animations_browser_docs",
|
||||
"//packages/animations/browser/testing:animations_browser_testing_docs",
|
||||
@@ -48,13 +45,11 @@ copy_to_directory(
|
||||
"//packages/common/testing:common_testing_docs",
|
||||
"//packages/common/upgrade:common_upgrade_docs",
|
||||
"//packages/core:core_docs",
|
||||
"//packages/core/global:core_global_docs",
|
||||
"//packages/core/rxjs-interop:core_rxjs-interop_docs",
|
||||
"//packages/core/testing:core_testing_docs",
|
||||
"//packages/elements:elements_docs",
|
||||
"//packages/forms:forms_docs",
|
||||
"//packages/localize:localize_docs",
|
||||
"//packages/localize/init:localize_docs",
|
||||
"//packages/platform-browser:platform-browser_docs",
|
||||
"//packages/platform-browser-dynamic:platform-browser_dynamic_docs",
|
||||
"//packages/platform-browser-dynamic/testing:platform-browser_dynamic_testing_docs",
|
||||
@@ -74,7 +69,6 @@ copy_to_directory(
|
||||
"//tools/manual_api_docs/elements:elements_docs",
|
||||
],
|
||||
replace_prefixes = {
|
||||
"aio/content/cli/**/": "cli/",
|
||||
"adev/src/content": "",
|
||||
"packages/**/": "api/",
|
||||
"tools/**/": "api/",
|
||||
|
||||
@@ -6,7 +6,7 @@ exports_files(
|
||||
|
||||
copy_to_bin(
|
||||
name = "images",
|
||||
srcs = glob(["**/*"]),
|
||||
srcs = glob(["*"]),
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
],
|
||||
|
||||
@@ -18,4 +18,5 @@ import {Type} from '@angular/core';
|
||||
* Map of the previews components, values are functions which returns the promise of the component type, which will be displayed as preview in the ExampleViewer component.
|
||||
* Keys has to be equal to paths written down in the docs markdown files.
|
||||
*/
|
||||
export const PREVIEWS_COMPONENTS_MAP: Record<string, () => Promise<Type<unknown>>> = {};
|
||||
export const PREVIEWS_COMPONENTS_MAP: Record<string, () => Promise<Type<unknown>>> = {
|
||||
};
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
load("@npm//@angular/docs:index.bzl", "generate_guides")
|
||||
|
||||
generate_guides(
|
||||
name = "content",
|
||||
srcs = [
|
||||
"error.md",
|
||||
],
|
||||
data = [],
|
||||
visibility = ["//adev:__subpackages__"],
|
||||
)
|
||||
@@ -82,10 +82,7 @@ export class NgComponentOutletCompleteExample implements OnInit {
|
||||
@ViewChild('svet', {static: true}) svetTemplateRef!: TemplateRef<any>;
|
||||
myContent?: any[][];
|
||||
|
||||
constructor(
|
||||
injector: Injector,
|
||||
private vcr: ViewContainerRef,
|
||||
) {
|
||||
constructor(injector: Injector, private vcr: ViewContainerRef) {
|
||||
this.myInjector = Injector.create({
|
||||
providers: [{provide: Greeter, deps: []}],
|
||||
parent: injector,
|
||||
|
||||
@@ -51,10 +51,7 @@ class DataListProvider {
|
||||
`,
|
||||
})
|
||||
class GiantList {
|
||||
constructor(
|
||||
private ref: ChangeDetectorRef,
|
||||
public dataProvider: DataListProvider,
|
||||
) {
|
||||
constructor(private ref: ChangeDetectorRef, public dataProvider: DataListProvider) {
|
||||
ref.detach();
|
||||
setInterval(() => {
|
||||
this.ref.detectChanges();
|
||||
@@ -84,10 +81,7 @@ class DataProvider {
|
||||
|
||||
@Component({selector: 'live-data', inputs: ['live'], template: 'Data: {{dataProvider.data}}'})
|
||||
class LiveData {
|
||||
constructor(
|
||||
private ref: ChangeDetectorRef,
|
||||
public dataProvider: DataProvider,
|
||||
) {}
|
||||
constructor(private ref: ChangeDetectorRef, public dataProvider: DataProvider) {}
|
||||
|
||||
@Input()
|
||||
set live(value: boolean) {
|
||||
|
||||
+1
-4
@@ -70,10 +70,7 @@ describe('navigate for test examples', () => {
|
||||
`,
|
||||
})
|
||||
class SearchCmp {
|
||||
constructor(
|
||||
readonly route: ActivatedRoute,
|
||||
readonly router: Router,
|
||||
) {}
|
||||
constructor(readonly route: ActivatedRoute, readonly router: Router) {}
|
||||
|
||||
async searchFor(thing: string) {
|
||||
await this.router.navigate([], {queryParams: {query: thing}});
|
||||
|
||||
@@ -5,6 +5,5 @@ generate_guides(
|
||||
srcs = glob([
|
||||
"*.md",
|
||||
]),
|
||||
mermaid_blocks = True,
|
||||
visibility = ["//adev:__subpackages__"],
|
||||
)
|
||||
|
||||
@@ -33,7 +33,8 @@ If Angular handles an event within a component without `OnPush` strategy, the fr
|
||||
|
||||
As an example, if we set the change detection strategy of `MainComponent` to `OnPush` and the user interacts with a component outside the subtree with root `MainComponent`, Angular will check all the green components from the diagram below (`AppComponent`, `HeaderComponent`, `SearchComponent`, `ButtonComponent`) unless `MainComponent` receives new inputs:
|
||||
|
||||
```mermaid
|
||||
<!-- TODO(josephperrott): enable this mermaid chart -->
|
||||
```
|
||||
graph TD;
|
||||
app[AppComponent] --- header[HeaderComponent];
|
||||
app --- main["MainComponent (OnPush)"];
|
||||
@@ -59,9 +60,10 @@ 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">
|
||||
<img alt="Change detection propagation from OnPush component" src="assets/content/images/best-practices/runtime-performance/on-push-trigger.svg">
|
||||
|
||||
```mermaid
|
||||
<!-- TODO(josephperrott): enable this mermaid chart -->
|
||||
```
|
||||
graph TD;
|
||||
app[AppComponent] --- header[HeaderComponent];
|
||||
app --- main["MainComponent (OnPush)"];
|
||||
@@ -87,7 +89,8 @@ If Angular handles an event in a component with OnPush, the framework will execu
|
||||
|
||||
As an example, in the diagram below, Angular handles an event in `LoginComponent` which uses OnPush. Angular will invoke change detection in the entire component subtree including `MainComponent` (`LoginComponent`’s parent), even though `MainComponent` has `OnPush` as well. Angular checks `MainComponent` as well because `LoginComponent` is part of its view.
|
||||
|
||||
```mermaid
|
||||
<!-- TODO(josephperrott): enable this mermaid chart -->
|
||||
```
|
||||
graph TD;
|
||||
app[AppComponent] --- header[HeaderComponent];
|
||||
app --- main["MainComponent (OnPush)"];
|
||||
@@ -112,7 +115,8 @@ Angular will run change detection within a child component with `OnPush` when se
|
||||
|
||||
For example, in the diagram below, `AppComponent` passes a new input to `MainComponent`, which has `OnPush`. Angular will run change detection in `MainComponent` but will not run change detection in `LoginComponent`, which also has `OnPush`, unless it receives new inputs as well.
|
||||
|
||||
```mermaid
|
||||
<!-- TODO(josephperrott): enable this mermaid chart -->
|
||||
```
|
||||
graph TD;
|
||||
app[AppComponent] --- header[HeaderComponent];
|
||||
app --- main["MainComponent (OnPush)"];
|
||||
|
||||
@@ -10,7 +10,7 @@ A single slow computation within a template or a lifecycle hook can slow down th
|
||||
|
||||
You can identify heavy computations with Angular DevTools’ profiler. In the performance timeline, click a bar to preview a particular change detection cycle. This displays a bar chart, which shows how long the framework spent in change detection for each component. When you click a component, you can preview how long Angular spent evaluating its template and lifecycle hooks.
|
||||
|
||||
<img alt="Angular DevTools profiler preview showing slow computation" src="assets/images/best-practices/runtime-performance/slow-computations.png">
|
||||
<img alt="Angular DevTools profiler preview showing slow computation" src="assets/content/images/best-practices/runtime-performance/slow-computations.png">
|
||||
|
||||
For example, in the preceding screenshot, the second recorded change detection cycle is selected. Angular spent over 573 ms on this cycle, with the most time spent in the `EmployeeListComponent`. In the details panel, you can see that Angular spent over 297 ms evaluating the template of the `EmployeeListComponent`.
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ This section covers how to identify such conditions, and how to run code outside
|
||||
|
||||
You can detect unnecessary change detection calls using Angular DevTools. Often they appear as consecutive bars in the profiler’s timeline with source `setTimeout`, `setInterval`, `requestAnimationFrame`, or an event handler. When you have limited calls within your application of these APIs, the change detection invocation is usually caused by a third-party library.
|
||||
|
||||
<img alt="Angular DevTools profiler preview showing Zone pollution" src="assets/images/best-practices/runtime-performance/zone-pollution.png">
|
||||
<img alt="Angular DevTools profiler preview showing Zone pollution" src="assets/content/images/best-practices/runtime-performance/zone-pollution.png">
|
||||
|
||||
In the image above, there is a series of change detection calls triggered by event handlers associated with an element. That’s a common challenge when using third-party, non-native Angular components, which do not alter the default behavior of `NgZone`.
|
||||
|
||||
|
||||
@@ -884,7 +884,7 @@ Here are templates and/or snippets for some of the web development editors and I
|
||||
|
||||
<a href="https://marketplace.visualstudio.com/items?itemName=johnpapa.Angular2">
|
||||
|
||||
<img alt="Use Extension" src="assets/images/guide/styleguide/use-extension.gif">
|
||||
<img alt="Use Extension" src="assets/content/images/guide/styleguide/use-extension.gif">
|
||||
|
||||
</a>
|
||||
|
||||
|
||||
@@ -58,7 +58,7 @@ In Chrome:
|
||||
1. Go to the **Network tab**.
|
||||
1. Select **Offline** in the **Throttling** dropdown menu.
|
||||
|
||||
<img alt="The offline option in the Network tab is selected" src="assets/images/guide/service-worker/offline-option.png">
|
||||
<img alt="The offline option in the Network tab is selected" src="assets/content/images/guide/service-worker/offline-option.png">
|
||||
|
||||
Now the application has no access to network interaction.
|
||||
|
||||
@@ -69,7 +69,7 @@ On a refresh, the page loads normally.
|
||||
|
||||
Look at the Network tab to verify that the service worker is active.
|
||||
|
||||
<img alt="Requests are marked as from ServiceWorker" src="assets/images/guide/service-worker/sw-active.png">
|
||||
<img alt="Requests are marked as from ServiceWorker" src="assets/content/images/guide/service-worker/sw-active.png">
|
||||
|
||||
HELPFUL: Under the "Size" column, the requests state is `(ServiceWorker)`.
|
||||
This means that the resources are not being loaded from the network.
|
||||
@@ -121,7 +121,7 @@ Now look at how the browser and service worker handle the updated application.
|
||||
1. Open [http://localhost:8080](http://localhost:8080) again in the same window.
|
||||
What happens?
|
||||
|
||||
<img alt="It still says Welcome to Service Workers!" src="assets/images/guide/service-worker/welcome-msg-en.png">
|
||||
<img alt="It still says Welcome to Service Workers!" src="assets/content/images/guide/service-worker/welcome-msg-en.png">
|
||||
|
||||
What went wrong?
|
||||
_Nothing, actually!_
|
||||
@@ -138,7 +138,7 @@ Now look at how the browser and service worker handle the updated application.
|
||||
|
||||
1. Refresh the page.
|
||||
|
||||
<img alt="The text has changed to say Bienvenue à app!" src="assets/images/guide/service-worker/welcome-msg-fr.png">
|
||||
<img alt="The text has changed to say Bienvenue à app!" src="assets/content/images/guide/service-worker/welcome-msg-fr.png">
|
||||
|
||||
The service worker installed the updated version of your application _in the background_, and the next time the page is loaded or reloaded, the service worker switches to the latest version.
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import {browser, element, by} from 'protractor';
|
||||
import { browser, element, by } from 'protractor';
|
||||
|
||||
describe('Accessibility example e2e tests', () => {
|
||||
|
||||
beforeEach(() => browser.get(''));
|
||||
|
||||
it('should display Accessibility Example', async () => {
|
||||
@@ -12,4 +13,5 @@ describe('Accessibility example e2e tests', () => {
|
||||
expect(await element(by.css('input')).getAttribute('value')).toEqual('16');
|
||||
expect(await element(by.css('app-example-progressbar div')).getCssValue('width')).toBe('48px');
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import {AppPage} from './app.po';
|
||||
import {browser, logging} from 'protractor';
|
||||
import { AppPage } from './app.po';
|
||||
import { browser, logging } from 'protractor';
|
||||
|
||||
describe('workspace-project App', () => {
|
||||
let page: AppPage;
|
||||
@@ -13,10 +13,8 @@ describe('workspace-project App', () => {
|
||||
afterEach(async () => {
|
||||
// Assert that there are no errors emitted from the browser
|
||||
const logs = await browser.manage().logs().get(logging.Type.BROWSER);
|
||||
expect(logs).not.toContain(
|
||||
jasmine.objectContaining({
|
||||
level: logging.Level.SEVERE,
|
||||
} as logging.Entry),
|
||||
);
|
||||
expect(logs).not.toContain(jasmine.objectContaining({
|
||||
level: logging.Level.SEVERE,
|
||||
} as logging.Entry));
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
import {TestBed} from '@angular/core/testing';
|
||||
import {AppComponent} from './app.component';
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
import { AppComponent } from './app.component';
|
||||
|
||||
describe('AppComponent', () => {
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [AppComponent],
|
||||
declarations: [
|
||||
AppComponent
|
||||
],
|
||||
}).compileComponents();
|
||||
});
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import {browser} from 'protractor';
|
||||
import {logging} from 'selenium-webdriver';
|
||||
import { browser } from 'protractor';
|
||||
import { logging } from 'selenium-webdriver';
|
||||
import * as openClose from './open-close.po';
|
||||
import * as statusSlider from './status-slider.po';
|
||||
import * as toggle from './toggle.po';
|
||||
@@ -7,8 +7,8 @@ import * as enterLeave from './enter-leave.po';
|
||||
import * as auto from './auto.po';
|
||||
import * as filterStagger from './filter-stagger.po';
|
||||
import * as heroGroups from './hero-groups';
|
||||
import {getLinkById, sleepFor} from './util';
|
||||
import {getComponentSection, getToggleButton} from './querying.po';
|
||||
import { getLinkById, sleepFor } from './util';
|
||||
import { getComponentSection, getToggleButton } from './querying.po';
|
||||
|
||||
describe('Animation Tests', () => {
|
||||
const routingAnimationDuration = 350;
|
||||
@@ -42,10 +42,7 @@ describe('Animation Tests', () => {
|
||||
|
||||
if (text.includes('Closed')) {
|
||||
await toggleButton.click();
|
||||
await browser.wait(
|
||||
async () => (await container.getCssValue('height')) === openHeight,
|
||||
2000,
|
||||
);
|
||||
await browser.wait(async () => await container.getCssValue('height') === openHeight, 2000);
|
||||
}
|
||||
|
||||
text = await container.getText();
|
||||
@@ -62,10 +59,7 @@ describe('Animation Tests', () => {
|
||||
|
||||
if (text.includes('Open')) {
|
||||
await toggleButton.click();
|
||||
await browser.wait(
|
||||
async () => (await container.getCssValue('height')) === closedHeight,
|
||||
2000,
|
||||
);
|
||||
await browser.wait(async () => await container.getCssValue('height') === closedHeight, 2000);
|
||||
}
|
||||
|
||||
text = await container.getText();
|
||||
@@ -82,7 +76,7 @@ describe('Animation Tests', () => {
|
||||
await toggleButton.click();
|
||||
|
||||
const logs = await browser.manage().logs().get(logging.Type.BROWSER);
|
||||
const animationMessages = logs.filter(({message}) => message.includes('Animation'));
|
||||
const animationMessages = logs.filter(({ message }) => message.includes('Animation'));
|
||||
|
||||
expect(animationMessages.length).toBeGreaterThan(0);
|
||||
});
|
||||
@@ -104,10 +98,7 @@ describe('Animation Tests', () => {
|
||||
|
||||
if (text === 'Active') {
|
||||
await toggleButton.click();
|
||||
await browser.wait(
|
||||
async () => (await container.getCssValue('backgroundColor')) === inactiveColor,
|
||||
3000,
|
||||
);
|
||||
await browser.wait(async () => await container.getCssValue('backgroundColor') === inactiveColor, 3000);
|
||||
}
|
||||
|
||||
text = await container.getText();
|
||||
@@ -124,10 +115,7 @@ describe('Animation Tests', () => {
|
||||
|
||||
if (text === 'Inactive') {
|
||||
await toggleButton.click();
|
||||
await browser.wait(
|
||||
async () => (await container.getCssValue('backgroundColor')) === activeColor,
|
||||
3000,
|
||||
);
|
||||
await browser.wait(async () => await container.getCssValue('backgroundColor') === activeColor, 3000);
|
||||
}
|
||||
|
||||
text = await container.getText();
|
||||
@@ -178,7 +166,7 @@ describe('Animation Tests', () => {
|
||||
const hero = heroesList.get(0);
|
||||
|
||||
await hero.click();
|
||||
await browser.wait(async () => (await heroesList.count()) < total, 2000);
|
||||
await browser.wait(async () => await heroesList.count() < total, 2000);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -202,7 +190,7 @@ describe('Animation Tests', () => {
|
||||
const hero = heroesList.get(0);
|
||||
|
||||
await hero.click();
|
||||
await browser.wait(async () => (await heroesList.count()) < total, 2000);
|
||||
await browser.wait(async () => await heroesList.count() < total, 2000);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -226,7 +214,7 @@ describe('Animation Tests', () => {
|
||||
const input = filterStagger.getInput();
|
||||
await input.sendKeys('Mag');
|
||||
|
||||
await browser.wait(async () => (await heroesList.count()) === 2, 2000);
|
||||
await browser.wait(async () => await heroesList.count() === 2, 2000);
|
||||
|
||||
const newTotal = await heroesList.count();
|
||||
expect(newTotal).toBeLessThan(total);
|
||||
@@ -257,7 +245,7 @@ describe('Animation Tests', () => {
|
||||
const hero = heroesList.get(0);
|
||||
|
||||
await hero.click();
|
||||
await browser.wait(async () => (await heroesList.count()) < total, 2000);
|
||||
await browser.wait(async () => await heroesList.count() < total, 2000);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -303,5 +291,6 @@ describe('Animation Tests', () => {
|
||||
await newPageSleepFor(queryingAnimationDuration);
|
||||
expect(await toggleButton.isEnabled()).toBe(true);
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import {by} from 'protractor';
|
||||
import {locate} from './util';
|
||||
import { by } from 'protractor';
|
||||
import { locate } from './util';
|
||||
|
||||
export function getPage() {
|
||||
return by.css('app-hero-list-auto-page');
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import {by} from 'protractor';
|
||||
import {locate} from './util';
|
||||
import { by } from 'protractor';
|
||||
import { locate } from './util';
|
||||
|
||||
export function getPage() {
|
||||
return by.css('app-hero-list-enter-leave-page');
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import {by} from 'protractor';
|
||||
import {locate} from './util';
|
||||
import { by } from 'protractor';
|
||||
import { locate } from './util';
|
||||
|
||||
export function getPage() {
|
||||
return by.css('app-hero-list-page');
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import {by} from 'protractor';
|
||||
import {locate} from './util';
|
||||
import { by } from 'protractor';
|
||||
import { locate } from './util';
|
||||
|
||||
export function getPage() {
|
||||
return by.css('app-hero-list-groups-page');
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import {by} from 'protractor';
|
||||
import {locate} from './util';
|
||||
import { by } from 'protractor';
|
||||
import { locate } from './util';
|
||||
|
||||
export function getPage() {
|
||||
return by.css('app-open-close-page');
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import {by} from 'protractor';
|
||||
import {locate} from './util';
|
||||
import { by } from 'protractor';
|
||||
import { locate } from './util';
|
||||
|
||||
export function getComponent() {
|
||||
return by.css('app-querying');
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import {by} from 'protractor';
|
||||
import {locate} from './util';
|
||||
import { by } from 'protractor';
|
||||
import { locate } from './util';
|
||||
|
||||
export function getPage() {
|
||||
return by.css('app-status-slider-page');
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import {by} from 'protractor';
|
||||
import {locate} from './util';
|
||||
import { by } from 'protractor';
|
||||
import { locate } from './util';
|
||||
|
||||
export function getPage() {
|
||||
return by.css('app-toggle-animations-child-page');
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import {Locator, ElementFinder, browser, by, element} from 'protractor';
|
||||
import { Locator, ElementFinder, browser, by, element } from 'protractor';
|
||||
|
||||
/**
|
||||
*
|
||||
@@ -6,9 +6,8 @@ import {Locator, ElementFinder, browser, by, element} from 'protractor';
|
||||
*/
|
||||
export function locate(locator: Locator, ...locators: Locator[]) {
|
||||
return locators.reduce(
|
||||
(current: ElementFinder, next: Locator) => current.element(next),
|
||||
element(locator),
|
||||
) as ElementFinder;
|
||||
(current: ElementFinder, next: Locator) => current.element(next),
|
||||
element(locator)) as ElementFinder;
|
||||
}
|
||||
|
||||
export async function sleepFor(time = 1000) {
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import {Component} from '@angular/core';
|
||||
import { Component } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
standalone: true,
|
||||
selector: 'app-about',
|
||||
templateUrl: './about.component.html',
|
||||
styleUrls: ['./about.component.css'],
|
||||
styleUrls: ['./about.component.css']
|
||||
})
|
||||
export class AboutComponent {}
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
// #docplaster
|
||||
// #docregion animation-const, trigger-const
|
||||
import {animation, style, animate, trigger, transition, useAnimation} from '@angular/animations';
|
||||
import { animation, style, animate, trigger, transition, useAnimation } from '@angular/animations';
|
||||
// #enddocregion trigger-const
|
||||
|
||||
export const transitionAnimation = animation([
|
||||
style({
|
||||
height: '{{ height }}',
|
||||
opacity: '{{ opacity }}',
|
||||
backgroundColor: '{{ backgroundColor }}',
|
||||
backgroundColor: '{{ backgroundColor }}'
|
||||
}),
|
||||
animate('{{ time }}'),
|
||||
animate('{{ time }}')
|
||||
]);
|
||||
// #enddocregion animation-const
|
||||
|
||||
@@ -21,9 +21,9 @@ export const triggerAnimation = trigger('openClose', [
|
||||
height: 0,
|
||||
opacity: 1,
|
||||
backgroundColor: 'red',
|
||||
time: '1s',
|
||||
},
|
||||
}),
|
||||
]),
|
||||
time: '1s'
|
||||
}
|
||||
})
|
||||
])
|
||||
]);
|
||||
// #enddocregion trigger-const
|
||||
|
||||
@@ -1,71 +1,71 @@
|
||||
import {
|
||||
animation,
|
||||
trigger,
|
||||
animateChild,
|
||||
group,
|
||||
transition,
|
||||
animate,
|
||||
style,
|
||||
query,
|
||||
animation, trigger, animateChild, group,
|
||||
transition, animate, style, query
|
||||
} from '@angular/animations';
|
||||
|
||||
export const transitionAnimation = animation([
|
||||
style({
|
||||
height: '{{ height }}',
|
||||
opacity: '{{ opacity }}',
|
||||
backgroundColor: '{{ backgroundColor }}',
|
||||
backgroundColor: '{{ backgroundColor }}'
|
||||
}),
|
||||
animate('{{ time }}'),
|
||||
animate('{{ time }}')
|
||||
]);
|
||||
|
||||
// Routable animations
|
||||
// #docregion route-animations
|
||||
export const slideInAnimation =
|
||||
// #docregion style-view
|
||||
// #docregion style-view
|
||||
trigger('routeAnimations', [
|
||||
transition('HomePage <=> AboutPage', [
|
||||
style({position: 'relative'}),
|
||||
style({ position: 'relative' }),
|
||||
query(':enter, :leave', [
|
||||
style({
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
left: 0,
|
||||
width: '100%',
|
||||
}),
|
||||
width: '100%'
|
||||
})
|
||||
]),
|
||||
// #enddocregion style-view
|
||||
// #docregion query
|
||||
query(':enter', [style({left: '-100%'})], {optional: true}),
|
||||
query(':leave', animateChild(), {optional: true}),
|
||||
// #enddocregion style-view
|
||||
// #docregion query
|
||||
query(':enter', [
|
||||
style({ left: '-100%' })
|
||||
], { optional: true }),
|
||||
query(':leave', animateChild(), { optional: true }),
|
||||
group([
|
||||
query(':leave', [animate('300ms ease-out', style({left: '100%'}))], {optional: true}),
|
||||
query(':enter', [animate('300ms ease-out', style({left: '0%'}))], {optional: true}),
|
||||
query(':leave', [
|
||||
animate('300ms ease-out', style({ left: '100%' }))
|
||||
], { optional: true }),
|
||||
query(':enter', [
|
||||
animate('300ms ease-out', style({ left: '0%' }))
|
||||
], { optional: true }),
|
||||
]),
|
||||
]),
|
||||
transition('* <=> *', [
|
||||
style({position: 'relative'}),
|
||||
query(
|
||||
':enter, :leave',
|
||||
[
|
||||
style({
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
left: 0,
|
||||
width: '100%',
|
||||
}),
|
||||
],
|
||||
{optional: true},
|
||||
),
|
||||
query(':enter', [style({left: '-100%'})], {optional: true}),
|
||||
query(':leave', animateChild(), {optional: true}),
|
||||
style({ position: 'relative' }),
|
||||
query(':enter, :leave', [
|
||||
style({
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
left: 0,
|
||||
width: '100%'
|
||||
})
|
||||
], { optional: true }),
|
||||
query(':enter', [
|
||||
style({ left: '-100%' })
|
||||
], { optional: true }),
|
||||
query(':leave', animateChild(), { optional: true }),
|
||||
group([
|
||||
query(':leave', [animate('200ms ease-out', style({left: '100%', opacity: 0}))], {
|
||||
optional: true,
|
||||
}),
|
||||
query(':enter', [animate('300ms ease-out', style({left: '0%'}))], {optional: true}),
|
||||
query('@*', animateChild(), {optional: true}),
|
||||
query(':leave', [
|
||||
animate('200ms ease-out', style({ left: '100%', opacity: 0 }))
|
||||
], { optional: true }),
|
||||
query(':enter', [
|
||||
animate('300ms ease-out', style({ left: '0%' }))
|
||||
], { optional: true }),
|
||||
query('@*', animateChild(), { optional: true })
|
||||
]),
|
||||
]),
|
||||
])
|
||||
// #enddocregion query
|
||||
]);
|
||||
// #enddocregion route-animations
|
||||
// #enddocregion route-animations
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// #docplaster
|
||||
// #docregion imports
|
||||
import {Component, HostBinding} from '@angular/core';
|
||||
import { Component, HostBinding } from '@angular/core';
|
||||
import {
|
||||
trigger,
|
||||
state,
|
||||
@@ -11,8 +11,8 @@ import {
|
||||
} from '@angular/animations';
|
||||
|
||||
// #enddocregion imports
|
||||
import {ChildrenOutletContexts, RouterLink, RouterOutlet} from '@angular/router';
|
||||
import {slideInAnimation} from './animations';
|
||||
import { ChildrenOutletContexts, RouterLink, RouterOutlet } from '@angular/router';
|
||||
import { slideInAnimation } from './animations';
|
||||
|
||||
// #docregion decorator, toggle-app-animations, define
|
||||
@Component({
|
||||
@@ -22,31 +22,31 @@ import {slideInAnimation} from './animations';
|
||||
styleUrls: ['app.component.css'],
|
||||
imports: [RouterLink, RouterOutlet],
|
||||
animations: [
|
||||
// #enddocregion decorator
|
||||
slideInAnimation,
|
||||
// #docregion decorator
|
||||
// #enddocregion toggle-app-animations, define
|
||||
// #enddocregion decorator
|
||||
slideInAnimation
|
||||
// #docregion decorator
|
||||
// #enddocregion toggle-app-animations, define
|
||||
// animation triggers go here
|
||||
// #docregion toggle-app-animations, define
|
||||
],
|
||||
// #docregion toggle-app-animations, define
|
||||
]
|
||||
})
|
||||
// #enddocregion decorator, define
|
||||
export class AppComponent {
|
||||
@HostBinding('@.disabled')
|
||||
public animationsDisabled = false;
|
||||
// #enddocregion toggle-app-animations
|
||||
// #enddocregion toggle-app-animations
|
||||
|
||||
// #docregion get-route-animations-data
|
||||
// #docregion get-route-animations-data
|
||||
constructor(private contexts: ChildrenOutletContexts) {}
|
||||
|
||||
getRouteAnimationData() {
|
||||
return this.contexts.getContext('primary')?.route?.snapshot?.data?.['animation'];
|
||||
}
|
||||
// #enddocregion get-route-animations-data
|
||||
// #enddocregion get-route-animations-data
|
||||
|
||||
toggleAnimations() {
|
||||
this.animationsDisabled = !this.animationsDisabled;
|
||||
}
|
||||
// #docregion toggle-app-animations
|
||||
// #docregion toggle-app-animations
|
||||
}
|
||||
// #enddocregion toggle-app-animations
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
import {ApplicationConfig} from '@angular/core';
|
||||
import {routes} from './app.routes';
|
||||
import {provideRouter} from '@angular/router';
|
||||
import {provideProtractorTestingSupport} from '@angular/platform-browser';
|
||||
import {provideAnimations} from '@angular/platform-browser/animations';
|
||||
import { ApplicationConfig } from '@angular/core';
|
||||
import { routes } from './app.routes';
|
||||
import { provideRouter } from '@angular/router';
|
||||
import { provideProtractorTestingSupport } from '@angular/platform-browser';
|
||||
import { provideAnimations } from '@angular/platform-browser/animations';
|
||||
|
||||
|
||||
export const appConfig: ApplicationConfig = {
|
||||
providers: [
|
||||
// needed for supporting e2e tests
|
||||
provideProtractorTestingSupport(),
|
||||
provideRouter(routes),
|
||||
provideAnimations(),
|
||||
],
|
||||
providers: [
|
||||
// needed for supporting e2e tests
|
||||
provideProtractorTestingSupport(),
|
||||
provideRouter(routes),
|
||||
provideAnimations(),
|
||||
]
|
||||
};
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
import {NgModule} from '@angular/core';
|
||||
import {BrowserModule} from '@angular/platform-browser';
|
||||
import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
|
||||
import { NgModule } from '@angular/core';
|
||||
import { BrowserModule } from '@angular/platform-browser';
|
||||
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
|
||||
|
||||
@NgModule({
|
||||
imports: [BrowserModule, BrowserAnimationsModule],
|
||||
declarations: [],
|
||||
bootstrap: [],
|
||||
imports: [
|
||||
BrowserModule,
|
||||
BrowserAnimationsModule
|
||||
],
|
||||
declarations: [ ],
|
||||
bootstrap: [ ]
|
||||
})
|
||||
export class AppModule {}
|
||||
export class AppModule { }
|
||||
|
||||
@@ -1,73 +1,73 @@
|
||||
import {Routes} from '@angular/router';
|
||||
import {OpenClosePageComponent} from './open-close-page.component';
|
||||
import {StatusSliderPageComponent} from './status-slider-page.component';
|
||||
import {ToggleAnimationsPageComponent} from './toggle-animations-page.component';
|
||||
import {HeroListPageComponent} from './hero-list-page.component';
|
||||
import {HeroListGroupPageComponent} from './hero-list-group-page.component';
|
||||
import {HeroListEnterLeavePageComponent} from './hero-list-enter-leave-page.component';
|
||||
import {HeroListAutoCalcPageComponent} from './hero-list-auto-page.component';
|
||||
import {InsertRemoveComponent} from './insert-remove.component';
|
||||
import {QueryingComponent} from './querying.component';
|
||||
import {HomeComponent} from './home.component';
|
||||
import {AboutComponent} from './about.component';
|
||||
import { Routes } from '@angular/router';
|
||||
import { OpenClosePageComponent } from './open-close-page.component';
|
||||
import { StatusSliderPageComponent } from './status-slider-page.component';
|
||||
import { ToggleAnimationsPageComponent } from './toggle-animations-page.component';
|
||||
import { HeroListPageComponent } from './hero-list-page.component';
|
||||
import { HeroListGroupPageComponent } from './hero-list-group-page.component';
|
||||
import { HeroListEnterLeavePageComponent } from './hero-list-enter-leave-page.component';
|
||||
import { HeroListAutoCalcPageComponent } from './hero-list-auto-page.component';
|
||||
import { InsertRemoveComponent } from './insert-remove.component';
|
||||
import { QueryingComponent } from './querying.component';
|
||||
import { HomeComponent } from './home.component';
|
||||
import { AboutComponent } from './about.component';
|
||||
|
||||
// #docregion route-animation-data
|
||||
export const routes: Routes = [
|
||||
{path: '', pathMatch: 'full', redirectTo: '/enter-leave'},
|
||||
{
|
||||
path: 'open-close',
|
||||
component: OpenClosePageComponent,
|
||||
data: {animation: 'openClosePage'},
|
||||
},
|
||||
{
|
||||
path: 'status',
|
||||
component: StatusSliderPageComponent,
|
||||
data: {animation: 'statusPage'},
|
||||
},
|
||||
{
|
||||
path: 'toggle',
|
||||
component: ToggleAnimationsPageComponent,
|
||||
data: {animation: 'togglePage'},
|
||||
},
|
||||
{
|
||||
path: 'heroes',
|
||||
component: HeroListPageComponent,
|
||||
data: {animation: 'filterPage'},
|
||||
},
|
||||
{
|
||||
path: 'hero-groups',
|
||||
component: HeroListGroupPageComponent,
|
||||
data: {animation: 'heroGroupPage'},
|
||||
},
|
||||
{
|
||||
path: 'enter-leave',
|
||||
component: HeroListEnterLeavePageComponent,
|
||||
data: {animation: 'enterLeavePage'},
|
||||
},
|
||||
{
|
||||
path: 'auto',
|
||||
component: HeroListAutoCalcPageComponent,
|
||||
data: {animation: 'autoPage'},
|
||||
},
|
||||
{
|
||||
path: 'insert-remove',
|
||||
component: InsertRemoveComponent,
|
||||
data: {animation: 'insertRemovePage'},
|
||||
},
|
||||
{
|
||||
path: 'querying',
|
||||
component: QueryingComponent,
|
||||
data: {animation: 'queryingPage'},
|
||||
},
|
||||
{
|
||||
path: 'home',
|
||||
component: HomeComponent,
|
||||
data: {animation: 'HomePage'},
|
||||
},
|
||||
{
|
||||
path: 'about',
|
||||
component: AboutComponent,
|
||||
data: {animation: 'AboutPage'},
|
||||
},
|
||||
{ path: '', pathMatch: 'full', redirectTo: '/enter-leave' },
|
||||
{
|
||||
path: 'open-close',
|
||||
component: OpenClosePageComponent,
|
||||
data: { animation: 'openClosePage' }
|
||||
},
|
||||
{
|
||||
path: 'status',
|
||||
component: StatusSliderPageComponent,
|
||||
data: { animation: 'statusPage' }
|
||||
},
|
||||
{
|
||||
path: 'toggle',
|
||||
component: ToggleAnimationsPageComponent,
|
||||
data: { animation: 'togglePage' }
|
||||
},
|
||||
{
|
||||
path: 'heroes',
|
||||
component: HeroListPageComponent,
|
||||
data: { animation: 'filterPage' }
|
||||
},
|
||||
{
|
||||
path: 'hero-groups',
|
||||
component: HeroListGroupPageComponent,
|
||||
data: { animation: 'heroGroupPage' }
|
||||
},
|
||||
{
|
||||
path: 'enter-leave',
|
||||
component: HeroListEnterLeavePageComponent,
|
||||
data: { animation: 'enterLeavePage' }
|
||||
},
|
||||
{
|
||||
path: 'auto',
|
||||
component: HeroListAutoCalcPageComponent,
|
||||
data: { animation: 'autoPage' }
|
||||
},
|
||||
{
|
||||
path: 'insert-remove',
|
||||
component: InsertRemoveComponent,
|
||||
data: { animation: 'insertRemovePage' }
|
||||
},
|
||||
{
|
||||
path: 'querying',
|
||||
component: QueryingComponent,
|
||||
data: { animation: 'queryingPage' }
|
||||
},
|
||||
{
|
||||
path: 'home',
|
||||
component: HomeComponent,
|
||||
data: { animation: 'HomePage' }
|
||||
},
|
||||
{
|
||||
path: 'about',
|
||||
component: AboutComponent,
|
||||
data: { animation: 'AboutPage' }
|
||||
},
|
||||
];
|
||||
// #enddocregion route-animation-data
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import {Component} from '@angular/core';
|
||||
import {HEROES} from './mock-heroes';
|
||||
import {HeroListAutoComponent} from './hero-list-auto.component';
|
||||
import { Component } from '@angular/core';
|
||||
import { HEROES } from './mock-heroes';
|
||||
import { HeroListAutoComponent } from './hero-list-auto.component';
|
||||
|
||||
@Component({
|
||||
standalone: true,
|
||||
@@ -12,12 +12,12 @@ import {HeroListAutoComponent} from './hero-list-auto.component';
|
||||
<app-hero-list-auto [heroes]="heroes" (remove)="onRemove($event)"></app-hero-list-auto>
|
||||
</section>
|
||||
`,
|
||||
imports: [HeroListAutoComponent],
|
||||
imports: [HeroListAutoComponent]
|
||||
})
|
||||
export class HeroListAutoCalcPageComponent {
|
||||
heroes = HEROES.slice();
|
||||
|
||||
onRemove(id: number) {
|
||||
this.heroes = this.heroes.filter((hero) => hero.id !== id);
|
||||
this.heroes = this.heroes.filter(hero => hero.id !== id);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,19 @@
|
||||
import {Component, Input, Output, EventEmitter} from '@angular/core';
|
||||
import {trigger, state, style, animate, transition} from '@angular/animations';
|
||||
import {
|
||||
Component,
|
||||
Input,
|
||||
Output,
|
||||
EventEmitter
|
||||
} from '@angular/core';
|
||||
import {
|
||||
trigger,
|
||||
state,
|
||||
style,
|
||||
animate,
|
||||
transition
|
||||
} from '@angular/animations';
|
||||
|
||||
import {Hero} from './hero';
|
||||
import {NgFor} from '@angular/common';
|
||||
import { Hero } from './hero';
|
||||
import { NgFor } from '@angular/common';
|
||||
|
||||
@Component({
|
||||
standalone: true,
|
||||
@@ -13,18 +24,21 @@ import {NgFor} from '@angular/common';
|
||||
// #docregion auto-calc
|
||||
animations: [
|
||||
trigger('shrinkOut', [
|
||||
state('in', style({height: '*'})),
|
||||
transition('* => void', [style({height: '*'}), animate(250, style({height: 0}))]),
|
||||
]),
|
||||
],
|
||||
state('in', style({ height: '*' })),
|
||||
transition('* => void', [
|
||||
style({ height: '*' }),
|
||||
animate(250, style({ height: 0 }))
|
||||
])
|
||||
])
|
||||
]
|
||||
// #enddocregion auto-calc
|
||||
})
|
||||
export class HeroListAutoComponent {
|
||||
@Input() heroes: Hero[] = [];
|
||||
@Input() heroes: Hero[] = [];
|
||||
|
||||
@Output() remove = new EventEmitter<number>();
|
||||
@Output() remove = new EventEmitter<number>();
|
||||
|
||||
removeHero(id: number) {
|
||||
this.remove.emit(id);
|
||||
}
|
||||
removeHero(id: number) {
|
||||
this.remove.emit(id);
|
||||
}
|
||||
}
|
||||
|
||||
+5
-5
@@ -1,6 +1,6 @@
|
||||
import {Component} from '@angular/core';
|
||||
import {HEROES} from './mock-heroes';
|
||||
import {HeroListEnterLeaveComponent} from './hero-list-enter-leave.component';
|
||||
import { Component } from '@angular/core';
|
||||
import { HEROES } from './mock-heroes';
|
||||
import { HeroListEnterLeaveComponent } from './hero-list-enter-leave.component';
|
||||
|
||||
@Component({
|
||||
standalone: true,
|
||||
@@ -12,12 +12,12 @@ import {HeroListEnterLeaveComponent} from './hero-list-enter-leave.component';
|
||||
<app-hero-list-enter-leave [heroes]="heroes" (remove)="onRemove($event)"></app-hero-list-enter-leave>
|
||||
</section>
|
||||
`,
|
||||
imports: [HeroListEnterLeaveComponent],
|
||||
imports: [HeroListEnterLeaveComponent]
|
||||
})
|
||||
export class HeroListEnterLeavePageComponent {
|
||||
heroes = HEROES.slice();
|
||||
|
||||
onRemove(id: number) {
|
||||
this.heroes = this.heroes.filter((hero) => hero.id !== id);
|
||||
this.heroes = this.heroes.filter(hero => hero.id !== id);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,19 @@
|
||||
import {Component, Input, Output, EventEmitter} from '@angular/core';
|
||||
import {trigger, state, style, animate, transition} from '@angular/animations';
|
||||
import {
|
||||
Component,
|
||||
Input,
|
||||
Output,
|
||||
EventEmitter
|
||||
} from '@angular/core';
|
||||
import {
|
||||
trigger,
|
||||
state,
|
||||
style,
|
||||
animate,
|
||||
transition
|
||||
} from '@angular/animations';
|
||||
|
||||
import {Hero} from './hero';
|
||||
import {NgFor} from '@angular/common';
|
||||
import { Hero } from './hero';
|
||||
import { NgFor } from '@angular/common';
|
||||
|
||||
@Component({
|
||||
standalone: true,
|
||||
@@ -24,11 +35,16 @@ import {NgFor} from '@angular/common';
|
||||
// #docregion animationdef
|
||||
animations: [
|
||||
trigger('flyInOut', [
|
||||
state('in', style({transform: 'translateX(0)'})),
|
||||
transition('void => *', [style({transform: 'translateX(-100%)'}), animate(100)]),
|
||||
transition('* => void', [animate(100, style({transform: 'translateX(100%)'}))]),
|
||||
]),
|
||||
],
|
||||
state('in', style({ transform: 'translateX(0)' })),
|
||||
transition('void => *', [
|
||||
style({ transform: 'translateX(-100%)' }),
|
||||
animate(100)
|
||||
]),
|
||||
transition('* => void', [
|
||||
animate(100, style({ transform: 'translateX(100%)' }))
|
||||
])
|
||||
])
|
||||
]
|
||||
// #enddocregion animationdef
|
||||
})
|
||||
export class HeroListEnterLeaveComponent {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import {Component} from '@angular/core';
|
||||
import {HEROES} from './mock-heroes';
|
||||
import {HeroListGroupsComponent} from './hero-list-groups.component';
|
||||
import { Component } from '@angular/core';
|
||||
import { HEROES } from './mock-heroes';
|
||||
import { HeroListGroupsComponent } from './hero-list-groups.component';
|
||||
|
||||
@Component({
|
||||
standalone: true,
|
||||
@@ -12,12 +12,12 @@ import {HeroListGroupsComponent} from './hero-list-groups.component';
|
||||
<app-hero-list-groups [heroes]="heroes" (remove)="onRemove($event)"></app-hero-list-groups>
|
||||
</section>
|
||||
`,
|
||||
imports: [HeroListGroupsComponent],
|
||||
imports: [HeroListGroupsComponent]
|
||||
})
|
||||
export class HeroListGroupPageComponent {
|
||||
heroes = HEROES.slice();
|
||||
|
||||
onRemove(id: number) {
|
||||
this.heroes = this.heroes.filter((hero) => hero.id !== id);
|
||||
this.heroes = this.heroes.filter(hero => hero.id !== id);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,20 @@
|
||||
import {Component, Input, Output, EventEmitter} from '@angular/core';
|
||||
import {trigger, state, style, animate, transition, group} from '@angular/animations';
|
||||
import {
|
||||
Component,
|
||||
Input,
|
||||
Output,
|
||||
EventEmitter
|
||||
} from '@angular/core';
|
||||
import {
|
||||
trigger,
|
||||
state,
|
||||
style,
|
||||
animate,
|
||||
transition,
|
||||
group
|
||||
} from '@angular/animations';
|
||||
|
||||
import {Hero} from './hero';
|
||||
import {NgFor} from '@angular/common';
|
||||
import { Hero } from './hero';
|
||||
import { NgFor } from '@angular/common';
|
||||
|
||||
@Component({
|
||||
standalone: true,
|
||||
@@ -24,59 +36,43 @@ import {NgFor} from '@angular/common';
|
||||
// #docregion animationdef
|
||||
animations: [
|
||||
trigger('flyInOut', [
|
||||
state(
|
||||
'in',
|
||||
style({
|
||||
width: '*',
|
||||
transform: 'translateX(0)',
|
||||
opacity: 1,
|
||||
}),
|
||||
),
|
||||
state('in', style({
|
||||
width: '*',
|
||||
transform: 'translateX(0)', opacity: 1
|
||||
})),
|
||||
transition(':enter', [
|
||||
style({width: 10, transform: 'translateX(50px)', opacity: 0}),
|
||||
style({ width: 10, transform: 'translateX(50px)', opacity: 0 }),
|
||||
group([
|
||||
animate(
|
||||
'0.3s 0.1s ease',
|
||||
style({
|
||||
transform: 'translateX(0)',
|
||||
width: '*',
|
||||
}),
|
||||
),
|
||||
animate(
|
||||
'0.3s ease',
|
||||
style({
|
||||
opacity: 1,
|
||||
}),
|
||||
),
|
||||
]),
|
||||
animate('0.3s 0.1s ease', style({
|
||||
transform: 'translateX(0)',
|
||||
width: '*'
|
||||
})),
|
||||
animate('0.3s ease', style({
|
||||
opacity: 1
|
||||
}))
|
||||
])
|
||||
]),
|
||||
transition(':leave', [
|
||||
group([
|
||||
animate(
|
||||
'0.3s ease',
|
||||
style({
|
||||
transform: 'translateX(50px)',
|
||||
width: 10,
|
||||
}),
|
||||
),
|
||||
animate(
|
||||
'0.3s 0.2s ease',
|
||||
style({
|
||||
opacity: 0,
|
||||
}),
|
||||
),
|
||||
]),
|
||||
]),
|
||||
]),
|
||||
],
|
||||
animate('0.3s ease', style({
|
||||
transform: 'translateX(50px)',
|
||||
width: 10
|
||||
})),
|
||||
animate('0.3s 0.2s ease', style({
|
||||
opacity: 0
|
||||
}))
|
||||
])
|
||||
])
|
||||
])
|
||||
]
|
||||
// #enddocregion animationdef
|
||||
})
|
||||
export class HeroListGroupsComponent {
|
||||
@Input() heroes: Hero[] = [];
|
||||
@Input() heroes: Hero[] = [];
|
||||
|
||||
@Output() remove = new EventEmitter<number>();
|
||||
@Output() remove = new EventEmitter<number>();
|
||||
|
||||
removeHero(id: number) {
|
||||
this.remove.emit(id);
|
||||
}
|
||||
removeHero(id: number) {
|
||||
this.remove.emit(id);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,65 +1,66 @@
|
||||
// #docplaster
|
||||
// #docregion
|
||||
import {Component, HostBinding, OnInit} from '@angular/core';
|
||||
import {trigger, transition, animate, style, query, stagger} from '@angular/animations';
|
||||
import {HEROES} from './mock-heroes';
|
||||
import {Hero} from './hero';
|
||||
import {NgFor} from '@angular/common';
|
||||
import { Component, HostBinding, OnInit } from '@angular/core';
|
||||
import { trigger, transition, animate, style, query, stagger } from '@angular/animations';
|
||||
import { HEROES } from './mock-heroes';
|
||||
import { Hero } from './hero';
|
||||
import { NgFor } from '@angular/common';
|
||||
|
||||
// #docregion filter-animations
|
||||
@Component({
|
||||
// #enddocregion filter-animations
|
||||
// #enddocregion filter-animations
|
||||
standalone: true,
|
||||
imports: [NgFor],
|
||||
selector: 'app-hero-list-page',
|
||||
templateUrl: 'hero-list-page.component.html',
|
||||
styleUrls: ['hero-list-page.component.css'],
|
||||
// #docregion page-animations, filter-animations
|
||||
// #docregion page-animations, filter-animations
|
||||
animations: [
|
||||
// #enddocregion filter-animations
|
||||
// #enddocregion filter-animations
|
||||
trigger('pageAnimations', [
|
||||
transition(':enter', [
|
||||
query('.hero', [
|
||||
style({opacity: 0, transform: 'translateY(-100px)'}),
|
||||
stagger(30, [
|
||||
animate('500ms cubic-bezier(0.35, 0, 0.25, 1)', style({opacity: 1, transform: 'none'})),
|
||||
]),
|
||||
]),
|
||||
]),
|
||||
animate('500ms cubic-bezier(0.35, 0, 0.25, 1)',
|
||||
style({ opacity: 1, transform: 'none' }))
|
||||
])
|
||||
])
|
||||
])
|
||||
]),
|
||||
// #enddocregion page-animations
|
||||
// #docregion increment
|
||||
// #docregion filter-animations
|
||||
// #enddocregion page-animations
|
||||
// #docregion increment
|
||||
// #docregion filter-animations
|
||||
trigger('filterAnimation', [
|
||||
transition(':enter, * => 0, * => -1', []),
|
||||
transition(':increment', [
|
||||
query(
|
||||
':enter',
|
||||
[
|
||||
style({opacity: 0, width: 0}),
|
||||
stagger(50, [animate('300ms ease-out', style({opacity: 1, width: '*'}))]),
|
||||
],
|
||||
{optional: true},
|
||||
),
|
||||
query(':enter', [
|
||||
style({ opacity: 0, width: 0 }),
|
||||
stagger(50, [
|
||||
animate('300ms ease-out', style({ opacity: 1, width: '*' })),
|
||||
]),
|
||||
], { optional: true })
|
||||
]),
|
||||
transition(':decrement', [
|
||||
query(':leave', [stagger(50, [animate('300ms ease-out', style({opacity: 0, width: 0}))])]),
|
||||
query(':leave', [
|
||||
stagger(50, [
|
||||
animate('300ms ease-out', style({ opacity: 0, width: 0 })),
|
||||
]),
|
||||
])
|
||||
]),
|
||||
]),
|
||||
// #enddocregion increment
|
||||
],
|
||||
// #enddocregion increment
|
||||
]
|
||||
})
|
||||
export class HeroListPageComponent implements OnInit {
|
||||
// #enddocregion filter-animations
|
||||
// #enddocregion filter-animations
|
||||
@HostBinding('@pageAnimations')
|
||||
public animatePage = true;
|
||||
|
||||
// #docregion filter-animations
|
||||
// #docregion filter-animations
|
||||
heroesTotal = -1;
|
||||
|
||||
get heroes() {
|
||||
return this._heroes;
|
||||
}
|
||||
get heroes() { return this._heroes; }
|
||||
private _heroes: Hero[] = [];
|
||||
|
||||
ngOnInit() {
|
||||
@@ -69,9 +70,7 @@ export class HeroListPageComponent implements OnInit {
|
||||
updateCriteria(criteria: string) {
|
||||
criteria = criteria ? criteria.trim() : '';
|
||||
|
||||
this._heroes = HEROES.filter((hero) =>
|
||||
hero.name.toLowerCase().includes(criteria.toLowerCase()),
|
||||
);
|
||||
this._heroes = HEROES.filter(hero => hero.name.toLowerCase().includes(criteria.toLowerCase()));
|
||||
const newTotal = this.heroes.length;
|
||||
|
||||
if (this.heroesTotal !== newTotal) {
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import {Component} from '@angular/core';
|
||||
import { Component } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
standalone: true,
|
||||
selector: 'app-home',
|
||||
templateUrl: './home.component.html',
|
||||
styleUrls: ['./home.component.css'],
|
||||
styleUrls: ['./home.component.css']
|
||||
})
|
||||
export class HomeComponent {}
|
||||
|
||||
@@ -1,22 +1,27 @@
|
||||
// #docplaster
|
||||
import {Component} from '@angular/core';
|
||||
import {trigger, transition, animate, style} from '@angular/animations';
|
||||
import {NgIf} from '@angular/common';
|
||||
import { Component } from '@angular/core';
|
||||
import { trigger, transition, animate, style } from '@angular/animations';
|
||||
import { NgIf } from '@angular/common';
|
||||
|
||||
@Component({
|
||||
standalone: true,
|
||||
selector: 'app-insert-remove',
|
||||
imports: [NgIf],
|
||||
animations: [
|
||||
// #docregion enter-leave-trigger
|
||||
// #docregion enter-leave-trigger
|
||||
trigger('myInsertRemoveTrigger', [
|
||||
transition(':enter', [style({opacity: 0}), animate('100ms', style({opacity: 1}))]),
|
||||
transition(':leave', [animate('100ms', style({opacity: 0}))]),
|
||||
transition(':enter', [
|
||||
style({ opacity: 0 }),
|
||||
animate('100ms', style({ opacity: 1 })),
|
||||
]),
|
||||
transition(':leave', [
|
||||
animate('100ms', style({ opacity: 0 }))
|
||||
])
|
||||
]),
|
||||
// #enddocregion enter-leave-trigger
|
||||
// #enddocregion enter-leave-trigger
|
||||
],
|
||||
templateUrl: 'insert-remove.component.html',
|
||||
styleUrls: ['insert-remove.component.css'],
|
||||
styleUrls: ['insert-remove.component.css']
|
||||
})
|
||||
export class InsertRemoveComponent {
|
||||
isShown = false;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user