mirror of
https://github.com/angular/angular.git
synced 2026-09-14 13:54:52 +08:00
Compare commits
34 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 51d53fc728 | |||
| be882c3efb | |||
| 18fa6d7556 | |||
| bb8a5a4c70 | |||
| 9c00506a26 | |||
| 79e8539ce2 | |||
| 6f79507ea7 | |||
| 6616019735 | |||
| 4d4858fa28 | |||
| 538fe47d9d | |||
| b07c549802 | |||
| 6f6ad0279b | |||
| d08f91f861 | |||
| 28aa7b3188 | |||
| 6ac0a68d73 | |||
| ea19bff302 | |||
| 1112e9196a | |||
| 0d8a5c4665 | |||
| 76ceebad04 | |||
| 99c2d76651 | |||
| 246cb9c381 | |||
| d7c75c6edb | |||
| 0daca457bb | |||
| 12f9ed01cc | |||
| 69713413f7 | |||
| 43d6816f9e | |||
| 4c4fea1304 | |||
| c6df89c827 | |||
| 9cae9b4a8c | |||
| 29b959ce31 | |||
| a24b24e8a4 | |||
| f51c008744 | |||
| 3dedd6a07a | |||
| 881376eb42 |
@@ -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@ae9155d1083fd8390e98bfe09a5ccf89f0375b52
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@e52eb8237f2ed71195f87ce8046467a176568e58
|
||||
- name: Setup Bazel
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@ae9155d1083fd8390e98bfe09a5ccf89f0375b52
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@e52eb8237f2ed71195f87ce8046467a176568e58
|
||||
- name: Setup Bazel RBE
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@ae9155d1083fd8390e98bfe09a5ccf89f0375b52
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@e52eb8237f2ed71195f87ce8046467a176568e58
|
||||
- name: Install node modules
|
||||
run: yarn install --frozen-lockfile
|
||||
- name: Build adev to ensure it continues to work
|
||||
run: yarn bazel build --config=aio_local_deps //adev:build
|
||||
- uses: angular/dev-infra/github-actions/previews/pack-and-upload-artifact@ae9155d1083fd8390e98bfe09a5ccf89f0375b52
|
||||
- uses: angular/dev-infra/github-actions/previews/pack-and-upload-artifact@73777547f819464aa5f48071b966eed34fd2c402
|
||||
with:
|
||||
workflow-artifact-name: 'adev-preview'
|
||||
pull-number: '${{github.event.pull_request.number}}'
|
||||
|
||||
@@ -40,11 +40,11 @@ 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@ae9155d1083fd8390e98bfe09a5ccf89f0375b52
|
||||
- uses: angular/dev-infra/github-actions/previews/upload-artifacts-to-firebase@73777547f819464aa5f48071b966eed34fd2c402
|
||||
with:
|
||||
github-token: '${{secrets.GITHUB_TOKEN}}'
|
||||
workflow-artifact-name: 'adev-preview'
|
||||
firebase-config-dir: './adev'
|
||||
firebase-public-dir: './adev/build/browser'
|
||||
firebase-public-dir: './dist/bin/adev/build/browser'
|
||||
firebase-project-id: '${{env.PREVIEW_PROJECT}}'
|
||||
firebase-service-key: '${{secrets.FIREBASE_PREVIEW_SERVICE_TOKEN}}'
|
||||
|
||||
@@ -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@ae9155d1083fd8390e98bfe09a5ccf89f0375b52
|
||||
- uses: angular/dev-infra/github-actions/bazel/configure-remote@7bd2697b40203c84826129b611e539fa663881f8
|
||||
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@ae9155d1083fd8390e98bfe09a5ccf89f0375b52
|
||||
- uses: angular/dev-infra/github-actions/previews/pack-and-upload-artifact@7bd2697b40203c84826129b611e539fa663881f8
|
||||
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@ae9155d1083fd8390e98bfe09a5ccf89f0375b52
|
||||
- uses: angular/dev-infra/github-actions/previews/upload-artifacts-to-firebase@7bd2697b40203c84826129b611e539fa663881f8
|
||||
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@ae9155d1083fd8390e98bfe09a5ccf89f0375b52
|
||||
- uses: angular/dev-infra/github-actions/branch-manager@7bd2697b40203c84826129b611e539fa663881f8
|
||||
with:
|
||||
angular-robot-key: ${{ secrets.ANGULAR_ROBOT_PRIVATE_KEY }}
|
||||
|
||||
@@ -38,7 +38,7 @@ jobs:
|
||||
|
||||
- uses: ./.github/actions/yarn-install
|
||||
|
||||
- uses: angular/dev-infra/github-actions/bazel/configure-remote@ae9155d1083fd8390e98bfe09a5ccf89f0375b52
|
||||
- uses: angular/dev-infra/github-actions/bazel/configure-remote@7bd2697b40203c84826129b611e539fa663881f8
|
||||
with:
|
||||
bazelrc: ./.bazelrc.user
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ jobs:
|
||||
SAUCE_ACCESS_KEY: ${{ secrets.SAUCE_ACCESS_KEY }}
|
||||
steps:
|
||||
- name: Initialize environment
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@ae9155d1083fd8390e98bfe09a5ccf89f0375b52
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@7bd2697b40203c84826129b611e539fa663881f8
|
||||
with:
|
||||
cache-node-modules: true
|
||||
# Checking out the pull request commit is intended here as we need to run the changed code tests.
|
||||
@@ -31,7 +31,7 @@ jobs:
|
||||
- name: Install node modules
|
||||
run: yarn install --frozen-lockfile
|
||||
- name: Setup Bazel
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@ae9155d1083fd8390e98bfe09a5ccf89f0375b52
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@7bd2697b40203c84826129b611e539fa663881f8
|
||||
- name: Starting Saucelabs tunnel service
|
||||
run: ./tools/saucelabs/sauce-service.sh run &
|
||||
# Build test fixtures for a test that rely on Bazel-generated fixtures. Note that disabling
|
||||
|
||||
+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@ae9155d1083fd8390e98bfe09a5ccf89f0375b52
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@7bd2697b40203c84826129b611e539fa663881f8
|
||||
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@ae9155d1083fd8390e98bfe09a5ccf89f0375b52
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@7bd2697b40203c84826129b611e539fa663881f8
|
||||
with:
|
||||
cache-node-modules: true
|
||||
- name: Setup Bazel
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@ae9155d1083fd8390e98bfe09a5ccf89f0375b52
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@7bd2697b40203c84826129b611e539fa663881f8
|
||||
- name: Setup Bazel RBE
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@ae9155d1083fd8390e98bfe09a5ccf89f0375b52
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@7bd2697b40203c84826129b611e539fa663881f8
|
||||
- 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@ae9155d1083fd8390e98bfe09a5ccf89f0375b52
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@7bd2697b40203c84826129b611e539fa663881f8
|
||||
with:
|
||||
cache-node-modules: true
|
||||
- name: Setup Bazel
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@ae9155d1083fd8390e98bfe09a5ccf89f0375b52
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@7bd2697b40203c84826129b611e539fa663881f8
|
||||
- name: Setup Bazel Remote Caching
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@ae9155d1083fd8390e98bfe09a5ccf89f0375b52
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@7bd2697b40203c84826129b611e539fa663881f8
|
||||
- name: Install node modules
|
||||
run: yarn install --frozen-lockfile --network-timeout 100000
|
||||
- name: Run CI tests for framework
|
||||
@@ -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@ae9155d1083fd8390e98bfe09a5ccf89f0375b52
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@7bd2697b40203c84826129b611e539fa663881f8
|
||||
with:
|
||||
cache-node-modules: true
|
||||
node-module-directories: |
|
||||
./aio/node_modules
|
||||
- name: Setup Bazel
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@ae9155d1083fd8390e98bfe09a5ccf89f0375b52
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@7bd2697b40203c84826129b611e539fa663881f8
|
||||
- name: Setup Bazel RBE
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@ae9155d1083fd8390e98bfe09a5ccf89f0375b52
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@7bd2697b40203c84826129b611e539fa663881f8
|
||||
- name: Install node modules for aio
|
||||
run: yarn install --cwd aio --frozen-lockfile
|
||||
- name: Run AIO tests with upstream packages
|
||||
@@ -115,11 +115,11 @@ jobs:
|
||||
labels: ubuntu-latest-4core
|
||||
steps:
|
||||
- name: Initialize environment
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@ae9155d1083fd8390e98bfe09a5ccf89f0375b52
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@e52eb8237f2ed71195f87ce8046467a176568e58
|
||||
- name: Setup Bazel
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@ae9155d1083fd8390e98bfe09a5ccf89f0375b52
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@e52eb8237f2ed71195f87ce8046467a176568e58
|
||||
- name: Setup Bazel RBE
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@ae9155d1083fd8390e98bfe09a5ccf89f0375b52
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@e52eb8237f2ed71195f87ce8046467a176568e58
|
||||
- name: Install node modules
|
||||
run: yarn install --frozen-lockfile
|
||||
- name: Build adev to ensure it continues to work
|
||||
@@ -130,15 +130,15 @@ jobs:
|
||||
labels: ubuntu-latest-4core
|
||||
steps:
|
||||
- name: Initialize environment
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@ae9155d1083fd8390e98bfe09a5ccf89f0375b52
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@7bd2697b40203c84826129b611e539fa663881f8
|
||||
with:
|
||||
cache-node-modules: true
|
||||
node-module-directories: |
|
||||
./aio/node_modules
|
||||
- name: Setup Bazel
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@ae9155d1083fd8390e98bfe09a5ccf89f0375b52
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@7bd2697b40203c84826129b611e539fa663881f8
|
||||
- name: Setup Bazel RBE
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@ae9155d1083fd8390e98bfe09a5ccf89f0375b52
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@7bd2697b40203c84826129b611e539fa663881f8
|
||||
- name: Install node modules
|
||||
run: yarn install --cwd aio --frozen-lockfile
|
||||
- name: Run AIO tests with local packages
|
||||
@@ -153,15 +153,15 @@ jobs:
|
||||
labels: ubuntu-latest
|
||||
steps:
|
||||
- name: Initialize environment
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@ae9155d1083fd8390e98bfe09a5ccf89f0375b52
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@7bd2697b40203c84826129b611e539fa663881f8
|
||||
with:
|
||||
cache-node-modules: true
|
||||
node-module-directories: |
|
||||
./aio/node_modules
|
||||
- name: Setup Bazel
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@ae9155d1083fd8390e98bfe09a5ccf89f0375b52
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@7bd2697b40203c84826129b611e539fa663881f8
|
||||
- name: Setup Bazel RBE
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@ae9155d1083fd8390e98bfe09a5ccf89f0375b52
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@7bd2697b40203c84826129b611e539fa663881f8
|
||||
- name: Install node modules for aio
|
||||
run: yarn install --cwd aio --frozen-lockfile
|
||||
- name: Set the stable branch environment variable
|
||||
@@ -197,13 +197,13 @@ jobs:
|
||||
labels: ubuntu-latest
|
||||
steps:
|
||||
- name: Initialize environment
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@ae9155d1083fd8390e98bfe09a5ccf89f0375b52
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@7bd2697b40203c84826129b611e539fa663881f8
|
||||
with:
|
||||
cache-node-modules: true
|
||||
- name: Setup Bazel
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@ae9155d1083fd8390e98bfe09a5ccf89f0375b52
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@7bd2697b40203c84826129b611e539fa663881f8
|
||||
- name: Setup Bazel RBE
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@ae9155d1083fd8390e98bfe09a5ccf89f0375b52
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@7bd2697b40203c84826129b611e539fa663881f8
|
||||
- name: Install node modules
|
||||
run: yarn install --frozen-lockfile
|
||||
- run: echo "https://${{secrets.SNAPSHOT_BUILDS_GITHUB_TOKEN}}:@github.com" > ${HOME}/.git_credentials
|
||||
@@ -215,7 +215,7 @@ jobs:
|
||||
labels: ubuntu-latest-4core
|
||||
steps:
|
||||
- name: Initialize environment
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@ae9155d1083fd8390e98bfe09a5ccf89f0375b52
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@7bd2697b40203c84826129b611e539fa663881f8
|
||||
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@ae9155d1083fd8390e98bfe09a5ccf89f0375b52
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@7bd2697b40203c84826129b611e539fa663881f8
|
||||
- name: Setup Bazel RBE
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@ae9155d1083fd8390e98bfe09a5ccf89f0375b52
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@7bd2697b40203c84826129b611e539fa663881f8
|
||||
- name: Install node modules
|
||||
run: yarn install --frozen-lockfile
|
||||
- run: |
|
||||
@@ -262,7 +262,7 @@ jobs:
|
||||
JOBS: 2
|
||||
steps:
|
||||
- name: Initialize environment
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@ae9155d1083fd8390e98bfe09a5ccf89f0375b52
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@7bd2697b40203c84826129b611e539fa663881f8
|
||||
with:
|
||||
cache-node-modules: true
|
||||
# Checking out the pull request commit is intended here as we need to run the changed code tests.
|
||||
@@ -270,9 +270,9 @@ jobs:
|
||||
- name: Install node modules
|
||||
run: yarn install --frozen-lockfile
|
||||
- name: Setup Bazel
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@ae9155d1083fd8390e98bfe09a5ccf89f0375b52
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@7bd2697b40203c84826129b611e539fa663881f8
|
||||
- name: Setup Bazel Remote Caching
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@ae9155d1083fd8390e98bfe09a5ccf89f0375b52
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@7bd2697b40203c84826129b611e539fa663881f8
|
||||
- name: Set up Sauce Tunnel Daemon
|
||||
run: yarn bazel run //tools/saucelabs-daemon/background-service -- $JOBS &
|
||||
env:
|
||||
|
||||
@@ -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@ae9155d1083fd8390e98bfe09a5ccf89f0375b52
|
||||
- uses: angular/dev-infra/github-actions/commit-message-based-labels@7bd2697b40203c84826129b611e539fa663881f8
|
||||
with:
|
||||
angular-robot-key: ${{ secrets.ANGULAR_ROBOT_PRIVATE_KEY }}
|
||||
post_approval_changes:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||
- uses: angular/dev-infra/github-actions/post-approval-changes@ae9155d1083fd8390e98bfe09a5ccf89f0375b52
|
||||
- uses: angular/dev-infra/github-actions/post-approval-changes@7bd2697b40203c84826129b611e539fa663881f8
|
||||
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@ae9155d1083fd8390e98bfe09a5ccf89f0375b52
|
||||
- uses: angular/dev-infra/github-actions/feature-request@7bd2697b40203c84826129b611e539fa663881f8
|
||||
with:
|
||||
angular-robot-key: ${{ secrets.ANGULAR_ROBOT_PRIVATE_KEY }}
|
||||
|
||||
@@ -14,7 +14,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||
- uses: angular/dev-infra/github-actions/google-internal-tests@ae9155d1083fd8390e98bfe09a5ccf89f0375b52
|
||||
- uses: angular/dev-infra/github-actions/google-internal-tests@7bd2697b40203c84826129b611e539fa663881f8
|
||||
with:
|
||||
run-tests-guide-url: http://go/angular-g3sync-start
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
@@ -9,6 +9,6 @@ jobs:
|
||||
status:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: angular/dev-infra/github-actions/unified-status-check@ae9155d1083fd8390e98bfe09a5ccf89f0375b52
|
||||
- uses: angular/dev-infra/github-actions/unified-status-check@7bd2697b40203c84826129b611e539fa663881f8
|
||||
with:
|
||||
angular-robot-key: ${{ secrets.ANGULAR_ROBOT_PRIVATE_KEY }}
|
||||
|
||||
@@ -22,15 +22,15 @@ jobs:
|
||||
version: ['https://next.angular.io/', 'https://angular.io/']
|
||||
steps:
|
||||
- name: Initialize environment
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@ae9155d1083fd8390e98bfe09a5ccf89f0375b52
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@7bd2697b40203c84826129b611e539fa663881f8
|
||||
with:
|
||||
cache-node-modules: true
|
||||
node-module-directories: |
|
||||
./aio/node_modules
|
||||
- name: Setup Bazel
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@ae9155d1083fd8390e98bfe09a5ccf89f0375b52
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@7bd2697b40203c84826129b611e539fa663881f8
|
||||
- name: Setup Bazel RBE
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@ae9155d1083fd8390e98bfe09a5ccf89f0375b52
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@7bd2697b40203c84826129b611e539fa663881f8
|
||||
- name: Install node modules in aio
|
||||
run: yarn install --frozen-lockfile --cwd aio
|
||||
- name: Run basic e2e and deployment config tests.
|
||||
@@ -44,7 +44,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Initialize environment
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@ae9155d1083fd8390e98bfe09a5ccf89f0375b52
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@7bd2697b40203c84826129b611e539fa663881f8
|
||||
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@ae9155d1083fd8390e98bfe09a5ccf89f0375b52
|
||||
uses: angular/dev-infra/github-actions/create-pr-for-changes@7bd2697b40203c84826129b611e539fa663881f8
|
||||
with:
|
||||
branch-prefix: update-cli-help
|
||||
pr-title: 'docs: update Angular CLI help [${{github.ref_name}}]'
|
||||
|
||||
@@ -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@ae9155d1083fd8390e98bfe09a5ccf89f0375b52
|
||||
uses: angular/dev-infra/github-actions/create-pr-for-changes@7bd2697b40203c84826129b611e539fa663881f8
|
||||
with:
|
||||
branch-prefix: docs-update-events
|
||||
pr-title: 'docs: update events'
|
||||
|
||||
@@ -7,15 +7,8 @@ export const format: FormatConfig = {
|
||||
'prettier': {
|
||||
'matchers': [
|
||||
'**/*.{yaml,yml}',
|
||||
'devtools/**/*.{js,ts}',
|
||||
'tools/**/*.{js,ts}',
|
||||
'modules/**/*.{js,ts}',
|
||||
'scripts/**/*.{js,ts}',
|
||||
'packages/animations/**/*.{js,ts}',
|
||||
'packages/bazel/**/*.{js,ts}',
|
||||
'packages/benchpress/**/*.{js,ts}',
|
||||
'packages/elements/**/*.{js,ts}',
|
||||
'packages/misc/**/*.{js,ts}',
|
||||
],
|
||||
},
|
||||
'clang-format': {
|
||||
@@ -44,15 +37,8 @@ export const format: FormatConfig = {
|
||||
'!adev/**',
|
||||
|
||||
// Migrated to prettier
|
||||
'!devtools/**/*.{js,ts}',
|
||||
'!tools/**/*.{js,ts}',
|
||||
'!modules/**/*.{js,ts}',
|
||||
'!scripts/**/*.{js,ts}',
|
||||
'!packages/animations/**/*.{js,ts}',
|
||||
'!packages/bazel/**/*.{js,ts}',
|
||||
'!packages/benchpress/**/*.{js,ts}',
|
||||
'!packages/elements/**/*.{js,ts}',
|
||||
'!packages/misc/**/*.{js,ts}',
|
||||
],
|
||||
},
|
||||
'buildifier': true,
|
||||
|
||||
@@ -31,6 +31,8 @@
|
||||
"packages/http/**",
|
||||
"**/.gitignore",
|
||||
"**/.gitkeep",
|
||||
"**/yarn.lock",
|
||||
"**/package.json",
|
||||
"**/third_party/**",
|
||||
"**/tsconfig-build.json",
|
||||
"**/tsconfig-tsec.json",
|
||||
|
||||
@@ -1150,7 +1150,6 @@ groups:
|
||||
- josephperrott
|
||||
- mgechev
|
||||
- twerske
|
||||
- ~jeanmeche
|
||||
|
||||
# =========================================================
|
||||
# Dev-infra
|
||||
@@ -1395,7 +1394,6 @@ groups:
|
||||
- devversion # Paul Gschwendtner
|
||||
- dgp1130 # Doug Parker
|
||||
- dylhunn # Dylan Hunn
|
||||
- jeanmeche # Matthieu Riegler
|
||||
- jelbourn # Jeremy Elbourn
|
||||
- jessicajaniuk # Jessica Janiuk
|
||||
- JiaLiPassion # Jia Li
|
||||
|
||||
@@ -61,25 +61,6 @@ Get started with Angular, learn the fundamentals and explore advanced topics on
|
||||
- [Lazy Loading][lazyloading]
|
||||
- [Animations][animations]
|
||||
|
||||
### Local Development
|
||||
|
||||
To contribute to Angular docs, you can setup a local environment with the following commands:
|
||||
|
||||
```bash
|
||||
# Clone Angular repo
|
||||
git clone https://github.com/angular/angular.git
|
||||
|
||||
# Navigate to project directory
|
||||
cd angular
|
||||
|
||||
# Install dependencies
|
||||
yarn
|
||||
|
||||
# Build and run local dev server
|
||||
# Note: Initial build will take some time
|
||||
yarn docs
|
||||
```
|
||||
|
||||
## Development Setup
|
||||
|
||||
### Prerequisites
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"hosting": {
|
||||
"target": "angular-docs",
|
||||
"public": "./build/browser",
|
||||
"public": "../dist/bin/adev/build/browser",
|
||||
"ignore": ["**/.*"],
|
||||
"headers": [
|
||||
{
|
||||
|
||||
@@ -78,7 +78,7 @@ For example, imagine the following scenario:
|
||||
1. After some time, a new version of the application is deployed to the server.
|
||||
This newer version includes the files `index.html`, `main.<main-hash-2>.js` and `lazy-chunk.<lazy-hash-2>.js`.
|
||||
|
||||
IMPORTANT: The hashes are different now, because the content of the files changed. The old version is no longer available on the server.
|
||||
IMPORANT: The hashes are different now, because the content of the files changed. The old version is no longer available on the server.
|
||||
|
||||
1. In the meantime, the user's browser decides to evict `lazy-chunk.<lazy-hash-1>.js` from its cache.
|
||||
Browsers might decide to evict specific (or all) resources from a cache in order to reclaim disk space.
|
||||
|
||||
@@ -240,13 +240,10 @@ In the example below, the prefetching starts when a browser becomes idle and the
|
||||
|
||||
## Testing
|
||||
|
||||
Angular provides TestBed APIs to simplify the process of testing `@defer` blocks and triggering different states during testing. By default, `@defer` blocks in tests will play through like a defer block would behave in a real application. If you want to manually step through states, you can switch the defer block behavior to `Manual` in the TestBed configuration.
|
||||
Angular provides TestBed APIs to simplify the process of testing `@defer` blocks and triggering different states during testing. By default, `@defer` blocks in tests are in "paused" (`@placeholder`) state, so that you can manually transition between states.
|
||||
|
||||
```typescript
|
||||
it('should render a defer block in different states', async () => {
|
||||
// configures the defer block behavior to start in "paused" state for manual control.
|
||||
TestBed.configureTestingModule({deferBlockBehavior: DeferBlockBehavior.Manual});
|
||||
|
||||
@Component({
|
||||
// ...
|
||||
template: `
|
||||
|
||||
@@ -11,7 +11,7 @@ The injection context is available in these situations:
|
||||
* In the `factory` function specified for an `InjectionToken`.
|
||||
* Within a stack frame that is run in a injection context.
|
||||
|
||||
Knowing when you are in an injection context, will allow you to use the [`inject`](api/core/inject) function to inject instances.
|
||||
Knowing when your are in an injection context, will allow you to use the [`inject`](api/core/inject) function to inject instances.
|
||||
|
||||
## Class constructors
|
||||
|
||||
@@ -33,7 +33,6 @@ class MyComponent {
|
||||
Some APIs are designed to be run in an injection context. This is the case, for example, of the router guards. It allows the use of [`inject`](api/core/inject) to access a service within the guard function.
|
||||
|
||||
Here is an example for `CanActivateFn`
|
||||
|
||||
<docs-code language="typescript" highlight="[3]">
|
||||
const canActivateTeam: CanActivateFn =
|
||||
(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) => {
|
||||
|
||||
@@ -50,7 +50,7 @@ const configPromise = firstValueFrom(config$);
|
||||
const req = httpTesting.expectOne('/api/config', 'Request to load the configuration');
|
||||
|
||||
// We can assert various properties of the request if desired.
|
||||
expect(req.request.method).toBe('GET');
|
||||
expect(req.method).toBe('GET');
|
||||
|
||||
// Flushing the request causes it to complete, delivering the result.
|
||||
req.flush(DEFAULT_CONFIG);
|
||||
|
||||
@@ -224,7 +224,7 @@ For details of those options and their possible values, see the [Angular CLI Ref
|
||||
|
||||
## Complex configuration values
|
||||
|
||||
The `assets`, `index`, `outputPath`, `styles`, and `scripts` options can have either simple path string values, or object values with specific fields.
|
||||
The `assets`, `index`, `styles`, and `scripts` options can have either simple path string values, or object values with specific fields.
|
||||
The `sourceMap` and `optimization` options can be set to a simple boolean value. They can also be given a complex value using the configuration file.
|
||||
|
||||
The following sections provide more details of how these complex values are used in each case.
|
||||
@@ -418,7 +418,6 @@ Several options can be used to fine-tune the optimization of an application.
|
||||
|:--- |:--- |:--- |:--- |
|
||||
| `minify` | Minify CSS definitions by removing extraneous whitespace and comments, merging identifiers, and minimizing values. | `boolean` | `true` |
|
||||
| `inlineCritical` | Extract and inline critical CSS definitions to improve [First Contentful Paint](https://web.dev/first-contentful-paint). | `boolean` | `true` |
|
||||
| `removeSpecialComments` | Remove comments in global CSS that contains `@license` or `@preserve` or that starts with `//!` or `/*!`. | `boolean` | `true` |
|
||||
|
||||
#### Fonts optimization options
|
||||
|
||||
@@ -509,16 +508,3 @@ When supplying the value as a string the filename of the specified path will be
|
||||
|:--- |:--- |:--- |:--- |
|
||||
| `input` | The path of a file to use for the application's generated HTML index. | `string` | None (required) |
|
||||
| `output` | The output path of the application's generated HTML index file. The full provided path will be used and will be considered relative to the application's configured output path. | `string` | `index.html` |
|
||||
|
||||
### Output path configuration
|
||||
|
||||
The `outputPath` option can be either a String which will be used as the `base` value or an Object for more fine-tune configuration.
|
||||
|
||||
Several options can be used to fine-tune the output structure of an application.
|
||||
|
||||
| Options | Details | Value type | Default value |
|
||||
|:--- |:--- |:--- |:--- |
|
||||
| `base` | Specify the output path relative to workspace root. | `string` | |
|
||||
| `browser` | The output directory name for your browser build is within the base output path. This can be safely served to users. | `string` | `browser` |
|
||||
| `server` | The output directory name of your server build within the output path base. | `string` | `server` |
|
||||
| `media` | The output directory name for your media files located within the output browser directory. These media files are commonly referred to as resources in CSS files. | `string` | `media` |
|
||||
|
||||
@@ -9,7 +9,6 @@ This table covers [Angular versions under active support](reference/releases#act
|
||||
|
||||
| Angular | Node.js | TypeScript | RxJS |
|
||||
| ------------------ | ------------------------------------ | -------------- | ------------------ |
|
||||
| 17.1.0 | ^18.13.0 \|\| ^20.9.0 | >=5.2.0 <5.4.0 | ^6.5.3 \|\| ^7.4.0 |
|
||||
| 17.0.x | ^18.13.0 \|\| ^20.9.0 | >=4.9.3 <5.3.0 | ^6.5.3 \|\| ^7.4.0 |
|
||||
| 16.1.x \|\| 16.2.x | ^16.14.0 \|\| ^18.10.0 | >=4.9.3 <5.2.0 | ^6.5.3 \|\| ^7.4.0 |
|
||||
| 16.0.x | ^16.14.0 \|\| ^18.10.0 | >=4.9.3 <5.1.0 | ^6.5.3 \|\| ^7.4.0 |
|
||||
|
||||
@@ -106,7 +106,7 @@ Also, the later sections of this guide provide additional information on several
|
||||
|
||||
For applications new to SSR, the [Angular SSR Guide](guide/ssr) provides additional information regarding the setup process for adding SSR to an application.
|
||||
|
||||
For applications that are already using SSR, additional adjustments will be needed to update the application server to support the new integrated SSR capabilities.
|
||||
For applications that are already using SSR, additional manual adjustments will be needed to update the application server to support the new integrated SSR capabilities.
|
||||
The `application` builder now provides the integrated functionality for all of the following preexisting builders:
|
||||
|
||||
- `app-shell`
|
||||
@@ -117,23 +117,25 @@ The `application` builder now provides the integrated functionality for all of t
|
||||
The `ng update` process will automatically remove usages of the `@nguniversal` scope packages where some of these builders were previously located.
|
||||
The new `@angular/ssr` package will also be automatically added and used with configuration and code being adjusted during the update.
|
||||
The `@angular/ssr` package supports the `browser` builder as well as the `application` builder.
|
||||
To convert from the separate SSR builders to the integrated capabilities of the `application` builder, run the experimental `use-application-builder` migration.
|
||||
To convert from the separate SSR builders to the integrated capabilities of the `application` builder, there are several required manual steps.
|
||||
However, as each application is different, there may be more application specific changes needed beyond these to complete the process.
|
||||
|
||||
<docs-code language="shell">
|
||||
1. Combine the options for the above mentioned SSR builders into the `application` builder options within the `angular.json` file.
|
||||
The previously used builders and their target configurations can then be fully removed from the file.
|
||||
2. Combine server TypeScript configuration from `tsconfig.server.json` into `tsconfig.app.json`.
|
||||
The `types` and `files` options are typically the only setting that needs to be combined but others may be needed based on application specific customizations.
|
||||
You should also add the TypeScript option `"esModuleInterop": true` to ensure `express` imports are [ESM compliant](#esm-default-imports-vs-namespace-imports).
|
||||
The `tsconfig.server.json` can then be removed as it will no longer be used during builds.
|
||||
3. Remove and/or update any `npm` scripts referencing the now removed builder targets.
|
||||
The `ng build` and `ng serve` commands provide equivalent functionality when using the `application` builder.
|
||||
4. Update application server code to remove Webpack specific elements.
|
||||
5. Update application server code to use new bootstrapping and output directory structure.
|
||||
An example of the changes for a v16 project that has been converted can be found [here](https://github.com/alan-agius4/angular-cli-use-application-builder/commit/1defdb93a7f508662bc427439e51505668bf84cd#diff-1ba718c1eb8aa39cd20c2562d92523068c734d75f54655e97d652b992d9b4259).
|
||||
6. Remove any CommonJS assumptions in the application server code such as `require`, `__filename`, `__dirname`, or other constructs from the [CommonJS module scope](https://nodejs.org/api/modules.html#the-module-scope).
|
||||
All application code should be ESM compatible.
|
||||
This does not apply to third-party dependencies.
|
||||
|
||||
ng update @angular/cli --name use-application-builder
|
||||
|
||||
</docs-code>
|
||||
|
||||
The migration does the following:
|
||||
|
||||
* Converts existing `browser` or `browser-esbuild` target to `application`
|
||||
* Removes any previous SSR builders (because `application` does that now).
|
||||
* Updates configuration accordingly.
|
||||
* Merges `tsconfig.server.json` with `tsconfig.app.json` and adds the TypeScript option `"esModuleInterop": true` to ensure `express` imports are [ESM compliant](#esm-default-imports-vs-namespace-imports).
|
||||
* Updates application server code to use new bootstrapping and output directory structure.
|
||||
|
||||
HELPFUL: Remember to remove any CommonJS assumptions in the application server code such as `require`, `__filename`, `__dirname`, or other constructs from the [CommonJS module scope](https://nodejs.org/api/modules.html#the-module-scope). All application code should be ESM compatible. This does not apply to third-party dependencies.
|
||||
In the future, a schematic will make this migration process easier for existing applications.
|
||||
|
||||
## Executing a build
|
||||
|
||||
@@ -173,6 +175,8 @@ Several build options are not yet implemented but will be added in the future as
|
||||
|
||||
- [WASM imports](https://github.com/angular/angular-cli/issues/25102) -- WASM can still be loaded manually via [standard web APIs](https://developer.mozilla.org/en-US/docs/WebAssembly/Loading_and_running).
|
||||
|
||||
Building libraries with the new build system via `ng-packagr` is also not yet possible but library build support will be available in a future release.
|
||||
|
||||
## ESM default imports vs. namespace imports
|
||||
|
||||
TypeScript by default allows default exports to be imported as namespace imports and then used in call expressions.
|
||||
|
||||
@@ -27,7 +27,7 @@ In the `HomeComponent` there is only a single housing location. In this step, yo
|
||||
1. Update the `HomeComponent` class to have a property called `housingLocationList`. Update your code to match the following code:
|
||||
<docs-code header="Add housingLocationList property" path="adev/src/content/tutorials/first-app/steps/09-services/src/app/home/home.component.ts" visibleLines="31-136"/>
|
||||
|
||||
IMPORTANT: Do not remove the `@Component` decorator, you will update that code in an upcoming step.
|
||||
IMPORANT: Do not remove the `@Component` decorator, you will update that code in an upcoming step.
|
||||
|
||||
</docs-step>
|
||||
|
||||
@@ -37,7 +37,7 @@ Now the app has a dataset that you can use to display the entries in the browser
|
||||
1. Update the `<app-housing-location>` tag in the template code to this:
|
||||
<docs-code header="Add ngFor to HomeComponent template" path="adev/src/content/tutorials/first-app/steps/09-services/src/app/home/home.component.ts" visibleLines="[21,24]"/>
|
||||
|
||||
Note, in the code `[housingLocation] = "housingLocation"` the `housingLocation` value now refers to the variable used in the `ngFor` directive. Before this change, it referred to the property on the `HomeComponent` class.
|
||||
Note, in the code `[housingLocation] = "housingLocation"` the `housingLocation` value now refers to the variable used in the `ngFor` directive. Before this change, it refered to the property on the `HomeComponent` class.
|
||||
|
||||
1. Save all changes.
|
||||
|
||||
|
||||
@@ -39,7 +39,7 @@ The `HomeComponent` already contains an input field that you will use to capture
|
||||
<input type="text" placeholder="Filter by city" #filter>
|
||||
</docs-code>
|
||||
|
||||
This example uses a [template reference variable](/guide/templates) to get access to the `input` element as its value.
|
||||
This example uses a [template reference variable](/guide/templatess) to get access to the `input` element as its value.
|
||||
|
||||
1. Next, update the component template to attach an event handler to the "Search" button.
|
||||
|
||||
|
||||
@@ -154,7 +154,7 @@ The data source has been configured, the next step is to update your web app to
|
||||
|
||||
1. Add a string property called `url` and set its value to `'http://localhost:3000/locations'`
|
||||
|
||||
<docs-code language="javascript">
|
||||
<docs-code anguage="javascript">
|
||||
url = 'http://localhost:3000/locations';
|
||||
</docs-code>
|
||||
|
||||
|
||||
@@ -58,7 +58,7 @@ Add a `@loading` block to the `@defer` block. The `@loading` block is where you
|
||||
|
||||
<docs-step title="Add a minimum duration">
|
||||
|
||||
Both `@placeholder` and `@loading` sections have optional parameters to prevent flickering from occurring when loading happens quickly. `@placeholder` has `minimum` and `@loading` has `minimum` and `after`. Add a `minimum` duration to the `@loading` block so it will be rendered for at least 2 seconds.
|
||||
Both `@placeholder` and `@loading` sections have optional parameters to prevent flickering from occuring when loading happens quickly. `@placeholder` has `minimum` and `@loading` has `minimum` and `after`. Add a `minimum` duration to the `@loading` block so it will be rendered for at least 2 seconds.
|
||||
|
||||
<docs-code language="html" highlight="[5]">
|
||||
@defer {
|
||||
|
||||
@@ -18,7 +18,7 @@ import { ReactiveFormsModule } from '@angular/forms';
|
||||
@Component({
|
||||
selector: 'app-root',
|
||||
standalone: true,
|
||||
template: `
|
||||
templateUrl: `
|
||||
<form>
|
||||
<label>Name
|
||||
<input type="text" />
|
||||
|
||||
@@ -43,4 +43,4 @@ Next, bind the `contentEditable` attribute of the `div` to the `isEditable` prop
|
||||
|
||||
The div is now editable. Nice work 👍
|
||||
|
||||
Property binding is one of Angular's many powerful features. If you'd like to learn more checkout [the Angular documentation](/guide/templates/property-binding).
|
||||
Property binding is one of Angular's many powerful features. If you'd like to learn more checkout [the Angular documention](/guide/templates/property-binding).
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Component Communication with `@Output`
|
||||
|
||||
When working with components it may be required to notify other components that something has happened. Perhaps a button has been clicked, an item has been added/removed from a list or some other important update has occurred. In this scenario components need to communicate with parent components.
|
||||
When working with components it may be required to notify other components that something has happened. Perhaps a button has been clicked, an item has been added/removed from a list or some other important update has occured. In this scenario components need to communicate with parent components.
|
||||
|
||||
Angular uses the `@Output` decorator to enable this type of behavior.
|
||||
|
||||
|
||||
@@ -240,13 +240,10 @@ In the example below, the prefetching starts when a browser becomes idle and the
|
||||
|
||||
## Testing
|
||||
|
||||
Angular provides TestBed APIs to simplify the process of testing `@defer` blocks and triggering different states during testing. By default, `@defer` blocks in tests will play through like a defer block would behave in a real application. If you want to manually step through states, you can switch the defer block behavior to `Manual` in the TestBed configuration.
|
||||
Angular provides TestBed APIs to simplify the process of testing `@defer` blocks and triggering different states during testing. By default, `@defer` blocks in tests are in "paused" (`@placeholder`) state, so that you can manually transition between states.
|
||||
|
||||
```typescript
|
||||
it('should render a defer block in different states', async () => {
|
||||
// configures the defer block behavior to start in "paused" state for manual control.
|
||||
TestBed.configureTestingModule({deferBlockBehavior: DeferBlockBehavior.Manual});
|
||||
|
||||
@Component({
|
||||
// ...
|
||||
template: `
|
||||
|
||||
@@ -96,12 +96,12 @@ The following table lists all the `browser` builder options that will need to be
|
||||
| `browser` Option | Action | Notes |
|
||||
| :-------------- | :----- | :----- |
|
||||
| `main` | rename option to `browser` | |
|
||||
| `polyfills` | convert value to an array | may already have been migrated |
|
||||
| `polyfills` | convert value to an array | may already have been migrated |
|
||||
| `buildOptimizer` | remove option |
|
||||
| `resourcesOutputPath` | move option to `outputPath.media` | defaults to `media` |
|
||||
| `resourcesOutputPath` | remove option | always `media` |
|
||||
| `vendorChunk` | remove option |
|
||||
| `commonChunk` | remove option |
|
||||
| `deployUrl` | remove option |
|
||||
| `deployUrl` | remove option |
|
||||
| `ngswConfigPath` | move value to `serviceWorker` and remove option | `serviceWorker` is now either `false` or a configuration path
|
||||
|
||||
|
||||
@@ -113,7 +113,7 @@ Also, the later sections of this guide provide additional information on several
|
||||
|
||||
For applications new to SSR, the [Angular SSR Guide](/guide/ssr) provides additional information regarding the setup process for adding SSR to an application.
|
||||
|
||||
For applications that are already using SSR, additional adjustments will be needed to update the application server to support the new integrated SSR capabilities.
|
||||
For applications that are already using SSR, additional manual adjustments will be needed to update the application server to support the new integrated SSR capabilities.
|
||||
The `application` builder now provides the integrated functionality for all of the following preexisting builders:
|
||||
|
||||
* `app-shell`
|
||||
@@ -124,27 +124,25 @@ The `application` builder now provides the integrated functionality for all of t
|
||||
The `ng update` process will automatically remove usages of the `@nguniversal` scope packages where some of these builders were previously located.
|
||||
The new `@angular/ssr` package will also be automatically added and used with configuration and code being adjusted during the update.
|
||||
The `@angular/ssr` package supports the `browser` builder as well as the `application` builder.
|
||||
To convert from the separate SSR builders to the integrated capabilities of the `application` builder, run the experimental `use-application-builder` migration.
|
||||
To convert from the separate SSR builders to the integrated capabilities of the `application` builder, there are several required manual steps.
|
||||
However, as each application is different, there may be more application specific changes needed beyond these to complete the process.
|
||||
|
||||
<code-example language="shell">
|
||||
1. Combine the options for the above mentioned SSR builders into the `application` builder options within the `angular.json` file.
|
||||
The previously used builders and their target configurations can then be fully removed from the file.
|
||||
2. Combine server TypeScript configuration from `tsconfig.server.json` into `tsconfig.app.json`.
|
||||
The `types` and `files` options are typically the only setting that needs to be combined but others may be needed based on application specific customizations.
|
||||
You should also add the TypeScript option `"esModuleInterop": true` to ensure `express` imports are [ESM compliant](#esm-default-imports-vs-namespace-imports).
|
||||
The `tsconfig.server.json` can then be removed as it will no longer be used during builds.
|
||||
3. Remove and/or update any `npm` scripts referencing the now removed builder targets.
|
||||
The `ng build` and `ng serve` commands provide equivalent functionality when using the `application` builder.
|
||||
4. Update application server code to remove Webpack specific elements.
|
||||
5. Update application server code to use new bootstrapping and output directory structure.
|
||||
An example of the changes for a v16 project that has been converted can be found [here](https://github.com/alan-agius4/angular-cli-use-application-builder/commit/1defdb93a7f508662bc427439e51505668bf84cd#diff-1ba718c1eb8aa39cd20c2562d92523068c734d75f54655e97d652b992d9b4259).
|
||||
6. Remove any CommonJS assumptions in the application server code such as `require`, `__filename`, `__dirname`, or other constructs from the [CommonJS module scope](https://nodejs.org/api/modules.html#the-module-scope).
|
||||
All application code should be ESM compatible.
|
||||
This does not apply to third-party dependencies.
|
||||
|
||||
ng update @angular/cli --name use-application-builder
|
||||
|
||||
</code-example>
|
||||
|
||||
The migration does the following:
|
||||
|
||||
* Converts existing `browser` or `browser-esbuild` target to `application`
|
||||
* Removes any previous SSR builders (because `application` does that now).
|
||||
* Updates configuration accordingly.
|
||||
* Merges `tsconfig.server.json` with `tsconfig.app.json` and adds the TypeScript option `"esModuleInterop": true` to ensure `express` imports are [ESM compliant](#esm-default-imports-vs-namespace-imports).
|
||||
* Updates application server code to use new bootstrapping and output directory structure.
|
||||
|
||||
<div class="alert is-important">
|
||||
|
||||
Remember to remove any CommonJS assumptions in the application server code such as `require`, `__filename`, `__dirname`, or other constructs from the [CommonJS module scope](https://nodejs.org/api/modules.html#the-module-scope). All application code should be ESM compatible. This does not apply to third-party dependencies.
|
||||
|
||||
</div>
|
||||
In the future, a schematic will make this migration process easier for existing applications.
|
||||
|
||||
## Executing a build
|
||||
|
||||
|
||||
@@ -10,7 +10,6 @@ covers [Angular versions under active support](guide/releases#actively-supported
|
||||
|
||||
| Angular | Node.js | TypeScript | RxJS |
|
||||
| ------------------ | ------------------------------------ | -------------- | ------------------ |
|
||||
| 17.1.0 | ^18.13.0 \|\| ^20.9.0 | >=5.2.0 <5.4.0 | ^6.5.3 \|\| ^7.4.0 |
|
||||
| 17.0.x | ^18.13.0 \|\| ^20.9.0 | >=4.9.3 <5.3.0 | ^6.5.3 \|\| ^7.4.0 |
|
||||
| 16.1.x \|\| 16.2.x | ^16.14.0 \|\| ^18.10.0 | >=4.9.3 <5.2.0 | ^6.5.3 \|\| ^7.4.0 |
|
||||
| 16.0.x | ^16.14.0 \|\| ^18.10.0 | >=4.9.3 <5.1.0 | ^6.5.3 \|\| ^7.4.0 |
|
||||
|
||||
@@ -311,7 +311,6 @@ Standalone components are a new organizational pattern that were introduced in A
|
||||
For example, in the traditional NgModule pattern, you would need to create a TodoModule and manage all of its dependencies through this module.
|
||||
|
||||
```ts (Todo.module.ts)
|
||||
import {NgModule} from '@angular/core';
|
||||
import {FormsModule} from '@angular/forms';
|
||||
import {TodoList} from '../todo/todo-list.component';
|
||||
|
||||
@@ -326,6 +325,7 @@ export class TodoModule {}
|
||||
However, you can now achieve something similar with a standalone component without the need for a module file:
|
||||
|
||||
```ts (Todo.component.ts)
|
||||
import {NgModule} from '@angular/core';
|
||||
import {FormsModule} from '@angular/forms';
|
||||
import {TodoList} from '../todo/todo-list.component';
|
||||
|
||||
|
||||
@@ -246,11 +246,12 @@ Some extra options can only be set through the configuration file, either by dir
|
||||
| `fileReplacements` | An object containing files and their compile-time replacements. See more in [Configure target-specific file replacements](guide/build#configure-target-specific-file-replacements). |
|
||||
`index` | Configures the generation of the application's HTML index. See more in [Index configuration](#index-config). \(Only available in `browser` section.\) | |
|
||||
|
||||
|
||||
<a id="complex-config"></a>
|
||||
|
||||
## Complex configuration values
|
||||
|
||||
The `assets`, `index`, `outputPath`, `styles`, and `scripts` options can have either simple path string values, or object values with specific fields.
|
||||
The `assets`, `index`, `styles`, and `scripts` options can have either simple path string values, or object values with specific fields.
|
||||
The `sourceMap` and `optimization` options can be set to a simple Boolean value with a command flag. They can also be given a complex value using the configuration file.
|
||||
|
||||
The following sections provide more details of how these complex values are used in each case.
|
||||
@@ -426,7 +427,7 @@ See also [Using runtime-global libraries inside your application](guide/using-li
|
||||
|
||||
### Optimization configuration
|
||||
|
||||
The `optimization` option can be either a Boolean or an Object for more fine-tune configuration.
|
||||
The `optimization` browser builder option can be either a Boolean or an Object for more fine-tune configuration.
|
||||
This option enables various optimizations of the build output, including:
|
||||
|
||||
<!-- vale Angular.Angular_Spelling = NO-->
|
||||
@@ -453,9 +454,8 @@ Several options can be used to fine-tune the optimization of an application.
|
||||
|
||||
| Options | Details | Value type | Default value |
|
||||
|:--- |:--- |:--- |:--- |
|
||||
| `minify` | Minify CSS definitions by removing extraneous whitespace and comments, merging identifiers, and minimizing values. | `boolean` | `true` |
|
||||
| `minify` | Minify CSS definitions by removing extraneous whitespace and comments, merging identifiers, and minimizing values. | `boolean` | `true` |
|
||||
| `inlineCritical` | Extract and inline critical CSS definitions to improve [First Contentful Paint](https://web.dev/first-contentful-paint). | `boolean` | `true` |
|
||||
| `removeSpecialComments` | Remove comments in global CSS that contains `@license` or `@preserve` or that starts with `//!` or `/*!`. | `boolean` | `true` |
|
||||
|
||||
<div class="alert is-helpful">
|
||||
|
||||
@@ -539,18 +539,6 @@ When supplying the value as a String the filename of the specified path will be
|
||||
| `input` | The path of a file to use for the application's generated HTML index. | `string` | |
|
||||
| `output` | The output path of the application's generated HTML index file. The full provided path will be used and will be considered relative to the application's configured output path. | `string` | `index.html` |
|
||||
|
||||
### Output path configuration
|
||||
|
||||
The `outputPath` option can be either a String which will be used as the `base` value or an Object for more fine-tune configuration.
|
||||
|
||||
Several options can be used to fine-tune the output structure of an application.
|
||||
|
||||
| Options | Details | Value type | Default value |
|
||||
|:--- |:--- |:--- |:--- |
|
||||
| `base` | Specify the output path relative to workspace root. | `string` | |
|
||||
| `browser` | The output directory name for your browser build is within the base output path. This can be safely served to users. | `string` | `browser` |
|
||||
| `server` | The output directory name of your server build within the output path base. | `string` | `server` |
|
||||
| `media` | The output directory name for your media files located within the output browser directory. These media files are commonly referred to as resources in CSS files. | `string` | `media` |
|
||||
|
||||
<!-- links -->
|
||||
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 130 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 338 KiB After Width: | Height: | Size: 26 KiB |
@@ -108,13 +108,6 @@
|
||||
"bio": "SDE III @cisco | GDE for Angular | Microsoft MVP | Author | Speaker | Passionate Programmer",
|
||||
"groups": ["GDE"]
|
||||
},
|
||||
"ankitasood": {
|
||||
"name": "Ankita Sood",
|
||||
"picture": "ankita.jpg",
|
||||
"groups": ["GDE"],
|
||||
"twitter": "guacamoleankita",
|
||||
"bio": "Ankita is an Angular developer, mentor and speaker. She is passionate about all things Angular and Design."
|
||||
},
|
||||
"antoniocardenas": {
|
||||
"name": "Antonio Cardenas",
|
||||
"picture": "antonio-cardenas.jpg",
|
||||
@@ -163,7 +156,8 @@
|
||||
"name": "Aristeidis Bampakos",
|
||||
"picture": "aristeidis.jpg",
|
||||
"twitter": "abampakos",
|
||||
"bio": "Aristeidis works as a Web Development Team Leader @ Plex-Earth, specializing in Angular development. He is the author of Learning Angular and Angular Projects books and also works as an Angular Senior Tech Instructor @ Code.Hub where he nurtures aspiring Angular developers and professionals. Aristeidis is currently leading the effort of making Angular accessible to the Greek development community by maintaining the Greek translation of the Angular documentation.",
|
||||
"website": "https://medium.com/@abampakos",
|
||||
"bio": "Aristeidis is a web development team lead at Plex-Earth working with Angular. He is the author of two books about Angular: Learning Angular - 3rd Edition and Angular Projects - 2nd Edition. He also works as an Angular senior tech instructor at Code.Hub where he teaches the Angular framework to other developers and professionals.",
|
||||
"groups": ["GDE"]
|
||||
},
|
||||
"bbrennan": {
|
||||
|
||||
@@ -255,7 +255,7 @@ Now that you've configured your application to retrieve shipping data, you can c
|
||||
|
||||
This section guides you through modifying the `ShippingComponent` to retrieve shipping data via HTTP from the `shipping.json` file.
|
||||
|
||||
1. In `shipping.component.ts`, import `OnInit`, `Observable` and `CartService`.
|
||||
1. In `shipping.component.ts`, import `CartService`.
|
||||
|
||||
<code-example header="src/app/shipping/shipping.component.ts" path="getting-started/src/app/shipping/shipping.component.ts" region="imports"></code-example>
|
||||
|
||||
|
||||
+19
-19
@@ -61,33 +61,33 @@
|
||||
},
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@angular/animations": "17.1.0-rc.0",
|
||||
"@angular/cdk": "17.1.0-rc.0",
|
||||
"@angular/common": "17.1.0-rc.0",
|
||||
"@angular/compiler": "17.1.0-rc.0",
|
||||
"@angular/core": "17.1.0-rc.0",
|
||||
"@angular/elements": "17.1.0-rc.0",
|
||||
"@angular/forms": "17.1.0-rc.0",
|
||||
"@angular/material": "17.1.0-rc.0",
|
||||
"@angular/platform-browser": "17.1.0-rc.0",
|
||||
"@angular/platform-browser-dynamic": "17.1.0-rc.0",
|
||||
"@angular/router": "17.1.0-rc.0",
|
||||
"@angular/service-worker": "17.1.0-rc.0",
|
||||
"@angular/animations": "17.1.0-next.5",
|
||||
"@angular/cdk": "17.1.0-next.3",
|
||||
"@angular/common": "17.1.0-next.5",
|
||||
"@angular/compiler": "17.1.0-next.5",
|
||||
"@angular/core": "17.1.0-next.5",
|
||||
"@angular/elements": "17.1.0-next.5",
|
||||
"@angular/forms": "17.1.0-next.5",
|
||||
"@angular/material": "17.1.0-next.3",
|
||||
"@angular/platform-browser": "17.1.0-next.5",
|
||||
"@angular/platform-browser-dynamic": "17.1.0-next.5",
|
||||
"@angular/router": "17.1.0-next.5",
|
||||
"@angular/service-worker": "17.1.0-next.5",
|
||||
"rxjs": "~7.8.0",
|
||||
"safevalues": "^0.5.0",
|
||||
"tslib": "^2.3.0",
|
||||
"zone.js": "~0.14.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@angular-devkit/architect-cli": "0.1701.0-rc.1",
|
||||
"@angular-devkit/build-angular": "17.1.0-rc.1",
|
||||
"@angular-devkit/architect-cli": "0.1701.0-rc.0",
|
||||
"@angular-devkit/build-angular": "17.1.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#8a32f9fd9af99832f337db9fe29842797121e96d",
|
||||
"@angular/cli": "17.1.0-rc.1",
|
||||
"@angular/compiler-cli": "17.1.0-rc.0",
|
||||
"@angular/build-tooling": "https://github.com/angular/dev-infra-private-build-tooling-builds.git#6e00fd0afb199cc491e800986970ee6df67d8226",
|
||||
"@angular/cli": "17.1.0-rc.0",
|
||||
"@angular/compiler-cli": "17.1.0-next.5",
|
||||
"@bazel/bazelisk": "^1.7.5",
|
||||
"@bazel/buildozer": "^6.0.0",
|
||||
"@bazel/ibazel": "^0.16.2",
|
||||
@@ -110,8 +110,8 @@
|
||||
"dgeni": "^0.4.14",
|
||||
"dgeni-packages": "^0.30.0",
|
||||
"entities": "^4.0.0",
|
||||
"eslint": "8.56.0",
|
||||
"eslint-plugin-import": "2.29.1",
|
||||
"eslint": "8.51.0",
|
||||
"eslint-plugin-import": "2.28.1",
|
||||
"eslint-plugin-jasmine": "4.1.3",
|
||||
"eslint-plugin-jsdoc": "46.8.2",
|
||||
"eslint-plugin-prefer-arrow": "1.2.3",
|
||||
|
||||
@@ -13,25 +13,25 @@
|
||||
},
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@angular/animations": "17.1.0",
|
||||
"@angular/common": "17.1.0",
|
||||
"@angular/compiler": "17.1.0",
|
||||
"@angular/core": "17.1.0",
|
||||
"@angular/forms": "17.1.0",
|
||||
"@angular/platform-browser": "17.1.0",
|
||||
"@angular/platform-browser-dynamic": "17.1.0",
|
||||
"@angular/router": "17.1.0",
|
||||
"@angular/animations": "17.0.8",
|
||||
"@angular/common": "17.0.8",
|
||||
"@angular/compiler": "17.0.8",
|
||||
"@angular/core": "17.0.8",
|
||||
"@angular/forms": "17.0.8",
|
||||
"@angular/platform-browser": "17.0.8",
|
||||
"@angular/platform-browser-dynamic": "17.0.8",
|
||||
"@angular/router": "17.0.8",
|
||||
"angular": "^1.8.3",
|
||||
"angular-in-memory-web-api": "~0.17.0",
|
||||
"angular-in-memory-web-api": "~0.16.0",
|
||||
"angular-route": "1.8.3",
|
||||
"rxjs": "~7.8.0",
|
||||
"tslib": "^2.3.0",
|
||||
"zone.js": "~0.14.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@angular-devkit/build-angular": "17.1.0",
|
||||
"@angular/cli": "17.1.0",
|
||||
"@angular/compiler-cli": "17.1.0",
|
||||
"@angular-devkit/build-angular": "17.0.9",
|
||||
"@angular/cli": "17.0.9",
|
||||
"@angular/compiler-cli": "17.0.8",
|
||||
"@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.0",
|
||||
"@angular/common": "17.1.0",
|
||||
"@angular/compiler": "17.1.0",
|
||||
"@angular/core": "17.1.0",
|
||||
"@angular/forms": "17.1.0",
|
||||
"@angular/platform-browser": "17.1.0",
|
||||
"@angular/platform-browser-dynamic": "17.1.0",
|
||||
"@angular/router": "17.1.0",
|
||||
"angular-in-memory-web-api": "~0.17.0",
|
||||
"@angular/animations": "17.0.8",
|
||||
"@angular/common": "17.0.8",
|
||||
"@angular/compiler": "17.0.8",
|
||||
"@angular/core": "17.0.8",
|
||||
"@angular/forms": "17.0.8",
|
||||
"@angular/platform-browser": "17.0.8",
|
||||
"@angular/platform-browser-dynamic": "17.0.8",
|
||||
"@angular/router": "17.0.8",
|
||||
"angular-in-memory-web-api": "~0.16.0",
|
||||
"rxjs": "~7.8.0",
|
||||
"tslib": "^2.3.0",
|
||||
"zone.js": "~0.14.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@angular-devkit/build-angular": "17.1.0",
|
||||
"@angular/cli": "17.1.0",
|
||||
"@angular/compiler-cli": "17.1.0",
|
||||
"@angular-devkit/build-angular": "17.0.9",
|
||||
"@angular/cli": "17.0.9",
|
||||
"@angular/compiler-cli": "17.0.8",
|
||||
"@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.0",
|
||||
"@angular/common": "17.1.0",
|
||||
"@angular/compiler": "17.1.0",
|
||||
"@angular/core": "17.1.0",
|
||||
"@angular/elements": "17.1.0",
|
||||
"@angular/forms": "17.1.0",
|
||||
"@angular/platform-browser": "17.1.0",
|
||||
"@angular/platform-browser-dynamic": "17.1.0",
|
||||
"@angular/router": "17.1.0",
|
||||
"angular-in-memory-web-api": "~0.17.0",
|
||||
"@angular/animations": "17.0.8",
|
||||
"@angular/common": "17.0.8",
|
||||
"@angular/compiler": "17.0.8",
|
||||
"@angular/core": "17.0.8",
|
||||
"@angular/elements": "17.0.8",
|
||||
"@angular/forms": "17.0.8",
|
||||
"@angular/platform-browser": "17.0.8",
|
||||
"@angular/platform-browser-dynamic": "17.0.8",
|
||||
"@angular/router": "17.0.8",
|
||||
"angular-in-memory-web-api": "~0.16.0",
|
||||
"rxjs": "~7.8.0",
|
||||
"tslib": "^2.3.0",
|
||||
"zone.js": "~0.14.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@angular-devkit/build-angular": "17.1.0",
|
||||
"@angular/cli": "17.1.0",
|
||||
"@angular/compiler-cli": "17.1.0",
|
||||
"@angular-devkit/build-angular": "17.0.9",
|
||||
"@angular/cli": "17.0.9",
|
||||
"@angular/compiler-cli": "17.0.8",
|
||||
"@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.0",
|
||||
"@angular/common": "17.1.0",
|
||||
"@angular/compiler": "17.1.0",
|
||||
"@angular/core": "17.1.0",
|
||||
"@angular/forms": "17.1.0",
|
||||
"@angular/localize": "17.1.0",
|
||||
"@angular/platform-browser": "17.1.0",
|
||||
"@angular/platform-browser-dynamic": "17.1.0",
|
||||
"@angular/router": "17.1.0",
|
||||
"angular-in-memory-web-api": "~0.17.0",
|
||||
"@angular/animations": "17.0.8",
|
||||
"@angular/common": "17.0.8",
|
||||
"@angular/compiler": "17.0.8",
|
||||
"@angular/core": "17.0.8",
|
||||
"@angular/forms": "17.0.8",
|
||||
"@angular/localize": "17.0.8",
|
||||
"@angular/platform-browser": "17.0.8",
|
||||
"@angular/platform-browser-dynamic": "17.0.8",
|
||||
"@angular/router": "17.0.8",
|
||||
"angular-in-memory-web-api": "~0.16.0",
|
||||
"rxjs": "~7.8.0",
|
||||
"tslib": "^2.3.0",
|
||||
"zone.js": "~0.14.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@angular-devkit/build-angular": "17.1.0",
|
||||
"@angular/cli": "17.1.0",
|
||||
"@angular/compiler-cli": "17.1.0",
|
||||
"@angular-devkit/build-angular": "17.0.9",
|
||||
"@angular/cli": "17.0.9",
|
||||
"@angular/compiler-cli": "17.0.8",
|
||||
"@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.0",
|
||||
"@angular/common": "17.1.0",
|
||||
"@angular/compiler": "17.1.0",
|
||||
"@angular/core": "17.1.0",
|
||||
"@angular/forms": "17.1.0",
|
||||
"@angular/platform-browser": "17.1.0",
|
||||
"@angular/platform-browser-dynamic": "17.1.0",
|
||||
"@angular/router": "17.1.0",
|
||||
"@angular/service-worker": "17.1.0",
|
||||
"angular-in-memory-web-api": "~0.17.0",
|
||||
"@angular/animations": "17.0.8",
|
||||
"@angular/common": "17.0.8",
|
||||
"@angular/compiler": "17.0.8",
|
||||
"@angular/core": "17.0.8",
|
||||
"@angular/forms": "17.0.8",
|
||||
"@angular/platform-browser": "17.0.8",
|
||||
"@angular/platform-browser-dynamic": "17.0.8",
|
||||
"@angular/router": "17.0.8",
|
||||
"@angular/service-worker": "17.0.8",
|
||||
"angular-in-memory-web-api": "~0.16.0",
|
||||
"rxjs": "~7.8.0",
|
||||
"tslib": "^2.3.0",
|
||||
"zone.js": "~0.14.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@angular-devkit/build-angular": "17.1.0",
|
||||
"@angular/cli": "17.1.0",
|
||||
"@angular/compiler-cli": "17.1.0",
|
||||
"@angular-devkit/build-angular": "17.0.9",
|
||||
"@angular/cli": "17.0.9",
|
||||
"@angular/compiler-cli": "17.0.8",
|
||||
"@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.0",
|
||||
"@angular/common": "17.1.0",
|
||||
"@angular/compiler": "17.1.0",
|
||||
"@angular/core": "17.1.0",
|
||||
"@angular/forms": "17.1.0",
|
||||
"@angular/platform-browser": "17.1.0",
|
||||
"@angular/platform-browser-dynamic": "17.1.0",
|
||||
"@angular/router": "17.1.0",
|
||||
"@angular/upgrade": "17.1.0",
|
||||
"@angular/animations": "17.0.8",
|
||||
"@angular/common": "17.0.8",
|
||||
"@angular/compiler": "17.0.8",
|
||||
"@angular/core": "17.0.8",
|
||||
"@angular/forms": "17.0.8",
|
||||
"@angular/platform-browser": "17.0.8",
|
||||
"@angular/platform-browser-dynamic": "17.0.8",
|
||||
"@angular/router": "17.0.8",
|
||||
"@angular/upgrade": "17.0.8",
|
||||
"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.0",
|
||||
"@angular/compiler-cli": "17.0.8",
|
||||
"@rollup/plugin-commonjs": "^25.0.0",
|
||||
"@rollup/plugin-node-resolve": "^13.2.0",
|
||||
"@types/angular": "^1.8.4",
|
||||
|
||||
@@ -13,26 +13,26 @@
|
||||
},
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@angular/animations": "17.1.0",
|
||||
"@angular/common": "17.1.0",
|
||||
"@angular/compiler": "17.1.0",
|
||||
"@angular/core": "17.1.0",
|
||||
"@angular/forms": "17.1.0",
|
||||
"@angular/platform-browser": "17.1.0",
|
||||
"@angular/platform-browser-dynamic": "17.1.0",
|
||||
"@angular/platform-server": "17.1.0",
|
||||
"@angular/router": "17.1.0",
|
||||
"@angular/ssr": "17.1.0",
|
||||
"angular-in-memory-web-api": "~0.17.0",
|
||||
"@angular/animations": "17.0.8",
|
||||
"@angular/common": "17.0.8",
|
||||
"@angular/compiler": "17.0.8",
|
||||
"@angular/core": "17.0.8",
|
||||
"@angular/forms": "17.0.8",
|
||||
"@angular/platform-browser": "17.0.8",
|
||||
"@angular/platform-browser-dynamic": "17.0.8",
|
||||
"@angular/platform-server": "17.0.8",
|
||||
"@angular/router": "17.0.8",
|
||||
"@angular/ssr": "17.0.9",
|
||||
"angular-in-memory-web-api": "~0.16.0",
|
||||
"express": "^4.18.1",
|
||||
"rxjs": "~7.8.0",
|
||||
"tslib": "^2.3.0",
|
||||
"zone.js": "~0.14.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@angular-devkit/build-angular": "17.1.0",
|
||||
"@angular/cli": "17.1.0",
|
||||
"@angular/compiler-cli": "17.1.0",
|
||||
"@angular-devkit/build-angular": "17.0.9",
|
||||
"@angular/cli": "17.0.9",
|
||||
"@angular/compiler-cli": "17.0.8",
|
||||
"@types/express": "^4.17.13",
|
||||
"@types/jasmine": "~5.1.0",
|
||||
"@types/node": "^16.11.35",
|
||||
|
||||
@@ -19,22 +19,22 @@
|
||||
"license": "MIT",
|
||||
"repository": {},
|
||||
"dependencies": {
|
||||
"@angular/animations": "17.1.0",
|
||||
"@angular/common": "17.1.0",
|
||||
"@angular/compiler": "17.1.0",
|
||||
"@angular/core": "17.1.0",
|
||||
"@angular/elements": "17.1.0",
|
||||
"@angular/forms": "17.1.0",
|
||||
"@angular/localize": "17.1.0",
|
||||
"@angular/platform-browser": "17.1.0",
|
||||
"@angular/platform-browser-dynamic": "17.1.0",
|
||||
"@angular/platform-server": "17.1.0",
|
||||
"@angular/router": "17.1.0",
|
||||
"@angular/service-worker": "17.1.0",
|
||||
"@angular/ssr": "17.1.0",
|
||||
"@angular/upgrade": "17.1.0",
|
||||
"@angular/animations": "17.0.8",
|
||||
"@angular/common": "17.0.8",
|
||||
"@angular/compiler": "17.0.8",
|
||||
"@angular/core": "17.0.8",
|
||||
"@angular/elements": "17.0.8",
|
||||
"@angular/forms": "17.0.8",
|
||||
"@angular/localize": "17.0.8",
|
||||
"@angular/platform-browser": "17.0.8",
|
||||
"@angular/platform-browser-dynamic": "17.0.8",
|
||||
"@angular/platform-server": "17.0.8",
|
||||
"@angular/router": "17.0.8",
|
||||
"@angular/service-worker": "17.0.8",
|
||||
"@angular/ssr": "17.0.9",
|
||||
"@angular/upgrade": "17.0.8",
|
||||
"angular": "^1.8.3",
|
||||
"angular-in-memory-web-api": "~0.17.0",
|
||||
"angular-in-memory-web-api": "~0.16.0",
|
||||
"angular-route": "1.8.3",
|
||||
"core-js": "^3.0.0",
|
||||
"express": "^4.18.1",
|
||||
@@ -45,9 +45,9 @@
|
||||
"zone.js": "~0.14.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@angular-devkit/build-angular": "17.1.0",
|
||||
"@angular/cli": "17.1.0",
|
||||
"@angular/compiler-cli": "17.1.0",
|
||||
"@angular-devkit/build-angular": "17.0.9",
|
||||
"@angular/cli": "17.0.9",
|
||||
"@angular/compiler-cli": "17.0.8",
|
||||
"@rollup/plugin-commonjs": "^25.0.0",
|
||||
"@rollup/plugin-node-resolve": "^13.2.0",
|
||||
"@types/angular": "^1.8.4",
|
||||
@@ -57,7 +57,7 @@
|
||||
"@types/angular-route": "^1.7.2",
|
||||
"@types/express": "^4.17.13",
|
||||
"@types/jasmine": "~5.1.0",
|
||||
"@types/jquery": "3.5.29",
|
||||
"@types/jquery": "3.5.23",
|
||||
"@types/node": "^16.11.35",
|
||||
"canonical-path": "1.0.0",
|
||||
"concurrently": "^8.0.0",
|
||||
|
||||
+894
-1014
File diff suppressed because it is too large
Load Diff
+1178
-806
File diff suppressed because it is too large
Load Diff
@@ -24,8 +24,8 @@ describe('Comment nodes', () => {
|
||||
it('should find comment nodes when the setting is enabled', () => {
|
||||
showComments();
|
||||
cy.get('.tree-wrapper')
|
||||
.find('.tree-node:contains("#comment")')
|
||||
.its('length')
|
||||
.should('not.eq', 0);
|
||||
.find('.tree-node:contains("#comment")')
|
||||
.its('length')
|
||||
.should('not.eq', 0);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -19,29 +19,27 @@ describe('Tracking items from application to component tree', () => {
|
||||
});
|
||||
|
||||
cy.get('.tree-wrapper')
|
||||
.find('.tree-node:contains("app-todo[TooltipDirective]")')
|
||||
.its('length')
|
||||
.should('eq', 2);
|
||||
.find('.tree-node:contains("app-todo[TooltipDirective]")')
|
||||
.its('length')
|
||||
.should('eq', 2);
|
||||
});
|
||||
|
||||
it('should be able to detect a new todo from user and add it to the tree', () => {
|
||||
cy.enter('#sample-app')
|
||||
.then((getBody) => {
|
||||
getBody().find('input.new-todo').type('Buy cookies{enter}');
|
||||
})
|
||||
.then(() => {
|
||||
cy.enter('#sample-app').then((getBody) => {
|
||||
getBody().find('app-todo').contains('Buy milk');
|
||||
.then((getBody) => {
|
||||
getBody().find('input.new-todo').type('Buy cookies{enter}');
|
||||
})
|
||||
.then(() => {
|
||||
cy.enter('#sample-app').then((getBody) => {
|
||||
getBody().find('app-todo').contains('Buy milk');
|
||||
|
||||
getBody().find('app-todo').contains('Build something fun!');
|
||||
getBody().find('app-todo').contains('Build something fun!');
|
||||
|
||||
getBody().find('app-todo').contains('Buy cookies');
|
||||
getBody().find('app-todo').contains('Buy cookies');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
cy.get('.tree-wrapper .tree-node:contains("app-todo[TooltipDirective]")').should(
|
||||
'have.length',
|
||||
3,
|
||||
);
|
||||
cy.get('.tree-wrapper .tree-node:contains("app-todo[TooltipDirective]")')
|
||||
.should('have.length', 3);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -83,9 +83,9 @@ describe('Search items in component tree', () => {
|
||||
const amountOfBreadcrumbButtons = 4;
|
||||
const amountOfScrollButtons = 2;
|
||||
cy.get('ng-breadcrumbs')
|
||||
.find('button')
|
||||
.its('length')
|
||||
.should('eq', amountOfScrollButtons + amountOfBreadcrumbButtons);
|
||||
.find('button')
|
||||
.its('length')
|
||||
.should('eq', amountOfScrollButtons + amountOfBreadcrumbButtons);
|
||||
|
||||
// should display correct text in explorer panel
|
||||
checkComponentName('app-todos');
|
||||
|
||||
@@ -18,9 +18,9 @@ describe('node selection', () => {
|
||||
cy.get('.tree-wrapper').get('.tree-node.selected').should('not.exist');
|
||||
|
||||
cy.get('.tree-wrapper')
|
||||
.find('.tree-node:contains("app-todo[TooltipDirective]")')
|
||||
.first()
|
||||
.click({force: true});
|
||||
.find('.tree-node:contains("app-todo[TooltipDirective]")')
|
||||
.first()
|
||||
.click({force: true});
|
||||
|
||||
cy.get('.tree-wrapper').find('.tree-node.selected').its('length').should('eq', 1);
|
||||
|
||||
@@ -39,9 +39,9 @@ describe('node selection', () => {
|
||||
});
|
||||
|
||||
cy.get('.tree-wrapper')
|
||||
.find('.tree-node:contains("app-todo[TooltipDirective]")')
|
||||
.last()
|
||||
.click({force: true});
|
||||
.find('.tree-node:contains("app-todo[TooltipDirective]")')
|
||||
.last()
|
||||
.click({force: true});
|
||||
|
||||
cy.enter('#sample-app').then((getBody) => {
|
||||
getBody().find('app-todo:contains("Buy milk")').find('.destroy').click();
|
||||
@@ -52,100 +52,86 @@ describe('node selection', () => {
|
||||
|
||||
it('should select nodes with same name', () => {
|
||||
cy.get('.tree-wrapper')
|
||||
.find('.tree-node:contains("app-todo[TooltipDirective]")')
|
||||
.first()
|
||||
.click({force: true});
|
||||
.find('.tree-node:contains("app-todo[TooltipDirective]")')
|
||||
.first()
|
||||
.click({force: true});
|
||||
|
||||
cy.get('.tree-wrapper')
|
||||
.find('.tree-node:contains("app-todo[TooltipDirective]")')
|
||||
.last()
|
||||
.click({force: true});
|
||||
.find('.tree-node:contains("app-todo[TooltipDirective]")')
|
||||
.last()
|
||||
.click({force: true});
|
||||
|
||||
cy.get('ng-property-view').last().find('mat-tree-node:contains("todo")').click();
|
||||
|
||||
cy.get('ng-property-view')
|
||||
.last()
|
||||
.find('mat-tree-node:contains("Build something fun!")')
|
||||
.its('length')
|
||||
.should('eq', 1);
|
||||
.last()
|
||||
.find('mat-tree-node:contains("Build something fun!")')
|
||||
.its('length')
|
||||
.should('eq', 1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('breadcrumb logic', () => {
|
||||
it('should overflow when breadcrumb list is long enough', () => {
|
||||
cy.get('.tree-wrapper')
|
||||
.find('.tree-node:contains("div[TooltipDirective]")')
|
||||
.last()
|
||||
.click({force: true})
|
||||
.then(() => {
|
||||
cy.get('ng-breadcrumbs')
|
||||
.find('.breadcrumbs')
|
||||
.then((breadcrumbsContainer) => {
|
||||
.find('.tree-node:contains("div[TooltipDirective]")')
|
||||
.last()
|
||||
.click({force: true})
|
||||
.then(() => {
|
||||
cy.get('ng-breadcrumbs').find('.breadcrumbs').then((breadcrumbsContainer) => {
|
||||
const hasOverflowX = () =>
|
||||
breadcrumbsContainer[0].scrollWidth > breadcrumbsContainer[0].clientWidth;
|
||||
breadcrumbsContainer[0].scrollWidth > breadcrumbsContainer[0].clientWidth;
|
||||
expect(hasOverflowX()).to.be.true;
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should scroll right when right scroll button is clicked', () => {
|
||||
cy.get('.tree-wrapper')
|
||||
.find('.tree-node:contains("div[TooltipDirective]")')
|
||||
.last()
|
||||
.click({force: true})
|
||||
.then(() => {
|
||||
cy.get('ng-breadcrumbs')
|
||||
.find('.breadcrumbs')
|
||||
.then((el) => {
|
||||
el[0].style.scrollBehavior = 'auto';
|
||||
})
|
||||
.then((breadcrumbsContainer) => {
|
||||
const scrollLeft = () => breadcrumbsContainer[0].scrollLeft;
|
||||
expect(scrollLeft()).to.eql(0);
|
||||
.find('.tree-node:contains("div[TooltipDirective]")')
|
||||
.last()
|
||||
.click({force: true})
|
||||
.then(() => {
|
||||
cy.get('ng-breadcrumbs')
|
||||
.find('.breadcrumbs')
|
||||
.then((el) => {
|
||||
el[0].style.scrollBehavior = 'auto';
|
||||
})
|
||||
.then((breadcrumbsContainer) => {
|
||||
const scrollLeft = () => breadcrumbsContainer[0].scrollLeft;
|
||||
expect(scrollLeft()).to.eql(0);
|
||||
|
||||
cy.get('ng-breadcrumbs')
|
||||
.find('.scroll-button')
|
||||
.last()
|
||||
.click()
|
||||
.then(() => {
|
||||
expect(scrollLeft()).to.be.greaterThan(0);
|
||||
cy.get('ng-breadcrumbs').find('.scroll-button').last().click().then(() => {
|
||||
expect(scrollLeft()).to.be.greaterThan(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should scroll left when left scroll button is clicked', () => {
|
||||
cy.get('.tree-wrapper')
|
||||
.find('.tree-node:contains("div[TooltipDirective]")')
|
||||
.last()
|
||||
.click({force: true})
|
||||
.then(() => {
|
||||
cy.get('ng-breadcrumbs')
|
||||
.find('.breadcrumbs')
|
||||
.then((el) => {
|
||||
el[0].style.scrollBehavior = 'auto';
|
||||
})
|
||||
.then((breadcrumbsContainer) => {
|
||||
const scrollLeft = () => breadcrumbsContainer[0].scrollLeft;
|
||||
expect(scrollLeft()).to.eql(0);
|
||||
.find('.tree-node:contains("div[TooltipDirective]")')
|
||||
.last()
|
||||
.click({force: true})
|
||||
.then(() => {
|
||||
cy.get('ng-breadcrumbs')
|
||||
.find('.breadcrumbs')
|
||||
.then((el) => {
|
||||
el[0].style.scrollBehavior = 'auto';
|
||||
})
|
||||
.then((breadcrumbsContainer) => {
|
||||
const scrollLeft = () => breadcrumbsContainer[0].scrollLeft;
|
||||
expect(scrollLeft()).to.eql(0);
|
||||
|
||||
cy.get('ng-breadcrumbs')
|
||||
.find('.scroll-button')
|
||||
.last()
|
||||
.click()
|
||||
.then(() => {
|
||||
expect(scrollLeft()).to.be.greaterThan(0);
|
||||
cy.get('ng-breadcrumbs').find('.scroll-button').last().click().then(() => {
|
||||
expect(scrollLeft()).to.be.greaterThan(0);
|
||||
|
||||
cy.get('ng-breadcrumbs')
|
||||
.find('.scroll-button')
|
||||
.first()
|
||||
.click()
|
||||
.then(() => {
|
||||
cy.get('ng-breadcrumbs').find('.scroll-button').first().click().then(() => {
|
||||
expect(scrollLeft()).to.eql(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -17,9 +17,9 @@ describe('edit properties of directive in the property view tab', () => {
|
||||
beforeEach(() => {
|
||||
// select todo node in component tree
|
||||
cy.get('.tree-wrapper')
|
||||
.find('.tree-node:contains("app-todo[TooltipDirective]")')
|
||||
.first()
|
||||
.click({force: true});
|
||||
.find('.tree-node:contains("app-todo[TooltipDirective]")')
|
||||
.first()
|
||||
.click({force: true});
|
||||
});
|
||||
|
||||
it('should be able to enable editMode', () => {
|
||||
@@ -28,13 +28,13 @@ describe('edit properties of directive in the property view tab', () => {
|
||||
});
|
||||
|
||||
cy.get('.explorer-panel:contains("app-todo")')
|
||||
.find('ng-property-view mat-tree-node:contains("editMode")')
|
||||
.find('ng-property-editor .editor')
|
||||
.click({force: true})
|
||||
.find('.editor-input')
|
||||
.clear()
|
||||
.type('true')
|
||||
.type('{enter}');
|
||||
.find('ng-property-view mat-tree-node:contains("editMode")')
|
||||
.find('ng-property-editor .editor')
|
||||
.click({force: true})
|
||||
.find('.editor-input')
|
||||
.clear()
|
||||
.type('true')
|
||||
.type('{enter}');
|
||||
|
||||
cy.enter('#sample-app').then((getBody) => {
|
||||
getBody().find('app-todo input.edit').should('be.visible');
|
||||
@@ -45,8 +45,8 @@ describe('edit properties of directive in the property view tab', () => {
|
||||
beforeEach(() => {
|
||||
// expand todo state
|
||||
cy.get('.explorer-panel:contains("app-todo")')
|
||||
.find('ng-property-view mat-tree-node:contains("todo")')
|
||||
.click();
|
||||
.find('ng-property-view mat-tree-node:contains("todo")')
|
||||
.click();
|
||||
});
|
||||
|
||||
it('should change todo label in app when edited', () => {
|
||||
@@ -57,13 +57,13 @@ describe('edit properties of directive in the property view tab', () => {
|
||||
|
||||
// find label variable and run through edit logic
|
||||
cy.get('.explorer-panel:contains("app-todo")')
|
||||
.find('ng-property-view mat-tree-node:contains("label")')
|
||||
.find('ng-property-editor .editor')
|
||||
.click()
|
||||
.find('.editor-input')
|
||||
.clear()
|
||||
.type('Buy cookies')
|
||||
.type('{enter}');
|
||||
.find('ng-property-view mat-tree-node:contains("label")')
|
||||
.find('ng-property-editor .editor')
|
||||
.click()
|
||||
.find('.editor-input')
|
||||
.clear()
|
||||
.type('Buy cookies')
|
||||
.type('{enter}');
|
||||
|
||||
// assert that the page has been updated
|
||||
cy.enter('#sample-app').then((getBody) => {
|
||||
@@ -79,13 +79,13 @@ describe('edit properties of directive in the property view tab', () => {
|
||||
|
||||
// find completed variable and run through edit logic
|
||||
cy.get('.explorer-panel:contains("app-todo")')
|
||||
.find('ng-property-view mat-tree-node:contains("completed")')
|
||||
.find('ng-property-editor .editor')
|
||||
.click()
|
||||
.find('.editor-input')
|
||||
.clear()
|
||||
.type('true')
|
||||
.type('{enter}');
|
||||
.find('ng-property-view mat-tree-node:contains("completed")')
|
||||
.find('ng-property-editor .editor')
|
||||
.click()
|
||||
.find('.editor-input')
|
||||
.clear()
|
||||
.type('true')
|
||||
.type('{enter}');
|
||||
|
||||
// assert that the page has been updated
|
||||
cy.enter('#sample-app').then((getBody) => {
|
||||
|
||||
@@ -21,21 +21,20 @@ describe('change of the state should reflect in property update', () => {
|
||||
|
||||
// Select the todo item
|
||||
cy.get('.tree-wrapper')
|
||||
.find('.tree-node:contains("app-todo[TooltipDirective]")')
|
||||
.first()
|
||||
.click({force: true});
|
||||
.find('.tree-node:contains("app-todo[TooltipDirective]")')
|
||||
.first()
|
||||
.click({force: true});
|
||||
|
||||
// Expand the todo in the property explorer
|
||||
cy.get('.explorer-panel:contains("app-todo")')
|
||||
.find('ng-property-view mat-tree-node:contains("todo")')
|
||||
.click();
|
||||
.find('ng-property-view mat-tree-node:contains("todo")')
|
||||
.click();
|
||||
|
||||
// Verify its value is now completed
|
||||
cy.contains(
|
||||
'.explorer-panel:contains("app-todo") ' +
|
||||
'ng-property-view mat-tree-node:contains("completed") ' +
|
||||
'ng-property-editor .editor',
|
||||
'true',
|
||||
);
|
||||
'.explorer-panel:contains("app-todo") ' +
|
||||
'ng-property-view mat-tree-node:contains("completed") ' +
|
||||
'ng-property-editor .editor',
|
||||
'true');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -17,9 +17,9 @@ describe('Viewing component metadata', () => {
|
||||
});
|
||||
|
||||
describe('viewing TodoComponent', () => {
|
||||
beforeEach(() =>
|
||||
prepareHeaderExpansionPanelForAssertions('.tree-node:contains("app-todo[TooltipDirective]")'),
|
||||
);
|
||||
beforeEach(
|
||||
() => prepareHeaderExpansionPanelForAssertions(
|
||||
'.tree-node:contains("app-todo[TooltipDirective]")'));
|
||||
|
||||
it('should display view encapsulation', () => {
|
||||
cy.contains('.meta-data-container .mat-button:first', 'View Encapsulation: Emulated');
|
||||
@@ -31,9 +31,9 @@ describe('Viewing component metadata', () => {
|
||||
});
|
||||
|
||||
describe('viewing DemoAppComponent', () => {
|
||||
beforeEach(() =>
|
||||
prepareHeaderExpansionPanelForAssertions('.tree-node:contains("app-demo-component")'),
|
||||
);
|
||||
beforeEach(
|
||||
() =>
|
||||
prepareHeaderExpansionPanelForAssertions('.tree-node:contains("app-demo-component")'));
|
||||
|
||||
it('should display view encapsulation', () => {
|
||||
cy.contains('.meta-data-container .mat-button:first', 'View Encapsulation: None');
|
||||
|
||||
@@ -17,4 +17,5 @@ import {AppComponent} from './app.component';
|
||||
providers: [],
|
||||
bootstrap: [AppComponent],
|
||||
})
|
||||
export class AppModule {}
|
||||
export class AppModule {
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ import {platformBrowserDynamic} from '@angular/platform-browser-dynamic';
|
||||
import {AppModule} from './app/app.module';
|
||||
|
||||
platformBrowserDynamic()
|
||||
.bootstrapModule(AppModule, {
|
||||
ngZone: 'noop',
|
||||
})
|
||||
.catch((err) => console.error(err));
|
||||
.bootstrapModule(AppModule, {
|
||||
ngZone: 'noop',
|
||||
})
|
||||
.catch((err) => console.error(err));
|
||||
|
||||
@@ -13,7 +13,7 @@ import {Router, RouterOutlet} from '@angular/router';
|
||||
selector: 'app-root',
|
||||
template: `<router-outlet></router-outlet>`,
|
||||
standalone: true,
|
||||
imports: [RouterOutlet],
|
||||
imports: [RouterOutlet]
|
||||
})
|
||||
export class AppComponent {
|
||||
constructor(public router: Router) {}
|
||||
|
||||
@@ -2,10 +2,3 @@
|
||||
<app-zippy [title]="getTitle()">This is my content</app-zippy>
|
||||
<app-heavy></app-heavy>
|
||||
<div #elementReference>HTMLElement</div>
|
||||
|
||||
<hr/>
|
||||
<h2>Signals</h2>
|
||||
<div>Object Signal: {{objectSignal()|json}}</div>
|
||||
<div>Object Computed: {{objectComputed()|json}}</div>
|
||||
<div>Editable Signal: {{primitiveSignal()}}</div>
|
||||
<div>Primitive computed: {{primitiveComputed()}}</div>
|
||||
|
||||
@@ -6,21 +6,7 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {JsonPipe} from '@angular/common';
|
||||
import {
|
||||
Component,
|
||||
computed,
|
||||
CUSTOM_ELEMENTS_SCHEMA,
|
||||
ElementRef,
|
||||
EventEmitter,
|
||||
inject,
|
||||
Injector,
|
||||
Input,
|
||||
Output,
|
||||
signal,
|
||||
ViewChild,
|
||||
ViewEncapsulation,
|
||||
} from '@angular/core';
|
||||
import {Component, CUSTOM_ELEMENTS_SCHEMA, ElementRef, EventEmitter, inject, Injector, Input, Output, ViewChild, ViewEncapsulation} from '@angular/core';
|
||||
import {createCustomElement} from '@angular/elements';
|
||||
import {RouterOutlet} from '@angular/router';
|
||||
import {initializeMessageBus} from 'ng-devtools-backend';
|
||||
@@ -36,8 +22,8 @@ import {ZippyComponent} from './zippy.component';
|
||||
styleUrls: ['./demo-app.component.scss'],
|
||||
encapsulation: ViewEncapsulation.None,
|
||||
standalone: true,
|
||||
imports: [HeavyComponent, RouterOutlet, JsonPipe],
|
||||
schemas: [CUSTOM_ELEMENTS_SCHEMA],
|
||||
imports: [HeavyComponent, RouterOutlet],
|
||||
schemas: [CUSTOM_ELEMENTS_SCHEMA]
|
||||
})
|
||||
export class DemoAppComponent {
|
||||
@ViewChild(ZippyComponent) zippy!: ZippyComponent;
|
||||
@@ -49,21 +35,12 @@ export class DemoAppComponent {
|
||||
@Output() outputOne = new EventEmitter();
|
||||
@Output('output_two') outputTwo = new EventEmitter();
|
||||
|
||||
primitiveSignal = signal(123);
|
||||
primitiveComputed = computed(() => this.primitiveSignal() ** 2);
|
||||
objectSignal = signal({name: 'John', age: 40});
|
||||
objectComputed = computed(() => {
|
||||
const original = this.objectSignal();
|
||||
return {...original, age: original.age + 1};
|
||||
});
|
||||
test = [signal(3)];
|
||||
|
||||
constructor() {
|
||||
const el = createCustomElement(ZippyComponent, {injector: inject(Injector)});
|
||||
customElements.define('app-zippy', el as any);
|
||||
}
|
||||
|
||||
getTitle(): '► Click to expand' | '▼ Click to collapse' {
|
||||
getTitle(): '► Click to expand'|'▼ Click to collapse' {
|
||||
if (!this.zippy || !this.zippy.visible) {
|
||||
return '► Click to expand';
|
||||
}
|
||||
@@ -84,10 +61,5 @@ export const ROUTES = [
|
||||
},
|
||||
];
|
||||
|
||||
initializeMessageBus(
|
||||
new ZoneUnawareIFrameMessageBus(
|
||||
'angular-devtools-backend',
|
||||
'angular-devtools',
|
||||
() => window.parent,
|
||||
),
|
||||
);
|
||||
initializeMessageBus(new ZoneUnawareIFrameMessageBus(
|
||||
'angular-devtools-backend', 'angular-devtools', () => window.parent));
|
||||
|
||||
@@ -18,7 +18,8 @@ const fib = (n: number): number => {
|
||||
@Component({selector: 'app-heavy', template: `<h1>{{ calculate() }}</h1>`, standalone: true})
|
||||
export class HeavyComponent {
|
||||
@Input()
|
||||
set foo(_: any) {}
|
||||
set foo(_: any) {
|
||||
}
|
||||
|
||||
state = {
|
||||
nested: {
|
||||
@@ -26,12 +27,14 @@ export class HeavyComponent {
|
||||
foo: 1,
|
||||
bar: 2,
|
||||
},
|
||||
[Symbol(3)](): number {
|
||||
return 1.618;
|
||||
},
|
||||
get foo(): number {
|
||||
return 42;
|
||||
},
|
||||
[Symbol(3)]():
|
||||
number {
|
||||
return 1.618;
|
||||
},
|
||||
get foo():
|
||||
number {
|
||||
return 42;
|
||||
},
|
||||
},
|
||||
};
|
||||
calculate(): number {
|
||||
|
||||
@@ -18,6 +18,7 @@ import {RouterLink, RouterOutlet} from '@angular/router';
|
||||
<a [routerLink]="">Home</a>
|
||||
<a [routerLink]="">Home</a>
|
||||
<a [routerLink]="">Home</a>
|
||||
`,
|
||||
`
|
||||
})
|
||||
export class AboutComponent {}
|
||||
export class AboutComponent {
|
||||
}
|
||||
|
||||
@@ -6,10 +6,12 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {CommonModule} from '@angular/common';
|
||||
import {Component, Inject} from '@angular/core';
|
||||
import {FormsModule} from '@angular/forms';
|
||||
import {MAT_DIALOG_DATA, MatDialogModule, MatDialogRef} from '@angular/material/dialog';
|
||||
import {MatFormField, MatLabel} from '@angular/material/form-field';
|
||||
import {MatFormFieldModule} from '@angular/material/form-field';
|
||||
|
||||
|
||||
export interface DialogData {
|
||||
animal: string;
|
||||
@@ -19,7 +21,7 @@ export interface DialogData {
|
||||
@Component({
|
||||
selector: 'app-dialog',
|
||||
standalone: true,
|
||||
imports: [MatDialogModule, MatFormField, MatLabel, FormsModule],
|
||||
imports: [MatDialogModule, MatFormFieldModule, FormsModule],
|
||||
template: `
|
||||
<h1 mat-dialog-title>Hi {{ data.name }}</h1>
|
||||
<div mat-dialog-content>
|
||||
@@ -37,9 +39,8 @@ export interface DialogData {
|
||||
})
|
||||
export class DialogComponent {
|
||||
constructor(
|
||||
public dialogRef: MatDialogRef<DialogComponent>,
|
||||
@Inject(MAT_DIALOG_DATA) public data: DialogData,
|
||||
) {}
|
||||
public dialogRef: MatDialogRef<DialogComponent>,
|
||||
@Inject(MAT_DIALOG_DATA) public data: DialogData) {}
|
||||
|
||||
onNoClick(): void {
|
||||
this.dialogRef.close();
|
||||
|
||||
@@ -10,7 +10,7 @@ import {OnDestroy, Pipe, PipeTransform} from '@angular/core';
|
||||
|
||||
@Pipe({name: 'sample', pure: false, standalone: true})
|
||||
export class SamplePipe implements PipeTransform, OnDestroy {
|
||||
transform(val: unknown) {
|
||||
transform(val: any): void {
|
||||
return val;
|
||||
}
|
||||
|
||||
|
||||
@@ -22,21 +22,17 @@ export interface Todo {
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
standalone: true,
|
||||
imports: [TooltipDirective],
|
||||
styles: [
|
||||
`
|
||||
styles: [`
|
||||
.destroy {
|
||||
cursor: pointer;
|
||||
display: unset !important;
|
||||
}
|
||||
`,
|
||||
],
|
||||
`],
|
||||
template: `
|
||||
<li [class.completed]="todo.completed">
|
||||
<div class="view" appTooltip>
|
||||
<input class="toggle" type="checkbox" [checked]="todo.completed" (change)="toggle()" />
|
||||
<label (dblclick)="enableEditMode()" [style.display]="editMode ? 'none' : 'block'">{{
|
||||
todo.label
|
||||
}}</label>
|
||||
<label (dblclick)="enableEditMode()" [style.display]="editMode ? 'none' : 'block'">{{ todo.label }}</label>
|
||||
<button class="destroy" (click)="delete.emit(todo)"></button>
|
||||
</div>
|
||||
<input
|
||||
@@ -46,7 +42,7 @@ export interface Todo {
|
||||
(keydown.enter)="completeEdit($any($event.target).value)"
|
||||
/>
|
||||
</li>
|
||||
`,
|
||||
`
|
||||
})
|
||||
export class TodoComponent {
|
||||
@Input() todo!: Todo;
|
||||
|
||||
@@ -7,16 +7,7 @@
|
||||
*/
|
||||
|
||||
import {NgForOf} from '@angular/common';
|
||||
import {
|
||||
ChangeDetectorRef,
|
||||
Component,
|
||||
EventEmitter,
|
||||
OnDestroy,
|
||||
OnInit,
|
||||
Output,
|
||||
Pipe,
|
||||
PipeTransform,
|
||||
} from '@angular/core';
|
||||
import {ChangeDetectorRef, Component, EventEmitter, OnDestroy, OnInit, Output, Pipe, PipeTransform} from '@angular/core';
|
||||
import {RouterLink} from '@angular/router';
|
||||
|
||||
import {SamplePipe} from './sample.pipe';
|
||||
@@ -66,25 +57,19 @@ const fib = (n: number): number => {
|
||||
<section class="todoapp">
|
||||
<header class="header">
|
||||
<h1>todos</h1>
|
||||
<input
|
||||
(keydown.enter)="addTodo(input)"
|
||||
#input
|
||||
class="new-todo"
|
||||
placeholder="What needs to be done?"
|
||||
autofocus
|
||||
/>
|
||||
<input (keydown.enter)="addTodo(input)" #input class="new-todo" placeholder="What needs to be done?" autofocus />
|
||||
</header>
|
||||
<section class="main">
|
||||
<input id="toggle-all" class="toggle-all" type="checkbox" />
|
||||
<label for="toggle-all">Mark all as complete</label>
|
||||
<ul class="todo-list">
|
||||
@for (todo of todos | todosFilter: filterValue; track todo) {
|
||||
<app-todo
|
||||
appTooltip
|
||||
[todo]="todo"
|
||||
(delete)="onDelete($event)"
|
||||
(update)="onChange($event)"
|
||||
/>
|
||||
<app-todo
|
||||
appTooltip
|
||||
[todo]="todo"
|
||||
(delete)="onDelete($event)"
|
||||
(update)="onChange($event)"
|
||||
/>
|
||||
}
|
||||
</ul>
|
||||
</section>
|
||||
@@ -95,7 +80,7 @@ const fib = (n: number): number => {
|
||||
<button class="clear-completed" (click)="clearCompleted()">Clear completed</button>
|
||||
</footer>
|
||||
</section>
|
||||
`,
|
||||
`
|
||||
})
|
||||
export class TodosComponent implements OnInit, OnDestroy {
|
||||
todos: Todo[] = [
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {CommonModule} from '@angular/common';
|
||||
import {Component} from '@angular/core';
|
||||
import {FormsModule} from '@angular/forms';
|
||||
import {MatDialog, MatDialogModule} from '@angular/material/dialog';
|
||||
@@ -18,9 +19,9 @@ import {DialogComponent} from './dialog.component';
|
||||
@Component({
|
||||
selector: 'app-todo-demo',
|
||||
standalone: true,
|
||||
imports: [RouterLink, RouterOutlet, MatDialogModule, FormsModule],
|
||||
styles: [
|
||||
`
|
||||
imports:
|
||||
[RouterLink, RouterOutlet, MatDialogModule, MatFormFieldModule, MatInputModule, FormsModule],
|
||||
styles: [`
|
||||
nav {
|
||||
padding-top: 20px;
|
||||
padding-bottom: 10px;
|
||||
@@ -36,8 +37,7 @@ import {DialogComponent} from './dialog.component';
|
||||
padding: 10px;
|
||||
margin-right: 20px;
|
||||
}
|
||||
`,
|
||||
],
|
||||
`],
|
||||
template: `
|
||||
<nav>
|
||||
<a routerLink="/demo-app/todos/app">Todos</a>
|
||||
@@ -47,7 +47,7 @@ import {DialogComponent} from './dialog.component';
|
||||
<button class="dialog-open-button" (click)="openDialog()">Open dialog</button>
|
||||
|
||||
<router-outlet></router-outlet>
|
||||
`,
|
||||
`
|
||||
})
|
||||
export class TodoAppComponent {
|
||||
name!: string;
|
||||
|
||||
@@ -11,29 +11,27 @@ import {Component, Input} from '@angular/core';
|
||||
@Component({
|
||||
selector: 'app-zippy',
|
||||
standalone: true,
|
||||
styles: [
|
||||
`
|
||||
:host {
|
||||
user-select: none;
|
||||
}
|
||||
styles: [`
|
||||
:host {
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
header {
|
||||
max-width: 120px;
|
||||
border: 1px solid #ccc;
|
||||
padding-left: 10px;
|
||||
padding-right: 10px;
|
||||
cursor: pointer;
|
||||
}
|
||||
header {
|
||||
max-width: 120px;
|
||||
border: 1px solid #ccc;
|
||||
padding-left: 10px;
|
||||
padding-right: 10px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
div {
|
||||
max-width: 120px;
|
||||
border-left: 1px solid #ccc;
|
||||
border-right: 1px solid #ccc;
|
||||
border-bottom: 1px solid #ccc;
|
||||
padding: 10px;
|
||||
}
|
||||
`,
|
||||
],
|
||||
div {
|
||||
max-width: 120px;
|
||||
border-left: 1px solid #ccc;
|
||||
border-right: 1px solid #ccc;
|
||||
border-bottom: 1px solid #ccc;
|
||||
padding: 10px;
|
||||
}
|
||||
`],
|
||||
template: `
|
||||
<section>
|
||||
<header (click)="visible = !visible">{{ title }}</header>
|
||||
@@ -41,7 +39,7 @@ import {Component, Input} from '@angular/core';
|
||||
<ng-content></ng-content>
|
||||
</div>
|
||||
</section>
|
||||
`,
|
||||
`
|
||||
})
|
||||
export class ZippyComponent {
|
||||
@Input() title!: string;
|
||||
|
||||
@@ -7,51 +7,45 @@
|
||||
*/
|
||||
|
||||
import {Component, ElementRef, ViewChild} from '@angular/core';
|
||||
import {DevToolsModule as NgDevToolsModule} from 'ng-devtools';
|
||||
import {Events, MessageBus, PriorityAwareMessageBus} from 'protocol';
|
||||
|
||||
import {IFrameMessageBus} from '../../../../../src/iframe-message-bus';
|
||||
import {DevToolsComponent} from 'ng-devtools';
|
||||
|
||||
@Component({
|
||||
standalone: true,
|
||||
imports: [DevToolsComponent],
|
||||
imports: [NgDevToolsModule],
|
||||
providers: [
|
||||
{
|
||||
provide: MessageBus,
|
||||
useFactory(): MessageBus<Events> {
|
||||
return new PriorityAwareMessageBus(
|
||||
new IFrameMessageBus(
|
||||
'angular-devtools',
|
||||
'angular-devtools-backend',
|
||||
return new PriorityAwareMessageBus(new IFrameMessageBus(
|
||||
'angular-devtools', 'angular-devtools-backend',
|
||||
// tslint:disable-next-line: no-non-null-assertion
|
||||
() => (document.querySelector('#sample-app') as HTMLIFrameElement).contentWindow!,
|
||||
),
|
||||
);
|
||||
() => (document.querySelector('#sample-app') as HTMLIFrameElement).contentWindow!));
|
||||
},
|
||||
},
|
||||
],
|
||||
styles: [
|
||||
`
|
||||
iframe {
|
||||
height: 340px;
|
||||
width: 100%;
|
||||
border: 0;
|
||||
}
|
||||
styles: [`
|
||||
iframe {
|
||||
height: 340px;
|
||||
width: 100%;
|
||||
border: 0;
|
||||
}
|
||||
|
||||
.devtools-wrapper {
|
||||
height: calc(100vh - 345px);
|
||||
}
|
||||
`,
|
||||
],
|
||||
.devtools-wrapper {
|
||||
height: calc(100vh - 345px);
|
||||
}
|
||||
`],
|
||||
template: `
|
||||
<iframe #ref src="demo-app/todos/app" id="sample-app"></iframe>
|
||||
<br />
|
||||
<div class="devtools-wrapper">
|
||||
<ng-devtools></ng-devtools>
|
||||
</div>
|
||||
`,
|
||||
`
|
||||
})
|
||||
export class DemoDevToolsComponent {
|
||||
messageBus: IFrameMessageBus | null = null;
|
||||
export class DevToolsComponent {
|
||||
messageBus: IFrameMessageBus|null = null;
|
||||
@ViewChild('ref') iframe!: ElementRef;
|
||||
}
|
||||
|
||||
@@ -8,5 +8,5 @@
|
||||
|
||||
export const environment = {
|
||||
production: false,
|
||||
LATEST_SHA: 'BUILD_SCM_COMMIT_SHA', // Stamped at build time by bazel
|
||||
LATEST_SHA: 'BUILD_SCM_COMMIT_SHA', // Stamped at build time by bazel
|
||||
};
|
||||
|
||||
@@ -16,6 +16,7 @@ import {DemoApplicationOperations} from '../../../src/demo-application-operation
|
||||
|
||||
import {AppComponent} from './app/app.component';
|
||||
|
||||
|
||||
bootstrapApplication(AppComponent, {
|
||||
providers: [
|
||||
provideAnimations(),
|
||||
@@ -23,7 +24,7 @@ bootstrapApplication(AppComponent, {
|
||||
{
|
||||
path: '',
|
||||
loadComponent: () =>
|
||||
import('./app/devtools-app/devtools-app.component').then((m) => m.DemoDevToolsComponent),
|
||||
import('./app/devtools-app/devtools-app.component').then((m) => m.DevToolsComponent),
|
||||
pathMatch: 'full',
|
||||
},
|
||||
{
|
||||
@@ -39,5 +40,5 @@ bootstrapApplication(AppComponent, {
|
||||
provide: ApplicationEnvironment,
|
||||
useClass: DemoApplicationEnvironment,
|
||||
},
|
||||
],
|
||||
]
|
||||
});
|
||||
|
||||
@@ -124,7 +124,6 @@ ts_library(
|
||||
srcs = ["component-tree.ts"],
|
||||
deps = [
|
||||
":interfaces",
|
||||
":utils",
|
||||
"//devtools/projects/ng-devtools-backend/src/lib/directive-forest",
|
||||
"//devtools/projects/ng-devtools-backend/src/lib/ng-debug-api",
|
||||
"//devtools/projects/ng-devtools-backend/src/lib/state-serializer",
|
||||
|
||||
@@ -6,43 +6,12 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {
|
||||
ComponentExplorerViewQuery,
|
||||
ComponentType,
|
||||
DevToolsNode,
|
||||
DirectivePosition,
|
||||
DirectiveType,
|
||||
ElementPosition,
|
||||
Events,
|
||||
MessageBus,
|
||||
ProfilerFrame,
|
||||
SerializedInjector,
|
||||
SerializedProviderRecord,
|
||||
} from 'protocol';
|
||||
import {ComponentExplorerViewQuery, ComponentType, DevToolsNode, DirectivePosition, DirectiveType, ElementPosition, Events, MessageBus, ProfilerFrame, SerializedInjector, SerializedProviderRecord} from 'protocol';
|
||||
import {debounceTime} from 'rxjs/operators';
|
||||
import {
|
||||
appIsAngularInDevMode,
|
||||
appIsAngularIvy,
|
||||
appIsSupportedAngularVersion,
|
||||
getAngularVersion,
|
||||
} from 'shared-utils';
|
||||
import {appIsAngularInDevMode, appIsAngularIvy, appIsSupportedAngularVersion, getAngularVersion,} from 'shared-utils';
|
||||
|
||||
import {ComponentInspector} from './component-inspector/component-inspector';
|
||||
import {
|
||||
getElementInjectorElement,
|
||||
getInjectorFromElementNode,
|
||||
getInjectorProviders,
|
||||
getInjectorResolutionPath,
|
||||
getLatestComponentState,
|
||||
idToInjector,
|
||||
injectorsSeen,
|
||||
isElementInjector,
|
||||
nodeInjectorToResolutionPath,
|
||||
queryDirectiveForest,
|
||||
serializeProviderRecord,
|
||||
serializeResolutionPath,
|
||||
updateState,
|
||||
} from './component-tree';
|
||||
import {getElementInjectorElement, getInjectorFromElementNode, getInjectorProviders, getInjectorResolutionPath, getLatestComponentState, idToInjector, injectorsSeen, isElementInjector, nodeInjectorToResolutionPath, queryDirectiveForest, serializeProviderRecord, serializeResolutionPath, updateState} from './component-tree';
|
||||
import {unHighlight} from './highlighter';
|
||||
import {disableTimingAPI, enableTimingAPI, initializeOrGetDirectiveForestHooks} from './hooks';
|
||||
import {start as startProfiling, stop as stopProfiling} from './hooks/capture';
|
||||
@@ -50,15 +19,13 @@ import {ComponentTreeNode} from './interfaces';
|
||||
import {ngDebugDependencyInjectionApiIsSupported} from './ng-debug-api/ng-debug-api';
|
||||
import {setConsoleReference} from './set-console-reference';
|
||||
import {serializeDirectiveState} from './state-serializer/state-serializer';
|
||||
import {hasDiDebugAPIs, runOutsideAngular} from './utils';
|
||||
import {runOutsideAngular} from './utils';
|
||||
|
||||
export const subscribeToClientEvents = (messageBus: MessageBus<Events>): void => {
|
||||
messageBus.on('shutdown', shutdownCallback(messageBus));
|
||||
|
||||
messageBus.on(
|
||||
'getLatestComponentExplorerView',
|
||||
getLatestComponentExplorerViewCallback(messageBus),
|
||||
);
|
||||
'getLatestComponentExplorerView', getLatestComponentExplorerViewCallback(messageBus));
|
||||
|
||||
messageBus.on('queryNgAvailability', checkForAngularCallback(messageBus));
|
||||
|
||||
@@ -87,8 +54,8 @@ export const subscribeToClientEvents = (messageBus: MessageBus<Events>): void =>
|
||||
// once every 250ms
|
||||
runOutsideAngular(() => {
|
||||
initializeOrGetDirectiveForestHooks()
|
||||
.profiler.changeDetection$.pipe(debounceTime(250))
|
||||
.subscribe(() => messageBus.emit('componentTreeDirty'));
|
||||
.profiler.changeDetection$.pipe(debounceTime(250))
|
||||
.subscribe(() => messageBus.emit('componentTreeDirty'));
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -101,58 +68,55 @@ const shutdownCallback = (messageBus: MessageBus<Events>) => () => {
|
||||
messageBus.destroy();
|
||||
};
|
||||
|
||||
const getLatestComponentExplorerViewCallback =
|
||||
(messageBus: MessageBus<Events>) => (query?: ComponentExplorerViewQuery) => {
|
||||
// We want to force re-indexing of the component tree.
|
||||
// Pressing the refresh button means the user saw stuck UI.
|
||||
const getLatestComponentExplorerViewCallback = (messageBus: MessageBus<Events>) =>
|
||||
(query?: ComponentExplorerViewQuery) => {
|
||||
// We want to force re-indexing of the component tree.
|
||||
// Pressing the refresh button means the user saw stuck UI.
|
||||
|
||||
initializeOrGetDirectiveForestHooks().indexForest();
|
||||
initializeOrGetDirectiveForestHooks().indexForest();
|
||||
|
||||
const forest = prepareForestForSerialization(
|
||||
initializeOrGetDirectiveForestHooks().getIndexedDirectiveForest(),
|
||||
ngDebugDependencyInjectionApiIsSupported(),
|
||||
);
|
||||
const forest = prepareForestForSerialization(
|
||||
initializeOrGetDirectiveForestHooks().getIndexedDirectiveForest(),
|
||||
ngDebugDependencyInjectionApiIsSupported());
|
||||
|
||||
// cleanup injector id mappings
|
||||
for (const injectorId of idToInjector.keys()) {
|
||||
if (!injectorsSeen.has(injectorId)) {
|
||||
const injector = idToInjector.get(injectorId)!;
|
||||
if (isElementInjector(injector)) {
|
||||
const element = getElementInjectorElement(injector);
|
||||
if (element) {
|
||||
nodeInjectorToResolutionPath.delete(element);
|
||||
// cleanup injector id mappings
|
||||
for (const injectorId of idToInjector.keys()) {
|
||||
if (!injectorsSeen.has(injectorId)) {
|
||||
const injector = idToInjector.get(injectorId)!;
|
||||
if (isElementInjector(injector)) {
|
||||
const element = getElementInjectorElement(injector);
|
||||
if (element) {
|
||||
nodeInjectorToResolutionPath.delete(element);
|
||||
}
|
||||
}
|
||||
|
||||
idToInjector.delete(injectorId);
|
||||
}
|
||||
|
||||
idToInjector.delete(injectorId);
|
||||
}
|
||||
}
|
||||
injectorsSeen.clear();
|
||||
injectorsSeen.clear();
|
||||
|
||||
if (!query) {
|
||||
messageBus.emit('latestComponentExplorerView', [{forest}]);
|
||||
return;
|
||||
}
|
||||
if (!query) {
|
||||
messageBus.emit('latestComponentExplorerView', [{forest}]);
|
||||
return;
|
||||
}
|
||||
|
||||
const state = getLatestComponentState(
|
||||
query,
|
||||
initializeOrGetDirectiveForestHooks().getDirectiveForest(),
|
||||
);
|
||||
const state = getLatestComponentState(
|
||||
query, initializeOrGetDirectiveForestHooks().getDirectiveForest());
|
||||
|
||||
if (state) {
|
||||
const {directiveProperties} = state;
|
||||
messageBus.emit('latestComponentExplorerView', [{forest, properties: directiveProperties}]);
|
||||
}
|
||||
};
|
||||
if (state) {
|
||||
const {directiveProperties} = state;
|
||||
messageBus.emit('latestComponentExplorerView', [{forest, properties: directiveProperties}]);
|
||||
}
|
||||
};
|
||||
|
||||
const checkForAngularCallback = (messageBus: MessageBus<Events>) => () =>
|
||||
checkForAngular(messageBus);
|
||||
checkForAngular(messageBus);
|
||||
const getRoutesCallback = (messageBus: MessageBus<Events>) => () => getRoutes(messageBus);
|
||||
|
||||
const startProfilingCallback = (messageBus: MessageBus<Events>) => () =>
|
||||
startProfiling((frame: ProfilerFrame) => {
|
||||
messageBus.emit('sendProfilerChunk', [frame]);
|
||||
});
|
||||
startProfiling((frame: ProfilerFrame) => {
|
||||
messageBus.emit('sendProfilerChunk', [frame]);
|
||||
});
|
||||
|
||||
const stopProfilingCallback = (messageBus: MessageBus<Events>) => () => {
|
||||
messageBus.emit('profilerResults', [stopProfiling()]);
|
||||
@@ -160,41 +124,33 @@ const stopProfilingCallback = (messageBus: MessageBus<Events>) => () => {
|
||||
|
||||
const selectedComponentCallback = (position: ElementPosition) => {
|
||||
const node = queryDirectiveForest(
|
||||
position,
|
||||
initializeOrGetDirectiveForestHooks().getIndexedDirectiveForest(),
|
||||
);
|
||||
position, initializeOrGetDirectiveForestHooks().getIndexedDirectiveForest());
|
||||
setConsoleReference({node, position});
|
||||
};
|
||||
|
||||
const getNestedPropertiesCallback =
|
||||
(messageBus: MessageBus<Events>) => (position: DirectivePosition, propPath: string[]) => {
|
||||
const emitEmpty = () => messageBus.emit('nestedProperties', [position, {props: {}}, propPath]);
|
||||
const node = queryDirectiveForest(
|
||||
position.element,
|
||||
initializeOrGetDirectiveForestHooks().getIndexedDirectiveForest(),
|
||||
);
|
||||
if (!node) {
|
||||
return emitEmpty();
|
||||
}
|
||||
const current =
|
||||
const getNestedPropertiesCallback = (messageBus: MessageBus<Events>) => (
|
||||
position: DirectivePosition, propPath: string[]) => {
|
||||
const emitEmpty = () => messageBus.emit('nestedProperties', [position, {props: {}}, propPath]);
|
||||
const node = queryDirectiveForest(
|
||||
position.element, initializeOrGetDirectiveForestHooks().getIndexedDirectiveForest());
|
||||
if (!node) {
|
||||
return emitEmpty();
|
||||
}
|
||||
const current =
|
||||
position.directive === undefined ? node.component : node.directives[position.directive];
|
||||
if (!current) {
|
||||
return emitEmpty();
|
||||
if (!current) {
|
||||
return emitEmpty();
|
||||
}
|
||||
let data = current.instance;
|
||||
for (const prop of propPath) {
|
||||
data = data[prop];
|
||||
if (!data) {
|
||||
console.error('Cannot access the properties', propPath, 'of', node);
|
||||
}
|
||||
let data = current.instance;
|
||||
for (const prop of propPath) {
|
||||
data = data[prop];
|
||||
if (!data) {
|
||||
console.error('Cannot access the properties', propPath, 'of', node);
|
||||
}
|
||||
}
|
||||
messageBus.emit('nestedProperties', [
|
||||
position,
|
||||
{props: serializeDirectiveState(data)},
|
||||
propPath,
|
||||
]);
|
||||
return;
|
||||
};
|
||||
}
|
||||
messageBus.emit('nestedProperties', [position, {props: serializeDirectiveState(data)}, propPath]);
|
||||
return;
|
||||
};
|
||||
|
||||
//
|
||||
// Subscribe Helpers
|
||||
@@ -253,46 +209,44 @@ export interface SerializableComponentInstanceType extends ComponentType {
|
||||
id: number;
|
||||
}
|
||||
|
||||
export interface SerializableComponentTreeNode
|
||||
extends DevToolsNode<SerializableDirectiveInstanceType, SerializableComponentInstanceType> {
|
||||
export interface SerializableComponentTreeNode extends
|
||||
DevToolsNode<SerializableDirectiveInstanceType, SerializableComponentInstanceType> {
|
||||
children: SerializableComponentTreeNode[];
|
||||
}
|
||||
|
||||
// Here we drop properties to prepare the tree for serialization.
|
||||
// We don't need the component instance, so we just traverse the tree
|
||||
// and leave the component name.
|
||||
const prepareForestForSerialization = (
|
||||
roots: ComponentTreeNode[],
|
||||
includeResolutionPath = false,
|
||||
): SerializableComponentTreeNode[] => {
|
||||
const serializedNodes: SerializableComponentTreeNode[] = [];
|
||||
for (const node of roots) {
|
||||
const serializedNode: SerializableComponentTreeNode = {
|
||||
element: node.element,
|
||||
component: node.component
|
||||
? {
|
||||
const prepareForestForSerialization = (roots: ComponentTreeNode[], includeResolutionPath = false):
|
||||
SerializableComponentTreeNode[] => {
|
||||
const serializedNodes: SerializableComponentTreeNode[] = [];
|
||||
for (const node of roots) {
|
||||
const serializedNode: SerializableComponentTreeNode = {
|
||||
element: node.element,
|
||||
component: node.component ? {
|
||||
name: node.component.name,
|
||||
isElement: node.component.isElement,
|
||||
id: initializeOrGetDirectiveForestHooks().getDirectiveId(node.component.instance)!,
|
||||
}
|
||||
: null,
|
||||
directives: node.directives.map((d) => ({
|
||||
name: d.name,
|
||||
id: initializeOrGetDirectiveForestHooks().getDirectiveId(d.instance)!,
|
||||
})),
|
||||
children: prepareForestForSerialization(node.children, includeResolutionPath),
|
||||
} :
|
||||
null,
|
||||
directives: node.directives.map(
|
||||
(d) => ({
|
||||
name: d.name,
|
||||
id: initializeOrGetDirectiveForestHooks().getDirectiveId(d.instance)!,
|
||||
})),
|
||||
children: prepareForestForSerialization(node.children, includeResolutionPath),
|
||||
};
|
||||
serializedNodes.push(serializedNode);
|
||||
|
||||
if (includeResolutionPath) {
|
||||
serializedNode.resolutionPath = getNodeDIResolutionPath(node);
|
||||
}
|
||||
}
|
||||
|
||||
return serializedNodes;
|
||||
};
|
||||
serializedNodes.push(serializedNode);
|
||||
|
||||
if (includeResolutionPath) {
|
||||
serializedNode.resolutionPath = getNodeDIResolutionPath(node);
|
||||
}
|
||||
}
|
||||
|
||||
return serializedNodes;
|
||||
};
|
||||
|
||||
function getNodeDIResolutionPath(node: ComponentTreeNode): SerializedInjector[] | undefined {
|
||||
function getNodeDIResolutionPath(node: ComponentTreeNode): SerializedInjector[]|undefined {
|
||||
const nodeInjector = getInjectorFromElementNode(node.nativeElement!);
|
||||
if (!nodeInjector) {
|
||||
return [];
|
||||
@@ -320,95 +274,91 @@ function getNodeDIResolutionPath(node: ComponentTreeNode): SerializedInjector[]
|
||||
return serializedPath;
|
||||
}
|
||||
|
||||
const getInjectorProvidersCallback =
|
||||
(messageBus: MessageBus<Events>) => (injector: SerializedInjector) => {
|
||||
if (!idToInjector.has(injector.id)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const providerRecords = getInjectorProviders(idToInjector.get(injector.id)!);
|
||||
const allProviderRecords: SerializedProviderRecord[] = [];
|
||||
|
||||
const tokenToRecords: Map<any, SerializedProviderRecord[]> = new Map();
|
||||
|
||||
for (const [index, providerRecord] of providerRecords.entries()) {
|
||||
const record = serializeProviderRecord(
|
||||
providerRecord,
|
||||
index,
|
||||
injector.type === 'environment',
|
||||
);
|
||||
|
||||
allProviderRecords.push(record);
|
||||
|
||||
const records = tokenToRecords.get(providerRecord.token) ?? [];
|
||||
records.push(record);
|
||||
tokenToRecords.set(providerRecord.token, records);
|
||||
}
|
||||
|
||||
const serializedProviderRecords: SerializedProviderRecord[] = [];
|
||||
|
||||
for (const [token, records] of tokenToRecords.entries()) {
|
||||
const multiRecords = records.filter((record) => record.multi);
|
||||
const nonMultiRecords = records.filter((record) => !record.multi);
|
||||
|
||||
for (const record of nonMultiRecords) {
|
||||
serializedProviderRecords.push(record);
|
||||
const getInjectorProvidersCallback = (messageBus: MessageBus<Events>) =>
|
||||
(injector: SerializedInjector) => {
|
||||
if (!idToInjector.has(injector.id)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const [firstMultiRecord] = multiRecords;
|
||||
if (firstMultiRecord !== undefined) {
|
||||
// All multi providers will have the same token, so we can just use the first one.
|
||||
serializedProviderRecords.push({
|
||||
token: firstMultiRecord.token,
|
||||
type: 'multi',
|
||||
multi: true,
|
||||
// todo(aleksanderbodurri): implememnt way to differentiate multi providers that
|
||||
// provided as viewProviders
|
||||
isViewProvider: firstMultiRecord.isViewProvider,
|
||||
index: records.map((record) => record.index as number),
|
||||
});
|
||||
const providerRecords = getInjectorProviders(idToInjector.get(injector.id)!);
|
||||
const allProviderRecords: SerializedProviderRecord[] = [];
|
||||
|
||||
const tokenToRecords: Map<any, SerializedProviderRecord[]> = new Map();
|
||||
|
||||
for (const [index, providerRecord] of providerRecords.entries()) {
|
||||
const record =
|
||||
serializeProviderRecord(providerRecord, index, injector.type === 'environment');
|
||||
|
||||
allProviderRecords.push(record);
|
||||
|
||||
const records = tokenToRecords.get(providerRecord.token) ?? [];
|
||||
records.push(record);
|
||||
tokenToRecords.set(providerRecord.token, records);
|
||||
}
|
||||
}
|
||||
|
||||
messageBus.emit('latestInjectorProviders', [injector, serializedProviderRecords]);
|
||||
};
|
||||
const serializedProviderRecords: SerializedProviderRecord[] = [];
|
||||
|
||||
const logProvider = (
|
||||
serializedInjector: SerializedInjector,
|
||||
serializedProvider: SerializedProviderRecord,
|
||||
): void => {
|
||||
if (!idToInjector.has(serializedInjector.id)) {
|
||||
return;
|
||||
}
|
||||
for (const [token, records] of tokenToRecords.entries()) {
|
||||
const multiRecords = records.filter(record => record.multi);
|
||||
const nonMultiRecords = records.filter(record => !record.multi);
|
||||
|
||||
const injector = idToInjector.get(serializedInjector.id)!;
|
||||
for (const record of nonMultiRecords) {
|
||||
serializedProviderRecords.push(record);
|
||||
}
|
||||
|
||||
const providerRecords = getInjectorProviders(injector);
|
||||
const [firstMultiRecord] = multiRecords;
|
||||
if (firstMultiRecord !== undefined) {
|
||||
// All multi providers will have the same token, so we can just use the first one.
|
||||
serializedProviderRecords.push({
|
||||
token: firstMultiRecord.token,
|
||||
type: 'multi',
|
||||
multi: true,
|
||||
// todo(aleksanderbodurri): implememnt way to differentiate multi providers that
|
||||
// provided as viewProviders
|
||||
isViewProvider: firstMultiRecord.isViewProvider,
|
||||
index: records.map(record => record.index as number),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
console.group(
|
||||
`%c${serializedInjector.name}`,
|
||||
`color: ${
|
||||
serializedInjector.type === 'element' ? '#a7d5a9' : '#f05057'
|
||||
}; font-size: 1.25rem; font-weight: bold;`,
|
||||
);
|
||||
// tslint:disable-next-line:no-console
|
||||
console.log('injector: ', injector);
|
||||
messageBus.emit('latestInjectorProviders', [injector, serializedProviderRecords]);
|
||||
};
|
||||
|
||||
if (typeof serializedProvider.index === 'number') {
|
||||
const provider = providerRecords[serializedProvider.index];
|
||||
const logProvider =
|
||||
(serializedInjector: SerializedInjector, serializedProvider: SerializedProviderRecord):
|
||||
void => {
|
||||
if (!idToInjector.has(serializedInjector.id)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// tslint:disable-next-line:no-console
|
||||
console.log('provider: ', provider);
|
||||
// tslint:disable-next-line:no-console
|
||||
console.log(`value: `, injector.get(provider.token, null, {optional: true}));
|
||||
} else if (Array.isArray(serializedProvider.index)) {
|
||||
const providers = serializedProvider.index.map((index) => providerRecords[index]);
|
||||
const injector = idToInjector.get(serializedInjector.id)!;
|
||||
|
||||
// tslint:disable-next-line:no-console
|
||||
console.log('providers: ', providers);
|
||||
// tslint:disable-next-line:no-console
|
||||
console.log(`value: `, injector.get(providers[0].token, null, {optional: true}));
|
||||
}
|
||||
const providerRecords = getInjectorProviders(injector);
|
||||
|
||||
console.groupEnd();
|
||||
};
|
||||
console.group(
|
||||
`%c${serializedInjector.name}`,
|
||||
`color: ${
|
||||
serializedInjector.type === 'element' ?
|
||||
'#a7d5a9' :
|
||||
'#f05057'}; font-size: 1.25rem; font-weight: bold;`);
|
||||
// tslint:disable-next-line:no-console
|
||||
console.log('injector: ', injector);
|
||||
|
||||
if (typeof serializedProvider.index === 'number') {
|
||||
const provider = providerRecords[serializedProvider.index];
|
||||
|
||||
// tslint:disable-next-line:no-console
|
||||
console.log('provider: ', provider);
|
||||
// tslint:disable-next-line:no-console
|
||||
console.log(`value: `, injector.get(provider.token, null, {optional: true}));
|
||||
} else if (Array.isArray(serializedProvider.index)) {
|
||||
const providers = serializedProvider.index.map(index => providerRecords[index]);
|
||||
|
||||
// tslint:disable-next-line:no-console
|
||||
console.log('providers: ', providers);
|
||||
// tslint:disable-next-line:no-console
|
||||
console.log(`value: `, injector.get(providers[0].token, null, {optional: true}));
|
||||
}
|
||||
|
||||
console.groupEnd();
|
||||
};
|
||||
|
||||
+9
-13
@@ -14,7 +14,7 @@ import {initializeOrGetDirectiveForestHooks} from '../hooks';
|
||||
import {ComponentTreeNode} from '../interfaces';
|
||||
|
||||
interface Type<T> extends Function {
|
||||
new (...args: any[]): T;
|
||||
new(...args: any[]): T;
|
||||
}
|
||||
export interface ComponentInspectorOptions {
|
||||
onComponentEnter: (id: number) => void;
|
||||
@@ -28,13 +28,11 @@ export class ComponentInspector {
|
||||
private readonly _onComponentSelect;
|
||||
private readonly _onComponentLeave;
|
||||
|
||||
constructor(
|
||||
componentOptions: ComponentInspectorOptions = {
|
||||
onComponentEnter: () => {},
|
||||
onComponentLeave: () => {},
|
||||
onComponentSelect: () => {},
|
||||
},
|
||||
) {
|
||||
constructor(componentOptions: ComponentInspectorOptions = {
|
||||
onComponentEnter: () => {},
|
||||
onComponentLeave: () => {},
|
||||
onComponentSelect: () => {},
|
||||
}) {
|
||||
this.bindMethods();
|
||||
this._onComponentEnter = componentOptions.onComponentEnter;
|
||||
this._onComponentSelect = componentOptions.onComponentSelect;
|
||||
@@ -59,8 +57,7 @@ export class ComponentInspector {
|
||||
|
||||
if (this._selectedComponent.component && this._selectedComponent.host) {
|
||||
this._onComponentSelect(
|
||||
initializeOrGetDirectiveForestHooks().getDirectiveId(this._selectedComponent.component)!,
|
||||
);
|
||||
initializeOrGetDirectiveForestHooks().getDirectiveId(this._selectedComponent.component)!);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -76,8 +73,7 @@ export class ComponentInspector {
|
||||
if (this._selectedComponent.component && this._selectedComponent.host) {
|
||||
highlight(this._selectedComponent.host);
|
||||
this._onComponentEnter(
|
||||
initializeOrGetDirectiveForestHooks().getDirectiveId(this._selectedComponent.component)!,
|
||||
);
|
||||
initializeOrGetDirectiveForestHooks().getDirectiveId(this._selectedComponent.component)!);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -97,7 +93,7 @@ export class ComponentInspector {
|
||||
|
||||
highlightByPosition(position: ElementPosition): void {
|
||||
const forest: ComponentTreeNode[] = initializeOrGetDirectiveForestHooks().getDirectiveForest();
|
||||
const elementToHighlight: HTMLElement | null = findNodeInForest(position, forest);
|
||||
const elementToHighlight: HTMLElement|null = findNodeInForest(position, forest);
|
||||
if (elementToHighlight) {
|
||||
highlight(elementToHighlight);
|
||||
}
|
||||
|
||||
@@ -5,28 +5,11 @@
|
||||
* 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 {
|
||||
ComponentExplorerViewQuery,
|
||||
DirectiveMetadata,
|
||||
DirectivesProperties,
|
||||
ElementPosition,
|
||||
PropertyQueryTypes,
|
||||
SerializedInjectedService,
|
||||
SerializedInjector,
|
||||
SerializedProviderRecord,
|
||||
UpdatedStateData,
|
||||
} from 'protocol';
|
||||
import {ComponentExplorerViewQuery, DirectiveMetadata, DirectivesProperties, ElementPosition, PropertyQueryTypes, SerializedInjectedService, SerializedInjector, SerializedProviderRecord, UpdatedStateData,} from 'protocol';
|
||||
|
||||
import {buildDirectiveTree, getLViewFromDirectiveOrElementInstance} from './directive-forest/index';
|
||||
import {
|
||||
ngDebugApiIsSupported,
|
||||
ngDebugClient,
|
||||
ngDebugDependencyInjectionApiIsSupported,
|
||||
} from './ng-debug-api/ng-debug-api';
|
||||
import {
|
||||
deeplySerializeSelectedProperties,
|
||||
serializeDirectiveState,
|
||||
} from './state-serializer/state-serializer';
|
||||
import {ngDebugApiIsSupported, ngDebugClient, ngDebugDependencyInjectionApiIsSupported} from './ng-debug-api/ng-debug-api';
|
||||
import {deeplySerializeSelectedProperties, serializeDirectiveState,} from './state-serializer/state-serializer';
|
||||
|
||||
// Need to be kept in sync with Angular framework
|
||||
// We can't directly import it from framework now
|
||||
@@ -39,21 +22,9 @@ enum ChangeDetectionStrategy {
|
||||
|
||||
import {ComponentTreeNode, DirectiveInstanceType, ComponentInstanceType} from './interfaces';
|
||||
|
||||
import type {
|
||||
ClassProvider,
|
||||
ExistingProvider,
|
||||
FactoryProvider,
|
||||
InjectOptions,
|
||||
InjectionToken,
|
||||
Injector,
|
||||
Type,
|
||||
ValueProvider,
|
||||
ɵComponentDebugMetadata as ComponentDebugMetadata,
|
||||
ɵProviderRecord as ProviderRecord,
|
||||
} from '@angular/core';
|
||||
import {isSignal} from './utils';
|
||||
import type {ClassProvider, ExistingProvider, FactoryProvider, InjectOptions, InjectionToken, Injector, Type, ValueProvider, ɵComponentDebugMetadata as ComponentDebugMetadata, ɵProviderRecord as ProviderRecord} from '@angular/core';
|
||||
|
||||
export const injectorToId = new WeakMap<Injector | HTMLElement, string>();
|
||||
export const injectorToId = new WeakMap<Injector|HTMLElement, string>();
|
||||
export const nodeInjectorToResolutionPath = new WeakMap<HTMLElement, SerializedInjector[]>();
|
||||
export const idToInjector = new Map<string, Injector>();
|
||||
export const injectorsSeen = new Set<string>();
|
||||
@@ -75,14 +46,12 @@ export function getInjectorResolutionPath(injector: Injector): Injector[] {
|
||||
return ngDebugClient().ɵgetInjectorResolutionPath(injector);
|
||||
}
|
||||
|
||||
export function getInjectorFromElementNode(element: Node): Injector | null {
|
||||
export function getInjectorFromElementNode(element: Node): Injector|null {
|
||||
return ngDebugClient().getInjector(element);
|
||||
}
|
||||
|
||||
function getDirectivesFromElement(element: HTMLElement): {
|
||||
component: unknown | null;
|
||||
directives: unknown[];
|
||||
} {
|
||||
function getDirectivesFromElement(element: HTMLElement):
|
||||
{component: unknown|null; directives: unknown[];} {
|
||||
let component = null;
|
||||
if (element instanceof Element) {
|
||||
component = ngDebugClient().getComponent(element);
|
||||
@@ -95,9 +64,9 @@ function getDirectivesFromElement(element: HTMLElement): {
|
||||
}
|
||||
|
||||
export const getLatestComponentState = (
|
||||
query: ComponentExplorerViewQuery,
|
||||
directiveForest?: ComponentTreeNode[],
|
||||
): {directiveProperties: DirectivesProperties} | undefined => {
|
||||
query: ComponentExplorerViewQuery,
|
||||
directiveForest?: ComponentTreeNode[],
|
||||
): {directiveProperties: DirectivesProperties}|undefined => {
|
||||
// if a directive forest is passed in we don't have to build the forest again.
|
||||
directiveForest = directiveForest ?? buildDirectiveForest();
|
||||
|
||||
@@ -111,19 +80,19 @@ export const getLatestComponentState = (
|
||||
const injector = ngDebugClient().getInjector(node.nativeElement!);
|
||||
|
||||
const injectors = getInjectorResolutionPath(injector);
|
||||
const resolutionPathWithProviders = !ngDebugDependencyInjectionApiIsSupported()
|
||||
? []
|
||||
: injectors.map((injector) => ({
|
||||
injector,
|
||||
providers: getInjectorProviders(injector),
|
||||
}));
|
||||
const populateResultSet = (dir: DirectiveInstanceType | ComponentInstanceType) => {
|
||||
const resolutionPathWithProviders = !ngDebugDependencyInjectionApiIsSupported() ?
|
||||
[] :
|
||||
injectors.map((injector) => ({
|
||||
injector,
|
||||
providers: getInjectorProviders(injector),
|
||||
}));
|
||||
const populateResultSet = (dir: DirectiveInstanceType|ComponentInstanceType) => {
|
||||
const {instance, name} = dir;
|
||||
const metadata = getDirectiveMetadata(instance);
|
||||
metadata.dependencies = getDependenciesForDirective(
|
||||
injector,
|
||||
resolutionPathWithProviders,
|
||||
instance.constructor,
|
||||
injector,
|
||||
resolutionPathWithProviders,
|
||||
instance.constructor,
|
||||
);
|
||||
|
||||
if (query.propertyQuery.type === PropertyQueryTypes.All) {
|
||||
@@ -136,9 +105,9 @@ export const getLatestComponentState = (
|
||||
if (query.propertyQuery.type === PropertyQueryTypes.Specified) {
|
||||
directiveProperties[name] = {
|
||||
props: deeplySerializeSelectedProperties(
|
||||
instance,
|
||||
query.propertyQuery.properties[name] || [],
|
||||
),
|
||||
instance,
|
||||
query.propertyQuery.properties[name] || [],
|
||||
),
|
||||
metadata,
|
||||
};
|
||||
}
|
||||
@@ -154,7 +123,7 @@ export const getLatestComponentState = (
|
||||
};
|
||||
};
|
||||
|
||||
function serializeElementInjectorWithId(injector: Injector): SerializedInjector | null {
|
||||
function serializeElementInjectorWithId(injector: Injector): SerializedInjector|null {
|
||||
let id: string;
|
||||
const element = getElementInjectorElement(injector);
|
||||
|
||||
@@ -176,7 +145,7 @@ function serializeElementInjectorWithId(injector: Injector): SerializedInjector
|
||||
return {id, ...serializedInjector};
|
||||
}
|
||||
|
||||
function serializeInjectorWithId(injector: Injector): SerializedInjector | null {
|
||||
function serializeInjectorWithId(injector: Injector): SerializedInjector|null {
|
||||
if (isElementInjector(injector)) {
|
||||
return serializeElementInjectorWithId(injector);
|
||||
} else {
|
||||
@@ -184,7 +153,7 @@ function serializeInjectorWithId(injector: Injector): SerializedInjector | null
|
||||
}
|
||||
}
|
||||
|
||||
function serializeEnvironmentInjectorWithId(injector: Injector): SerializedInjector | null {
|
||||
function serializeEnvironmentInjectorWithId(injector: Injector): SerializedInjector|null {
|
||||
let id: string;
|
||||
|
||||
if (!injectorToId.has(injector)) {
|
||||
@@ -254,16 +223,16 @@ export function getInjectorProviders(injector: Injector) {
|
||||
}
|
||||
|
||||
const getDependenciesForDirective = (
|
||||
injector: Injector,
|
||||
resolutionPath: {injector: Injector; providers: ProviderRecord[]}[],
|
||||
directive: any,
|
||||
): SerializedInjectedService[] => {
|
||||
injector: Injector,
|
||||
resolutionPath: {injector: Injector; providers: ProviderRecord[]}[],
|
||||
directive: any,
|
||||
): SerializedInjectedService[] => {
|
||||
if (!ngDebugApiIsSupported('ɵgetDependenciesFromInjectable')) {
|
||||
return [];
|
||||
}
|
||||
|
||||
let dependencies =
|
||||
ngDebugClient().ɵgetDependenciesFromInjectable(injector, directive)?.dependencies ?? [];
|
||||
ngDebugClient().ɵgetDependenciesFromInjectable(injector, directive)?.dependencies ?? [];
|
||||
const serializedInjectedServices: SerializedInjectedService[] = [];
|
||||
|
||||
let position = 0;
|
||||
@@ -286,9 +255,8 @@ const getDependenciesForDirective = (
|
||||
// dependency (2)
|
||||
const dependencyResolutionPath: SerializedInjector[] = [
|
||||
// (1)
|
||||
...resolutionPath
|
||||
.slice(0, foundInjectorIndex + 1)
|
||||
.map((node) => serializeInjectorWithId(node.injector)!),
|
||||
...resolutionPath.slice(0, foundInjectorIndex + 1)
|
||||
.map((node) => serializeInjectorWithId(node.injector)!),
|
||||
|
||||
// (2)
|
||||
// We slice the import path to remove the first element because this is the same
|
||||
@@ -345,7 +313,7 @@ function stripUnderscore(str: string): string {
|
||||
return str;
|
||||
}
|
||||
|
||||
export function serializeInjector(injector: Injector): Omit<SerializedInjector, 'id'> | null {
|
||||
export function serializeInjector(injector: Injector): Omit<SerializedInjector, 'id'>|null {
|
||||
const metadata = getInjectorMetadata(injector);
|
||||
|
||||
if (metadata === null) {
|
||||
@@ -385,11 +353,11 @@ export function serializeInjector(injector: Injector): Omit<SerializedInjector,
|
||||
}
|
||||
|
||||
export function serializeProviderRecord(
|
||||
providerRecord: ProviderRecord,
|
||||
index: number,
|
||||
hasImportPath = false,
|
||||
): SerializedProviderRecord {
|
||||
let type: 'type' | 'class' | 'value' | 'factory' | 'existing' = 'type';
|
||||
providerRecord: ProviderRecord,
|
||||
index: number,
|
||||
hasImportPath = false,
|
||||
): SerializedProviderRecord {
|
||||
let type: 'type'|'class'|'value'|'factory'|'existing' = 'type';
|
||||
let multi = false;
|
||||
|
||||
if (typeof providerRecord.provider === 'object') {
|
||||
@@ -409,11 +377,7 @@ export function serializeProviderRecord(
|
||||
}
|
||||
|
||||
const serializedProvider: {
|
||||
token: string;
|
||||
type: typeof type;
|
||||
multi: boolean;
|
||||
isViewProvider: boolean;
|
||||
index: number;
|
||||
token: string; type: typeof type; multi: boolean; isViewProvider: boolean; index: number;
|
||||
importPath?: string[];
|
||||
} = {
|
||||
token: valueToLabel(providerRecord.token),
|
||||
@@ -424,9 +388,10 @@ export function serializeProviderRecord(
|
||||
};
|
||||
|
||||
if (hasImportPath) {
|
||||
serializedProvider['importPath'] = (providerRecord.importPath ?? []).map((injector) =>
|
||||
valueToLabel(injector),
|
||||
);
|
||||
serializedProvider['importPath'] = (providerRecord.importPath ?? [])
|
||||
.map(
|
||||
(injector) => valueToLabel(injector),
|
||||
);
|
||||
}
|
||||
|
||||
return serializedProvider;
|
||||
@@ -435,8 +400,8 @@ export function serializeProviderRecord(
|
||||
function elementToDirectiveNames(element: HTMLElement): string[] {
|
||||
const {component, directives} = getDirectivesFromElement(element);
|
||||
return [component, ...directives]
|
||||
.map((dir) => dir?.constructor?.name ?? '')
|
||||
.filter((dir) => !!dir);
|
||||
.map((dir) => dir?.constructor?.name ?? '')
|
||||
.filter((dir) => !!dir);
|
||||
}
|
||||
|
||||
export function getElementInjectorElement(elementInjector: Injector): HTMLElement {
|
||||
@@ -447,7 +412,7 @@ export function getElementInjectorElement(elementInjector: Injector): HTMLElemen
|
||||
return getInjectorMetadata(elementInjector)!.source as HTMLElement;
|
||||
}
|
||||
|
||||
function isInjectionToken(token: Type<unknown> | InjectionToken<unknown>): boolean {
|
||||
function isInjectionToken(token: Type<unknown>|InjectionToken<unknown>): boolean {
|
||||
return token.constructor.name === 'InjectionToken';
|
||||
}
|
||||
|
||||
@@ -481,7 +446,7 @@ const getRoots = () => {
|
||||
const roots = Array.from(document.documentElement.querySelectorAll('[ng-version]'));
|
||||
|
||||
const isTopLevel = (element: Element) => {
|
||||
let parent: Element | null = element;
|
||||
let parent: Element|null = element;
|
||||
|
||||
while (parent?.parentElement) {
|
||||
parent = parent.parentElement;
|
||||
@@ -504,13 +469,13 @@ export const buildDirectiveForest = (): ComponentTreeNode[] => {
|
||||
// Based on an ElementID we return a specific component node.
|
||||
// If we can't find any, we return null.
|
||||
export const queryDirectiveForest = (
|
||||
position: ElementPosition,
|
||||
forest: ComponentTreeNode[],
|
||||
): ComponentTreeNode | null => {
|
||||
position: ElementPosition,
|
||||
forest: ComponentTreeNode[],
|
||||
): ComponentTreeNode|null => {
|
||||
if (!position.length) {
|
||||
return null;
|
||||
}
|
||||
let node: null | ComponentTreeNode = null;
|
||||
let node: null|ComponentTreeNode = null;
|
||||
for (const i of position) {
|
||||
node = forest[i];
|
||||
if (!node) {
|
||||
@@ -522,16 +487,16 @@ export const queryDirectiveForest = (
|
||||
};
|
||||
|
||||
export const findNodeInForest = (
|
||||
position: ElementPosition,
|
||||
forest: ComponentTreeNode[],
|
||||
): HTMLElement | null => {
|
||||
const foundComponent: ComponentTreeNode | null = queryDirectiveForest(position, forest);
|
||||
position: ElementPosition,
|
||||
forest: ComponentTreeNode[],
|
||||
): HTMLElement|null => {
|
||||
const foundComponent: ComponentTreeNode|null = queryDirectiveForest(position, forest);
|
||||
return foundComponent ? (foundComponent.nativeElement as HTMLElement) : null;
|
||||
};
|
||||
|
||||
export const findNodeFromSerializedPosition = (
|
||||
serializedPosition: string,
|
||||
): ComponentTreeNode | null => {
|
||||
serializedPosition: string,
|
||||
): ComponentTreeNode|null => {
|
||||
const position: number[] = serializedPosition.split(',').map((index) => parseInt(index, 10));
|
||||
return queryDirectiveForest(position, buildDirectiveForest());
|
||||
};
|
||||
@@ -541,9 +506,9 @@ export const updateState = (updatedStateData: UpdatedStateData): void => {
|
||||
const node = queryDirectiveForest(updatedStateData.directiveId.element, buildDirectiveForest());
|
||||
if (!node) {
|
||||
console.warn(
|
||||
'Could not update the state of component',
|
||||
updatedStateData,
|
||||
'because the component was not found',
|
||||
'Could not update the state of component',
|
||||
updatedStateData,
|
||||
'because the component was not found',
|
||||
);
|
||||
return;
|
||||
}
|
||||
@@ -572,27 +537,19 @@ const mutateComponentOrDirective = (updatedStateData: UpdatedStateData, compOrDi
|
||||
parentObjectOfValueToUpdate = parentObjectOfValueToUpdate[key];
|
||||
});
|
||||
|
||||
if (isSignal(parentObjectOfValueToUpdate)) {
|
||||
// we don't support updating nested objects in signals yet
|
||||
return;
|
||||
}
|
||||
|
||||
// When we try to set a property which only has a getter
|
||||
// the line below could throw an error.
|
||||
try {
|
||||
if (isSignal(parentObjectOfValueToUpdate[valueKey])) {
|
||||
parentObjectOfValueToUpdate[valueKey].set(updatedStateData.newValue);
|
||||
} else {
|
||||
parentObjectOfValueToUpdate[valueKey] = updatedStateData.newValue;
|
||||
}
|
||||
} catch {}
|
||||
parentObjectOfValueToUpdate[valueKey] = updatedStateData.newValue;
|
||||
} catch {
|
||||
}
|
||||
};
|
||||
|
||||
export function serializeResolutionPath(resolutionPath: Injector[]): SerializedInjector[] {
|
||||
const serializedResolutionPath: SerializedInjector[] = [];
|
||||
|
||||
for (const injector of resolutionPath) {
|
||||
let serializedInjectorWithId: SerializedInjector | null = null;
|
||||
let serializedInjectorWithId: SerializedInjector|null = null;
|
||||
|
||||
if (isElementInjector(injector)) {
|
||||
serializedInjectorWithId = serializeElementInjectorWithId(injector);
|
||||
|
||||
@@ -9,16 +9,12 @@
|
||||
import {LTreeStrategy} from './ltree';
|
||||
import {RTreeStrategy} from './render-tree';
|
||||
|
||||
export {
|
||||
getDirectiveHostElement,
|
||||
getLViewFromDirectiveOrElementInstance,
|
||||
METADATA_PROPERTY_NAME,
|
||||
} from './ltree';
|
||||
export {getDirectiveHostElement, getLViewFromDirectiveOrElementInstance, METADATA_PROPERTY_NAME} from './ltree';
|
||||
|
||||
// The order of the strategies matters. Lower indices have higher priority.
|
||||
const strategies = [new RTreeStrategy(), new LTreeStrategy()];
|
||||
|
||||
let strategy: null | RTreeStrategy | LTreeStrategy = null;
|
||||
let strategy: null|RTreeStrategy|LTreeStrategy = null;
|
||||
|
||||
const selectStrategy = (element: Element) => {
|
||||
for (const s of strategies) {
|
||||
|
||||
@@ -28,19 +28,20 @@ const TYPE = 1;
|
||||
const ELEMENT = 0;
|
||||
const LVIEW_TVIEW = 1;
|
||||
|
||||
|
||||
// Big oversimplification of the LView structure.
|
||||
type LView = Array<any>;
|
||||
|
||||
export const isLContainer = (value: unknown): boolean => {
|
||||
export const isLContainer = (value: any): boolean => {
|
||||
return Array.isArray(value) && value[TYPE] === true;
|
||||
};
|
||||
|
||||
const isLView = (value: unknown): value is LView => {
|
||||
const isLView = (value: any): boolean => {
|
||||
return Array.isArray(value) && typeof value[TYPE] === 'object';
|
||||
};
|
||||
|
||||
export const METADATA_PROPERTY_NAME = '__ngContext__';
|
||||
export function getLViewFromDirectiveOrElementInstance(dir: any): null | LView {
|
||||
export function getLViewFromDirectiveOrElementInstance(dir: any): null|LView {
|
||||
if (!dir) {
|
||||
return null;
|
||||
}
|
||||
@@ -77,9 +78,9 @@ export class LTreeStrategy {
|
||||
return typeof (element as any).__ngContext__ !== 'undefined';
|
||||
}
|
||||
|
||||
private _getNode(lView: LView, data: any, idx: number): ComponentTreeNode {
|
||||
private _getNode(lView: any, data: any, idx: number): ComponentTreeNode {
|
||||
const directives: DirectiveInstanceType[] = [];
|
||||
let component: ComponentInstanceType | null = null;
|
||||
let component: ComponentInstanceType|null = null;
|
||||
const tNode = data[idx];
|
||||
const node = lView[idx][ELEMENT];
|
||||
const element = (node.tagName || node.nodeName).toLowerCase();
|
||||
|
||||
@@ -19,7 +19,7 @@ describe('render tree extraction', () => {
|
||||
componentMap = new Map();
|
||||
|
||||
(window as any).ng = {
|
||||
getDirectiveMetadata(): void {},
|
||||
getDirectiveMetadata(): void{},
|
||||
getComponent(element: Element): any {
|
||||
return componentMap.get(element);
|
||||
},
|
||||
@@ -32,12 +32,12 @@ describe('render tree extraction', () => {
|
||||
afterEach(() => delete (window as any).ng);
|
||||
|
||||
it('should detect Angular Ivy apps', () => {
|
||||
expect(treeStrategy.supports()).toBeTrue();
|
||||
expect(treeStrategy.supports({})).toBeTrue();
|
||||
});
|
||||
|
||||
it('should fail with detection of non-Ivy apps', () => {
|
||||
delete (window as any).ng.getDirectiveMetadata;
|
||||
expect(treeStrategy.supports()).toBeFalse();
|
||||
expect(treeStrategy.supports({})).toBeFalse();
|
||||
});
|
||||
|
||||
it('should extract render tree from an empty element', () => {
|
||||
|
||||
@@ -10,60 +10,56 @@ import {ComponentTreeNode} from '../interfaces';
|
||||
import {ngDebugClient} from '../ng-debug-api/ng-debug-api';
|
||||
import {isCustomElement} from '../utils';
|
||||
|
||||
const extractViewTree = (
|
||||
domNode: Node | Element,
|
||||
result: ComponentTreeNode[],
|
||||
getComponent: (element: Element) => {} | null,
|
||||
getDirectives: (node: Node) => {}[],
|
||||
): ComponentTreeNode[] => {
|
||||
const directives = getDirectives(domNode);
|
||||
if (!directives.length && !(domNode instanceof Element)) {
|
||||
return result;
|
||||
}
|
||||
const componentTreeNode: ComponentTreeNode = {
|
||||
children: [],
|
||||
component: null,
|
||||
directives: directives.map((dir) => {
|
||||
return {
|
||||
instance: dir,
|
||||
name: dir.constructor.name,
|
||||
const extractViewTree =
|
||||
(domNode: Node|Element, result: ComponentTreeNode[],
|
||||
getComponent: (element: Element) => {} | null,
|
||||
getDirectives: (node: Node) => {}[]): ComponentTreeNode[] => {
|
||||
const directives = getDirectives(domNode);
|
||||
if (!directives.length && !(domNode instanceof Element)) {
|
||||
return result;
|
||||
}
|
||||
const componentTreeNode: ComponentTreeNode = {
|
||||
children: [],
|
||||
component: null,
|
||||
directives: directives.map((dir) => {
|
||||
return {
|
||||
instance: dir,
|
||||
name: dir.constructor.name,
|
||||
};
|
||||
}),
|
||||
element: domNode.nodeName.toLowerCase(),
|
||||
nativeElement: domNode,
|
||||
};
|
||||
}),
|
||||
element: domNode.nodeName.toLowerCase(),
|
||||
nativeElement: domNode,
|
||||
};
|
||||
if (!(domNode instanceof Element)) {
|
||||
result.push(componentTreeNode);
|
||||
return result;
|
||||
}
|
||||
const component = getComponent(domNode);
|
||||
if (component) {
|
||||
componentTreeNode.component = {
|
||||
instance: component,
|
||||
isElement: isCustomElement(domNode),
|
||||
name: domNode.nodeName.toLowerCase(),
|
||||
if (!(domNode instanceof Element)) {
|
||||
result.push(componentTreeNode);
|
||||
return result;
|
||||
}
|
||||
const component = getComponent(domNode);
|
||||
if (component) {
|
||||
componentTreeNode.component = {
|
||||
instance: component,
|
||||
isElement: isCustomElement(domNode),
|
||||
name: domNode.nodeName.toLowerCase(),
|
||||
};
|
||||
}
|
||||
if (component || componentTreeNode.directives.length) {
|
||||
result.push(componentTreeNode);
|
||||
}
|
||||
if (componentTreeNode.component || componentTreeNode.directives.length) {
|
||||
domNode.childNodes.forEach(
|
||||
(node) =>
|
||||
extractViewTree(node, componentTreeNode.children, getComponent, getDirectives));
|
||||
} else {
|
||||
domNode.childNodes.forEach(
|
||||
(node) => extractViewTree(node, result, getComponent, getDirectives));
|
||||
}
|
||||
return result;
|
||||
};
|
||||
}
|
||||
if (component || componentTreeNode.directives.length) {
|
||||
result.push(componentTreeNode);
|
||||
}
|
||||
if (componentTreeNode.component || componentTreeNode.directives.length) {
|
||||
domNode.childNodes.forEach((node) =>
|
||||
extractViewTree(node, componentTreeNode.children, getComponent, getDirectives),
|
||||
);
|
||||
} else {
|
||||
domNode.childNodes.forEach((node) =>
|
||||
extractViewTree(node, result, getComponent, getDirectives),
|
||||
);
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
export class RTreeStrategy {
|
||||
supports(): boolean {
|
||||
return (['getDirectiveMetadata', 'getComponent', 'getDirectives'] as const).every(
|
||||
(method) => typeof ngDebugClient()[method] === 'function',
|
||||
);
|
||||
supports(_: any): boolean {
|
||||
return (['getDirectiveMetadata', 'getComponent', 'getDirectives'] as const)
|
||||
.every((method) => typeof ngDebugClient()[method] === 'function');
|
||||
}
|
||||
|
||||
build(element: Element): ComponentTreeNode[] {
|
||||
|
||||
@@ -12,7 +12,7 @@ let overlayContent: HTMLElement;
|
||||
declare const ng: any;
|
||||
|
||||
interface Type<T> extends Function {
|
||||
new (...args: any[]): T;
|
||||
new(...args: any[]): T;
|
||||
}
|
||||
|
||||
const DEV_TOOLS_HIGHLIGHT_NODE_ID = '____ngDevToolsHighlight';
|
||||
@@ -40,27 +40,26 @@ function init(): void {
|
||||
overlay.appendChild(overlayContent);
|
||||
}
|
||||
|
||||
export const findComponentAndHost = (
|
||||
el: Node | undefined,
|
||||
): {component: any; host: HTMLElement | null} => {
|
||||
if (!el) {
|
||||
return {component: null, host: null};
|
||||
}
|
||||
while (el) {
|
||||
const component = el instanceof HTMLElement && ng.getComponent(el);
|
||||
if (component) {
|
||||
return {component, host: el as HTMLElement};
|
||||
}
|
||||
if (!el.parentElement) {
|
||||
break;
|
||||
}
|
||||
el = el.parentElement;
|
||||
}
|
||||
return {component: null, host: null};
|
||||
};
|
||||
export const findComponentAndHost =
|
||||
(el: Node|undefined): {component: any; host: HTMLElement | null} => {
|
||||
if (!el) {
|
||||
return {component: null, host: null};
|
||||
}
|
||||
while (el) {
|
||||
const component = el instanceof HTMLElement && ng.getComponent(el);
|
||||
if (component) {
|
||||
return {component, host: el as HTMLElement};
|
||||
}
|
||||
if (!el.parentElement) {
|
||||
break;
|
||||
}
|
||||
el = el.parentElement;
|
||||
}
|
||||
return {component: null, host: null};
|
||||
};
|
||||
|
||||
// Todo(aleksanderbodurri): this should not be part of the highlighter, move this somewhere else
|
||||
export function getDirectiveName(dir: Type<unknown> | undefined | null): string {
|
||||
export function getDirectiveName(dir: Type<unknown>|undefined|null): string {
|
||||
return dir ? dir.constructor.name : 'unknown';
|
||||
}
|
||||
|
||||
@@ -98,12 +97,11 @@ export function inDoc(node: any): boolean {
|
||||
}
|
||||
const doc = node.ownerDocument.documentElement;
|
||||
const parent = node.parentNode;
|
||||
return (
|
||||
doc === node || doc === parent || !!(parent && parent.nodeType === 1 && doc.contains(parent))
|
||||
);
|
||||
return doc === node || doc === parent ||
|
||||
!!(parent && parent.nodeType === 1 && doc.contains(parent));
|
||||
}
|
||||
|
||||
function getComponentRect(el: Node): DOMRect | undefined {
|
||||
function getComponentRect(el: Node): DOMRect|undefined {
|
||||
if (!(el instanceof HTMLElement)) {
|
||||
return;
|
||||
}
|
||||
@@ -139,7 +137,7 @@ function positionOverlayContent(dimensions: DOMRect) {
|
||||
|
||||
// Attempt to position the content element so that it's always in the
|
||||
// viewport along the Y axis. Prefer to position on the bottom.
|
||||
if (dimensions.bottom + yOffset <= viewportHeight) {
|
||||
if ((dimensions.bottom + yOffset) <= viewportHeight) {
|
||||
style.bottom = yOffsetValue;
|
||||
// If it doesn't fit on the bottom, try to position on top.
|
||||
} else if (dimensions.top - yOffset >= 0) {
|
||||
|
||||
@@ -6,13 +6,7 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {
|
||||
DirectiveProfile,
|
||||
ElementPosition,
|
||||
ElementProfile,
|
||||
LifecycleProfile,
|
||||
ProfilerFrame,
|
||||
} from 'protocol';
|
||||
import {DirectiveProfile, ElementPosition, ElementProfile, LifecycleProfile, ProfilerFrame} from 'protocol';
|
||||
|
||||
import {getDirectiveName} from '../highlighter';
|
||||
import {ComponentTreeNode} from '../interfaces';
|
||||
@@ -65,20 +59,16 @@ const getHooks = (onFrame: (frame: ProfilerFrame) => void): Partial<Hooks> => {
|
||||
// We flush here because it's possible the current node to overwrite
|
||||
// an existing removed node.
|
||||
onCreate(
|
||||
directive: any,
|
||||
node: Node,
|
||||
_: number,
|
||||
isComponent: boolean,
|
||||
position: ElementPosition,
|
||||
): void {
|
||||
eventMap.set(directive, {
|
||||
isElement: isCustomElement(node),
|
||||
name: getDirectiveName(directive),
|
||||
isComponent,
|
||||
lifecycle: {},
|
||||
outputs: {},
|
||||
});
|
||||
},
|
||||
directive: any, node: Node, _: number, isComponent: boolean, position: ElementPosition):
|
||||
void {
|
||||
eventMap.set(directive, {
|
||||
isElement: isCustomElement(node),
|
||||
name: getDirectiveName(directive),
|
||||
isComponent,
|
||||
lifecycle: {},
|
||||
outputs: {},
|
||||
});
|
||||
},
|
||||
onChangeDetectionStart(component: any, node: Node): void {
|
||||
startEvent(timeStartMap, component, 'changeDetection');
|
||||
if (!inChangeDetection) {
|
||||
@@ -121,31 +111,22 @@ const getHooks = (onFrame: (frame: ProfilerFrame) => void): Partial<Hooks> => {
|
||||
console.warn('Could not find profile for', component);
|
||||
}
|
||||
},
|
||||
onDestroy(
|
||||
directive: any,
|
||||
node: Node,
|
||||
_: number,
|
||||
isComponent: boolean,
|
||||
__: ElementPosition,
|
||||
): void {
|
||||
// Make sure we reflect such directives in the report.
|
||||
if (!eventMap.has(directive)) {
|
||||
eventMap.set(directive, {
|
||||
isElement: isComponent && isCustomElement(node),
|
||||
name: getDirectiveName(directive),
|
||||
isComponent,
|
||||
lifecycle: {},
|
||||
outputs: {},
|
||||
});
|
||||
}
|
||||
},
|
||||
onDestroy(directive: any, node: Node, _: number, isComponent: boolean, __: ElementPosition):
|
||||
void {
|
||||
// Make sure we reflect such directives in the report.
|
||||
if (!eventMap.has(directive)) {
|
||||
eventMap.set(directive, {
|
||||
isElement: isComponent && isCustomElement(node),
|
||||
name: getDirectiveName(directive),
|
||||
isComponent,
|
||||
lifecycle: {},
|
||||
outputs: {},
|
||||
});
|
||||
}
|
||||
},
|
||||
onLifecycleHookStart(
|
||||
directive: any,
|
||||
hookName: keyof LifecycleProfile,
|
||||
node: Node,
|
||||
__: number,
|
||||
isComponent: boolean,
|
||||
): void {
|
||||
directive: any, hookName: keyof LifecycleProfile, node: Node, __: number,
|
||||
isComponent: boolean): void {
|
||||
startEvent(timeStartMap, directive, hookName);
|
||||
if (!eventMap.has(directive)) {
|
||||
eventMap.set(directive, {
|
||||
@@ -158,12 +139,7 @@ const getHooks = (onFrame: (frame: ProfilerFrame) => void): Partial<Hooks> => {
|
||||
}
|
||||
},
|
||||
onLifecycleHookEnd(
|
||||
directive: any,
|
||||
hookName: keyof LifecycleProfile,
|
||||
_: Node,
|
||||
__: number,
|
||||
___: boolean,
|
||||
): void {
|
||||
directive: any, hookName: keyof LifecycleProfile, _: Node, __: number, ___: boolean): void {
|
||||
const dir = eventMap.get(directive);
|
||||
const startTimestamp = getEventStart(timeStartMap, directive, hookName);
|
||||
if (startTimestamp === undefined) {
|
||||
@@ -177,23 +153,19 @@ const getHooks = (onFrame: (frame: ProfilerFrame) => void): Partial<Hooks> => {
|
||||
dir.lifecycle[hookName] = (dir.lifecycle[hookName] || 0) + duration;
|
||||
frameDuration += duration;
|
||||
},
|
||||
onOutputStart(
|
||||
componentOrDirective: any,
|
||||
outputName: string,
|
||||
node: Node,
|
||||
isComponent: boolean,
|
||||
): void {
|
||||
startEvent(timeStartMap, componentOrDirective, outputName);
|
||||
if (!eventMap.has(componentOrDirective)) {
|
||||
eventMap.set(componentOrDirective, {
|
||||
isElement: isCustomElement(node),
|
||||
name: getDirectiveName(componentOrDirective),
|
||||
isComponent,
|
||||
lifecycle: {},
|
||||
outputs: {},
|
||||
});
|
||||
}
|
||||
},
|
||||
onOutputStart(componentOrDirective: any, outputName: string, node: Node, isComponent: boolean):
|
||||
void {
|
||||
startEvent(timeStartMap, componentOrDirective, outputName);
|
||||
if (!eventMap.has(componentOrDirective)) {
|
||||
eventMap.set(componentOrDirective, {
|
||||
isElement: isCustomElement(node),
|
||||
name: getDirectiveName(componentOrDirective),
|
||||
isComponent,
|
||||
lifecycle: {},
|
||||
outputs: {},
|
||||
});
|
||||
}
|
||||
},
|
||||
onOutputEnd(componentOrDirective: any, outputName: string): void {
|
||||
const name = outputName;
|
||||
const entry = eventMap.get(componentOrDirective);
|
||||
@@ -203,10 +175,8 @@ const getHooks = (onFrame: (frame: ProfilerFrame) => void): Partial<Hooks> => {
|
||||
}
|
||||
if (!entry) {
|
||||
console.warn(
|
||||
'Could not find directive or component in onOutputEnd callback',
|
||||
componentOrDirective,
|
||||
outputName,
|
||||
);
|
||||
'Could not find directive or component in onOutputEnd callback', componentOrDirective,
|
||||
outputName);
|
||||
return;
|
||||
}
|
||||
const duration = performance.now() - startTimestamp;
|
||||
@@ -245,36 +215,33 @@ const insertOrMerge = (lastFrame: ElementProfile, profile: DirectiveProfile) =>
|
||||
}
|
||||
};
|
||||
|
||||
const insertElementProfile = (
|
||||
frames: ElementProfile[],
|
||||
position: ElementPosition,
|
||||
profile?: DirectiveProfile,
|
||||
) => {
|
||||
if (!profile) {
|
||||
return;
|
||||
}
|
||||
const original = frames;
|
||||
for (let i = 0; i < position.length - 1; i++) {
|
||||
const pos = position[i];
|
||||
if (!frames[pos]) {
|
||||
// TODO(mgechev): consider how to ensure we don't hit this case
|
||||
console.warn('Unable to find parent node for', profile, original);
|
||||
return;
|
||||
}
|
||||
frames = frames[pos].children;
|
||||
}
|
||||
const lastIdx = position[position.length - 1];
|
||||
let lastFrame: ElementProfile = {
|
||||
children: [],
|
||||
directives: [],
|
||||
};
|
||||
if (frames[lastIdx]) {
|
||||
lastFrame = frames[lastIdx];
|
||||
} else {
|
||||
frames[lastIdx] = lastFrame;
|
||||
}
|
||||
insertOrMerge(lastFrame, profile);
|
||||
};
|
||||
const insertElementProfile =
|
||||
(frames: ElementProfile[], position: ElementPosition, profile?: DirectiveProfile) => {
|
||||
if (!profile) {
|
||||
return;
|
||||
}
|
||||
const original = frames;
|
||||
for (let i = 0; i < position.length - 1; i++) {
|
||||
const pos = position[i];
|
||||
if (!frames[pos]) {
|
||||
// TODO(mgechev): consider how to ensure we don't hit this case
|
||||
console.warn('Unable to find parent node for', profile, original);
|
||||
return;
|
||||
}
|
||||
frames = frames[pos].children;
|
||||
}
|
||||
const lastIdx = position[position.length - 1];
|
||||
let lastFrame: ElementProfile = {
|
||||
children: [],
|
||||
directives: [],
|
||||
};
|
||||
if (frames[lastIdx]) {
|
||||
lastFrame = frames[lastIdx];
|
||||
} else {
|
||||
frames[lastIdx] = lastFrame;
|
||||
}
|
||||
insertOrMerge(lastFrame, profile);
|
||||
};
|
||||
|
||||
const prepareInitialFrame = (source: string, duration: number) => {
|
||||
const frame: ProfilerFrame = {
|
||||
@@ -285,7 +252,7 @@ const prepareInitialFrame = (source: string, duration: number) => {
|
||||
const directiveForestHooks = initializeOrGetDirectiveForestHooks();
|
||||
const directiveForest = directiveForestHooks.getIndexedDirectiveForest();
|
||||
const traverse = (node: ComponentTreeNode, children = frame.directives) => {
|
||||
let position: ElementPosition | undefined;
|
||||
let position: ElementPosition|undefined;
|
||||
if (node.component) {
|
||||
position = directiveForestHooks.getDirectivePosition(node.component.instance);
|
||||
} else {
|
||||
|
||||
@@ -28,7 +28,7 @@ export class DirectiveForestHooks {
|
||||
|
||||
profiler: Profiler = selectProfilerStrategy();
|
||||
|
||||
getDirectivePosition(dir: any): ElementPosition | undefined {
|
||||
getDirectivePosition(dir: any): ElementPosition|undefined {
|
||||
const result = this._tracker.getDirectivePosition(dir);
|
||||
if (result === undefined) {
|
||||
console.warn('Unable to find position of', dir);
|
||||
@@ -36,7 +36,7 @@ export class DirectiveForestHooks {
|
||||
return result;
|
||||
}
|
||||
|
||||
getDirectiveId(dir: any): number | undefined {
|
||||
getDirectiveId(dir: any): number|undefined {
|
||||
const result = this._tracker.getDirectiveId(dir);
|
||||
if (result === undefined) {
|
||||
console.warn('Unable to find ID of', result);
|
||||
|
||||
@@ -12,7 +12,7 @@ import {buildDirectiveForest} from '../component-tree';
|
||||
import {ComponentInstanceType, ComponentTreeNode, DirectiveInstanceType} from '../interfaces';
|
||||
|
||||
export declare interface Type<T> extends Function {
|
||||
new (...args: any[]): T;
|
||||
new(...args: any[]): T;
|
||||
}
|
||||
|
||||
interface TreeNode {
|
||||
@@ -22,8 +22,7 @@ interface TreeNode {
|
||||
}
|
||||
|
||||
export type NodeArray = {
|
||||
directive: any;
|
||||
isComponent: boolean;
|
||||
directive: any; isComponent: boolean;
|
||||
}[];
|
||||
|
||||
export class IdentityTracker {
|
||||
@@ -44,11 +43,11 @@ export class IdentityTracker {
|
||||
return IdentityTracker._instance;
|
||||
}
|
||||
|
||||
getDirectivePosition(dir: any): ElementPosition | undefined {
|
||||
getDirectivePosition(dir: any): ElementPosition|undefined {
|
||||
return this._currentDirectivePosition.get(dir);
|
||||
}
|
||||
|
||||
getDirectiveId(dir: any): number | undefined {
|
||||
getDirectiveId(dir: any): number|undefined {
|
||||
return this._currentDirectiveId.get(dir);
|
||||
}
|
||||
|
||||
@@ -57,9 +56,7 @@ export class IdentityTracker {
|
||||
}
|
||||
|
||||
index(): {
|
||||
newNodes: NodeArray;
|
||||
removedNodes: NodeArray;
|
||||
indexedForest: IndexedNode[];
|
||||
newNodes: NodeArray; removedNodes: NodeArray; indexedForest: IndexedNode[];
|
||||
directiveForest: ComponentTreeNode[];
|
||||
} {
|
||||
const directiveForest = buildDirectiveForest();
|
||||
@@ -81,11 +78,8 @@ export class IdentityTracker {
|
||||
}
|
||||
|
||||
private _index(
|
||||
node: IndexedNode,
|
||||
parent: TreeNode | null,
|
||||
newNodes: {directive: any; isComponent: boolean}[],
|
||||
allNodes: Set<any>,
|
||||
): void {
|
||||
node: IndexedNode, parent: TreeNode|null, newNodes: {directive: any; isComponent: boolean}[],
|
||||
allNodes: Set<any>): void {
|
||||
if (node.component) {
|
||||
allNodes.add(node.component.instance);
|
||||
this.isComponent.set(node.component.instance, true);
|
||||
@@ -119,10 +113,7 @@ export interface IndexedNode extends DevToolsNode<DirectiveInstanceType, Compone
|
||||
}
|
||||
|
||||
const indexTree = <T extends DevToolsNode<DirectiveInstanceType, ComponentInstanceType>>(
|
||||
node: T,
|
||||
idx: number,
|
||||
parentPosition: number[] = [],
|
||||
): IndexedNode => {
|
||||
node: T, idx: number, parentPosition: number[] = []): IndexedNode => {
|
||||
const position = parentPosition.concat([idx]);
|
||||
return {
|
||||
position,
|
||||
@@ -135,5 +126,4 @@ const indexTree = <T extends DevToolsNode<DirectiveInstanceType, ComponentInstan
|
||||
};
|
||||
|
||||
export const indexForest = <T extends DevToolsNode<DirectiveInstanceType, ComponentInstanceType>>(
|
||||
forest: T[],
|
||||
): IndexedNode[] => forest.map((n, i) => indexTree(n, i));
|
||||
forest: T[]): IndexedNode[] => forest.map((n, i) => indexTree(n, i));
|
||||
|
||||
@@ -15,9 +15,9 @@ import {DirectiveForestHooks} from './hooks';
|
||||
const markName = (s: string, method: Method) => `🅰️ ${s}#${method}`;
|
||||
|
||||
const supportsPerformance =
|
||||
globalThis.performance && typeof globalThis.performance.getEntriesByName === 'function';
|
||||
globalThis.performance && typeof globalThis.performance.getEntriesByName === 'function';
|
||||
|
||||
type Method = keyof LifecycleProfile | 'changeDetection' | string;
|
||||
type Method = keyof LifecycleProfile|'changeDetection'|string;
|
||||
|
||||
const recordMark = (s: string, method: Method) => {
|
||||
if (supportsPerformance) {
|
||||
|
||||
@@ -15,37 +15,32 @@ import {IdentityTracker, NodeArray} from '../identity-tracker';
|
||||
|
||||
import {getLifeCycleName, Hooks, Profiler} from './shared';
|
||||
|
||||
type ProfilerCallback = (
|
||||
event: ɵProfilerEvent,
|
||||
instanceOrLView: {} | null,
|
||||
hookOrListener: any,
|
||||
) => void;
|
||||
type ProfilerCallback = (event: ɵProfilerEvent, instanceOrLView: {}|null, hookOrListener: any) =>
|
||||
void;
|
||||
|
||||
/** Implementation of Profiler that utilizes framework APIs fire profiler hooks. */
|
||||
export class NgProfiler extends Profiler {
|
||||
private _tracker = IdentityTracker.getInstance();
|
||||
private _callbacks: ProfilerCallback[] = [];
|
||||
private _lastDirectiveInstance: {} | null = null;
|
||||
private _lastDirectiveInstance: {}|null = null;
|
||||
|
||||
constructor(config: Partial<Hooks> = {}) {
|
||||
super(config);
|
||||
this._setProfilerCallback(
|
||||
(event: ɵProfilerEvent, instanceOrLView: {} | null, hookOrListener: any) => {
|
||||
if (this[event] === undefined) {
|
||||
return;
|
||||
}
|
||||
(event: ɵProfilerEvent, instanceOrLView: {}|null, hookOrListener: any) => {
|
||||
if (this[event] === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
this[event](instanceOrLView, hookOrListener);
|
||||
},
|
||||
);
|
||||
this[event](instanceOrLView, hookOrListener);
|
||||
});
|
||||
this._initialize();
|
||||
}
|
||||
|
||||
private _initialize(): void {
|
||||
ngDebugClient().ɵsetProfiler(
|
||||
(event: ɵProfilerEvent, instanceOrLView: {} | null, hookOrListener: any) =>
|
||||
this._callbacks.forEach((cb) => cb(event, instanceOrLView, hookOrListener)),
|
||||
);
|
||||
(event: ɵProfilerEvent, instanceOrLView: {}|null, hookOrListener: any) =>
|
||||
this._callbacks.forEach((cb) => cb(event, instanceOrLView, hookOrListener)));
|
||||
}
|
||||
|
||||
private _setProfilerCallback(callback: ProfilerCallback): void {
|
||||
@@ -111,11 +106,9 @@ export class NgProfiler extends Profiler {
|
||||
}
|
||||
|
||||
this._onChangeDetectionStart(
|
||||
this._lastDirectiveInstance,
|
||||
getDirectiveHostElement(this._lastDirectiveInstance),
|
||||
this._tracker.getDirectiveId(this._lastDirectiveInstance),
|
||||
this._tracker.getDirectivePosition(this._lastDirectiveInstance),
|
||||
);
|
||||
this._lastDirectiveInstance, getDirectiveHostElement(this._lastDirectiveInstance),
|
||||
this._tracker.getDirectiveId(this._lastDirectiveInstance),
|
||||
this._tracker.getDirectivePosition(this._lastDirectiveInstance));
|
||||
}
|
||||
|
||||
[ɵProfilerEvent.TemplateUpdateEnd](context: any, _hookOrListener: any): void {
|
||||
@@ -128,11 +121,9 @@ export class NgProfiler extends Profiler {
|
||||
}
|
||||
|
||||
this._onChangeDetectionEnd(
|
||||
this._lastDirectiveInstance,
|
||||
getDirectiveHostElement(this._lastDirectiveInstance),
|
||||
this._tracker.getDirectiveId(this._lastDirectiveInstance),
|
||||
this._tracker.getDirectivePosition(this._lastDirectiveInstance),
|
||||
);
|
||||
this._lastDirectiveInstance, getDirectiveHostElement(this._lastDirectiveInstance),
|
||||
this._tracker.getDirectiveId(this._lastDirectiveInstance),
|
||||
this._tracker.getDirectivePosition(this._lastDirectiveInstance));
|
||||
}
|
||||
|
||||
[ɵProfilerEvent.LifecycleHookStart](directive: any, hook: any): void {
|
||||
|
||||
@@ -6,11 +6,7 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {
|
||||
getDirectiveHostElement,
|
||||
getLViewFromDirectiveOrElementInstance,
|
||||
METADATA_PROPERTY_NAME,
|
||||
} from '../../directive-forest';
|
||||
import {getDirectiveHostElement, getLViewFromDirectiveOrElementInstance, METADATA_PROPERTY_NAME,} from '../../directive-forest';
|
||||
import {runOutsideAngular} from '../../utils';
|
||||
import {IdentityTracker, NodeArray} from '../identity-tracker';
|
||||
|
||||
@@ -86,7 +82,7 @@ export class PatchingProfiler extends Profiler {
|
||||
if (original.patched) {
|
||||
return;
|
||||
}
|
||||
declarations.tView.template = function (_: any, component: any): void {
|
||||
declarations.tView.template = function(_: any, component: any): void {
|
||||
if (!self._inChangeDetection) {
|
||||
self._inChangeDetection = true;
|
||||
runOutsideAngular(() => {
|
||||
@@ -126,7 +122,7 @@ export class PatchingProfiler extends Profiler {
|
||||
}
|
||||
if (typeof el === 'function') {
|
||||
const self = this;
|
||||
current[idx] = function (): any {
|
||||
current[idx] = function(): any {
|
||||
// We currently don't want to notify the consumer
|
||||
// for execution of lifecycle hooks of services and pipes.
|
||||
// These two abstractions don't have `__ngContext__`, and
|
||||
|
||||
@@ -11,64 +11,32 @@ import {Subject} from 'rxjs';
|
||||
|
||||
import {NodeArray} from '../identity-tracker';
|
||||
|
||||
type CreationHook = (
|
||||
componentOrDirective: any,
|
||||
node: Node,
|
||||
id: number,
|
||||
isComponent: boolean,
|
||||
position: ElementPosition,
|
||||
) => void;
|
||||
type CreationHook =
|
||||
(componentOrDirective: any, node: Node, id: number, isComponent: boolean,
|
||||
position: ElementPosition) => void;
|
||||
|
||||
type LifecycleStartHook = (
|
||||
componentOrDirective: any,
|
||||
hook: keyof LifecycleProfile,
|
||||
node: Node,
|
||||
id: number,
|
||||
isComponent: boolean,
|
||||
) => void;
|
||||
type LifecycleStartHook =
|
||||
(componentOrDirective: any, hook: keyof LifecycleProfile, node: Node, id: number,
|
||||
isComponent: boolean) => void;
|
||||
|
||||
type LifecycleEndHook = (
|
||||
componentOrDirective: any,
|
||||
hook: keyof LifecycleProfile,
|
||||
node: Node,
|
||||
id: number,
|
||||
isComponent: boolean,
|
||||
) => void;
|
||||
type LifecycleEndHook =
|
||||
(componentOrDirective: any, hook: keyof LifecycleProfile, node: Node, id: number,
|
||||
isComponent: boolean) => void;
|
||||
|
||||
type ChangeDetectionStartHook = (
|
||||
component: any,
|
||||
node: Node,
|
||||
id: number,
|
||||
position: ElementPosition,
|
||||
) => void;
|
||||
type ChangeDetectionStartHook =
|
||||
(component: any, node: Node, id: number, position: ElementPosition) => void;
|
||||
|
||||
type ChangeDetectionEndHook = (
|
||||
component: any,
|
||||
node: Node,
|
||||
id: number,
|
||||
position: ElementPosition,
|
||||
) => void;
|
||||
type ChangeDetectionEndHook = (component: any, node: Node, id: number, position: ElementPosition) =>
|
||||
void;
|
||||
|
||||
type DestroyHook = (
|
||||
componentOrDirective: any,
|
||||
node: Node,
|
||||
id: number,
|
||||
isComponent: boolean,
|
||||
position: ElementPosition,
|
||||
) => void;
|
||||
type DestroyHook =
|
||||
(componentOrDirective: any, node: Node, id: number, isComponent: boolean,
|
||||
position: ElementPosition) => void;
|
||||
|
||||
type OutputStartHook = (
|
||||
componentOrDirective: any,
|
||||
outputName: string,
|
||||
node: Node,
|
||||
isComponent: boolean,
|
||||
) => void;
|
||||
type OutputEndHook = (
|
||||
componentOrDirective: any,
|
||||
outputName: string,
|
||||
node: Node,
|
||||
isComponent: boolean,
|
||||
) => void;
|
||||
type OutputStartHook =
|
||||
(componentOrDirective: any, outputName: string, node: Node, isComponent: boolean) => void;
|
||||
type OutputEndHook =
|
||||
(componentOrDirective: any, outputName: string, node: Node, isComponent: boolean) => void;
|
||||
|
||||
export interface Hooks {
|
||||
onCreate: CreationHook;
|
||||
@@ -111,12 +79,8 @@ export abstract class Profiler {
|
||||
|
||||
/** @internal */
|
||||
protected _onCreate(
|
||||
_: any,
|
||||
__: Node,
|
||||
id: number | undefined,
|
||||
___: boolean,
|
||||
position: ElementPosition | undefined,
|
||||
): void {
|
||||
_: any, __: Node, id: number|undefined, ___: boolean,
|
||||
position: ElementPosition|undefined): void {
|
||||
if (id === undefined || position === undefined) {
|
||||
return;
|
||||
}
|
||||
@@ -125,12 +89,8 @@ export abstract class Profiler {
|
||||
|
||||
/** @internal */
|
||||
protected _onDestroy(
|
||||
_: any,
|
||||
__: Node,
|
||||
id: number | undefined,
|
||||
___: boolean,
|
||||
position: ElementPosition | undefined,
|
||||
): void {
|
||||
_: any, __: Node, id: number|undefined, ___: boolean,
|
||||
position: ElementPosition|undefined): void {
|
||||
if (id === undefined || position === undefined) {
|
||||
return;
|
||||
}
|
||||
@@ -139,11 +99,7 @@ export abstract class Profiler {
|
||||
|
||||
/** @internal */
|
||||
protected _onChangeDetectionStart(
|
||||
_: any,
|
||||
__: Node,
|
||||
id: number | undefined,
|
||||
position: ElementPosition | undefined,
|
||||
): void {
|
||||
_: any, __: Node, id: number|undefined, position: ElementPosition|undefined): void {
|
||||
if (id === undefined || position === undefined) {
|
||||
return;
|
||||
}
|
||||
@@ -152,11 +108,7 @@ export abstract class Profiler {
|
||||
|
||||
/** @internal */
|
||||
protected _onChangeDetectionEnd(
|
||||
_: any,
|
||||
__: Node,
|
||||
id: number | undefined,
|
||||
position: ElementPosition | undefined,
|
||||
): void {
|
||||
_: any, __: Node, id: number|undefined, position: ElementPosition|undefined): void {
|
||||
if (id === undefined || position === undefined) {
|
||||
return;
|
||||
}
|
||||
@@ -165,12 +117,8 @@ export abstract class Profiler {
|
||||
|
||||
/** @internal */
|
||||
protected _onLifecycleHookStart(
|
||||
_: any,
|
||||
__: keyof LifecycleProfile | 'unknown',
|
||||
___: Node,
|
||||
id: number | undefined,
|
||||
____: boolean,
|
||||
): void {
|
||||
_: any, __: keyof LifecycleProfile|'unknown', ___: Node, id: number|undefined,
|
||||
____: boolean): void {
|
||||
if (id === undefined) {
|
||||
return;
|
||||
}
|
||||
@@ -179,12 +127,8 @@ export abstract class Profiler {
|
||||
|
||||
/** @internal */
|
||||
protected _onLifecycleHookEnd(
|
||||
_: any,
|
||||
__: keyof LifecycleProfile | 'unknown',
|
||||
___: Node,
|
||||
id: number | undefined,
|
||||
____: boolean,
|
||||
): void {
|
||||
_: any, __: keyof LifecycleProfile|'unknown', ___: Node, id: number|undefined,
|
||||
____: boolean): void {
|
||||
if (id === undefined) {
|
||||
return;
|
||||
}
|
||||
@@ -192,13 +136,8 @@ export abstract class Profiler {
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
protected _onOutputStart(
|
||||
_: any,
|
||||
__: string,
|
||||
___: Node,
|
||||
id: number | undefined,
|
||||
____: boolean,
|
||||
): void {
|
||||
protected _onOutputStart(_: any, __: string, ___: Node, id: number|undefined, ____: boolean):
|
||||
void {
|
||||
if (id === undefined) {
|
||||
return;
|
||||
}
|
||||
@@ -206,13 +145,7 @@ export abstract class Profiler {
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
protected _onOutputEnd(
|
||||
_: any,
|
||||
__: string,
|
||||
___: Node,
|
||||
id: number | undefined,
|
||||
____: boolean,
|
||||
): void {
|
||||
protected _onOutputEnd(_: any, __: string, ___: Node, id: number|undefined, ____: boolean): void {
|
||||
if (id === undefined) {
|
||||
return;
|
||||
}
|
||||
@@ -243,7 +176,7 @@ const hookNames = [
|
||||
|
||||
const hookMethodNames = new Set(hookNames.map((hook) => `ng${hook}`));
|
||||
|
||||
export const getLifeCycleName = (obj: {}, fn: any): keyof LifecycleProfile | 'unknown' => {
|
||||
export const getLifeCycleName = (obj: {}, fn: any): keyof LifecycleProfile|'unknown' => {
|
||||
const proto = Object.getPrototypeOf(obj);
|
||||
const keys = Object.getOwnPropertyNames(proto);
|
||||
for (const propName of keys) {
|
||||
|
||||
@@ -24,7 +24,7 @@ export interface ComponentInstanceType {
|
||||
isElement: boolean;
|
||||
}
|
||||
|
||||
export interface ComponentTreeNode
|
||||
extends DevToolsNode<DirectiveInstanceType, ComponentInstanceType> {
|
||||
export interface ComponentTreeNode extends
|
||||
DevToolsNode<DirectiveInstanceType, ComponentInstanceType> {
|
||||
children: ComponentTreeNode[];
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@ export function parseRoutes(router: Router): Route {
|
||||
return root;
|
||||
}
|
||||
|
||||
function assignChildrenToParent(parentPath: string | null, children: Routes): Route[] {
|
||||
function assignChildrenToParent(parentPath: string|null, children: Routes): Route[] {
|
||||
return children.map((child: AngularRoute) => {
|
||||
const childName = childRouteName(child);
|
||||
const childDescendents: [any] = (child as any)._loadedConfig?.routes || child.children;
|
||||
|
||||
@@ -12,7 +12,7 @@ import {arrayEquals} from 'shared-utils';
|
||||
import {ComponentTreeNode} from './interfaces';
|
||||
|
||||
interface ConsoleReferenceNode {
|
||||
node: ComponentTreeNode | null;
|
||||
node: ComponentTreeNode|null;
|
||||
position: ElementPosition;
|
||||
}
|
||||
|
||||
@@ -35,9 +35,8 @@ const _setConsoleReference = (referenceNode: ConsoleReferenceNode) => {
|
||||
};
|
||||
|
||||
const prepareCurrentReferencesForInsertion = (referenceNode: ConsoleReferenceNode) => {
|
||||
const foundIndex = nodesForConsoleReference.findIndex((nodeToLookFor) =>
|
||||
arrayEquals(nodeToLookFor.position, referenceNode.position),
|
||||
);
|
||||
const foundIndex = nodesForConsoleReference.findIndex(
|
||||
(nodeToLookFor) => arrayEquals(nodeToLookFor.position, referenceNode.position));
|
||||
if (foundIndex !== -1) {
|
||||
nodesForConsoleReference.splice(foundIndex, 1);
|
||||
} else if (nodesForConsoleReference.length === CAPACITY) {
|
||||
@@ -46,12 +45,12 @@ const prepareCurrentReferencesForInsertion = (referenceNode: ConsoleReferenceNod
|
||||
};
|
||||
|
||||
const assignConsoleReferencesFrom = (referenceNodes: ConsoleReferenceNode[]) => {
|
||||
referenceNodes.forEach((referenceNode, index) =>
|
||||
setDirectiveKey(referenceNode.node, getConsoleReferenceWithIndexOf(index)),
|
||||
);
|
||||
referenceNodes.forEach(
|
||||
(referenceNode, index) =>
|
||||
setDirectiveKey(referenceNode.node, getConsoleReferenceWithIndexOf(index)));
|
||||
};
|
||||
|
||||
const setDirectiveKey = (node: ComponentTreeNode | null, key: string) => {
|
||||
const setDirectiveKey = (node: ComponentTreeNode|null, key: string) => {
|
||||
Object.defineProperty(window, key, {
|
||||
get: () => {
|
||||
if (node?.component) {
|
||||
@@ -67,4 +66,4 @@ const setDirectiveKey = (node: ComponentTreeNode | null, key: string) => {
|
||||
};
|
||||
|
||||
const getConsoleReferenceWithIndexOf = (consoleReferenceIndex: number) =>
|
||||
`${CONSOLE_REFERENCE_PREFIX}${consoleReferenceIndex}`;
|
||||
`${CONSOLE_REFERENCE_PREFIX}${consoleReferenceIndex}`;
|
||||
|
||||
@@ -6,8 +6,6 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {unwrapSignal} from '../utils';
|
||||
|
||||
// Intentionally do not include all the prototype (Except for getters and setters)
|
||||
// because it contains inherited methods (hasOwnProperty, etc.). Also ignore symbols
|
||||
// because it is tricky to pass a path to a symbol.
|
||||
@@ -18,13 +16,12 @@ export function getKeys(obj: {}): string[] {
|
||||
if (!obj) {
|
||||
return [];
|
||||
}
|
||||
obj = unwrapSignal(obj);
|
||||
const properties = Object.getOwnPropertyNames(obj);
|
||||
|
||||
const prototypeMembers = Object.getOwnPropertyDescriptors(Object.getPrototypeOf(obj));
|
||||
|
||||
const ignoreList = ['__proto__'];
|
||||
const gettersAndSetters = Object.keys(prototypeMembers).filter((methodName) => {
|
||||
const gettersAndSetters = Object.keys(prototypeMembers).filter(methodName => {
|
||||
if (ignoreList.includes(methodName)) {
|
||||
return false;
|
||||
}
|
||||
@@ -42,6 +39,6 @@ export function getKeys(obj: {}): string[] {
|
||||
* @param propName The string representation of the target property name
|
||||
* @returns The Descriptor object of the property
|
||||
*/
|
||||
export const getDescriptor = (instance: any, propName: string): PropertyDescriptor | undefined =>
|
||||
Object.getOwnPropertyDescriptor(instance, propName) ||
|
||||
Object.getOwnPropertyDescriptor(Object.getPrototypeOf(instance), propName);
|
||||
export const getDescriptor = (instance: any, propName: string) =>
|
||||
Object.getOwnPropertyDescriptor(instance, propName) ||
|
||||
Object.getOwnPropertyDescriptor(Object.getPrototypeOf(instance), propName);
|
||||
|
||||
@@ -73,14 +73,11 @@ describe('getPropType', () => {
|
||||
propTypeName: 'Set',
|
||||
},
|
||||
{
|
||||
expression: new Map<unknown, unknown>([
|
||||
['name', 'John'],
|
||||
['age', 40],
|
||||
[{id: 123}, undefined],
|
||||
]),
|
||||
expression:
|
||||
new Map<unknown, unknown>([['name', 'John'], ['age', 40], [{id: 123}, undefined]]),
|
||||
propType: PropType.Map,
|
||||
propTypeName: 'Map',
|
||||
},
|
||||
}
|
||||
];
|
||||
for (const {expression, propType, propTypeName} of testCases) {
|
||||
it(`should determine ${String(expression)} as PropType:${propTypeName}(${propType})`, () => {
|
||||
|
||||
@@ -8,8 +8,6 @@
|
||||
|
||||
import {PropType} from 'protocol';
|
||||
|
||||
import {isSignal} from '../utils';
|
||||
|
||||
const commonTypes = {
|
||||
boolean: PropType.Boolean,
|
||||
bigint: PropType.BigInt,
|
||||
@@ -26,10 +24,6 @@ const commonTypes = {
|
||||
* @see `devtools/projects/protocol`
|
||||
*/
|
||||
export const getPropType = (prop: unknown): PropType => {
|
||||
if (isSignal(prop)) {
|
||||
prop = prop();
|
||||
}
|
||||
|
||||
if (prop === undefined) {
|
||||
return PropType.Undefined;
|
||||
}
|
||||
|
||||
+184
-249
@@ -6,30 +6,26 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {ContainerType, Descriptor, NestedProp, PropType} from 'protocol';
|
||||
|
||||
import {isSignal, unwrapSignal} from '../utils';
|
||||
import {Descriptor, NestedProp, PropType} from 'protocol';
|
||||
|
||||
import {getDescriptor, getKeys} from './object-utils';
|
||||
|
||||
// todo(aleksanderbodurri) pull this out of this file
|
||||
const METADATA_PROPERTY_NAME = '__ngContext__';
|
||||
|
||||
type NestedType = PropType.Array | PropType.Object;
|
||||
type NestedType = PropType.Array|PropType.Object;
|
||||
|
||||
export interface CompositeType {
|
||||
type: Extract<PropType, NestedType>;
|
||||
prop: any;
|
||||
containerType: ContainerType;
|
||||
}
|
||||
|
||||
export interface TerminalType {
|
||||
type: Exclude<PropType, NestedType>;
|
||||
prop: any;
|
||||
containerType: ContainerType;
|
||||
}
|
||||
|
||||
export type PropertyData = TerminalType | CompositeType;
|
||||
export type PropertyData = TerminalType|CompositeType;
|
||||
|
||||
export type Formatter<Result> = {
|
||||
[key in PropType]: (data: any) => Result;
|
||||
@@ -40,15 +36,23 @@ interface LevelOptions {
|
||||
level?: number;
|
||||
}
|
||||
|
||||
const serializable: Set<PropType> = new Set([
|
||||
PropType.Boolean,
|
||||
PropType.String,
|
||||
PropType.Null,
|
||||
PropType.Number,
|
||||
PropType.Object,
|
||||
PropType.Undefined,
|
||||
PropType.Unknown,
|
||||
]);
|
||||
const serializable: {[key in PropType]: boolean} = {
|
||||
[PropType.Boolean]: true,
|
||||
[PropType.String]: true,
|
||||
[PropType.Null]: true,
|
||||
[PropType.Number]: true,
|
||||
[PropType.Object]: true,
|
||||
[PropType.Undefined]: true,
|
||||
[PropType.Unknown]: true,
|
||||
[PropType.Array]: false,
|
||||
[PropType.Set]: false,
|
||||
[PropType.Map]: false,
|
||||
[PropType.BigInt]: false,
|
||||
[PropType.Function]: false,
|
||||
[PropType.HTMLNode]: false,
|
||||
[PropType.Symbol]: false,
|
||||
[PropType.Date]: false,
|
||||
};
|
||||
|
||||
const typeToDescriptorPreview: Formatter<string> = {
|
||||
[PropType.Array]: (prop: Array<unknown>) => `Array(${prop.length})`,
|
||||
@@ -73,280 +77,211 @@ const typeToDescriptorPreview: Formatter<string> = {
|
||||
[PropType.Unknown]: (_: any) => 'unknown',
|
||||
};
|
||||
|
||||
type Key = string | number;
|
||||
type NestedSerializerFn = (
|
||||
instance: any,
|
||||
propName: string | number,
|
||||
nodes: NestedProp[],
|
||||
isReadonly: boolean,
|
||||
currentLevel: number,
|
||||
level?: number,
|
||||
) => Descriptor;
|
||||
|
||||
type Key = string|number;
|
||||
const ignoreList: Set<Key> = new Set([METADATA_PROPERTY_NAME, '__ngSimpleChanges__']);
|
||||
|
||||
const shallowPropTypeToTreeMetaData: Record<
|
||||
Exclude<PropType, NestedType>,
|
||||
{editable: boolean; expandable: boolean}
|
||||
> = {
|
||||
[PropType.String]: {
|
||||
editable: true,
|
||||
expandable: false,
|
||||
},
|
||||
[PropType.BigInt]: {
|
||||
editable: false,
|
||||
expandable: false,
|
||||
},
|
||||
[PropType.Boolean]: {
|
||||
editable: true,
|
||||
expandable: false,
|
||||
},
|
||||
[PropType.Number]: {
|
||||
editable: true,
|
||||
expandable: false,
|
||||
},
|
||||
[PropType.Date]: {
|
||||
editable: false,
|
||||
expandable: false,
|
||||
},
|
||||
[PropType.Null]: {
|
||||
editable: true,
|
||||
expandable: false,
|
||||
},
|
||||
[PropType.Undefined]: {
|
||||
editable: true,
|
||||
expandable: false,
|
||||
},
|
||||
[PropType.Symbol]: {
|
||||
editable: false,
|
||||
expandable: false,
|
||||
},
|
||||
[PropType.Function]: {
|
||||
editable: false,
|
||||
expandable: false,
|
||||
},
|
||||
[PropType.HTMLNode]: {
|
||||
editable: false,
|
||||
expandable: false,
|
||||
},
|
||||
[PropType.Unknown]: {
|
||||
editable: false,
|
||||
expandable: false,
|
||||
},
|
||||
[PropType.Set]: {
|
||||
editable: false,
|
||||
expandable: false,
|
||||
},
|
||||
[PropType.Map]: {
|
||||
editable: false,
|
||||
expandable: false,
|
||||
},
|
||||
};
|
||||
const shallowPropTypeToTreeMetaData:
|
||||
Record<Exclude<PropType, NestedType>, {editable: boolean, expandable: boolean}> = {
|
||||
[PropType.String]: {
|
||||
editable: true,
|
||||
expandable: false,
|
||||
},
|
||||
[PropType.BigInt]: {
|
||||
editable: false,
|
||||
expandable: false,
|
||||
},
|
||||
[PropType.Boolean]: {
|
||||
editable: true,
|
||||
expandable: false,
|
||||
},
|
||||
[PropType.Number]: {
|
||||
editable: true,
|
||||
expandable: false,
|
||||
},
|
||||
[PropType.Date]: {
|
||||
editable: false,
|
||||
expandable: false,
|
||||
},
|
||||
[PropType.Null]: {
|
||||
editable: true,
|
||||
expandable: false,
|
||||
},
|
||||
[PropType.Undefined]: {
|
||||
editable: true,
|
||||
expandable: false,
|
||||
},
|
||||
[PropType.Symbol]: {
|
||||
editable: false,
|
||||
expandable: false,
|
||||
},
|
||||
[PropType.Function]: {
|
||||
editable: false,
|
||||
expandable: false,
|
||||
},
|
||||
[PropType.HTMLNode]: {
|
||||
editable: false,
|
||||
expandable: false,
|
||||
},
|
||||
[PropType.Unknown]: {
|
||||
editable: false,
|
||||
expandable: false,
|
||||
},
|
||||
[PropType.Set]: {
|
||||
editable: false,
|
||||
expandable: false,
|
||||
},
|
||||
[PropType.Map]: {
|
||||
editable: false,
|
||||
expandable: false,
|
||||
},
|
||||
};
|
||||
|
||||
const isEditable = (
|
||||
descriptor: PropertyDescriptor | undefined,
|
||||
propName: string | number,
|
||||
propData: TerminalType,
|
||||
isGetterOrSetter: boolean,
|
||||
) => {
|
||||
if (propData.containerType === 'ReadonlySignal') {
|
||||
return false;
|
||||
}
|
||||
const isEditable =
|
||||
(descriptor: any, propName: string|number, propData: TerminalType,
|
||||
isGetterOrSetter: boolean) => {
|
||||
if (typeof propName === 'symbol') {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (typeof propName === 'symbol') {
|
||||
return false;
|
||||
}
|
||||
if (isGetterOrSetter) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (isGetterOrSetter) {
|
||||
return false;
|
||||
}
|
||||
if (descriptor?.writable === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (descriptor?.writable === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return shallowPropTypeToTreeMetaData[propData.type].editable;
|
||||
};
|
||||
return shallowPropTypeToTreeMetaData[propData.type].editable;
|
||||
};
|
||||
|
||||
const isGetterOrSetter = (descriptor: any): boolean =>
|
||||
(descriptor?.set || descriptor?.get) && !('value' in descriptor);
|
||||
(descriptor?.set || descriptor?.get) && !('value' in descriptor);
|
||||
|
||||
const getPreview = (propData: TerminalType | CompositeType, isGetterOrSetter: boolean) => {
|
||||
if (propData.containerType === 'ReadonlySignal') {
|
||||
return `Readonly Signal(${typeToDescriptorPreview[propData.type](propData.prop())})`;
|
||||
} else if (propData.containerType === 'WritableSignal') {
|
||||
return `Signal(${typeToDescriptorPreview[propData.type](propData.prop())})`;
|
||||
}
|
||||
return !isGetterOrSetter
|
||||
? typeToDescriptorPreview[propData.type](propData.prop)
|
||||
: typeToDescriptorPreview[PropType.Function]({name: ''});
|
||||
const getPreview = (propData: TerminalType|CompositeType, isGetterOrSetter: boolean) => {
|
||||
return !isGetterOrSetter ? typeToDescriptorPreview[propData.type](propData.prop) :
|
||||
typeToDescriptorPreview[PropType.Function]({name: ''});
|
||||
};
|
||||
|
||||
export function createShallowSerializedDescriptor(
|
||||
instance: any,
|
||||
propName: string | number,
|
||||
propData: TerminalType,
|
||||
isReadonly: boolean,
|
||||
): Descriptor {
|
||||
const {type, containerType} = propData;
|
||||
export const createShallowSerializedDescriptor =
|
||||
(instance: any, propName: string|number, propData: TerminalType): Descriptor => {
|
||||
const {type} = propData;
|
||||
|
||||
const descriptor = getDescriptor(instance, propName as string);
|
||||
const getterOrSetter: boolean = isGetterOrSetter(descriptor);
|
||||
const descriptor = getDescriptor(instance, propName as string);
|
||||
const getterOrSetter: boolean = isGetterOrSetter(descriptor);
|
||||
|
||||
const shallowSerializedDescriptor: Descriptor = {
|
||||
type,
|
||||
expandable: shallowPropTypeToTreeMetaData[type].expandable,
|
||||
editable: isEditable(descriptor, propName, propData, getterOrSetter) && !isReadonly,
|
||||
preview: getPreview(propData, getterOrSetter),
|
||||
containerType,
|
||||
};
|
||||
const shallowSerializedDescriptor: Descriptor = {
|
||||
type,
|
||||
expandable: shallowPropTypeToTreeMetaData[type].expandable,
|
||||
editable: isEditable(descriptor, propName, propData, getterOrSetter),
|
||||
preview: getPreview(propData, getterOrSetter),
|
||||
};
|
||||
|
||||
if (propData.prop !== undefined && serializable.has(type)) {
|
||||
shallowSerializedDescriptor.value = unwrapSignal(propData.prop);
|
||||
}
|
||||
if (propData.prop !== undefined && serializable[type]) {
|
||||
shallowSerializedDescriptor.value = propData.prop;
|
||||
}
|
||||
|
||||
return shallowSerializedDescriptor;
|
||||
}
|
||||
return shallowSerializedDescriptor;
|
||||
};
|
||||
|
||||
export function createLevelSerializedDescriptor(
|
||||
instance: {},
|
||||
propName: string | number,
|
||||
propData: CompositeType,
|
||||
levelOptions: LevelOptions,
|
||||
continuation: (
|
||||
instance: any,
|
||||
propName: string | number,
|
||||
isReadonly: boolean,
|
||||
level?: number,
|
||||
max?: number,
|
||||
) => Descriptor,
|
||||
): Descriptor {
|
||||
const {type, prop, containerType} = propData;
|
||||
export const createLevelSerializedDescriptor =
|
||||
(instance: {}, propName: string|number, propData: CompositeType, levelOptions: LevelOptions,
|
||||
continuation: (instance: any, propName: string|number, level?: number, max?: number) => void):
|
||||
Descriptor => {
|
||||
const {type, prop} = propData;
|
||||
|
||||
const descriptor = getDescriptor(instance, propName as string);
|
||||
const getterOrSetter: boolean = isGetterOrSetter(descriptor);
|
||||
const descriptor = getDescriptor(instance, propName as string);
|
||||
const getterOrSetter: boolean = isGetterOrSetter(descriptor);
|
||||
|
||||
const levelSerializedDescriptor: Descriptor = {
|
||||
type,
|
||||
editable: false,
|
||||
expandable: !getterOrSetter && getKeys(prop).length > 0,
|
||||
preview: getPreview(propData, getterOrSetter),
|
||||
containerType,
|
||||
};
|
||||
const levelSerializedDescriptor: Descriptor = {
|
||||
type,
|
||||
editable: false,
|
||||
expandable: !getterOrSetter && getKeys(prop).length > 0,
|
||||
preview: getPreview(propData, getterOrSetter),
|
||||
};
|
||||
|
||||
if (levelOptions.level !== undefined && levelOptions.currentLevel < levelOptions.level) {
|
||||
const value = getLevelDescriptorValue(propData, levelOptions, continuation);
|
||||
if (value !== undefined) {
|
||||
levelSerializedDescriptor.value = value;
|
||||
}
|
||||
}
|
||||
if (levelOptions.level !== undefined && levelOptions.currentLevel < levelOptions.level) {
|
||||
const value = getLevelDescriptorValue(propData, levelOptions, continuation);
|
||||
if (value !== undefined) {
|
||||
levelSerializedDescriptor.value = value;
|
||||
}
|
||||
}
|
||||
|
||||
return levelSerializedDescriptor;
|
||||
}
|
||||
return levelSerializedDescriptor;
|
||||
};
|
||||
|
||||
export function createNestedSerializedDescriptor(
|
||||
instance: {},
|
||||
propName: string | number,
|
||||
propData: CompositeType,
|
||||
levelOptions: LevelOptions,
|
||||
nodes: NestedProp[],
|
||||
nestedSerializer: NestedSerializerFn,
|
||||
): Descriptor {
|
||||
const {type, prop, containerType} = propData;
|
||||
export const createNestedSerializedDescriptor =
|
||||
(instance: {}, propName: string|number, propData: CompositeType, levelOptions: LevelOptions,
|
||||
nodes: NestedProp[],
|
||||
nestedSerializer: (
|
||||
instance: any, propName: string|number, nodes: NestedProp[], currentLevel: number,
|
||||
level?: number) => void): Descriptor => {
|
||||
const {type, prop} = propData;
|
||||
|
||||
const descriptor = getDescriptor(instance, propName as string);
|
||||
const getterOrSetter: boolean = isGetterOrSetter(descriptor);
|
||||
const descriptor = getDescriptor(instance, propName as string);
|
||||
const getterOrSetter: boolean = isGetterOrSetter(descriptor);
|
||||
|
||||
const nestedSerializedDescriptor: Descriptor = {
|
||||
type,
|
||||
editable: false,
|
||||
expandable: !getterOrSetter && getKeys(prop).length > 0,
|
||||
preview: getPreview(propData, getterOrSetter),
|
||||
containerType,
|
||||
};
|
||||
const nestedSerializedDescriptor: Descriptor = {
|
||||
type,
|
||||
editable: false,
|
||||
expandable: !getterOrSetter && getKeys(prop).length > 0,
|
||||
preview: getPreview(propData, getterOrSetter),
|
||||
};
|
||||
|
||||
if (nodes?.length) {
|
||||
const value = getNestedDescriptorValue(propData, levelOptions, nodes, nestedSerializer);
|
||||
if (value !== undefined) {
|
||||
nestedSerializedDescriptor.value = value;
|
||||
}
|
||||
}
|
||||
return nestedSerializedDescriptor;
|
||||
}
|
||||
if (nodes?.length) {
|
||||
const value = getNestedDescriptorValue(propData, levelOptions, nodes, nestedSerializer);
|
||||
if (value !== undefined) {
|
||||
nestedSerializedDescriptor.value = value;
|
||||
}
|
||||
}
|
||||
return nestedSerializedDescriptor;
|
||||
};
|
||||
|
||||
function getNestedDescriptorValue(
|
||||
propData: CompositeType,
|
||||
levelOptions: LevelOptions,
|
||||
nodes: NestedProp[],
|
||||
nestedSerializer: NestedSerializerFn,
|
||||
) {
|
||||
propData: CompositeType, levelOptions: LevelOptions, nodes: NestedProp[],
|
||||
nestedSerializer: (
|
||||
instance: any, propName: string|number, nodes: NestedProp[], currentLevel: number,
|
||||
level?: number) => void) {
|
||||
const {type, prop} = propData;
|
||||
const {currentLevel} = levelOptions;
|
||||
const value = unwrapSignal(prop);
|
||||
|
||||
switch (type) {
|
||||
case PropType.Array:
|
||||
return nodes.map((nestedProp) =>
|
||||
nestedSerializer(value, nestedProp.name, nestedProp.children, false, currentLevel + 1),
|
||||
);
|
||||
return nodes.map(
|
||||
(nestedProp) =>
|
||||
nestedSerializer(prop, nestedProp.name, nestedProp.children, currentLevel + 1));
|
||||
case PropType.Object:
|
||||
return nodes.reduce(
|
||||
(accumulator, nestedProp) => {
|
||||
if (prop.hasOwnProperty(nestedProp.name) && !ignoreList.has(nestedProp.name)) {
|
||||
accumulator[nestedProp.name] = nestedSerializer(
|
||||
value,
|
||||
nestedProp.name,
|
||||
nestedProp.children,
|
||||
false,
|
||||
currentLevel + 1,
|
||||
);
|
||||
}
|
||||
return accumulator;
|
||||
},
|
||||
{} as Record<string, Descriptor>,
|
||||
);
|
||||
return nodes.reduce((accumulator, nestedProp) => {
|
||||
if (prop.hasOwnProperty(nestedProp.name) && !ignoreList.has(nestedProp.name)) {
|
||||
accumulator[nestedProp.name] =
|
||||
nestedSerializer(prop, nestedProp.name, nestedProp.children, currentLevel + 1);
|
||||
}
|
||||
return accumulator;
|
||||
}, {} as Record<string, void>);
|
||||
}
|
||||
}
|
||||
|
||||
function getLevelDescriptorValue(
|
||||
propData: CompositeType,
|
||||
levelOptions: LevelOptions,
|
||||
continuation: (
|
||||
instance: any,
|
||||
propName: string | number,
|
||||
isReadonly: boolean,
|
||||
level?: number,
|
||||
max?: number,
|
||||
) => Descriptor,
|
||||
) {
|
||||
propData: CompositeType, levelOptions: LevelOptions,
|
||||
continuation: (instance: any, propName: string|number, level?: number, max?: number) => void) {
|
||||
const {type, prop} = propData;
|
||||
const {currentLevel, level} = levelOptions;
|
||||
const value = unwrapSignal(prop);
|
||||
const isReadonly = isSignal(prop);
|
||||
|
||||
switch (type) {
|
||||
case PropType.Array:
|
||||
return prop.map((_: any, idx: number) =>
|
||||
continuation(value, idx, isReadonly, currentLevel + 1, level),
|
||||
);
|
||||
return prop.map((_: any, idx: number) => continuation(prop, idx, currentLevel + 1, level));
|
||||
case PropType.Object:
|
||||
return getKeys(prop).reduce(
|
||||
(accumulator, propName) => {
|
||||
if (!ignoreList.has(propName)) {
|
||||
accumulator[propName] = continuation(
|
||||
value,
|
||||
propName,
|
||||
isReadonly,
|
||||
currentLevel + 1,
|
||||
level,
|
||||
);
|
||||
}
|
||||
return accumulator;
|
||||
},
|
||||
{} as Record<string, Descriptor>,
|
||||
);
|
||||
return getKeys(prop).reduce((accumulator, propName) => {
|
||||
if (!ignoreList.has(propName)) {
|
||||
accumulator[propName] = continuation(prop, propName, currentLevel + 1, level);
|
||||
}
|
||||
return accumulator;
|
||||
}, {} as Record<string, void>);
|
||||
}
|
||||
}
|
||||
|
||||
function truncate(str: string, max = 20): string {
|
||||
return str.length > max ? str.substring(0, max) + '...' : str;
|
||||
}
|
||||
const truncate = (str: string, max = 20): string => {
|
||||
if (str.length > max) {
|
||||
return str.substring(0, max) + '...';
|
||||
}
|
||||
return str;
|
||||
};
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user