mirror of
https://github.com/angular/angular.git
synced 2026-09-14 13:54:52 +08:00
Compare commits
26 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 0b95d141ab | |||
| 78b48cc272 | |||
| 1c536250b6 | |||
| 674b6c36b0 | |||
| 35925263bd | |||
| 501c159e05 | |||
| 277540c4fc | |||
| 6d00115bf4 | |||
| ef965ac87c | |||
| 369e96c837 | |||
| 2363e77002 | |||
| 1581988d2e | |||
| 0ecc2d12f1 | |||
| 453e00c9b0 | |||
| 4d722a276f | |||
| 744cb1e561 | |||
| deb9ea07c5 | |||
| 62b87b4551 | |||
| 6c278a9069 | |||
| 5de985a565 | |||
| 85c05514be | |||
| 7aa92fa8fe | |||
| 4b96f370ee | |||
| 9e2c1405d1 | |||
| 9f05732656 | |||
| ed41bfcad7 |
@@ -177,6 +177,9 @@ test:saucelabs --flaky_test_attempts=1
|
||||
|
||||
# --ng_perf will ask the Ivy compiler to produce performance results for each build.
|
||||
build --flag_alias=ng_perf=//packages/compiler-cli:ng_perf
|
||||
# --adev_fast will run adev build/serve in a faster mode, skipping things like prerendering
|
||||
# for local development.
|
||||
build --flag_alias=fast_adev=//adev:fast_build_mode
|
||||
|
||||
####################################################
|
||||
# User bazel configuration
|
||||
|
||||
@@ -21,16 +21,16 @@ jobs:
|
||||
(github.event.action == 'synchronize' && contains(github.event.pull_request.labels.*.name, 'adev: preview'))
|
||||
steps:
|
||||
- name: Initialize environment
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@f89362d698858f9674463e48fde514b097a15838
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@c83e99a12397014162531ca125c94549db55dd84
|
||||
- name: Setup Bazel
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@f89362d698858f9674463e48fde514b097a15838
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@c83e99a12397014162531ca125c94549db55dd84
|
||||
- name: Setup Bazel RBE
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@f89362d698858f9674463e48fde514b097a15838
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@c83e99a12397014162531ca125c94549db55dd84
|
||||
- name: Install node modules
|
||||
run: yarn install --frozen-lockfile
|
||||
- name: Build adev to ensure it continues to work
|
||||
run: yarn bazel build --config=aio_local_deps //adev:build
|
||||
- uses: angular/dev-infra/github-actions/previews/pack-and-upload-artifact@f89362d698858f9674463e48fde514b097a15838
|
||||
- uses: angular/dev-infra/github-actions/previews/pack-and-upload-artifact@c83e99a12397014162531ca125c94549db55dd84
|
||||
with:
|
||||
workflow-artifact-name: 'adev-preview'
|
||||
pull-number: '${{github.event.pull_request.number}}'
|
||||
|
||||
@@ -40,7 +40,7 @@ jobs:
|
||||
npx -y firebase-tools@latest target:clear --config adev/firebase.json --project ${{env.PREVIEW_PROJECT}} hosting angular-docs
|
||||
npx -y firebase-tools@latest target:apply --config adev/firebase.json --project ${{env.PREVIEW_PROJECT}} hosting angular-docs ${{env.PREVIEW_SITE}}
|
||||
|
||||
- uses: angular/dev-infra/github-actions/previews/upload-artifacts-to-firebase@f89362d698858f9674463e48fde514b097a15838
|
||||
- uses: angular/dev-infra/github-actions/previews/upload-artifacts-to-firebase@c83e99a12397014162531ca125c94549db55dd84
|
||||
with:
|
||||
github-token: '${{secrets.GITHUB_TOKEN}}'
|
||||
workflow-artifact-name: 'adev-preview'
|
||||
|
||||
@@ -25,7 +25,7 @@ jobs:
|
||||
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||
- uses: ./.github/actions/yarn-install
|
||||
|
||||
- uses: angular/dev-infra/github-actions/bazel/configure-remote@f89362d698858f9674463e48fde514b097a15838
|
||||
- uses: angular/dev-infra/github-actions/bazel/configure-remote@c83e99a12397014162531ca125c94549db55dd84
|
||||
with:
|
||||
bazelrc: ./.bazelrc.user
|
||||
|
||||
@@ -34,7 +34,7 @@ jobs:
|
||||
# the number of concurrent actions is determined based on the host resources.
|
||||
- run: bazel build //aio:build --jobs=32 --announce_rc --verbose_failures
|
||||
|
||||
- uses: angular/dev-infra/github-actions/previews/pack-and-upload-artifact@f89362d698858f9674463e48fde514b097a15838
|
||||
- uses: angular/dev-infra/github-actions/previews/pack-and-upload-artifact@c83e99a12397014162531ca125c94549db55dd84
|
||||
with:
|
||||
workflow-artifact-name: 'aio'
|
||||
pull-number: '${{github.event.pull_request.number}}'
|
||||
|
||||
@@ -34,7 +34,7 @@ jobs:
|
||||
npx -y firebase-tools@latest target:clear --project ${{env.PREVIEW_PROJECT}} hosting aio
|
||||
npx -y firebase-tools@latest target:apply --project ${{env.PREVIEW_PROJECT}} hosting aio ${{env.PREVIEW_SITE}}
|
||||
|
||||
- uses: angular/dev-infra/github-actions/previews/upload-artifacts-to-firebase@f89362d698858f9674463e48fde514b097a15838
|
||||
- uses: angular/dev-infra/github-actions/previews/upload-artifacts-to-firebase@c83e99a12397014162531ca125c94549db55dd84
|
||||
with:
|
||||
github-token: '${{secrets.GITHUB_TOKEN}}'
|
||||
workflow-artifact-name: 'aio'
|
||||
|
||||
@@ -16,6 +16,6 @@ jobs:
|
||||
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: angular/dev-infra/github-actions/branch-manager@f89362d698858f9674463e48fde514b097a15838
|
||||
- uses: angular/dev-infra/github-actions/branch-manager@c83e99a12397014162531ca125c94549db55dd84
|
||||
with:
|
||||
angular-robot-key: ${{ secrets.ANGULAR_ROBOT_PRIVATE_KEY }}
|
||||
|
||||
@@ -38,7 +38,7 @@ jobs:
|
||||
|
||||
- uses: ./.github/actions/yarn-install
|
||||
|
||||
- uses: angular/dev-infra/github-actions/bazel/configure-remote@f89362d698858f9674463e48fde514b097a15838
|
||||
- uses: angular/dev-infra/github-actions/bazel/configure-remote@c83e99a12397014162531ca125c94549db55dd84
|
||||
with:
|
||||
bazelrc: ./.bazelrc.user
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ jobs:
|
||||
SAUCE_ACCESS_KEY: ${{ secrets.SAUCE_ACCESS_KEY }}
|
||||
steps:
|
||||
- name: Initialize environment
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@f89362d698858f9674463e48fde514b097a15838
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@c83e99a12397014162531ca125c94549db55dd84
|
||||
with:
|
||||
cache-node-modules: true
|
||||
# Checking out the pull request commit is intended here as we need to run the changed code tests.
|
||||
@@ -31,7 +31,7 @@ jobs:
|
||||
- name: Install node modules
|
||||
run: yarn install --frozen-lockfile
|
||||
- name: Setup Bazel
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@f89362d698858f9674463e48fde514b097a15838
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@c83e99a12397014162531ca125c94549db55dd84
|
||||
- name: Starting Saucelabs tunnel service
|
||||
run: ./tools/saucelabs/sauce-service.sh run &
|
||||
# Build test fixtures for a test that rely on Bazel-generated fixtures. Note that disabling
|
||||
|
||||
+28
-28
@@ -23,7 +23,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Initialize environment
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@f89362d698858f9674463e48fde514b097a15838
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@c83e99a12397014162531ca125c94549db55dd84
|
||||
with:
|
||||
cache-node-modules: true
|
||||
node-module-directories: |
|
||||
@@ -60,13 +60,13 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Initialize environment
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@f89362d698858f9674463e48fde514b097a15838
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@c83e99a12397014162531ca125c94549db55dd84
|
||||
with:
|
||||
cache-node-modules: true
|
||||
- name: Setup Bazel
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@f89362d698858f9674463e48fde514b097a15838
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@c83e99a12397014162531ca125c94549db55dd84
|
||||
- name: Setup Bazel RBE
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@f89362d698858f9674463e48fde514b097a15838
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@c83e99a12397014162531ca125c94549db55dd84
|
||||
- name: Install node modules
|
||||
run: yarn install --frozen-lockfile
|
||||
- name: Run unit tests
|
||||
@@ -78,13 +78,13 @@ jobs:
|
||||
runs-on: ubuntu-latest-4core
|
||||
steps:
|
||||
- name: Initialize environment
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@f89362d698858f9674463e48fde514b097a15838
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@c83e99a12397014162531ca125c94549db55dd84
|
||||
with:
|
||||
cache-node-modules: true
|
||||
- name: Setup Bazel
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@f89362d698858f9674463e48fde514b097a15838
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@c83e99a12397014162531ca125c94549db55dd84
|
||||
- name: Setup Bazel Remote Caching
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@f89362d698858f9674463e48fde514b097a15838
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@c83e99a12397014162531ca125c94549db55dd84
|
||||
- name: Install node modules
|
||||
run: yarn install --frozen-lockfile --network-timeout 100000
|
||||
- name: Run CI tests for framework
|
||||
@@ -94,15 +94,15 @@ jobs:
|
||||
runs-on: ubuntu-latest-4core
|
||||
steps:
|
||||
- name: Initialize environment
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@f89362d698858f9674463e48fde514b097a15838
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@c83e99a12397014162531ca125c94549db55dd84
|
||||
with:
|
||||
cache-node-modules: true
|
||||
node-module-directories: |
|
||||
./aio/node_modules
|
||||
- name: Setup Bazel
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@f89362d698858f9674463e48fde514b097a15838
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@c83e99a12397014162531ca125c94549db55dd84
|
||||
- name: Setup Bazel RBE
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@f89362d698858f9674463e48fde514b097a15838
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@c83e99a12397014162531ca125c94549db55dd84
|
||||
- name: Install node modules for aio
|
||||
run: yarn install --cwd aio --frozen-lockfile
|
||||
- name: Run AIO tests with upstream packages
|
||||
@@ -115,11 +115,11 @@ jobs:
|
||||
labels: ubuntu-latest-4core
|
||||
steps:
|
||||
- name: Initialize environment
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@f89362d698858f9674463e48fde514b097a15838
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@c83e99a12397014162531ca125c94549db55dd84
|
||||
- name: Setup Bazel
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@f89362d698858f9674463e48fde514b097a15838
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@c83e99a12397014162531ca125c94549db55dd84
|
||||
- name: Setup Bazel RBE
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@f89362d698858f9674463e48fde514b097a15838
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@c83e99a12397014162531ca125c94549db55dd84
|
||||
- name: Install node modules
|
||||
run: yarn install --frozen-lockfile
|
||||
- name: Build adev to ensure it continues to work
|
||||
@@ -130,15 +130,15 @@ jobs:
|
||||
labels: ubuntu-latest-4core
|
||||
steps:
|
||||
- name: Initialize environment
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@f89362d698858f9674463e48fde514b097a15838
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@c83e99a12397014162531ca125c94549db55dd84
|
||||
with:
|
||||
cache-node-modules: true
|
||||
node-module-directories: |
|
||||
./aio/node_modules
|
||||
- name: Setup Bazel
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@f89362d698858f9674463e48fde514b097a15838
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@c83e99a12397014162531ca125c94549db55dd84
|
||||
- name: Setup Bazel RBE
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@f89362d698858f9674463e48fde514b097a15838
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@c83e99a12397014162531ca125c94549db55dd84
|
||||
- name: Install node modules
|
||||
run: yarn install --cwd aio --frozen-lockfile
|
||||
- name: Run AIO tests with local packages
|
||||
@@ -153,15 +153,15 @@ jobs:
|
||||
labels: ubuntu-latest
|
||||
steps:
|
||||
- name: Initialize environment
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@f89362d698858f9674463e48fde514b097a15838
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@c83e99a12397014162531ca125c94549db55dd84
|
||||
with:
|
||||
cache-node-modules: true
|
||||
node-module-directories: |
|
||||
./aio/node_modules
|
||||
- name: Setup Bazel
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@f89362d698858f9674463e48fde514b097a15838
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@c83e99a12397014162531ca125c94549db55dd84
|
||||
- name: Setup Bazel RBE
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@f89362d698858f9674463e48fde514b097a15838
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@c83e99a12397014162531ca125c94549db55dd84
|
||||
- name: Install node modules for aio
|
||||
run: yarn install --cwd aio --frozen-lockfile
|
||||
- name: Set the stable branch environment variable
|
||||
@@ -197,13 +197,13 @@ jobs:
|
||||
labels: ubuntu-latest
|
||||
steps:
|
||||
- name: Initialize environment
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@f89362d698858f9674463e48fde514b097a15838
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@c83e99a12397014162531ca125c94549db55dd84
|
||||
with:
|
||||
cache-node-modules: true
|
||||
- name: Setup Bazel
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@f89362d698858f9674463e48fde514b097a15838
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@c83e99a12397014162531ca125c94549db55dd84
|
||||
- name: Setup Bazel RBE
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@f89362d698858f9674463e48fde514b097a15838
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@c83e99a12397014162531ca125c94549db55dd84
|
||||
- name: Install node modules
|
||||
run: yarn install --frozen-lockfile
|
||||
- run: echo "https://${{secrets.SNAPSHOT_BUILDS_GITHUB_TOKEN}}:@github.com" > ${HOME}/.git_credentials
|
||||
@@ -215,7 +215,7 @@ jobs:
|
||||
labels: ubuntu-latest-4core
|
||||
steps:
|
||||
- name: Initialize environment
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@f89362d698858f9674463e48fde514b097a15838
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@c83e99a12397014162531ca125c94549db55dd84
|
||||
with:
|
||||
cache-node-modules: true
|
||||
node-module-directories: |
|
||||
@@ -223,9 +223,9 @@ jobs:
|
||||
./packages/zone.js/node_modules
|
||||
./packages/zone.js/test/typings/node_modules
|
||||
- name: Setup Bazel
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@f89362d698858f9674463e48fde514b097a15838
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@c83e99a12397014162531ca125c94549db55dd84
|
||||
- name: Setup Bazel RBE
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@f89362d698858f9674463e48fde514b097a15838
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@c83e99a12397014162531ca125c94549db55dd84
|
||||
- name: Install node modules
|
||||
run: yarn install --frozen-lockfile
|
||||
- run: |
|
||||
@@ -264,7 +264,7 @@ jobs:
|
||||
JOBS: 2
|
||||
steps:
|
||||
- name: Initialize environment
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@f89362d698858f9674463e48fde514b097a15838
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@c83e99a12397014162531ca125c94549db55dd84
|
||||
with:
|
||||
cache-node-modules: true
|
||||
# Checking out the pull request commit is intended here as we need to run the changed code tests.
|
||||
@@ -272,9 +272,9 @@ jobs:
|
||||
- name: Install node modules
|
||||
run: yarn install --frozen-lockfile
|
||||
- name: Setup Bazel
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@f89362d698858f9674463e48fde514b097a15838
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@c83e99a12397014162531ca125c94549db55dd84
|
||||
- name: Setup Bazel Remote Caching
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@f89362d698858f9674463e48fde514b097a15838
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@c83e99a12397014162531ca125c94549db55dd84
|
||||
- name: Set up Sauce Tunnel Daemon
|
||||
run: yarn bazel run //tools/saucelabs-daemon/background-service -- $JOBS &
|
||||
env:
|
||||
|
||||
@@ -13,13 +13,13 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||
- uses: angular/dev-infra/github-actions/commit-message-based-labels@f89362d698858f9674463e48fde514b097a15838
|
||||
- uses: angular/dev-infra/github-actions/commit-message-based-labels@c83e99a12397014162531ca125c94549db55dd84
|
||||
with:
|
||||
angular-robot-key: ${{ secrets.ANGULAR_ROBOT_PRIVATE_KEY }}
|
||||
post_approval_changes:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||
- uses: angular/dev-infra/github-actions/post-approval-changes@f89362d698858f9674463e48fde514b097a15838
|
||||
- uses: angular/dev-infra/github-actions/post-approval-changes@c83e99a12397014162531ca125c94549db55dd84
|
||||
with:
|
||||
angular-robot-key: ${{ secrets.ANGULAR_ROBOT_PRIVATE_KEY }}
|
||||
|
||||
@@ -14,6 +14,6 @@ jobs:
|
||||
if: github.repository == 'angular/angular'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: angular/dev-infra/github-actions/feature-request@f89362d698858f9674463e48fde514b097a15838
|
||||
- uses: angular/dev-infra/github-actions/feature-request@c83e99a12397014162531ca125c94549db55dd84
|
||||
with:
|
||||
angular-robot-key: ${{ secrets.ANGULAR_ROBOT_PRIVATE_KEY }}
|
||||
|
||||
@@ -14,7 +14,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||
- uses: angular/dev-infra/github-actions/google-internal-tests@f89362d698858f9674463e48fde514b097a15838
|
||||
- uses: angular/dev-infra/github-actions/google-internal-tests@c83e99a12397014162531ca125c94549db55dd84
|
||||
with:
|
||||
run-tests-guide-url: http://go/angular-g3sync-start
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
@@ -9,6 +9,6 @@ jobs:
|
||||
status:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: angular/dev-infra/github-actions/unified-status-check@f89362d698858f9674463e48fde514b097a15838
|
||||
- uses: angular/dev-infra/github-actions/unified-status-check@c83e99a12397014162531ca125c94549db55dd84
|
||||
with:
|
||||
angular-robot-key: ${{ secrets.ANGULAR_ROBOT_PRIVATE_KEY }}
|
||||
|
||||
@@ -22,15 +22,15 @@ jobs:
|
||||
version: ['https://next.angular.io/', 'https://angular.io/']
|
||||
steps:
|
||||
- name: Initialize environment
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@f89362d698858f9674463e48fde514b097a15838
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@c83e99a12397014162531ca125c94549db55dd84
|
||||
with:
|
||||
cache-node-modules: true
|
||||
node-module-directories: |
|
||||
./aio/node_modules
|
||||
- name: Setup Bazel
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@f89362d698858f9674463e48fde514b097a15838
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@c83e99a12397014162531ca125c94549db55dd84
|
||||
- name: Setup Bazel RBE
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@f89362d698858f9674463e48fde514b097a15838
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@c83e99a12397014162531ca125c94549db55dd84
|
||||
- name: Install node modules in aio
|
||||
run: yarn install --frozen-lockfile --cwd aio
|
||||
- name: Run basic e2e and deployment config tests.
|
||||
@@ -44,7 +44,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Initialize environment
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@f89362d698858f9674463e48fde514b097a15838
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@c83e99a12397014162531ca125c94549db55dd84
|
||||
with:
|
||||
cache-node-modules: true
|
||||
node-module-directories: |
|
||||
|
||||
@@ -32,7 +32,7 @@ jobs:
|
||||
env:
|
||||
ANGULAR_CLI_BUILDS_READONLY_GITHUB_TOKEN: ${{ secrets.ANGULAR_CLI_BUILDS_READONLY_GITHUB_TOKEN }}
|
||||
- name: Create a PR (if necessary)
|
||||
uses: angular/dev-infra/github-actions/create-pr-for-changes@f89362d698858f9674463e48fde514b097a15838
|
||||
uses: angular/dev-infra/github-actions/create-pr-for-changes@c83e99a12397014162531ca125c94549db55dd84
|
||||
with:
|
||||
branch-prefix: update-cli-help
|
||||
pr-title: 'docs: update Angular CLI help [${{github.ref_name}}]'
|
||||
|
||||
@@ -35,7 +35,7 @@ jobs:
|
||||
- name: Generate `events.json`
|
||||
run: node aio/scripts/generate-events/index.mjs --ignore-invalid-dates
|
||||
- name: Create a PR (if necessary)
|
||||
uses: angular/dev-infra/github-actions/create-pr-for-changes@f89362d698858f9674463e48fde514b097a15838
|
||||
uses: angular/dev-infra/github-actions/create-pr-for-changes@c83e99a12397014162531ca125c94549db55dd84
|
||||
with:
|
||||
branch-prefix: docs-update-events
|
||||
pr-title: 'docs: update events'
|
||||
|
||||
+3
-1
@@ -20,6 +20,7 @@ export const format: FormatConfig = {
|
||||
'packages/examples/**/*.{js,ts}',
|
||||
'packages/misc/**/*.{js,ts}',
|
||||
'packages/private/**/*.{js,ts}',
|
||||
'packages/router/**/*.{js,ts}',
|
||||
'packages/service-worker/**/*.{js,ts}',
|
||||
'packages/upgrade/**/*.{js,ts}',
|
||||
|
||||
@@ -33,7 +34,7 @@ export const format: FormatConfig = {
|
||||
},
|
||||
'clang-format': {
|
||||
'matchers': [
|
||||
//'**/*.{js,ts}',
|
||||
'**/*.{js,ts}',
|
||||
// TODO: burn down format failures and remove aio and integration exceptions.
|
||||
'!aio/**',
|
||||
'!integration/**',
|
||||
@@ -70,6 +71,7 @@ export const format: FormatConfig = {
|
||||
'!packages/examples/**/*.{js,ts}',
|
||||
'!packages/misc/**/*.{js,ts}',
|
||||
'!packages/private/**/*.{js,ts}',
|
||||
'!packages/router/**/*.{js,ts}',
|
||||
'!packages/service-worker/**/*.{js,ts}',
|
||||
'!packages/upgrade/**/*.{js,ts}',
|
||||
],
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
"printWidth": 100,
|
||||
"tabWidth": 2,
|
||||
"tabs": false,
|
||||
"embeddedLanguageFormatting": "off",
|
||||
"singleQuote": true,
|
||||
"semicolon": true,
|
||||
"quoteProps": "preserve",
|
||||
|
||||
+110
-112
@@ -1,3 +1,51 @@
|
||||
<a name="17.2.0"></a>
|
||||
# 17.2.0 (2024-02-14)
|
||||
### common
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [03c3b3eb79](https://github.com/angular/angular/commit/03c3b3eb79ec061b0031d6ad7ba386d185c87d8d) | feat | add Netlify image loader ([#54311](https://github.com/angular/angular/pull/54311)) |
|
||||
| [f5c520b836](https://github.com/angular/angular/commit/f5c520b836c4545c7043649f28b3a0369c168747) | feat | add placeholder to NgOptimizedImage ([#53783](https://github.com/angular/angular/pull/53783)) |
|
||||
### compiler
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [47e6e84101](https://github.com/angular/angular/commit/47e6e841016abfca0c1aa84051d82a04b3027617) | feat | Add a TSConfig option `useTemplatePipeline` ([#54057](https://github.com/angular/angular/pull/54057)) |
|
||||
| [66e940aebf](https://github.com/angular/angular/commit/66e940aebfd5a93944860a4e0dbd14e1072f80f2) | feat | scope selectors in @starting-style ([#53943](https://github.com/angular/angular/pull/53943)) |
|
||||
| [7b4d275f49](https://github.com/angular/angular/commit/7b4d275f494a64c38b61cea7045ba8b6e8447b78) | fix | Fix the template pipeline option ([#54148](https://github.com/angular/angular/pull/54148)) |
|
||||
### compiler-cli
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [7e861c640e](https://github.com/angular/angular/commit/7e861c640edf90c5f8d4f7e091861d3d98cd49c0) | feat | generate extra imports for component local dependencies in local mode ([#53543](https://github.com/angular/angular/pull/53543)) |
|
||||
| [3263df23f2](https://github.com/angular/angular/commit/3263df23f2f4da722ef2c1a1dacfb0866498dd60) | feat | generate global imports in local compilation mode ([#53543](https://github.com/angular/angular/pull/53543)) |
|
||||
| [b774e22d8e](https://github.com/angular/angular/commit/b774e22d8e384f43e9cd8f5c55475d06e7f66988) | feat | make it configurable to generate alias reexports ([#53937](https://github.com/angular/angular/pull/53937)) |
|
||||
| [3e1384048e](https://github.com/angular/angular/commit/3e1384048eb76c92532ae19aa2883318121c00e8) | feat | support host directives for local compilation mode ([#53877](https://github.com/angular/angular/pull/53877)) |
|
||||
| [a592904c69](https://github.com/angular/angular/commit/a592904c691844d2c1aed00bd914edabef49f9b1) | fix | allow custom/duplicate decorators for `@Injectable` classes in local compilation mode ([#54139](https://github.com/angular/angular/pull/54139)) |
|
||||
| [4b1d948b36](https://github.com/angular/angular/commit/4b1d948b36285ec6d80dbe93e0b92133f9d4be94) | fix | consider the case of duplicate Angular decorators in local compilation diagnostics ([#54139](https://github.com/angular/angular/pull/54139)) |
|
||||
| [96bcf4fb12](https://github.com/angular/angular/commit/96bcf4fb1208d1f073784a2cde4a03553e905807) | fix | forbid custom/duplicate decorator when option `forbidOrphanComponents` is set ([#54139](https://github.com/angular/angular/pull/54139)) |
|
||||
| [64fa5715c6](https://github.com/angular/angular/commit/64fa5715c696101fba0b4f8623eaec0eadc5b159) | fix | generating extra imports in local compilation mode when cycle is introduced ([#53543](https://github.com/angular/angular/pull/53543)) |
|
||||
| [6c8b09468a](https://github.com/angular/angular/commit/6c8b09468a05a80cba3960861f0ab8d3bae80415) | fix | highlight the unresolved element in the @Component.styles array for the error LOCAL_COMPILATION_UNRESOLVED_CONST ([#54230](https://github.com/angular/angular/pull/54230)) |
|
||||
| [0970129e20](https://github.com/angular/angular/commit/0970129e20f77dc309f2b4f76f961b310124778c) | fix | show proper error for custom decorators in local compilation mode ([#53983](https://github.com/angular/angular/pull/53983)) |
|
||||
| [f39cb06418](https://github.com/angular/angular/commit/f39cb064183d984254bdf4e41b61d3dc9379738a) | fix | show specific error for unresolved @Directive.exportAs in local compilation mode ([#54230](https://github.com/angular/angular/pull/54230)) |
|
||||
| [f3851b5945](https://github.com/angular/angular/commit/f3851b59459a1d9c214ace3db5a716d51c1f93c7) | fix | show specific error for unresolved @HostBinding's argument in local compilation mode ([#54230](https://github.com/angular/angular/pull/54230)) |
|
||||
| [39ddd884e8](https://github.com/angular/angular/commit/39ddd884e826cc0be63fd0f7d7de20d642877ef9) | fix | show specific error for unresolved @HostListener's event name in local compilation mode ([#54230](https://github.com/angular/angular/pull/54230)) |
|
||||
| [5d633240fd](https://github.com/angular/angular/commit/5d633240fd5927c4318a9240e60c3a30b2333cee) | fix | show the correct message for the error LOCAL_COMPILATION_UNRESOLVED_CONST when an unresolved symbol used for @Component.styles ([#54230](https://github.com/angular/angular/pull/54230)) |
|
||||
| [58b8a232d6](https://github.com/angular/angular/commit/58b8a232d64f5fe3207c90c8145cab36e7e192c2) | fix | support jumping to definitions of signal-based inputs ([#54053](https://github.com/angular/angular/pull/54053)) |
|
||||
### core
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [702ab28b4c](https://github.com/angular/angular/commit/702ab28b4c07a903c403a20af2ca287348b6afd0) | feat | add support for model inputs ([#54252](https://github.com/angular/angular/pull/54252)) |
|
||||
| [e95ef2cbc6](https://github.com/angular/angular/commit/e95ef2cbc6f850d8fe96218b74cff76cea947674) | feat | expose queries as signals ([#54283](https://github.com/angular/angular/pull/54283)) |
|
||||
| [656bc282e3](https://github.com/angular/angular/commit/656bc282e345c5e37a9189a0a4daa631e02c31bf) | fix | add toString implementation to signals ([#54002](https://github.com/angular/angular/pull/54002)) |
|
||||
| [62b87b4551](https://github.com/angular/angular/commit/62b87b4551d77815f58af152d1921de3733621ba) | fix | do not crash for signal query that does not have any matches ([#54353](https://github.com/angular/angular/pull/54353)) |
|
||||
| [4b96f370ee](https://github.com/angular/angular/commit/4b96f370eea08d2531cc54f65a651f94b504692d) | fix | expose model signal subcribe for type checking purposes ([#54357](https://github.com/angular/angular/pull/54357)) |
|
||||
| [744cb1e561](https://github.com/angular/angular/commit/744cb1e561d9b0cbffd20ad612d5ea9e2cf6b2e7) | fix | return the same children query results if there are no changes ([#54392](https://github.com/angular/angular/pull/54392)) |
|
||||
| [6d00115bf4](https://github.com/angular/angular/commit/6d00115bf45731b80178e11921df6ec1dc89efaf) | fix | show placeholder block on the server with immediate trigger ([#54394](https://github.com/angular/angular/pull/54394)) |
|
||||
### http
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [1c536250b6](https://github.com/angular/angular/commit/1c536250b6112c67a68159de50c4afe0dbc440f8) | fix | Use string body to generate transfer cache key. ([#54379](https://github.com/angular/angular/pull/54379)) |
|
||||
|
||||
<!-- CHANGELOG SPLIT MARKER -->
|
||||
|
||||
<a name="17.1.3"></a>
|
||||
# 17.1.3 (2024-02-08)
|
||||
### compiler-cli
|
||||
@@ -21,50 +69,9 @@
|
||||
|
||||
<!-- CHANGELOG SPLIT MARKER -->
|
||||
|
||||
<a name="17.2.0-next.1"></a>
|
||||
# 17.2.0-next.1 (2024-01-31)
|
||||
###
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [dfc6c8d0c7](https://github.com/angular/angular/commit/dfc6c8d0c76380ee7beb6d904261e40857b375ed) | fix | cta clickability issue in adev homepage. ([#52905](https://github.com/angular/angular/pull/52905)) |
|
||||
### animations
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [75aeae42b7](https://github.com/angular/angular/commit/75aeae42b7f512553262d515966f43d11e34d228) | fix | cleanup DOM elements when root view is removed with async animations ([#53033](https://github.com/angular/angular/pull/53033)) |
|
||||
### common
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [f5c520b836](https://github.com/angular/angular/commit/f5c520b836c4545c7043649f28b3a0369c168747) | feat | add placeholder to NgOptimizedImage ([#53783](https://github.com/angular/angular/pull/53783)) |
|
||||
| [122213d37d](https://github.com/angular/angular/commit/122213d37d3e73fc0dcfd5a10a2c388dc573b6cf) | fix | The date pipe should return ISO format for week and week-year as intended in the unit test. ([#53879](https://github.com/angular/angular/pull/53879)) |
|
||||
### compiler
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [47e6e84101](https://github.com/angular/angular/commit/47e6e841016abfca0c1aa84051d82a04b3027617) | feat | Add a TSConfig option `useTemplatePipeline` ([#54057](https://github.com/angular/angular/pull/54057)) |
|
||||
| [7b4d275f49](https://github.com/angular/angular/commit/7b4d275f494a64c38b61cea7045ba8b6e8447b78) | fix | Fix the template pipeline option ([#54148](https://github.com/angular/angular/pull/54148)) |
|
||||
| [eddf5dae5e](https://github.com/angular/angular/commit/eddf5dae5eb9e1aa3ca4c276c2cb2b897b73a9e0) | fix | Update type check block to fix control flow source mappings ([#53980](https://github.com/angular/angular/pull/53980)) |
|
||||
### compiler-cli
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [7e861c640e](https://github.com/angular/angular/commit/7e861c640edf90c5f8d4f7e091861d3d98cd49c0) | feat | generate extra imports for component local dependencies in local mode ([#53543](https://github.com/angular/angular/pull/53543)) |
|
||||
| [3263df23f2](https://github.com/angular/angular/commit/3263df23f2f4da722ef2c1a1dacfb0866498dd60) | feat | generate global imports in local compilation mode ([#53543](https://github.com/angular/angular/pull/53543)) |
|
||||
| [64fa5715c6](https://github.com/angular/angular/commit/64fa5715c696101fba0b4f8623eaec0eadc5b159) | fix | generating extra imports in local compilation mode when cycle is introduced ([#53543](https://github.com/angular/angular/pull/53543)) |
|
||||
| [0970129e20](https://github.com/angular/angular/commit/0970129e20f77dc309f2b4f76f961b310124778c) | fix | show proper error for custom decorators in local compilation mode ([#53983](https://github.com/angular/angular/pull/53983)) |
|
||||
| [58b8a232d6](https://github.com/angular/angular/commit/58b8a232d64f5fe3207c90c8145cab36e7e192c2) | fix | support jumping to definitions of signal-based inputs ([#54053](https://github.com/angular/angular/pull/54053)) |
|
||||
### core
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [656bc282e3](https://github.com/angular/angular/commit/656bc282e345c5e37a9189a0a4daa631e02c31bf) | fix | add toString implementation to signals ([#54002](https://github.com/angular/angular/pull/54002)) |
|
||||
| [037b79b72e](https://github.com/angular/angular/commit/037b79b72ea18f08b3a74f9ad541bbdca183b1aa) | fix | change defer block fixture default behavior to playthrough ([#54088](https://github.com/angular/angular/pull/54088)) |
|
||||
### migrations
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [28ad6fc4ad](https://github.com/angular/angular/commit/28ad6fc4ad518884bb5777fd20e9075d8969b27a) | fix | error in standalone migration when non-array value is used as declarations in TestBed ([#54122](https://github.com/angular/angular/pull/54122)) |
|
||||
|
||||
<!-- CHANGELOG SPLIT MARKER -->
|
||||
|
||||
<a name="17.1.2"></a>
|
||||
# 17.1.2 (2024-01-31)
|
||||
###
|
||||
###
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [ccddacf11d](https://github.com/angular/angular/commit/ccddacf11deaebeda12e1bdb6e93ec401397d352) | fix | cta clickability issue in adev homepage. ([#52905](https://github.com/angular/angular/pull/52905)) |
|
||||
@@ -91,15 +98,6 @@
|
||||
|
||||
<!-- CHANGELOG SPLIT MARKER -->
|
||||
|
||||
<a name="17.2.0-next.0"></a>
|
||||
# 17.2.0-next.0 (2024-01-24)
|
||||
### compiler
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [66e940aebf](https://github.com/angular/angular/commit/66e940aebfd5a93944860a4e0dbd14e1072f80f2) | feat | scope selectors in @starting-style ([#53943](https://github.com/angular/angular/pull/53943)) |
|
||||
|
||||
<!-- CHANGELOG SPLIT MARKER -->
|
||||
|
||||
<a name="17.1.1"></a>
|
||||
# 17.1.1 (2024-01-24)
|
||||
### router
|
||||
@@ -435,9 +433,9 @@ Live long and prosper 🖖🏻
|
||||
[Blog post "Angular v17 is now available"](http://goo.gle/angular-v17).
|
||||
|
||||
## Breaking Changes
|
||||
###
|
||||
###
|
||||
- Node.js v16 support has been removed and the minimum support version has been bumped to 18.13.0.
|
||||
|
||||
|
||||
Node.js v16 is planned to be End-of-Life on 2023-09-11. Angular will stop supporting Node.js v16 in Angular v17. For Node.js release schedule details, please see: https://github.com/nodejs/release#release-schedule
|
||||
### common
|
||||
- the NgSwitch directive now defaults to the === equality operator,
|
||||
@@ -451,30 +449,30 @@ Live long and prosper 🖖🏻
|
||||
- The `mutate` method was removed from the `WritableSignal` interface and completely
|
||||
dropped from the public API surface. As an alternative, please use the `update` method and
|
||||
make immutable changes to the object.
|
||||
|
||||
|
||||
Example before:
|
||||
|
||||
|
||||
```typescript
|
||||
items.mutate(itemsArray => itemsArray.push(newItem));
|
||||
```
|
||||
|
||||
|
||||
Example after:
|
||||
|
||||
|
||||
```typescript
|
||||
items.update(itemsArray => [itemsArray, …newItem]);
|
||||
```
|
||||
- The `mutate` method was removed from the `WritableSignal` interface and completely
|
||||
dropped from the public API surface. As an alternative please use the update method and
|
||||
make immutable changes to the object.
|
||||
|
||||
|
||||
Example before:
|
||||
|
||||
|
||||
```typescript
|
||||
items.mutate(itemsArray => itemsArray.push(newItem));
|
||||
```
|
||||
|
||||
|
||||
Example after:
|
||||
|
||||
|
||||
```typescript
|
||||
items.update(itemsArray => [itemsArray, …newItem]);
|
||||
```
|
||||
@@ -490,7 +488,7 @@ Live long and prosper 🖖🏻
|
||||
`detectChanges` on that component's `ChangeDetectorRef`.
|
||||
### platform-browser
|
||||
- `REMOVE_STYLES_ON_COMPONENT_DESTROY` default value is now `true`. This causes CSS of components to be removed from the DOM when destroyed. You retain the previous behaviour by providing the `REMOVE_STYLES_ON_COMPONENT_DESTROY` injection token.
|
||||
|
||||
|
||||
```ts
|
||||
import {REMOVE_STYLES_ON_COMPONENT_DESTROY} from '@angular/platform-browser';
|
||||
...
|
||||
@@ -513,13 +511,13 @@ Live long and prosper 🖖🏻
|
||||
This should instead be configured through the provideRouter or RouterModule.forRoot APIs.
|
||||
- The following Router properties have been removed from
|
||||
the public API:
|
||||
|
||||
|
||||
- canceledNavigationResolution
|
||||
- paramsInheritanceStrategy
|
||||
- titleStrategy
|
||||
- urlUpdateStrategy
|
||||
- malformedUriErrorHandler
|
||||
|
||||
|
||||
These should instead be configured through the `provideRouter` or
|
||||
`RouterModule.forRoot` APIs.
|
||||
- The `setupTestingRouter` function has been removed. Use
|
||||
@@ -530,7 +528,7 @@ Live long and prosper 🖖🏻
|
||||
handled in the `UrlSerializer.parse` method.
|
||||
### zone.js
|
||||
- Deep and legacy `dist/` imports like `zone.js/bundles/zone-testing.js` and `zone.js/dist/zone` are no longer allowed. `zone-testing-bundle` and `zone-testing-node-bundle` are also no longer part of the package.
|
||||
|
||||
|
||||
The proper way to import `zone.js` and `zone.js/testing` is:
|
||||
```js
|
||||
import 'zone.js';
|
||||
@@ -541,7 +539,7 @@ Live long and prosper 🖖🏻
|
||||
- The `AnimationDriver.NOOP` symbol is deprecated, use `NoopAnimationDriver` instead.
|
||||
### core
|
||||
- `ChangeDetectorRef.checkNoChanges` is deprecated.
|
||||
|
||||
|
||||
Test code should use `ComponentFixture` instead of `ChangeDetectorRef`.
|
||||
Application code should not call `ChangeDetectorRef.checkNoChanges` directly.
|
||||
- Swapping out the context object for `EmbeddedViewRef`
|
||||
@@ -550,12 +548,12 @@ Live long and prosper 🖖🏻
|
||||
simple assignments in most cases, or `Object.assign` , or alternatively
|
||||
still replace the full object by using a `Proxy` (see `NgTemplateOutlet`
|
||||
as an example).
|
||||
|
||||
|
||||
Also adds a warning if the deprecated
|
||||
- NgProbeToken
|
||||
|
||||
|
||||
The `NgProbeToken` is not used internally since the transition from View Engine to Ivy. The token has no utility and can be removed from applications and libraries.
|
||||
###
|
||||
###
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [59aa0634f4](https://github.com/angular/angular/commit/59aa0634f4d4694203f2a69c40017fe5a3962514) | build | remove support for Node.js v16 ([#51755](https://github.com/angular/angular/pull/51755)) |
|
||||
@@ -1169,7 +1167,7 @@ Live long and prosper 🖖🏻
|
||||
[Blog post "Angular v16 is now available"](https://goo.gle/angular-v16).
|
||||
|
||||
## Breaking Changes
|
||||
###
|
||||
###
|
||||
- Angular Compatibility Compiler (ngcc) has been removed and as a result Angular View Engine libraries will no longer work
|
||||
- Deprecated `EventManager` method `addGlobalEventListener` has been removed as it is not used by Ivy.
|
||||
### bazel
|
||||
@@ -1192,14 +1190,14 @@ Live long and prosper 🖖🏻
|
||||
The ideal fix would be to update the code to instead be compatible with
|
||||
`MockPlatformLocation` instead.
|
||||
- If the 'ngTemplateOutletContext' is different from the context, it will result in a compile-time error.
|
||||
|
||||
|
||||
Before the change, the following template was compiling:
|
||||
|
||||
|
||||
```typescript
|
||||
interface MyContext {
|
||||
$implicit: string;
|
||||
}
|
||||
|
||||
|
||||
@Component({
|
||||
standalone: true,
|
||||
imports: [NgTemplateOutlet],
|
||||
@@ -1217,7 +1215,7 @@ Live long and prosper 🖖🏻
|
||||
}
|
||||
```
|
||||
However, it does not compile now because the 'xxx' property does not exist in 'MyContext', resulting in the error: 'Type '{ $implicit: string; xxx: string; }' is not assignable to type 'MyContext'.'
|
||||
|
||||
|
||||
The solution is either:
|
||||
- add the 'xxx' property to 'MyContext' with the correct type or
|
||||
- add '$any(...)' inside the template to make the error disappear. However, adding '$any(...)' does not correct the error but only preserves the previous behavior of the code.
|
||||
@@ -1236,7 +1234,7 @@ Live long and prosper 🖖🏻
|
||||
value differs from the previous call to `setInput`.
|
||||
- `RendererType2.styles` no longer accepts a nested arrays.
|
||||
- The `APP_ID` token value is no longer randomly generated. If you are bootstrapping multiple application on the same page you will need to set to provide the `APP_ID` yourself.
|
||||
|
||||
|
||||
```ts
|
||||
bootstrapApplication(ComponentA, {
|
||||
providers: [
|
||||
@@ -1247,21 +1245,21 @@ Live long and prosper 🖖🏻
|
||||
```
|
||||
- The `ReflectiveInjector` and related symbols were removed. Please update the code to avoid references to the `ReflectiveInjector` symbol. Use `Injector.create` as a replacement to create an injector instead.
|
||||
- Node.js v14 support has been removed
|
||||
|
||||
|
||||
Node.js v14 is planned to be End-of-Life on 2023-04-30. Angular will stop supporting Node.js v14 in Angular v16. Angular v16 will continue to officially support Node.js versions v16 and v18.
|
||||
### platform-browser
|
||||
- The deprecated `BrowserTransferStateModule` was removed, since it's no longer needed. The `TransferState` class can be injected without providing the module. The `BrowserTransferStateModule` was empty starting from v14 and you can just remove the reference to that module from your applications.
|
||||
### platform-server
|
||||
- Users that are using SSR with JIT mode will now need to add `import to @angular/compiler` before bootstrapping the application.
|
||||
|
||||
|
||||
**NOTE:** this does not effect users using the Angular CLI.
|
||||
- `renderApplication` method no longer accepts a root component as first argument. Instead, provide a bootstrapping function that returns a `Promise<ApplicationRef>`.
|
||||
|
||||
|
||||
Before
|
||||
```ts
|
||||
const output: string = await renderApplication(RootComponent, options);
|
||||
```
|
||||
|
||||
|
||||
Now
|
||||
```ts
|
||||
const bootstrap = () => bootstrapApplication(RootComponent, appConfig);
|
||||
@@ -1296,14 +1294,14 @@ Live long and prosper 🖖🏻
|
||||
## Deprecations
|
||||
### core
|
||||
- `makeStateKey`, `StateKey` and `TransferState` exports have been moved from `@angular/platform-browser` to `@angular/core`. Please update the imports.
|
||||
|
||||
|
||||
```diff
|
||||
- import {makeStateKey, StateKey, TransferState} from '@angular/platform-browser';
|
||||
+ import {makeStateKey, StateKey, TransferState} from '@angular/core';
|
||||
```
|
||||
- `EnvironmentInjector.runInContext` is now deprecated, with
|
||||
`runInInjectionContext` functioning as a direct replacement:
|
||||
|
||||
|
||||
```typescript
|
||||
// Previous method version (deprecated):
|
||||
envInjector.runInContext(fn);
|
||||
@@ -1316,7 +1314,7 @@ Live long and prosper 🖖🏻
|
||||
### platform-browser
|
||||
- `BrowserModule.withServerTransition` has been deprecated. `APP_ID` should be used instead to set the application ID.
|
||||
NB: Unless, you render multiple Angular applications on the same page, setting an application ID is not necessary.
|
||||
|
||||
|
||||
Before:
|
||||
```ts
|
||||
imports: [
|
||||
@@ -1324,7 +1322,7 @@ Live long and prosper 🖖🏻
|
||||
...
|
||||
]
|
||||
```
|
||||
|
||||
|
||||
After:
|
||||
```ts
|
||||
imports: [
|
||||
@@ -1336,7 +1334,7 @@ Live long and prosper 🖖🏻
|
||||
- `ApplicationConfig` has moved, please import `ApplicationConfig` from `@angular/core` instead.
|
||||
### platform-server
|
||||
- `PlatformConfig.baseUrl` and `PlatformConfig.useAbsoluteUrl` platform-server config options are deprecated as these were not used.
|
||||
###
|
||||
###
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [48aa96ea13](https://github.com/angular/angular/commit/48aa96ea13ebfadf2f6b13516c7702dae740a7be) | refactor | remove Angular Compatibility Compiler (ngcc) ([#49101](https://github.com/angular/angular/pull/49101)) |
|
||||
@@ -1645,11 +1643,11 @@ Alan Agius, Andrew Kushnir, Aristeidis Bampakos, Craig Spence, Doug Parker, Ivá
|
||||
<a name="15.2.0"></a>
|
||||
# 15.2.0 (2023-02-22)
|
||||
## Deprecations
|
||||
###
|
||||
###
|
||||
- Class and `InjectionToken` guards and resolvers are
|
||||
deprecated. Instead, write guards as plain JavaScript functions and
|
||||
inject dependencies with `inject` from `@angular/core`.
|
||||
###
|
||||
###
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [926c35f4ac](https://github.com/angular/angular/commit/926c35f4ac70f5e4d142e545d6d056dd67aac97b) | docs | Deprecate class and InjectionToken and resolvers ([#47924](https://github.com/angular/angular/pull/47924)) |
|
||||
@@ -1814,21 +1812,21 @@ Alan Agius, Alex Rickabaugh, Andrew Scott, Aristeidis Bampakos, Bob Watson, Jens
|
||||
- CanLoad guards in the Router are deprecated. Use CanMatch
|
||||
instead.
|
||||
- router writable properties
|
||||
|
||||
|
||||
The following strategies are meant to be configured by registering the
|
||||
application strategy in DI via the `providers` in the root `NgModule` or
|
||||
`bootstrapApplication`:
|
||||
* `routeReuseStrategy`
|
||||
* `titleStrategy`
|
||||
* `urlHandlingStrategy`
|
||||
|
||||
|
||||
The following options are meant to be configured using the options
|
||||
available in `RouterModule.forRoot` or `provideRouter`.
|
||||
* `onSameUrlNavigation`
|
||||
* `paramsInheritanceStrategy`
|
||||
* `urlUpdateStrategy`
|
||||
* `canceledNavigationResolution`
|
||||
|
||||
|
||||
The following options are available in `RouterModule.forRoot` but not
|
||||
available in `provideRouter`:
|
||||
* `malformedUriErrorHandler` - This was found to not be used anywhere
|
||||
@@ -2023,23 +2021,23 @@ Andrew Kushnir
|
||||
- Keyframes names are now prefixed with the component's "scope name".
|
||||
For example, the following keyframes rule in a component definition,
|
||||
whose "scope name" is host-my-cmp:
|
||||
|
||||
|
||||
@keyframes foo { ... }
|
||||
|
||||
|
||||
will become:
|
||||
|
||||
|
||||
@keyframes host-my-cmp_foo { ... }
|
||||
|
||||
|
||||
Any TypeScript/JavaScript code which relied on the names of keyframes rules
|
||||
will no longer match.
|
||||
|
||||
|
||||
The recommended solutions in this case are to either:
|
||||
- change the component's view encapsulation to the `None` or `ShadowDom`
|
||||
- define keyframes rules in global stylesheets (e.g styles.css)
|
||||
- define keyframes rules programmatically in code.
|
||||
### compiler-cli
|
||||
- Invalid constructors for DI may now report compilation errors
|
||||
|
||||
|
||||
When a class inherits its constructor from a base class, the compiler may now
|
||||
report an error when that constructor cannot be used for DI purposes. This may
|
||||
either be because the base class is missing an Angular decorator such as
|
||||
@@ -2048,7 +2046,7 @@ Andrew Kushnir
|
||||
These situations used to behave unexpectedly at runtime, where the class may be
|
||||
constructed without any of its constructor parameters, so this is now reported
|
||||
as an error during compilation.
|
||||
|
||||
|
||||
Any new errors that may be reported because of this change can be resolved either
|
||||
by decorating the base class from which the constructor is inherited, or by adding
|
||||
an explicit constructor to the class for which the error is reported.
|
||||
@@ -2082,16 +2080,16 @@ Andrew Kushnir
|
||||
### core
|
||||
- - The ability to pass an `NgModule` to the `providedIn` option for
|
||||
`@Injectable` and `InjectionToken` is now deprecated.
|
||||
|
||||
|
||||
`providedIn: NgModule` was intended to be a tree-shakable alternative to
|
||||
NgModule providers. It does not have wide usage, and in most cases is used
|
||||
incorrectly, in circumstances where `providedIn: 'root'` should be
|
||||
preferred. If providers should truly be scoped to a specific NgModule, use
|
||||
`NgModule.providers` instead.
|
||||
|
||||
|
||||
- The ability to set `providedIn: 'any'` for an `@Injectable` or
|
||||
`InjectionToken` is now deprecated.
|
||||
|
||||
|
||||
`providedIn: 'any'` is an option with confusing semantics and is almost
|
||||
never used apart from a handful of esoteric cases internal to the framework.
|
||||
- The bit field signature of `Injector.get()` has been deprecated, in favor of the new options object.
|
||||
@@ -2694,16 +2692,16 @@ Adrien Crivelli, Alan Agius, Alex Rickabaugh, Andrew Kushnir, Andrew Scott, Dyla
|
||||
- Keyframes names are now prefixed with the component's "scope name".
|
||||
For example, the following keyframes rule in a component definition,
|
||||
whose "scope name" is host-my-cmp:
|
||||
|
||||
|
||||
@keyframes foo { ... }
|
||||
|
||||
|
||||
will become:
|
||||
|
||||
|
||||
@keyframes host-my-cmp_foo { ... }
|
||||
|
||||
|
||||
Any TypeScript/JavaScript code which relied on the names of keyframes rules
|
||||
will no longer match.
|
||||
|
||||
|
||||
The recommended solutions in this case are to either:
|
||||
- change the component's view encapsulation to the `None` or `ShadowDom`
|
||||
- define keyframes rules in global stylesheets (e.g styles.css)
|
||||
@@ -2713,23 +2711,23 @@ Adrien Crivelli, Alan Agius, Alex Rickabaugh, Andrew Kushnir, Andrew Scott, Dyla
|
||||
- Support for Node.js v12 has been removed as it will become EOL on 2022-04-30. Please use Node.js v14.15 or later.
|
||||
- TypeScript versions older than 4.6 are no longer supported.
|
||||
- Forms [email] input coercion
|
||||
|
||||
|
||||
Forms [email] input value will be considered as true if it is defined with any value rather
|
||||
than false and 'false'.
|
||||
- Since Ivy, TestBed doesn't use AOT summaries. The `aotSummaries` fields in TestBed APIs were present, but unused. The fields were deprecated in previous major version and in v14 those fields are removed. The `aotSummaries` fields were completely unused, so you can just drop them from the TestBed APIs usage.
|
||||
### forms
|
||||
- Forms classes accept a generic.
|
||||
|
||||
|
||||
Forms model classes now accept a generic type parameter. Untyped versions of these classes are available to opt-out of the new, stricter behavior.
|
||||
- objects with a length key set to zero will no longer validate as empty.
|
||||
|
||||
|
||||
This is technically a breaking change, since objects with a key `length` and value `0` will no longer validate as empty. This is a very minor change, and any reliance on this behavior is probably a bug anyway.
|
||||
### http
|
||||
- Queries including + will now actually query for + instead of space.
|
||||
Most workarounds involving custom codecs will be unaffected.
|
||||
Possible server-side workarounds will need to be undone.
|
||||
- JSONP will throw an error when headers are set on a reques
|
||||
|
||||
|
||||
JSONP does not support headers being set on requests. Before when
|
||||
a request was sent to a JSONP backend that had headers set the headers
|
||||
were ignored. The JSONP backend will now throw an error if it
|
||||
@@ -2740,7 +2738,7 @@ Adrien Crivelli, Alan Agius, Alex Rickabaugh, Andrew Kushnir, Andrew Scott, Dyla
|
||||
- This change may cause a breaking change in unit tests that are implicitly depending on a specific
|
||||
number and sequence of change detections in order for their assertions to pass.
|
||||
- This may break invalid calls to `TransferState` methods.
|
||||
|
||||
|
||||
This tightens parameter types of `TransferState` usage, and is a minor breaking change which may reveal existing problematic calls.
|
||||
### router
|
||||
- The type of `Route.pathMatch` is now stricter. Places that use
|
||||
@@ -2762,7 +2760,7 @@ Adrien Crivelli, Alan Agius, Alex Rickabaugh, Andrew Kushnir, Andrew Scott, Dyla
|
||||
actually gets set to something completely different. It's set to the
|
||||
current internal `UrlTree` of the Router at the time navigation
|
||||
occurs.
|
||||
|
||||
|
||||
With this change, there is no exact replacement for the old value of
|
||||
`initialUrl` because it was never intended to be exposed.
|
||||
`Router.url` is likely the best replacement for this.
|
||||
@@ -2786,7 +2784,7 @@ Adrien Crivelli, Alan Agius, Alex Rickabaugh, Andrew Kushnir, Andrew Scott, Dyla
|
||||
returned by other guards: only the first value is used.
|
||||
### zone.js
|
||||
- in TaskTrackingZoneSpec track a periodic task until it is cancelled
|
||||
|
||||
|
||||
The breaking change is scoped only to the plugin
|
||||
`zone.js/plugins/task-tracking`. If you used `TaskTrackingZoneSpec` and
|
||||
checked the pending macroTasks e.g. using `(this.ngZone as any)._inner
|
||||
@@ -2795,7 +2793,7 @@ Adrien Crivelli, Alan Agius, Alex Rickabaugh, Andrew Kushnir, Andrew Scott, Dyla
|
||||
previously the `setInterval` macrotask was no longer tracked after its
|
||||
callback was executed for the first time. Now it's tracked until
|
||||
the task is explicitly cancelled, e.g with `clearInterval(id)`.
|
||||
|
||||
|
||||
## Deprecations
|
||||
### common
|
||||
- The `ngModuleFactory` input of the `NgComponentOutlet` directive is deprecated in favor of a newly added `ngModule` input. The `ngModule` input accepts references to the NgModule class directly, without the need to resolve module factory first.
|
||||
|
||||
@@ -64,7 +64,17 @@ load("@rules_nodejs//nodejs:repositories.bzl", "nodejs_register_toolchains")
|
||||
|
||||
nodejs_register_toolchains(
|
||||
name = "nodejs",
|
||||
node_version = "18.13.0",
|
||||
node_repositories = {
|
||||
"18.18.2-darwin_arm64": ("node-v18.18.2-darwin-arm64.tar.gz", "node-v18.18.2-darwin-arm64", "9f982cc91b28778dd8638e4f94563b0c2a1da7aba62beb72bd427721035ab553"),
|
||||
"18.18.2-darwin_amd64": ("node-v18.18.2-darwin-x64.tar.gz", "node-v18.18.2-darwin-x64", "5bb8da908ed590e256a69bf2862238c8a67bc4600119f2f7721ca18a7c810c0f"),
|
||||
"18.18.2-linux_arm64": ("node-v18.18.2-linux-arm64.tar.xz", "node-v18.18.2-linux-arm64", "2e630e18548627f61eaf573233da7949dc0a1df5eef3f486fa9820c5f6c121aa"),
|
||||
"18.18.2-linux_ppc64le": ("node-v18.18.2-linux-ppc64le.tar.xz", "node-v18.18.2-linux-ppc64le", "b0adff5cf5938266b711d6c724fb134d802e0dee40b3a3f73d162de1b3d11880"),
|
||||
"18.18.2-linux_s390x": ("node-v18.18.2-linux-s390x.tar.xz", "node-v18.18.2-linux-s390x", "c70ec2074b5e2b42c55bb4b8105418b67bf8a61c500d9376a07430dfcc341fdb"),
|
||||
"18.18.2-linux_amd64": ("node-v18.18.2-linux-x64.tar.xz", "node-v18.18.2-linux-x64", "75aba25ae76999309fc6c598efe56ce53fbfc221381a44a840864276264ab8ac"),
|
||||
"18.18.2-windows_amd64": ("node-v18.18.2-win-x64.zip", "node-v18.18.2-win-x64", "3bb0e51e579a41a22b3bf6cb2f3e79c03801aa17acbe0ca00fc555d1282e7acd"),
|
||||
},
|
||||
# We need at least Node 18.17 due to some transitive dependencies.
|
||||
node_version = "18.18.2",
|
||||
)
|
||||
|
||||
# Download npm dependencies.
|
||||
|
||||
+26
-1
@@ -1,4 +1,5 @@
|
||||
load("//:packages.bzl", "link_packages")
|
||||
load("@bazel_skylib//rules:common_settings.bzl", "bool_flag")
|
||||
load("@build_bazel_rules_nodejs//:index.bzl", "copy_to_bin")
|
||||
load("@npm//@angular-devkit/architect-cli:index.bzl", "architect", "architect_test")
|
||||
|
||||
@@ -96,12 +97,36 @@ copy_to_bin(
|
||||
srcs = APPLICATION_FILES,
|
||||
)
|
||||
|
||||
bool_flag(
|
||||
name = "fast_build_mode",
|
||||
build_setting_default = False,
|
||||
)
|
||||
|
||||
config_setting(
|
||||
name = "fast",
|
||||
flag_values = {
|
||||
":fast_build_mode": "true",
|
||||
},
|
||||
)
|
||||
|
||||
config_setting(
|
||||
name = "full",
|
||||
flag_values = {
|
||||
":fast_build_mode": "false",
|
||||
},
|
||||
)
|
||||
|
||||
config_based_architect_flags = select({
|
||||
":fast": ["--no-prerender"],
|
||||
":full": ["--prerender"],
|
||||
})
|
||||
|
||||
architect(
|
||||
name = "build",
|
||||
args = [
|
||||
"angular-dev:build",
|
||||
"--output-path=build",
|
||||
],
|
||||
] + config_based_architect_flags,
|
||||
chdir = "$(RULEDIR)",
|
||||
data = APPLICATION_DEPS + [
|
||||
":application_files_bin",
|
||||
|
||||
@@ -26,7 +26,6 @@
|
||||
"index": "src/index.html",
|
||||
"browser": "src/main.ts",
|
||||
"server": "src/main.server.ts",
|
||||
"prerender": true,
|
||||
"polyfills": ["src/polyfills.ts", "zone.js"],
|
||||
"tsConfig": "tsconfig.app.json",
|
||||
"inlineStyleLanguage": "scss",
|
||||
|
||||
@@ -65,10 +65,13 @@ copy_to_directory(
|
||||
"//packages/upgrade:upgrade_docs",
|
||||
"//packages/upgrade/static:upgrade_static_docs",
|
||||
"//packages/upgrade/static/testing:upgrade_static_testing_docs",
|
||||
"//tools/manual_api_docs/blocks:blocks_docs",
|
||||
"//tools/manual_api_docs/elements:elements_docs",
|
||||
],
|
||||
replace_prefixes = {
|
||||
"adev/src/content": "",
|
||||
"packages/**/": "api/",
|
||||
"tools/**/": "api/",
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@@ -43,7 +43,7 @@ It also includes troubleshooting information and any recommended manual changes
|
||||
For simple updates, the CLI command [`ng update`](cli/update) is all you need.
|
||||
Without additional arguments, [`ng update`](cli/update) lists the updates that are available to you and provides recommended steps to update your application to the most current version.
|
||||
|
||||
[Angular Versioning and Releases](reference/releases#versioning "Angular Release Practices, Versioning") describes the level of change that you can expect based a release's version number.
|
||||
[Angular Versioning and Releases](reference/releases#versioning "Angular Release Practices, Versioning") describes the level of change that you can expect based on a release's version number.
|
||||
It also describes supported update paths.
|
||||
|
||||
## Resource summary
|
||||
|
||||
@@ -58,7 +58,7 @@ It also includes troubleshooting information and any recommended manual changes
|
||||
For simple updates, the CLI command [`ng update`](cli/update) is all you need.
|
||||
Without additional arguments, [`ng update`](cli/update) lists the updates that are available to you and provides recommended steps to update your application to the most current version.
|
||||
|
||||
[Angular Versioning and Releases](guide/releases#versioning "Angular Release Practices, Versioning") describes the level of change that you can expect based a release's version number.
|
||||
[Angular Versioning and Releases](guide/releases#versioning "Angular Release Practices, Versioning") describes the level of change that you can expect based on a release's version number.
|
||||
It also describes supported update paths.
|
||||
|
||||
<a id="resources"></a>
|
||||
|
||||
+17
-17
@@ -61,33 +61,33 @@
|
||||
},
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@angular/animations": "17.2.0-next.0",
|
||||
"@angular/cdk": "17.2.0-next.0",
|
||||
"@angular/common": "17.2.0-next.0",
|
||||
"@angular/compiler": "17.2.0-next.0",
|
||||
"@angular/core": "17.2.0-next.0",
|
||||
"@angular/elements": "17.2.0-next.0",
|
||||
"@angular/forms": "17.2.0-next.0",
|
||||
"@angular/material": "17.2.0-next.0",
|
||||
"@angular/platform-browser": "17.2.0-next.0",
|
||||
"@angular/platform-browser-dynamic": "17.2.0-next.0",
|
||||
"@angular/router": "17.2.0-next.0",
|
||||
"@angular/service-worker": "17.2.0-next.0",
|
||||
"@angular/animations": "17.2.0-rc.1",
|
||||
"@angular/cdk": "17.2.0-rc.0",
|
||||
"@angular/common": "17.2.0-rc.1",
|
||||
"@angular/compiler": "17.2.0-rc.1",
|
||||
"@angular/core": "17.2.0-rc.1",
|
||||
"@angular/elements": "17.2.0-rc.1",
|
||||
"@angular/forms": "17.2.0-rc.1",
|
||||
"@angular/material": "17.2.0-rc.0",
|
||||
"@angular/platform-browser": "17.2.0-rc.1",
|
||||
"@angular/platform-browser-dynamic": "17.2.0-rc.1",
|
||||
"@angular/router": "17.2.0-rc.1",
|
||||
"@angular/service-worker": "17.2.0-rc.1",
|
||||
"rxjs": "~7.8.0",
|
||||
"safevalues": "^0.5.0",
|
||||
"tslib": "^2.3.0",
|
||||
"zone.js": "~0.14.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@angular-devkit/architect-cli": "0.1702.0-next.0",
|
||||
"@angular-devkit/build-angular": "17.2.0-next.0",
|
||||
"@angular-devkit/architect-cli": "0.1702.0-rc.0",
|
||||
"@angular-devkit/build-angular": "17.2.0-rc.0",
|
||||
"@angular-eslint/builder": "16.0.3",
|
||||
"@angular-eslint/eslint-plugin": "16.0.3",
|
||||
"@angular-eslint/eslint-plugin-template": "16.0.3",
|
||||
"@angular-eslint/template-parser": "^16.0.3",
|
||||
"@angular/build-tooling": "https://github.com/angular/dev-infra-private-build-tooling-builds.git#07da06414a4ce04a8f9710f2bc9346c2c753a0d2",
|
||||
"@angular/cli": "17.2.0-next.0",
|
||||
"@angular/compiler-cli": "17.2.0-next.0",
|
||||
"@angular/build-tooling": "https://github.com/angular/dev-infra-private-build-tooling-builds.git#7c4cf003cb4ac849986beaa243d7e85a893612f2",
|
||||
"@angular/cli": "17.2.0-rc.0",
|
||||
"@angular/compiler-cli": "17.2.0-rc.1",
|
||||
"@bazel/bazelisk": "^1.7.5",
|
||||
"@bazel/buildozer": "^6.0.0",
|
||||
"@bazel/ibazel": "^0.16.2",
|
||||
|
||||
@@ -94,6 +94,10 @@ aio-api-list {
|
||||
h2 {
|
||||
margin-top: 16px;
|
||||
margin-bottom: 16px;
|
||||
|
||||
& .deprecated-api-item {
|
||||
text-decoration: line-through;
|
||||
}
|
||||
}
|
||||
|
||||
/* API CLASS LIST */
|
||||
|
||||
@@ -13,14 +13,14 @@
|
||||
},
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@angular/animations": "17.1.1",
|
||||
"@angular/common": "17.1.1",
|
||||
"@angular/compiler": "17.1.1",
|
||||
"@angular/core": "17.1.1",
|
||||
"@angular/forms": "17.1.1",
|
||||
"@angular/platform-browser": "17.1.1",
|
||||
"@angular/platform-browser-dynamic": "17.1.1",
|
||||
"@angular/router": "17.1.1",
|
||||
"@angular/animations": "17.1.3",
|
||||
"@angular/common": "17.1.3",
|
||||
"@angular/compiler": "17.1.3",
|
||||
"@angular/core": "17.1.3",
|
||||
"@angular/forms": "17.1.3",
|
||||
"@angular/platform-browser": "17.1.3",
|
||||
"@angular/platform-browser-dynamic": "17.1.3",
|
||||
"@angular/router": "17.1.3",
|
||||
"angular": "^1.8.3",
|
||||
"angular-in-memory-web-api": "~0.17.0",
|
||||
"angular-route": "1.8.3",
|
||||
@@ -29,9 +29,9 @@
|
||||
"zone.js": "~0.14.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@angular-devkit/build-angular": "17.1.1",
|
||||
"@angular/cli": "17.1.1",
|
||||
"@angular/compiler-cli": "17.1.1",
|
||||
"@angular-devkit/build-angular": "17.1.3",
|
||||
"@angular/cli": "17.1.3",
|
||||
"@angular/compiler-cli": "17.1.3",
|
||||
"@types/angular": "^1.8.4",
|
||||
"@types/angular-route": "^1.7.2",
|
||||
"@types/jasmine": "~5.1.0",
|
||||
|
||||
@@ -13,23 +13,23 @@
|
||||
},
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@angular/animations": "17.1.1",
|
||||
"@angular/common": "17.1.1",
|
||||
"@angular/compiler": "17.1.1",
|
||||
"@angular/core": "17.1.1",
|
||||
"@angular/forms": "17.1.1",
|
||||
"@angular/platform-browser": "17.1.1",
|
||||
"@angular/platform-browser-dynamic": "17.1.1",
|
||||
"@angular/router": "17.1.1",
|
||||
"@angular/animations": "17.1.3",
|
||||
"@angular/common": "17.1.3",
|
||||
"@angular/compiler": "17.1.3",
|
||||
"@angular/core": "17.1.3",
|
||||
"@angular/forms": "17.1.3",
|
||||
"@angular/platform-browser": "17.1.3",
|
||||
"@angular/platform-browser-dynamic": "17.1.3",
|
||||
"@angular/router": "17.1.3",
|
||||
"angular-in-memory-web-api": "~0.17.0",
|
||||
"rxjs": "~7.8.0",
|
||||
"tslib": "^2.3.0",
|
||||
"zone.js": "~0.14.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@angular-devkit/build-angular": "17.1.1",
|
||||
"@angular/cli": "17.1.1",
|
||||
"@angular/compiler-cli": "17.1.1",
|
||||
"@angular-devkit/build-angular": "17.1.3",
|
||||
"@angular/cli": "17.1.3",
|
||||
"@angular/compiler-cli": "17.1.3",
|
||||
"@types/jasmine": "~5.1.0",
|
||||
"@types/node": "^16.11.35",
|
||||
"copyfiles": "^2.4.1",
|
||||
|
||||
@@ -13,24 +13,24 @@
|
||||
},
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@angular/animations": "17.1.1",
|
||||
"@angular/common": "17.1.1",
|
||||
"@angular/compiler": "17.1.1",
|
||||
"@angular/core": "17.1.1",
|
||||
"@angular/elements": "17.1.1",
|
||||
"@angular/forms": "17.1.1",
|
||||
"@angular/platform-browser": "17.1.1",
|
||||
"@angular/platform-browser-dynamic": "17.1.1",
|
||||
"@angular/router": "17.1.1",
|
||||
"@angular/animations": "17.1.3",
|
||||
"@angular/common": "17.1.3",
|
||||
"@angular/compiler": "17.1.3",
|
||||
"@angular/core": "17.1.3",
|
||||
"@angular/elements": "17.1.3",
|
||||
"@angular/forms": "17.1.3",
|
||||
"@angular/platform-browser": "17.1.3",
|
||||
"@angular/platform-browser-dynamic": "17.1.3",
|
||||
"@angular/router": "17.1.3",
|
||||
"angular-in-memory-web-api": "~0.17.0",
|
||||
"rxjs": "~7.8.0",
|
||||
"tslib": "^2.3.0",
|
||||
"zone.js": "~0.14.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@angular-devkit/build-angular": "17.1.1",
|
||||
"@angular/cli": "17.1.1",
|
||||
"@angular/compiler-cli": "17.1.1",
|
||||
"@angular-devkit/build-angular": "17.1.3",
|
||||
"@angular/cli": "17.1.3",
|
||||
"@angular/compiler-cli": "17.1.3",
|
||||
"@types/jasmine": "~5.1.0",
|
||||
"@types/node": "^16.11.35",
|
||||
"jasmine-core": "~5.1.0",
|
||||
|
||||
@@ -15,24 +15,24 @@
|
||||
},
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@angular/animations": "17.1.1",
|
||||
"@angular/common": "17.1.1",
|
||||
"@angular/compiler": "17.1.1",
|
||||
"@angular/core": "17.1.1",
|
||||
"@angular/forms": "17.1.1",
|
||||
"@angular/localize": "17.1.1",
|
||||
"@angular/platform-browser": "17.1.1",
|
||||
"@angular/platform-browser-dynamic": "17.1.1",
|
||||
"@angular/router": "17.1.1",
|
||||
"@angular/animations": "17.1.3",
|
||||
"@angular/common": "17.1.3",
|
||||
"@angular/compiler": "17.1.3",
|
||||
"@angular/core": "17.1.3",
|
||||
"@angular/forms": "17.1.3",
|
||||
"@angular/localize": "17.1.3",
|
||||
"@angular/platform-browser": "17.1.3",
|
||||
"@angular/platform-browser-dynamic": "17.1.3",
|
||||
"@angular/router": "17.1.3",
|
||||
"angular-in-memory-web-api": "~0.17.0",
|
||||
"rxjs": "~7.8.0",
|
||||
"tslib": "^2.3.0",
|
||||
"zone.js": "~0.14.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@angular-devkit/build-angular": "17.1.1",
|
||||
"@angular/cli": "17.1.1",
|
||||
"@angular/compiler-cli": "17.1.1",
|
||||
"@angular-devkit/build-angular": "17.1.3",
|
||||
"@angular/cli": "17.1.3",
|
||||
"@angular/compiler-cli": "17.1.3",
|
||||
"@types/jasmine": "~5.1.0",
|
||||
"@types/node": "^16.11.35",
|
||||
"jasmine-core": "~5.1.0",
|
||||
|
||||
@@ -13,24 +13,24 @@
|
||||
},
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@angular/animations": "17.1.1",
|
||||
"@angular/common": "17.1.1",
|
||||
"@angular/compiler": "17.1.1",
|
||||
"@angular/core": "17.1.1",
|
||||
"@angular/forms": "17.1.1",
|
||||
"@angular/platform-browser": "17.1.1",
|
||||
"@angular/platform-browser-dynamic": "17.1.1",
|
||||
"@angular/router": "17.1.1",
|
||||
"@angular/service-worker": "17.1.1",
|
||||
"@angular/animations": "17.1.3",
|
||||
"@angular/common": "17.1.3",
|
||||
"@angular/compiler": "17.1.3",
|
||||
"@angular/core": "17.1.3",
|
||||
"@angular/forms": "17.1.3",
|
||||
"@angular/platform-browser": "17.1.3",
|
||||
"@angular/platform-browser-dynamic": "17.1.3",
|
||||
"@angular/router": "17.1.3",
|
||||
"@angular/service-worker": "17.1.3",
|
||||
"angular-in-memory-web-api": "~0.17.0",
|
||||
"rxjs": "~7.8.0",
|
||||
"tslib": "^2.3.0",
|
||||
"zone.js": "~0.14.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@angular-devkit/build-angular": "17.1.1",
|
||||
"@angular/cli": "17.1.1",
|
||||
"@angular/compiler-cli": "17.1.1",
|
||||
"@angular-devkit/build-angular": "17.1.3",
|
||||
"@angular/cli": "17.1.3",
|
||||
"@angular/compiler-cli": "17.1.3",
|
||||
"@types/jasmine": "~5.1.0",
|
||||
"@types/node": "^16.11.35",
|
||||
"jasmine-core": "~5.1.0",
|
||||
|
||||
@@ -25,15 +25,15 @@
|
||||
},
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@angular/animations": "17.1.1",
|
||||
"@angular/common": "17.1.1",
|
||||
"@angular/compiler": "17.1.1",
|
||||
"@angular/core": "17.1.1",
|
||||
"@angular/forms": "17.1.1",
|
||||
"@angular/platform-browser": "17.1.1",
|
||||
"@angular/platform-browser-dynamic": "17.1.1",
|
||||
"@angular/router": "17.1.1",
|
||||
"@angular/upgrade": "17.1.1",
|
||||
"@angular/animations": "17.1.3",
|
||||
"@angular/common": "17.1.3",
|
||||
"@angular/compiler": "17.1.3",
|
||||
"@angular/core": "17.1.3",
|
||||
"@angular/forms": "17.1.3",
|
||||
"@angular/platform-browser": "17.1.3",
|
||||
"@angular/platform-browser-dynamic": "17.1.3",
|
||||
"@angular/router": "17.1.3",
|
||||
"@angular/upgrade": "17.1.3",
|
||||
"core-js": "^3.0.0",
|
||||
"rxjs": "~7.8.0",
|
||||
"systemjs": "~0.19.39",
|
||||
@@ -42,7 +42,7 @@
|
||||
"zone.js": "~0.14.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@angular/compiler-cli": "17.1.1",
|
||||
"@angular/compiler-cli": "17.1.3",
|
||||
"@rollup/plugin-commonjs": "^25.0.0",
|
||||
"@rollup/plugin-node-resolve": "^13.2.0",
|
||||
"@types/angular": "^1.8.4",
|
||||
|
||||
@@ -13,16 +13,16 @@
|
||||
},
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@angular/animations": "17.1.1",
|
||||
"@angular/common": "17.1.1",
|
||||
"@angular/compiler": "17.1.1",
|
||||
"@angular/core": "17.1.1",
|
||||
"@angular/forms": "17.1.1",
|
||||
"@angular/platform-browser": "17.1.1",
|
||||
"@angular/platform-browser-dynamic": "17.1.1",
|
||||
"@angular/platform-server": "17.1.1",
|
||||
"@angular/router": "17.1.1",
|
||||
"@angular/ssr": "17.1.1",
|
||||
"@angular/animations": "17.1.3",
|
||||
"@angular/common": "17.1.3",
|
||||
"@angular/compiler": "17.1.3",
|
||||
"@angular/core": "17.1.3",
|
||||
"@angular/forms": "17.1.3",
|
||||
"@angular/platform-browser": "17.1.3",
|
||||
"@angular/platform-browser-dynamic": "17.1.3",
|
||||
"@angular/platform-server": "17.1.3",
|
||||
"@angular/router": "17.1.3",
|
||||
"@angular/ssr": "17.1.3",
|
||||
"angular-in-memory-web-api": "~0.17.0",
|
||||
"express": "^4.18.1",
|
||||
"rxjs": "~7.8.0",
|
||||
@@ -30,9 +30,9 @@
|
||||
"zone.js": "~0.14.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@angular-devkit/build-angular": "17.1.1",
|
||||
"@angular/cli": "17.1.1",
|
||||
"@angular/compiler-cli": "17.1.1",
|
||||
"@angular-devkit/build-angular": "17.1.3",
|
||||
"@angular/cli": "17.1.3",
|
||||
"@angular/compiler-cli": "17.1.3",
|
||||
"@types/express": "^4.17.13",
|
||||
"@types/jasmine": "~5.1.0",
|
||||
"@types/node": "^16.11.35",
|
||||
|
||||
@@ -19,20 +19,20 @@
|
||||
"license": "MIT",
|
||||
"repository": {},
|
||||
"dependencies": {
|
||||
"@angular/animations": "17.1.1",
|
||||
"@angular/common": "17.1.1",
|
||||
"@angular/compiler": "17.1.1",
|
||||
"@angular/core": "17.1.1",
|
||||
"@angular/elements": "17.1.1",
|
||||
"@angular/forms": "17.1.1",
|
||||
"@angular/localize": "17.1.1",
|
||||
"@angular/platform-browser": "17.1.1",
|
||||
"@angular/platform-browser-dynamic": "17.1.1",
|
||||
"@angular/platform-server": "17.1.1",
|
||||
"@angular/router": "17.1.1",
|
||||
"@angular/service-worker": "17.1.1",
|
||||
"@angular/ssr": "17.1.1",
|
||||
"@angular/upgrade": "17.1.1",
|
||||
"@angular/animations": "17.1.3",
|
||||
"@angular/common": "17.1.3",
|
||||
"@angular/compiler": "17.1.3",
|
||||
"@angular/core": "17.1.3",
|
||||
"@angular/elements": "17.1.3",
|
||||
"@angular/forms": "17.1.3",
|
||||
"@angular/localize": "17.1.3",
|
||||
"@angular/platform-browser": "17.1.3",
|
||||
"@angular/platform-browser-dynamic": "17.1.3",
|
||||
"@angular/platform-server": "17.1.3",
|
||||
"@angular/router": "17.1.3",
|
||||
"@angular/service-worker": "17.1.3",
|
||||
"@angular/ssr": "17.1.3",
|
||||
"@angular/upgrade": "17.1.3",
|
||||
"angular": "^1.8.3",
|
||||
"angular-in-memory-web-api": "~0.17.0",
|
||||
"angular-route": "1.8.3",
|
||||
@@ -45,9 +45,9 @@
|
||||
"zone.js": "~0.14.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@angular-devkit/build-angular": "17.1.1",
|
||||
"@angular/cli": "17.1.1",
|
||||
"@angular/compiler-cli": "17.1.1",
|
||||
"@angular-devkit/build-angular": "17.1.3",
|
||||
"@angular/cli": "17.1.3",
|
||||
"@angular/compiler-cli": "17.1.3",
|
||||
"@rollup/plugin-commonjs": "^25.0.0",
|
||||
"@rollup/plugin-node-resolve": "^13.2.0",
|
||||
"@types/angular": "^1.8.4",
|
||||
|
||||
+105
-105
@@ -10,23 +10,23 @@
|
||||
"@jridgewell/gen-mapping" "^0.3.0"
|
||||
"@jridgewell/trace-mapping" "^0.3.9"
|
||||
|
||||
"@angular-devkit/architect@0.1701.1":
|
||||
version "0.1701.1"
|
||||
resolved "https://registry.yarnpkg.com/@angular-devkit/architect/-/architect-0.1701.1.tgz#80e2bc94763e21c273ef6d9f905390f1d935df60"
|
||||
integrity sha512-vT3ZRAIfNyIg0vJWT6umPbCKiKFCukNkxLe9kgOU0tinZKNr/LgWYaBZ92Rxxi6C3NrAnmAYjsih8x4zdyoRXw==
|
||||
"@angular-devkit/architect@0.1701.3":
|
||||
version "0.1701.3"
|
||||
resolved "https://registry.yarnpkg.com/@angular-devkit/architect/-/architect-0.1701.3.tgz#974cc8680baa5b8440cfc8039bbc4c46008e80bf"
|
||||
integrity sha512-K5rvhslbXNwx04cCLviEJCA27MwoJRMMzALFXySi9BqjZnZUOtZnOBuuCdrTPaRmFaYqGO4Im5GNzpbb/NB8zg==
|
||||
dependencies:
|
||||
"@angular-devkit/core" "17.1.1"
|
||||
"@angular-devkit/core" "17.1.3"
|
||||
rxjs "7.8.1"
|
||||
|
||||
"@angular-devkit/build-angular@17.1.1":
|
||||
version "17.1.1"
|
||||
resolved "https://registry.yarnpkg.com/@angular-devkit/build-angular/-/build-angular-17.1.1.tgz#26a65e3daf8d306e39634b3322ee68ff19d0118c"
|
||||
integrity sha512-GchDM8H+RQNts731c+jnhDgOm0PnCS3YB12uVwRiGsaNsUMrqKnu3P0poh6AImDMPyXKnIvTWLDCMD8TDziR0A==
|
||||
"@angular-devkit/build-angular@17.1.3":
|
||||
version "17.1.3"
|
||||
resolved "https://registry.yarnpkg.com/@angular-devkit/build-angular/-/build-angular-17.1.3.tgz#05634e6a81ade1eb4e12844e37cbf636bf949bc4"
|
||||
integrity sha512-pusFVSWMnrm2GrF3+Fw19OhA2rNw4WkfTMUruhaKAjW5QIvZ3wHYf+pH//1Ud+tuhFBi9BH7UALP2vnJMu1ehw==
|
||||
dependencies:
|
||||
"@ampproject/remapping" "2.2.1"
|
||||
"@angular-devkit/architect" "0.1701.1"
|
||||
"@angular-devkit/build-webpack" "0.1701.1"
|
||||
"@angular-devkit/core" "17.1.1"
|
||||
"@angular-devkit/architect" "0.1701.3"
|
||||
"@angular-devkit/build-webpack" "0.1701.3"
|
||||
"@angular-devkit/core" "17.1.3"
|
||||
"@babel/core" "7.23.7"
|
||||
"@babel/generator" "7.23.6"
|
||||
"@babel/helper-annotate-as-pure" "7.22.5"
|
||||
@@ -37,7 +37,7 @@
|
||||
"@babel/preset-env" "7.23.7"
|
||||
"@babel/runtime" "7.23.7"
|
||||
"@discoveryjs/json-ext" "0.5.7"
|
||||
"@ngtools/webpack" "17.1.1"
|
||||
"@ngtools/webpack" "17.1.3"
|
||||
"@vitejs/plugin-basic-ssl" "1.0.2"
|
||||
ansi-colors "4.1.3"
|
||||
autoprefixer "10.4.16"
|
||||
@@ -90,18 +90,18 @@
|
||||
optionalDependencies:
|
||||
esbuild "0.19.11"
|
||||
|
||||
"@angular-devkit/build-webpack@0.1701.1":
|
||||
version "0.1701.1"
|
||||
resolved "https://registry.yarnpkg.com/@angular-devkit/build-webpack/-/build-webpack-0.1701.1.tgz#ab510fef446d2a9a2e71d4b1a688000494b28e09"
|
||||
integrity sha512-YgNl/6xLmI0XdUCu/H4Jyi34BhrANCDP4N2Pz+tGwnz2+Vl8oZGLPGtKVbh/LKSAmEHk/B6GQUekSBpKWrPJoA==
|
||||
"@angular-devkit/build-webpack@0.1701.3":
|
||||
version "0.1701.3"
|
||||
resolved "https://registry.yarnpkg.com/@angular-devkit/build-webpack/-/build-webpack-0.1701.3.tgz#584275d98be9e76d5db3d870ff37c57175d04a45"
|
||||
integrity sha512-fpZtJf6yvXM7mX1T83caeYpa0e3zPv7sgKmx0ZIJKGL8+DETgNcCCeCTgui7HMBcHGCD8yj72DZ8xMMBWwVBIA==
|
||||
dependencies:
|
||||
"@angular-devkit/architect" "0.1701.1"
|
||||
"@angular-devkit/architect" "0.1701.3"
|
||||
rxjs "7.8.1"
|
||||
|
||||
"@angular-devkit/core@17.1.1":
|
||||
version "17.1.1"
|
||||
resolved "https://registry.yarnpkg.com/@angular-devkit/core/-/core-17.1.1.tgz#c140e6fcd158cea87e15d0d09f3879fc23a52637"
|
||||
integrity sha512-b1wd1caegc1p18nTrfPhfHQAZW1GnWWKGldq5MZ8C/nkgJbjjN8SKb1Vw7GONkOnH6KxWDAXS4i93/wdQcz4Bg==
|
||||
"@angular-devkit/core@17.1.3":
|
||||
version "17.1.3"
|
||||
resolved "https://registry.yarnpkg.com/@angular-devkit/core/-/core-17.1.3.tgz#d3b2212f9fa753506b1ccab24ee34edcdcd02e63"
|
||||
integrity sha512-iuVK4hyW3YhusxIi8zGBvvVA9pWtDT3H6LQbWdVk9D3jXCZBIrEMklvAiJErqficKnUurf6gtFOeA8Fop6GotA==
|
||||
dependencies:
|
||||
ajv "8.12.0"
|
||||
ajv-formats "2.1.1"
|
||||
@@ -110,33 +110,33 @@
|
||||
rxjs "7.8.1"
|
||||
source-map "0.7.4"
|
||||
|
||||
"@angular-devkit/schematics@17.1.1":
|
||||
version "17.1.1"
|
||||
resolved "https://registry.yarnpkg.com/@angular-devkit/schematics/-/schematics-17.1.1.tgz#caaaeff64519d691121a92c92dbfd53581ff0bc4"
|
||||
integrity sha512-3AtEO7k0Znzg11o51ZqebkW8063QkZ7Y7BC96Oye+wSdpT3ow57P0w0UtOpUNesNKzj1iMuPWqqm4i+YqitjCw==
|
||||
"@angular-devkit/schematics@17.1.3":
|
||||
version "17.1.3"
|
||||
resolved "https://registry.yarnpkg.com/@angular-devkit/schematics/-/schematics-17.1.3.tgz#9fc4c82f64af9375249910d26093e449a73f0166"
|
||||
integrity sha512-zKoWG1hDfvi1vR9Hqoca9hWo9vDg8evmQvGcBW5jXR5ndZi5Oit/uDcGdA8WUKvBd1EG7WMqp0FgcDR9EA9WCw==
|
||||
dependencies:
|
||||
"@angular-devkit/core" "17.1.1"
|
||||
"@angular-devkit/core" "17.1.3"
|
||||
jsonc-parser "3.2.0"
|
||||
magic-string "0.30.5"
|
||||
ora "5.4.1"
|
||||
rxjs "7.8.1"
|
||||
|
||||
"@angular/animations@17.1.1":
|
||||
version "17.1.1"
|
||||
resolved "https://registry.yarnpkg.com/@angular/animations/-/animations-17.1.1.tgz#45b311fd9bd1d4aee1a4bedb49a4164cc622cd51"
|
||||
integrity sha512-85qm8b4fNKa5zx4YlvCvAb3lWGlRsEcClnpCuwSVP8pXG7n/cW8MsJOh8i/wOkQTr9Ac7vvAE+yFWMi2A/qlTg==
|
||||
"@angular/animations@17.1.3":
|
||||
version "17.1.3"
|
||||
resolved "https://registry.yarnpkg.com/@angular/animations/-/animations-17.1.3.tgz#be67fc479cfc700d7c1270222003c6d2bc622194"
|
||||
integrity sha512-AS9CHOjjKqkuAzlKEMJfAkZfkIdSoagB3D8HwvH+ZHo6GVJc9KbtLQn/okNijFK+Fg7QK/hYbQ3lJhjgk0GQDA==
|
||||
dependencies:
|
||||
tslib "^2.3.0"
|
||||
|
||||
"@angular/cli@17.1.1":
|
||||
version "17.1.1"
|
||||
resolved "https://registry.yarnpkg.com/@angular/cli/-/cli-17.1.1.tgz#3aadcee37b8fd5b4ecb6c7ae8057dfb26973cc73"
|
||||
integrity sha512-JG/Vf+PScR3PC6u7B+jFF4s5eBByzCpOfCfRFw98nlCqDAOxYOig7wi2Sbp5fnvILQH8vbc/NG8MzdgONrG6Hg==
|
||||
"@angular/cli@17.1.3":
|
||||
version "17.1.3"
|
||||
resolved "https://registry.yarnpkg.com/@angular/cli/-/cli-17.1.3.tgz#45af3e5041f3df14ff10dc48ee64960f575d5aac"
|
||||
integrity sha512-ysPWDdqo2cwfeskKVAg8p4C8xuezWcIWyW/ACSjWw6yp4OZvyVd6cGZrc0POVZzAPtTOYJSgWOpF/DCHQFluSg==
|
||||
dependencies:
|
||||
"@angular-devkit/architect" "0.1701.1"
|
||||
"@angular-devkit/core" "17.1.1"
|
||||
"@angular-devkit/schematics" "17.1.1"
|
||||
"@schematics/angular" "17.1.1"
|
||||
"@angular-devkit/architect" "0.1701.3"
|
||||
"@angular-devkit/core" "17.1.3"
|
||||
"@angular-devkit/schematics" "17.1.3"
|
||||
"@schematics/angular" "17.1.3"
|
||||
"@yarnpkg/lockfile" "1.1.0"
|
||||
ansi-colors "4.1.3"
|
||||
ini "4.1.1"
|
||||
@@ -152,17 +152,17 @@
|
||||
symbol-observable "4.0.0"
|
||||
yargs "17.7.2"
|
||||
|
||||
"@angular/common@17.1.1":
|
||||
version "17.1.1"
|
||||
resolved "https://registry.yarnpkg.com/@angular/common/-/common-17.1.1.tgz#67f6469329130d7c43d36e1cb3e6220157391ea0"
|
||||
integrity sha512-YMM2vImWJg7H3Yaej7ncGpFKT28V2Y6X9/rLpRdSKAiUbcbj7GeWtX/upfZGR9KmD08baYZw0YTNMR03Ubv/mg==
|
||||
"@angular/common@17.1.3":
|
||||
version "17.1.3"
|
||||
resolved "https://registry.yarnpkg.com/@angular/common/-/common-17.1.3.tgz#d8411db80510a99a23ad01a56264c3594625936a"
|
||||
integrity sha512-AzLzoNSeRSNGBQk0K+iG0XdYG36SDeJqYqE8rfoiWuv1NDFLL05UJM2/fQfaMNg0oX5bHOlHUqHFj3sFR/NVpw==
|
||||
dependencies:
|
||||
tslib "^2.3.0"
|
||||
|
||||
"@angular/compiler-cli@17.1.1":
|
||||
version "17.1.1"
|
||||
resolved "https://registry.yarnpkg.com/@angular/compiler-cli/-/compiler-cli-17.1.1.tgz#554e4cd090f4d64294bedd03868d3853a6d1af45"
|
||||
integrity sha512-d6Aev1P92q7wd5u3UcJifzNlU9svxaYI2Ts6MKoD4jY4/GPN/gPDqi20weDMujEgirrkcwGbsCXBRoEGkA5c9A==
|
||||
"@angular/compiler-cli@17.1.3":
|
||||
version "17.1.3"
|
||||
resolved "https://registry.yarnpkg.com/@angular/compiler-cli/-/compiler-cli-17.1.3.tgz#f1cbb68eb089c7aa161bab315cdc1a0004fac768"
|
||||
integrity sha512-bNDHXo3Twub0BZK9OmXly+0REs0RuR1SUXlTAeq+0XubCvnBDvpg9peL7UTTGS5YRo9sUTBnR6faSUA1F5objQ==
|
||||
dependencies:
|
||||
"@babel/core" "7.23.2"
|
||||
"@jridgewell/sourcemap-codec" "^1.4.14"
|
||||
@@ -173,92 +173,92 @@
|
||||
tslib "^2.3.0"
|
||||
yargs "^17.2.1"
|
||||
|
||||
"@angular/compiler@17.1.1":
|
||||
version "17.1.1"
|
||||
resolved "https://registry.yarnpkg.com/@angular/compiler/-/compiler-17.1.1.tgz#b3e182390eb4f54690c0b6dd60d02ac389a1c0b4"
|
||||
integrity sha512-lEQ5YNMJQm2iO2EZbGkwL3SqnxtE2ENfymgbS023F6ACsnP3kKB2DMwOnIbGgQY4+8r4sJFiMAIjEkj5c9kttg==
|
||||
"@angular/compiler@17.1.3":
|
||||
version "17.1.3"
|
||||
resolved "https://registry.yarnpkg.com/@angular/compiler/-/compiler-17.1.3.tgz#157e834eb4b0cb4f339bc8a0da8dcb438e92034a"
|
||||
integrity sha512-k/s21gPPKStxVOLr6l4Y145OIxyBY7BhTPVOl/qEAgE+IcZ9vkiA8dYl8yjL884Kl1ZKPmFA3AofMJjWjZGNag==
|
||||
dependencies:
|
||||
tslib "^2.3.0"
|
||||
|
||||
"@angular/core@17.1.1":
|
||||
version "17.1.1"
|
||||
resolved "https://registry.yarnpkg.com/@angular/core/-/core-17.1.1.tgz#9da19f0c832d69994c79255c56e81f40ae7ae68a"
|
||||
integrity sha512-JtNYM9eHr8eUSrGPq/kn0+/F+TSZ7EBWxZhM1ZndOlGu1gA4fGhrDid4ZXIHIs07DbM4NZjMn+LhRyx02YDsSA==
|
||||
"@angular/core@17.1.3":
|
||||
version "17.1.3"
|
||||
resolved "https://registry.yarnpkg.com/@angular/core/-/core-17.1.3.tgz#12f4adb8d1037ba1572c7531997d82a82fbe8fc1"
|
||||
integrity sha512-2lZ4DRHN8KJ/aQads+YXIcx5Ri9yyeFIlw69m5Pn7wAi/+Rakg7IsclgLaWs7aBtWwMHG7LnqFKxAVq7CjXKtA==
|
||||
dependencies:
|
||||
tslib "^2.3.0"
|
||||
|
||||
"@angular/elements@17.1.1":
|
||||
version "17.1.1"
|
||||
resolved "https://registry.yarnpkg.com/@angular/elements/-/elements-17.1.1.tgz#1fbbe9291a5029949a6bc5862b3ca11b945d94a9"
|
||||
integrity sha512-GrPUCSw3RpjX1XmUpXrRdf19YE27hFDqXNpc/t36gWrQ3ndkYfU+I+fhNXigSoBvDW9/Pk+EJXTKX1LJ2CeOdw==
|
||||
"@angular/elements@17.1.3":
|
||||
version "17.1.3"
|
||||
resolved "https://registry.yarnpkg.com/@angular/elements/-/elements-17.1.3.tgz#944cb425e8c724417e499ab730009ce32d5ed2f7"
|
||||
integrity sha512-q2JCogiHFB7Ac3IpiQGIUNF7ZDM5t/fF7xlDFvTLto7HHCb0g8y4erzmUmV+QRKX0YKedo5QAkd8w9TFlJ4ecA==
|
||||
dependencies:
|
||||
tslib "^2.3.0"
|
||||
|
||||
"@angular/forms@17.1.1":
|
||||
version "17.1.1"
|
||||
resolved "https://registry.yarnpkg.com/@angular/forms/-/forms-17.1.1.tgz#1e07572b207f4e78dc8abe9435d00c8fc0f37343"
|
||||
integrity sha512-rqHVzaJDV8+VbnfC6mDgzX6ooa0X0hmnd+XfuOZaEJ7MtyOmqQ8qas2PAKXU7nMIImYXfYc4O4XWbSc1pRy1Hw==
|
||||
"@angular/forms@17.1.3":
|
||||
version "17.1.3"
|
||||
resolved "https://registry.yarnpkg.com/@angular/forms/-/forms-17.1.3.tgz#1e87b39e237a1d6f7cc2f78ad8d6b597b5ef08aa"
|
||||
integrity sha512-aNa0jGLT5d+hnKVrSo8tk3TRo/NLNu1RxLNx8RhIczKAeCK3eD8SvTMy27iJtyXmNG2GWN7QPiDeGepd75nbxQ==
|
||||
dependencies:
|
||||
tslib "^2.3.0"
|
||||
|
||||
"@angular/localize@17.1.1":
|
||||
version "17.1.1"
|
||||
resolved "https://registry.yarnpkg.com/@angular/localize/-/localize-17.1.1.tgz#f64b1724bdf4f0ab33551ca7bddd5e92d8cba6b8"
|
||||
integrity sha512-vZc+YhpE7eRWFzdZFOidSjZlTl6+8VH0x2m7WesaSgw2fu1IgOQffZFnaGveAQ8DXbMfgBb4exPNJFPYmNgoSQ==
|
||||
"@angular/localize@17.1.3":
|
||||
version "17.1.3"
|
||||
resolved "https://registry.yarnpkg.com/@angular/localize/-/localize-17.1.3.tgz#626564dcd89a3f5d26cd31eb2fcdc848e01898ad"
|
||||
integrity sha512-Jdz5FWcaQLRhEhUHJk0iyMOT1ts4HHcY9B20mBmAd7u3kqEoBc57N8hOX0vHjU4/rk1tGqIvzfhJJf6zqIWcZQ==
|
||||
dependencies:
|
||||
"@babel/core" "7.23.2"
|
||||
"@types/babel__core" "7.20.2"
|
||||
fast-glob "3.3.1"
|
||||
yargs "^17.2.1"
|
||||
|
||||
"@angular/platform-browser-dynamic@17.1.1":
|
||||
version "17.1.1"
|
||||
resolved "https://registry.yarnpkg.com/@angular/platform-browser-dynamic/-/platform-browser-dynamic-17.1.1.tgz#8d389d9739319a1260df27aceb7f4fffdecd28b7"
|
||||
integrity sha512-UKI8966nwo+p01EjmQdkepLIeVLpPZTSDZAM4va7CfMO6lbCN5xFecDd/sVbut8J6ySIsbJxyDkP+SHMQjE+xg==
|
||||
"@angular/platform-browser-dynamic@17.1.3":
|
||||
version "17.1.3"
|
||||
resolved "https://registry.yarnpkg.com/@angular/platform-browser-dynamic/-/platform-browser-dynamic-17.1.3.tgz#7391d547a22b85e95de8ee1f16faf6b876f26ae1"
|
||||
integrity sha512-0lFhcFJfDzCSSVe8l8OY+UgUiwUwcbxwpvLod3XWBpf1iEUlr5720FIMA3VJYwpW3Oj4Uey3nVm13EMtRqpqdA==
|
||||
dependencies:
|
||||
tslib "^2.3.0"
|
||||
|
||||
"@angular/platform-browser@17.1.1":
|
||||
version "17.1.1"
|
||||
resolved "https://registry.yarnpkg.com/@angular/platform-browser/-/platform-browser-17.1.1.tgz#758b289f9d4920217c3bf754135e1558cd3d1cc1"
|
||||
integrity sha512-/80znuEkdDvsF6EX/fe03isQlLCUS9+ldCgB4n0ZL+qAkf2/lJlU3n97SyEN7rzb189U+K1fDe0fb1nDwbbcWQ==
|
||||
"@angular/platform-browser@17.1.3":
|
||||
version "17.1.3"
|
||||
resolved "https://registry.yarnpkg.com/@angular/platform-browser/-/platform-browser-17.1.3.tgz#3883cf089f2e5bd24b99df1e7c2cdd3df3d97514"
|
||||
integrity sha512-onPCvdk9f/6OhOo2zP6nfGKpzLma1QIxpFqD3jymbmIJTcVMOOQDMYW3eLtY+uSX8ribcJ7GQcbDGIM4rliTFg==
|
||||
dependencies:
|
||||
tslib "^2.3.0"
|
||||
|
||||
"@angular/platform-server@17.1.1":
|
||||
version "17.1.1"
|
||||
resolved "https://registry.yarnpkg.com/@angular/platform-server/-/platform-server-17.1.1.tgz#9e0feec18c10974e3eed80e9a4e8cf28be6490a7"
|
||||
integrity sha512-zZbJL56m1n/btwVV0TNc/YPqXYZp9rcnDEvl9HHFNfoinkQrcxHy6MHQUjEyQ1IAIv9Y0f73pmSbJ86BcloIAw==
|
||||
"@angular/platform-server@17.1.3":
|
||||
version "17.1.3"
|
||||
resolved "https://registry.yarnpkg.com/@angular/platform-server/-/platform-server-17.1.3.tgz#b30236072a8a6f97198eaf41a121759e62922477"
|
||||
integrity sha512-EB+sk4oZuUPEbkGSM0jpHAKiZVH98IBlXaRYDcmRCRw1hjZwsxl0kAQNuInSGfwJlyjVF7WCrZ6eNCuxlxXiAQ==
|
||||
dependencies:
|
||||
tslib "^2.3.0"
|
||||
xhr2 "^0.2.0"
|
||||
|
||||
"@angular/router@17.1.1":
|
||||
version "17.1.1"
|
||||
resolved "https://registry.yarnpkg.com/@angular/router/-/router-17.1.1.tgz#752e5856360844512f78267bea5364c7b7dac0d7"
|
||||
integrity sha512-pPIRX0v8agij2dRSU25iwj9qFy0S25cztsy7bGfZ+M510jwRCqu1JsitqXtQ85XSv/bdFqiNiFU0UbwVFl+QiQ==
|
||||
"@angular/router@17.1.3":
|
||||
version "17.1.3"
|
||||
resolved "https://registry.yarnpkg.com/@angular/router/-/router-17.1.3.tgz#30d12b43079f44fda2ab968cde41da3548450d9e"
|
||||
integrity sha512-6HigdtFjm+76UU2hiLGLE2SpOecQhD6TnAVTocDuRitpN5m0dyiffBrqxarfNwoZuMdIiXyqClJR4JRo1rJjoQ==
|
||||
dependencies:
|
||||
tslib "^2.3.0"
|
||||
|
||||
"@angular/service-worker@17.1.1":
|
||||
version "17.1.1"
|
||||
resolved "https://registry.yarnpkg.com/@angular/service-worker/-/service-worker-17.1.1.tgz#1cc2823d9a5fce14197f008b3b226dcab4d24a58"
|
||||
integrity sha512-/ggG10tY5LWsGvJYLGhE0gdL8r9fEmRrZ6i+UCdwG/+Ydyb5GBxLS7OGH6yy0R8WP19FDRmVoDVMFJMtc4JMAw==
|
||||
"@angular/service-worker@17.1.3":
|
||||
version "17.1.3"
|
||||
resolved "https://registry.yarnpkg.com/@angular/service-worker/-/service-worker-17.1.3.tgz#1222de7a4752b6f8dc7bbb5dcc6015db13346b45"
|
||||
integrity sha512-DSndSwNvNnETiqtFAt/jPUMBUUfAxot7IhEUp19KFbS4HhasSZ+QxkNECN9bT6SUWjeaN7wvPcvR4gdSkQs3Pg==
|
||||
dependencies:
|
||||
tslib "^2.3.0"
|
||||
|
||||
"@angular/ssr@17.1.1":
|
||||
version "17.1.1"
|
||||
resolved "https://registry.yarnpkg.com/@angular/ssr/-/ssr-17.1.1.tgz#8199fc8ce5d321c45592f471b523813ea8d39885"
|
||||
integrity sha512-HHWk827BBLkVNpwsp31GYO484vXdFGMUoegoHCVd3qZL7gXcaAdN+zreE9E1OnaIImZWpj99le3MAsH6ee69BQ==
|
||||
"@angular/ssr@17.1.3":
|
||||
version "17.1.3"
|
||||
resolved "https://registry.yarnpkg.com/@angular/ssr/-/ssr-17.1.3.tgz#4bab9172d31043baeef4fdafb8a7789567f1704e"
|
||||
integrity sha512-zgghp3gdh+pv//lYWWynfZOE9jkndSmflojgRWBR4FdAtapdyrm1MaE6TIwC7qT+r/sW0bKypK2fKxwN8sXpHw==
|
||||
dependencies:
|
||||
critters "0.0.20"
|
||||
tslib "^2.3.0"
|
||||
|
||||
"@angular/upgrade@17.1.1":
|
||||
version "17.1.1"
|
||||
resolved "https://registry.yarnpkg.com/@angular/upgrade/-/upgrade-17.1.1.tgz#2c6b8b4f690782423a499b0ca1c4f9cbd319bf17"
|
||||
integrity sha512-DfIzp/t3cZZGcCyNghpyzBQFDMKo8qmRADtmR3ToBujERX4cwDcEySa/XBnFDjedcDusQB8pABnLOiatNk3HzA==
|
||||
"@angular/upgrade@17.1.3":
|
||||
version "17.1.3"
|
||||
resolved "https://registry.yarnpkg.com/@angular/upgrade/-/upgrade-17.1.3.tgz#4cf0a05866dced04114cb40de9f9e75d6897c0e4"
|
||||
integrity sha512-Iw6J9jOg4P+CohWCsbchPnmOiLzuHhs5y0RqdA4+JL0dtTK8FkGkgaI39za74MYAbqNm6Z+q2yzlwyoe3gNEuw==
|
||||
dependencies:
|
||||
tslib "^2.3.0"
|
||||
|
||||
@@ -1737,10 +1737,10 @@
|
||||
dependencies:
|
||||
call-bind "^1.0.2"
|
||||
|
||||
"@ngtools/webpack@17.1.1":
|
||||
version "17.1.1"
|
||||
resolved "https://registry.yarnpkg.com/@ngtools/webpack/-/webpack-17.1.1.tgz#271177de0a382fccb5ba887a110ada9da79dc653"
|
||||
integrity sha512-uPWEpRuAUmMBZhYMpkEHNbeI8QAgeVM5lnWM+02lK75u1+sgYy32ed+RcRvEI+8hRQcsCQ8HtR4QubgJb4TzCQ==
|
||||
"@ngtools/webpack@17.1.3":
|
||||
version "17.1.3"
|
||||
resolved "https://registry.yarnpkg.com/@ngtools/webpack/-/webpack-17.1.3.tgz#510c5f6f6bf9ae470b0e43d4560cfc4ffc3a5ed5"
|
||||
integrity sha512-mszRSb7aMNKHnkh3Jrfo83KVOguX/cUamJJcGIYe9o7tnLGRIoMp4vP0fx6Og4J0/CGDRhSDG4IiJ29aOU7K8A==
|
||||
|
||||
"@nodelib/fs.scandir@2.1.5":
|
||||
version "2.1.5"
|
||||
@@ -1937,13 +1937,13 @@
|
||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.9.5.tgz#10491ccf4f63c814d4149e0316541476ea603602"
|
||||
integrity sha512-1q+mykKE3Vot1kaFJIDoUFv5TuW+QQVaf2FmTT9krg86pQrGStOSJJ0Zil7CFagyxDuouTepzt5Y5TVzyajOdQ==
|
||||
|
||||
"@schematics/angular@17.1.1":
|
||||
version "17.1.1"
|
||||
resolved "https://registry.yarnpkg.com/@schematics/angular/-/angular-17.1.1.tgz#5ac8f09a66e7dff86cbf664b9388a0dde4a97de3"
|
||||
integrity sha512-1Wqefy1W9Y63g48Fp7BscL95V4U1seDGgZawH6DcJnytJVW89hazao7YREzLYfdoediuw7lU+OHJksWYe4VQww==
|
||||
"@schematics/angular@17.1.3":
|
||||
version "17.1.3"
|
||||
resolved "https://registry.yarnpkg.com/@schematics/angular/-/angular-17.1.3.tgz#3c3e0f67985ed500d9d3b66fdb63387cc775e9a9"
|
||||
integrity sha512-hmeasOvzmniy6urtzUKhEqGO67iPuLX/dVtkF4nWp2NTtcEKlvcJobNDMc+CTlX4+ZMPVOvmhDMQqrlfekZ+NQ==
|
||||
dependencies:
|
||||
"@angular-devkit/core" "17.1.1"
|
||||
"@angular-devkit/schematics" "17.1.1"
|
||||
"@angular-devkit/core" "17.1.3"
|
||||
"@angular-devkit/schematics" "17.1.3"
|
||||
jsonc-parser "3.2.0"
|
||||
|
||||
"@sigstore/bundle@^2.1.0":
|
||||
|
||||
+779
-108
File diff suppressed because it is too large
Load Diff
+2
@@ -32,6 +32,7 @@ import {isChildOf, parentCollapsed} from './directive-forest-utils';
|
||||
import {IndexedNode} from './index-forest';
|
||||
import {MatIcon} from '@angular/material/icon';
|
||||
import {FilterComponent} from './filter/filter.component';
|
||||
import {MatTooltip} from '@angular/material/tooltip';
|
||||
|
||||
@Component({
|
||||
selector: 'ng-directive-forest',
|
||||
@@ -45,6 +46,7 @@ import {FilterComponent} from './filter/filter.component';
|
||||
CdkFixedSizeVirtualScroll,
|
||||
CdkVirtualForOf,
|
||||
MatIcon,
|
||||
MatTooltip,
|
||||
],
|
||||
})
|
||||
export class DirectiveForestComponent {
|
||||
|
||||
@@ -1098,7 +1098,9 @@ export interface ModelSignal<T> extends WritableSignal<T> {
|
||||
// (undocumented)
|
||||
[ɵINPUT_SIGNAL_BRAND_WRITE_TYPE]: T;
|
||||
// (undocumented)
|
||||
[SIGNAL]: ModelSignalNode<T>;
|
||||
[SIGNAL]: InputSignalNode<T, T>;
|
||||
// @deprecated (undocumented)
|
||||
subscribe(callback: (value: T) => void): () => void;
|
||||
}
|
||||
|
||||
// @public @deprecated
|
||||
|
||||
+12
-12
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "angular-srcs",
|
||||
"version": "17.2.0-next.1",
|
||||
"version": "17.2.0",
|
||||
"private": true,
|
||||
"description": "Angular - a web framework for modern web apps",
|
||||
"homepage": "https://github.com/angular/angular",
|
||||
@@ -43,18 +43,18 @@
|
||||
"devtools:build:chrome": "bazelisk build --config snapshot-build --//devtools/projects/shell-browser/src:flag_browser=chrome -- devtools/projects/shell-browser/src:prodapp",
|
||||
"devtools:build:firefox": "bazelisk build --config snapshot-build --//devtools/projects/shell-browser/src:flag_browser=firefox -- devtools/projects/shell-browser/src:prodapp",
|
||||
"devtools:test": "bazelisk test --config snapshot-build --//devtools/projects/shell-browser/src:flag_browser=chrome -- //devtools/...",
|
||||
"docs": "yarn bazel run --config=aio_local_deps //adev:serve",
|
||||
"docs:build": "yarn bazel build --config=aio_local_deps //adev:build",
|
||||
"docs": "[[ -n $CI ]] && echo 'Cannot run this yarn script on CI' && exit 1 || yarn bazel run --config=aio_local_deps //adev:serve --fast_adev",
|
||||
"docs:build": "[[ -n $CI ]] && echo 'Cannot run this yarn script on CI' && exit 1 || yarn bazel build --config=aio_local_deps //adev:build --fast_adev",
|
||||
"benchmarks": "ts-node --esm scripts/benchmarks/index.mts"
|
||||
},
|
||||
"// 1": "dependencies are used locally and by bazel",
|
||||
"dependencies": {
|
||||
"@angular-devkit/build-angular": "17.2.0-next.0",
|
||||
"@angular-devkit/core": "17.2.0-next.0",
|
||||
"@angular-devkit/schematics": "17.2.0-next.0",
|
||||
"@angular/cdk": "17.2.0-next.0",
|
||||
"@angular/cli": "17.2.0-next.0",
|
||||
"@angular/material": "17.2.0-next.0",
|
||||
"@angular-devkit/build-angular": "17.2.0-rc.0",
|
||||
"@angular-devkit/core": "17.2.0-rc.0",
|
||||
"@angular-devkit/schematics": "17.2.0-rc.0",
|
||||
"@angular/cdk": "17.2.0-rc.0",
|
||||
"@angular/cli": "17.2.0-rc.0",
|
||||
"@angular/material": "17.2.0-rc.0",
|
||||
"@babel/cli": "7.23.9",
|
||||
"@babel/core": "7.23.9",
|
||||
"@babel/generator": "7.23.6",
|
||||
@@ -71,7 +71,7 @@
|
||||
"@rollup/plugin-babel": "^6.0.0",
|
||||
"@rollup/plugin-commonjs": "^25.0.0",
|
||||
"@rollup/plugin-node-resolve": "^13.0.4",
|
||||
"@schematics/angular": "17.2.0-next.0",
|
||||
"@schematics/angular": "17.2.0-rc.0",
|
||||
"@types/angular": "^1.6.47",
|
||||
"@types/babel__core": "7.20.5",
|
||||
"@types/babel__generator": "7.6.8",
|
||||
@@ -152,9 +152,9 @@
|
||||
"@actions/core": "^1.10.0",
|
||||
"@angular-devkit/architect-cli": "^0.1702.0-next",
|
||||
"@angular/animations": "^17.2.0-next",
|
||||
"@angular/build-tooling": "https://github.com/angular/dev-infra-private-build-tooling-builds.git#07da06414a4ce04a8f9710f2bc9346c2c753a0d2",
|
||||
"@angular/build-tooling": "https://github.com/angular/dev-infra-private-build-tooling-builds.git#7c4cf003cb4ac849986beaa243d7e85a893612f2",
|
||||
"@angular/docs": "https://github.com/angular/dev-infra-private-docs-builds.git#87eb92c0cc022678fa3fedbd5ac25b84f285a7fa",
|
||||
"@angular/ng-dev": "https://github.com/angular/dev-infra-private-ng-dev-builds.git#eacabd9f66aad05822ccb5b0dc893ebe7eb62321",
|
||||
"@angular/ng-dev": "https://github.com/angular/dev-infra-private-ng-dev-builds.git#c21f93acb618bcaeda52a8065e7b6c9242def182",
|
||||
"@babel/helper-remap-async-to-generator": "^7.18.9",
|
||||
"@babel/plugin-proposal-async-generator-functions": "^7.20.7",
|
||||
"@bazel/bazelisk": "^1.7.5",
|
||||
|
||||
@@ -98,5 +98,7 @@ generate_api_manifest(
|
||||
"//packages/upgrade:upgrade_docs_extraction",
|
||||
"//packages/upgrade/static:upgrade_static_docs_extraction",
|
||||
"//packages/upgrade/static/testing:upgrade_static_testing_docs_extraction",
|
||||
"//tools/manual_api_docs/blocks",
|
||||
"//tools/manual_api_docs/elements",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -197,13 +197,14 @@ function getFilteredHeaders(
|
||||
|
||||
function makeCacheKey(request: HttpRequest<any>): StateKey<TransferHttpResponse> {
|
||||
// make the params encoded same as a url so it's easy to identify
|
||||
const {params, method, responseType, url} = request;
|
||||
const {params, method, responseType, url, body} = request;
|
||||
const encodedParams = params
|
||||
.keys()
|
||||
.sort()
|
||||
.map((k) => `${k}=${params.getAll(k)}`)
|
||||
.join('&');
|
||||
const key = method + '.' + responseType + '.' + url + '?' + encodedParams;
|
||||
const strBody = typeof body === 'string' ? body : '';
|
||||
const key = [method, responseType, url, strBody, encodedParams].join('|');
|
||||
|
||||
const hash = generateHash(key);
|
||||
|
||||
|
||||
@@ -98,8 +98,8 @@ describe('TransferCache', () => {
|
||||
|
||||
it('should store HTTP calls in cache when application is not stable', () => {
|
||||
makeRequestAndExpectOne('/test', 'foo');
|
||||
const key = makeStateKey('432906284');
|
||||
const transferState = TestBed.inject(TransferState);
|
||||
const key = makeStateKey(Object.keys((transferState as any).store)[0]);
|
||||
expect(transferState.get(key, null)).toEqual(jasmine.objectContaining({[BODY]: 'foo'}));
|
||||
});
|
||||
|
||||
@@ -115,7 +115,7 @@ describe('TransferCache', () => {
|
||||
|
||||
const transferState = TestBed.inject(TransferState);
|
||||
expect(JSON.parse(transferState.toJson()) as Record<string, unknown>).toEqual({
|
||||
'3706062792': {
|
||||
'2400571479': {
|
||||
[BODY]: 'foo',
|
||||
[HEADERS]: {},
|
||||
[STATUS]: 200,
|
||||
@@ -123,7 +123,7 @@ describe('TransferCache', () => {
|
||||
[URL]: '/test-1',
|
||||
[RESPONSE_TYPE]: 'json',
|
||||
},
|
||||
'3706062823': {
|
||||
'2400572440': {
|
||||
[BODY]: 'buzz',
|
||||
[HEADERS]: {},
|
||||
[STATUS]: 200,
|
||||
|
||||
@@ -74,6 +74,14 @@ export interface InternalOptions {
|
||||
* @internal
|
||||
*/
|
||||
_enableBlockSyntax?: boolean;
|
||||
|
||||
/**
|
||||
* Detected version of `@angular/core` in the workspace. Used by the
|
||||
* compiler to adjust the output depending on the available symbols.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
_angularCoreVersion?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -266,6 +266,7 @@ export class NgCompiler {
|
||||
readonly ignoreForEmit: Set<ts.SourceFile>;
|
||||
readonly enableTemplateTypeChecker: boolean;
|
||||
private readonly enableBlockSyntax: boolean;
|
||||
private readonly angularCoreVersion: Version|null;
|
||||
|
||||
/**
|
||||
* `NgCompiler` can be reused for multiple compilations (for resource-only changes), and each
|
||||
@@ -330,7 +331,10 @@ export class NgCompiler {
|
||||
) {
|
||||
this.enableTemplateTypeChecker =
|
||||
enableTemplateTypeChecker || (options['_enableTemplateTypeChecker'] ?? false);
|
||||
// TODO(crisbeto): remove this flag and base `enableBlockSyntx` on the `angularCoreVersion`.
|
||||
this.enableBlockSyntax = options['_enableBlockSyntax'] ?? true;
|
||||
this.angularCoreVersion =
|
||||
options['_angularCoreVersion'] == null ? null : new Version(options['_angularCoreVersion']);
|
||||
this.constructionDiagnostics.push(
|
||||
...this.adapter.constructionDiagnostics, ...verifyCompatibleTypeCheckOptions(this.options));
|
||||
|
||||
@@ -791,6 +795,14 @@ export class NgCompiler {
|
||||
|
||||
const useInlineTypeConstructors = this.programDriver.supportsInlineOperations;
|
||||
|
||||
// Only Angular versions greater than 17.2 have the necessary symbols to type check signals in
|
||||
// two-way bindings. We also allow version 0.0.0 in case somebody is using Angular at head.
|
||||
const allowSignalsInTwoWayBindings = this.angularCoreVersion === null ||
|
||||
this.angularCoreVersion.major > 17 ||
|
||||
this.angularCoreVersion.major === 17 && this.angularCoreVersion.minor >= 2 ||
|
||||
(this.angularCoreVersion.major === 0 && this.angularCoreVersion.minor === 0 ||
|
||||
this.angularCoreVersion.patch === 0);
|
||||
|
||||
// First select a type-checking configuration, based on whether full template type-checking is
|
||||
// requested.
|
||||
let typeCheckingConfig: TypeCheckingConfig;
|
||||
@@ -829,6 +841,7 @@ export class NgCompiler {
|
||||
suggestionsForSuboptimalTypeInference: this.enableTemplateTypeChecker && !strictTemplates,
|
||||
controlFlowPreventingContentProjection:
|
||||
this.options.extendedDiagnostics?.defaultCategory || DiagnosticCategoryLabel.Warning,
|
||||
allowSignalsInTwoWayBindings,
|
||||
};
|
||||
} else {
|
||||
typeCheckingConfig = {
|
||||
@@ -859,6 +872,7 @@ export class NgCompiler {
|
||||
suggestionsForSuboptimalTypeInference: false,
|
||||
controlFlowPreventingContentProjection:
|
||||
this.options.extendedDiagnostics?.defaultCategory || DiagnosticCategoryLabel.Warning,
|
||||
allowSignalsInTwoWayBindings,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1441,3 +1455,17 @@ function versionMapFromProgram(
|
||||
}
|
||||
return versions;
|
||||
}
|
||||
|
||||
/** Representation of a parsed persion string. */
|
||||
class Version {
|
||||
readonly major: number;
|
||||
readonly minor: number;
|
||||
readonly patch: number;
|
||||
|
||||
constructor(public full: string) {
|
||||
[this.major, this.minor, this.patch] = full.split('.').map(part => {
|
||||
const parsed = parseInt(part);
|
||||
return isNaN(parsed) ? -1 : parsed;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -138,8 +138,7 @@ export interface WritableSignal<T> extends Signal<T> {
|
||||
asReadonly(): Signal<T>;
|
||||
}
|
||||
|
||||
// Note: needs to be kept in sync with the copies in `render3/reactivity/signal.ts` and
|
||||
// `ngtsc/typecheck/testing/index.ts` to ensure consistent tests.
|
||||
// Note: needs to be kept in sync with the copies in `render3/reactivity/signal.ts`.
|
||||
export function ɵunwrapWritableSignal<T>(value: T|{[WRITABLE_SIGNAL]: T}): T {
|
||||
return null!;
|
||||
}
|
||||
@@ -200,7 +199,7 @@ export interface ModelOptions {
|
||||
export interface ModelSignal<T> extends WritableSignal<T> {
|
||||
[ɵINPUT_SIGNAL_BRAND_READ_TYPE]: T;
|
||||
[ɵINPUT_SIGNAL_BRAND_WRITE_TYPE]: T;
|
||||
subscribe(callback: (value: T) => void): {unsubscribe: () => void};
|
||||
subscribe(callback: (value: T) => void): () => void;
|
||||
}
|
||||
|
||||
export interface ModelFunction {
|
||||
@@ -217,7 +216,20 @@ export const viewChildren: any = null!;
|
||||
export const contentChild: any = null!;
|
||||
export const contentChildren: any = null!;
|
||||
|
||||
export interface OutputEmitter<T> {
|
||||
emit(value: T): void;
|
||||
subscribe(listener: (v: T) => void): void;
|
||||
}
|
||||
|
||||
/** Initializer-based output() API. */
|
||||
export function output<T>(_opts?: {alias?: string}): EventEmitter<T> {
|
||||
export function output<T>(_opts?: {alias?: string}): OutputEmitter<T> {
|
||||
return null!;
|
||||
}
|
||||
|
||||
export interface CreateComputedOptions<T> {
|
||||
equal?: (a: T, b: T) => boolean;
|
||||
}
|
||||
|
||||
export function computed<T>(computation: () => T, options?: CreateComputedOptions<T>): Signal<T> {
|
||||
return null!;
|
||||
}
|
||||
|
||||
@@ -330,6 +330,11 @@ export interface TypeCheckingConfig {
|
||||
* opportunities to improve their own developer experience.
|
||||
*/
|
||||
suggestionsForSuboptimalTypeInference: boolean;
|
||||
|
||||
/**
|
||||
* Whether the type of two-way bindings should be widened to allow `WritableSignal`.
|
||||
*/
|
||||
allowSignalsInTwoWayBindings: boolean;
|
||||
}
|
||||
|
||||
|
||||
|
||||
+3
@@ -21,6 +21,9 @@ ts_library(
|
||||
jasmine_node_test(
|
||||
name = "test",
|
||||
bootstrap = ["//tools/testing:node_no_angular"],
|
||||
data = [
|
||||
"//packages/compiler-cli/src/ngtsc/testing/fake_core:npm_package",
|
||||
],
|
||||
deps = [
|
||||
":test_lib",
|
||||
],
|
||||
|
||||
+3
@@ -21,6 +21,9 @@ ts_library(
|
||||
jasmine_node_test(
|
||||
name = "test",
|
||||
bootstrap = ["//tools/testing:node_no_angular"],
|
||||
data = [
|
||||
"//packages/compiler-cli/src/ngtsc/testing/fake_core:npm_package",
|
||||
],
|
||||
deps = [
|
||||
":test_lib",
|
||||
],
|
||||
|
||||
+3
@@ -21,6 +21,9 @@ ts_library(
|
||||
jasmine_node_test(
|
||||
name = "test",
|
||||
bootstrap = ["//tools/testing:node_no_angular"],
|
||||
data = [
|
||||
"//packages/compiler-cli/src/ngtsc/testing/fake_core:npm_package",
|
||||
],
|
||||
deps = [
|
||||
":test_lib",
|
||||
],
|
||||
|
||||
+3
@@ -21,6 +21,9 @@ ts_library(
|
||||
jasmine_node_test(
|
||||
name = "test",
|
||||
bootstrap = ["//tools/testing:node_no_angular"],
|
||||
data = [
|
||||
"//packages/compiler-cli/src/ngtsc/testing/fake_core:npm_package",
|
||||
],
|
||||
deps = [
|
||||
":test_lib",
|
||||
],
|
||||
|
||||
+3
@@ -21,6 +21,9 @@ ts_library(
|
||||
jasmine_node_test(
|
||||
name = "test",
|
||||
bootstrap = ["//tools/testing:node_no_angular"],
|
||||
data = [
|
||||
"//packages/compiler-cli/src/ngtsc/testing/fake_core:npm_package",
|
||||
],
|
||||
deps = [
|
||||
":test_lib",
|
||||
],
|
||||
|
||||
+3
@@ -21,6 +21,9 @@ ts_library(
|
||||
jasmine_node_test(
|
||||
name = "test",
|
||||
bootstrap = ["//tools/testing:node_no_angular"],
|
||||
data = [
|
||||
"//packages/compiler-cli/src/ngtsc/testing/fake_core:npm_package",
|
||||
],
|
||||
deps = [
|
||||
":test_lib",
|
||||
],
|
||||
|
||||
+3
@@ -21,6 +21,9 @@ ts_library(
|
||||
jasmine_node_test(
|
||||
name = "test",
|
||||
bootstrap = ["//tools/testing:node_no_angular"],
|
||||
data = [
|
||||
"//packages/compiler-cli/src/ngtsc/testing/fake_core:npm_package",
|
||||
],
|
||||
deps = [
|
||||
":test_lib",
|
||||
],
|
||||
|
||||
+3
@@ -21,6 +21,9 @@ ts_library(
|
||||
jasmine_node_test(
|
||||
name = "test",
|
||||
bootstrap = ["//tools/testing:node_no_angular"],
|
||||
data = [
|
||||
"//packages/compiler-cli/src/ngtsc/testing/fake_core:npm_package",
|
||||
],
|
||||
deps = [
|
||||
":test_lib",
|
||||
],
|
||||
|
||||
+3
@@ -21,6 +21,9 @@ ts_library(
|
||||
jasmine_node_test(
|
||||
name = "test",
|
||||
bootstrap = ["//tools/testing:node_no_angular"],
|
||||
data = [
|
||||
"//packages/compiler-cli/src/ngtsc/testing/fake_core:npm_package",
|
||||
],
|
||||
deps = [
|
||||
":test_lib",
|
||||
],
|
||||
|
||||
@@ -815,7 +815,7 @@ class TcbDirectiveInputsOp extends TcbOp {
|
||||
}
|
||||
|
||||
// Two-way bindings accept `T | WritableSignal<T>` so we have to unwrap the value.
|
||||
if (isTwoWayBinding) {
|
||||
if (isTwoWayBinding && this.tcb.env.config.allowSignalsInTwoWayBindings) {
|
||||
assignment = unwrapWritableSignal(assignment, this.tcb);
|
||||
}
|
||||
|
||||
@@ -2499,7 +2499,7 @@ function tcbCallTypeCtor(
|
||||
// For bound inputs, the property is assigned the binding expression.
|
||||
let expr = widenBinding(input.expression, tcb);
|
||||
|
||||
if (input.isTwoWayBinding) {
|
||||
if (input.isTwoWayBinding && tcb.env.config.allowSignalsInTwoWayBindings) {
|
||||
expr = unwrapWritableSignal(expr, tcb);
|
||||
}
|
||||
|
||||
|
||||
@@ -35,6 +35,9 @@ ts_library(
|
||||
jasmine_node_test(
|
||||
name = "test",
|
||||
bootstrap = ["//tools/testing:node_no_angular"],
|
||||
data = [
|
||||
"//packages/compiler-cli/src/ngtsc/testing/fake_core:npm_package",
|
||||
],
|
||||
deps = [
|
||||
":test_lib",
|
||||
],
|
||||
|
||||
@@ -0,0 +1,735 @@
|
||||
/**
|
||||
* @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.io/license
|
||||
*/
|
||||
|
||||
import {runInEachFileSystem} from '../../file_system/testing';
|
||||
|
||||
import {generateDiagnoseJasmineSpecs, TestCase} from './test_case_helper';
|
||||
|
||||
runInEachFileSystem(() => {
|
||||
describe('model inputs type-check diagnostics', () => {
|
||||
const bindingCases: TestCase[] = [
|
||||
{
|
||||
id: 'static binding',
|
||||
inputs: {show: {type: 'ModelSignal<boolean>', isSignal: true}},
|
||||
outputs: {showChange: {type: 'ModelSignal<boolean>'}},
|
||||
template: `<div dir show="works">`,
|
||||
expected: [
|
||||
`TestComponent.html(1, 10): Type 'string' is not assignable to type 'boolean'.`,
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'one-way property binding',
|
||||
inputs: {show: {type: 'ModelSignal<boolean>', isSignal: true}},
|
||||
outputs: {showChange: {type: 'ModelSignal<boolean>'}},
|
||||
template: `<div dir [show]="prop">`,
|
||||
component: 'prop = true;',
|
||||
expected: [],
|
||||
},
|
||||
{
|
||||
id: 'complex object one-way binding',
|
||||
inputs: {show: {type: 'ModelSignal<{works: boolean}>', isSignal: true}},
|
||||
outputs: {showChange: {type: 'ModelSignal<{works: boolean}>'}},
|
||||
template: `<div dir [show]="{works: true}">`,
|
||||
expected: [],
|
||||
},
|
||||
{
|
||||
id: 'complex object one-way binding, unexpected extra fields',
|
||||
inputs: {show: {type: 'ModelSignal<{works: boolean}>', isSignal: true}},
|
||||
outputs: {showChange: {type: 'ModelSignal<boolean>'}},
|
||||
template: `<div dir [show]="{works: true, extraField: true}">`,
|
||||
expected: [
|
||||
jasmine.stringContaining(
|
||||
`Object literal may only specify known properties, and '"extraField"' does not exist in type '{ works: boolean; }'.`)
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'complex object input, missing fields',
|
||||
inputs: {show: {type: 'ModelSignal<{works: boolean}>', isSignal: true}},
|
||||
outputs: {showChange: {type: 'ModelSignal<boolean>'}},
|
||||
template: `<div dir [show]="{}">`,
|
||||
expected: [
|
||||
`TestComponent.html(1, 11): Property 'works' is missing in type '{}' but required in type '{ works: boolean; }'.`
|
||||
],
|
||||
},
|
||||
// mixing cases
|
||||
{
|
||||
id: 'mixing zone input and model, valid',
|
||||
inputs: {
|
||||
zoneProp: {type: 'string', isSignal: false},
|
||||
signalProp: {type: 'ModelSignal<string>', isSignal: true},
|
||||
},
|
||||
outputs: {signalPropChange: {type: 'ModelSignal<string>'}},
|
||||
template: `<div dir [zoneProp]="'works'" [signalProp]="'stringVal'">`,
|
||||
expected: []
|
||||
},
|
||||
{
|
||||
id: 'mixing zone input and model, invalid zone binding',
|
||||
inputs: {
|
||||
zoneProp: {type: 'string', isSignal: false},
|
||||
signalProp: {type: 'ModelSignal<string>', isSignal: true},
|
||||
},
|
||||
outputs: {signalPropChange: {type: 'ModelSignal<string>'}},
|
||||
template: `<div dir [zoneProp]="false" [signalProp]="'stringVal'">`,
|
||||
expected: [`TestComponent.html(1, 11): Type 'boolean' is not assignable to type 'string'.`]
|
||||
},
|
||||
{
|
||||
id: 'mixing zone input and model, invalid signal binding',
|
||||
inputs: {
|
||||
zoneProp: {type: 'string', isSignal: false},
|
||||
signalProp: {type: 'ModelSignal<string>', isSignal: true},
|
||||
},
|
||||
outputs: {signalPropChange: {type: 'ModelSignal<string>'}},
|
||||
template: `<div dir [zoneProp]="'works'" [signalProp]="{}">`,
|
||||
expected: [`TestComponent.html(1, 32): Type '{}' is not assignable to type 'string'.`]
|
||||
},
|
||||
{
|
||||
id: 'mixing zone input and model, both invalid',
|
||||
inputs: {
|
||||
zoneProp: {type: 'string', isSignal: false},
|
||||
signalProp: {type: 'ModelSignal<string>', isSignal: true},
|
||||
},
|
||||
outputs: {signalPropChange: {type: 'ModelSignal<string>'}},
|
||||
template: `<div dir [zoneProp]="false" [signalProp]="{}">`,
|
||||
expected: [
|
||||
`TestComponent.html(1, 11): Type 'boolean' is not assignable to type 'string'.`,
|
||||
`TestComponent.html(1, 30): Type '{}' is not assignable to type 'string'.`
|
||||
]
|
||||
},
|
||||
// restricted fields
|
||||
{
|
||||
id: 'disallows access to private model',
|
||||
inputs: {
|
||||
pattern: {type: 'ModelSignal<string>', isSignal: true, restrictionModifier: 'private'}
|
||||
},
|
||||
outputs: {patternChange: {type: 'ModelSignal<string>'}},
|
||||
template: `<div dir [pattern]="'works'">`,
|
||||
expected: [
|
||||
`TestComponent.html(1, 11): Property 'pattern' is private and only accessible within class 'Dir'.`
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'disallows access to protected model',
|
||||
inputs: {
|
||||
pattern: {type: 'ModelSignal<string>', isSignal: true, restrictionModifier: 'protected'}
|
||||
},
|
||||
outputs: {patternChange: {type: 'ModelSignal<string>'}},
|
||||
template: `<div dir [pattern]="'works'">`,
|
||||
expected: [
|
||||
`TestComponent.html(1, 11): Property 'pattern' is protected and only accessible within class 'Dir' and its subclasses.`
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'allows access to readonly model by default',
|
||||
inputs: {
|
||||
pattern: {type: 'ModelSignal<string>', isSignal: true, restrictionModifier: 'readonly'}
|
||||
},
|
||||
outputs: {patternChange: {type: 'ModelSignal<string>'}},
|
||||
template: `<div dir [pattern]="'works'">`,
|
||||
expected: [],
|
||||
},
|
||||
// restricted fields (but opt-out of check)
|
||||
{
|
||||
id: 'allow access to private input if modifiers are explicitly ignored',
|
||||
inputs: {
|
||||
pattern: {type: 'ModelSignal<string>', isSignal: true, restrictionModifier: 'private'}
|
||||
},
|
||||
outputs: {patternChange: {type: 'ModelSignal<string>'}},
|
||||
template: `<div dir [pattern]="'works'">`,
|
||||
expected: [],
|
||||
options: {
|
||||
honorAccessModifiersForInputBindings: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'allow access to protected model if modifiers are explicitly ignored',
|
||||
inputs: {
|
||||
pattern: {type: 'ModelSignal<string>', isSignal: true, restrictionModifier: 'protected'}
|
||||
},
|
||||
outputs: {patternChange: {type: 'ModelSignal<string>'}},
|
||||
template: `<div dir [pattern]="'works'">`,
|
||||
expected: [],
|
||||
options: {
|
||||
honorAccessModifiersForInputBindings: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'allow access to readonly input if modifiers are explicitly ignored',
|
||||
inputs: {
|
||||
pattern: {type: 'ModelSignal<string>', isSignal: true, restrictionModifier: 'readonly'}
|
||||
},
|
||||
outputs: {patternChange: {type: 'ModelSignal<string>'}},
|
||||
template: `<div dir [pattern]="'works'">`,
|
||||
expected: [],
|
||||
options: {
|
||||
honorAccessModifiersForInputBindings: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'allow access to private model if modifiers are explicitly ignored, but error if not assignable',
|
||||
inputs: {
|
||||
pattern: {type: 'ModelSignal<string>', isSignal: true, restrictionModifier: 'private'}
|
||||
},
|
||||
outputs: {patternChange: {type: 'ModelSignal<string>'}},
|
||||
template: `<div dir [pattern]="false">`,
|
||||
expected: [
|
||||
`TestComponent.html(1, 11): Type 'boolean' is not assignable to type 'string'.`,
|
||||
],
|
||||
options: {
|
||||
honorAccessModifiersForInputBindings: false,
|
||||
},
|
||||
},
|
||||
// coercion is not supported / respected
|
||||
{
|
||||
id: 'coercion members are not respected',
|
||||
inputs: {pattern: {type: 'ModelSignal<string>', isSignal: true}},
|
||||
outputs: {patternChange: {type: 'ModelSignal<string>'}},
|
||||
extraDirectiveMembers: [
|
||||
'static ngAcceptInputType_pattern: string|boolean',
|
||||
],
|
||||
template: `<div dir [pattern]="false">`,
|
||||
expected: [
|
||||
`TestComponent.html(1, 11): Type 'boolean' is not assignable to type 'string'.`,
|
||||
],
|
||||
},
|
||||
// with generics (type constructor tests)
|
||||
{
|
||||
id: 'generic inference and one-way binding to directive, all model inputs',
|
||||
inputs: {
|
||||
gen: {
|
||||
type: 'ModelSignal<T>',
|
||||
isSignal: true,
|
||||
},
|
||||
other: {
|
||||
type: 'ModelSignal<T>',
|
||||
isSignal: true,
|
||||
}
|
||||
},
|
||||
outputs: {
|
||||
genChange: {type: 'ModelSignal<T>'},
|
||||
otherChange: {type: 'ModelSignal<T>'},
|
||||
},
|
||||
directiveGenerics: '<T>',
|
||||
template: `<div dir [gen]="false" [other]="'invalid'">`,
|
||||
expected: [
|
||||
`TestComponent.html(1, 25): Type 'string' is not assignable to type 'boolean'.`,
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'generic inference and one-way binding to directive, mix of zone input and model',
|
||||
inputs: {
|
||||
gen: {
|
||||
type: 'ModelSignal<T>',
|
||||
isSignal: true,
|
||||
},
|
||||
other: {
|
||||
type: 'T',
|
||||
isSignal: false,
|
||||
}
|
||||
},
|
||||
outputs: {genChange: {type: 'ModelSignal<T>'}},
|
||||
directiveGenerics: '<T>',
|
||||
template: `<div dir [gen]="false" [other]="'invalid'">`,
|
||||
expected: [
|
||||
`TestComponent.html(1, 11): Type 'boolean' is not assignable to type 'string'.`,
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'generic inference and one-way binding to directive (with `extends boolean`), all model inputs',
|
||||
inputs: {
|
||||
gen: {
|
||||
type: 'ModelSignal<T>',
|
||||
isSignal: true,
|
||||
},
|
||||
other: {
|
||||
type: 'ModelSignal<T>',
|
||||
isSignal: true,
|
||||
}
|
||||
},
|
||||
outputs: {genChange: {type: 'ModelSignal<T>'}, otherChange: {type: 'ModelSignal<T>'}},
|
||||
directiveGenerics: '<T extends boolean>',
|
||||
template: `<div dir [gen]="false" [other]="'invalid'">`,
|
||||
expected: [
|
||||
`TestComponent.html(1, 25): Type '"invalid"' is not assignable to type 'false'.`,
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'generic inference and one-way binding to directive (with `extends boolean`), mix of zone inputs and model',
|
||||
inputs: {
|
||||
gen: {
|
||||
type: 'ModelSignal<T>',
|
||||
isSignal: true,
|
||||
},
|
||||
other: {
|
||||
type: 'T',
|
||||
isSignal: false,
|
||||
}
|
||||
},
|
||||
outputs: {genChange: {type: 'ModelSignal<T>'}},
|
||||
directiveGenerics: '<T extends boolean>',
|
||||
template: `<div dir [gen]="false" [other]="'invalid'">`,
|
||||
expected: [
|
||||
`TestComponent.html(1, 25): Type 'string' is not assignable to type 'boolean'.`,
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'generic multi-inference and one-way bindings to directive, all model inputs',
|
||||
inputs: {
|
||||
gen: {
|
||||
type: 'ModelSignal<T>',
|
||||
isSignal: true,
|
||||
},
|
||||
other: {
|
||||
type: 'ModelSignal<U>',
|
||||
isSignal: true,
|
||||
}
|
||||
},
|
||||
outputs: {
|
||||
genChange: {type: 'ModelSignal<T>'},
|
||||
otherChange: {type: 'ModelSignal<U>'},
|
||||
},
|
||||
extraDirectiveMembers: [
|
||||
'tester: {t: T, u: U} = null!',
|
||||
],
|
||||
directiveGenerics: '<T, U>',
|
||||
template: `
|
||||
<div dir [gen]="false" [other]="'text'"
|
||||
#ref="dir" (click)="ref.tester = {t: 1, u: 0}">`,
|
||||
expected: [
|
||||
`TestComponent.html(3, 61): Type 'number' is not assignable to type 'boolean'.`,
|
||||
`TestComponent.html(3, 67): Type 'number' is not assignable to type 'string'.`,
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'generic multi-inference and one-way bindings to directive, mix of zone inputs and models',
|
||||
inputs: {
|
||||
gen: {
|
||||
type: 'ModelSignal<T>',
|
||||
isSignal: true,
|
||||
},
|
||||
other: {
|
||||
type: 'U',
|
||||
isSignal: false,
|
||||
}
|
||||
},
|
||||
outputs: {genChange: {type: 'ModelSignal<T>'}},
|
||||
extraDirectiveMembers: [
|
||||
'tester: {t: T, u: U} = null!',
|
||||
],
|
||||
directiveGenerics: '<T, U>',
|
||||
template: `
|
||||
<div dir [gen]="false" [other]="'text'"
|
||||
#ref="dir" (click)="ref.tester = {t: 1, u: 0}">`,
|
||||
expected: [
|
||||
`TestComponent.html(3, 61): Type 'number' is not assignable to type 'boolean'.`,
|
||||
`TestComponent.html(3, 67): Type 'number' is not assignable to type 'string'.`,
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'generic multi-inference and one-way bindings to directive, more complicated generic inference',
|
||||
inputs: {
|
||||
gen: {
|
||||
type: 'ModelSignal<T>',
|
||||
isSignal: true,
|
||||
},
|
||||
other: {
|
||||
type: 'ModelSignal<{u: U}>',
|
||||
isSignal: true,
|
||||
}
|
||||
},
|
||||
outputs: {
|
||||
genChange: {type: 'ModelSignal<T>'},
|
||||
otherChange: {type: 'ModelSignal<{u: U}>'},
|
||||
},
|
||||
extraDirectiveMembers: [
|
||||
'tester: {t: T, u: U} = null!',
|
||||
],
|
||||
directiveGenerics: '<T, U>',
|
||||
template: `
|
||||
<div dir [gen]="false" [other]="{u: null}"
|
||||
#ref="dir" (click)="ref.tester = {t: 1, u: 0}">`,
|
||||
expected: [
|
||||
`TestComponent.html(3, 57): Type 'number' is not assignable to type 'boolean'.`,
|
||||
`TestComponent.html(3, 63): Type 'number' is not assignable to type 'null'.`,
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'inline constructor generic inference, one-way binding',
|
||||
inputs: {
|
||||
bla: {
|
||||
type: 'ModelSignal<T>',
|
||||
isSignal: true,
|
||||
},
|
||||
},
|
||||
outputs: {blaChange: {type: 'ModelSignal<T>'}},
|
||||
extraFileContent: `
|
||||
class SomeNonExportedClass {}
|
||||
`,
|
||||
extraDirectiveMembers: [
|
||||
`tester: {t: T} = null!`,
|
||||
],
|
||||
directiveGenerics: '<T extends SomeNonExportedClass>',
|
||||
template: `<div dir [bla]="prop" #ref="dir" (click)="ref.tester = {t: 0}">`,
|
||||
component: `prop: HTMLElement = null!`,
|
||||
expected: [
|
||||
// This verifies that the `ref.tester.ts` is correctly inferred to be `HTMLElement`.
|
||||
`TestComponent.html(1, 60): Type 'number' is not assignable to type 'HTMLElement'.`,
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'generic inference and two-way binding to directive, all model inputs',
|
||||
inputs: {
|
||||
gen: {
|
||||
type: 'ModelSignal<T>',
|
||||
isSignal: true,
|
||||
},
|
||||
other: {
|
||||
type: 'ModelSignal<T>',
|
||||
isSignal: true,
|
||||
}
|
||||
},
|
||||
outputs: {
|
||||
genChange: {type: 'ModelSignal<T>'},
|
||||
otherChange: {type: 'ModelSignal<T>'},
|
||||
},
|
||||
directiveGenerics: '<T>',
|
||||
component: `
|
||||
genVal!: boolean;
|
||||
otherVal!: string;
|
||||
`,
|
||||
template: `<div dir [(gen)]="genVal" [(other)]="otherVal">`,
|
||||
expected: [
|
||||
`TestComponent.html(1, 29): Type 'string' is not assignable to type 'boolean'.`,
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'generic inference and two-way binding to directive, mix of zone input and model',
|
||||
inputs: {
|
||||
gen: {
|
||||
type: 'ModelSignal<T>',
|
||||
isSignal: true,
|
||||
},
|
||||
other: {
|
||||
type: 'T',
|
||||
isSignal: false,
|
||||
}
|
||||
},
|
||||
outputs: {
|
||||
genChange: {type: 'ModelSignal<T>'},
|
||||
otherChange: {type: 'EventEmitter<T>'},
|
||||
},
|
||||
directiveGenerics: '<T>',
|
||||
template: `<div dir [(gen)]="genVal" [(other)]="otherVal">`,
|
||||
component: `
|
||||
genVal!: boolean;
|
||||
otherVal!: string;
|
||||
`,
|
||||
expected: [
|
||||
`TestComponent.html(1, 12): Type 'boolean' is not assignable to type 'string'.`,
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'generic inference and two-way binding to directive (with `extends boolean`), all model inputs',
|
||||
inputs: {
|
||||
gen: {
|
||||
type: 'ModelSignal<T>',
|
||||
isSignal: true,
|
||||
},
|
||||
other: {
|
||||
type: 'ModelSignal<T>',
|
||||
isSignal: true,
|
||||
}
|
||||
},
|
||||
outputs: {genChange: {type: 'ModelSignal<T>'}, otherChange: {type: 'ModelSignal<T>'}},
|
||||
directiveGenerics: '<T extends boolean>',
|
||||
template: `<div dir [(gen)]="genVal" [(other)]="otherVal">`,
|
||||
component: `
|
||||
genVal!: boolean;
|
||||
otherVal!: string;
|
||||
`,
|
||||
expected: [
|
||||
`TestComponent.html(1, 29): Type 'string' is not assignable to type 'boolean'.`,
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'generic inference and two-way binding to directive (with `extends boolean`), mix of zone inputs and model',
|
||||
inputs: {
|
||||
gen: {
|
||||
type: 'ModelSignal<T>',
|
||||
isSignal: true,
|
||||
},
|
||||
other: {
|
||||
type: 'T',
|
||||
isSignal: false,
|
||||
}
|
||||
},
|
||||
outputs: {
|
||||
genChange: {type: 'ModelSignal<T>'},
|
||||
otherChange: {type: 'EventEmitter<T>'},
|
||||
},
|
||||
directiveGenerics: '<T extends boolean>',
|
||||
template: `<div dir [(gen)]="genVal" [(other)]="otherVal">`,
|
||||
component: `
|
||||
genVal!: boolean;
|
||||
otherVal!: string;
|
||||
`,
|
||||
expected: [
|
||||
`TestComponent.html(1, 29): Type 'string' is not assignable to type 'boolean'.`,
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'generic multi-inference and two-way bindings to directive, all model inputs',
|
||||
inputs: {
|
||||
gen: {
|
||||
type: 'ModelSignal<T>',
|
||||
isSignal: true,
|
||||
},
|
||||
other: {
|
||||
type: 'ModelSignal<U>',
|
||||
isSignal: true,
|
||||
}
|
||||
},
|
||||
outputs: {
|
||||
genChange: {type: 'ModelSignal<T>'},
|
||||
otherChange: {type: 'ModelSignal<U>'},
|
||||
},
|
||||
extraDirectiveMembers: [
|
||||
'tester: {t: T, u: U} = null!',
|
||||
],
|
||||
directiveGenerics: '<T, U>',
|
||||
template: `
|
||||
<div dir [(gen)]="genVal" [(other)]="otherVal"
|
||||
#ref="dir" (click)="ref.tester = {t: 1, u: 0}">`,
|
||||
component: `
|
||||
genVal!: boolean;
|
||||
otherVal!: string;
|
||||
`,
|
||||
expected: [
|
||||
`TestComponent.html(3, 61): Type 'number' is not assignable to type 'boolean'.`,
|
||||
`TestComponent.html(3, 67): Type 'number' is not assignable to type 'string'.`,
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'generic multi-inference and two-way bindings to directive, mix of zone inputs and models',
|
||||
inputs: {
|
||||
gen: {
|
||||
type: 'ModelSignal<T>',
|
||||
isSignal: true,
|
||||
},
|
||||
other: {
|
||||
type: 'U',
|
||||
isSignal: false,
|
||||
}
|
||||
},
|
||||
outputs: {
|
||||
genChange: {type: 'ModelSignal<T>'},
|
||||
otherChange: {type: 'EventEmitter<U>'},
|
||||
},
|
||||
extraDirectiveMembers: [
|
||||
'tester: {t: T, u: U} = null!',
|
||||
],
|
||||
directiveGenerics: '<T, U>',
|
||||
template: `
|
||||
<div dir [(gen)]="genVal" [(other)]="otherVal"
|
||||
#ref="dir" (click)="ref.tester = {t: 1, u: 0}">`,
|
||||
component: `
|
||||
genVal!: boolean;
|
||||
otherVal!: string;
|
||||
`,
|
||||
expected: [
|
||||
`TestComponent.html(3, 61): Type 'number' is not assignable to type 'boolean'.`,
|
||||
`TestComponent.html(3, 67): Type 'number' is not assignable to type 'string'.`,
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'generic multi-inference and two-way bindings to directive, more complicated generic inference',
|
||||
inputs: {
|
||||
gen: {
|
||||
type: 'ModelSignal<T>',
|
||||
isSignal: true,
|
||||
},
|
||||
other: {
|
||||
type: 'ModelSignal<{u: U}>',
|
||||
isSignal: true,
|
||||
}
|
||||
},
|
||||
outputs: {
|
||||
genChange: {type: 'ModelSignal<T>'},
|
||||
otherChange: {type: 'ModelSignal<{u: U}>'},
|
||||
},
|
||||
extraDirectiveMembers: [
|
||||
'tester: {t: T, u: U} = null!',
|
||||
],
|
||||
directiveGenerics: '<T, U>',
|
||||
template: `
|
||||
<div dir [(gen)]="genVal" [(other)]="otherVal"
|
||||
#ref="dir" (click)="ref.tester = {t: 1, u: 0}">`,
|
||||
component: `
|
||||
genVal!: boolean;
|
||||
otherVal!: {u: null};
|
||||
`,
|
||||
expected: [
|
||||
`TestComponent.html(3, 57): Type 'number' is not assignable to type 'boolean'.`,
|
||||
`TestComponent.html(3, 63): Type 'number' is not assignable to type 'null'.`,
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'inline constructor generic inference, two-way binding',
|
||||
inputs: {
|
||||
bla: {
|
||||
type: 'ModelSignal<T>',
|
||||
isSignal: true,
|
||||
},
|
||||
},
|
||||
outputs: {blaChange: {type: 'ModelSignal<T>'}},
|
||||
extraFileContent: `
|
||||
class SomeNonExportedClass {}
|
||||
`,
|
||||
extraDirectiveMembers: [
|
||||
`tester: {t: T} = null!`,
|
||||
],
|
||||
directiveGenerics: '<T extends SomeNonExportedClass>',
|
||||
template: `<div dir [(bla)]="prop" #ref="dir" (click)="ref.tester = {t: 0}">`,
|
||||
component: `prop: HTMLElement = null!`,
|
||||
expected: [
|
||||
// This verifies that the `ref.tester.ts` is correctly inferred to be `HTMLElement`.
|
||||
`TestComponent.html(1, 62): Type 'number' is not assignable to type 'HTMLElement'.`,
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'one-way output binding to a model',
|
||||
inputs: {evt: {type: 'ModelSignal<string>', isSignal: true}},
|
||||
outputs: {evtChange: {type: 'ModelSignal<string>'}},
|
||||
template: `<div dir (evtChange)="$event.bla">`,
|
||||
expected: [
|
||||
`TestComponent.html(1, 30): Property 'bla' does not exist on type 'string'.`,
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'one-way output to a model with a void type',
|
||||
inputs: {evt: {type: 'ModelSignal<string>', isSignal: true}},
|
||||
outputs: {evtChange: {type: 'ModelSignal<void>'}},
|
||||
template: `<div dir (evtChange)="$event.x">`,
|
||||
expected: [
|
||||
`TestComponent.html(1, 30): Property 'x' does not exist on type 'void'.`,
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'two-way binding to primitive, invalid',
|
||||
inputs: {value: {type: 'ModelSignal<string>', isSignal: true}},
|
||||
outputs: {valueChange: {type: 'ModelSignal<string>'}},
|
||||
template: `<div dir [(value)]="bla">`,
|
||||
component: `bla = true;`,
|
||||
expected: [
|
||||
`TestComponent.html(1, 12): Type 'boolean' is not assignable to type 'string'.`,
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'two-way binding to primitive, valid',
|
||||
inputs: {value: {type: 'ModelSignal<string>', isSignal: true}},
|
||||
outputs: {valueChange: {type: 'ModelSignal<string>'}},
|
||||
template: `<div dir [(value)]="bla">`,
|
||||
component: `bla: string = ''`,
|
||||
expected: [],
|
||||
},
|
||||
// mixing cases
|
||||
{
|
||||
id: 'mixing decorator-based and model-based event bindings',
|
||||
inputs: {evt1: {type: 'ModelSignal<string>', isSignal: true}},
|
||||
outputs: {
|
||||
evt1Change: {type: 'ModelSignal<string>'},
|
||||
evt2: {type: 'EventEmitter<string>'},
|
||||
},
|
||||
template: `<div dir (evt1Change)="x1 = $event" (evt2)="x2 = $event">`,
|
||||
component: `
|
||||
x1: never = null!;
|
||||
x2: never = null!;
|
||||
`,
|
||||
expected: [
|
||||
`TestComponent.html(1, 24): Type 'string' is not assignable to type 'never'.`,
|
||||
`TestComponent.html(1, 45): Type 'string' is not assignable to type 'never'.`,
|
||||
]
|
||||
},
|
||||
// restricted fields
|
||||
{
|
||||
id: 'allows access to private output',
|
||||
inputs: {
|
||||
evt: {
|
||||
type: 'ModelSignal<string>',
|
||||
isSignal: true,
|
||||
restrictionModifier: 'private',
|
||||
}
|
||||
},
|
||||
outputs: {evtChange: {type: 'ModelSignal<string>', restrictionModifier: 'private'}},
|
||||
template: `<div dir (evtChange)="true">`,
|
||||
expected: [],
|
||||
},
|
||||
{
|
||||
id: 'allows access to protected output',
|
||||
inputs: {
|
||||
evt: {
|
||||
type: 'ModelSignal<string>',
|
||||
isSignal: true,
|
||||
restrictionModifier: 'protected',
|
||||
}
|
||||
},
|
||||
outputs: {evt: {type: 'ModelSignal<string>', restrictionModifier: 'protected'}},
|
||||
template: `<div dir (evtChange)="true">`,
|
||||
expected: [],
|
||||
},
|
||||
{
|
||||
id: 'WritableSignal binding, valid',
|
||||
inputs: {value: {type: 'ModelSignal<boolean>', isSignal: true}},
|
||||
outputs: {valueChange: {type: 'ModelSignal<boolean>'}},
|
||||
template: `<div dir [(value)]="val">`,
|
||||
component: `val!: WritableSignal<boolean>;`,
|
||||
expected: [],
|
||||
},
|
||||
{
|
||||
id: 'WritableSignal binding, invalid',
|
||||
inputs: {value: {type: 'ModelSignal<boolean>', isSignal: true}},
|
||||
outputs: {valueChange: {type: 'ModelSignal<boolean>'}},
|
||||
template: `<div dir [(value)]="val">`,
|
||||
component: `val!: WritableSignal<string>;`,
|
||||
expected: [
|
||||
`TestComponent.html(1, 12): Type 'string' is not assignable to type 'boolean'.`,
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'non-writable signal binding',
|
||||
inputs: {value: {type: 'ModelSignal<boolean>', isSignal: true}},
|
||||
outputs: {valueChange: {type: 'ModelSignal<boolean>'}},
|
||||
template: `<div dir [(value)]="val">`,
|
||||
component: `val!: InputSignal<boolean>;`,
|
||||
expected: [
|
||||
`TestComponent.html(1, 10): Type 'InputSignal<boolean>' is not assignable to type 'boolean'.`,
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'getter function binding, valid',
|
||||
inputs: {value: {type: 'ModelSignal<(v: string) => number>', isSignal: true}},
|
||||
outputs: {valueChange: {type: 'ModelSignal<(v: string) => number>'}},
|
||||
template: `<div dir [(value)]="val">`,
|
||||
component: `val!: (v: string) => number;`,
|
||||
expected: [],
|
||||
},
|
||||
{
|
||||
id: 'getter function binding, invalid',
|
||||
inputs: {value: {type: 'ModelSignal<(v: number) => number>', isSignal: true}},
|
||||
outputs: {valueChange: {type: 'ModelSignal<(v: number) => number>'}},
|
||||
template: `<div dir [(value)]="val">`,
|
||||
component: `val!: (v: string) => number;`,
|
||||
expected: [
|
||||
jasmine.stringContaining(
|
||||
`TestComponent.html(1, 12): Type '(v: string) => number' is not assignable to type '(v: number) => number`),
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
generateDiagnoseJasmineSpecs(bindingCases);
|
||||
});
|
||||
});
|
||||
@@ -66,7 +66,14 @@ export function typeCheckDiagnose(c: TestCase) {
|
||||
name => `${outputs[name].restrictionModifier ?? ''} ${name}: ${outputs[name].type}`);
|
||||
|
||||
const testComponent = `
|
||||
import {InputSignal, EventEmitter, OutputEmitter, InputSignalWithTransform} from '@angular/core';
|
||||
import {
|
||||
InputSignal,
|
||||
EventEmitter,
|
||||
OutputEmitter,
|
||||
InputSignalWithTransform,
|
||||
ModelSignal,
|
||||
WritableSignal,
|
||||
} from '@angular/core';
|
||||
|
||||
${c.extraFileContent ?? ''}
|
||||
|
||||
|
||||
@@ -673,8 +673,10 @@ describe('type check blocks', () => {
|
||||
isGeneric: true,
|
||||
}];
|
||||
const block = tcb(TEMPLATE, DIRECTIVES);
|
||||
expect(block).toContain('const _ctor1: <T extends string = any>(init: Pick<i0.TwoWay<T>, "input">) => i0.TwoWay<T> = null!');
|
||||
expect(block).toContain('var _t1 = _ctor1({ "input": (i1.ɵunwrapWritableSignal(((this).value))) });');
|
||||
expect(block).toContain(
|
||||
'const _ctor1: <T extends string = any>(init: Pick<i0.TwoWay<T>, "input">) => i0.TwoWay<T> = null!');
|
||||
expect(block).toContain(
|
||||
'var _t1 = _ctor1({ "input": (i1.ɵunwrapWritableSignal(((this).value))) });');
|
||||
expect(block).toContain('_t1.input = i1.ɵunwrapWritableSignal((((this).value)));');
|
||||
});
|
||||
|
||||
@@ -697,7 +699,8 @@ describe('type check blocks', () => {
|
||||
}];
|
||||
const block = tcb(TEMPLATE, DIRECTIVES);
|
||||
expect(block).toContain('var _t1 = null! as i0.TwoWay;');
|
||||
expect(block).toContain('_t1.input[i1.ɵINPUT_SIGNAL_BRAND_WRITE_TYPE] = i1.ɵunwrapWritableSignal((((this).value)));');
|
||||
expect(block).toContain(
|
||||
'_t1.input[i1.ɵINPUT_SIGNAL_BRAND_WRITE_TYPE] = i1.ɵunwrapWritableSignal((((this).value)));');
|
||||
});
|
||||
|
||||
it('should handle a two-way binding to an input with a transform', () => {
|
||||
@@ -871,6 +874,7 @@ describe('type check blocks', () => {
|
||||
useInlineTypeConstructors: true,
|
||||
suggestionsForSuboptimalTypeInference: false,
|
||||
controlFlowPreventingContentProjection: 'warning',
|
||||
allowSignalsInTwoWayBindings: true,
|
||||
};
|
||||
|
||||
describe('config.applyTemplateContextGuards', () => {
|
||||
@@ -1214,6 +1218,37 @@ describe('type check blocks', () => {
|
||||
'_t1.fieldA = (((this).foo)); ');
|
||||
});
|
||||
});
|
||||
|
||||
describe('config.allowSignalsInTwoWayBindings', () => {
|
||||
it('should not unwrap signals in two-way binding expressions', () => {
|
||||
const TEMPLATE = `<div twoWay [(input)]="value"></div>`;
|
||||
const DIRECTIVES: TestDeclaration[] = [{
|
||||
type: 'directive',
|
||||
name: 'TwoWay',
|
||||
selector: '[twoWay]',
|
||||
inputs: {input: 'input'},
|
||||
outputs: {inputChange: 'inputChange'},
|
||||
}];
|
||||
const block =
|
||||
tcb(TEMPLATE, DIRECTIVES, {...BASE_CONFIG, allowSignalsInTwoWayBindings: false});
|
||||
expect(block).not.toContain('ɵunwrapWritableSignal');
|
||||
});
|
||||
|
||||
it('should not unwrap signals in two-way bindings to generic directives', () => {
|
||||
const TEMPLATE = `<div twoWay [(input)]="value"></div>`;
|
||||
const DIRECTIVES: TestDeclaration[] = [{
|
||||
type: 'directive',
|
||||
name: 'TwoWay',
|
||||
selector: '[twoWay]',
|
||||
inputs: {input: 'input'},
|
||||
outputs: {inputChange: 'inputChange'},
|
||||
isGeneric: true,
|
||||
}];
|
||||
const block =
|
||||
tcb(TEMPLATE, DIRECTIVES, {...BASE_CONFIG, allowSignalsInTwoWayBindings: false});
|
||||
expect(block).not.toContain('ɵunwrapWritableSignal');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should use `any` type for type constructors with bound generic params ' +
|
||||
|
||||
@@ -7,6 +7,8 @@
|
||||
*/
|
||||
|
||||
import {BindingPipe, CssSelector, ParseSourceFile, ParseSourceSpan, parseTemplate, ParseTemplateOptions, R3TargetBinder, SchemaMetadata, SelectorMatcher, TmplAstElement} from '@angular/compiler';
|
||||
import {readFileSync} from 'fs';
|
||||
import path from 'path';
|
||||
import ts from 'typescript';
|
||||
|
||||
import {absoluteFrom, AbsoluteFsPath, getSourceFileOrError, LogicalFileSystem} from '../../file_system';
|
||||
@@ -18,7 +20,7 @@ import {NOOP_PERF_RECORDER} from '../../perf';
|
||||
import {TsCreateProgramDriver} from '../../program_driver';
|
||||
import {ClassDeclaration, isNamedClassDeclaration, TypeScriptReflectionHost} from '../../reflection';
|
||||
import {ComponentScopeKind, ComponentScopeReader, LocalModuleScope, ScopeData, TypeCheckScopeRegistry} from '../../scope';
|
||||
import {makeProgram} from '../../testing';
|
||||
import {makeProgram, resolveFromRunfiles} from '../../testing';
|
||||
import {getRootDirs} from '../../util/src/typescript';
|
||||
import {OptimizeFor, ProgramTypeCheckAdapter, TemplateDiagnostic, TemplateTypeChecker, TypeCheckContext} from '../api';
|
||||
import {TemplateId, TemplateSourceMapping, TypeCheckableDirectiveMeta, TypeCheckBlockMetadata, TypeCheckingConfig} from '../api/api';
|
||||
@@ -90,133 +92,12 @@ export function typescriptLibDts(): TestFile {
|
||||
}
|
||||
|
||||
export function angularCoreDts(): TestFile {
|
||||
const directory =
|
||||
resolveFromRunfiles('angular/packages/compiler-cli/src/ngtsc/testing/fake_core/npm_package');
|
||||
|
||||
return {
|
||||
name: absoluteFrom('/node_modules/@angular/core/index.d.ts'),
|
||||
contents: `
|
||||
export declare class TemplateRef<C> {
|
||||
readonly elementRef: unknown;
|
||||
createEmbeddedView(context: C): unknown;
|
||||
}
|
||||
|
||||
export declare class EventEmitter<T> {
|
||||
subscribe(next?: (value: T) => void, error?: (error: any) => void, complete?: () => void): unknown;
|
||||
subscribe(observerOrNext?: any, error?: any, complete?: any): unknown;
|
||||
}
|
||||
|
||||
export declare type NgIterable<T> = Array<T> | Iterable<T>;
|
||||
|
||||
/**
|
||||
* -------
|
||||
* Signals
|
||||
* ------
|
||||
*/
|
||||
|
||||
export declare const SIGNAL: unique symbol;
|
||||
export declare type Signal<T> = (() => T) & {
|
||||
[SIGNAL]: unknown;
|
||||
};
|
||||
export declare function signal<T>(initialValue: T): WritableSignal<T>;
|
||||
export declare function computed<T>(computation: () => T): Signal<T>;
|
||||
|
||||
const WRITABLE_SIGNAL = /* @__PURE__ */ Symbol('WRITABLE_SIGNAL');
|
||||
|
||||
export interface WritableSignal<T> extends Signal<T> {
|
||||
[WRITABLE_SIGNAL]: T;
|
||||
set(value: T): void;
|
||||
update(updateFn: (value: T) => T): void;
|
||||
asReadonly(): Signal<T>;
|
||||
}
|
||||
|
||||
/**
|
||||
* -------
|
||||
* Signal inputs
|
||||
* ------
|
||||
*/
|
||||
|
||||
export interface InputOptions<ReadT, WriteT> {
|
||||
alias?: string;
|
||||
transform?: (v: WriteT) => ReadT;
|
||||
}
|
||||
|
||||
export type InputOptionsWithoutTransform<ReadT> =
|
||||
InputOptions<ReadT, ReadT>&{transform?: undefined};
|
||||
export type InputOptionsWithTransform<ReadT, WriteT> =
|
||||
Required<Pick<InputOptions<ReadT, WriteT>, 'transform'>>&InputOptions<ReadT, WriteT>;
|
||||
|
||||
const ɵINPUT_SIGNAL_BRAND_READ_TYPE: unique symbol;
|
||||
export const ɵINPUT_SIGNAL_BRAND_WRITE_TYPE: unique symbol;
|
||||
|
||||
export interface InputSignalWithTransform<ReadT, WriteT> extends Signal<ReadT> {
|
||||
[ɵINPUT_SIGNAL_BRAND_READ_TYPE]: ReadT;
|
||||
[ɵINPUT_SIGNAL_BRAND_WRITE_TYPE]: WriteT;
|
||||
}
|
||||
export interface InputSignal<ReadT> extends InputSignalWithTransform<ReadT, ReadT> {}
|
||||
|
||||
export function inputFunction<ReadT>(): InputSignal<ReadT|undefined>;
|
||||
export function inputFunction<ReadT>(
|
||||
initialValue: ReadT, opts?: InputOptionsWithoutTransform<ReadT>): InputSignal<ReadT>;
|
||||
export function inputFunction<ReadT, WriteT>(
|
||||
initialValue: ReadT,
|
||||
opts: InputOptionsWithTransform<ReadT, WriteT>): InputSignalWithTransform<ReadT, WriteT>;
|
||||
export function inputFunction<ReadT, WriteT>(
|
||||
_initialValue?: ReadT,
|
||||
_opts?: InputOptions<ReadT, WriteT>): InputSignalWithTransform<ReadT|undefined, WriteT> {
|
||||
return null!;
|
||||
}
|
||||
|
||||
export function inputRequiredFunction<ReadT>(opts?: InputOptionsWithoutTransform<ReadT>):
|
||||
InputSignal<ReadT>;
|
||||
export function inputRequiredFunction<ReadT, WriteT>(
|
||||
opts: InputOptionsWithTransform<ReadT, WriteT>): InputSignalWithTransform<ReadT, WriteT>;
|
||||
export function inputRequiredFunction<ReadT, WriteT>(_opts?: InputSignalWithTransform<ReadT, WriteT>):
|
||||
InputSignalWithTransform<ReadT, WriteT> {
|
||||
return null!;
|
||||
}
|
||||
|
||||
export type InputFunction = typeof inputFunction&{required: typeof inputRequiredFunction};
|
||||
|
||||
export const input: InputFunction = (() => {
|
||||
(inputFunction as any).required = inputRequiredFunction;
|
||||
return inputFunction as InputFunction;
|
||||
})();
|
||||
|
||||
export type Signal<T> = (() => T);
|
||||
|
||||
// Note: needs to be kept in sync with the copies in render3/reactivity/signal.ts and
|
||||
// fake_core/index.ts to ensure consistent tests.
|
||||
export function ɵunwrapWritableSignal<T>(value: T|{[WRITABLE_SIGNAL]: T}): T {
|
||||
return null!;
|
||||
}
|
||||
|
||||
export interface ModelOptions {
|
||||
alias?: string;
|
||||
}
|
||||
|
||||
export interface ModelSignal<T> extends WritableSignal<T> {
|
||||
[ɵINPUT_SIGNAL_BRAND_READ_TYPE]: T;
|
||||
[ɵINPUT_SIGNAL_BRAND_WRITE_TYPE]: T;
|
||||
subscribe(callback: (value: T) => void): {unsubscribe: () => void};
|
||||
}
|
||||
|
||||
export interface ModelFunction {
|
||||
<T>(): ModelSignal<T|undefined>;
|
||||
<T>(initialValue: T, opts?: ModelOptions): ModelSignal<T>;
|
||||
required<T>(opts?: ModelOptions): ModelSignal<T>;
|
||||
}
|
||||
|
||||
export const model: ModelFunction = null!;
|
||||
|
||||
export type ɵUnwrapInputSignalWriteType<Field> =
|
||||
Field extends InputSignalWithTransform<unknown, infer WriteT>? WriteT : never;
|
||||
export type ɵUnwrapDirectiveSignalInputs<Dir, Fields extends keyof Dir> = {
|
||||
[P in Fields]: ɵUnwrapInputSignalWriteType<Dir[P]>
|
||||
};
|
||||
|
||||
export interface OutputEmitter<T> {
|
||||
emit(value: T): void;
|
||||
subscribe(listener: (v: T) => void): void;
|
||||
}
|
||||
`
|
||||
contents: readFileSync(path.join(directory, 'index.d.ts'), 'utf8'),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -333,6 +214,7 @@ export const ALL_ENABLED_CONFIG: Readonly<TypeCheckingConfig> = {
|
||||
useInlineTypeConstructors: true,
|
||||
suggestionsForSuboptimalTypeInference: false,
|
||||
controlFlowPreventingContentProjection: 'warning',
|
||||
allowSignalsInTwoWayBindings: true,
|
||||
};
|
||||
|
||||
// Remove 'ref' from TypeCheckableDirectiveMeta and add a 'selector' instead.
|
||||
@@ -444,6 +326,7 @@ export function tcb(
|
||||
enableTemplateTypeChecker: false,
|
||||
useInlineTypeConstructors: true,
|
||||
suggestionsForSuboptimalTypeInference: false,
|
||||
allowSignalsInTwoWayBindings: true,
|
||||
...config
|
||||
};
|
||||
options = options || {
|
||||
|
||||
@@ -24,9 +24,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDE
|
||||
****************************************************************************************************/
|
||||
import * as i0 from "@angular/core";
|
||||
export declare class TestDir {
|
||||
a: import("@angular/core").EventEmitter<unknown>;
|
||||
b: import("@angular/core").EventEmitter<string>;
|
||||
c: import("@angular/core").EventEmitter<void>;
|
||||
a: import("@angular/core").OutputEmitter<unknown>;
|
||||
b: import("@angular/core").OutputEmitter<string>;
|
||||
c: import("@angular/core").OutputEmitter<void>;
|
||||
static ɵfac: i0.ɵɵFactoryDeclaration<TestDir, never>;
|
||||
static ɵdir: i0.ɵɵDirectiveDeclaration<TestDir, never, never, {}, { "a": "a"; "b": "b"; "c": "cPublic"; }, never, never, true, never>;
|
||||
}
|
||||
@@ -58,9 +58,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDE
|
||||
****************************************************************************************************/
|
||||
import * as i0 from "@angular/core";
|
||||
export declare class TestComp {
|
||||
a: import("@angular/core").EventEmitter<unknown>;
|
||||
b: import("@angular/core").EventEmitter<string>;
|
||||
c: import("@angular/core").EventEmitter<void>;
|
||||
a: import("@angular/core").OutputEmitter<unknown>;
|
||||
b: import("@angular/core").OutputEmitter<string>;
|
||||
c: import("@angular/core").OutputEmitter<void>;
|
||||
static ɵfac: i0.ɵɵFactoryDeclaration<TestComp, never>;
|
||||
static ɵcmp: i0.ɵɵComponentDeclaration<TestComp, "ng-component", never, {}, { "a": "a"; "b": "b"; "c": "cPublic"; }, never, never, true, never>;
|
||||
}
|
||||
@@ -102,9 +102,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDE
|
||||
import { EventEmitter } from '@angular/core';
|
||||
import * as i0 from "@angular/core";
|
||||
export declare class TestDir {
|
||||
click1: EventEmitter<unknown>;
|
||||
click2: EventEmitter<boolean>;
|
||||
_bla: EventEmitter<void>;
|
||||
click1: import("@angular/core").OutputEmitter<unknown>;
|
||||
click2: import("@angular/core").OutputEmitter<boolean>;
|
||||
_bla: import("@angular/core").OutputEmitter<void>;
|
||||
clickDecorator1: EventEmitter<unknown>;
|
||||
clickDecorator2: EventEmitter<boolean>;
|
||||
_blaDecorator: EventEmitter<void>;
|
||||
|
||||
@@ -0,0 +1,313 @@
|
||||
/**
|
||||
* @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.io/license
|
||||
*/
|
||||
|
||||
import {runInEachFileSystem} from '../../src/ngtsc/file_system/testing';
|
||||
import {loadStandardTestFiles} from '../../src/ngtsc/testing';
|
||||
|
||||
import {NgtscTestEnvironment} from './env';
|
||||
|
||||
const testFiles = loadStandardTestFiles();
|
||||
|
||||
runInEachFileSystem(() => {
|
||||
describe('initializer-based input() API', () => {
|
||||
let env!: NgtscTestEnvironment;
|
||||
|
||||
beforeEach(() => {
|
||||
env = NgtscTestEnvironment.setup(testFiles);
|
||||
env.tsconfig({strictTemplates: true});
|
||||
});
|
||||
|
||||
it('should handle a basic, primitive valued input', () => {
|
||||
env.write('test.ts', `
|
||||
import {Directive, input} from '@angular/core';
|
||||
|
||||
@Directive()
|
||||
export class TestDir {
|
||||
data = input('test');
|
||||
}
|
||||
`);
|
||||
env.driveMain();
|
||||
const js = env.getContents('test.js');
|
||||
expect(js).toContain('inputs: { data: [i0.ɵɵInputFlags.SignalBased, "data"] }');
|
||||
});
|
||||
|
||||
it('should fail if @Input is applied on signal input member', () => {
|
||||
env.write('test.ts', `
|
||||
import {Directive, Input, input} from '@angular/core';
|
||||
|
||||
@Directive()
|
||||
export class TestDir {
|
||||
@Input() data = input('test');
|
||||
}
|
||||
`);
|
||||
const diagnostics = env.driveDiagnostics();
|
||||
expect(diagnostics).toEqual([jasmine.objectContaining({
|
||||
messageText: `Using @Input with a signal input is not allowed.`,
|
||||
})]);
|
||||
});
|
||||
|
||||
it('should fail if signal input is also declared in `inputs` decorator field.', () => {
|
||||
env.write('test.ts', `
|
||||
import {Directive, input} from '@angular/core';
|
||||
|
||||
@Directive({
|
||||
inputs: ['data'],
|
||||
})
|
||||
export class TestDir {
|
||||
data = input('test');
|
||||
}
|
||||
`);
|
||||
const diagnostics = env.driveDiagnostics();
|
||||
expect(diagnostics).toEqual([
|
||||
jasmine.objectContaining({
|
||||
messageText: 'Input "data" is also declared as non-signal in @Directive.',
|
||||
}),
|
||||
]);
|
||||
});
|
||||
|
||||
it('should fail if signal input declares a non-statically analyzable alias', () => {
|
||||
env.write('test.ts', `
|
||||
import {Directive, input} from '@angular/core';
|
||||
|
||||
const ALIAS = 'bla';
|
||||
|
||||
@Directive({
|
||||
inputs: ['data'],
|
||||
})
|
||||
export class TestDir {
|
||||
data = input('test', {alias: ALIAS});
|
||||
}
|
||||
`);
|
||||
const diagnostics = env.driveDiagnostics();
|
||||
expect(diagnostics).toEqual([
|
||||
jasmine.objectContaining({
|
||||
messageText: 'Alias needs to be a string that is statically analyzable.',
|
||||
}),
|
||||
]);
|
||||
});
|
||||
|
||||
it('should fail if signal input declares a non-statically analyzable options', () => {
|
||||
env.write('test.ts', `
|
||||
import {Directive, input} from '@angular/core';
|
||||
|
||||
const OPTIONS = {};
|
||||
|
||||
@Directive({
|
||||
inputs: ['data'],
|
||||
})
|
||||
export class TestDir {
|
||||
data = input('test', OPTIONS);
|
||||
}
|
||||
`);
|
||||
const diagnostics = env.driveDiagnostics();
|
||||
expect(diagnostics).toEqual([
|
||||
jasmine.objectContaining({
|
||||
messageText: 'Argument needs to be an object literal that is statically analyzable.',
|
||||
}),
|
||||
]);
|
||||
});
|
||||
|
||||
it('should fail if signal input is declared on static member', () => {
|
||||
env.write('test.ts', `
|
||||
import {Directive, input} from '@angular/core';
|
||||
|
||||
@Directive()
|
||||
export class TestDir {
|
||||
static data = input('test');
|
||||
}
|
||||
`);
|
||||
const diagnostics = env.driveDiagnostics();
|
||||
expect(diagnostics).toEqual([
|
||||
jasmine.objectContaining({
|
||||
messageText: 'Input "data" is incorrectly declared as static member of "TestDir".',
|
||||
}),
|
||||
]);
|
||||
});
|
||||
|
||||
it('should handle an alias configured, primitive valued input', () => {
|
||||
env.write('test.ts', `
|
||||
import {Directive, input} from '@angular/core';
|
||||
|
||||
@Directive()
|
||||
export class TestDir {
|
||||
data = input('test', {
|
||||
alias: 'publicName',
|
||||
});
|
||||
}
|
||||
`);
|
||||
env.driveMain();
|
||||
const js = env.getContents('test.js');
|
||||
expect(js).toContain('inputs: { data: [i0.ɵɵInputFlags.SignalBased, "publicName", "data"] }');
|
||||
});
|
||||
|
||||
it('should error if a required input declares an initial value', () => {
|
||||
env.write('test.ts', `
|
||||
import {Directive, input} from '@angular/core';
|
||||
|
||||
@Directive()
|
||||
export class TestDir {
|
||||
data = input.required({
|
||||
initialValue: 'bla',
|
||||
});
|
||||
}
|
||||
`);
|
||||
const diagnostics = env.driveDiagnostics();
|
||||
expect(diagnostics[0].messageText).toEqual(jasmine.objectContaining({
|
||||
messageText: 'No overload matches this call.',
|
||||
}));
|
||||
});
|
||||
|
||||
|
||||
it('should handle a transform and required input', () => {
|
||||
env.write('test.ts', `
|
||||
import {Directive, input} from '@angular/core';
|
||||
|
||||
@Directive()
|
||||
export class TestDir {
|
||||
data = input.required({
|
||||
transform: (v: string|number) => 'works',
|
||||
});
|
||||
}
|
||||
`);
|
||||
env.driveMain();
|
||||
expect(env.getContents('test.js'))
|
||||
.toContain(`inputs: { data: [i0.ɵɵInputFlags.SignalBased, "data"] }`);
|
||||
expect(env.getContents('test.d.ts')).toContain('"required": true; "isSignal": true;');
|
||||
expect(env.getContents('test.d.ts'))
|
||||
.withContext(
|
||||
'Expected no coercion member as input signal captures the write type of the transform')
|
||||
.not.toContain('ngAcceptInputType');
|
||||
});
|
||||
|
||||
|
||||
it('should handle a non-primitive initial value', () => {
|
||||
env.write('test.ts', `
|
||||
import {Directive, input} from '@angular/core';
|
||||
|
||||
@Directive()
|
||||
export class TestDir {
|
||||
data = input(/default pattern/);
|
||||
}
|
||||
`);
|
||||
env.driveMain();
|
||||
expect(env.getContents('test.js'))
|
||||
.toContain(`inputs: { data: [i0.ɵɵInputFlags.SignalBased, "data"] }`);
|
||||
});
|
||||
|
||||
// TODO(crisbeto): we may not want to support this combination. Will discuss with the team.
|
||||
it('should report mixed two-way binding with a signal input', () => {
|
||||
env.write('test.ts', `
|
||||
import {Component, Directive, input, Output, EventEmitter} from '@angular/core';
|
||||
|
||||
@Directive({standalone: true, selector: '[dir]'})
|
||||
export class TestDir {
|
||||
value = input('hello');
|
||||
@Output() valueChange = new EventEmitter<string>();
|
||||
}
|
||||
|
||||
@Component({
|
||||
standalone: true,
|
||||
template: \`<div dir [(value)]="value"></div>\`,
|
||||
imports: [TestDir],
|
||||
})
|
||||
export class TestComp {
|
||||
value = 1;
|
||||
}
|
||||
`);
|
||||
|
||||
const diags = env.driveDiagnostics();
|
||||
expect(diags.length).toBe(1);
|
||||
expect(diags[0].messageText).toBe(`Type 'number' is not assignable to type 'string'.`);
|
||||
});
|
||||
|
||||
describe('type checking', () => {
|
||||
it('should work', () => {
|
||||
env.write('test.ts', `
|
||||
import {Component, Directive, input} from '@angular/core';
|
||||
|
||||
@Directive({
|
||||
selector: '[directiveName]',
|
||||
standalone: true,
|
||||
})
|
||||
export class TestDir {
|
||||
data = input(1);
|
||||
}
|
||||
|
||||
@Component({
|
||||
standalone: true,
|
||||
template: \`<div directiveName [data]="false"></div>\`,
|
||||
imports: [TestDir],
|
||||
})
|
||||
export class TestComp {
|
||||
}
|
||||
`);
|
||||
|
||||
const diagnostics = env.driveDiagnostics();
|
||||
expect(diagnostics.length).toBe(1);
|
||||
expect(diagnostics[0].messageText)
|
||||
.toBe(`Type 'boolean' is not assignable to type 'number'.`);
|
||||
});
|
||||
|
||||
it('should work with transforms', () => {
|
||||
env.write('test.ts', `
|
||||
import {Component, Directive, input} from '@angular/core';
|
||||
|
||||
@Directive({
|
||||
selector: '[directiveName]',
|
||||
standalone: true,
|
||||
})
|
||||
export class TestDir {
|
||||
data = input.required({
|
||||
transform: (v: string|number) => 'works',
|
||||
});
|
||||
}
|
||||
|
||||
@Component({
|
||||
standalone: true,
|
||||
template: \`<div directiveName [data]="false"></div>\`,
|
||||
imports: [TestDir],
|
||||
})
|
||||
export class TestComp {
|
||||
}
|
||||
`);
|
||||
|
||||
const diagnostics = env.driveDiagnostics();
|
||||
expect(diagnostics.length).toBe(1);
|
||||
expect(diagnostics[0].messageText)
|
||||
.toBe(`Type 'boolean' is not assignable to type 'string | number'.`);
|
||||
});
|
||||
|
||||
it('should report unset required inputs', () => {
|
||||
env.write('test.ts', `
|
||||
import {Component, Directive, input} from '@angular/core';
|
||||
|
||||
@Directive({
|
||||
selector: '[directiveName]',
|
||||
standalone: true,
|
||||
})
|
||||
export class TestDir {
|
||||
data = input.required<boolean>();
|
||||
}
|
||||
|
||||
@Component({
|
||||
standalone: true,
|
||||
template: \`<div directiveName></div>\`,
|
||||
imports: [TestDir],
|
||||
})
|
||||
export class TestComp {
|
||||
}
|
||||
`);
|
||||
|
||||
const diagnostics = env.driveDiagnostics();
|
||||
expect(diagnostics.length).toBe(1);
|
||||
expect(diagnostics[0].messageText)
|
||||
.toBe(`Required input 'data' from directive TestDir must be specified.`);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
+59
-298
@@ -14,304 +14,7 @@ import {NgtscTestEnvironment} from './env';
|
||||
const testFiles = loadStandardTestFiles();
|
||||
|
||||
runInEachFileSystem(() => {
|
||||
describe('input()', () => {
|
||||
let env!: NgtscTestEnvironment;
|
||||
|
||||
beforeEach(() => {
|
||||
env = NgtscTestEnvironment.setup(testFiles);
|
||||
env.tsconfig({strictTemplates: true});
|
||||
});
|
||||
|
||||
it('should handle a basic, primitive valued input', () => {
|
||||
env.write('test.ts', `
|
||||
import {Directive, input} from '@angular/core';
|
||||
|
||||
@Directive()
|
||||
export class TestDir {
|
||||
data = input('test');
|
||||
}
|
||||
`);
|
||||
env.driveMain();
|
||||
const js = env.getContents('test.js');
|
||||
expect(js).toContain('inputs: { data: [i0.ɵɵInputFlags.SignalBased, "data"] }');
|
||||
});
|
||||
|
||||
it('should fail if @Input is applied on signal input member', () => {
|
||||
env.write('test.ts', `
|
||||
import {Directive, Input, input} from '@angular/core';
|
||||
|
||||
@Directive()
|
||||
export class TestDir {
|
||||
@Input() data = input('test');
|
||||
}
|
||||
`);
|
||||
const diagnostics = env.driveDiagnostics();
|
||||
expect(diagnostics).toEqual([jasmine.objectContaining({
|
||||
messageText: `Using @Input with a signal input is not allowed.`,
|
||||
})]);
|
||||
});
|
||||
|
||||
it('should fail if signal input is also declared in `inputs` decorator field.', () => {
|
||||
env.write('test.ts', `
|
||||
import {Directive, input} from '@angular/core';
|
||||
|
||||
@Directive({
|
||||
inputs: ['data'],
|
||||
})
|
||||
export class TestDir {
|
||||
data = input('test');
|
||||
}
|
||||
`);
|
||||
const diagnostics = env.driveDiagnostics();
|
||||
expect(diagnostics).toEqual([
|
||||
jasmine.objectContaining({
|
||||
messageText: 'Input "data" is also declared as non-signal in @Directive.',
|
||||
}),
|
||||
]);
|
||||
});
|
||||
|
||||
it('should fail if signal input declares a non-statically analyzable alias', () => {
|
||||
env.write('test.ts', `
|
||||
import {Directive, input} from '@angular/core';
|
||||
|
||||
const ALIAS = 'bla';
|
||||
|
||||
@Directive({
|
||||
inputs: ['data'],
|
||||
})
|
||||
export class TestDir {
|
||||
data = input('test', {alias: ALIAS});
|
||||
}
|
||||
`);
|
||||
const diagnostics = env.driveDiagnostics();
|
||||
expect(diagnostics).toEqual([
|
||||
jasmine.objectContaining({
|
||||
messageText: 'Alias needs to be a string that is statically analyzable.',
|
||||
}),
|
||||
]);
|
||||
});
|
||||
|
||||
it('should fail if signal input declares a non-statically analyzable options', () => {
|
||||
env.write('test.ts', `
|
||||
import {Directive, input} from '@angular/core';
|
||||
|
||||
const OPTIONS = {};
|
||||
|
||||
@Directive({
|
||||
inputs: ['data'],
|
||||
})
|
||||
export class TestDir {
|
||||
data = input('test', OPTIONS);
|
||||
}
|
||||
`);
|
||||
const diagnostics = env.driveDiagnostics();
|
||||
expect(diagnostics).toEqual([
|
||||
jasmine.objectContaining({
|
||||
messageText: 'Argument needs to be an object literal that is statically analyzable.',
|
||||
}),
|
||||
]);
|
||||
});
|
||||
|
||||
it('should fail if signal input is declared on static member', () => {
|
||||
env.write('test.ts', `
|
||||
import {Directive, input} from '@angular/core';
|
||||
|
||||
@Directive()
|
||||
export class TestDir {
|
||||
static data = input('test');
|
||||
}
|
||||
`);
|
||||
const diagnostics = env.driveDiagnostics();
|
||||
expect(diagnostics).toEqual([
|
||||
jasmine.objectContaining({
|
||||
messageText: 'Input "data" is incorrectly declared as static member of "TestDir".',
|
||||
}),
|
||||
]);
|
||||
});
|
||||
|
||||
it('should handle an alias configured, primitive valued input', () => {
|
||||
env.write('test.ts', `
|
||||
import {Directive, input} from '@angular/core';
|
||||
|
||||
@Directive()
|
||||
export class TestDir {
|
||||
data = input('test', {
|
||||
alias: 'publicName',
|
||||
});
|
||||
}
|
||||
`);
|
||||
env.driveMain();
|
||||
const js = env.getContents('test.js');
|
||||
expect(js).toContain('inputs: { data: [i0.ɵɵInputFlags.SignalBased, "publicName", "data"] }');
|
||||
});
|
||||
|
||||
it('should error if a required input declares an initial value', () => {
|
||||
env.write('test.ts', `
|
||||
import {Directive, input} from '@angular/core';
|
||||
|
||||
@Directive()
|
||||
export class TestDir {
|
||||
data = input.required({
|
||||
initialValue: 'bla',
|
||||
});
|
||||
}
|
||||
`);
|
||||
const diagnostics = env.driveDiagnostics();
|
||||
expect(diagnostics[0].messageText).toEqual(jasmine.objectContaining({
|
||||
messageText: 'No overload matches this call.',
|
||||
}));
|
||||
});
|
||||
|
||||
|
||||
it('should handle a transform and required input', () => {
|
||||
env.write('test.ts', `
|
||||
import {Directive, input} from '@angular/core';
|
||||
|
||||
@Directive()
|
||||
export class TestDir {
|
||||
data = input.required({
|
||||
transform: (v: string|number) => 'works',
|
||||
});
|
||||
}
|
||||
`);
|
||||
env.driveMain();
|
||||
expect(env.getContents('test.js'))
|
||||
.toContain(`inputs: { data: [i0.ɵɵInputFlags.SignalBased, "data"] }`);
|
||||
expect(env.getContents('test.d.ts')).toContain('"required": true; "isSignal": true;');
|
||||
expect(env.getContents('test.d.ts'))
|
||||
.withContext(
|
||||
'Expected no coercion member as input signal captures the write type of the transform')
|
||||
.not.toContain('ngAcceptInputType');
|
||||
});
|
||||
|
||||
|
||||
it('should handle a non-primitive initial value', () => {
|
||||
env.write('test.ts', `
|
||||
import {Directive, input} from '@angular/core';
|
||||
|
||||
@Directive()
|
||||
export class TestDir {
|
||||
data = input(/default pattern/);
|
||||
}
|
||||
`);
|
||||
env.driveMain();
|
||||
expect(env.getContents('test.js'))
|
||||
.toContain(`inputs: { data: [i0.ɵɵInputFlags.SignalBased, "data"] }`);
|
||||
});
|
||||
|
||||
// TODO(crisbeto): we may not want to support this combination. Will discuss with the team.
|
||||
it('should report mixed two-way binding with a signal input', () => {
|
||||
env.write('test.ts', `
|
||||
import {Component, Directive, input, Output, EventEmitter} from '@angular/core';
|
||||
|
||||
@Directive({standalone: true, selector: '[dir]'})
|
||||
export class TestDir {
|
||||
value = input('hello');
|
||||
@Output() valueChange = new EventEmitter<string>();
|
||||
}
|
||||
|
||||
@Component({
|
||||
standalone: true,
|
||||
template: \`<div dir [(value)]="value"></div>\`,
|
||||
imports: [TestDir],
|
||||
})
|
||||
export class TestComp {
|
||||
value = 1;
|
||||
}
|
||||
`);
|
||||
|
||||
const diags = env.driveDiagnostics();
|
||||
expect(diags.length).toBe(1);
|
||||
expect(diags[0].messageText).toBe(`Type 'number' is not assignable to type 'string'.`);
|
||||
});
|
||||
|
||||
describe('type checking', () => {
|
||||
it('should work', () => {
|
||||
env.write('test.ts', `
|
||||
import {Component, Directive, input} from '@angular/core';
|
||||
|
||||
@Directive({
|
||||
selector: '[directiveName]',
|
||||
standalone: true,
|
||||
})
|
||||
export class TestDir {
|
||||
data = input(1);
|
||||
}
|
||||
|
||||
@Component({
|
||||
standalone: true,
|
||||
template: \`<div directiveName [data]="false"></div>\`,
|
||||
imports: [TestDir],
|
||||
})
|
||||
export class TestComp {
|
||||
}
|
||||
`);
|
||||
|
||||
const diagnostics = env.driveDiagnostics();
|
||||
expect(diagnostics.length).toBe(1);
|
||||
expect(diagnostics[0].messageText)
|
||||
.toBe(`Type 'boolean' is not assignable to type 'number'.`);
|
||||
});
|
||||
|
||||
it('should work with transforms', () => {
|
||||
env.write('test.ts', `
|
||||
import {Component, Directive, input} from '@angular/core';
|
||||
|
||||
@Directive({
|
||||
selector: '[directiveName]',
|
||||
standalone: true,
|
||||
})
|
||||
export class TestDir {
|
||||
data = input.required({
|
||||
transform: (v: string|number) => 'works',
|
||||
});
|
||||
}
|
||||
|
||||
@Component({
|
||||
standalone: true,
|
||||
template: \`<div directiveName [data]="false"></div>\`,
|
||||
imports: [TestDir],
|
||||
})
|
||||
export class TestComp {
|
||||
}
|
||||
`);
|
||||
|
||||
const diagnostics = env.driveDiagnostics();
|
||||
expect(diagnostics.length).toBe(1);
|
||||
expect(diagnostics[0].messageText)
|
||||
.toBe(`Type 'boolean' is not assignable to type 'string | number'.`);
|
||||
});
|
||||
|
||||
it('should report unset required inputs', () => {
|
||||
env.write('test.ts', `
|
||||
import {Component, Directive, input} from '@angular/core';
|
||||
|
||||
@Directive({
|
||||
selector: '[directiveName]',
|
||||
standalone: true,
|
||||
})
|
||||
export class TestDir {
|
||||
data = input.required<boolean>();
|
||||
}
|
||||
|
||||
@Component({
|
||||
standalone: true,
|
||||
template: \`<div directiveName></div>\`,
|
||||
imports: [TestDir],
|
||||
})
|
||||
export class TestComp {
|
||||
}
|
||||
`);
|
||||
|
||||
const diagnostics = env.driveDiagnostics();
|
||||
expect(diagnostics.length).toBe(1);
|
||||
expect(diagnostics[0].messageText)
|
||||
.toBe(`Required input 'data' from directive TestDir must be specified.`);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('model()', () => {
|
||||
describe('initializer-based model() API', () => {
|
||||
let env!: NgtscTestEnvironment;
|
||||
|
||||
beforeEach(() => {
|
||||
@@ -828,5 +531,63 @@ runInEachFileSystem(() => {
|
||||
const diags = env.driveDiagnostics();
|
||||
expect(diags).toEqual([]);
|
||||
});
|
||||
|
||||
it('should not widen the type of two-way bindings on Angular versions less than 17.2', () => {
|
||||
env.tsconfig({_angularCoreVersion: '16.50.60', strictTemplates: true});
|
||||
env.write('test.ts', `
|
||||
import {Component, Directive, Input, Output, EventEmitter, signal} from '@angular/core';
|
||||
|
||||
@Directive({
|
||||
selector: '[dir]',
|
||||
standalone: true,
|
||||
})
|
||||
export class TestDir {
|
||||
@Input() value = 0;
|
||||
@Output() valueChange = new EventEmitter<number>();
|
||||
}
|
||||
|
||||
@Component({
|
||||
standalone: true,
|
||||
template: \`<div dir [(value)]="value"></div>\`,
|
||||
imports: [TestDir],
|
||||
})
|
||||
export class TestComp {
|
||||
value = signal(1);
|
||||
}
|
||||
`);
|
||||
|
||||
const diags = env.driveDiagnostics();
|
||||
expect(diags.length).toBe(1);
|
||||
expect(diags[0].messageText)
|
||||
.toBe(`Type 'WritableSignal<number>' is not assignable to type 'number'.`);
|
||||
});
|
||||
|
||||
it('should widen the type of two-way bindings on Angular version 0.0.0', () => {
|
||||
env.tsconfig({_angularCoreVersion: '0.0.0-PLACEHOLDER', strictTemplates: true});
|
||||
env.write('test.ts', `
|
||||
import {Component, Directive, Input, Output, EventEmitter, signal} from '@angular/core';
|
||||
|
||||
@Directive({
|
||||
selector: '[dir]',
|
||||
standalone: true,
|
||||
})
|
||||
export class TestDir {
|
||||
@Input() value = 0;
|
||||
@Output() valueChange = new EventEmitter<number>();
|
||||
}
|
||||
|
||||
@Component({
|
||||
standalone: true,
|
||||
template: \`<div dir [(value)]="value"></div>\`,
|
||||
imports: [TestDir],
|
||||
})
|
||||
export class TestComp {
|
||||
value = signal(1);
|
||||
}
|
||||
`);
|
||||
|
||||
const diags = env.driveDiagnostics();
|
||||
expect(diags).toEqual([]);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -14,8 +14,7 @@ export const REQUIRED_UNSET_VALUE = /* @__PURE__ */ Symbol('InputSignalNode#UNSE
|
||||
* Reactive node type for an input signal. An input signal extends a signal.
|
||||
* There are special properties to enable transforms and required inputs.
|
||||
*/
|
||||
export interface InputSignalNode<ReadT, WriteT> extends
|
||||
SignalNode<ReadT|typeof REQUIRED_UNSET_VALUE> {
|
||||
export interface InputSignalNode<ReadT, WriteT> extends SignalNode<ReadT> {
|
||||
/**
|
||||
* User-configured transform that will run whenever a new value is applied
|
||||
* to the input signal node.
|
||||
|
||||
@@ -6,8 +6,9 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {REQUIRED_UNSET_VALUE} from '../input/input_signal_node';
|
||||
|
||||
import {createModelSignal, ModelOptions, ModelSignal} from './model_signal';
|
||||
import {REQUIRED_UNSET_VALUE} from './model_signal_node';
|
||||
|
||||
export function modelFunction<T>(initialValue?: T): ModelSignal<T|undefined> {
|
||||
return createModelSignal(initialValue);
|
||||
|
||||
@@ -12,8 +12,7 @@ import {RuntimeError, RuntimeErrorCode} from '../../errors';
|
||||
import {Signal} from '../../render3/reactivity/api';
|
||||
import {WritableSignal} from '../../render3/reactivity/signal';
|
||||
import {ɵINPUT_SIGNAL_BRAND_READ_TYPE, ɵINPUT_SIGNAL_BRAND_WRITE_TYPE} from '../input/input_signal';
|
||||
|
||||
import {MODEL_SIGNAL_NODE, ModelSignalNode, REQUIRED_UNSET_VALUE} from './model_signal_node';
|
||||
import {INPUT_SIGNAL_NODE, InputSignalNode, REQUIRED_UNSET_VALUE} from '../input/input_signal_node';
|
||||
|
||||
/**
|
||||
* @developerPreview
|
||||
@@ -37,16 +36,13 @@ export interface ModelOptions {
|
||||
* @developerPreview
|
||||
*/
|
||||
export interface ModelSignal<T> extends WritableSignal<T> {
|
||||
[SIGNAL]: ModelSignalNode<T>;
|
||||
[SIGNAL]: InputSignalNode<T, T>;
|
||||
[ɵINPUT_SIGNAL_BRAND_READ_TYPE]: T;
|
||||
[ɵINPUT_SIGNAL_BRAND_WRITE_TYPE]: T;
|
||||
|
||||
// TODO(crisbeto): mark this as @internal
|
||||
/**
|
||||
* Subscribes to changes in the model's value. Used by listener instructions at runtime.
|
||||
* @deprecated Do not use, will be removed.
|
||||
*/
|
||||
subscribe(callback: (value: T) => void): {unsubscribe: () => void};
|
||||
// TODO(crisbeto): either make this a public API or mark as internal pending discussion.
|
||||
/** @deprecated Do not use, will be removed. */
|
||||
subscribe(callback: (value: T) => void): () => void;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -58,7 +54,7 @@ export interface ModelSignal<T> extends WritableSignal<T> {
|
||||
*/
|
||||
export function createModelSignal<T>(initialValue: T): ModelSignal<T> {
|
||||
const subscriptions: ((value: T) => void)[] = [];
|
||||
const node: ModelSignalNode<T> = Object.create(MODEL_SIGNAL_NODE);
|
||||
const node: InputSignalNode<T, T> = Object.create(INPUT_SIGNAL_NODE);
|
||||
|
||||
node.value = initialValue;
|
||||
|
||||
@@ -92,14 +88,11 @@ export function createModelSignal<T>(initialValue: T): ModelSignal<T> {
|
||||
getter.subscribe = (callback: (value: T) => void) => {
|
||||
subscriptions.push(callback);
|
||||
|
||||
// TODO(crisbeto): figure out if we can get rid of the object literal.
|
||||
return {
|
||||
unsubscribe: () => {
|
||||
const index = subscriptions.indexOf(callback);
|
||||
return () => {
|
||||
const index = subscriptions.indexOf(callback);
|
||||
|
||||
if (index > -1) {
|
||||
subscriptions.splice(index, 1);
|
||||
}
|
||||
if (index > -1) {
|
||||
subscriptions.splice(index, 1);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
@@ -1,33 +0,0 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google LLC All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {SIGNAL_NODE, SignalNode, signalSetFn} from '@angular/core/primitives/signals';
|
||||
|
||||
export const REQUIRED_UNSET_VALUE = /* @__PURE__ */ Symbol('ModelSignalNode#UNSET');
|
||||
|
||||
/**
|
||||
* Reactive node type for a model signal. Model signals extend
|
||||
* signals by adding the ability to track subscriptions and to be required.
|
||||
*/
|
||||
export interface ModelSignalNode<T> extends SignalNode<T> {
|
||||
/** Used by the runtime to write a value to the signal input. */
|
||||
applyValueToInputSignal: (node: ModelSignalNode<T>, value: T) => void;
|
||||
}
|
||||
|
||||
// Note: Using an IIFE here to ensure that the spread assignment is not considered
|
||||
// a side-effect, ending up preserving `COMPUTED_NODE` and `REACTIVE_NODE`.
|
||||
// TODO: remove when https://github.com/evanw/esbuild/issues/3392 is resolved.
|
||||
export const MODEL_SIGNAL_NODE: ModelSignalNode<unknown> = /* @__PURE__ */ (() => {
|
||||
return {
|
||||
...SIGNAL_NODE,
|
||||
|
||||
// TODO(crisbeto): figure out how to avoid this.
|
||||
// Maybe set an input flag that the value is writeable?
|
||||
applyValueToInputSignal: <T>(node: ModelSignalNode<T>, value: T) => signalSetFn(node, value)
|
||||
};
|
||||
})();
|
||||
@@ -258,12 +258,13 @@ export function ɵɵdeferOnImmediate() {
|
||||
const lView = getLView();
|
||||
const tNode = getCurrentTNode()!;
|
||||
const tView = lView[TVIEW];
|
||||
const injector = lView[INJECTOR]!;
|
||||
const tDetails = getTDeferBlockDetails(tView, tNode);
|
||||
|
||||
// Render placeholder block only if loading template is not present
|
||||
// to avoid content flickering, since it would be immediately replaced
|
||||
// Render placeholder block only if loading template is not present and we're on
|
||||
// the client to avoid content flickering, since it would be immediately replaced
|
||||
// by the loading block.
|
||||
if (tDetails.loadingTmplIndex === null) {
|
||||
if (!shouldTriggerDeferBlock(injector) || tDetails.loadingTmplIndex === null) {
|
||||
renderPlaceholder(lView, tNode);
|
||||
}
|
||||
triggerDeferBlock(lView, tNode);
|
||||
|
||||
@@ -207,10 +207,16 @@ export function listenerInternal(
|
||||
directiveInstance.constructor.name}'.`);
|
||||
}
|
||||
|
||||
const subscription = (output as SubscribableOutput<unknown>).subscribe(listenerFn);
|
||||
const subscriptionOrCallback =
|
||||
(output as SubscribableOutput<unknown>).subscribe(listenerFn);
|
||||
const idx = lCleanup.length;
|
||||
lCleanup.push(listenerFn, subscription);
|
||||
tCleanup && tCleanup.push(eventName, tNode.index, idx, -(idx + 1));
|
||||
lCleanup.push(listenerFn, subscriptionOrCallback);
|
||||
if (tCleanup) {
|
||||
// The cleanup function expects negative indexes to be
|
||||
// of type Subscription while positive are cleanup functions.
|
||||
const cleanupIdx = typeof subscriptionOrCallback === 'function' ? idx + 1 : -(idx + 1);
|
||||
tCleanup.push(eventName, tNode.index, idx, cleanupIdx);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -277,8 +283,8 @@ function wrapListener(
|
||||
}
|
||||
|
||||
/** Describes a subscribable output field value. */
|
||||
export interface SubscribableOutput<T> {
|
||||
subscribe(listener: (v: T) => void): {unsubscribe: () => void;};
|
||||
interface SubscribableOutput<T> {
|
||||
subscribe(listener: (v: T) => void): {unsubscribe: () => void;}|(() => void);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -287,7 +293,7 @@ export interface SubscribableOutput<T> {
|
||||
* For example, an `EventEmitter, a `Subject`, an `Observable` or an
|
||||
* `OutputEmitter`.
|
||||
*/
|
||||
export function isOutputSubscribable(value: unknown): value is SubscribableOutput<unknown> {
|
||||
function isOutputSubscribable(value: unknown): value is SubscribableOutput<unknown> {
|
||||
return value != null &&
|
||||
typeof (value as Partial<SubscribableOutput<unknown>>).subscribe === 'function';
|
||||
}
|
||||
|
||||
@@ -442,7 +442,7 @@ function processCleanups(tView: TView, lView: LView): void {
|
||||
const targetIdx = tCleanup[i + 3];
|
||||
ngDevMode && assertNumber(targetIdx, 'cleanup target must be a number');
|
||||
if (targetIdx >= 0) {
|
||||
// unregister
|
||||
// Destroy anything whose teardown is a function call (e.g. QueryList, ModelSignal).
|
||||
lCleanup[targetIdx]();
|
||||
} else {
|
||||
// Subscription
|
||||
|
||||
@@ -354,9 +354,9 @@ function materializeViewResults<T>(
|
||||
const lQuery = lView[QUERIES]!.queries![queryIndex];
|
||||
if (lQuery.matches === null) {
|
||||
const tViewData = tView.data;
|
||||
const tQueryMatches = tQuery.matches!;
|
||||
const result: T|null[] = [];
|
||||
for (let i = 0; i < tQueryMatches.length; i += 2) {
|
||||
const tQueryMatches = tQuery.matches;
|
||||
const result: Array<T|null> = [];
|
||||
for (let i = 0; tQueryMatches !== null && i < tQueryMatches.length; i += 2) {
|
||||
const matchedNodeIdx = tQueryMatches[i];
|
||||
if (matchedNodeIdx < 0) {
|
||||
// we at the <ng-template> marker which might have results in views created based on this
|
||||
|
||||
@@ -24,6 +24,11 @@ interface QuerySignalNode<T> extends ComputedNode<T|ReadonlyArray<T>> {
|
||||
_queryIndex?: number;
|
||||
_queryList?: QueryList<T>;
|
||||
_dirtyCounter: WritableSignal<number>;
|
||||
/**
|
||||
* Stores the last seen, flattened results for a query. This is to avoid marking the signal result
|
||||
* computed as dirty when there was view manipulation that didn't impact final results.
|
||||
*/
|
||||
_flatValue?: T|ReadonlyArray<T>;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -58,6 +63,7 @@ function createQuerySignalFn<V>(firstOnly: boolean, required: boolean) {
|
||||
});
|
||||
node = signalFn[SIGNAL] as QuerySignalNode<V>;
|
||||
node._dirtyCounter = signal(0);
|
||||
node._flatValue = undefined;
|
||||
|
||||
if (ngDevMode) {
|
||||
signalFn.toString = () => `[Query Signal]`;
|
||||
@@ -111,5 +117,15 @@ function refreshSignalQuery<V>(node: QuerySignalNode<V>, firstOnly: boolean): V|
|
||||
|
||||
queryList.reset(results, unwrapElementRef);
|
||||
|
||||
return firstOnly ? queryList.first : queryList.toArray();
|
||||
if (firstOnly) {
|
||||
return queryList.first;
|
||||
} else {
|
||||
// TODO: remove access to the private _changesDetected field by abstracting / removing usage of
|
||||
// QueryList in the signal-based queries (perf follow-up)
|
||||
const resultChanged = (queryList as any as {_changesDetected: boolean})._changesDetected;
|
||||
if (resultChanged || node._flatValue === undefined) {
|
||||
return node._flatValue = queryList.toArray();
|
||||
}
|
||||
return node._flatValue;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,8 +43,7 @@ export interface WritableSignal<T> extends Signal<T> {
|
||||
* @codeGenApi
|
||||
*/
|
||||
export function ɵunwrapWritableSignal<T>(value: T|{[WRITABLE_SIGNAL]: T}): T {
|
||||
// Note: needs to be kept in sync with the copies in `fake_core/index.ts` and
|
||||
// `ngtsc/typecheck/testing/index.ts` to ensure consistent tests.
|
||||
// Note: needs to be kept in sync with the copy in `fake_core/index.ts`.
|
||||
// Note: the function uses `WRITABLE_SIGNAL` as a brand instead of `WritableSignal<T>`,
|
||||
// because the latter incorrectly unwraps non-signal getter functions.
|
||||
return null!;
|
||||
|
||||
@@ -564,4 +564,29 @@ describe('model inputs', () => {
|
||||
}),
|
||||
]);
|
||||
});
|
||||
|
||||
it('should not throw for mixed model and output subscriptions', () => {
|
||||
@Directive({selector: '[dir]', standalone: true})
|
||||
class Dir {
|
||||
model = model(0);
|
||||
@Output() output = new EventEmitter();
|
||||
model2 = model(0);
|
||||
@Output() output2 = new EventEmitter();
|
||||
}
|
||||
|
||||
@Component({
|
||||
template: `
|
||||
<div dir (model)="noop()" (output)="noop()" (model2)="noop()" (output2)="noop()"></div>
|
||||
`,
|
||||
standalone: true,
|
||||
imports: [Dir],
|
||||
})
|
||||
class App {
|
||||
noop() {}
|
||||
}
|
||||
|
||||
const fixture = TestBed.createComponent(App);
|
||||
fixture.detectChanges();
|
||||
expect(() => fixture.destroy()).not.toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -183,6 +183,23 @@ describe('queries as signals', () => {
|
||||
expect(result2.length).toBe(1);
|
||||
expect(result2).toBe(result1);
|
||||
});
|
||||
|
||||
it('should be empty when no query matches exist', () => {
|
||||
@Component({
|
||||
standalone: true,
|
||||
template: ``,
|
||||
})
|
||||
class AppComponent {
|
||||
result = viewChild('unknown');
|
||||
results = viewChildren('unknown');
|
||||
}
|
||||
|
||||
const fixture = TestBed.createComponent(AppComponent);
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(fixture.componentInstance.result()).toBeUndefined();
|
||||
expect(fixture.componentInstance.results().length).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('content queries', () => {
|
||||
@@ -319,5 +336,109 @@ describe('queries as signals', () => {
|
||||
|
||||
expect(queryDir.results().length).toBe(2);
|
||||
});
|
||||
|
||||
it('should be empty when no query matches exist', () => {
|
||||
@Directive({
|
||||
selector: '[declare]',
|
||||
standalone: true,
|
||||
})
|
||||
class DeclareQuery {
|
||||
result = contentChild('unknown');
|
||||
results = contentChildren('unknown');
|
||||
}
|
||||
|
||||
@Component({
|
||||
standalone: true,
|
||||
imports: [DeclareQuery],
|
||||
template: `<div declare></div>`,
|
||||
})
|
||||
class AppComponent {
|
||||
}
|
||||
|
||||
const fixture = TestBed.createComponent(AppComponent);
|
||||
fixture.detectChanges();
|
||||
const queryDir =
|
||||
fixture.debugElement.query(By.directive(DeclareQuery)).injector.get(DeclareQuery);
|
||||
|
||||
expect(queryDir.result()).toBeUndefined();
|
||||
expect(queryDir.results().length).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('reactivity and performance', () => {
|
||||
it('should not dirty a children query when a list of matches does not change - a view with matches',
|
||||
() => {
|
||||
let recomputeCount = 0;
|
||||
|
||||
@Component({
|
||||
standalone: true,
|
||||
template: `
|
||||
<div #el></div>
|
||||
@if (show) {
|
||||
<div #el></div>
|
||||
}
|
||||
`,
|
||||
})
|
||||
class AppComponent {
|
||||
divEls = viewChildren<ElementRef<HTMLDivElement>>('el');
|
||||
foundElCount = computed(() => {
|
||||
recomputeCount++;
|
||||
return this.divEls().length;
|
||||
});
|
||||
show = false;
|
||||
}
|
||||
|
||||
const fixture = TestBed.createComponent(AppComponent);
|
||||
fixture.detectChanges();
|
||||
expect(fixture.componentInstance.foundElCount()).toBe(1);
|
||||
expect(recomputeCount).toBe(1);
|
||||
|
||||
// trigger view manipulation that should dirty queries but not change the results
|
||||
fixture.componentInstance.show = true;
|
||||
fixture.detectChanges();
|
||||
fixture.componentInstance.show = false;
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(fixture.componentInstance.foundElCount()).toBe(1);
|
||||
expect(recomputeCount).toBe(1);
|
||||
});
|
||||
|
||||
it('should not dirty a children query when a list of matches does not change - a view with another container',
|
||||
() => {
|
||||
let recomputeCount = 0;
|
||||
|
||||
@Component({
|
||||
standalone: true,
|
||||
template: `
|
||||
<div #el></div>
|
||||
@if (show) {
|
||||
<!-- an empty if to create a container -->
|
||||
@if (true) {}
|
||||
}
|
||||
`,
|
||||
})
|
||||
class AppComponent {
|
||||
divEls = viewChildren<ElementRef<HTMLDivElement>>('el');
|
||||
foundElCount = computed(() => {
|
||||
recomputeCount++;
|
||||
return this.divEls().length;
|
||||
});
|
||||
show = false;
|
||||
}
|
||||
|
||||
const fixture = TestBed.createComponent(AppComponent);
|
||||
fixture.detectChanges();
|
||||
expect(fixture.componentInstance.foundElCount()).toBe(1);
|
||||
expect(recomputeCount).toBe(1);
|
||||
|
||||
// trigger view manipulation that should dirty queries but not change the results
|
||||
fixture.componentInstance.show = true;
|
||||
fixture.detectChanges();
|
||||
fixture.componentInstance.show = false;
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(fixture.componentInstance.foundElCount()).toBe(1);
|
||||
expect(recomputeCount).toBe(1);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -7,6 +7,7 @@ ts_library(
|
||||
"signal_input_signature_test.ts",
|
||||
"signal_model_signature_test.ts",
|
||||
"signal_queries_signature_test.ts",
|
||||
"unwrap_writable_signal_signature_test.ts",
|
||||
],
|
||||
deps = ["//packages/core"],
|
||||
)
|
||||
|
||||
@@ -15,7 +15,7 @@ import {ModelSignal} from '@angular/core/src/authoring/model/model_signal';
|
||||
* in `ModelSignal`, but it's marked as internal which makes it unavailable in tests.
|
||||
*/
|
||||
type SubscribableSignal<T> = ModelSignal<T>&{
|
||||
subscribe(callback: (value: T) => void): {unsubscribe: () => void};
|
||||
subscribe(callback: (value: T) => void): () => void;
|
||||
};
|
||||
|
||||
describe('model signal', () => {
|
||||
@@ -68,7 +68,7 @@ describe('model signal', () => {
|
||||
signal.set(1);
|
||||
expect(values).toEqual([1]);
|
||||
|
||||
subscription.unsubscribe();
|
||||
subscription();
|
||||
signal.set(2);
|
||||
expect(values).toEqual([1]);
|
||||
});
|
||||
@@ -103,11 +103,11 @@ describe('model signal', () => {
|
||||
signal2.set(1);
|
||||
expect(emitCount).toBe(2);
|
||||
|
||||
subscription1.unsubscribe();
|
||||
subscription1();
|
||||
signal2.set(2);
|
||||
expect(emitCount).toBe(3);
|
||||
|
||||
subscription2.unsubscribe();
|
||||
subscription2();
|
||||
signal2.set(3);
|
||||
expect(emitCount).toBe(3);
|
||||
});
|
||||
|
||||
@@ -11,6 +11,17 @@ import path from 'path';
|
||||
import ts from 'typescript';
|
||||
import url from 'url';
|
||||
|
||||
/** Currently-configured tests. */
|
||||
const TESTS = new Map<string, (value: string) => string>([
|
||||
[
|
||||
'signal_input_signature_test',
|
||||
v => v.includes(',') ? `InputSignalWithTransform<${v}>` : `InputSignal<${v}>`
|
||||
],
|
||||
['signal_queries_signature_test', v => `Signal<${v}>`],
|
||||
['signal_model_signature_test', v => `ModelSignal<${v}>`],
|
||||
['unwrap_writable_signal_signature_test', v => v]
|
||||
]);
|
||||
|
||||
const containingDir = path.dirname(url.fileURLToPath(import.meta.url));
|
||||
|
||||
/**
|
||||
@@ -21,8 +32,7 @@ function isTestClass(classDeclaration: ts.ClassDeclaration): boolean {
|
||||
return classDeclaration.name !== undefined && classDeclaration.name.text.endsWith('Test');
|
||||
}
|
||||
|
||||
function testFile(
|
||||
testFileName: string, getType: (v: string) => string, inferWriteType: boolean): boolean {
|
||||
function testFile(testFileName: string, getType: (v: string) => string): boolean {
|
||||
const fileContent = fs.readFileSync(path.join(containingDir, `${testFileName}.d.ts`), 'utf8');
|
||||
const sourceFile = ts.createSourceFile('test.ts', fileContent, ts.ScriptTarget.ESNext, true);
|
||||
const testClazz = sourceFile.statements.find(
|
||||
@@ -63,11 +73,7 @@ function testFile(
|
||||
async function main() {
|
||||
let failing = false;
|
||||
|
||||
failing ||= testFile(
|
||||
'signal_input_signature_test',
|
||||
v => v.includes(',') ? `InputSignalWithTransform<${v}>` : `InputSignal<${v}>`, true);
|
||||
failing ||= testFile('signal_queries_signature_test', v => `Signal<${v}>`, false);
|
||||
failing ||= testFile('signal_model_signature_test', v => `ModelSignal<${v}>`, false);
|
||||
TESTS.forEach((callback, filename) => failing ||= testFile(filename, callback));
|
||||
|
||||
if (failing) {
|
||||
throw new Error('Failing assertions');
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
/**
|
||||
* @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.io/license
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview
|
||||
* This file contains various signal `model()` patterns and ensures
|
||||
* the resulting types match our expectations (via comments asserting the `.d.ts`).
|
||||
*/
|
||||
|
||||
import {input, model, signal, ɵunwrapWritableSignal as unwrapWritableSignal} from '@angular/core';
|
||||
// clang-format off
|
||||
// import preserved to simplify `.d.ts` emit and simplify the `type_tester` logic.
|
||||
// tslint:disable-next-line no-duplicate-imports
|
||||
import {InputSignal, WritableSignal} from '@angular/core';
|
||||
// clang-format on
|
||||
|
||||
export class SignalModelSignatureTest {
|
||||
/** string | undefined */
|
||||
optionalModel = unwrapWritableSignal(model<string>());
|
||||
|
||||
/** string */
|
||||
requiredModel = unwrapWritableSignal(model.required<string>());
|
||||
|
||||
/** string | number */
|
||||
writableSignal = unwrapWritableSignal(signal<string|number>(0));
|
||||
|
||||
/** InputSignal<string | undefined> */
|
||||
optionalReadonlySignal = unwrapWritableSignal(input<string>());
|
||||
|
||||
/** InputSignal<string> */
|
||||
requiredReadonlySignal = unwrapWritableSignal(input.required<string>());
|
||||
|
||||
/** number */
|
||||
primitiveValue = unwrapWritableSignal(123);
|
||||
|
||||
/** (value: string | null | undefined) => number */
|
||||
getterFunction =
|
||||
unwrapWritableSignal((value: string|null|undefined) => value ? parseInt(value) : 0);
|
||||
}
|
||||
@@ -1562,9 +1562,6 @@
|
||||
{
|
||||
"name": "init_model_signal"
|
||||
},
|
||||
{
|
||||
"name": "init_model_signal_node"
|
||||
},
|
||||
{
|
||||
"name": "init_module"
|
||||
},
|
||||
|
||||
@@ -31,6 +31,11 @@ export interface PluginConfig {
|
||||
* versions of Angular that do not support blocks (pre-v17) used with the language service.
|
||||
*/
|
||||
enableBlockSyntax?: false;
|
||||
|
||||
/**
|
||||
* Version of `@angular/core` that was detected in the user's workspace.
|
||||
*/
|
||||
angularCoreVersion?: string;
|
||||
}
|
||||
|
||||
export type GetTcbResponse = {
|
||||
|
||||
@@ -523,6 +523,8 @@ function parseNgCompilerOptions(
|
||||
options['_enableBlockSyntax'] = false;
|
||||
}
|
||||
|
||||
options['_angularCoreVersion'] = config.angularCoreVersion;
|
||||
|
||||
return options;
|
||||
}
|
||||
|
||||
|
||||
@@ -388,6 +388,74 @@ describe('completions', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('model inputs', () => {
|
||||
const directiveWithModel = {
|
||||
'Dir': `
|
||||
@Directive({
|
||||
selector: '[dir]',
|
||||
})
|
||||
export class Dir {
|
||||
twoWayValue = model<string>();
|
||||
}
|
||||
`
|
||||
};
|
||||
|
||||
it('should return completions for both properties and events', () => {
|
||||
const {templateFile} = setup(`<button dir ></button>`, ``, directiveWithModel);
|
||||
templateFile.moveCursorToText(`<button dir ¦>`);
|
||||
const completions = templateFile.getCompletionsAtPosition();
|
||||
|
||||
expectContain(completions, DisplayInfoKind.PROPERTY, ['[twoWayValue]']);
|
||||
expectContain(completions, DisplayInfoKind.PROPERTY, ['[(twoWayValue)]']);
|
||||
expectContain(completions, DisplayInfoKind.EVENT, ['(twoWayValueChange)']);
|
||||
});
|
||||
|
||||
it('should return property access completions in the property side of the binding', () => {
|
||||
const {templateFile} = setup(`<input dir [twoWayValue]="'foo'.">`, '', directiveWithModel);
|
||||
templateFile.moveCursorToText(`dir [twoWayValue]="'foo'.¦">`);
|
||||
|
||||
const completions = templateFile.getCompletionsAtPosition();
|
||||
expectContain(
|
||||
completions, ts.ScriptElementKind.memberFunctionElement,
|
||||
[`charAt`, 'toLowerCase', /* etc. */]);
|
||||
});
|
||||
|
||||
it('should return property access completions in the event side of the binding', () => {
|
||||
const {templateFile} =
|
||||
setup(`<input dir (twoWayValueChange)="'foo'.">`, '', directiveWithModel);
|
||||
templateFile.moveCursorToText(`dir (twoWayValueChange)="'foo'.¦">`);
|
||||
|
||||
const completions = templateFile.getCompletionsAtPosition();
|
||||
expectContain(
|
||||
completions, ts.ScriptElementKind.memberFunctionElement,
|
||||
[`charAt`, 'toLowerCase', /* etc. */]);
|
||||
});
|
||||
|
||||
it('should return property access completions in a two-way binding', () => {
|
||||
const {templateFile} = setup(`<input dir [(twoWayValue)]="'foo'.">`, '', directiveWithModel);
|
||||
templateFile.moveCursorToText(`dir [(twoWayValue)]="'foo'.¦">`);
|
||||
|
||||
const completions = templateFile.getCompletionsAtPosition();
|
||||
expectContain(
|
||||
completions, ts.ScriptElementKind.memberFunctionElement,
|
||||
[`charAt`, 'toLowerCase', /* etc. */]);
|
||||
});
|
||||
|
||||
it('should return completions of string literals, number literals, `true`, ' +
|
||||
'`false`, `null` and `undefined`',
|
||||
() => {
|
||||
const {templateFile} =
|
||||
setup(`<input dir (twoWayValueChange)="$event.">`, '', directiveWithModel);
|
||||
templateFile.moveCursorToText('dir (twoWayValueChange)="$event.¦">');
|
||||
|
||||
const completions = templateFile.getCompletionsAtPosition();
|
||||
|
||||
expectContain(
|
||||
completions, ts.ScriptElementKind.memberFunctionElement,
|
||||
[`charAt`, 'toLowerCase', /* etc. */]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('for blocks', () => {
|
||||
const completionPrefixes = ['@', '@i'];
|
||||
|
||||
@@ -1680,7 +1748,15 @@ function setup(
|
||||
const env = LanguageServiceTestEnv.setup();
|
||||
const project = env.addProject('test', {
|
||||
'test.ts': `
|
||||
import {Component, input, output, Directive, NgModule, Pipe, TemplateRef} from '@angular/core';
|
||||
import {Component,
|
||||
input,
|
||||
output,
|
||||
model,
|
||||
Directive,
|
||||
NgModule,
|
||||
Pipe,
|
||||
TemplateRef,
|
||||
} from '@angular/core';
|
||||
|
||||
${functionDeclarations}
|
||||
|
||||
|
||||
@@ -208,6 +208,103 @@ describe('definitions', () => {
|
||||
assertFileNames([def3, def2, def], ['dir3.ts', 'dir2.ts', 'dir.ts']);
|
||||
});
|
||||
|
||||
it('gets definitions for all model inputs when attribute matches more than one in a static attribute',
|
||||
() => {
|
||||
initMockFileSystem('Native');
|
||||
const files = {
|
||||
'app.ts': `
|
||||
import {Component, NgModule} from '@angular/core';
|
||||
import {CommonModule} from '@angular/common';
|
||||
|
||||
@Component({templateUrl: 'app.html'})
|
||||
export class AppCmp {}
|
||||
`,
|
||||
'app.html': '<div dir inputA="abc"></div>',
|
||||
'dir.ts': `
|
||||
import {Directive, model} from '@angular/core';
|
||||
|
||||
@Directive({selector: '[dir]'})
|
||||
export class MyDir {
|
||||
inputA = model('');
|
||||
}`,
|
||||
'dir2.ts': `
|
||||
import {Directive, model} from '@angular/core';
|
||||
|
||||
@Directive({selector: '[dir]'})
|
||||
export class MyDir2 {
|
||||
inputA = model('');
|
||||
}`
|
||||
|
||||
};
|
||||
const env = LanguageServiceTestEnv.setup();
|
||||
const project = createModuleAndProjectWithDeclarations(env, 'test', files);
|
||||
const template = project.openFile('app.html');
|
||||
template.moveCursorToText('inpu¦tA="abc"');
|
||||
|
||||
const {textSpan, definitions} = getDefinitionsAndAssertBoundSpan(env, template);
|
||||
expect(template.contents.slice(textSpan.start, textSpan.start + textSpan.length))
|
||||
.toBe('inputA');
|
||||
|
||||
expect(definitions.length).toBe(2);
|
||||
const [def, def2] = definitions;
|
||||
expect(def.textSpan).toContain('inputA');
|
||||
expect(def2.textSpan).toContain('inputA');
|
||||
// TODO(atscott): investigate why the text span includes more than just 'inputA'
|
||||
// assertTextSpans([def, def2], ['inputA']);
|
||||
assertFileNames([def, def2], ['dir2.ts', 'dir.ts']);
|
||||
});
|
||||
|
||||
it('gets definitions for all model inputs when attribute matches more than one in a two-way binding',
|
||||
() => {
|
||||
initMockFileSystem('Native');
|
||||
const files = {
|
||||
'app.ts': `
|
||||
import {Component, NgModule} from '@angular/core';
|
||||
import {CommonModule} from '@angular/common';
|
||||
|
||||
@Component({templateUrl: 'app.html'})
|
||||
export class AppCmp {
|
||||
value = 'abc';
|
||||
}
|
||||
`,
|
||||
'app.html': '<div dir [(inputA)]="value"></div>',
|
||||
'dir.ts': `
|
||||
import {Directive, model} from '@angular/core';
|
||||
|
||||
@Directive({selector: '[dir]'})
|
||||
export class MyDir {
|
||||
inputA = model('');
|
||||
}`,
|
||||
'dir2.ts': `
|
||||
import {Directive, model} from '@angular/core';
|
||||
|
||||
@Directive({selector: '[dir]'})
|
||||
export class MyDir2 {
|
||||
inputA = model('');
|
||||
}`
|
||||
|
||||
};
|
||||
const env = LanguageServiceTestEnv.setup();
|
||||
const project = createModuleAndProjectWithDeclarations(env, 'test', files);
|
||||
const template = project.openFile('app.html');
|
||||
template.moveCursorToText('[(inpu¦tA)]="value"');
|
||||
|
||||
const {textSpan, definitions} = getDefinitionsAndAssertBoundSpan(env, template);
|
||||
expect(template.contents.slice(textSpan.start, textSpan.start + textSpan.length))
|
||||
.toBe('inputA');
|
||||
|
||||
expect(definitions.length).toBe(4);
|
||||
const [def, def2, def3, def4] = definitions;
|
||||
|
||||
// We have four definitons to the `inputA` member, because each `model()`
|
||||
// instance implicitly creates both an input and an output.
|
||||
expect(def.textSpan).toContain('inputA');
|
||||
expect(def2.textSpan).toContain('inputA');
|
||||
expect(def3.textSpan).toContain('inputA');
|
||||
expect(def4.textSpan).toContain('inputA');
|
||||
assertFileNames([def, def2, def3, def4], ['dir2.ts', 'dir.ts', 'dir2.ts', 'dir.ts']);
|
||||
});
|
||||
|
||||
it('should go to the pre-compiled style sheet', () => {
|
||||
initMockFileSystem('Native');
|
||||
const files = {
|
||||
|
||||
@@ -83,7 +83,7 @@ describe('language service adapter', () => {
|
||||
});
|
||||
|
||||
it('should always disable block syntax if enableBlockSyntax is false', () => {
|
||||
const {project, tsLS, configFileFs} = setup();
|
||||
const {project, tsLS} = setup();
|
||||
const ngLS = new LanguageService(project, tsLS, {
|
||||
enableBlockSyntax: false,
|
||||
});
|
||||
@@ -92,6 +92,17 @@ describe('language service adapter', () => {
|
||||
'_enableBlockSyntax': false,
|
||||
}));
|
||||
});
|
||||
|
||||
it('should pass the @angular/core version along to the compiler', () => {
|
||||
const {project, tsLS} = setup();
|
||||
const ngLS = new LanguageService(project, tsLS, {
|
||||
angularCoreVersion: '17.2.11-rc.8',
|
||||
});
|
||||
|
||||
expect(ngLS.getCompilerOptions()).toEqual(jasmine.objectContaining({
|
||||
'_angularCoreVersion': '17.2.11-rc.8',
|
||||
}));
|
||||
});
|
||||
});
|
||||
|
||||
describe('compiler options diagnostics', () => {
|
||||
|
||||
@@ -14,7 +14,7 @@ import {createModuleAndProjectWithDeclarations, LanguageServiceTestEnv, Project}
|
||||
function quickInfoSkeleton(): {[fileName: string]: string} {
|
||||
return {
|
||||
'app.ts': `
|
||||
import {Component, Directive, EventEmitter, Input, NgModule, Output, Pipe, PipeTransform} from '@angular/core';
|
||||
import {Component, Directive, EventEmitter, Input, NgModule, Output, Pipe, PipeTransform, model} from '@angular/core';
|
||||
import {CommonModule} from '@angular/common';
|
||||
|
||||
export interface Address {
|
||||
@@ -71,6 +71,14 @@ function quickInfoSkeleton(): {[fileName: string]: string} {
|
||||
@Output() modelChange!: EventEmitter<string>;
|
||||
}
|
||||
|
||||
@Directive({
|
||||
selector: '[signal-model]',
|
||||
exportAs: 'signalModel',
|
||||
})
|
||||
export class SignalModel {
|
||||
signalModel = model<string>();
|
||||
}
|
||||
|
||||
@Directive({selector: 'button[custom-button][compound]'})
|
||||
export class CompoundCustomButtonDirective {
|
||||
@Input() config?: {color?: string};
|
||||
@@ -82,6 +90,7 @@ function quickInfoSkeleton(): {[fileName: string]: string} {
|
||||
CompoundCustomButtonDirective,
|
||||
StringModel,
|
||||
TestComponent,
|
||||
SignalModel,
|
||||
],
|
||||
imports: [
|
||||
CommonModule,
|
||||
@@ -237,6 +246,15 @@ describe('quick info', () => {
|
||||
expectedDisplayString: '(property) StringModel.model: string'
|
||||
});
|
||||
});
|
||||
|
||||
it('should work for signal-based two-way binding providers', () => {
|
||||
expectQuickInfo({
|
||||
templateOverride: `<test-comp signal-model [(signa¦lModel)]="title"></test-comp>`,
|
||||
expectedSpanText: 'signalModel',
|
||||
expectedDisplayString:
|
||||
'(property) SignalModel.signalModel: ModelSignal<string | undefined>'
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('outputs', () => {
|
||||
|
||||
@@ -1285,6 +1285,35 @@ describe('find references and rename locations', () => {
|
||||
assertTextSpans(refs, ['model', 'modelChange']);
|
||||
});
|
||||
|
||||
it('should get references to model() input binding', () => {
|
||||
const files = {
|
||||
'dir.ts': `
|
||||
import {Directive, model} from '@angular/core';
|
||||
|
||||
@Directive({selector: '[signal-model]'})
|
||||
export class SignalModel {
|
||||
signalModel = model<string>();
|
||||
}`,
|
||||
'app.ts': `
|
||||
import {Component} from '@angular/core';
|
||||
|
||||
@Component({template: '<div signal-model [(signalModel)]="title"></div>'})
|
||||
export class AppCmp {
|
||||
title = 'title';
|
||||
}`
|
||||
};
|
||||
|
||||
env = LanguageServiceTestEnv.setup();
|
||||
const project = createModuleAndProjectWithDeclarations(env, 'test', files);
|
||||
const file = project.openFile('app.ts');
|
||||
file.moveCursorToText('[(signal¦Model)]');
|
||||
|
||||
const refs = getReferencesAtPosition(file)!;
|
||||
expect(refs.length).toBe(2);
|
||||
assertFileNames(refs, ['dir.ts', 'app.ts']);
|
||||
assertTextSpans(refs, ['signalModel']);
|
||||
});
|
||||
|
||||
describe('directives', () => {
|
||||
describe('when cursor is on the directive class', () => {
|
||||
let file: OpenBuffer;
|
||||
@@ -1513,7 +1542,7 @@ describe('find references and rename locations', () => {
|
||||
import {Component} from '@angular/core';
|
||||
|
||||
@Component({
|
||||
template: '@if (x; as aliasX) { {{aliasX}} {{aliasX + "second"}} }',
|
||||
template: '@if (x; as aliasX) { {{aliasX}} {{aliasX + "second"}} }',
|
||||
standalone: true
|
||||
})
|
||||
export class AppCmp {
|
||||
|
||||
@@ -106,7 +106,70 @@ describe('type definitions', () => {
|
||||
project, {templateOverride: `<my-dir (name¦Changes)="doSmth()" />`});
|
||||
expect(definitions!.length).toEqual(1);
|
||||
|
||||
assertTextSpans(definitions, ['EventEmitter']);
|
||||
assertTextSpans(definitions, ['OutputEmitter']);
|
||||
assertFileNames(definitions, ['index.d.ts']);
|
||||
});
|
||||
});
|
||||
|
||||
describe('model inputs', () => {
|
||||
const files = {
|
||||
'app.ts': `
|
||||
import {Component, Directive, model} from '@angular/core';
|
||||
|
||||
@Directive({
|
||||
selector: 'my-dir',
|
||||
standalone: true
|
||||
})
|
||||
export class MyDir {
|
||||
twoWayValue = model<string>();
|
||||
}
|
||||
|
||||
@Component({
|
||||
templateUrl: 'app.html',
|
||||
standalone: true,
|
||||
imports: [MyDir],
|
||||
})
|
||||
export class AppCmp {
|
||||
noop() {}
|
||||
value = 'hello';
|
||||
}
|
||||
`,
|
||||
'app.html': `Will be overridden`,
|
||||
};
|
||||
|
||||
it('should return the definition for the property side of a two-way binding', () => {
|
||||
initMockFileSystem('Native');
|
||||
env = LanguageServiceTestEnv.setup();
|
||||
const project = env.addProject('test', files);
|
||||
const definitions = getTypeDefinitionsAndAssertBoundSpan(
|
||||
project, {templateOverride: `<my-dir [twoWa¦yValue]="value" />`});
|
||||
|
||||
expect(definitions.length).toBe(1);
|
||||
assertTextSpans(definitions, ['ModelSignal']);
|
||||
assertFileNames(definitions, ['index.d.ts']);
|
||||
});
|
||||
|
||||
it('should return the definition for the event side of a two-way binding', () => {
|
||||
initMockFileSystem('Native');
|
||||
env = LanguageServiceTestEnv.setup();
|
||||
const project = env.addProject('test', files);
|
||||
const definitions = getTypeDefinitionsAndAssertBoundSpan(
|
||||
project, {templateOverride: `<my-dir (twoWayV¦alueChange)="noop()" />`});
|
||||
|
||||
expect(definitions.length).toBe(1);
|
||||
assertTextSpans(definitions, ['ModelSignal']);
|
||||
assertFileNames(definitions, ['index.d.ts']);
|
||||
});
|
||||
|
||||
it('should return the definition of a two-way binding', () => {
|
||||
initMockFileSystem('Native');
|
||||
env = LanguageServiceTestEnv.setup();
|
||||
const project = env.addProject('test', files);
|
||||
const definitions = getTypeDefinitionsAndAssertBoundSpan(
|
||||
project, {templateOverride: `<my-dir [(twoWa¦yValue)]="value" />`});
|
||||
|
||||
expect(definitions.length).toBe(1);
|
||||
assertTextSpans(definitions, ['ModelSignal']);
|
||||
assertFileNames(definitions, ['index.d.ts']);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -16,9 +16,8 @@ import {navigationCancelingError} from './navigation_canceling_error';
|
||||
import {Params, PRIMARY_OUTLET} from './shared';
|
||||
import {UrlSegment, UrlSegmentGroup, UrlSerializer, UrlTree} from './url_tree';
|
||||
|
||||
|
||||
export class NoMatch {
|
||||
public segmentGroup: UrlSegmentGroup|null;
|
||||
public segmentGroup: UrlSegmentGroup | null;
|
||||
|
||||
constructor(segmentGroup?: UrlSegmentGroup) {
|
||||
this.segmentGroup = segmentGroup || null;
|
||||
@@ -40,23 +39,30 @@ export function absoluteRedirect(newTree: UrlTree): Observable<any> {
|
||||
}
|
||||
|
||||
export function namedOutletsRedirect(redirectTo: string): Observable<any> {
|
||||
return throwError(new RuntimeError(
|
||||
return throwError(
|
||||
new RuntimeError(
|
||||
RuntimeErrorCode.NAMED_OUTLET_REDIRECT,
|
||||
(typeof ngDevMode === 'undefined' || ngDevMode) &&
|
||||
`Only absolute redirects can have named outlets. redirectTo: '${redirectTo}'`));
|
||||
`Only absolute redirects can have named outlets. redirectTo: '${redirectTo}'`,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
export function canLoadFails(route: Route): Observable<LoadedRouterConfig> {
|
||||
return throwError(navigationCancelingError(
|
||||
return throwError(
|
||||
navigationCancelingError(
|
||||
(typeof ngDevMode === 'undefined' || ngDevMode) &&
|
||||
`Cannot load children because the guard of the route "path: '${
|
||||
route.path}'" returned false`,
|
||||
NavigationCancellationCode.GuardRejected));
|
||||
`Cannot load children because the guard of the route "path: '${route.path}'" returned false`,
|
||||
NavigationCancellationCode.GuardRejected,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
export class ApplyRedirects {
|
||||
constructor(private urlSerializer: UrlSerializer, private urlTree: UrlTree) {}
|
||||
constructor(
|
||||
private urlSerializer: UrlSerializer,
|
||||
private urlTree: UrlTree,
|
||||
) {}
|
||||
|
||||
lineralizeSegments(route: Route, urlTree: UrlTree): Observable<UrlSegment[]> {
|
||||
let res: UrlSegment[] = [];
|
||||
@@ -76,9 +82,16 @@ export class ApplyRedirects {
|
||||
}
|
||||
|
||||
applyRedirectCommands(
|
||||
segments: UrlSegment[], redirectTo: string, posParams: {[k: string]: UrlSegment}): UrlTree {
|
||||
segments: UrlSegment[],
|
||||
redirectTo: string,
|
||||
posParams: {[k: string]: UrlSegment},
|
||||
): UrlTree {
|
||||
const newTree = this.applyRedirectCreateUrlTree(
|
||||
redirectTo, this.urlSerializer.parse(redirectTo), segments, posParams);
|
||||
redirectTo,
|
||||
this.urlSerializer.parse(redirectTo),
|
||||
segments,
|
||||
posParams,
|
||||
);
|
||||
if (redirectTo.startsWith('/')) {
|
||||
throw new AbsoluteRedirect(newTree);
|
||||
}
|
||||
@@ -86,12 +99,17 @@ export class ApplyRedirects {
|
||||
}
|
||||
|
||||
applyRedirectCreateUrlTree(
|
||||
redirectTo: string, urlTree: UrlTree, segments: UrlSegment[],
|
||||
posParams: {[k: string]: UrlSegment}): UrlTree {
|
||||
redirectTo: string,
|
||||
urlTree: UrlTree,
|
||||
segments: UrlSegment[],
|
||||
posParams: {[k: string]: UrlSegment},
|
||||
): UrlTree {
|
||||
const newRoot = this.createSegmentGroup(redirectTo, urlTree.root, segments, posParams);
|
||||
return new UrlTree(
|
||||
newRoot, this.createQueryParams(urlTree.queryParams, this.urlTree.queryParams),
|
||||
urlTree.fragment);
|
||||
newRoot,
|
||||
this.createQueryParams(urlTree.queryParams, this.urlTree.queryParams),
|
||||
urlTree.fragment,
|
||||
);
|
||||
}
|
||||
|
||||
createQueryParams(redirectToParams: Params, actualParams: Params): Params {
|
||||
@@ -109,8 +127,11 @@ export class ApplyRedirects {
|
||||
}
|
||||
|
||||
createSegmentGroup(
|
||||
redirectTo: string, group: UrlSegmentGroup, segments: UrlSegment[],
|
||||
posParams: {[k: string]: UrlSegment}): UrlSegmentGroup {
|
||||
redirectTo: string,
|
||||
group: UrlSegmentGroup,
|
||||
segments: UrlSegment[],
|
||||
posParams: {[k: string]: UrlSegment},
|
||||
): UrlSegmentGroup {
|
||||
const updatedSegments = this.createSegments(redirectTo, group.segments, segments, posParams);
|
||||
|
||||
let children: {[n: string]: UrlSegmentGroup} = {};
|
||||
@@ -122,22 +143,30 @@ export class ApplyRedirects {
|
||||
}
|
||||
|
||||
createSegments(
|
||||
redirectTo: string, redirectToSegments: UrlSegment[], actualSegments: UrlSegment[],
|
||||
posParams: {[k: string]: UrlSegment}): UrlSegment[] {
|
||||
return redirectToSegments.map(
|
||||
s => s.path.startsWith(':') ? this.findPosParam(redirectTo, s, posParams) :
|
||||
this.findOrReturn(s, actualSegments));
|
||||
redirectTo: string,
|
||||
redirectToSegments: UrlSegment[],
|
||||
actualSegments: UrlSegment[],
|
||||
posParams: {[k: string]: UrlSegment},
|
||||
): UrlSegment[] {
|
||||
return redirectToSegments.map((s) =>
|
||||
s.path.startsWith(':')
|
||||
? this.findPosParam(redirectTo, s, posParams)
|
||||
: this.findOrReturn(s, actualSegments),
|
||||
);
|
||||
}
|
||||
|
||||
findPosParam(
|
||||
redirectTo: string, redirectToUrlSegment: UrlSegment,
|
||||
posParams: {[k: string]: UrlSegment}): UrlSegment {
|
||||
redirectTo: string,
|
||||
redirectToUrlSegment: UrlSegment,
|
||||
posParams: {[k: string]: UrlSegment},
|
||||
): UrlSegment {
|
||||
const pos = posParams[redirectToUrlSegment.path.substring(1)];
|
||||
if (!pos)
|
||||
throw new RuntimeError(
|
||||
RuntimeErrorCode.MISSING_REDIRECT,
|
||||
(typeof ngDevMode === 'undefined' || ngDevMode) &&
|
||||
`Cannot redirect to '${redirectTo}'. Cannot find '${redirectToUrlSegment.path}'.`);
|
||||
RuntimeErrorCode.MISSING_REDIRECT,
|
||||
(typeof ngDevMode === 'undefined' || ngDevMode) &&
|
||||
`Cannot redirect to '${redirectTo}'. Cannot find '${redirectToUrlSegment.path}'.`,
|
||||
);
|
||||
return pos;
|
||||
}
|
||||
|
||||
|
||||
@@ -24,7 +24,6 @@ import {RouterOutlet} from '../directives/router_outlet';
|
||||
imports: [RouterOutlet],
|
||||
standalone: true,
|
||||
})
|
||||
export class ɵEmptyOutletComponent {
|
||||
}
|
||||
export class ɵEmptyOutletComponent {}
|
||||
|
||||
export {ɵEmptyOutletComponent as EmptyOutletComponent};
|
||||
|
||||
@@ -9,19 +9,28 @@
|
||||
import {BehaviorSubject} from 'rxjs';
|
||||
|
||||
import {DetachedRouteHandleInternal, RouteReuseStrategy} from './route_reuse_strategy';
|
||||
import {ActivatedRoute, ActivatedRouteSnapshot, RouterState, RouterStateSnapshot} from './router_state';
|
||||
import {
|
||||
ActivatedRoute,
|
||||
ActivatedRouteSnapshot,
|
||||
RouterState,
|
||||
RouterStateSnapshot,
|
||||
} from './router_state';
|
||||
import {TreeNode} from './utils/tree';
|
||||
|
||||
export function createRouterState(
|
||||
routeReuseStrategy: RouteReuseStrategy, curr: RouterStateSnapshot,
|
||||
prevState: RouterState): RouterState {
|
||||
routeReuseStrategy: RouteReuseStrategy,
|
||||
curr: RouterStateSnapshot,
|
||||
prevState: RouterState,
|
||||
): RouterState {
|
||||
const root = createNode(routeReuseStrategy, curr._root, prevState ? prevState._root : undefined);
|
||||
return new RouterState(root, curr);
|
||||
}
|
||||
|
||||
function createNode(
|
||||
routeReuseStrategy: RouteReuseStrategy, curr: TreeNode<ActivatedRouteSnapshot>,
|
||||
prevState?: TreeNode<ActivatedRoute>): TreeNode<ActivatedRoute> {
|
||||
routeReuseStrategy: RouteReuseStrategy,
|
||||
curr: TreeNode<ActivatedRouteSnapshot>,
|
||||
prevState?: TreeNode<ActivatedRoute>,
|
||||
): TreeNode<ActivatedRoute> {
|
||||
// reuse an activated route that is currently displayed on the screen
|
||||
if (prevState && routeReuseStrategy.shouldReuseRoute(curr.value, prevState.value.snapshot)) {
|
||||
const value = prevState.value;
|
||||
@@ -35,21 +44,23 @@ function createNode(
|
||||
if (detachedRouteHandle !== null) {
|
||||
const tree = (detachedRouteHandle as DetachedRouteHandleInternal).route;
|
||||
tree.value._futureSnapshot = curr.value;
|
||||
tree.children = curr.children.map(c => createNode(routeReuseStrategy, c));
|
||||
tree.children = curr.children.map((c) => createNode(routeReuseStrategy, c));
|
||||
return tree;
|
||||
}
|
||||
}
|
||||
|
||||
const value = createActivatedRoute(curr.value);
|
||||
const children = curr.children.map(c => createNode(routeReuseStrategy, c));
|
||||
const children = curr.children.map((c) => createNode(routeReuseStrategy, c));
|
||||
return new TreeNode<ActivatedRoute>(value, children);
|
||||
}
|
||||
}
|
||||
|
||||
function createOrReuseChildren(
|
||||
routeReuseStrategy: RouteReuseStrategy, curr: TreeNode<ActivatedRouteSnapshot>,
|
||||
prevState: TreeNode<ActivatedRoute>) {
|
||||
return curr.children.map(child => {
|
||||
routeReuseStrategy: RouteReuseStrategy,
|
||||
curr: TreeNode<ActivatedRouteSnapshot>,
|
||||
prevState: TreeNode<ActivatedRoute>,
|
||||
) {
|
||||
return curr.children.map((child) => {
|
||||
for (const p of prevState.children) {
|
||||
if (routeReuseStrategy.shouldReuseRoute(child.value, p.value.snapshot)) {
|
||||
return createNode(routeReuseStrategy, child, p);
|
||||
@@ -61,6 +72,13 @@ function createOrReuseChildren(
|
||||
|
||||
function createActivatedRoute(c: ActivatedRouteSnapshot) {
|
||||
return new ActivatedRoute(
|
||||
new BehaviorSubject(c.url), new BehaviorSubject(c.params), new BehaviorSubject(c.queryParams),
|
||||
new BehaviorSubject(c.fragment), new BehaviorSubject(c.data), c.outlet, c.component, c);
|
||||
new BehaviorSubject(c.url),
|
||||
new BehaviorSubject(c.params),
|
||||
new BehaviorSubject(c.queryParams),
|
||||
new BehaviorSubject(c.fragment),
|
||||
new BehaviorSubject(c.data),
|
||||
c.outlet,
|
||||
c.component,
|
||||
c,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -14,7 +14,6 @@ import {Params, PRIMARY_OUTLET} from './shared';
|
||||
import {createRoot, squashSegmentGroup, UrlSegment, UrlSegmentGroup, UrlTree} from './url_tree';
|
||||
import {last, shallowEqual} from './utils/collection';
|
||||
|
||||
|
||||
/**
|
||||
* Creates a `UrlTree` relative to an `ActivatedRouteSnapshot`.
|
||||
*
|
||||
@@ -67,17 +66,21 @@ import {last, shallowEqual} from './utils/collection';
|
||||
* ```
|
||||
*/
|
||||
export function createUrlTreeFromSnapshot(
|
||||
relativeTo: ActivatedRouteSnapshot, commands: any[], queryParams: Params|null = null,
|
||||
fragment: string|null = null): UrlTree {
|
||||
relativeTo: ActivatedRouteSnapshot,
|
||||
commands: any[],
|
||||
queryParams: Params | null = null,
|
||||
fragment: string | null = null,
|
||||
): UrlTree {
|
||||
const relativeToUrlSegmentGroup = createSegmentGroupFromRoute(relativeTo);
|
||||
return createUrlTreeFromSegmentGroup(relativeToUrlSegmentGroup, commands, queryParams, fragment);
|
||||
}
|
||||
|
||||
export function createSegmentGroupFromRoute(route: ActivatedRouteSnapshot): UrlSegmentGroup {
|
||||
let targetGroup: UrlSegmentGroup|undefined;
|
||||
let targetGroup: UrlSegmentGroup | undefined;
|
||||
|
||||
function createSegmentGroupFromRouteRecursive(currentRoute: ActivatedRouteSnapshot):
|
||||
UrlSegmentGroup {
|
||||
function createSegmentGroupFromRouteRecursive(
|
||||
currentRoute: ActivatedRouteSnapshot,
|
||||
): UrlSegmentGroup {
|
||||
const childOutlets: {[outlet: string]: UrlSegmentGroup} = {};
|
||||
for (const childSnapshot of currentRoute.children) {
|
||||
const root = createSegmentGroupFromRouteRecursive(childSnapshot);
|
||||
@@ -96,8 +99,11 @@ export function createSegmentGroupFromRoute(route: ActivatedRouteSnapshot): UrlS
|
||||
}
|
||||
|
||||
export function createUrlTreeFromSegmentGroup(
|
||||
relativeTo: UrlSegmentGroup, commands: any[], queryParams: Params|null,
|
||||
fragment: string|null): UrlTree {
|
||||
relativeTo: UrlSegmentGroup,
|
||||
commands: any[],
|
||||
queryParams: Params | null,
|
||||
fragment: string | null,
|
||||
): UrlTree {
|
||||
let root = relativeTo;
|
||||
while (root.parent) {
|
||||
root = root.parent;
|
||||
@@ -116,9 +122,9 @@ export function createUrlTreeFromSegmentGroup(
|
||||
}
|
||||
|
||||
const position = findStartingPositionForTargetGroup(nav, root, relativeTo);
|
||||
const newSegmentGroup = position.processChildren ?
|
||||
updateSegmentGroupChildren(position.segmentGroup, position.index, nav.commands) :
|
||||
updateSegmentGroup(position.segmentGroup, position.index, nav.commands);
|
||||
const newSegmentGroup = position.processChildren
|
||||
? updateSegmentGroupChildren(position.segmentGroup, position.index, nav.commands)
|
||||
: updateSegmentGroup(position.segmentGroup, position.index, nav.commands);
|
||||
return tree(root, position.segmentGroup, newSegmentGroup, queryParams, fragment);
|
||||
}
|
||||
|
||||
@@ -135,8 +141,12 @@ function isCommandWithOutlets(command: any): command is {outlets: {[key: string]
|
||||
}
|
||||
|
||||
function tree(
|
||||
oldRoot: UrlSegmentGroup, oldSegmentGroup: UrlSegmentGroup, newSegmentGroup: UrlSegmentGroup,
|
||||
queryParams: Params|null, fragment: string|null): UrlTree {
|
||||
oldRoot: UrlSegmentGroup,
|
||||
oldSegmentGroup: UrlSegmentGroup,
|
||||
newSegmentGroup: UrlSegmentGroup,
|
||||
queryParams: Params | null,
|
||||
fragment: string | null,
|
||||
): UrlTree {
|
||||
let qp: any = {};
|
||||
if (queryParams) {
|
||||
Object.entries(queryParams).forEach(([name, value]) => {
|
||||
@@ -163,8 +173,10 @@ function tree(
|
||||
* value.
|
||||
*/
|
||||
function replaceSegment(
|
||||
current: UrlSegmentGroup, oldSegment: UrlSegmentGroup,
|
||||
newSegment: UrlSegmentGroup): UrlSegmentGroup {
|
||||
current: UrlSegmentGroup,
|
||||
oldSegment: UrlSegmentGroup,
|
||||
newSegment: UrlSegmentGroup,
|
||||
): UrlSegmentGroup {
|
||||
const children: {[key: string]: UrlSegmentGroup} = {};
|
||||
Object.entries(current.children).forEach(([outletName, c]) => {
|
||||
if (c === oldSegment) {
|
||||
@@ -178,20 +190,25 @@ function replaceSegment(
|
||||
|
||||
class Navigation {
|
||||
constructor(
|
||||
public isAbsolute: boolean, public numberOfDoubleDots: number, public commands: any[]) {
|
||||
public isAbsolute: boolean,
|
||||
public numberOfDoubleDots: number,
|
||||
public commands: any[],
|
||||
) {
|
||||
if (isAbsolute && commands.length > 0 && isMatrixParams(commands[0])) {
|
||||
throw new RuntimeError(
|
||||
RuntimeErrorCode.ROOT_SEGMENT_MATRIX_PARAMS,
|
||||
(typeof ngDevMode === 'undefined' || ngDevMode) &&
|
||||
'Root segment cannot have matrix parameters');
|
||||
RuntimeErrorCode.ROOT_SEGMENT_MATRIX_PARAMS,
|
||||
(typeof ngDevMode === 'undefined' || ngDevMode) &&
|
||||
'Root segment cannot have matrix parameters',
|
||||
);
|
||||
}
|
||||
|
||||
const cmdWithOutlet = commands.find(isCommandWithOutlets);
|
||||
if (cmdWithOutlet && cmdWithOutlet !== last(commands)) {
|
||||
throw new RuntimeError(
|
||||
RuntimeErrorCode.MISPLACED_OUTLETS_COMMAND,
|
||||
(typeof ngDevMode === 'undefined' || ngDevMode) &&
|
||||
'{outlets:{}} has to be the last command');
|
||||
RuntimeErrorCode.MISPLACED_OUTLETS_COMMAND,
|
||||
(typeof ngDevMode === 'undefined' || ngDevMode) &&
|
||||
'{outlets:{}} has to be the last command',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -202,7 +219,7 @@ class Navigation {
|
||||
|
||||
/** Transforms commands to a normalized `Navigation` */
|
||||
function computeNavigation(commands: any[]): Navigation {
|
||||
if ((typeof commands[0] === 'string') && commands.length === 1 && commands[0] === '/') {
|
||||
if (typeof commands[0] === 'string' && commands.length === 1 && commands[0] === '/') {
|
||||
return new Navigation(true, 0, commands);
|
||||
}
|
||||
|
||||
@@ -232,9 +249,11 @@ function computeNavigation(commands: any[]): Navigation {
|
||||
cmd.split('/').forEach((urlPart, partIndex) => {
|
||||
if (partIndex == 0 && urlPart === '.') {
|
||||
// skip './a'
|
||||
} else if (partIndex == 0 && urlPart === '') { // '/a'
|
||||
} else if (partIndex == 0 && urlPart === '') {
|
||||
// '/a'
|
||||
isAbsolute = true;
|
||||
} else if (urlPart === '..') { // '../a'
|
||||
} else if (urlPart === '..') {
|
||||
// '../a'
|
||||
numberOfDoubleDots++;
|
||||
} else if (urlPart != '') {
|
||||
res.push(urlPart);
|
||||
@@ -252,12 +271,17 @@ function computeNavigation(commands: any[]): Navigation {
|
||||
|
||||
class Position {
|
||||
constructor(
|
||||
public segmentGroup: UrlSegmentGroup, public processChildren: boolean, public index: number) {
|
||||
}
|
||||
public segmentGroup: UrlSegmentGroup,
|
||||
public processChildren: boolean,
|
||||
public index: number,
|
||||
) {}
|
||||
}
|
||||
|
||||
function findStartingPositionForTargetGroup(
|
||||
nav: Navigation, root: UrlSegmentGroup, target: UrlSegmentGroup): Position {
|
||||
nav: Navigation,
|
||||
root: UrlSegmentGroup,
|
||||
target: UrlSegmentGroup,
|
||||
): Position {
|
||||
if (nav.isAbsolute) {
|
||||
return new Position(root, true, 0);
|
||||
}
|
||||
@@ -279,7 +303,10 @@ function findStartingPositionForTargetGroup(
|
||||
}
|
||||
|
||||
function createPositionApplyingDoubleDots(
|
||||
group: UrlSegmentGroup, index: number, numberOfDoubleDots: number): Position {
|
||||
group: UrlSegmentGroup,
|
||||
index: number,
|
||||
numberOfDoubleDots: number,
|
||||
): Position {
|
||||
let g = group;
|
||||
let ci = index;
|
||||
let dd = numberOfDoubleDots;
|
||||
@@ -288,15 +315,16 @@ function createPositionApplyingDoubleDots(
|
||||
g = g.parent!;
|
||||
if (!g) {
|
||||
throw new RuntimeError(
|
||||
RuntimeErrorCode.INVALID_DOUBLE_DOTS,
|
||||
(typeof ngDevMode === 'undefined' || ngDevMode) && 'Invalid number of \'../\'');
|
||||
RuntimeErrorCode.INVALID_DOUBLE_DOTS,
|
||||
(typeof ngDevMode === 'undefined' || ngDevMode) && "Invalid number of '../'",
|
||||
);
|
||||
}
|
||||
ci = g.segments.length;
|
||||
}
|
||||
return new Position(g, false, ci - dd);
|
||||
}
|
||||
|
||||
function getOutlets(commands: unknown[]): {[k: string]: unknown[]|string} {
|
||||
function getOutlets(commands: unknown[]): {[k: string]: unknown[] | string} {
|
||||
if (isCommandWithOutlets(commands[0])) {
|
||||
return commands[0].outlets;
|
||||
}
|
||||
@@ -305,7 +333,10 @@ function getOutlets(commands: unknown[]): {[k: string]: unknown[]|string} {
|
||||
}
|
||||
|
||||
function updateSegmentGroup(
|
||||
segmentGroup: UrlSegmentGroup|undefined, startIndex: number, commands: any[]): UrlSegmentGroup {
|
||||
segmentGroup: UrlSegmentGroup | undefined,
|
||||
startIndex: number,
|
||||
commands: any[],
|
||||
): UrlSegmentGroup {
|
||||
segmentGroup ??= new UrlSegmentGroup([], {});
|
||||
if (segmentGroup.segments.length === 0 && segmentGroup.hasChildren()) {
|
||||
return updateSegmentGroupChildren(segmentGroup, startIndex, commands);
|
||||
@@ -315,8 +346,10 @@ function updateSegmentGroup(
|
||||
const slicedCommands = commands.slice(m.commandIndex);
|
||||
if (m.match && m.pathIndex < segmentGroup.segments.length) {
|
||||
const g = new UrlSegmentGroup(segmentGroup.segments.slice(0, m.pathIndex), {});
|
||||
g.children[PRIMARY_OUTLET] =
|
||||
new UrlSegmentGroup(segmentGroup.segments.slice(m.pathIndex), segmentGroup.children);
|
||||
g.children[PRIMARY_OUTLET] = new UrlSegmentGroup(
|
||||
segmentGroup.segments.slice(m.pathIndex),
|
||||
segmentGroup.children,
|
||||
);
|
||||
return updateSegmentGroupChildren(g, 0, slicedCommands);
|
||||
} else if (m.match && slicedCommands.length === 0) {
|
||||
return new UrlSegmentGroup(segmentGroup.segments, {});
|
||||
@@ -330,7 +363,10 @@ function updateSegmentGroup(
|
||||
}
|
||||
|
||||
function updateSegmentGroupChildren(
|
||||
segmentGroup: UrlSegmentGroup, startIndex: number, commands: any[]): UrlSegmentGroup {
|
||||
segmentGroup: UrlSegmentGroup,
|
||||
startIndex: number,
|
||||
commands: any[],
|
||||
): UrlSegmentGroup {
|
||||
if (commands.length === 0) {
|
||||
return new UrlSegmentGroup(segmentGroup.segments, {});
|
||||
} else {
|
||||
@@ -357,11 +393,17 @@ function updateSegmentGroupChildren(
|
||||
// `UrlSegmentGroup` that is created from an "unsquashed"/expanded `ActivatedRoute` tree.
|
||||
// This code effectively "squashes" empty path primary routes when they have no siblings on
|
||||
// the same level of the tree.
|
||||
if (Object.keys(outlets).some(o => o !== PRIMARY_OUTLET) &&
|
||||
segmentGroup.children[PRIMARY_OUTLET] && segmentGroup.numberOfChildren === 1 &&
|
||||
segmentGroup.children[PRIMARY_OUTLET].segments.length === 0) {
|
||||
const childrenOfEmptyChild =
|
||||
updateSegmentGroupChildren(segmentGroup.children[PRIMARY_OUTLET], startIndex, commands);
|
||||
if (
|
||||
Object.keys(outlets).some((o) => o !== PRIMARY_OUTLET) &&
|
||||
segmentGroup.children[PRIMARY_OUTLET] &&
|
||||
segmentGroup.numberOfChildren === 1 &&
|
||||
segmentGroup.children[PRIMARY_OUTLET].segments.length === 0
|
||||
) {
|
||||
const childrenOfEmptyChild = updateSegmentGroupChildren(
|
||||
segmentGroup.children[PRIMARY_OUTLET],
|
||||
startIndex,
|
||||
commands,
|
||||
);
|
||||
return new UrlSegmentGroup(segmentGroup.segments, childrenOfEmptyChild.children);
|
||||
}
|
||||
|
||||
@@ -400,11 +442,11 @@ function prefixedWith(segmentGroup: UrlSegmentGroup, startIndex: number, command
|
||||
}
|
||||
const curr = `${command}`;
|
||||
const next =
|
||||
currentCommandIndex < commands.length - 1 ? commands[currentCommandIndex + 1] : null;
|
||||
currentCommandIndex < commands.length - 1 ? commands[currentCommandIndex + 1] : null;
|
||||
|
||||
if (currentPathIndex > 0 && curr === undefined) break;
|
||||
|
||||
if (curr && next && (typeof next === 'object') && next.outlets === undefined) {
|
||||
if (curr && next && typeof next === 'object' && next.outlets === undefined) {
|
||||
if (!compare(curr, next, path)) return noMatch;
|
||||
currentCommandIndex += 2;
|
||||
} else {
|
||||
@@ -418,7 +460,10 @@ function prefixedWith(segmentGroup: UrlSegmentGroup, startIndex: number, command
|
||||
}
|
||||
|
||||
function createNewSegmentGroup(
|
||||
segmentGroup: UrlSegmentGroup, startIndex: number, commands: any[]): UrlSegmentGroup {
|
||||
segmentGroup: UrlSegmentGroup,
|
||||
startIndex: number,
|
||||
commands: any[],
|
||||
): UrlSegmentGroup {
|
||||
const paths = segmentGroup.segments.slice(0, startIndex);
|
||||
|
||||
let i = 0;
|
||||
@@ -438,7 +483,7 @@ function createNewSegmentGroup(
|
||||
}
|
||||
|
||||
const curr = isCommandWithOutlets(command) ? command.outlets[PRIMARY_OUTLET] : `${command}`;
|
||||
const next = (i < commands.length - 1) ? commands[i + 1] : null;
|
||||
const next = i < commands.length - 1 ? commands[i + 1] : null;
|
||||
if (curr && next && isMatrixParams(next)) {
|
||||
paths.push(new UrlSegment(curr, stringify(next)));
|
||||
i += 2;
|
||||
@@ -450,8 +495,9 @@ function createNewSegmentGroup(
|
||||
return new UrlSegmentGroup(paths, {});
|
||||
}
|
||||
|
||||
function createNewSegmentChildren(outlets: {[name: string]: unknown[]|string}):
|
||||
{[outlet: string]: UrlSegmentGroup} {
|
||||
function createNewSegmentChildren(outlets: {[name: string]: unknown[] | string}): {
|
||||
[outlet: string]: UrlSegmentGroup;
|
||||
} {
|
||||
const children: {[outlet: string]: UrlSegmentGroup} = {};
|
||||
Object.entries(outlets).forEach(([outlet, commands]) => {
|
||||
if (typeof commands === 'string') {
|
||||
@@ -466,7 +512,7 @@ function createNewSegmentChildren(outlets: {[name: string]: unknown[]|string}):
|
||||
|
||||
function stringify(params: {[key: string]: any}): {[key: string]: string} {
|
||||
const res: {[key: string]: string} = {};
|
||||
Object.entries(params).forEach(([k, v]) => res[k] = `${v}`);
|
||||
Object.entries(params).forEach(([k, v]) => (res[k] = `${v}`));
|
||||
return res;
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,20 @@
|
||||
*/
|
||||
|
||||
import {LocationStrategy} from '@angular/common';
|
||||
import {Attribute, booleanAttribute, Directive, ElementRef, HostBinding, HostListener, Input, OnChanges, OnDestroy, Renderer2, SimpleChanges, ɵɵsanitizeUrlOrResourceUrl} from '@angular/core';
|
||||
import {
|
||||
Attribute,
|
||||
booleanAttribute,
|
||||
Directive,
|
||||
ElementRef,
|
||||
HostBinding,
|
||||
HostListener,
|
||||
Input,
|
||||
OnChanges,
|
||||
OnDestroy,
|
||||
Renderer2,
|
||||
SimpleChanges,
|
||||
ɵɵsanitizeUrlOrResourceUrl,
|
||||
} from '@angular/core';
|
||||
import {Subject, Subscription} from 'rxjs';
|
||||
|
||||
import {Event, NavigationEnd} from '../events';
|
||||
@@ -17,7 +30,6 @@ import {ActivatedRoute} from '../router_state';
|
||||
import {Params} from '../shared';
|
||||
import {UrlTree} from '../url_tree';
|
||||
|
||||
|
||||
/**
|
||||
* @description
|
||||
*
|
||||
@@ -124,7 +136,7 @@ export class RouterLink implements OnChanges, OnDestroy {
|
||||
* Represents an `href` attribute value applied to a host element,
|
||||
* when a host element is `<a>`. For other tags, the value is `null`.
|
||||
*/
|
||||
href: string|null = null;
|
||||
href: string | null = null;
|
||||
|
||||
/**
|
||||
* Represents the `target` attribute on a host element.
|
||||
@@ -138,7 +150,7 @@ export class RouterLink implements OnChanges, OnDestroy {
|
||||
* @see {@link UrlCreationOptions#queryParams}
|
||||
* @see {@link Router#createUrlTree}
|
||||
*/
|
||||
@Input() queryParams?: Params|null;
|
||||
@Input() queryParams?: Params | null;
|
||||
/**
|
||||
* Passed to {@link Router#createUrlTree} as part of the
|
||||
* `UrlCreationOptions`.
|
||||
@@ -152,7 +164,7 @@ export class RouterLink implements OnChanges, OnDestroy {
|
||||
* @see {@link UrlCreationOptions#queryParamsHandling}
|
||||
* @see {@link Router#createUrlTree}
|
||||
*/
|
||||
@Input() queryParamsHandling?: QueryParamsHandling|null;
|
||||
@Input() queryParamsHandling?: QueryParamsHandling | null;
|
||||
/**
|
||||
* Passed to {@link Router#navigateByUrl} as part of the
|
||||
* `NavigationBehaviorOptions`.
|
||||
@@ -176,9 +188,9 @@ export class RouterLink implements OnChanges, OnDestroy {
|
||||
* @see {@link UrlCreationOptions#relativeTo}
|
||||
* @see {@link Router#createUrlTree}
|
||||
*/
|
||||
@Input() relativeTo?: ActivatedRoute|null;
|
||||
@Input() relativeTo?: ActivatedRoute | null;
|
||||
|
||||
private commands: any[]|null = null;
|
||||
private commands: any[] | null = null;
|
||||
|
||||
/** Whether a host element is an `<a>` tag. */
|
||||
private isAnchorElement: boolean;
|
||||
@@ -189,10 +201,13 @@ export class RouterLink implements OnChanges, OnDestroy {
|
||||
onChanges = new Subject<RouterLink>();
|
||||
|
||||
constructor(
|
||||
private router: Router, private route: ActivatedRoute,
|
||||
@Attribute('tabindex') private readonly tabIndexAttribute: string|null|undefined,
|
||||
private readonly renderer: Renderer2, private readonly el: ElementRef,
|
||||
private locationStrategy?: LocationStrategy) {
|
||||
private router: Router,
|
||||
private route: ActivatedRoute,
|
||||
@Attribute('tabindex') private readonly tabIndexAttribute: string | null | undefined,
|
||||
private readonly renderer: Renderer2,
|
||||
private readonly el: ElementRef,
|
||||
private locationStrategy?: LocationStrategy,
|
||||
) {
|
||||
const tagName = el.nativeElement.tagName?.toLowerCase();
|
||||
this.isAnchorElement = tagName === 'a' || tagName === 'area';
|
||||
|
||||
@@ -235,7 +250,7 @@ export class RouterLink implements OnChanges, OnDestroy {
|
||||
* Modifies the tab index if there was not a tabindex attribute on the element during
|
||||
* instantiation.
|
||||
*/
|
||||
private setTabIndexIfNotOnNativeEl(newTabIndex: string|null) {
|
||||
private setTabIndexIfNotOnNativeEl(newTabIndex: string | null) {
|
||||
if (this.tabIndexAttribute != null /* both `null` and `undefined` */ || this.isAnchorElement) {
|
||||
return;
|
||||
}
|
||||
@@ -260,7 +275,7 @@ export class RouterLink implements OnChanges, OnDestroy {
|
||||
* @see {@link Router#createUrlTree}
|
||||
*/
|
||||
@Input()
|
||||
set routerLink(commands: any[]|string|null|undefined) {
|
||||
set routerLink(commands: any[] | string | null | undefined) {
|
||||
if (commands != null) {
|
||||
this.commands = Array.isArray(commands) ? commands : [commands];
|
||||
this.setTabIndexIfNotOnNativeEl('0');
|
||||
@@ -271,13 +286,22 @@ export class RouterLink implements OnChanges, OnDestroy {
|
||||
}
|
||||
|
||||
/** @nodoc */
|
||||
@HostListener(
|
||||
'click',
|
||||
['$event.button', '$event.ctrlKey', '$event.shiftKey', '$event.altKey', '$event.metaKey'])
|
||||
onClick(button: number, ctrlKey: boolean, shiftKey: boolean, altKey: boolean, metaKey: boolean):
|
||||
boolean {
|
||||
@HostListener('click', [
|
||||
'$event.button',
|
||||
'$event.ctrlKey',
|
||||
'$event.shiftKey',
|
||||
'$event.altKey',
|
||||
'$event.metaKey',
|
||||
])
|
||||
onClick(
|
||||
button: number,
|
||||
ctrlKey: boolean,
|
||||
shiftKey: boolean,
|
||||
altKey: boolean,
|
||||
metaKey: boolean,
|
||||
): boolean {
|
||||
const urlTree = this.urlTree;
|
||||
|
||||
|
||||
if (urlTree === null) {
|
||||
return true;
|
||||
}
|
||||
@@ -313,27 +337,33 @@ export class RouterLink implements OnChanges, OnDestroy {
|
||||
|
||||
private updateHref(): void {
|
||||
const urlTree = this.urlTree;
|
||||
this.href = urlTree !== null && this.locationStrategy ?
|
||||
this.locationStrategy?.prepareExternalUrl(this.router.serializeUrl(urlTree)) :
|
||||
null;
|
||||
this.href =
|
||||
urlTree !== null && this.locationStrategy
|
||||
? this.locationStrategy?.prepareExternalUrl(this.router.serializeUrl(urlTree))
|
||||
: null;
|
||||
|
||||
const sanitizedValue = this.href === null ?
|
||||
null :
|
||||
// This class represents a directive that can be added to both `<a>` elements,
|
||||
// as well as other elements. As a result, we can't define security context at
|
||||
// compile time. So the security context is deferred to runtime.
|
||||
// The `ɵɵsanitizeUrlOrResourceUrl` selects the necessary sanitizer function
|
||||
// based on the tag and property names. The logic mimics the one from
|
||||
// `packages/compiler/src/schema/dom_security_schema.ts`, which is used at compile time.
|
||||
//
|
||||
// Note: we should investigate whether we can switch to using `@HostBinding('attr.href')`
|
||||
// instead of applying a value via a renderer, after a final merge of the
|
||||
// `RouterLinkWithHref` directive.
|
||||
ɵɵsanitizeUrlOrResourceUrl(this.href, this.el.nativeElement.tagName.toLowerCase(), 'href');
|
||||
const sanitizedValue =
|
||||
this.href === null
|
||||
? null
|
||||
: // This class represents a directive that can be added to both `<a>` elements,
|
||||
// as well as other elements. As a result, we can't define security context at
|
||||
// compile time. So the security context is deferred to runtime.
|
||||
// The `ɵɵsanitizeUrlOrResourceUrl` selects the necessary sanitizer function
|
||||
// based on the tag and property names. The logic mimics the one from
|
||||
// `packages/compiler/src/schema/dom_security_schema.ts`, which is used at compile time.
|
||||
//
|
||||
// Note: we should investigate whether we can switch to using `@HostBinding('attr.href')`
|
||||
// instead of applying a value via a renderer, after a final merge of the
|
||||
// `RouterLinkWithHref` directive.
|
||||
ɵɵsanitizeUrlOrResourceUrl(
|
||||
this.href,
|
||||
this.el.nativeElement.tagName.toLowerCase(),
|
||||
'href',
|
||||
);
|
||||
this.applyAttributeValue('href', sanitizedValue);
|
||||
}
|
||||
|
||||
private applyAttributeValue(attrName: string, attrValue: string|null) {
|
||||
private applyAttributeValue(attrName: string, attrValue: string | null) {
|
||||
const renderer = this.renderer;
|
||||
const nativeElement = this.el.nativeElement;
|
||||
if (attrValue !== null) {
|
||||
@@ -343,7 +373,7 @@ export class RouterLink implements OnChanges, OnDestroy {
|
||||
}
|
||||
}
|
||||
|
||||
get urlTree(): UrlTree|null {
|
||||
get urlTree(): UrlTree | null {
|
||||
if (this.commands === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -6,7 +6,22 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {AfterContentInit, ChangeDetectorRef, ContentChildren, Directive, ElementRef, EventEmitter, Input, OnChanges, OnDestroy, Optional, Output, QueryList, Renderer2, SimpleChanges} from '@angular/core';
|
||||
import {
|
||||
AfterContentInit,
|
||||
ChangeDetectorRef,
|
||||
ContentChildren,
|
||||
Directive,
|
||||
ElementRef,
|
||||
EventEmitter,
|
||||
Input,
|
||||
OnChanges,
|
||||
OnDestroy,
|
||||
Optional,
|
||||
Output,
|
||||
QueryList,
|
||||
Renderer2,
|
||||
SimpleChanges,
|
||||
} from '@angular/core';
|
||||
import {from, of, Subscription} from 'rxjs';
|
||||
import {mergeAll} from 'rxjs/operators';
|
||||
|
||||
@@ -16,7 +31,6 @@ import {IsActiveMatchOptions} from '../url_tree';
|
||||
|
||||
import {RouterLink} from './router_link';
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
* @description
|
||||
@@ -110,8 +124,7 @@ export class RouterLinkActive implements OnChanges, OnDestroy, AfterContentInit
|
||||
*
|
||||
* @see {@link Router#isActive}
|
||||
*/
|
||||
@Input() routerLinkActiveOptions: {exact: boolean}|IsActiveMatchOptions = {exact: false};
|
||||
|
||||
@Input() routerLinkActiveOptions: {exact: boolean} | IsActiveMatchOptions = {exact: false};
|
||||
|
||||
/**
|
||||
* Aria-current attribute to apply when the router link is active.
|
||||
@@ -120,7 +133,7 @@ export class RouterLinkActive implements OnChanges, OnDestroy, AfterContentInit
|
||||
*
|
||||
* @see {@link https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-current}
|
||||
*/
|
||||
@Input() ariaCurrentWhenActive?: 'page'|'step'|'location'|'date'|'time'|true|false;
|
||||
@Input() ariaCurrentWhenActive?: 'page' | 'step' | 'location' | 'date' | 'time' | true | false;
|
||||
|
||||
/**
|
||||
*
|
||||
@@ -141,8 +154,12 @@ export class RouterLinkActive implements OnChanges, OnDestroy, AfterContentInit
|
||||
@Output() readonly isActiveChange: EventEmitter<boolean> = new EventEmitter();
|
||||
|
||||
constructor(
|
||||
private router: Router, private element: ElementRef, private renderer: Renderer2,
|
||||
private readonly cdr: ChangeDetectorRef, @Optional() private link?: RouterLink) {
|
||||
private router: Router,
|
||||
private element: ElementRef,
|
||||
private renderer: Renderer2,
|
||||
private readonly cdr: ChangeDetectorRef,
|
||||
@Optional() private link?: RouterLink,
|
||||
) {
|
||||
this.routerEventsSubscription = router.events.subscribe((s: Event) => {
|
||||
if (s instanceof NavigationEnd) {
|
||||
this.update();
|
||||
@@ -153,28 +170,32 @@ export class RouterLinkActive implements OnChanges, OnDestroy, AfterContentInit
|
||||
/** @nodoc */
|
||||
ngAfterContentInit(): void {
|
||||
// `of(null)` is used to force subscribe body to execute once immediately (like `startWith`).
|
||||
of(this.links.changes, of(null)).pipe(mergeAll()).subscribe(_ => {
|
||||
this.update();
|
||||
this.subscribeToEachLinkOnChanges();
|
||||
});
|
||||
of(this.links.changes, of(null))
|
||||
.pipe(mergeAll())
|
||||
.subscribe((_) => {
|
||||
this.update();
|
||||
this.subscribeToEachLinkOnChanges();
|
||||
});
|
||||
}
|
||||
|
||||
private subscribeToEachLinkOnChanges() {
|
||||
this.linkInputChangesSubscription?.unsubscribe();
|
||||
const allLinkChanges = [...this.links.toArray(), this.link]
|
||||
.filter((link): link is RouterLink => !!link)
|
||||
.map(link => link.onChanges);
|
||||
this.linkInputChangesSubscription = from(allLinkChanges).pipe(mergeAll()).subscribe(link => {
|
||||
if (this._isActive !== this.isLinkActive(this.router)(link)) {
|
||||
this.update();
|
||||
}
|
||||
});
|
||||
.filter((link): link is RouterLink => !!link)
|
||||
.map((link) => link.onChanges);
|
||||
this.linkInputChangesSubscription = from(allLinkChanges)
|
||||
.pipe(mergeAll())
|
||||
.subscribe((link) => {
|
||||
if (this._isActive !== this.isLinkActive(this.router)(link)) {
|
||||
this.update();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Input()
|
||||
set routerLinkActive(data: string[]|string) {
|
||||
set routerLinkActive(data: string[] | string) {
|
||||
const classes = Array.isArray(data) ? data : data.split(' ');
|
||||
this.classes = classes.filter(c => !!c);
|
||||
this.classes = classes.filter((c) => !!c);
|
||||
}
|
||||
|
||||
/** @nodoc */
|
||||
@@ -203,7 +224,10 @@ export class RouterLinkActive implements OnChanges, OnDestroy, AfterContentInit
|
||||
});
|
||||
if (hasActiveLinks && this.ariaCurrentWhenActive !== undefined) {
|
||||
this.renderer.setAttribute(
|
||||
this.element.nativeElement, 'aria-current', this.ariaCurrentWhenActive.toString());
|
||||
this.element.nativeElement,
|
||||
'aria-current',
|
||||
this.ariaCurrentWhenActive.toString(),
|
||||
);
|
||||
} else {
|
||||
this.renderer.removeAttribute(this.element.nativeElement, 'aria-current');
|
||||
}
|
||||
@@ -215,11 +239,12 @@ export class RouterLinkActive implements OnChanges, OnDestroy, AfterContentInit
|
||||
}
|
||||
|
||||
private isLinkActive(router: Router): (link: RouterLink) => boolean {
|
||||
const options: boolean|IsActiveMatchOptions =
|
||||
isActiveMatchOptions(this.routerLinkActiveOptions) ?
|
||||
this.routerLinkActiveOptions :
|
||||
// While the types should disallow `undefined` here, it's possible without strict inputs
|
||||
(this.routerLinkActiveOptions.exact || false);
|
||||
const options: boolean | IsActiveMatchOptions = isActiveMatchOptions(
|
||||
this.routerLinkActiveOptions,
|
||||
)
|
||||
? this.routerLinkActiveOptions
|
||||
: // While the types should disallow `undefined` here, it's possible without strict inputs
|
||||
this.routerLinkActiveOptions.exact || false;
|
||||
return (link: RouterLink) => {
|
||||
const urlTree = link.urlTree;
|
||||
return urlTree ? router.isActive(urlTree, options) : false;
|
||||
@@ -228,14 +253,15 @@ export class RouterLinkActive implements OnChanges, OnDestroy, AfterContentInit
|
||||
|
||||
private hasActiveLinks(): boolean {
|
||||
const isActiveCheckFn = this.isLinkActive(this.router);
|
||||
return this.link && isActiveCheckFn(this.link) || this.links.some(isActiveCheckFn);
|
||||
return (this.link && isActiveCheckFn(this.link)) || this.links.some(isActiveCheckFn);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Use instead of `'paths' in options` to be compatible with property renaming
|
||||
*/
|
||||
function isActiveMatchOptions(options: {exact: boolean}|
|
||||
IsActiveMatchOptions): options is IsActiveMatchOptions {
|
||||
function isActiveMatchOptions(
|
||||
options: {exact: boolean} | IsActiveMatchOptions,
|
||||
): options is IsActiveMatchOptions {
|
||||
return !!(options as IsActiveMatchOptions).paths;
|
||||
}
|
||||
|
||||
@@ -6,7 +6,25 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {ChangeDetectorRef, ComponentRef, Directive, EnvironmentInjector, EventEmitter, inject, Injectable, InjectionToken, Injector, Input, OnDestroy, OnInit, Output, reflectComponentType, SimpleChanges, ViewContainerRef, ɵRuntimeError as RuntimeError,} from '@angular/core';
|
||||
import {
|
||||
ChangeDetectorRef,
|
||||
ComponentRef,
|
||||
Directive,
|
||||
EnvironmentInjector,
|
||||
EventEmitter,
|
||||
inject,
|
||||
Injectable,
|
||||
InjectionToken,
|
||||
Injector,
|
||||
Input,
|
||||
OnDestroy,
|
||||
OnInit,
|
||||
Output,
|
||||
reflectComponentType,
|
||||
SimpleChanges,
|
||||
ViewContainerRef,
|
||||
ɵRuntimeError as RuntimeError,
|
||||
} from '@angular/core';
|
||||
import {combineLatest, of, Subscription} from 'rxjs';
|
||||
import {switchMap} from 'rxjs/operators';
|
||||
|
||||
@@ -16,7 +34,6 @@ import {ChildrenOutletContexts} from '../router_outlet_context';
|
||||
import {ActivatedRoute} from '../router_state';
|
||||
import {PRIMARY_OUTLET} from '../shared';
|
||||
|
||||
|
||||
/**
|
||||
* An interface that defines the contract for developing a component outlet for the `Router`.
|
||||
*
|
||||
@@ -39,7 +56,7 @@ export interface RouterOutletContract {
|
||||
isActivated: boolean;
|
||||
|
||||
/** The instance of the activated component or `null` if the outlet is not activated. */
|
||||
component: Object|null;
|
||||
component: Object | null;
|
||||
|
||||
/**
|
||||
* The `Data` of the `ActivatedRoute` snapshot.
|
||||
@@ -49,12 +66,15 @@ export interface RouterOutletContract {
|
||||
/**
|
||||
* The `ActivatedRoute` for the outlet or `null` if the outlet is not activated.
|
||||
*/
|
||||
activatedRoute: ActivatedRoute|null;
|
||||
activatedRoute: ActivatedRoute | null;
|
||||
|
||||
/**
|
||||
* Called by the `Router` when the outlet should activate (create a component).
|
||||
*/
|
||||
activateWith(activatedRoute: ActivatedRoute, environmentInjector: EnvironmentInjector|null): void;
|
||||
activateWith(
|
||||
activatedRoute: ActivatedRoute,
|
||||
environmentInjector: EnvironmentInjector | null,
|
||||
): void;
|
||||
|
||||
/**
|
||||
* A request to destroy the currently activated component.
|
||||
@@ -167,12 +187,12 @@ export interface RouterOutletContract {
|
||||
standalone: true,
|
||||
})
|
||||
export class RouterOutlet implements OnDestroy, OnInit, RouterOutletContract {
|
||||
private activated: ComponentRef<any>|null = null;
|
||||
private activated: ComponentRef<any> | null = null;
|
||||
/** @internal */
|
||||
get activatedComponentRef(): ComponentRef<any>|null {
|
||||
get activatedComponentRef(): ComponentRef<any> | null {
|
||||
return this.activated;
|
||||
}
|
||||
private _activatedRoute: ActivatedRoute|null = null;
|
||||
private _activatedRoute: ActivatedRoute | null = null;
|
||||
/**
|
||||
* The name of the outlet
|
||||
*
|
||||
@@ -270,16 +290,18 @@ export class RouterOutlet implements OnDestroy, OnInit, RouterOutletContract {
|
||||
get component(): Object {
|
||||
if (!this.activated)
|
||||
throw new RuntimeError(
|
||||
RuntimeErrorCode.OUTLET_NOT_ACTIVATED,
|
||||
(typeof ngDevMode === 'undefined' || ngDevMode) && 'Outlet is not activated');
|
||||
RuntimeErrorCode.OUTLET_NOT_ACTIVATED,
|
||||
(typeof ngDevMode === 'undefined' || ngDevMode) && 'Outlet is not activated',
|
||||
);
|
||||
return this.activated.instance;
|
||||
}
|
||||
|
||||
get activatedRoute(): ActivatedRoute {
|
||||
if (!this.activated)
|
||||
throw new RuntimeError(
|
||||
RuntimeErrorCode.OUTLET_NOT_ACTIVATED,
|
||||
(typeof ngDevMode === 'undefined' || ngDevMode) && 'Outlet is not activated');
|
||||
RuntimeErrorCode.OUTLET_NOT_ACTIVATED,
|
||||
(typeof ngDevMode === 'undefined' || ngDevMode) && 'Outlet is not activated',
|
||||
);
|
||||
return this._activatedRoute as ActivatedRoute;
|
||||
}
|
||||
|
||||
@@ -296,8 +318,9 @@ export class RouterOutlet implements OnDestroy, OnInit, RouterOutletContract {
|
||||
detach(): ComponentRef<any> {
|
||||
if (!this.activated)
|
||||
throw new RuntimeError(
|
||||
RuntimeErrorCode.OUTLET_NOT_ACTIVATED,
|
||||
(typeof ngDevMode === 'undefined' || ngDevMode) && 'Outlet is not activated');
|
||||
RuntimeErrorCode.OUTLET_NOT_ACTIVATED,
|
||||
(typeof ngDevMode === 'undefined' || ngDevMode) && 'Outlet is not activated',
|
||||
);
|
||||
this.location.detach();
|
||||
const cmp = this.activated;
|
||||
this.activated = null;
|
||||
@@ -327,12 +350,13 @@ export class RouterOutlet implements OnDestroy, OnInit, RouterOutletContract {
|
||||
}
|
||||
}
|
||||
|
||||
activateWith(activatedRoute: ActivatedRoute, environmentInjector?: EnvironmentInjector|null) {
|
||||
activateWith(activatedRoute: ActivatedRoute, environmentInjector?: EnvironmentInjector | null) {
|
||||
if (this.isActivated) {
|
||||
throw new RuntimeError(
|
||||
RuntimeErrorCode.OUTLET_ALREADY_ACTIVATED,
|
||||
(typeof ngDevMode === 'undefined' || ngDevMode) &&
|
||||
'Cannot activate an already activated outlet');
|
||||
RuntimeErrorCode.OUTLET_ALREADY_ACTIVATED,
|
||||
(typeof ngDevMode === 'undefined' || ngDevMode) &&
|
||||
'Cannot activate an already activated outlet',
|
||||
);
|
||||
}
|
||||
this._activatedRoute = activatedRoute;
|
||||
const location = this.location;
|
||||
@@ -344,7 +368,7 @@ export class RouterOutlet implements OnDestroy, OnInit, RouterOutletContract {
|
||||
this.activated = location.createComponent(component, {
|
||||
index: location.length,
|
||||
injector,
|
||||
environmentInjector: environmentInjector ?? this.environmentInjector
|
||||
environmentInjector: environmentInjector ?? this.environmentInjector,
|
||||
});
|
||||
// Calling `markForCheck` to make sure we will run the change detection when the
|
||||
// `RouterOutlet` is inside a `ChangeDetectionStrategy.OnPush` component.
|
||||
@@ -356,8 +380,10 @@ export class RouterOutlet implements OnDestroy, OnInit, RouterOutletContract {
|
||||
|
||||
class OutletInjector implements Injector {
|
||||
constructor(
|
||||
private route: ActivatedRoute, private childContexts: ChildrenOutletContexts,
|
||||
private parent: Injector) {}
|
||||
private route: ActivatedRoute,
|
||||
private childContexts: ChildrenOutletContexts,
|
||||
private parent: Injector,
|
||||
) {}
|
||||
|
||||
get(token: any, notFoundValue?: any): any {
|
||||
if (token === ActivatedRoute) {
|
||||
@@ -390,7 +416,7 @@ export const INPUT_BINDER = new InjectionToken<RoutedComponentInputBinder>('');
|
||||
*/
|
||||
@Injectable()
|
||||
export class RoutedComponentInputBinder {
|
||||
private outletDataSubscriptions = new Map<RouterOutlet, Subscription>;
|
||||
private outletDataSubscriptions = new Map<RouterOutlet, Subscription>();
|
||||
|
||||
bindActivatedRouteToOutletComponent(outlet: RouterOutlet) {
|
||||
this.unsubscribeFromRouteData(outlet);
|
||||
@@ -404,43 +430,48 @@ export class RoutedComponentInputBinder {
|
||||
|
||||
private subscribeToRouteData(outlet: RouterOutlet) {
|
||||
const {activatedRoute} = outlet;
|
||||
const dataSubscription =
|
||||
combineLatest([
|
||||
activatedRoute.queryParams,
|
||||
activatedRoute.params,
|
||||
activatedRoute.data,
|
||||
])
|
||||
.pipe(switchMap(([queryParams, params, data], index) => {
|
||||
data = {...queryParams, ...params, ...data};
|
||||
// Get the first result from the data subscription synchronously so it's available to
|
||||
// the component as soon as possible (and doesn't require a second change detection).
|
||||
if (index === 0) {
|
||||
return of(data);
|
||||
}
|
||||
// Promise.resolve is used to avoid synchronously writing the wrong data when
|
||||
// two of the Observables in the `combineLatest` stream emit one after
|
||||
// another.
|
||||
return Promise.resolve(data);
|
||||
}))
|
||||
.subscribe(data => {
|
||||
// Outlet may have been deactivated or changed names to be associated with a different
|
||||
// route
|
||||
if (!outlet.isActivated || !outlet.activatedComponentRef ||
|
||||
outlet.activatedRoute !== activatedRoute || activatedRoute.component === null) {
|
||||
this.unsubscribeFromRouteData(outlet);
|
||||
return;
|
||||
}
|
||||
const dataSubscription = combineLatest([
|
||||
activatedRoute.queryParams,
|
||||
activatedRoute.params,
|
||||
activatedRoute.data,
|
||||
])
|
||||
.pipe(
|
||||
switchMap(([queryParams, params, data], index) => {
|
||||
data = {...queryParams, ...params, ...data};
|
||||
// Get the first result from the data subscription synchronously so it's available to
|
||||
// the component as soon as possible (and doesn't require a second change detection).
|
||||
if (index === 0) {
|
||||
return of(data);
|
||||
}
|
||||
// Promise.resolve is used to avoid synchronously writing the wrong data when
|
||||
// two of the Observables in the `combineLatest` stream emit one after
|
||||
// another.
|
||||
return Promise.resolve(data);
|
||||
}),
|
||||
)
|
||||
.subscribe((data) => {
|
||||
// Outlet may have been deactivated or changed names to be associated with a different
|
||||
// route
|
||||
if (
|
||||
!outlet.isActivated ||
|
||||
!outlet.activatedComponentRef ||
|
||||
outlet.activatedRoute !== activatedRoute ||
|
||||
activatedRoute.component === null
|
||||
) {
|
||||
this.unsubscribeFromRouteData(outlet);
|
||||
return;
|
||||
}
|
||||
|
||||
const mirror = reflectComponentType(activatedRoute.component);
|
||||
if (!mirror) {
|
||||
this.unsubscribeFromRouteData(outlet);
|
||||
return;
|
||||
}
|
||||
const mirror = reflectComponentType(activatedRoute.component);
|
||||
if (!mirror) {
|
||||
this.unsubscribeFromRouteData(outlet);
|
||||
return;
|
||||
}
|
||||
|
||||
for (const {templateName} of mirror.inputs) {
|
||||
outlet.activatedComponentRef.setInput(templateName, data[templateName]);
|
||||
}
|
||||
});
|
||||
for (const {templateName} of mirror.inputs) {
|
||||
outlet.activatedComponentRef.setInput(templateName, data[templateName]);
|
||||
}
|
||||
});
|
||||
|
||||
this.outletDataSubscriptions.set(outlet, dataSubscription);
|
||||
}
|
||||
|
||||
+180
-163
@@ -19,7 +19,7 @@ import {UrlTree} from './url_tree';
|
||||
*
|
||||
* @publicApi
|
||||
*/
|
||||
export type NavigationTrigger = 'imperative'|'popstate'|'hashchange';
|
||||
export type NavigationTrigger = 'imperative' | 'popstate' | 'hashchange';
|
||||
export const IMPERATIVE_NAVIGATION = 'imperative';
|
||||
|
||||
/**
|
||||
@@ -73,10 +73,11 @@ export enum EventType {
|
||||
*/
|
||||
export class RouterEvent {
|
||||
constructor(
|
||||
/** A unique ID that the router assigns to every router navigation. */
|
||||
public id: number,
|
||||
/** The URL that is the destination for this navigation. */
|
||||
public url: string) {}
|
||||
/** A unique ID that the router assigns to every router navigation. */
|
||||
public id: number,
|
||||
/** The URL that is the destination for this navigation. */
|
||||
public url: string,
|
||||
) {}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -114,17 +115,18 @@ export class NavigationStart extends RouterEvent {
|
||||
* remembered state, such as scroll position.
|
||||
*
|
||||
*/
|
||||
restoredState?: {[k: string]: any, navigationId: number}|null;
|
||||
restoredState?: {[k: string]: any; navigationId: number} | null;
|
||||
|
||||
constructor(
|
||||
/** @docsNotRequired */
|
||||
id: number,
|
||||
/** @docsNotRequired */
|
||||
url: string,
|
||||
/** @docsNotRequired */
|
||||
navigationTrigger: NavigationTrigger = 'imperative',
|
||||
/** @docsNotRequired */
|
||||
restoredState: {[k: string]: any, navigationId: number}|null = null) {
|
||||
/** @docsNotRequired */
|
||||
id: number,
|
||||
/** @docsNotRequired */
|
||||
url: string,
|
||||
/** @docsNotRequired */
|
||||
navigationTrigger: NavigationTrigger = 'imperative',
|
||||
/** @docsNotRequired */
|
||||
restoredState: {[k: string]: any; navigationId: number} | null = null,
|
||||
) {
|
||||
super(id, url);
|
||||
this.navigationTrigger = navigationTrigger;
|
||||
this.restoredState = restoredState;
|
||||
@@ -149,19 +151,19 @@ export class NavigationEnd extends RouterEvent {
|
||||
readonly type = EventType.NavigationEnd;
|
||||
|
||||
constructor(
|
||||
/** @docsNotRequired */
|
||||
id: number,
|
||||
/** @docsNotRequired */
|
||||
url: string,
|
||||
/** @docsNotRequired */
|
||||
public urlAfterRedirects: string) {
|
||||
/** @docsNotRequired */
|
||||
id: number,
|
||||
/** @docsNotRequired */
|
||||
url: string,
|
||||
/** @docsNotRequired */
|
||||
public urlAfterRedirects: string,
|
||||
) {
|
||||
super(id, url);
|
||||
}
|
||||
|
||||
/** @docsNotRequired */
|
||||
override toString(): string {
|
||||
return `NavigationEnd(id: ${this.id}, url: '${this.url}', urlAfterRedirects: '${
|
||||
this.urlAfterRedirects}')`;
|
||||
return `NavigationEnd(id: ${this.id}, url: '${this.url}', urlAfterRedirects: '${this.urlAfterRedirects}')`;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -225,21 +227,22 @@ export class NavigationCancel extends RouterEvent {
|
||||
readonly type = EventType.NavigationCancel;
|
||||
|
||||
constructor(
|
||||
/** @docsNotRequired */
|
||||
id: number,
|
||||
/** @docsNotRequired */
|
||||
url: string,
|
||||
/**
|
||||
* A description of why the navigation was cancelled. For debug purposes only. Use `code`
|
||||
* instead for a stable cancellation reason that can be used in production.
|
||||
*/
|
||||
public reason: string,
|
||||
/**
|
||||
* A code to indicate why the navigation was canceled. This cancellation code is stable for
|
||||
* the reason and can be relied on whereas the `reason` string could change and should not be
|
||||
* used in production.
|
||||
*/
|
||||
readonly code?: NavigationCancellationCode) {
|
||||
/** @docsNotRequired */
|
||||
id: number,
|
||||
/** @docsNotRequired */
|
||||
url: string,
|
||||
/**
|
||||
* A description of why the navigation was cancelled. For debug purposes only. Use `code`
|
||||
* instead for a stable cancellation reason that can be used in production.
|
||||
*/
|
||||
public reason: string,
|
||||
/**
|
||||
* A code to indicate why the navigation was canceled. This cancellation code is stable for
|
||||
* the reason and can be relied on whereas the `reason` string could change and should not be
|
||||
* used in production.
|
||||
*/
|
||||
readonly code?: NavigationCancellationCode,
|
||||
) {
|
||||
super(id, url);
|
||||
}
|
||||
|
||||
@@ -261,21 +264,22 @@ export class NavigationSkipped extends RouterEvent {
|
||||
readonly type = EventType.NavigationSkipped;
|
||||
|
||||
constructor(
|
||||
/** @docsNotRequired */
|
||||
id: number,
|
||||
/** @docsNotRequired */
|
||||
url: string,
|
||||
/**
|
||||
* A description of why the navigation was skipped. For debug purposes only. Use `code`
|
||||
* instead for a stable skipped reason that can be used in production.
|
||||
*/
|
||||
public reason: string,
|
||||
/**
|
||||
* A code to indicate why the navigation was skipped. This code is stable for
|
||||
* the reason and can be relied on whereas the `reason` string could change and should not be
|
||||
* used in production.
|
||||
*/
|
||||
readonly code?: NavigationSkippedCode) {
|
||||
/** @docsNotRequired */
|
||||
id: number,
|
||||
/** @docsNotRequired */
|
||||
url: string,
|
||||
/**
|
||||
* A description of why the navigation was skipped. For debug purposes only. Use `code`
|
||||
* instead for a stable skipped reason that can be used in production.
|
||||
*/
|
||||
public reason: string,
|
||||
/**
|
||||
* A code to indicate why the navigation was skipped. This code is stable for
|
||||
* the reason and can be relied on whereas the `reason` string could change and should not be
|
||||
* used in production.
|
||||
*/
|
||||
readonly code?: NavigationSkippedCode,
|
||||
) {
|
||||
super(id, url);
|
||||
}
|
||||
}
|
||||
@@ -293,19 +297,20 @@ export class NavigationError extends RouterEvent {
|
||||
readonly type = EventType.NavigationError;
|
||||
|
||||
constructor(
|
||||
/** @docsNotRequired */
|
||||
id: number,
|
||||
/** @docsNotRequired */
|
||||
url: string,
|
||||
/** @docsNotRequired */
|
||||
public error: any,
|
||||
/**
|
||||
* The target of the navigation when the error occurred.
|
||||
*
|
||||
* Note that this can be `undefined` because an error could have occurred before the
|
||||
* `RouterStateSnapshot` was created for the navigation.
|
||||
*/
|
||||
readonly target?: RouterStateSnapshot) {
|
||||
/** @docsNotRequired */
|
||||
id: number,
|
||||
/** @docsNotRequired */
|
||||
url: string,
|
||||
/** @docsNotRequired */
|
||||
public error: any,
|
||||
/**
|
||||
* The target of the navigation when the error occurred.
|
||||
*
|
||||
* Note that this can be `undefined` because an error could have occurred before the
|
||||
* `RouterStateSnapshot` was created for the navigation.
|
||||
*/
|
||||
readonly target?: RouterStateSnapshot,
|
||||
) {
|
||||
super(id, url);
|
||||
}
|
||||
|
||||
@@ -324,21 +329,21 @@ export class RoutesRecognized extends RouterEvent {
|
||||
readonly type = EventType.RoutesRecognized;
|
||||
|
||||
constructor(
|
||||
/** @docsNotRequired */
|
||||
id: number,
|
||||
/** @docsNotRequired */
|
||||
url: string,
|
||||
/** @docsNotRequired */
|
||||
public urlAfterRedirects: string,
|
||||
/** @docsNotRequired */
|
||||
public state: RouterStateSnapshot) {
|
||||
/** @docsNotRequired */
|
||||
id: number,
|
||||
/** @docsNotRequired */
|
||||
url: string,
|
||||
/** @docsNotRequired */
|
||||
public urlAfterRedirects: string,
|
||||
/** @docsNotRequired */
|
||||
public state: RouterStateSnapshot,
|
||||
) {
|
||||
super(id, url);
|
||||
}
|
||||
|
||||
/** @docsNotRequired */
|
||||
override toString(): string {
|
||||
return `RoutesRecognized(id: ${this.id}, url: '${this.url}', urlAfterRedirects: '${
|
||||
this.urlAfterRedirects}', state: ${this.state})`;
|
||||
return `RoutesRecognized(id: ${this.id}, url: '${this.url}', urlAfterRedirects: '${this.urlAfterRedirects}', state: ${this.state})`;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -353,20 +358,20 @@ export class GuardsCheckStart extends RouterEvent {
|
||||
readonly type = EventType.GuardsCheckStart;
|
||||
|
||||
constructor(
|
||||
/** @docsNotRequired */
|
||||
id: number,
|
||||
/** @docsNotRequired */
|
||||
url: string,
|
||||
/** @docsNotRequired */
|
||||
public urlAfterRedirects: string,
|
||||
/** @docsNotRequired */
|
||||
public state: RouterStateSnapshot) {
|
||||
/** @docsNotRequired */
|
||||
id: number,
|
||||
/** @docsNotRequired */
|
||||
url: string,
|
||||
/** @docsNotRequired */
|
||||
public urlAfterRedirects: string,
|
||||
/** @docsNotRequired */
|
||||
public state: RouterStateSnapshot,
|
||||
) {
|
||||
super(id, url);
|
||||
}
|
||||
|
||||
override toString(): string {
|
||||
return `GuardsCheckStart(id: ${this.id}, url: '${this.url}', urlAfterRedirects: '${
|
||||
this.urlAfterRedirects}', state: ${this.state})`;
|
||||
return `GuardsCheckStart(id: ${this.id}, url: '${this.url}', urlAfterRedirects: '${this.urlAfterRedirects}', state: ${this.state})`;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -381,22 +386,22 @@ export class GuardsCheckEnd extends RouterEvent {
|
||||
readonly type = EventType.GuardsCheckEnd;
|
||||
|
||||
constructor(
|
||||
/** @docsNotRequired */
|
||||
id: number,
|
||||
/** @docsNotRequired */
|
||||
url: string,
|
||||
/** @docsNotRequired */
|
||||
public urlAfterRedirects: string,
|
||||
/** @docsNotRequired */
|
||||
public state: RouterStateSnapshot,
|
||||
/** @docsNotRequired */
|
||||
public shouldActivate: boolean) {
|
||||
/** @docsNotRequired */
|
||||
id: number,
|
||||
/** @docsNotRequired */
|
||||
url: string,
|
||||
/** @docsNotRequired */
|
||||
public urlAfterRedirects: string,
|
||||
/** @docsNotRequired */
|
||||
public state: RouterStateSnapshot,
|
||||
/** @docsNotRequired */
|
||||
public shouldActivate: boolean,
|
||||
) {
|
||||
super(id, url);
|
||||
}
|
||||
|
||||
override toString(): string {
|
||||
return `GuardsCheckEnd(id: ${this.id}, url: '${this.url}', urlAfterRedirects: '${
|
||||
this.urlAfterRedirects}', state: ${this.state}, shouldActivate: ${this.shouldActivate})`;
|
||||
return `GuardsCheckEnd(id: ${this.id}, url: '${this.url}', urlAfterRedirects: '${this.urlAfterRedirects}', state: ${this.state}, shouldActivate: ${this.shouldActivate})`;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -414,20 +419,20 @@ export class ResolveStart extends RouterEvent {
|
||||
readonly type = EventType.ResolveStart;
|
||||
|
||||
constructor(
|
||||
/** @docsNotRequired */
|
||||
id: number,
|
||||
/** @docsNotRequired */
|
||||
url: string,
|
||||
/** @docsNotRequired */
|
||||
public urlAfterRedirects: string,
|
||||
/** @docsNotRequired */
|
||||
public state: RouterStateSnapshot) {
|
||||
/** @docsNotRequired */
|
||||
id: number,
|
||||
/** @docsNotRequired */
|
||||
url: string,
|
||||
/** @docsNotRequired */
|
||||
public urlAfterRedirects: string,
|
||||
/** @docsNotRequired */
|
||||
public state: RouterStateSnapshot,
|
||||
) {
|
||||
super(id, url);
|
||||
}
|
||||
|
||||
override toString(): string {
|
||||
return `ResolveStart(id: ${this.id}, url: '${this.url}', urlAfterRedirects: '${
|
||||
this.urlAfterRedirects}', state: ${this.state})`;
|
||||
return `ResolveStart(id: ${this.id}, url: '${this.url}', urlAfterRedirects: '${this.urlAfterRedirects}', state: ${this.state})`;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -441,20 +446,20 @@ export class ResolveEnd extends RouterEvent {
|
||||
readonly type = EventType.ResolveEnd;
|
||||
|
||||
constructor(
|
||||
/** @docsNotRequired */
|
||||
id: number,
|
||||
/** @docsNotRequired */
|
||||
url: string,
|
||||
/** @docsNotRequired */
|
||||
public urlAfterRedirects: string,
|
||||
/** @docsNotRequired */
|
||||
public state: RouterStateSnapshot) {
|
||||
/** @docsNotRequired */
|
||||
id: number,
|
||||
/** @docsNotRequired */
|
||||
url: string,
|
||||
/** @docsNotRequired */
|
||||
public urlAfterRedirects: string,
|
||||
/** @docsNotRequired */
|
||||
public state: RouterStateSnapshot,
|
||||
) {
|
||||
super(id, url);
|
||||
}
|
||||
|
||||
override toString(): string {
|
||||
return `ResolveEnd(id: ${this.id}, url: '${this.url}', urlAfterRedirects: '${
|
||||
this.urlAfterRedirects}', state: ${this.state})`;
|
||||
return `ResolveEnd(id: ${this.id}, url: '${this.url}', urlAfterRedirects: '${this.urlAfterRedirects}', state: ${this.state})`;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -469,8 +474,9 @@ export class RouteConfigLoadStart {
|
||||
readonly type = EventType.RouteConfigLoadStart;
|
||||
|
||||
constructor(
|
||||
/** @docsNotRequired */
|
||||
public route: Route) {}
|
||||
/** @docsNotRequired */
|
||||
public route: Route,
|
||||
) {}
|
||||
toString(): string {
|
||||
return `RouteConfigLoadStart(path: ${this.route.path})`;
|
||||
}
|
||||
@@ -487,8 +493,9 @@ export class RouteConfigLoadEnd {
|
||||
readonly type = EventType.RouteConfigLoadEnd;
|
||||
|
||||
constructor(
|
||||
/** @docsNotRequired */
|
||||
public route: Route) {}
|
||||
/** @docsNotRequired */
|
||||
public route: Route,
|
||||
) {}
|
||||
toString(): string {
|
||||
return `RouteConfigLoadEnd(path: ${this.route.path})`;
|
||||
}
|
||||
@@ -506,10 +513,11 @@ export class ChildActivationStart {
|
||||
readonly type = EventType.ChildActivationStart;
|
||||
|
||||
constructor(
|
||||
/** @docsNotRequired */
|
||||
public snapshot: ActivatedRouteSnapshot) {}
|
||||
/** @docsNotRequired */
|
||||
public snapshot: ActivatedRouteSnapshot,
|
||||
) {}
|
||||
toString(): string {
|
||||
const path = this.snapshot.routeConfig && this.snapshot.routeConfig.path || '';
|
||||
const path = (this.snapshot.routeConfig && this.snapshot.routeConfig.path) || '';
|
||||
return `ChildActivationStart(path: '${path}')`;
|
||||
}
|
||||
}
|
||||
@@ -525,10 +533,11 @@ export class ChildActivationEnd {
|
||||
readonly type = EventType.ChildActivationEnd;
|
||||
|
||||
constructor(
|
||||
/** @docsNotRequired */
|
||||
public snapshot: ActivatedRouteSnapshot) {}
|
||||
/** @docsNotRequired */
|
||||
public snapshot: ActivatedRouteSnapshot,
|
||||
) {}
|
||||
toString(): string {
|
||||
const path = this.snapshot.routeConfig && this.snapshot.routeConfig.path || '';
|
||||
const path = (this.snapshot.routeConfig && this.snapshot.routeConfig.path) || '';
|
||||
return `ChildActivationEnd(path: '${path}')`;
|
||||
}
|
||||
}
|
||||
@@ -545,10 +554,11 @@ export class ActivationStart {
|
||||
readonly type = EventType.ActivationStart;
|
||||
|
||||
constructor(
|
||||
/** @docsNotRequired */
|
||||
public snapshot: ActivatedRouteSnapshot) {}
|
||||
/** @docsNotRequired */
|
||||
public snapshot: ActivatedRouteSnapshot,
|
||||
) {}
|
||||
toString(): string {
|
||||
const path = this.snapshot.routeConfig && this.snapshot.routeConfig.path || '';
|
||||
const path = (this.snapshot.routeConfig && this.snapshot.routeConfig.path) || '';
|
||||
return `ActivationStart(path: '${path}')`;
|
||||
}
|
||||
}
|
||||
@@ -565,10 +575,11 @@ export class ActivationEnd {
|
||||
readonly type = EventType.ActivationEnd;
|
||||
|
||||
constructor(
|
||||
/** @docsNotRequired */
|
||||
public snapshot: ActivatedRouteSnapshot) {}
|
||||
/** @docsNotRequired */
|
||||
public snapshot: ActivatedRouteSnapshot,
|
||||
) {}
|
||||
toString(): string {
|
||||
const path = this.snapshot.routeConfig && this.snapshot.routeConfig.path || '';
|
||||
const path = (this.snapshot.routeConfig && this.snapshot.routeConfig.path) || '';
|
||||
return `ActivationEnd(path: '${path}')`;
|
||||
}
|
||||
}
|
||||
@@ -582,14 +593,15 @@ export class Scroll {
|
||||
readonly type = EventType.Scroll;
|
||||
|
||||
constructor(
|
||||
/** @docsNotRequired */
|
||||
readonly routerEvent: NavigationEnd|NavigationSkipped,
|
||||
/** @docsNotRequired */
|
||||
readonly routerEvent: NavigationEnd | NavigationSkipped,
|
||||
|
||||
/** @docsNotRequired */
|
||||
readonly position: [number, number]|null,
|
||||
/** @docsNotRequired */
|
||||
readonly position: [number, number] | null,
|
||||
|
||||
/** @docsNotRequired */
|
||||
readonly anchor: string|null) {}
|
||||
/** @docsNotRequired */
|
||||
readonly anchor: string | null,
|
||||
) {}
|
||||
|
||||
toString(): string {
|
||||
const pos = this.position ? `${this.position[0]}, ${this.position[1]}` : null;
|
||||
@@ -601,7 +613,7 @@ export class BeforeActivateRoutes {}
|
||||
export class RedirectRequest {
|
||||
constructor(readonly url: UrlTree) {}
|
||||
}
|
||||
export type PrivateRouterEvents = BeforeActivateRoutes|RedirectRequest;
|
||||
export type PrivateRouterEvents = BeforeActivateRoutes | RedirectRequest;
|
||||
|
||||
/**
|
||||
* Router events that allow you to track the lifecycle of the router.
|
||||
@@ -636,10 +648,24 @@ export type PrivateRouterEvents = BeforeActivateRoutes|RedirectRequest;
|
||||
*
|
||||
* @publicApi
|
||||
*/
|
||||
export type Event = NavigationStart|NavigationEnd|NavigationCancel|NavigationError|RoutesRecognized|
|
||||
GuardsCheckStart|GuardsCheckEnd|RouteConfigLoadStart|RouteConfigLoadEnd|ChildActivationStart|
|
||||
ChildActivationEnd|ActivationStart|ActivationEnd|Scroll|ResolveStart|ResolveEnd|
|
||||
NavigationSkipped;
|
||||
export type Event =
|
||||
| NavigationStart
|
||||
| NavigationEnd
|
||||
| NavigationCancel
|
||||
| NavigationError
|
||||
| RoutesRecognized
|
||||
| GuardsCheckStart
|
||||
| GuardsCheckEnd
|
||||
| RouteConfigLoadStart
|
||||
| RouteConfigLoadEnd
|
||||
| ChildActivationStart
|
||||
| ChildActivationEnd
|
||||
| ActivationStart
|
||||
| ActivationEnd
|
||||
| Scroll
|
||||
| ResolveStart
|
||||
| ResolveEnd
|
||||
| NavigationSkipped;
|
||||
|
||||
export function stringifyEvent(routerEvent: Event): string {
|
||||
switch (routerEvent.type) {
|
||||
@@ -652,42 +678,33 @@ export function stringifyEvent(routerEvent: Event): string {
|
||||
case EventType.ChildActivationStart:
|
||||
return `ChildActivationStart(path: '${routerEvent.snapshot.routeConfig?.path || ''}')`;
|
||||
case EventType.GuardsCheckEnd:
|
||||
return `GuardsCheckEnd(id: ${routerEvent.id}, url: '${
|
||||
routerEvent.url}', urlAfterRedirects: '${routerEvent.urlAfterRedirects}', state: ${
|
||||
routerEvent.state}, shouldActivate: ${routerEvent.shouldActivate})`;
|
||||
return `GuardsCheckEnd(id: ${routerEvent.id}, url: '${routerEvent.url}', urlAfterRedirects: '${routerEvent.urlAfterRedirects}', state: ${routerEvent.state}, shouldActivate: ${routerEvent.shouldActivate})`;
|
||||
case EventType.GuardsCheckStart:
|
||||
return `GuardsCheckStart(id: ${routerEvent.id}, url: '${
|
||||
routerEvent.url}', urlAfterRedirects: '${routerEvent.urlAfterRedirects}', state: ${
|
||||
routerEvent.state})`;
|
||||
return `GuardsCheckStart(id: ${routerEvent.id}, url: '${routerEvent.url}', urlAfterRedirects: '${routerEvent.urlAfterRedirects}', state: ${routerEvent.state})`;
|
||||
case EventType.NavigationCancel:
|
||||
return `NavigationCancel(id: ${routerEvent.id}, url: '${routerEvent.url}')`;
|
||||
case EventType.NavigationSkipped:
|
||||
return `NavigationSkipped(id: ${routerEvent.id}, url: '${routerEvent.url}')`;
|
||||
case EventType.NavigationEnd:
|
||||
return `NavigationEnd(id: ${routerEvent.id}, url: '${routerEvent.url}', urlAfterRedirects: '${
|
||||
routerEvent.urlAfterRedirects}')`;
|
||||
return `NavigationEnd(id: ${routerEvent.id}, url: '${routerEvent.url}', urlAfterRedirects: '${routerEvent.urlAfterRedirects}')`;
|
||||
case EventType.NavigationError:
|
||||
return `NavigationError(id: ${routerEvent.id}, url: '${routerEvent.url}', error: ${
|
||||
routerEvent.error})`;
|
||||
return `NavigationError(id: ${routerEvent.id}, url: '${routerEvent.url}', error: ${routerEvent.error})`;
|
||||
case EventType.NavigationStart:
|
||||
return `NavigationStart(id: ${routerEvent.id}, url: '${routerEvent.url}')`;
|
||||
case EventType.ResolveEnd:
|
||||
return `ResolveEnd(id: ${routerEvent.id}, url: '${routerEvent.url}', urlAfterRedirects: '${
|
||||
routerEvent.urlAfterRedirects}', state: ${routerEvent.state})`;
|
||||
return `ResolveEnd(id: ${routerEvent.id}, url: '${routerEvent.url}', urlAfterRedirects: '${routerEvent.urlAfterRedirects}', state: ${routerEvent.state})`;
|
||||
case EventType.ResolveStart:
|
||||
return `ResolveStart(id: ${routerEvent.id}, url: '${routerEvent.url}', urlAfterRedirects: '${
|
||||
routerEvent.urlAfterRedirects}', state: ${routerEvent.state})`;
|
||||
return `ResolveStart(id: ${routerEvent.id}, url: '${routerEvent.url}', urlAfterRedirects: '${routerEvent.urlAfterRedirects}', state: ${routerEvent.state})`;
|
||||
case EventType.RouteConfigLoadEnd:
|
||||
return `RouteConfigLoadEnd(path: ${routerEvent.route.path})`;
|
||||
case EventType.RouteConfigLoadStart:
|
||||
return `RouteConfigLoadStart(path: ${routerEvent.route.path})`;
|
||||
case EventType.RoutesRecognized:
|
||||
return `RoutesRecognized(id: ${routerEvent.id}, url: '${
|
||||
routerEvent.url}', urlAfterRedirects: '${routerEvent.urlAfterRedirects}', state: ${
|
||||
routerEvent.state})`;
|
||||
return `RoutesRecognized(id: ${routerEvent.id}, url: '${routerEvent.url}', urlAfterRedirects: '${routerEvent.urlAfterRedirects}', state: ${routerEvent.state})`;
|
||||
case EventType.Scroll:
|
||||
const pos =
|
||||
routerEvent.position ? `${routerEvent.position[0]}, ${routerEvent.position[1]}` : null;
|
||||
const pos = routerEvent.position
|
||||
? `${routerEvent.position[0]}, ${routerEvent.position[1]}`
|
||||
: null;
|
||||
return `Scroll(anchor: '${routerEvent.anchor}', position: '${pos}')`;
|
||||
}
|
||||
}
|
||||
|
||||
+110
-10
@@ -6,31 +6,131 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
|
||||
export {createUrlTreeFromSnapshot} from './create_url_tree';
|
||||
export {RouterLink, RouterLinkWithHref} from './directives/router_link';
|
||||
export {RouterLinkActive} from './directives/router_link_active';
|
||||
export {RouterOutlet, RouterOutletContract} from './directives/router_outlet';
|
||||
export {ActivationEnd, ActivationStart, ChildActivationEnd, ChildActivationStart, Event, EventType, GuardsCheckEnd, GuardsCheckStart, NavigationCancel, NavigationCancellationCode as NavigationCancellationCode, NavigationEnd, NavigationError, NavigationSkipped, NavigationSkippedCode, NavigationStart, ResolveEnd, ResolveStart, RouteConfigLoadEnd, RouteConfigLoadStart, RouterEvent, RoutesRecognized, Scroll} from './events';
|
||||
export {CanActivateChildFn, CanActivateFn, CanDeactivateFn, CanLoadFn, CanMatchFn, Data, DefaultExport, LoadChildren, LoadChildrenCallback, NavigationBehaviorOptions, OnSameUrlNavigation, QueryParamsHandling, ResolveData, ResolveFn, Route, Routes, RunGuardsAndResolvers, UrlMatcher, UrlMatchResult} from './models';
|
||||
export {
|
||||
ActivationEnd,
|
||||
ActivationStart,
|
||||
ChildActivationEnd,
|
||||
ChildActivationStart,
|
||||
Event,
|
||||
EventType,
|
||||
GuardsCheckEnd,
|
||||
GuardsCheckStart,
|
||||
NavigationCancel,
|
||||
NavigationCancellationCode as NavigationCancellationCode,
|
||||
NavigationEnd,
|
||||
NavigationError,
|
||||
NavigationSkipped,
|
||||
NavigationSkippedCode,
|
||||
NavigationStart,
|
||||
ResolveEnd,
|
||||
ResolveStart,
|
||||
RouteConfigLoadEnd,
|
||||
RouteConfigLoadStart,
|
||||
RouterEvent,
|
||||
RoutesRecognized,
|
||||
Scroll,
|
||||
} from './events';
|
||||
export {
|
||||
CanActivateChildFn,
|
||||
CanActivateFn,
|
||||
CanDeactivateFn,
|
||||
CanLoadFn,
|
||||
CanMatchFn,
|
||||
Data,
|
||||
DefaultExport,
|
||||
LoadChildren,
|
||||
LoadChildrenCallback,
|
||||
NavigationBehaviorOptions,
|
||||
OnSameUrlNavigation,
|
||||
QueryParamsHandling,
|
||||
ResolveData,
|
||||
ResolveFn,
|
||||
Route,
|
||||
Routes,
|
||||
RunGuardsAndResolvers,
|
||||
UrlMatcher,
|
||||
UrlMatchResult,
|
||||
} from './models';
|
||||
export {ViewTransitionInfo, ViewTransitionsFeatureOptions} from './utils/view_transition';
|
||||
|
||||
export * from './models_deprecated';
|
||||
export {Navigation, NavigationExtras, UrlCreationOptions} from './navigation_transition';
|
||||
export {DefaultTitleStrategy, TitleStrategy} from './page_title_strategy';
|
||||
export {DebugTracingFeature, DisabledInitialNavigationFeature, withViewTransitions, ViewTransitionsFeature, EnabledBlockingInitialNavigationFeature, InitialNavigationFeature, InMemoryScrollingFeature, NavigationErrorHandlerFeature, PreloadingFeature, provideRouter, provideRoutes, RouterConfigurationFeature, RouterFeature, RouterFeatures, RouterHashLocationFeature, withComponentInputBinding, withDebugTracing, withDisabledInitialNavigation, withEnabledBlockingInitialNavigation, withHashLocation, withInMemoryScrolling, withNavigationErrorHandler, withPreloading, withRouterConfig} from './provide_router';
|
||||
export {BaseRouteReuseStrategy, DetachedRouteHandle, RouteReuseStrategy} from './route_reuse_strategy';
|
||||
export {
|
||||
DebugTracingFeature,
|
||||
DisabledInitialNavigationFeature,
|
||||
withViewTransitions,
|
||||
ViewTransitionsFeature,
|
||||
EnabledBlockingInitialNavigationFeature,
|
||||
InitialNavigationFeature,
|
||||
InMemoryScrollingFeature,
|
||||
NavigationErrorHandlerFeature,
|
||||
PreloadingFeature,
|
||||
provideRouter,
|
||||
provideRoutes,
|
||||
RouterConfigurationFeature,
|
||||
RouterFeature,
|
||||
RouterFeatures,
|
||||
RouterHashLocationFeature,
|
||||
withComponentInputBinding,
|
||||
withDebugTracing,
|
||||
withDisabledInitialNavigation,
|
||||
withEnabledBlockingInitialNavigation,
|
||||
withHashLocation,
|
||||
withInMemoryScrolling,
|
||||
withNavigationErrorHandler,
|
||||
withPreloading,
|
||||
withRouterConfig,
|
||||
} from './provide_router';
|
||||
export {
|
||||
BaseRouteReuseStrategy,
|
||||
DetachedRouteHandle,
|
||||
RouteReuseStrategy,
|
||||
} from './route_reuse_strategy';
|
||||
export {Router} from './router';
|
||||
export {ExtraOptions, InitialNavigation, InMemoryScrollingOptions, ROUTER_CONFIGURATION, RouterConfigOptions} from './router_config';
|
||||
export {
|
||||
ExtraOptions,
|
||||
InitialNavigation,
|
||||
InMemoryScrollingOptions,
|
||||
ROUTER_CONFIGURATION,
|
||||
RouterConfigOptions,
|
||||
} from './router_config';
|
||||
export {ROUTES} from './router_config_loader';
|
||||
export {ROUTER_INITIALIZER, RouterModule} from './router_module';
|
||||
export {ChildrenOutletContexts, OutletContext} from './router_outlet_context';
|
||||
export {NoPreloading, PreloadAllModules, PreloadingStrategy, RouterPreloader} from './router_preloader';
|
||||
export {ActivatedRoute, ActivatedRouteSnapshot, RouterState, RouterStateSnapshot} from './router_state';
|
||||
export {
|
||||
NoPreloading,
|
||||
PreloadAllModules,
|
||||
PreloadingStrategy,
|
||||
RouterPreloader,
|
||||
} from './router_preloader';
|
||||
export {
|
||||
ActivatedRoute,
|
||||
ActivatedRouteSnapshot,
|
||||
RouterState,
|
||||
RouterStateSnapshot,
|
||||
} from './router_state';
|
||||
export {convertToParamMap, defaultUrlMatcher, ParamMap, Params, PRIMARY_OUTLET} from './shared';
|
||||
export {UrlHandlingStrategy} from './url_handling_strategy';
|
||||
export {DefaultUrlSerializer, IsActiveMatchOptions, UrlSegment, UrlSegmentGroup, UrlSerializer, UrlTree} from './url_tree';
|
||||
export {mapToCanActivate, mapToCanActivateChild, mapToCanDeactivate, mapToCanMatch, mapToResolve} from './utils/functional_guards';
|
||||
export {
|
||||
DefaultUrlSerializer,
|
||||
IsActiveMatchOptions,
|
||||
UrlSegment,
|
||||
UrlSegmentGroup,
|
||||
UrlSerializer,
|
||||
UrlTree,
|
||||
} from './url_tree';
|
||||
export {
|
||||
mapToCanActivate,
|
||||
mapToCanActivateChild,
|
||||
mapToCanDeactivate,
|
||||
mapToCanMatch,
|
||||
mapToResolve,
|
||||
} from './utils/functional_guards';
|
||||
export {VERSION} from './version';
|
||||
|
||||
export * from './private_export';
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user