mirror of
https://github.com/angular/angular.git
synced 2026-09-14 13:54:52 +08:00
Compare commits
163 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 7529512e7b | |||
| 05729f4116 | |||
| 41fd77664e | |||
| 044dac98c9 | |||
| 56b551d273 | |||
| 705fb0d236 | |||
| b6f22a7526 | |||
| 8ed63bc1bb | |||
| e98facebac | |||
| f9b6bf91be | |||
| c41b3c83ca | |||
| e59432cf07 | |||
| b144dd946e | |||
| 64bdffb97a | |||
| 7832be5905 | |||
| 4e1f28eddf | |||
| 36d740d6ef | |||
| 30f6db37a0 | |||
| 442ef134b8 | |||
| b1d0d20240 | |||
| 4e87b58fd1 | |||
| ec65169dfb | |||
| 15c42969fc | |||
| cfdadb562f | |||
| 23ca88522b | |||
| ea8008d95e | |||
| cef606aa8e | |||
| 44155c9209 | |||
| 43b89e7bb1 | |||
| 5e2f312321 | |||
| bd15db3960 | |||
| bde1b37e0b | |||
| 5131794cde | |||
| 70fb3bba4b | |||
| 5db2df8aad | |||
| bb12b30d52 | |||
| 0c105476a1 | |||
| 48dc0d6dc7 | |||
| 9d8e943e62 | |||
| 1a71167b5c | |||
| 83218bd0d1 | |||
| 5cc80dc72f | |||
| 9cdf950108 | |||
| 279e6b846f | |||
| 07cc97ff9b | |||
| 684e837758 | |||
| dad02c6535 | |||
| dfc8c432db | |||
| b967ca6b3b | |||
| 7bcdf7c143 | |||
| 484e0cf43e | |||
| 6b019ae958 | |||
| 6dc41265fd | |||
| 3076254567 | |||
| 90a16a1088 | |||
| 8d770eccee | |||
| 699733fcc3 | |||
| b76af47966 | |||
| 6accd1b0f5 | |||
| f08dbe0970 | |||
| 6c7493e39d | |||
| cc97128760 | |||
| 39e8bd0f4b | |||
| ba4b3dbed7 | |||
| d9e022dae2 | |||
| 48d899f423 | |||
| 19b6564cb2 | |||
| f24246ef25 | |||
| 65d2ca9ed3 | |||
| a2db87ef81 | |||
| 94ed3c3630 | |||
| a64a5755e8 | |||
| 20add2738c | |||
| e27a319261 | |||
| fbbe5d55b1 | |||
| 507ed58152 | |||
| 31bbbe9665 | |||
| 83abe5f606 | |||
| 26b3a8f206 | |||
| edae26e117 | |||
| 8579ff8352 | |||
| 2bffacbe5e | |||
| f24a8c60ad | |||
| 036ee75684 | |||
| 33a826b196 | |||
| 114658cf8c | |||
| d30ea1b89c | |||
| aff6d3d135 | |||
| 1c31c0d141 | |||
| 3f0116607d | |||
| 080fb08b76 | |||
| d3dde17693 | |||
| 9ae9d9adde | |||
| c615bdcde9 | |||
| 79c337448e | |||
| c67c551fb8 | |||
| 1149686da5 | |||
| 5c3be24712 | |||
| 444671ac5b | |||
| d1233ebfa8 | |||
| 1c328ac261 | |||
| cd31bfcc3e | |||
| 27174165a6 | |||
| bf89792f8b | |||
| 49e3ec0dcf | |||
| c2de5f68b3 | |||
| dd70675893 | |||
| 1dd94476b3 | |||
| c288a037b5 | |||
| b81ea4f672 | |||
| 1b3b05bf72 | |||
| cfad089cc3 | |||
| 95d227e5ef | |||
| ddfaf0cd46 | |||
| b09f4a5c79 | |||
| 6c0c012e08 | |||
| a16dfb1b36 | |||
| e549bdec8c | |||
| 16ca676db0 | |||
| 187758ccca | |||
| 330c24aed9 | |||
| ada75b605e | |||
| b06ed3cbf8 | |||
| e012cf0acf | |||
| 8e9463bffb | |||
| 9b4727587a | |||
| ee2f07fcff | |||
| f76fd48b4a | |||
| 5de4b0cb70 | |||
| fca21320e6 | |||
| c4126c7b9a | |||
| 176475f0fc | |||
| 7d6636087d | |||
| ce3a9a24d7 | |||
| ef17371ab6 | |||
| 6473214384 | |||
| f23946abe9 | |||
| fa6d9c22ac | |||
| d5f467e601 | |||
| b5f28006b3 | |||
| aac3e8acf6 | |||
| 10fb8722fe | |||
| 93c1918ab1 | |||
| ebfbaeba54 | |||
| 9c5a6a6442 | |||
| 67366d2f93 | |||
| 225e602176 | |||
| 02a768e1d2 | |||
| 35d20cb4b4 | |||
| 352021e1d2 | |||
| 285abbb38f | |||
| 3fd6551cae | |||
| ea8662ab3e | |||
| 1d28a42815 | |||
| 21740846b5 | |||
| 4fde15438f | |||
| 7a1bf0610e | |||
| 195d0b1ff5 | |||
| 053593ef3e | |||
| 347c3d5761 | |||
| 8449c071da | |||
| f88ed8e470 | |||
| 11ea0a0f7d |
@@ -0,0 +1,21 @@
|
||||
# all commits that change formatting to prettier
|
||||
fd544159e300340a25a48e3f40c58795991a4c17
|
||||
31fdf0fbea6b89c8d3d141b2ef8e79c2737287cb
|
||||
0d78a92431178be73342aeddda4b62d9e49138ba
|
||||
f307e95459898b9027d5e13357a809d8952e9017
|
||||
b1dffa4abe8321a47d79b2ea29ee32a81acfe031
|
||||
ca517d7f2c38775969a65fde1d88f41ad5f27931
|
||||
8f69c83b848ad0dc3c7ffce2541fc726693dedcf
|
||||
a5b5b7d5ef84b9852d2115dd7a764f4ab3299379
|
||||
894c1c5f100a62866cc7ed3e23d025c8e505c60d
|
||||
ca39926de2ccaceb11de47a35e5bf9899147a877
|
||||
b857aafcb9bdb5f1400a4cfedd56657e71ebe8cd
|
||||
c4b880a025823efadb9d76195c6261f2992c7654
|
||||
4c2b0d94e22f022969b86b95af516b76322840e0
|
||||
0460a9dfafe3a7ebbff1b87e52048fb7416bba43
|
||||
ec03e462f3d4ec90d1267474cc344563c949c2cf
|
||||
bbbe477f479f20722f0fea7ccc46095aad5d4253
|
||||
cc34e5fb072e7903e01463953a19d18503a39347
|
||||
f9781f9804f1e7c84df356d9aeb267730a7fc499
|
||||
dc62446ef7f2898a20d3be1196ecaf2a92a1077d
|
||||
711cb416260ca11ff158fc39b372efa2cf022d36
|
||||
@@ -1,7 +1,7 @@
|
||||
## PR Checklist
|
||||
Please check if your PR fulfills the following requirements:
|
||||
|
||||
- [ ] The commit message follows our guidelines: https://github.com/angular/angular/blob/main/CONTRIBUTING.md#commit
|
||||
- [ ] The commit message follows our guidelines: https://github.com/angular/angular/blob/main/contributing-docs/commit-message-guidelines.md
|
||||
- [ ] Tests for the changes have been added (for bug fixes / features)
|
||||
- [ ] Docs have been added / updated (for bug fixes / features)
|
||||
|
||||
|
||||
@@ -29830,7 +29830,7 @@ function legacyRestEndpointMethods(octokit) {
|
||||
legacyRestEndpointMethods.VERSION = VERSION9;
|
||||
|
||||
//
|
||||
var VERSION10 = "21.1.0";
|
||||
var VERSION10 = "21.1.1";
|
||||
|
||||
//
|
||||
var Octokit2 = Octokit.plugin(requestLog, legacyRestEndpointMethods, paginateRest).defaults(
|
||||
|
||||
@@ -4,7 +4,7 @@ description: 'Installs the dependencies using Yarn'
|
||||
runs:
|
||||
using: 'composite'
|
||||
steps:
|
||||
- uses: actions/cache@0c907a75c2c80ebcb7f088228285e798b750cf8f # v4
|
||||
- uses: actions/cache@d4323d4df104b026a6aa633fdb11d772146be0bf # v4
|
||||
with:
|
||||
path: |
|
||||
./node_modules/
|
||||
|
||||
@@ -95,7 +95,7 @@ jobs:
|
||||
- name: Run CI tests for framework
|
||||
run: yarn tsx ./scripts/build/build-packages-dist.mts
|
||||
- name: Archive build artifacts
|
||||
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
|
||||
uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4.6.1
|
||||
with:
|
||||
name: pr-artifacts-${{ github.event.number }}
|
||||
path: dist/packages-dist/
|
||||
|
||||
@@ -30,7 +30,7 @@ jobs:
|
||||
persist-credentials: false
|
||||
|
||||
- name: 'Run analysis'
|
||||
uses: ossf/scorecard-action@62b2cac7ed8198b15735ed49ab1e5cf35480ba46 # v2.4.0
|
||||
uses: ossf/scorecard-action@f49aabe0b5af0936a0987cfb85d86b75731b0186 # v2.4.1
|
||||
with:
|
||||
results_file: results.sarif
|
||||
results_format: sarif
|
||||
@@ -39,7 +39,7 @@ jobs:
|
||||
|
||||
# Upload the results as artifacts.
|
||||
- name: 'Upload artifact'
|
||||
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
|
||||
uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4.6.1
|
||||
with:
|
||||
name: SARIF file
|
||||
path: results.sarif
|
||||
@@ -47,6 +47,6 @@ jobs:
|
||||
|
||||
# Upload the results to GitHub's code scanning dashboard.
|
||||
- name: 'Upload to code-scanning'
|
||||
uses: github/codeql-action/upload-sarif@9e8d0789d4a0fa9ceb6b1738f7e269594bdd67f0 # v3.28.9
|
||||
uses: github/codeql-action/upload-sarif@6bb031afdd8eb862ea3fc1848194185e076637e5 # v3.28.11
|
||||
with:
|
||||
sarif_file: results.sarif
|
||||
|
||||
+1
-1
@@ -74,7 +74,7 @@ Fixes #<issue number>
|
||||
# =============================
|
||||
#
|
||||
# The full specification of the Angular Commit Message Format can be found at
|
||||
# https://github.com/angular/angular/blob/main/CONTRIBUTING.md#commit
|
||||
# https://github.com/angular/angular/blob/main/contributing-docs/commit-message-guidelines.md
|
||||
#
|
||||
# The following is an excerpt of the specification with the most commonly needed info.
|
||||
#
|
||||
|
||||
@@ -7,6 +7,8 @@ export const commitMessage: CommitMessageConfig = {
|
||||
maxLineLength: Infinity,
|
||||
minBodyLength: 20,
|
||||
minBodyLengthTypeExcludes: ['docs'],
|
||||
// If you update this, also update the docs.
|
||||
// https://github.com/angular/angular/blob/main/contributing-docs/commit-message-guidelines.md#scope
|
||||
scopes: [
|
||||
'animations',
|
||||
'bazel',
|
||||
|
||||
+2
-23
@@ -144,7 +144,6 @@ groups:
|
||||
- >
|
||||
contains_any_globs(files.exclude('packages/core/primitives/*'), [
|
||||
'contributing-docs/public-api-surface.md',
|
||||
'goldens/circular-deps/packages.json',
|
||||
'integration/**/{*,.*}',
|
||||
'modules/**/{*,.*}',
|
||||
'packages/animations/**/{*,.*}',
|
||||
@@ -395,6 +394,8 @@ groups:
|
||||
- pkozlowski-opensource
|
||||
- kirjs
|
||||
- mmalerba
|
||||
- crisbeto
|
||||
- devversion
|
||||
- ~iteriani
|
||||
- ~tbondwilkinson
|
||||
- ~rahatarmanahmed
|
||||
@@ -430,28 +431,6 @@ groups:
|
||||
required: 1 # Require that 1 person approve
|
||||
reviewed_for: required
|
||||
|
||||
# ================================================
|
||||
# Circular dependencies
|
||||
# ================================================
|
||||
circular-dependencies:
|
||||
<<: *defaults
|
||||
conditions:
|
||||
- *no-groups-above-this-pending
|
||||
- *no-groups-above-this-rejected
|
||||
- >
|
||||
contains_any_globs(files, [
|
||||
'goldens/circular-deps/packages.json'
|
||||
])
|
||||
reviewers:
|
||||
users:
|
||||
- alxhub
|
||||
- AndrewKushnir
|
||||
- atscott
|
||||
- ~jelbourn
|
||||
- thePunderWoman
|
||||
- pkozlowski-opensource
|
||||
- kirjs
|
||||
|
||||
####################################################################################
|
||||
# Special Cases
|
||||
####################################################################################
|
||||
|
||||
Vendored
+8
-5
@@ -1,9 +1,9 @@
|
||||
{
|
||||
"[javascript]": {
|
||||
"editor.formatOnSave": true,
|
||||
"editor.formatOnSave": true
|
||||
},
|
||||
"[typescript]": {
|
||||
"editor.formatOnSave": true,
|
||||
"editor.formatOnSave": true
|
||||
},
|
||||
// Exclude third party modules and build artifacts from the editor watchers/searches.
|
||||
"files.watcherExclude": {
|
||||
@@ -11,14 +11,17 @@
|
||||
"**/.git/subtree-cache/**": true,
|
||||
"**/node_modules/**": true,
|
||||
"**/bazel-out/**": true,
|
||||
"**/dist/**": true,
|
||||
"**/dist/**": true
|
||||
},
|
||||
"search.exclude": {
|
||||
"**/node_modules": true,
|
||||
"**/bower_components": true,
|
||||
"**/bazel-out": true,
|
||||
"**/dist": true,
|
||||
".history": true,
|
||||
".history": true
|
||||
},
|
||||
"git.ignoreLimitWarning": true,
|
||||
}
|
||||
"gitlens.advanced.blame.customArguments": [
|
||||
"--ignore-revs-file .git-blame-ignore-revs"
|
||||
]
|
||||
}
|
||||
|
||||
+98
-101
@@ -1,3 +1,101 @@
|
||||
<a name="19.2.2"></a>
|
||||
# 19.2.2 (2025-03-12)
|
||||
### common
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [90a16a1088](https://github.com/angular/angular/commit/90a16a10888eee37d8a61cdbfad070e002a3cfdf) | fix | support equality function in httpResource ([#60026](https://github.com/angular/angular/pull/60026)) |
|
||||
### compiler
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [56b551d273](https://github.com/angular/angular/commit/56b551d273a0978e1f2e2ef914c1d7ae942a28a8) | fix | incorrect spans for template literals ([#60323](https://github.com/angular/angular/pull/60323)) ([#60331](https://github.com/angular/angular/pull/60331)) |
|
||||
### compiler-cli
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [23ca88522b](https://github.com/angular/angular/commit/23ca88522bbc23e24a2b20e48e62edcce3a42eb6) | fix | handle transformed classes when generating HMR code ([#60298](https://github.com/angular/angular/pull/60298)) |
|
||||
### core
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [6dc41265fd](https://github.com/angular/angular/commit/6dc41265fd47df3fd0462bcd8f451eb6aea972ef) | fix | check whether application is destroyed before initializing event replay ([#59789](https://github.com/angular/angular/pull/59789)) |
|
||||
| [bb12b30d52](https://github.com/angular/angular/commit/bb12b30d5213912f50f53aff60a11e6d47349c82) | fix | ensures immediate trigger fires properly with lazy loaded routes ([#60203](https://github.com/angular/angular/pull/60203)) |
|
||||
| [b144dd946e](https://github.com/angular/angular/commit/b144dd946e38e52ce716aca7c6ba7c1a1a02f13d) | fix | fix removal of a container reference used in the component file ([#60210](https://github.com/angular/angular/pull/60210)) |
|
||||
### platform-server
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [15c42969fc](https://github.com/angular/angular/commit/15c42969fc42c76e3bd593201164183fb82d70f6) | fix | add missing peer dependency for `rxjs` ([#60308](https://github.com/angular/angular/pull/60308)) |
|
||||
### router
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [7bcdf7c143](https://github.com/angular/angular/commit/7bcdf7c1435f766b2b0bbd383358ef2ddabf217a) | fix | update symbols ([#60233](https://github.com/angular/angular/pull/60233)) |
|
||||
|
||||
<!-- CHANGELOG SPLIT MARKER -->
|
||||
|
||||
<a name="19.2.1"></a>
|
||||
# 19.2.1 (2025-03-05)
|
||||
### common
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [c2de5f68b3](https://github.com/angular/angular/commit/c2de5f68b3d3cd8a0c43b3c4325a1a1db874e132) | fix | clean up `onUrlChange` listener when root scope is destroyed ([#60004](https://github.com/angular/angular/pull/60004)) |
|
||||
### compiler-cli
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [1dd94476b3](https://github.com/angular/angular/commit/1dd94476b35d08e753766b1d0c5d8af5faa017a9) | fix | ensure template IDs are not reused if a source file changes ([#60152](https://github.com/angular/angular/pull/60152)) |
|
||||
### core
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [1b3b05bf72](https://github.com/angular/angular/commit/1b3b05bf7294963aa50f83b726db620b250b1a5c) | fix | cache ComponentRef inputs and outputs ([#60156](https://github.com/angular/angular/pull/60156)) |
|
||||
| [330c24aed9](https://github.com/angular/angular/commit/330c24aed92e4b916fb7ac6a91fb17c3ae728869) | fix | prevent invoking replay listeners on disconnected nodes ([#60103](https://github.com/angular/angular/pull/60103)) |
|
||||
| [cfad089cc3](https://github.com/angular/angular/commit/cfad089cc359cddf08d462662d2a6bf3acab9b22) | fix | prevents event replay from being called on comment nodes ([#60130](https://github.com/angular/angular/pull/60130)) |
|
||||
### language-service
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [3f0116607d](https://github.com/angular/angular/commit/3f0116607dc3ad7e31cb4d895a56094f77c82f5d) | fix | Forward the tags for quick info from the type definition ([#59524](https://github.com/angular/angular/pull/59524)) |
|
||||
|
||||
<!-- CHANGELOG SPLIT MARKER -->
|
||||
|
||||
<a name="19.2.0"></a>
|
||||
# 19.2.0 (2025-02-26)
|
||||
### common
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [3e39da593a](https://github.com/angular/angular/commit/3e39da593a0a0c047a2a03b8d5fcabf9dbace40f) | feat | introduce experimental `httpResource` ([#59876](https://github.com/angular/angular/pull/59876)) |
|
||||
### compiler
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [5b20bab96d](https://github.com/angular/angular/commit/5b20bab96d20fe89b5cc4b4af28edbaae2604da1) | feat | Add Skip Hydration diagnostic. ([#59576](https://github.com/angular/angular/pull/59576)) |
|
||||
| [fe8a68329b](https://github.com/angular/angular/commit/fe8a68329b50363f914a728579392f3fc68670a6) | feat | support untagged template literals in expressions ([#59230](https://github.com/angular/angular/pull/59230)) |
|
||||
### core
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [2588985f43](https://github.com/angular/angular/commit/2588985f433b20a6a5a8d239347291f5d6fb2451) | feat | pass signal node to throwInvalidWriteToSignalErrorFn ([#59600](https://github.com/angular/angular/pull/59600)) |
|
||||
| [168516462a](https://github.com/angular/angular/commit/168516462a9673b158fcaa38b8ce17bf684a8ac9) | feat | support default value in `resource()` ([#59655](https://github.com/angular/angular/pull/59655)) |
|
||||
| [bc2ad7bfd3](https://github.com/angular/angular/commit/bc2ad7bfd37a61992b550943de5da0eab2eec98b) | feat | support streaming resources ([#59573](https://github.com/angular/angular/pull/59573)) |
|
||||
| [146ab9a76e](https://github.com/angular/angular/commit/146ab9a76e6b4d8db7d08d34e2571ba5207f8756) | feat | support TypeScript 5.8 ([#59830](https://github.com/angular/angular/pull/59830)) |
|
||||
| [6c92d65349](https://github.com/angular/angular/commit/6c92d653493404a5f13aa59cde390bcbed973fb6) | fix | add `hasValue` narrowing to `ResourceRef` ([#59708](https://github.com/angular/angular/pull/59708)) |
|
||||
| [96e602ebe9](https://github.com/angular/angular/commit/96e602ebe9cdf7355befad22c11f9f91e0436e01) | fix | cancel in-progress request when same value is assigned ([#59280](https://github.com/angular/angular/pull/59280)) |
|
||||
| [6789c7ef94](https://github.com/angular/angular/commit/6789c7ef947952551d7598fe37a3d86093b75720) | fix | Defer afterRender until after first CD ([#59455](https://github.com/angular/angular/pull/59455)) ([#59551](https://github.com/angular/angular/pull/59551)) |
|
||||
| [c87e581dd9](https://github.com/angular/angular/commit/c87e581dd9e240c88cea50f222942873bdccd01d) | fix | Don't run effects in check no changes pass ([#59455](https://github.com/angular/angular/pull/59455)) ([#59551](https://github.com/angular/angular/pull/59551)) |
|
||||
| [127fc0dc84](https://github.com/angular/angular/commit/127fc0dc847a4e8b62be36cdd980a067c4da974f) | fix | fix `resource()`'s `previous.state` ([#59708](https://github.com/angular/angular/pull/59708)) |
|
||||
| [b592b1b051](https://github.com/angular/angular/commit/b592b1b0516786c52c7d0638c4e7545b0de8a545) | fix | fix race condition in resource() ([#59851](https://github.com/angular/angular/pull/59851)) |
|
||||
| [a299e02e91](https://github.com/angular/angular/commit/a299e02e9141cdc4d74185deb58308fa010bb36e) | fix | preserve tracing snapshot until tick finishes ([#59796](https://github.com/angular/angular/pull/59796)) |
|
||||
### forms
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [fa0c3e3210](https://github.com/angular/angular/commit/fa0c3e3210885a36e5c9e9eb76e821032f5cd215) | feat | support type set in form validators ([#45793](https://github.com/angular/angular/pull/45793)) |
|
||||
### migrations
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [1cd3a7db83](https://github.com/angular/angular/commit/1cd3a7db83e1d05a31d23324676420b614cdabe2) | feat | add migration to convert templates to use self-closing tags ([#57342](https://github.com/angular/angular/pull/57342)) |
|
||||
### platform-browser
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [e6cb411e43](https://github.com/angular/angular/commit/e6cb411e4393a4b1f5852d3d7c5b9622504399b1) | fix | automatically disable animations on the server ([#59762](https://github.com/angular/angular/pull/59762)) |
|
||||
### platform-server
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [fc5d187da5](https://github.com/angular/angular/commit/fc5d187da5e8895d60caa35b7b59e234998eddf0) | fix | decouple server from animations module ([#59762](https://github.com/angular/angular/pull/59762)) |
|
||||
|
||||
<!-- CHANGELOG SPLIT MARKER -->
|
||||
|
||||
<a name="19.1.7"></a>
|
||||
# 19.1.7 (2025-02-19)
|
||||
### common
|
||||
@@ -15,30 +113,6 @@
|
||||
|
||||
<!-- CHANGELOG SPLIT MARKER -->
|
||||
|
||||
<a name="19.2.0-next.3"></a>
|
||||
# 19.2.0-next.3 (2025-02-13)
|
||||
### compiler
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [9e847fc60d](https://github.com/angular/angular/commit/9e847fc60d4eef47e665e789ccd2d4f0b4bb94ea) | fix | handle tracking expressions requiring temporary variables ([#58520](https://github.com/angular/angular/pull/58520)) |
|
||||
### compiler-cli
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [5cd26a9420](https://github.com/angular/angular/commit/5cd26a94206dfe8aabdf0dd15bfc09e7a8c606da) | fix | handle deferred blocks with shared dependencies correctly ([#59926](https://github.com/angular/angular/pull/59926)) |
|
||||
### core
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [6789c7ef94](https://github.com/angular/angular/commit/6789c7ef947952551d7598fe37a3d86093b75720) | fix | Defer afterRender until after first CD ([#59455](https://github.com/angular/angular/pull/59455)) ([#59551](https://github.com/angular/angular/pull/59551)) |
|
||||
| [c87e581dd9](https://github.com/angular/angular/commit/c87e581dd9e240c88cea50f222942873bdccd01d) | fix | Don't run effects in check no changes pass ([#59455](https://github.com/angular/angular/pull/59455)) ([#59551](https://github.com/angular/angular/pull/59551)) |
|
||||
| [b0266bda4a](https://github.com/angular/angular/commit/b0266bda4ad4efd19710fd0363a50984f48269dc) | fix | invalidate HMR component if replacement throws an error ([#59854](https://github.com/angular/angular/pull/59854)) |
|
||||
### migrations
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [aa285c548c](https://github.com/angular/angular/commit/aa285c548c164917ebb5760802484843c6830daf) | fix | account for let declarations in control flow migration ([#59861](https://github.com/angular/angular/pull/59861)) |
|
||||
| [1119f85ca9](https://github.com/angular/angular/commit/1119f85ca935f43641f53ee8bc50efa47ad09717) | fix | count used dependencies inside existing control flow ([#59861](https://github.com/angular/angular/pull/59861)) |
|
||||
|
||||
<!-- CHANGELOG SPLIT MARKER -->
|
||||
|
||||
<a name="19.1.6"></a>
|
||||
# 19.1.6 (2025-02-12)
|
||||
### compiler
|
||||
@@ -61,27 +135,6 @@
|
||||
|
||||
<!-- CHANGELOG SPLIT MARKER -->
|
||||
|
||||
<a name="19.2.0-next.2"></a>
|
||||
# 19.2.0-next.2 (2025-02-06)
|
||||
### compiler-cli
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [a97136052f9](https://github.com/angular/angular/commit/a97136052f9cfdb696a3b335ed7a1b9fb994d408) | fix | gracefully fall back if const enum cannot be passed through ([#59815](https://github.com/angular/angular/pull/59815)) |
|
||||
| [bae94b82fda](https://github.com/angular/angular/commit/bae94b82fda1669a6aafad975a3a0eb6f2743039) | fix | handle const enums used inside HMR data ([#59815](https://github.com/angular/angular/pull/59815)) |
|
||||
| [03bcd30e348](https://github.com/angular/angular/commit/03bcd30e3487443983d450a5dfea2d68d551bbd3) | fix | handle enum members without initializers in partial evaluator ([#59815](https://github.com/angular/angular/pull/59815)) |
|
||||
### core
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [146ab9a76e6](https://github.com/angular/angular/commit/146ab9a76e6b4d8db7d08d34e2571ba5207f8756) | feat | support TypeScript 5.8 ([#59830](https://github.com/angular/angular/pull/59830)) |
|
||||
| [b592b1b0516](https://github.com/angular/angular/commit/b592b1b0516786c52c7d0638c4e7545b0de8a545) | fix | fix race condition in resource() ([#59851](https://github.com/angular/angular/pull/59851)) |
|
||||
| [a299e02e914](https://github.com/angular/angular/commit/a299e02e9141cdc4d74185deb58308fa010bb36e) | fix | preserve tracing snapshot until tick finishes ([#59796](https://github.com/angular/angular/pull/59796)) |
|
||||
### forms
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [fa0c3e32108](https://github.com/angular/angular/commit/fa0c3e3210885a36e5c9e9eb76e821032f5cd215) | feat | support type set in form validators ([#45793](https://github.com/angular/angular/pull/45793)) |
|
||||
|
||||
<!-- CHANGELOG SPLIT MARKER -->
|
||||
|
||||
<a name="19.1.5"></a>
|
||||
# 19.1.5 (2025-02-06)
|
||||
### compiler-cli
|
||||
@@ -93,45 +146,6 @@
|
||||
|
||||
<!-- CHANGELOG SPLIT MARKER -->
|
||||
|
||||
<a name="19.2.0-next.1"></a>
|
||||
# 19.2.0-next.1 (2025-01-29)
|
||||
### core
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [168516462a9](https://github.com/angular/angular/commit/168516462a9673b158fcaa38b8ce17bf684a8ac9) | feat | support default value in `resource()` ([#59655](https://github.com/angular/angular/pull/59655)) |
|
||||
| [6c92d653493](https://github.com/angular/angular/commit/6c92d653493404a5f13aa59cde390bcbed973fb6) | fix | add `hasValue` narrowing to `ResourceRef` ([#59708](https://github.com/angular/angular/pull/59708)) |
|
||||
| [96e602ebe9c](https://github.com/angular/angular/commit/96e602ebe9cdf7355befad22c11f9f91e0436e01) | fix | cancel in-progress request when same value is assigned ([#59280](https://github.com/angular/angular/pull/59280)) |
|
||||
| [cf9054248d1](https://github.com/angular/angular/commit/cf9054248d1b8b5658c5f8b582cea71535f081c2) | fix | check whether application is destroyed before printing hydration stats ([#59716](https://github.com/angular/angular/pull/59716)) |
|
||||
| [82876242e55](https://github.com/angular/angular/commit/82876242e557abbced793cff06c4d68c4721e6d2) | fix | ensure type is preserved during HMR ([#59700](https://github.com/angular/angular/pull/59700)) |
|
||||
| [127fc0dc847](https://github.com/angular/angular/commit/127fc0dc847a4e8b62be36cdd980a067c4da974f) | fix | fix `resource()`'s `previous.state` ([#59708](https://github.com/angular/angular/pull/59708)) |
|
||||
| [f862ace1819](https://github.com/angular/angular/commit/f862ace18191d7fda3b12fc3c6486c035a6b431d) | fix | fixes test timer-based test flakiness in CI ([#59674](https://github.com/angular/angular/pull/59674)) |
|
||||
### elements
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [0c1ecb453c8](https://github.com/angular/angular/commit/0c1ecb453c8b01447fb5a9f38fdfe9936a60abe5) | fix | not setting initial value on signal-based input ([#59773](https://github.com/angular/angular/pull/59773)) |
|
||||
### platform-browser
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [e6cb411e439](https://github.com/angular/angular/commit/e6cb411e4393a4b1f5852d3d7c5b9622504399b1) | fix | automatically disable animations on the server ([#59762](https://github.com/angular/angular/pull/59762)) |
|
||||
| [6b09716754b](https://github.com/angular/angular/commit/6b09716754b979c98489a2710eda977e51fe92d0) | fix | prepend `baseHref` to `sourceMappingURL` in CSS content ([#59730](https://github.com/angular/angular/pull/59730)) |
|
||||
| [fd7ee47bf70](https://github.com/angular/angular/commit/fd7ee47bf702029e481ad40362684f0a01ef2ec0) | fix | Update pseudoevent created by createMouseSpecialEvent to populate `_originalEvent` property ([#59690](https://github.com/angular/angular/pull/59690)) |
|
||||
| [65cf061feb4](https://github.com/angular/angular/commit/65cf061feb402e1532cd06d0fda5d2b7749ac421) | fix | Update pseudoevent created by createMouseSpecialEvent to populate `_originalEvent` property ([#59690](https://github.com/angular/angular/pull/59690)) |
|
||||
| [af9d74710c4](https://github.com/angular/angular/commit/af9d74710c460b0aaeaf3226533e9e544025523c) | fix | Update pseudoevent created by createMouseSpecialEvent to populate `_originalEvent` property ([#59690](https://github.com/angular/angular/pull/59690)) |
|
||||
### platform-server
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [fc5d187da5e](https://github.com/angular/angular/commit/fc5d187da5e8895d60caa35b7b59e234998eddf0) | fix | decouple server from animations module ([#59762](https://github.com/angular/angular/pull/59762)) |
|
||||
### router
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [c7b6e1107c6](https://github.com/angular/angular/commit/c7b6e1107c6fcc7ed1dbb7c7b8698b09bfa8f1cc) | fix | prevent error handling when injector is destroyed ([#59457](https://github.com/angular/angular/pull/59457)) |
|
||||
### service-worker
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [e2987a1d4ab](https://github.com/angular/angular/commit/e2987a1d4ab09b1abf1f42597c40f2055db116e1) | fix | add missing `rxjs` peer dependency ([#59747](https://github.com/angular/angular/pull/59747)) |
|
||||
|
||||
<!-- CHANGELOG SPLIT MARKER -->
|
||||
|
||||
<a name="19.1.4"></a>
|
||||
# 19.1.4 (2025-01-29)
|
||||
### core
|
||||
@@ -162,23 +176,6 @@
|
||||
|
||||
<!-- CHANGELOG SPLIT MARKER -->
|
||||
|
||||
<a name="19.2.0-next.0"></a>
|
||||
# 19.2.0-next.0 (2025-01-22)
|
||||
### compiler
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [fe8a68329b](https://github.com/angular/angular/commit/fe8a68329b50363f914a728579392f3fc68670a6) | feat | support untagged template literals in expressions ([#59230](https://github.com/angular/angular/pull/59230)) |
|
||||
### compiler-cli
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [ed705a856a](https://github.com/angular/angular/commit/ed705a856a164f91d33e2173b4f371329d07c472) | feat | detect missing structural directive imports ([#59443](https://github.com/angular/angular/pull/59443)) |
|
||||
### core
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [bc2ad7bfd3](https://github.com/angular/angular/commit/bc2ad7bfd37a61992b550943de5da0eab2eec98b) | feat | support streaming resources ([#59573](https://github.com/angular/angular/pull/59573)) |
|
||||
|
||||
<!-- CHANGELOG SPLIT MARKER -->
|
||||
|
||||
<a name="19.1.3"></a>
|
||||
# 19.1.3 (2025-01-22)
|
||||
### compiler
|
||||
|
||||
+6
-155
@@ -103,7 +103,7 @@ Before you submit your Pull Request (PR) consider the following guidelines:
|
||||
|
||||
8. Run the full Angular test suite, as described in the [developer documentation][dev-doc], and ensure that all tests pass.
|
||||
|
||||
9. Commit your changes using a descriptive commit message that follows our [commit message conventions](#commit).
|
||||
9. Commit your changes using a descriptive commit message that follows our [commit message conventions][commit-message-guidelines].
|
||||
Adherence to these conventions is necessary because release notes are automatically generated from these messages.
|
||||
|
||||
```shell
|
||||
@@ -145,7 +145,7 @@ That's it! Thank you for your contribution!
|
||||
|
||||
##### Updating the commit message
|
||||
|
||||
A reviewer might often suggest changes to a commit message (for example, to add more context for a change or adhere to our [commit message guidelines](#commit)).
|
||||
A reviewer might often suggest changes to a commit message (for example, to add more context for a change or adhere to our [commit message guidelines][commit-message-guidelines]).
|
||||
In order to update the commit message of the last commit on your branch:
|
||||
|
||||
1. Check out your branch:
|
||||
@@ -210,163 +210,14 @@ To ensure consistency throughout the source code, keep these rules in mind as yo
|
||||
An automated formatter is available, see [DEVELOPER.md](contributing-docs/building-and-testing-angular.md#formatting-your-source-code).
|
||||
|
||||
|
||||
## <a name="commit"></a> Commit Message Format
|
||||
|
||||
*This specification is inspired by and supersedes the [AngularJS commit message format][commit-message-format].*
|
||||
|
||||
We have very precise rules over how our Git commit messages must be formatted.
|
||||
This format leads to **easier to read commit history**.
|
||||
|
||||
Each commit message consists of a **header**, a **body**, and a **footer**.
|
||||
|
||||
|
||||
```
|
||||
<header>
|
||||
<BLANK LINE>
|
||||
<body>
|
||||
<BLANK LINE>
|
||||
<footer>
|
||||
```
|
||||
|
||||
The `header` is mandatory and must conform to the [Commit Message Header](#commit-header) format.
|
||||
|
||||
The `body` is mandatory for all commits except for those of type "docs".
|
||||
When the body is present it must be at least 20 characters long and must conform to the [Commit Message Body](#commit-body) format.
|
||||
|
||||
The `footer` is optional. The [Commit Message Footer](#commit-footer) format describes what the footer is used for and the structure it must have.
|
||||
|
||||
|
||||
#### <a name="commit-header"></a>Commit Message Header
|
||||
## <a name="commit"></a> Commit Message Guidelines
|
||||
|
||||
We have very precise rules over how our Git commit messages must be formatted:
|
||||
```
|
||||
<type>(<scope>): <short summary>
|
||||
│ │ │
|
||||
│ │ └─⫸ Summary in present tense. Not capitalized. No period at the end.
|
||||
│ │
|
||||
│ └─⫸ Commit Scope: animations|bazel|benchpress|common|compiler|compiler-cli|core|
|
||||
│ elements|forms|http|language-service|localize|platform-browser|
|
||||
│ platform-browser-dynamic|platform-server|router|service-worker|
|
||||
│ upgrade|zone.js|packaging|changelog|docs-infra|migrations|
|
||||
│ devtools
|
||||
│
|
||||
└─⫸ Commit Type: build|ci|docs|feat|fix|perf|refactor|test
|
||||
```
|
||||
|
||||
The `<type>` and `<summary>` fields are mandatory, the `(<scope>)` field is optional.
|
||||
|
||||
|
||||
##### Type
|
||||
|
||||
Must be one of the following:
|
||||
|
||||
* **build**: Changes that affect the build system or external dependencies (example scopes: gulp, broccoli, npm)
|
||||
* **ci**: Changes to our CI configuration files and scripts (examples: Github Actions, SauceLabs)
|
||||
* **docs**: Documentation only changes
|
||||
* **feat**: A new feature
|
||||
* **fix**: A bug fix
|
||||
* **perf**: A code change that improves performance
|
||||
* **refactor**: A code change that neither fixes a bug nor adds a feature
|
||||
* **test**: Adding missing tests or correcting existing tests
|
||||
|
||||
|
||||
##### Scope
|
||||
The scope should be the name of the npm package affected (as perceived by the person reading the changelog generated from commit messages).
|
||||
|
||||
The following is the list of supported scopes:
|
||||
|
||||
* `animations`
|
||||
* `bazel`
|
||||
* `benchpress`
|
||||
* `common`
|
||||
* `compiler`
|
||||
* `compiler-cli`
|
||||
* `core`
|
||||
* `elements`
|
||||
* `forms`
|
||||
* `http`
|
||||
* `language-service`
|
||||
* `localize`
|
||||
* `platform-browser`
|
||||
* `platform-browser-dynamic`
|
||||
* `platform-server`
|
||||
* `router`
|
||||
* `service-worker`
|
||||
* `upgrade`
|
||||
* `zone.js`
|
||||
|
||||
There are currently a few exceptions to the "use package name" rule:
|
||||
|
||||
* `packaging`: used for changes that change the npm package layout in all of our packages, e.g. public path changes, package.json changes done to all packages, d.ts file/format changes, changes to bundles, etc.
|
||||
|
||||
* `changelog`: used for updating the release notes in CHANGELOG.md
|
||||
|
||||
* `dev-infra`: used for dev-infra related changes within the directories /scripts and /tools
|
||||
|
||||
* `docs-infra`: used for docs-app (angular.dev) related changes within the /adev directory of the repo
|
||||
|
||||
* `migrations`: used for changes to the `ng update` migrations.
|
||||
|
||||
* `devtools`: used for changes in the [browser extension](./devtools/README.md).
|
||||
|
||||
* none/empty string: useful for `test` and `refactor` changes that are done across all packages (e.g. `test: add missing unit tests`) and for docs changes that are not related to a specific package (e.g. `docs: fix typo in tutorial`).
|
||||
|
||||
|
||||
##### Summary
|
||||
|
||||
Use the summary field to provide a succinct description of the change:
|
||||
|
||||
* use the imperative, present tense: "change" not "changed" nor "changes"
|
||||
* don't capitalize the first letter
|
||||
* no dot (.) at the end
|
||||
|
||||
|
||||
#### <a name="commit-body"></a>Commit Message Body
|
||||
|
||||
Just as in the summary, use the imperative, present tense: "fix" not "fixed" nor "fixes".
|
||||
|
||||
Explain the motivation for the change in the commit message body. This commit message should explain _why_ you are making the change.
|
||||
You can include a comparison of the previous behavior with the new behavior in order to illustrate the impact of the change.
|
||||
|
||||
|
||||
#### <a name="commit-footer"></a>Commit Message Footer
|
||||
|
||||
The footer can contain information about breaking changes and deprecations and is also the place to reference GitHub issues, Jira tickets, and other PRs that this commit closes or is related to.
|
||||
For example:
|
||||
|
||||
```
|
||||
BREAKING CHANGE: <breaking change summary>
|
||||
<BLANK LINE>
|
||||
<breaking change description + migration instructions>
|
||||
<BLANK LINE>
|
||||
<BLANK LINE>
|
||||
Fixes #<issue number>
|
||||
```
|
||||
|
||||
or
|
||||
|
||||
```
|
||||
DEPRECATED: <what is deprecated>
|
||||
<BLANK LINE>
|
||||
<deprecation description + recommended update path>
|
||||
<BLANK LINE>
|
||||
<BLANK LINE>
|
||||
Closes #<pr number>
|
||||
```
|
||||
|
||||
Breaking Change section should start with the phrase `BREAKING CHANGE: ` followed by a summary of the breaking change, a blank line, and a detailed description of the breaking change that also includes migration instructions.
|
||||
|
||||
Similarly, a Deprecation section should start with `DEPRECATED: ` followed by a short description of what is deprecated, a blank line, and a detailed description of the deprecation that also mentions the recommended update path.
|
||||
|
||||
|
||||
### Revert commits
|
||||
|
||||
If the commit reverts a previous commit, it should begin with `revert: `, followed by the header of the reverted commit.
|
||||
|
||||
The content of the commit message body should contain:
|
||||
|
||||
- information about the SHA of the commit being reverted in the following format: `This reverts commit <SHA>`,
|
||||
- a clear description of the reason for reverting the commit message.
|
||||
|
||||
See [Commit Message Guidelines][commit-message-guidelines] for details.
|
||||
|
||||
## <a name="cla"></a> Signing the CLA
|
||||
|
||||
@@ -390,9 +241,9 @@ The following documents can help you sort out issues with GitHub accounts and mu
|
||||
|
||||
|
||||
[coc]: https://github.com/angular/code-of-conduct/blob/main/CODE_OF_CONDUCT.md
|
||||
[commit-message-format]: https://docs.google.com/document/d/1QrDFcIiPjSLDn3EL15IJygNPiHORgU1_OOAqWjiDU5Y/edit#
|
||||
[corporate-cla]: https://cla.developers.google.com/about/google-corporate
|
||||
[dev-doc]: ./contributing-docs/building-and-testing-angular.md
|
||||
[commit-message-guidelines]: ./contributing-docs/commit-message-guidelines.md
|
||||
[github]: https://github.com/angular/angular
|
||||
[discord]: https://discord.gg/angular
|
||||
[individual-cla]: https://cla.developers.google.com/about/google-individual
|
||||
|
||||
@@ -143,10 +143,10 @@ cldr_xml_data_repository(
|
||||
# sass rules
|
||||
http_archive(
|
||||
name = "io_bazel_rules_sass",
|
||||
sha256 = "54bca211ea0a4de2c740a6e24b9d11225942a95452799b6a64dba36e082b7249",
|
||||
strip_prefix = "rules_sass-c01e8848f30f8e4672babcbe41c3ac3551f3a800",
|
||||
sha256 = "e8b863f6be5609c6ed15664d12512ee36d93e8bcb2c1a331f85d9e8758a82ee7",
|
||||
strip_prefix = "rules_sass-3b3667fd5861b06a03bea1f1946b55ac7100d4ea",
|
||||
urls = [
|
||||
"https://github.com/bazelbuild/rules_sass/archive/c01e8848f30f8e4672babcbe41c3ac3551f3a800.zip",
|
||||
"https://github.com/bazelbuild/rules_sass/archive/3b3667fd5861b06a03bea1f1946b55ac7100d4ea.zip",
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
@@ -10,7 +10,6 @@ ng_module(
|
||||
module_name = "@angular/docs",
|
||||
deps = [
|
||||
"//adev/shared-docs/components",
|
||||
"//adev/shared-docs/constants",
|
||||
"//adev/shared-docs/directives",
|
||||
"//adev/shared-docs/interfaces",
|
||||
"//adev/shared-docs/pipes",
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
load("//tools:defaults.bzl", "karma_web_test_suite", "ng_module", "ts_library")
|
||||
load("@io_bazel_rules_sass//:defs.bzl", "sass_binary")
|
||||
load("//tools:defaults.bzl", "karma_web_test_suite", "ng_module", "ts_library")
|
||||
|
||||
package(default_visibility = ["//visibility:private"])
|
||||
|
||||
@@ -23,6 +23,7 @@ ng_module(
|
||||
"//packages/common",
|
||||
"//packages/core",
|
||||
"//packages/router",
|
||||
"@npm//@angular/material",
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
@@ -7,8 +7,15 @@
|
||||
<li class="docs-faceted-list-item">
|
||||
@if (item.path) {
|
||||
@if (item.isExternal) {
|
||||
<a [href]="item.path" target="_blank">
|
||||
<span [class.docs-external-link]="item.isExternal">{{ item.label }}</span>
|
||||
<a
|
||||
[href]="item.path" target="_blank"
|
||||
[matTooltip]="item.label"
|
||||
[matTooltipDisabled]="item.label.length < 27"
|
||||
matTooltipPosition="after"
|
||||
[attr.aria-label]="item.label"
|
||||
[matTooltipClass]="'API-tooltip'"
|
||||
>
|
||||
<span [class.docs-external-link]="item.isExternal" class="docs-faceted-list-item-text">{{ item.label }}</span>
|
||||
@if (item.children && item.level! > 1 && !item.isExpanded) {
|
||||
<docs-icon>chevron_right</docs-icon>
|
||||
}
|
||||
@@ -25,8 +32,13 @@
|
||||
}"
|
||||
routerLinkActive="docs-faceted-list-item-active"
|
||||
(click)="emitClickOnLink()"
|
||||
[matTooltip]="item.label"
|
||||
[matTooltipDisabled]="item.label.length < 27"
|
||||
matTooltipPosition="after"
|
||||
[attr.aria-label]="item.label"
|
||||
[matTooltipClass]="'API-tooltip'"
|
||||
>
|
||||
<span>{{item.label}}</span>
|
||||
<span class="docs-faceted-list-item-text">{{item.label}}</span>
|
||||
@if (item.children && !item.isExpanded) {
|
||||
<docs-icon>chevron_right</docs-icon>
|
||||
}
|
||||
@@ -35,8 +47,14 @@
|
||||
} @else {
|
||||
<!-- Nav Section Header -->
|
||||
@if (item.level !== collapsableLevel() && item.level !== expandableLevel()) {
|
||||
<div class="docs-secondary-nav-header">
|
||||
<span>{{item.label}}</span>
|
||||
<div class="docs-secondary-nav-header"
|
||||
[matTooltip]="item.label"
|
||||
[matTooltipDisabled]="item.label.length < 27"
|
||||
matTooltipPosition="after"
|
||||
[attr.aria-label]="item.label"
|
||||
[matTooltipClass]="'API-tooltip'"
|
||||
>
|
||||
<span class="docs-faceted-list-item-text">{{item.label}}</span>
|
||||
</div>
|
||||
}
|
||||
|
||||
@@ -54,13 +72,17 @@
|
||||
[class.docs-expanded-button]="item.children && item.level == collapsableLevel()"
|
||||
[class.docs-not-expanded-button]="item.children && item.level === expandableLevel()"
|
||||
[class.docs-nav-item-has-icon]="
|
||||
item.children && item.level === expandableLevel() && !item.isExpanded
|
||||
"
|
||||
item.children && item.level === expandableLevel() && !item.isExpanded"
|
||||
[matTooltip]="item.label"
|
||||
[matTooltipDisabled]="item.label.length < 27"
|
||||
matTooltipPosition="after"
|
||||
[attr.aria-label]="item.label"
|
||||
[matTooltipClass]="'API-tooltip'"
|
||||
>
|
||||
@if (item.children && item.level === collapsableLevel()) {
|
||||
<docs-icon>arrow_back</docs-icon>
|
||||
}
|
||||
<span>{{ item.label }}</span>
|
||||
<span class="docs-faceted-list-item-text">{{ item.label }}</span>
|
||||
</button>
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,6 +45,12 @@
|
||||
border: 0;
|
||||
}
|
||||
|
||||
.docs-faceted-list-item-text {
|
||||
max-width: calc(100% - 1rem);
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.docs-nav-item-has-icon {
|
||||
&::after {
|
||||
// FIXME: for some reason this disappears when transformed
|
||||
@@ -115,7 +121,7 @@ a,
|
||||
|
||||
// Add padding-bottom to last item in the list
|
||||
.docs-navigation-list {
|
||||
width: 100%;
|
||||
width: 15rem;
|
||||
|
||||
li:last-of-type {
|
||||
ul:last-of-type {
|
||||
@@ -144,3 +150,12 @@ a,
|
||||
margin-inline-end: 0.4rem;
|
||||
}
|
||||
}
|
||||
|
||||
::ng-deep .API-tooltip{
|
||||
.mat-mdc-tooltip-surface{
|
||||
max-width: unset;
|
||||
width:auto;
|
||||
word-break: keep-all;
|
||||
white-space: pre;
|
||||
}
|
||||
}
|
||||
@@ -13,10 +13,18 @@ import {RouterLink, RouterLinkActive} from '@angular/router';
|
||||
import {IconComponent} from '../icon/icon.component';
|
||||
import {IsActiveNavigationItem} from '../../pipes/is-active-navigation-item.pipe';
|
||||
import {NgTemplateOutlet} from '@angular/common';
|
||||
import {MatTooltipModule} from '@angular/material/tooltip';
|
||||
|
||||
@Component({
|
||||
selector: 'docs-navigation-list',
|
||||
imports: [RouterLink, RouterLinkActive, IconComponent, IsActiveNavigationItem, NgTemplateOutlet],
|
||||
imports: [
|
||||
RouterLink,
|
||||
RouterLinkActive,
|
||||
IconComponent,
|
||||
IsActiveNavigationItem,
|
||||
NgTemplateOutlet,
|
||||
MatTooltipModule,
|
||||
],
|
||||
templateUrl: './navigation-list.component.html',
|
||||
styleUrls: ['./navigation-list.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
|
||||
@@ -2,6 +2,8 @@ dialog {
|
||||
background-color: transparent;
|
||||
border: none;
|
||||
padding-block-end: 3rem;
|
||||
margin: 0 auto;
|
||||
top: 15vh;
|
||||
|
||||
&::backdrop {
|
||||
backdrop-filter: blur(5px);
|
||||
|
||||
@@ -6,26 +6,18 @@
|
||||
<ul class="docs-faceted-list">
|
||||
<!-- TODO: Hide li elements with class docs-toc-item-h3 for laptop, table and phone screen resolutions -->
|
||||
@for (item of tableOfContentItems(); track item.id) {
|
||||
<li
|
||||
class="docs-faceted-list-item"
|
||||
[class.docs-toc-item-h2]="item.level === TableOfContentsLevel.H2"
|
||||
[class.docs-toc-item-h3]="item.level === TableOfContentsLevel.H3"
|
||||
>
|
||||
<!-- Not using routerLink + fragment because of: https://github.com/angular/angular/issues/30139 -->
|
||||
<a
|
||||
[href]="location.path() + '#' + item.id"
|
||||
[class.docs-faceted-list-item-active]="item.id === activeItemId()"
|
||||
>
|
||||
{{ item.title }}
|
||||
</a>
|
||||
</li>
|
||||
<li class="docs-faceted-list-item" [class.docs-toc-item-h2]="item.level === TableOfContentsLevel.H2"
|
||||
[class.docs-toc-item-h3]="item.level === TableOfContentsLevel.H3">
|
||||
<!-- Not using routerLink + fragment because of: https://github.com/angular/angular/issues/30139 -->
|
||||
<a [href]="location.path() + '#' + item.id" [class.docs-faceted-list-item-active]="item.id === activeItemId()">
|
||||
{{ item.title }}
|
||||
</a>
|
||||
</li>
|
||||
}
|
||||
</ul>
|
||||
</nav>
|
||||
@if (shouldDisplayScrollToTop()) {
|
||||
<button type="button" (click)="scrollToTop()">
|
||||
<docs-icon role="presentation">arrow_upward_alt</docs-icon>
|
||||
Back to the top
|
||||
</button>
|
||||
}
|
||||
</aside>
|
||||
<button type="button" (click)="scrollToTop()">
|
||||
<docs-icon role="presentation">arrow_upward_alt</docs-icon>
|
||||
Back to the top
|
||||
</button>
|
||||
</aside>
|
||||
@@ -11,8 +11,8 @@ import {
|
||||
Component,
|
||||
DestroyRef,
|
||||
input,
|
||||
computed,
|
||||
inject,
|
||||
afterNextRender,
|
||||
} from '@angular/core';
|
||||
import {Location} from '@angular/common';
|
||||
import {TableOfContentsLevel} from '../../interfaces/index';
|
||||
@@ -39,12 +39,15 @@ export class TableOfContents {
|
||||
tableOfContentItems = this.tableOfContentsLoader.tableOfContentItems;
|
||||
|
||||
activeItemId = this.scrollSpy.activeItemId;
|
||||
shouldDisplayScrollToTop = computed(() => !this.scrollSpy.scrollbarThumbOnTop());
|
||||
TableOfContentsLevel = TableOfContentsLevel;
|
||||
|
||||
ngAfterViewInit() {
|
||||
this.tableOfContentsLoader.buildTableOfContent(this.contentSourceElement());
|
||||
this.scrollSpy.startListeningToScroll(this.contentSourceElement(), this.destroyRef);
|
||||
constructor() {
|
||||
afterNextRender({
|
||||
read: () => {
|
||||
this.tableOfContentsLoader.buildTableOfContent(this.contentSourceElement());
|
||||
this.scrollSpy.setupActiveItemListener(this.contentSourceElement(), this.destroyRef);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
scrollToTop(): void {
|
||||
|
||||
@@ -121,7 +121,7 @@ describe('DocViewer', () => {
|
||||
expect(checkIcon.nativeElement.innerHTML).toBe('check');
|
||||
});
|
||||
|
||||
it('should display example viewer in multi file mode when user clicks expand', async () => {
|
||||
it('should display example viewer in multi file mode when provided example is multi file snippet', async () => {
|
||||
const fixture = TestBed.createComponent(DocViewer);
|
||||
fixture.componentRef.setInput(
|
||||
'docContent',
|
||||
@@ -131,10 +131,6 @@ describe('DocViewer', () => {
|
||||
await fixture.whenStable();
|
||||
|
||||
const exampleViewer = fixture.debugElement.query(By.directive(ExampleViewer));
|
||||
const expandButton = fixture.debugElement.query(
|
||||
By.css('button[aria-label="Expand code example"]'),
|
||||
);
|
||||
expandButton.nativeElement.click();
|
||||
|
||||
expect(exampleViewer).not.toBeNull();
|
||||
expect(exampleViewer.componentInstance.view()).toBe(CodeExampleViewMode.MULTI_FILE);
|
||||
|
||||
@@ -196,13 +196,13 @@ export class DocViewer implements OnChanges {
|
||||
const exampleRef = this.viewContainer.createComponent(ExampleViewer);
|
||||
|
||||
this.countOfExamples++;
|
||||
exampleRef.instance.metadata = {
|
||||
exampleRef.setInput('metadata', {
|
||||
title: title ?? firstCodeSnippetTitle,
|
||||
path,
|
||||
files: snippets,
|
||||
preview,
|
||||
id: this.countOfExamples,
|
||||
};
|
||||
});
|
||||
|
||||
exampleRef.instance.githubUrl = `${GITHUB_CONTENT_URL}/${snippets[0].name}`;
|
||||
exampleRef.instance.stackblitzUrl = `${ASSETS_EXAMPLES_PATH}/${snippets[0].name}.html`;
|
||||
@@ -293,9 +293,6 @@ export class DocViewer implements OnChanges {
|
||||
}
|
||||
}
|
||||
|
||||
// Trigger change detection after setting inputs.
|
||||
componentRef.changeDetectorRef.detectChanges();
|
||||
|
||||
// Attach a view to the ApplicationRef for change detection
|
||||
// purposes and for hydration serialization to pick it up
|
||||
// during SSG.
|
||||
|
||||
+42
-9
@@ -100,26 +100,59 @@ describe('ExampleViewer', () => {
|
||||
expect(component.tabs()![2].name).toBe('another-example.ts');
|
||||
});
|
||||
|
||||
it('should expandable be false when none of the example files have defined visibleLinesRange ', waitForAsync(async () => {
|
||||
it('should expand button not appear when there is no hidden line', waitForAsync(async () => {
|
||||
component.metadata = getMetadata();
|
||||
await component.renderExample();
|
||||
expect(component.expandable()).toBeFalse();
|
||||
const button = fixture.debugElement.query(By.css('button[aria-label="Expand code example"]'));
|
||||
expect(button).toBeNull();
|
||||
}));
|
||||
|
||||
it('should expandable be true when at least one example file has defined visibleLinesRange ', waitForAsync(async () => {
|
||||
it('should have line with hidden line class when expand button is present', waitForAsync(async () => {
|
||||
const expectedCodeSnippetContent =
|
||||
'typescript code<br/>' + '<div class="line">hidden line</div>';
|
||||
|
||||
component.metadata = getMetadata({
|
||||
files: [
|
||||
{name: 'example.ts', content: 'typescript file'},
|
||||
{
|
||||
name: 'example.html',
|
||||
content: 'html file',
|
||||
visibleLinesRange: '[1, 2]',
|
||||
name: 'example.ts',
|
||||
content: `<pre><code>${expectedCodeSnippetContent}</code></pre>`,
|
||||
visibleLinesRange: '[1]',
|
||||
},
|
||||
{name: 'another-example.ts', content: 'css file'},
|
||||
],
|
||||
});
|
||||
|
||||
await component.renderExample();
|
||||
expect(component.expandable()).toBeTrue();
|
||||
fixture.detectChanges();
|
||||
|
||||
const hiddenLine = fixture.debugElement.query(By.css('div[class="line hidden"]'));
|
||||
expect(hiddenLine).toBeTruthy();
|
||||
}));
|
||||
|
||||
it('should have no more line with hidden line class when expand button is clicked', waitForAsync(async () => {
|
||||
const expectedCodeSnippetContent =
|
||||
'typescript code<br/>' + '<div class="line">hidden line</div>';
|
||||
|
||||
component.metadata = getMetadata({
|
||||
files: [
|
||||
{
|
||||
name: 'example.ts',
|
||||
content: `<pre><code>${expectedCodeSnippetContent}</code></pre>`,
|
||||
visibleLinesRange: '[1]',
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
await component.renderExample();
|
||||
fixture.detectChanges();
|
||||
|
||||
const expandButton = fixture.debugElement.query(
|
||||
By.css('button[aria-label="Expand code example"]'),
|
||||
);
|
||||
expandButton.nativeElement.click();
|
||||
fixture.detectChanges();
|
||||
|
||||
const hiddenLine = fixture.debugElement.query(By.css('div[class="line hidden"]'));
|
||||
expect(hiddenLine).toBeNull();
|
||||
}));
|
||||
|
||||
it('should set exampleComponent when metadata contains path and preview is true', waitForAsync(async () => {
|
||||
|
||||
@@ -7,18 +7,21 @@
|
||||
*/
|
||||
|
||||
import {
|
||||
afterNextRender,
|
||||
ChangeDetectionStrategy,
|
||||
Component,
|
||||
DestroyRef,
|
||||
Input,
|
||||
Type,
|
||||
computed,
|
||||
inject,
|
||||
ChangeDetectorRef,
|
||||
ViewChild,
|
||||
signal,
|
||||
Component,
|
||||
computed,
|
||||
DestroyRef,
|
||||
ElementRef,
|
||||
forwardRef,
|
||||
inject,
|
||||
Injector,
|
||||
input,
|
||||
Input,
|
||||
signal,
|
||||
Type,
|
||||
ViewChild,
|
||||
} from '@angular/core';
|
||||
import {CommonModule, DOCUMENT} from '@angular/common';
|
||||
import {MatTabGroup, MatTabsModule} from '@angular/material/tabs';
|
||||
@@ -47,10 +50,7 @@ export const HIDDEN_CLASS_NAME = 'hidden';
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class ExampleViewer {
|
||||
// TODO: replace by signal-based input when it'll be available
|
||||
@Input({required: true}) set metadata(value: ExampleMetadata) {
|
||||
this.exampleMetadata.set(value);
|
||||
}
|
||||
exampleMetadata = input.required<ExampleMetadata>({alias: 'metadata'});
|
||||
|
||||
@Input() githubUrl: string | null = null;
|
||||
@Input() stackblitzUrl: string | null = null;
|
||||
@@ -60,6 +60,7 @@ export class ExampleViewer {
|
||||
private readonly clipboard = inject(Clipboard);
|
||||
private readonly destroyRef = inject(DestroyRef);
|
||||
private readonly document = inject(DOCUMENT);
|
||||
private readonly injector = inject(Injector);
|
||||
private readonly elementRef = inject(ElementRef<HTMLElement>);
|
||||
private readonly exampleViewerContentLoader = inject(EXAMPLE_VIEWER_CONTENT_LOADER);
|
||||
|
||||
@@ -74,8 +75,8 @@ export class ExampleViewer {
|
||||
CodeExampleViewMode = CodeExampleViewMode;
|
||||
exampleComponent?: Type<unknown>;
|
||||
|
||||
expandable = signal<boolean>(false);
|
||||
expanded = signal<boolean>(false);
|
||||
exampleMetadata = signal<ExampleMetadata | null>(null);
|
||||
snippetCode = signal<Snippet | undefined>(undefined);
|
||||
tabs = computed(() =>
|
||||
this.exampleMetadata()?.files.map((file) => ({
|
||||
@@ -89,9 +90,6 @@ export class ExampleViewer {
|
||||
? CodeExampleViewMode.SNIPPET
|
||||
: CodeExampleViewMode.MULTI_FILE,
|
||||
);
|
||||
expandable = computed(() =>
|
||||
this.exampleMetadata()?.files.some((file) => !!file.visibleLinesRange),
|
||||
);
|
||||
|
||||
async renderExample(): Promise<void> {
|
||||
// Lazy load live example component
|
||||
@@ -103,18 +101,27 @@ export class ExampleViewer {
|
||||
|
||||
this.snippetCode.set(this.exampleMetadata()?.files[0]);
|
||||
|
||||
this.changeDetector.detectChanges();
|
||||
afterNextRender(
|
||||
() => {
|
||||
// Several function below query the DOM directly, we need to wait until the DOM is rendered.
|
||||
this.setCodeLinesVisibility();
|
||||
|
||||
this.setCodeLinesVisibility();
|
||||
this.elementRef.nativeElement.setAttribute(
|
||||
'id',
|
||||
`example-${this.exampleMetadata()?.id.toString()!}`,
|
||||
);
|
||||
|
||||
this.elementRef.nativeElement.setAttribute(
|
||||
'id',
|
||||
`example-${this.exampleMetadata()?.id.toString()!}`,
|
||||
this.matTabGroup?.realignInkBar();
|
||||
|
||||
this.listenToMatTabIndexChange();
|
||||
|
||||
const lines = this.getHiddenCodeLines();
|
||||
const lineNumbers = this.getHiddenCodeLineNumbers();
|
||||
|
||||
this.expandable.set(lines.length > 0 || lineNumbers.length > 0);
|
||||
},
|
||||
{injector: this.injector},
|
||||
);
|
||||
|
||||
this.matTabGroup?.realignInkBar();
|
||||
|
||||
this.listenToMatTabIndexChange();
|
||||
}
|
||||
|
||||
toggleExampleVisibility(): void {
|
||||
@@ -157,21 +164,9 @@ export class ExampleViewer {
|
||||
}
|
||||
|
||||
private handleExpandedStateForCodeBlock(): void {
|
||||
const lines = <HTMLDivElement[]>(
|
||||
Array.from(
|
||||
this.elementRef.nativeElement.querySelectorAll(
|
||||
`.${CODE_LINE_CLASS_NAME}.${HIDDEN_CLASS_NAME}`,
|
||||
),
|
||||
)
|
||||
);
|
||||
const lines = this.getHiddenCodeLines();
|
||||
|
||||
const lineNumbers = <HTMLSpanElement[]>(
|
||||
Array.from(
|
||||
this.elementRef.nativeElement.querySelectorAll(
|
||||
`.${CODE_LINE_NUMBER_CLASS_NAME}.${HIDDEN_CLASS_NAME}`,
|
||||
),
|
||||
)
|
||||
);
|
||||
const lineNumbers = this.getHiddenCodeLineNumbers();
|
||||
|
||||
const gapLines = <HTMLDivElement[]>(
|
||||
Array.from(
|
||||
@@ -239,4 +234,24 @@ export class ExampleViewer {
|
||||
element.parentNode?.insertBefore(separator, element);
|
||||
}
|
||||
}
|
||||
|
||||
private getHiddenCodeLines(): HTMLDivElement[] {
|
||||
return <HTMLDivElement[]>(
|
||||
Array.from(
|
||||
this.elementRef.nativeElement.querySelectorAll(
|
||||
`.${CODE_LINE_CLASS_NAME}.${HIDDEN_CLASS_NAME}`,
|
||||
),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
private getHiddenCodeLineNumbers(): HTMLSpanElement[] {
|
||||
return <HTMLSpanElement[]>(
|
||||
Array.from(
|
||||
this.elementRef.nativeElement.querySelectorAll(
|
||||
`.${CODE_LINE_NUMBER_CLASS_NAME}.${HIDDEN_CLASS_NAME}`,
|
||||
),
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
load("//tools:defaults.bzl", "ts_library")
|
||||
|
||||
package(default_visibility = ["//visibility:private"])
|
||||
|
||||
ts_library(
|
||||
name = "constants",
|
||||
srcs = [
|
||||
"index.ts",
|
||||
],
|
||||
visibility = [
|
||||
"//adev/shared-docs:__subpackages__",
|
||||
],
|
||||
deps = [
|
||||
":lib",
|
||||
],
|
||||
)
|
||||
|
||||
ts_library(
|
||||
name = "lib",
|
||||
srcs = glob(
|
||||
[
|
||||
"*.ts",
|
||||
],
|
||||
exclude = [
|
||||
"index.ts",
|
||||
],
|
||||
),
|
||||
)
|
||||
@@ -1,13 +0,0 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright Google LLC All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.dev/license
|
||||
*/
|
||||
|
||||
// Used for both the table of contents and the home animation
|
||||
export const RESIZE_EVENT_DELAY = 500;
|
||||
|
||||
// Used for the home animation
|
||||
export const WEBGL_LOADED_DELAY = 250;
|
||||
@@ -12,7 +12,6 @@
|
||||
|
||||
export * from './directives/index';
|
||||
export * from './components/index';
|
||||
export * from './constants/index';
|
||||
export * from './interfaces/index';
|
||||
export * from './providers/index';
|
||||
export * from './services/index';
|
||||
|
||||
@@ -19,6 +19,4 @@ export interface TableOfContentsItem {
|
||||
level: TableOfContentsLevel;
|
||||
/** The unique title for this document page. */
|
||||
title: string;
|
||||
/** The top offset px of the heading */
|
||||
top: number;
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
import {Renderer, Tokens} from 'marked';
|
||||
import {codeToHtml} from '../shiki/shiki';
|
||||
import {SECTION_HEADING, SECTION_SUB_HEADING} from '../styling/css-classes';
|
||||
|
||||
/**
|
||||
* Custom renderer for marked that will be used to transform markdown files to HTML
|
||||
@@ -55,21 +56,39 @@ export const renderer: Partial<Renderer> = {
|
||||
<div class="docs-table docs-scroll-track-transparent">
|
||||
<table>
|
||||
<thead>
|
||||
${this.tablerow({
|
||||
text: header.map((cell) => this.tablecell(cell)).join(''),
|
||||
})}
|
||||
${this.tablerow({text: header.map((cell) => this.tablecell(cell)).join('')})}
|
||||
</thead>
|
||||
<tbody>
|
||||
${rows
|
||||
.map((row) =>
|
||||
this.tablerow({
|
||||
text: row.map((cell) => this.tablecell(cell)).join(''),
|
||||
}),
|
||||
)
|
||||
.map((row) => this.tablerow({text: row.map((cell) => this.tablecell(cell)).join('')}))
|
||||
.join('')}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
`;
|
||||
},
|
||||
|
||||
heading(this: Renderer, {text, depth, tokens}: Tokens.Heading) {
|
||||
const id = text
|
||||
.toLowerCase()
|
||||
.replaceAll(' ', '-')
|
||||
.replace(/[^a-z0-9-]/g, '');
|
||||
|
||||
// Since we have a code transformer `addApiLinksToHtml` which adds anchors
|
||||
// to code blocks of known symbols, we add an additional `data-skip-anchor`
|
||||
// attribute that prevents the transformation. This is needed since nested
|
||||
// anchor tags are illegal and break the HTML.
|
||||
const textRenderer = new Renderer();
|
||||
textRenderer.codespan = ({text}) => `<code data-skip-anchor>${text}</code>`;
|
||||
const parsedText = this.parser.parseInline(tokens, textRenderer);
|
||||
|
||||
// The template matches templates/section-heading.tsx
|
||||
return `
|
||||
<h${depth} id="${id}" class="${SECTION_HEADING} ${SECTION_SUB_HEADING}">
|
||||
<a href="#${id}" aria-label="Link to ${text} section" tabIndex="-1">
|
||||
${parsedText}
|
||||
</a>
|
||||
</h${depth}>
|
||||
`;
|
||||
},
|
||||
};
|
||||
|
||||
@@ -27,3 +27,4 @@ export const HEADER_ENTRY_LABEL = 'docs-api-item-label';
|
||||
|
||||
export const SECTION_CONTAINER = 'docs-reference-section';
|
||||
export const SECTION_HEADING = 'docs-reference-section-heading';
|
||||
export const SECTION_SUB_HEADING = 'docs-reference-section-heading--sub';
|
||||
|
||||
@@ -38,8 +38,6 @@ export function getCurrentSymbol(): string | undefined {
|
||||
return currentSymbol;
|
||||
}
|
||||
|
||||
export function logUnknownSymbol(link: string, symbol: string): void {
|
||||
console.warn(
|
||||
`WARNING: {@link ${link}} is invalid, ${symbol} or ${currentSymbol}.${symbol} is unknown in this context`,
|
||||
);
|
||||
export function unknownSymbolMessage(link: string, symbol: string): string {
|
||||
return `WARNING: {@link ${link}} is invalid, ${symbol} or ${currentSymbol}.${symbol} is unknown in this context`;
|
||||
}
|
||||
|
||||
@@ -25,9 +25,7 @@ export function HeaderApi(props: {entry: DocEntryRenderable; showFullDescription
|
||||
// When ADEV is not deployed on the main branch branch anymore,
|
||||
// We should update it to point to the tag of the released version which ADEV runs on.
|
||||
|
||||
const sourceUrl = isDocEntryWithSourceInfo(entry)
|
||||
? `https://github.com/angular/angular/blob/main${entry.source.filePath}#L${entry.source.startLine}-L${entry.source.endLine}`
|
||||
: null;
|
||||
const sourceUrl = sourceUrlForEntry(entry);
|
||||
|
||||
return (
|
||||
<header className={HEADER_CLASS_NAME}>
|
||||
@@ -40,9 +38,7 @@ export function HeaderApi(props: {entry: DocEntryRenderable; showFullDescription
|
||||
{getEntryTypeDisplayName(entry.entryType)}
|
||||
</div>
|
||||
{entry.isDeprecated && (
|
||||
<div className={`${HEADER_ENTRY_LABEL} type-deprecated full`}>
|
||||
Deprecated
|
||||
</div>
|
||||
<div className={`${HEADER_ENTRY_LABEL} type-deprecated full`}>Deprecated</div>
|
||||
)}
|
||||
{entry.isDeveloperPreview && (
|
||||
<div className={`${HEADER_ENTRY_LABEL} type-developer_preview full`}>
|
||||
@@ -95,3 +91,16 @@ function getEntryTypeDisplayName(entryType: EntryType | string): string {
|
||||
}
|
||||
return entryType;
|
||||
}
|
||||
|
||||
function sourceUrlForEntry(entry: DocEntryRenderable): string | null {
|
||||
if (!isDocEntryWithSourceInfo(entry)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (entry.source.filePath.includes('node_modules')) {
|
||||
// We don't know the source path in external repos link the CLI
|
||||
return null;
|
||||
} else {
|
||||
return `https://github.com/angular/angular/blob/main${entry.source.filePath}#L${entry.source.startLine}-L${entry.source.endLine}`;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -754,6 +754,525 @@
|
||||
"startLine": 112,
|
||||
"endLine": 115
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "HttpRequest",
|
||||
"isAbstract": false,
|
||||
"entryType": "undecorated_class",
|
||||
"members": [
|
||||
{
|
||||
"name": "constructor",
|
||||
"signatures": [
|
||||
{
|
||||
"name": "constructor",
|
||||
"params": [
|
||||
{
|
||||
"name": "method",
|
||||
"description": "",
|
||||
"type": "\"GET\" | \"HEAD\"",
|
||||
"isOptional": false,
|
||||
"isRestParam": false
|
||||
},
|
||||
{
|
||||
"name": "url",
|
||||
"description": "",
|
||||
"type": "string",
|
||||
"isOptional": false,
|
||||
"isRestParam": false
|
||||
},
|
||||
{
|
||||
"name": "init",
|
||||
"description": "",
|
||||
"type": "{ headers?: HttpHeaders | undefined; context?: HttpContext | undefined; reportProgress?: boolean | undefined; params?: HttpParams | undefined; responseType?: \"arraybuffer\" | \"blob\" | \"text\" | \"json\" | undefined; withCredentials?: boolean | undefined; transferCache?: boolean | { includeHeaders?: string[] | undefined; } | undefined; } | undefined",
|
||||
"isOptional": true,
|
||||
"isRestParam": false
|
||||
}
|
||||
],
|
||||
"returnType": "foobar",
|
||||
"description": "",
|
||||
"entryType": "function",
|
||||
"jsdocTags": [],
|
||||
"rawComment": "",
|
||||
"generics": [],
|
||||
"isNewType": false
|
||||
},
|
||||
{
|
||||
"name": "constructor",
|
||||
"params": [
|
||||
{
|
||||
"name": "method",
|
||||
"description": "",
|
||||
"type": "\"DELETE\" | \"JSONP\" | \"OPTIONS\"",
|
||||
"isOptional": false,
|
||||
"isRestParam": false
|
||||
},
|
||||
{
|
||||
"name": "url",
|
||||
"description": "",
|
||||
"type": "string",
|
||||
"isOptional": false,
|
||||
"isRestParam": false
|
||||
},
|
||||
{
|
||||
"name": "init",
|
||||
"description": "",
|
||||
"type": "{ headers?: HttpHeaders | undefined; context?: HttpContext | undefined; reportProgress?: boolean | undefined; params?: HttpParams | undefined; responseType?: \"arraybuffer\" | \"blob\" | \"text\" | \"json\" | undefined; withCredentials?: boolean | undefined; } | undefined",
|
||||
"isOptional": true,
|
||||
"isRestParam": false
|
||||
}
|
||||
],
|
||||
"returnType": "foobar",
|
||||
"description": "",
|
||||
"entryType": "function",
|
||||
"jsdocTags": [],
|
||||
"rawComment": "",
|
||||
"generics": [],
|
||||
"isNewType": false
|
||||
},
|
||||
{
|
||||
"name": "constructor",
|
||||
"params": [
|
||||
{
|
||||
"name": "method",
|
||||
"description": "",
|
||||
"type": "\"POST\"",
|
||||
"isOptional": false,
|
||||
"isRestParam": false
|
||||
},
|
||||
{
|
||||
"name": "url",
|
||||
"description": "",
|
||||
"type": "string",
|
||||
"isOptional": false,
|
||||
"isRestParam": false
|
||||
},
|
||||
{
|
||||
"name": "body",
|
||||
"description": "",
|
||||
"type": "T | null",
|
||||
"isOptional": false,
|
||||
"isRestParam": false
|
||||
},
|
||||
{
|
||||
"name": "init",
|
||||
"description": "",
|
||||
"type": "{ headers?: HttpHeaders | undefined; context?: HttpContext | undefined; reportProgress?: boolean | undefined; params?: HttpParams | undefined; responseType?: \"arraybuffer\" | \"blob\" | \"text\" | \"json\" | undefined; withCredentials?: boolean | undefined; transferCache?: boolean | { includeHeaders?: string[] | undefined; } | undefined; } | undefined",
|
||||
"isOptional": true,
|
||||
"isRestParam": false
|
||||
}
|
||||
],
|
||||
"returnType": "foobar",
|
||||
"description": "",
|
||||
"entryType": "function",
|
||||
"jsdocTags": [],
|
||||
"rawComment": "",
|
||||
"generics": [],
|
||||
"isNewType": false
|
||||
},
|
||||
{
|
||||
"name": "constructor",
|
||||
"params": [
|
||||
{
|
||||
"name": "method",
|
||||
"description": "",
|
||||
"type": "\"PUT\" | \"PATCH\"",
|
||||
"isOptional": false,
|
||||
"isRestParam": false
|
||||
},
|
||||
{
|
||||
"name": "url",
|
||||
"description": "",
|
||||
"type": "string",
|
||||
"isOptional": false,
|
||||
"isRestParam": false
|
||||
},
|
||||
{
|
||||
"name": "body",
|
||||
"description": "",
|
||||
"type": "T | null",
|
||||
"isOptional": false,
|
||||
"isRestParam": false
|
||||
},
|
||||
{
|
||||
"name": "init",
|
||||
"description": "",
|
||||
"type": "{ headers?: HttpHeaders | undefined; context?: HttpContext | undefined; reportProgress?: boolean | undefined; params?: HttpParams | undefined; responseType?: \"arraybuffer\" | \"blob\" | \"text\" | \"json\" | undefined; withCredentials?: boolean | undefined; } | undefined",
|
||||
"isOptional": true,
|
||||
"isRestParam": false
|
||||
}
|
||||
],
|
||||
"returnType": "foobar",
|
||||
"description": "",
|
||||
"entryType": "function",
|
||||
"jsdocTags": [],
|
||||
"rawComment": "",
|
||||
"generics": [],
|
||||
"isNewType": false
|
||||
},
|
||||
{
|
||||
"name": "constructor",
|
||||
"params": [
|
||||
{
|
||||
"name": "method",
|
||||
"description": "",
|
||||
"type": "string",
|
||||
"isOptional": false,
|
||||
"isRestParam": false
|
||||
},
|
||||
{
|
||||
"name": "url",
|
||||
"description": "",
|
||||
"type": "string",
|
||||
"isOptional": false,
|
||||
"isRestParam": false
|
||||
},
|
||||
{
|
||||
"name": "body",
|
||||
"description": "",
|
||||
"type": "T | null",
|
||||
"isOptional": false,
|
||||
"isRestParam": false
|
||||
},
|
||||
{
|
||||
"name": "init",
|
||||
"description": "",
|
||||
"type": "{ headers?: HttpHeaders | undefined; context?: HttpContext | undefined; reportProgress?: boolean | undefined; params?: HttpParams | undefined; responseType?: \"arraybuffer\" | \"blob\" | \"text\" | \"json\" | undefined; withCredentials?: boolean | undefined; transferCache?: boolean | { includeHeaders?: string[] | undefined; } | undefined; } | undefined",
|
||||
"isOptional": true,
|
||||
"isRestParam": false
|
||||
}
|
||||
],
|
||||
"returnType": "foobar",
|
||||
"description": "",
|
||||
"entryType": "function",
|
||||
"jsdocTags": [],
|
||||
"rawComment": "",
|
||||
"generics": [],
|
||||
"isNewType": false
|
||||
},
|
||||
{
|
||||
"name": "constructor",
|
||||
"params": [
|
||||
{
|
||||
"name": "method",
|
||||
"description": "",
|
||||
"type": "string",
|
||||
"isOptional": false,
|
||||
"isRestParam": false
|
||||
},
|
||||
{
|
||||
"name": "url",
|
||||
"description": "",
|
||||
"type": "string",
|
||||
"isOptional": false,
|
||||
"isRestParam": false
|
||||
},
|
||||
{
|
||||
"name": "third",
|
||||
"description": "",
|
||||
"type": "T | { headers?: HttpHeaders | undefined; context?: HttpContext | undefined; reportProgress?: boolean | undefined; params?: HttpParams | undefined; responseType?: \"arraybuffer\" | \"blob\" | \"text\" | \"json\" | undefined; withCredentials?: boolean | undefined; transferCache?: boolean | { includeHeaders?: string[] | undefined; } | undefined; } | null | undefined",
|
||||
"isOptional": true,
|
||||
"isRestParam": false
|
||||
},
|
||||
{
|
||||
"name": "fourth",
|
||||
"description": "",
|
||||
"type": "{ headers?: HttpHeaders | undefined; context?: HttpContext | undefined; reportProgress?: boolean | undefined; params?: HttpParams | undefined; responseType?: \"arraybuffer\" | \"blob\" | \"text\" | \"json\" | undefined; withCredentials?: boolean | undefined; transferCache?: boolean | { includeHeaders?: string[] | undefined; } | undefined; } | undefined",
|
||||
"isOptional": true,
|
||||
"isRestParam": false
|
||||
}
|
||||
],
|
||||
"returnType": "foobar",
|
||||
"description": "",
|
||||
"entryType": "function",
|
||||
"jsdocTags": [],
|
||||
"rawComment": "",
|
||||
"generics": [],
|
||||
"isNewType": false
|
||||
}
|
||||
],
|
||||
"implementation": {
|
||||
"params": [
|
||||
{
|
||||
"name": "method",
|
||||
"description": "",
|
||||
"type": "string",
|
||||
"isOptional": false,
|
||||
"isRestParam": false
|
||||
},
|
||||
{
|
||||
"name": "url",
|
||||
"description": "",
|
||||
"type": "string",
|
||||
"isOptional": false,
|
||||
"isRestParam": false
|
||||
},
|
||||
{
|
||||
"name": "third",
|
||||
"description": "",
|
||||
"type": "T | { headers?: HttpHeaders | undefined; context?: HttpContext | undefined; reportProgress?: boolean | undefined; params?: HttpParams | undefined; responseType?: \"arraybuffer\" | \"blob\" | \"text\" | \"json\" | undefined; withCredentials?: boolean | undefined; transferCache?: boolean | { includeHeaders?: string[] | undefined; } | undefined; } | null | undefined",
|
||||
"isOptional": true,
|
||||
"isRestParam": false
|
||||
},
|
||||
{
|
||||
"name": "fourth",
|
||||
"description": "",
|
||||
"type": "{ headers?: HttpHeaders | undefined; context?: HttpContext | undefined; reportProgress?: boolean | undefined; params?: HttpParams | undefined; responseType?: \"arraybuffer\" | \"blob\" | \"text\" | \"json\" | undefined; withCredentials?: boolean | undefined; transferCache?: boolean | { includeHeaders?: string[] | undefined; } | undefined; } | undefined",
|
||||
"isOptional": true,
|
||||
"isRestParam": false
|
||||
}
|
||||
],
|
||||
"isNewType": false,
|
||||
"returnType": "HttpRequest<T>",
|
||||
"generics": [],
|
||||
"name": "constructor",
|
||||
"description": "",
|
||||
"entryType": "function",
|
||||
"jsdocTags": [],
|
||||
"rawComment": ""
|
||||
},
|
||||
"entryType": "function",
|
||||
"description": "",
|
||||
"jsdocTags": [],
|
||||
"rawComment": "",
|
||||
"memberType": "method",
|
||||
"memberTags": []
|
||||
},
|
||||
{
|
||||
"name": "body",
|
||||
"type": "T | null",
|
||||
"memberType": "property",
|
||||
"memberTags": ["readonly"],
|
||||
"description": "The request body, or `null` if one isn't set.\n\nBodies are not enforced to be immutable, as they can include a reference to any\nuser-defined data type. However, interceptors should take care to preserve\nidempotence by treating them as such.",
|
||||
"jsdocTags": []
|
||||
},
|
||||
{
|
||||
"name": "headers",
|
||||
"type": "HttpHeaders",
|
||||
"memberType": "property",
|
||||
"memberTags": ["readonly"],
|
||||
"description": "Outgoing headers for this request.",
|
||||
"jsdocTags": []
|
||||
},
|
||||
{
|
||||
"name": "context",
|
||||
"type": "HttpContext",
|
||||
"memberType": "property",
|
||||
"memberTags": ["readonly"],
|
||||
"description": "Shared and mutable context that can be used by interceptors",
|
||||
"jsdocTags": []
|
||||
},
|
||||
{
|
||||
"name": "reportProgress",
|
||||
"type": "boolean",
|
||||
"memberType": "property",
|
||||
"memberTags": ["readonly"],
|
||||
"description": "Whether this request should be made in a way that exposes progress events.\n\nProgress events are expensive (change detection runs on each event) and so\nthey should only be requested if the consumer intends to monitor them.\n\nNote: The `FetchBackend` doesn't support progress report on uploads.",
|
||||
"jsdocTags": []
|
||||
},
|
||||
{
|
||||
"name": "withCredentials",
|
||||
"type": "boolean",
|
||||
"memberType": "property",
|
||||
"memberTags": ["readonly"],
|
||||
"description": "Whether this request should be sent with outgoing credentials (cookies).",
|
||||
"jsdocTags": []
|
||||
},
|
||||
{
|
||||
"name": "responseType",
|
||||
"type": "\"arraybuffer\" | \"blob\" | \"text\" | \"json\"",
|
||||
"memberType": "property",
|
||||
"memberTags": ["readonly"],
|
||||
"description": "The expected response type of the server.\n\nThis is used to parse the response appropriately before returning it to\nthe requestee.",
|
||||
"jsdocTags": []
|
||||
},
|
||||
{
|
||||
"name": "method",
|
||||
"type": "string",
|
||||
"memberType": "property",
|
||||
"memberTags": ["readonly"],
|
||||
"description": "The outgoing HTTP request method.",
|
||||
"jsdocTags": []
|
||||
},
|
||||
{
|
||||
"name": "params",
|
||||
"type": "HttpParams",
|
||||
"memberType": "property",
|
||||
"memberTags": ["readonly"],
|
||||
"description": "Outgoing URL parameters.\n\nTo pass a string representation of HTTP parameters in the URL-query-string format,\nthe `HttpParamsOptions`' `fromString` may be used. For example:\n\n```ts\nnew HttpParams({fromString: 'angular=awesome'})\n```",
|
||||
"jsdocTags": []
|
||||
},
|
||||
{
|
||||
"name": "urlWithParams",
|
||||
"type": "string",
|
||||
"memberType": "property",
|
||||
"memberTags": ["readonly"],
|
||||
"description": "The outgoing URL with all URL parameters set.",
|
||||
"jsdocTags": []
|
||||
},
|
||||
{
|
||||
"name": "transferCache",
|
||||
"type": "boolean | { includeHeaders?: string[] | undefined; } | undefined",
|
||||
"memberType": "property",
|
||||
"memberTags": ["readonly", "optional"],
|
||||
"description": "The HttpTransferCache option for the request",
|
||||
"jsdocTags": []
|
||||
},
|
||||
{
|
||||
"name": "serializeBody",
|
||||
"signatures": [
|
||||
{
|
||||
"name": "serializeBody",
|
||||
"entryType": "function",
|
||||
"description": "Transform the free-form body into a serialized format suitable for\ntransmission to the server.",
|
||||
"generics": [],
|
||||
"isNewType": false,
|
||||
"jsdocTags": [],
|
||||
"params": [],
|
||||
"rawComment": "/**\n * Transform the free-form body into a serialized format suitable for\n * transmission to the server.\n */",
|
||||
"returnType": "string | ArrayBuffer | Blob | FormData | URLSearchParams | null"
|
||||
}
|
||||
],
|
||||
"implementation": {
|
||||
"params": [],
|
||||
"isNewType": false,
|
||||
"returnType": "string | ArrayBuffer | Blob | FormData | URLSearchParams | null",
|
||||
"generics": [],
|
||||
"name": "serializeBody",
|
||||
"description": "Transform the free-form body into a serialized format suitable for\ntransmission to the server.",
|
||||
"entryType": "function",
|
||||
"jsdocTags": [],
|
||||
"rawComment": "/**\n * Transform the free-form body into a serialized format suitable for\n * transmission to the server.\n */"
|
||||
},
|
||||
"entryType": "function",
|
||||
"description": "Transform the free-form body into a serialized format suitable for\ntransmission to the server.",
|
||||
"jsdocTags": [],
|
||||
"rawComment": "/**\n * Transform the free-form body into a serialized format suitable for\n * transmission to the server.\n */",
|
||||
"memberType": "method",
|
||||
"memberTags": []
|
||||
},
|
||||
{
|
||||
"name": "detectContentTypeHeader",
|
||||
"signatures": [
|
||||
{
|
||||
"name": "detectContentTypeHeader",
|
||||
"entryType": "function",
|
||||
"description": "Examine the body and attempt to infer an appropriate MIME type\nfor it.\n\nIf no such type can be inferred, this method will return `null`.",
|
||||
"generics": [],
|
||||
"isNewType": false,
|
||||
"jsdocTags": [],
|
||||
"params": [],
|
||||
"rawComment": "/**\n * Examine the body and attempt to infer an appropriate MIME type\n * for it.\n *\n * If no such type can be inferred, this method will return `null`.\n */",
|
||||
"returnType": "string | null"
|
||||
}
|
||||
],
|
||||
"implementation": {
|
||||
"params": [],
|
||||
"isNewType": false,
|
||||
"returnType": "string | null",
|
||||
"generics": [],
|
||||
"name": "detectContentTypeHeader",
|
||||
"description": "Examine the body and attempt to infer an appropriate MIME type\nfor it.\n\nIf no such type can be inferred, this method will return `null`.",
|
||||
"entryType": "function",
|
||||
"jsdocTags": [],
|
||||
"rawComment": "/**\n * Examine the body and attempt to infer an appropriate MIME type\n * for it.\n *\n * If no such type can be inferred, this method will return `null`.\n */"
|
||||
},
|
||||
"entryType": "function",
|
||||
"description": "Examine the body and attempt to infer an appropriate MIME type\nfor it.\n\nIf no such type can be inferred, this method will return `null`.",
|
||||
"jsdocTags": [],
|
||||
"rawComment": "/**\n * Examine the body and attempt to infer an appropriate MIME type\n * for it.\n *\n * If no such type can be inferred, this method will return `null`.\n */",
|
||||
"memberType": "method",
|
||||
"memberTags": []
|
||||
},
|
||||
{
|
||||
"name": "clone",
|
||||
"signatures": [
|
||||
{
|
||||
"name": "clone",
|
||||
"entryType": "function",
|
||||
"description": "",
|
||||
"generics": [],
|
||||
"isNewType": false,
|
||||
"jsdocTags": [],
|
||||
"params": [],
|
||||
"rawComment": "",
|
||||
"returnType": "HttpRequest<T>"
|
||||
},
|
||||
{
|
||||
"name": "clone",
|
||||
"entryType": "function",
|
||||
"description": "",
|
||||
"generics": [],
|
||||
"isNewType": false,
|
||||
"jsdocTags": [],
|
||||
"params": [
|
||||
{
|
||||
"name": "update",
|
||||
"description": "",
|
||||
"type": "{ headers?: HttpHeaders | undefined; context?: HttpContext | undefined; reportProgress?: boolean | undefined; params?: HttpParams | undefined; responseType?: \"arraybuffer\" | \"blob\" | \"text\" | \"json\" | undefined; withCredentials?: boolean | undefined; transferCache?: boolean | { includeHeaders?: string[] | undefined; } | undefined; body?: T | null | undefined; method?: string | undefined; url?: string | undefined; setHeaders?: { [name: string]: string | string[]; } | undefined; setParams?: { [param: string]: string; } | undefined; }",
|
||||
"isOptional": false,
|
||||
"isRestParam": false
|
||||
}
|
||||
],
|
||||
"rawComment": "",
|
||||
"returnType": "HttpRequest<T>"
|
||||
},
|
||||
{
|
||||
"name": "clone",
|
||||
"entryType": "function",
|
||||
"description": "",
|
||||
"generics": [{"name": "V"}],
|
||||
"isNewType": false,
|
||||
"jsdocTags": [],
|
||||
"params": [
|
||||
{
|
||||
"name": "update",
|
||||
"description": "",
|
||||
"type": "{ headers?: HttpHeaders | undefined; context?: HttpContext | undefined; reportProgress?: boolean | undefined; params?: HttpParams | undefined; responseType?: \"arraybuffer\" | \"blob\" | \"text\" | \"json\" | undefined; withCredentials?: boolean | undefined; transferCache?: boolean | { includeHeaders?: string[] | undefined; } | undefined; body?: V | null | undefined; method?: string | undefined; url?: string | undefined; setHeaders?: { [name: string]: string | string[]; } | undefined; setParams?: { [param: string]: string; } | undefined; }",
|
||||
"isOptional": false,
|
||||
"isRestParam": false
|
||||
}
|
||||
],
|
||||
"rawComment": "",
|
||||
"returnType": "HttpRequest<V>"
|
||||
}
|
||||
],
|
||||
"implementation": {
|
||||
"params": [
|
||||
{
|
||||
"name": "update",
|
||||
"description": "",
|
||||
"type": "{ headers?: HttpHeaders | undefined; context?: HttpContext | undefined; reportProgress?: boolean | undefined; params?: HttpParams | undefined; responseType?: \"arraybuffer\" | \"blob\" | \"text\" | \"json\" | undefined; withCredentials?: boolean | undefined; transferCache?: boolean | { includeHeaders?: string[] | undefined; } | undefined; body?: any; method?: string | undefined; url?: string | undefined; setHeaders?: { [name: string]: string | string[]; } | undefined; setParams?: { [param: string]: string; } | undefined; }",
|
||||
"isOptional": true,
|
||||
"isRestParam": false
|
||||
}
|
||||
],
|
||||
"isNewType": false,
|
||||
"returnType": "HttpRequest<any>",
|
||||
"generics": [],
|
||||
"name": "clone",
|
||||
"description": "",
|
||||
"entryType": "function",
|
||||
"jsdocTags": [],
|
||||
"rawComment": ""
|
||||
},
|
||||
"entryType": "function",
|
||||
"description": "",
|
||||
"jsdocTags": [],
|
||||
"rawComment": "",
|
||||
"memberType": "method",
|
||||
"memberTags": []
|
||||
}
|
||||
],
|
||||
"generics": [{"name": "T"}],
|
||||
"description": "An outgoing HTTP request with an optional typed body.\n\n`HttpRequest` represents an outgoing request, including URL, method,\nheaders, body, and other request configuration options. Instances should be\nassumed to be immutable. To modify a `HttpRequest`, the `clone`\nmethod should be used.",
|
||||
"jsdocTags": [{"name": "publicApi", "comment": ""}],
|
||||
"rawComment": "/**\n * An outgoing HTTP request with an optional typed body.\n *\n * `HttpRequest` represents an outgoing request, including URL, method,\n * headers, body, and other request configuration options. Instances should be\n * assumed to be immutable. To modify a `HttpRequest`, the `clone`\n * method should be used.\n *\n * @publicApi\n */",
|
||||
"implements": [],
|
||||
"source": {
|
||||
"filePath": "/packages/common/http/src/request.ts",
|
||||
"startLine": 131,
|
||||
"endLine": 578
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -34,6 +34,13 @@ describe('markdown to html', () => {
|
||||
const symbols = new Map<string, string>([
|
||||
['AfterRenderPhase', 'core'],
|
||||
['afterRender', 'core'],
|
||||
['EmbeddedViewRef', 'core'],
|
||||
['ChangeDetectionStrategy', 'core'],
|
||||
['ChangeDetectorRef', 'core'],
|
||||
['withNoHttpTransferCache', 'platform-browser'],
|
||||
['withHttpTransferCacheOptions', 'platform-browser'],
|
||||
['withI18nSupport', 'platform-browser'],
|
||||
['withEventReplay', 'platform-browser'],
|
||||
]);
|
||||
setSymbols(symbols);
|
||||
for (const entry of entryJson.entries) {
|
||||
|
||||
@@ -12,6 +12,7 @@ import {getRenderable} from '../processing';
|
||||
import {DocEntryRenderable} from '../entities/renderables';
|
||||
import {initHighlighter} from '../shiki/shiki';
|
||||
import {configureMarkedGlobally} from '../marked/configuration';
|
||||
import {setSymbols} from '../symbol-context';
|
||||
|
||||
// Note: The tests will probably break if the schema of the api extraction changes.
|
||||
// All entries in the fake-entries are extracted from Angular's api.
|
||||
@@ -28,6 +29,19 @@ describe('renderable', () => {
|
||||
encoding: 'utf-8',
|
||||
});
|
||||
const entryJson = JSON.parse(entryContent) as any;
|
||||
const symbols = new Map<string, string>([
|
||||
['AfterRenderPhase', 'core'],
|
||||
['afterRender', 'core'],
|
||||
['EmbeddedViewRef', 'core'],
|
||||
['ChangeDetectionStrategy', 'core'],
|
||||
['ChangeDetectorRef', 'core'],
|
||||
['withNoHttpTransferCache', 'platform-browser'],
|
||||
['withHttpTransferCacheOptions', 'platform-browser'],
|
||||
['withI18nSupport', 'platform-browser'],
|
||||
['withEventReplay', 'platform-browser'],
|
||||
]);
|
||||
setSymbols(symbols);
|
||||
|
||||
for (const entry of entryJson.entries) {
|
||||
const renderableJson = getRenderable(entry, '@angular/fakeentry') as DocEntryRenderable;
|
||||
entries.set(entry['name'], renderableJson);
|
||||
|
||||
@@ -66,6 +66,14 @@ describe('jsdoc transforms', () => {
|
||||
name: 'see',
|
||||
comment: '{@link https://angular.dev}',
|
||||
},
|
||||
{
|
||||
name: 'see',
|
||||
comment: '{@link /cli/build ng build}',
|
||||
},
|
||||
{
|
||||
name: 'see',
|
||||
comment: '{@link /ecosystem/rxjs-interop/output-interop Output Interop}',
|
||||
},
|
||||
],
|
||||
moduleName: 'test',
|
||||
});
|
||||
@@ -117,5 +125,30 @@ describe('jsdoc transforms', () => {
|
||||
label: 'angular.dev',
|
||||
url: 'https://angular.dev',
|
||||
});
|
||||
|
||||
expect(entry.additionalLinks[10]).toEqual({
|
||||
label: 'ng build',
|
||||
url: '/cli/build',
|
||||
});
|
||||
|
||||
expect(entry.additionalLinks[11]).toEqual({
|
||||
label: 'Output Interop',
|
||||
url: '/ecosystem/rxjs-interop/output-interop',
|
||||
});
|
||||
});
|
||||
|
||||
it('should throw on invalid relatie @link', () => {
|
||||
const entryFn = () =>
|
||||
addHtmlAdditionalLinks({
|
||||
jsdocTags: [
|
||||
{
|
||||
name: 'see',
|
||||
comment: '{@link cli/build ng build}',
|
||||
},
|
||||
],
|
||||
moduleName: 'test',
|
||||
});
|
||||
|
||||
expect(entryFn).toThrowError(/Forbidden relative link: cli\/build ng build/);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -476,10 +476,11 @@ function appendPrefixAndSuffix(entry: DocEntry, codeTocData: CodeTableOfContents
|
||||
*/
|
||||
export function addApiLinksToHtml(htmlString: string): string {
|
||||
const result = htmlString.replace(
|
||||
// This regex looks for span/code blocks not wrapped by an anchor block.
|
||||
// This regex looks for span/code blocks not wrapped by an anchor block
|
||||
// or the tag doesn't contain `data-skip-anchor` attribute.
|
||||
// Their content are then replaced with a link if the symbol is known
|
||||
// The captured content ==> vvvvvvvv
|
||||
/(?<!<a[^>]*>)(<(?:(?:span)|(?:code))[^>]*>\s*)([^<]*?)(\s*<\/(?:span|code)>)/g,
|
||||
// The captured content ==> vvvvvvvv
|
||||
/(?<!<a[^>]*>)(<(?:(?:span)|(?:code))(?!\sdata-skip-anchor)[^>]*>\s*)([^<]*?)(\s*<\/(?:span|code)>)/g,
|
||||
(type: string, span1: string, potentialSymbolName: string, span2: string) => {
|
||||
let [symbol, subSymbol] = potentialSymbolName.split(/(?:#|\.)/) as [string, string?];
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@ import {
|
||||
|
||||
import {getLinkToModule} from './url-transforms';
|
||||
import {addApiLinksToHtml} from './code-transforms';
|
||||
import {getCurrentSymbol, getModuleName, logUnknownSymbol} from '../symbol-context';
|
||||
import {getCurrentSymbol, getModuleName, unknownSymbolMessage} from '../symbol-context';
|
||||
|
||||
export const JS_DOC_REMARKS_TAG = 'remarks';
|
||||
export const JS_DOC_USAGE_NOTES_TAG = 'usageNotes';
|
||||
@@ -58,16 +58,10 @@ export function addHtmlDescription<T extends HasDescription & HasModuleName>(
|
||||
|
||||
const description = !!entry.description ? entry.description : jsDocDescription;
|
||||
const shortTextMatch = description.match(firstParagraphRule);
|
||||
const htmlDescription = getHtmlForJsDocText(description, entry).trim();
|
||||
const shortHtmlDescription = getHtmlForJsDocText(
|
||||
shortTextMatch ? shortTextMatch[0] : '',
|
||||
entry,
|
||||
).trim();
|
||||
return {
|
||||
...entry,
|
||||
htmlDescription,
|
||||
shortHtmlDescription,
|
||||
};
|
||||
const htmlDescription = getHtmlForJsDocText(description).trim();
|
||||
const shortHtmlDescription = getHtmlForJsDocText(shortTextMatch ? shortTextMatch[0] : '').trim();
|
||||
|
||||
return {...entry, htmlDescription, shortHtmlDescription};
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -81,7 +75,7 @@ export function addHtmlJsDocTagComments<T extends HasJsDocTags & HasModuleName>(
|
||||
...entry,
|
||||
jsdocTags: entry.jsdocTags.map((tag) => ({
|
||||
...tag,
|
||||
htmlComment: getHtmlForJsDocText(tag.comment, entry),
|
||||
htmlComment: getHtmlForJsDocText(tag.comment),
|
||||
})),
|
||||
};
|
||||
}
|
||||
@@ -100,20 +94,16 @@ export function addHtmlUsageNotes<T extends HasJsDocTags>(entry: T): T & HasHtml
|
||||
const usageNotesTag = entry.jsdocTags.find(
|
||||
({name}) => name === JS_DOC_USAGE_NOTES_TAG || name === JS_DOC_REMARKS_TAG,
|
||||
);
|
||||
const htmlUsageNotes = usageNotesTag
|
||||
? (marked.parse(wrapExampleHtmlElementsWithCode(usageNotesTag.comment)) as string)
|
||||
: '';
|
||||
|
||||
const transformedHtml = addApiLinksToHtml(htmlUsageNotes);
|
||||
const htmlUsageNotes = usageNotesTag ? getHtmlForJsDocText(usageNotesTag.comment) : '';
|
||||
|
||||
return {
|
||||
...entry,
|
||||
htmlUsageNotes: transformedHtml,
|
||||
htmlUsageNotes,
|
||||
};
|
||||
}
|
||||
|
||||
/** Given a markdown JsDoc text, gets the rendered HTML. */
|
||||
function getHtmlForJsDocText<T extends HasModuleName>(text: string, entry: T): string {
|
||||
function getHtmlForJsDocText(text: string): string {
|
||||
const parsed = marked.parse(convertLinks(wrapExampleHtmlElementsWithCode(text))) as string;
|
||||
return addApiLinksToHtml(parsed);
|
||||
}
|
||||
@@ -126,7 +116,7 @@ export function setEntryFlags<T extends HasJsDocTags & HasModuleName>(
|
||||
...entry,
|
||||
isDeprecated: isDeprecatedEntry(entry),
|
||||
deprecationMessage: deprecationMessage
|
||||
? getHtmlForJsDocText(deprecationMessage, entry)
|
||||
? getHtmlForJsDocText(deprecationMessage)
|
||||
: deprecationMessage,
|
||||
isDeveloperPreview: isDeveloperPreview(entry),
|
||||
isExperimental: isExperimental(entry),
|
||||
@@ -195,10 +185,16 @@ function parseAtLink(link: string): {label: string; url: string} {
|
||||
let [rawSymbol, description] = link.split(/\s(.+)/);
|
||||
if (rawSymbol.startsWith('#')) {
|
||||
rawSymbol = rawSymbol.substring(1);
|
||||
} else if (rawSymbol.startsWith('http://') || rawSymbol.startsWith('https://')) {
|
||||
} else if (rawSymbol.includes('/')) {
|
||||
if (!rawSymbol.startsWith('/') && !rawSymbol.startsWith('http')) {
|
||||
throw Error(
|
||||
`Forbidden relative link: ${link}. Links should be absolute and start with a slash`,
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
url: rawSymbol,
|
||||
label: rawSymbol.split('/').pop()!,
|
||||
label: description ?? rawSymbol.split('/').pop()!,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -214,11 +210,9 @@ function parseAtLink(link: string): {label: string; url: string} {
|
||||
moduleName = getModuleName(`${currentSymbol}.${symbol}`);
|
||||
|
||||
if (!moduleName || !currentSymbol) {
|
||||
// TODO: remove the links that generate this error
|
||||
// TODO: throw an error when there are no more warning generated
|
||||
logUnknownSymbol(link, symbol);
|
||||
return {label, url: '#'};
|
||||
throw unknownSymbolMessage(link, symbol);
|
||||
}
|
||||
|
||||
subSymbol = symbol;
|
||||
symbol = currentSymbol;
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
"prefix": "app",
|
||||
"architect": {
|
||||
"build": {
|
||||
"builder": "@angular-devkit/build-angular:application",
|
||||
"builder": "@angular/build:application",
|
||||
"options": {
|
||||
"outputPath": "dist/example-app",
|
||||
"index": "src/index.html",
|
||||
@@ -66,7 +66,7 @@
|
||||
"defaultConfiguration": "production"
|
||||
},
|
||||
"serve": {
|
||||
"builder": "@angular-devkit/build-angular:dev-server",
|
||||
"builder": "@angular/build:dev-server",
|
||||
"configurations": {
|
||||
"production": {
|
||||
"browserTarget": "example-app:build:production"
|
||||
@@ -78,13 +78,13 @@
|
||||
"defaultConfiguration": "development"
|
||||
},
|
||||
"extract-i18n": {
|
||||
"builder": "@angular-devkit/build-angular:extract-i18n",
|
||||
"builder": "@angular/build:extract-i18n",
|
||||
"options": {
|
||||
"browserTarget": "example-app:build"
|
||||
}
|
||||
},
|
||||
"test": {
|
||||
"builder": "@angular-devkit/build-angular:karma",
|
||||
"builder": "@angular/build:karma",
|
||||
"options": {
|
||||
"main": "src/test.ts",
|
||||
"polyfills": ["zone.js", "zone.js/testing"],
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
"zone.js": "~0.13.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@angular-devkit/build-angular": "^19.0.0",
|
||||
"@angular/build": "^19.0.0",
|
||||
"@angular/cli": "^19.0.0",
|
||||
"@angular/compiler-cli": "^19.0.0",
|
||||
"@types/jasmine": "~3.10.0",
|
||||
|
||||
@@ -39,7 +39,7 @@
|
||||
"prefix": "app",
|
||||
"architect": {
|
||||
"build": {
|
||||
"builder": "@angular-devkit/build-angular:application",
|
||||
"builder": "@angular/build:application",
|
||||
"options": {
|
||||
"outputPath": "dist/first-app",
|
||||
"index": "src/index.html",
|
||||
@@ -76,7 +76,7 @@
|
||||
"defaultConfiguration": "production"
|
||||
},
|
||||
"serve": {
|
||||
"builder": "@angular-devkit/build-angular:dev-server",
|
||||
"builder": "@angular/build:dev-server",
|
||||
"configurations": {
|
||||
"production": {
|
||||
"buildTarget": "first-app:build:production"
|
||||
@@ -88,7 +88,7 @@
|
||||
"defaultConfiguration": "development"
|
||||
},
|
||||
"extract-i18n": {
|
||||
"builder": "@angular-devkit/build-angular:extract-i18n",
|
||||
"builder": "@angular/build:extract-i18n",
|
||||
"options": {
|
||||
"buildTarget": "first-app:build"
|
||||
}
|
||||
|
||||
+3821
-9261
File diff suppressed because it is too large
Load Diff
@@ -3,7 +3,7 @@
|
||||
"version": "0.0.0",
|
||||
"scripts": {
|
||||
"ng": "ng",
|
||||
"start": "NG_BUILD_PARALLEL_TS=0 ng serve",
|
||||
"start": "ng serve",
|
||||
"build": "ng build",
|
||||
"watch": "ng build --watch --configuration development"
|
||||
},
|
||||
@@ -15,12 +15,12 @@
|
||||
"@angular/platform-browser": "^19.0.0",
|
||||
"rxjs": "~7.8.0",
|
||||
"tslib": "^2.3.0",
|
||||
"zone.js": "~0.14.0"
|
||||
"zone.js": "~0.15.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@angular-devkit/build-angular": "^19.0.0",
|
||||
"@angular/build": "^19.0.0",
|
||||
"@angular/cli": "^19.0.0",
|
||||
"@angular/compiler-cli": "^19.0.0",
|
||||
"typescript": "~5.6.3"
|
||||
"typescript": "~5.7.3"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,7 +25,6 @@ ng_module(
|
||||
],
|
||||
),
|
||||
deps = [
|
||||
"//adev/shared-docs/constants",
|
||||
"//adev/shared-docs/interfaces",
|
||||
"//adev/shared-docs/providers",
|
||||
"//adev/shared-docs/utils",
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
* found in the LICENSE file at https://angular.dev/license
|
||||
*/
|
||||
|
||||
import {Injectable, inject, signal} from '@angular/core';
|
||||
import {Injectable, inject, linkedSignal, signal} from '@angular/core';
|
||||
import {NavigationItem} from '../interfaces/index';
|
||||
import {Router} from '@angular/router';
|
||||
|
||||
@@ -19,11 +19,13 @@ export class NavigationState {
|
||||
private readonly _activeNavigationItem = signal<NavigationItem | null>(null);
|
||||
private readonly _expandedItems = signal<NavigationItem[]>([]);
|
||||
private readonly _isMobileNavVisible = signal<boolean>(false);
|
||||
private readonly _level = linkedSignal(() => this._expandedItems().length);
|
||||
|
||||
primaryActiveRouteItem = signal<string | null>(null);
|
||||
activeNavigationItem = this._activeNavigationItem.asReadonly();
|
||||
expandedItems = this._expandedItems.asReadonly();
|
||||
isMobileNavVisible = this._isMobileNavVisible.asReadonly();
|
||||
level = this._level.asReadonly();
|
||||
|
||||
async toggleItem(item: NavigationItem): Promise<void> {
|
||||
if (!item.children) {
|
||||
@@ -59,7 +61,7 @@ export class NavigationState {
|
||||
return;
|
||||
}
|
||||
// Returns item when parent node was already expanded
|
||||
const parentItem = this._expandedItems().find(
|
||||
const parentItem = this.actualExpandedItems().find(
|
||||
(expandedItem) =>
|
||||
item.parent?.label === expandedItem.label && item.parent?.path === expandedItem.path,
|
||||
);
|
||||
@@ -100,13 +102,25 @@ export class NavigationState {
|
||||
private expand(item: NavigationItem): void {
|
||||
// Add item to the expanded items list
|
||||
this._expandedItems.update((expandedItems) => {
|
||||
return [...(expandedItems ?? []), {...item, isExpanded: true}];
|
||||
return [...(this.actualExpandedItems() ?? []), {...item, isExpanded: true}];
|
||||
});
|
||||
|
||||
// No need to update the level here, this is handled by linkedSignal already
|
||||
}
|
||||
|
||||
private collapse(item: NavigationItem): void {
|
||||
item.isExpanded = false;
|
||||
this._expandedItems.update((expandedItems) => expandedItems.slice(0, -1));
|
||||
|
||||
// We won't remove the item, just update the level,
|
||||
// this allows animation on the items to hide them without destroying them
|
||||
this._level.set(this.actualExpandedItems().length - 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* return the actual navigation items, that is to say the one that match the current level
|
||||
*/
|
||||
private actualExpandedItems() {
|
||||
return this.expandedItems().slice(0, this.level());
|
||||
}
|
||||
|
||||
private async navigateToFirstPageOfTheCategory(path: string): Promise<boolean> {
|
||||
|
||||
@@ -6,10 +6,11 @@
|
||||
* found in the LICENSE file at https://angular.dev/license
|
||||
*/
|
||||
|
||||
import {DOCUMENT, isPlatformBrowser} from '@angular/common';
|
||||
import {inject, signal, Injectable, PLATFORM_ID} from '@angular/core';
|
||||
import {DOCUMENT} from '@angular/common';
|
||||
import {inject, signal, Injectable, PLATFORM_ID, DestroyRef} from '@angular/core';
|
||||
|
||||
import {TableOfContentsItem, TableOfContentsLevel} from '../interfaces/index';
|
||||
import {on} from 'events';
|
||||
|
||||
/**
|
||||
* Name of an attribute that is set on an element that should be
|
||||
@@ -29,38 +30,19 @@ export class TableOfContentsLoader {
|
||||
readonly tableOfContentItems = signal([] as TableOfContentsItem[]);
|
||||
|
||||
private readonly document = inject(DOCUMENT);
|
||||
private readonly platformId = inject(PLATFORM_ID);
|
||||
|
||||
buildTableOfContent(docElement: Element): void {
|
||||
const headings = this.getHeadings(docElement);
|
||||
|
||||
const tocList: TableOfContentsItem[] = headings.map((heading) => ({
|
||||
id: heading.id,
|
||||
level: heading.tagName.toLowerCase() as TableOfContentsLevel,
|
||||
title: this.getHeadingTitle(heading),
|
||||
top: this.calculateTop(heading),
|
||||
}));
|
||||
|
||||
this.tableOfContentItems.set(tocList);
|
||||
}
|
||||
|
||||
// Update top value of heading, it should be executed after window resize
|
||||
updateHeadingsTopValue(element: HTMLElement): void {
|
||||
const headings = this.getHeadings(element);
|
||||
const updatedTopValues = new Map<string, number>();
|
||||
|
||||
for (const heading of headings) {
|
||||
updatedTopValues.set(heading.id, this.calculateTop(heading));
|
||||
}
|
||||
|
||||
this.tableOfContentItems.update((oldItems) => {
|
||||
let newItems = [...oldItems];
|
||||
for (const item of newItems) {
|
||||
item.top = updatedTopValues.get(item.id) ?? 0;
|
||||
}
|
||||
return newItems;
|
||||
});
|
||||
}
|
||||
|
||||
private getHeadingTitle(heading: HTMLHeadingElement): string {
|
||||
const div: HTMLDivElement = this.document.createElement('div');
|
||||
div.innerHTML = heading.innerHTML;
|
||||
@@ -79,11 +61,37 @@ export class TableOfContentsLoader {
|
||||
);
|
||||
}
|
||||
|
||||
private calculateTop(heading: HTMLHeadingElement): number {
|
||||
if (!isPlatformBrowser(this.platformId)) return 0;
|
||||
return (
|
||||
Math.floor(heading.offsetTop > 0 ? heading.offsetTop : heading.getClientRects()[0]?.top) -
|
||||
this.toleranceThreshold
|
||||
);
|
||||
/**
|
||||
* The methods setups several IntersectionObservers to determine when a heading is at the top of
|
||||
* the viewport. Using an IntersectionObserver is more efficient than reading DOM position
|
||||
* as it won't trigger any reflow.
|
||||
*/
|
||||
setupIntersectionObserver(
|
||||
element: Element,
|
||||
destroyRef: DestroyRef,
|
||||
onActiveId: (id: string) => void,
|
||||
): void {
|
||||
// If we're at the top the we need the default active id to be the first heading.
|
||||
const headings = this.getHeadings(element);
|
||||
onActiveId(headings[0].id);
|
||||
|
||||
headings.forEach((heading) => {
|
||||
const ioConfiguration = {
|
||||
/**
|
||||
* This rootMargin creates a horizontal line at 5% from the top of the viewport
|
||||
* that will help trigger an intersection at that the very point.
|
||||
*/
|
||||
rootMargin: '0% 0% -95% 0%',
|
||||
/** 0 is the default */
|
||||
threshold: 0,
|
||||
};
|
||||
const observer = new IntersectionObserver((entries, o) => {
|
||||
if (entries[0].isIntersecting) {
|
||||
onActiveId(entries[0].target.id);
|
||||
}
|
||||
}, ioConfiguration);
|
||||
observer.observe(heading);
|
||||
destroyRef.onDestroy(() => observer.disconnect());
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,153 +6,32 @@
|
||||
* found in the LICENSE file at https://angular.dev/license
|
||||
*/
|
||||
|
||||
import {DOCUMENT, ViewportScroller} from '@angular/common';
|
||||
import {
|
||||
DestroyRef,
|
||||
EnvironmentInjector,
|
||||
Injectable,
|
||||
afterNextRender,
|
||||
inject,
|
||||
signal,
|
||||
} from '@angular/core';
|
||||
import {RESIZE_EVENT_DELAY} from '../constants/index';
|
||||
import {takeUntilDestroyed} from '@angular/core/rxjs-interop';
|
||||
import {auditTime, debounceTime, fromEvent, startWith} from 'rxjs';
|
||||
import {WINDOW} from '../providers/index';
|
||||
import {ViewportScroller} from '@angular/common';
|
||||
import {DestroyRef, Injectable, inject, signal} from '@angular/core';
|
||||
import {TableOfContentsLoader} from './table-of-contents-loader.service';
|
||||
|
||||
export const SCROLL_EVENT_DELAY = 20;
|
||||
|
||||
@Injectable({providedIn: 'root'})
|
||||
// The service is responsible for listening for scrolling and resizing,
|
||||
// thanks to which it sets the active item in the Table of contents
|
||||
export class TableOfContentsScrollSpy {
|
||||
private readonly tableOfContentsLoader = inject(TableOfContentsLoader);
|
||||
private readonly document = inject(DOCUMENT);
|
||||
private readonly window = inject(WINDOW);
|
||||
private readonly viewportScroller = inject(ViewportScroller);
|
||||
private readonly injector = inject(EnvironmentInjector);
|
||||
private contentSourceElement: HTMLElement | null = null;
|
||||
|
||||
private lastContentWidth = 0;
|
||||
|
||||
activeItemId = signal<string | null>(null);
|
||||
scrollbarThumbOnTop = signal<boolean>(true);
|
||||
|
||||
startListeningToScroll(
|
||||
contentSourceElement: HTMLElement | null,
|
||||
// `destroyRef` is required because the caller may invoke `startListeningToScroll`
|
||||
// multiple times. Without it, previous event listeners would not be disposed of,
|
||||
// leading to the accumulation of new event listeners.
|
||||
destroyRef: DestroyRef,
|
||||
) {
|
||||
this.contentSourceElement = contentSourceElement;
|
||||
this.lastContentWidth = this.getContentWidth();
|
||||
|
||||
this.setScrollEventHandlers(destroyRef);
|
||||
this.setResizeEventHandlers(destroyRef);
|
||||
|
||||
destroyRef.onDestroy(() => {
|
||||
// We also need to clean up the source element once the view that calls
|
||||
// `startListeningToScroll` is destroyed, as this will keep a reference
|
||||
// to an element that has been removed from the DOM.
|
||||
this.contentSourceElement = null;
|
||||
});
|
||||
}
|
||||
|
||||
scrollToTop(): void {
|
||||
this.viewportScroller.scrollToPosition([0, 0]);
|
||||
}
|
||||
|
||||
// After window resize, we should update top value of each table content item
|
||||
private setResizeEventHandlers(destroyRef: DestroyRef) {
|
||||
fromEvent(this.window, 'resize')
|
||||
.pipe(debounceTime(RESIZE_EVENT_DELAY), takeUntilDestroyed(destroyRef), startWith())
|
||||
.subscribe(() => {
|
||||
this.updateHeadingsTopAfterResize();
|
||||
});
|
||||
|
||||
// We need to observe the height of the docs-viewer because it may change after the
|
||||
// assets (fonts, images) are loaded. They can (and will) change the y-position of the headings.
|
||||
const docsViewer = this.document.querySelector('docs-viewer');
|
||||
if (docsViewer) {
|
||||
const ref = afterNextRender(
|
||||
() => {
|
||||
const resizeObserver = new ResizeObserver(() => this.updateHeadingsTopAfterResize());
|
||||
resizeObserver.observe(docsViewer);
|
||||
destroyRef.onDestroy(() => resizeObserver.disconnect());
|
||||
setupActiveItemListener(contentSourceElement: HTMLElement, destroyRef: DestroyRef): void {
|
||||
if (contentSourceElement) {
|
||||
this.tableOfContentsLoader.setupIntersectionObserver(
|
||||
contentSourceElement,
|
||||
destroyRef,
|
||||
(id) => {
|
||||
this.activeItemId.set(id);
|
||||
},
|
||||
{injector: this.injector, manualCleanup: true},
|
||||
);
|
||||
|
||||
destroyRef.onDestroy(() => ref.destroy());
|
||||
}
|
||||
}
|
||||
|
||||
private updateHeadingsTopAfterResize(): void {
|
||||
this.lastContentWidth = this.getContentWidth();
|
||||
|
||||
const contentElement = this.contentSourceElement;
|
||||
if (contentElement) {
|
||||
this.tableOfContentsLoader.updateHeadingsTopValue(contentElement);
|
||||
this.setActiveItemId();
|
||||
}
|
||||
}
|
||||
|
||||
private setScrollEventHandlers(destroyRef: DestroyRef): void {
|
||||
const scroll$ = fromEvent(this.document, 'scroll').pipe(
|
||||
auditTime(SCROLL_EVENT_DELAY),
|
||||
takeUntilDestroyed(destroyRef),
|
||||
);
|
||||
|
||||
scroll$.subscribe(() => this.setActiveItemId());
|
||||
}
|
||||
|
||||
private setActiveItemId(): void {
|
||||
const tableOfContentItems = this.tableOfContentsLoader.tableOfContentItems();
|
||||
|
||||
if (tableOfContentItems.length === 0) return;
|
||||
|
||||
// Resize could emit scroll event, in that case we could stop setting active item until resize will be finished
|
||||
if (this.lastContentWidth !== this.getContentWidth()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const scrollOffset = this.getScrollOffset();
|
||||
if (scrollOffset === null) return;
|
||||
|
||||
for (const [i, currentLink] of tableOfContentItems.entries()) {
|
||||
const nextLink = tableOfContentItems[i + 1];
|
||||
|
||||
// A link is considered active if the page is scrolled past the
|
||||
// anchor without also being scrolled passed the next link.
|
||||
const isActive =
|
||||
scrollOffset >= currentLink.top && (!nextLink || nextLink.top >= scrollOffset);
|
||||
|
||||
// When active item was changed then trigger change detection
|
||||
if (isActive && this.activeItemId() !== currentLink.id) {
|
||||
this.activeItemId.set(currentLink.id);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (scrollOffset < tableOfContentItems[0].top && this.activeItemId() !== null) {
|
||||
this.activeItemId.set(null);
|
||||
}
|
||||
|
||||
const scrollOffsetZero = scrollOffset === 0;
|
||||
if (scrollOffsetZero !== this.scrollbarThumbOnTop()) {
|
||||
// we want to trigger change detection only when the value changes
|
||||
this.scrollbarThumbOnTop.set(scrollOffsetZero);
|
||||
}
|
||||
}
|
||||
|
||||
// Gets the scroll offset of the scroll container
|
||||
private getScrollOffset(): number {
|
||||
return this.window.scrollY;
|
||||
}
|
||||
|
||||
private getContentWidth(): number {
|
||||
return this.document.body.clientWidth || Number.MAX_SAFE_INTEGER;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -66,7 +66,11 @@
|
||||
.docs-reference-section-heading {
|
||||
padding-block-start: 3rem;
|
||||
|
||||
a {
|
||||
&--sub {
|
||||
padding-block-start: 1rem;
|
||||
}
|
||||
|
||||
& > a {
|
||||
@include anchor.docs-anchor();
|
||||
color: inherit;
|
||||
}
|
||||
@@ -98,7 +102,7 @@
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
&.highlighted {
|
||||
&.docs-highlighted-card {
|
||||
box-shadow: 10px 4px 40px 0 rgba(0, 0, 0, 0.01);
|
||||
|
||||
&::before {
|
||||
|
||||
@@ -79,6 +79,7 @@
|
||||
}
|
||||
|
||||
p > a,
|
||||
p > em > a,
|
||||
td > a,
|
||||
div > a:not(.docs-card),
|
||||
code > a,
|
||||
@@ -93,6 +94,7 @@
|
||||
}
|
||||
|
||||
p > a,
|
||||
p > em > a,
|
||||
.docs-list a,
|
||||
.docs-card a {
|
||||
margin-block: 0;
|
||||
|
||||
@@ -58,7 +58,7 @@
|
||||
fill: var(--page-background) !important;
|
||||
}
|
||||
|
||||
.nodeLabel:not(.node:has(polygon) .nodeLabel) {
|
||||
.nodeLabel:not(.node:has(polygon) .nodeLabel):not(.eventNode .nodeLabel) {
|
||||
fill: var(--primary-contrast) !important;
|
||||
color: var(--primary-contrast) !important;
|
||||
}
|
||||
@@ -71,7 +71,6 @@
|
||||
}
|
||||
|
||||
.nodeLabel p {
|
||||
color: var(--page-background) !important;
|
||||
font-weight: 800 !important;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
gtag: (...args: any[]) => void;
|
||||
gtag?(...args: any[]): void;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
import {TestBed} from '@angular/core/testing';
|
||||
import {AppComponent} from './app.component';
|
||||
import {provideRouter} from '@angular/router';
|
||||
import {provideRouter, withComponentInputBinding} from '@angular/router';
|
||||
import {routes} from './routes';
|
||||
import {Search, WINDOW} from '@angular/docs';
|
||||
import {CURRENT_MAJOR_VERSION} from './core/providers/current-version';
|
||||
@@ -21,7 +21,7 @@ describe('AppComponent', () => {
|
||||
it('should create the app', () => {
|
||||
TestBed.configureTestingModule({
|
||||
providers: [
|
||||
provideRouter(routes),
|
||||
provideRouter(routes, withComponentInputBinding()),
|
||||
{
|
||||
provide: WINDOW,
|
||||
useValue: fakeWindow,
|
||||
|
||||
@@ -13,11 +13,11 @@ import {
|
||||
inject,
|
||||
OnInit,
|
||||
PLATFORM_ID,
|
||||
input,
|
||||
signal,
|
||||
WritableSignal,
|
||||
} from '@angular/core';
|
||||
import {NavigationEnd, NavigationSkipped, Router, RouterOutlet} from '@angular/router';
|
||||
import {filter, map, skip} from 'rxjs/operators';
|
||||
import {filter, map} from 'rxjs/operators';
|
||||
import {
|
||||
CookiePopup,
|
||||
getActivatedRouteSnapshotFromRouter,
|
||||
@@ -56,13 +56,12 @@ export class AppComponent implements OnInit {
|
||||
private readonly router = inject(Router);
|
||||
private readonly headerService = inject(HeaderService);
|
||||
|
||||
currentUrl = signal('');
|
||||
displayFooter = signal(false);
|
||||
displaySecondaryNav = signal(false);
|
||||
displaySearchDialog: WritableSignal<boolean> = inject(IS_SEARCH_DIALOG_OPEN);
|
||||
|
||||
isBrowser = isPlatformBrowser(inject(PLATFORM_ID));
|
||||
|
||||
displaySecondaryNav = signal(false);
|
||||
displayFooter = signal(false);
|
||||
displaySearchDialog = inject(IS_SEARCH_DIALOG_OPEN);
|
||||
|
||||
ngOnInit(): void {
|
||||
this.closeSearchDialogOnNavigationSkipped();
|
||||
this.router.events
|
||||
@@ -71,35 +70,24 @@ export class AppComponent implements OnInit {
|
||||
map((event) => event.urlAfterRedirects),
|
||||
)
|
||||
.subscribe((url) => {
|
||||
this.currentUrl.set(url);
|
||||
this.setComponentsVisibility();
|
||||
this.displaySearchDialog.set(false);
|
||||
// We can't use an input binded to the route here
|
||||
// because AppComponent itself is not a routed component
|
||||
// so we access it via the snapshot
|
||||
const activatedRoute = getActivatedRouteSnapshotFromRouter(this.router);
|
||||
this.displayFooter.set(!activatedRoute.data['hideFooter']);
|
||||
this.displaySecondaryNav.set(activatedRoute.data['displaySecondaryNav']);
|
||||
|
||||
this.displaySearchDialog.set(false);
|
||||
this.updateCanonicalLink(url);
|
||||
});
|
||||
}
|
||||
|
||||
focusFirstHeading(): void {
|
||||
if (!this.isBrowser) {
|
||||
return;
|
||||
}
|
||||
|
||||
const h1 = this.document.querySelector<HTMLHeadingElement>('h1:not(docs-top-level-banner h1)');
|
||||
h1?.focus();
|
||||
}
|
||||
|
||||
private updateCanonicalLink(absoluteUrl: string) {
|
||||
this.headerService.setCanonical(absoluteUrl);
|
||||
}
|
||||
|
||||
private setComponentsVisibility(): void {
|
||||
const activatedRoute = getActivatedRouteSnapshotFromRouter(this.router as any);
|
||||
|
||||
this.displaySecondaryNav.set(activatedRoute.data['displaySecondaryNav']);
|
||||
this.displayFooter.set(!activatedRoute.data['hideFooter']);
|
||||
}
|
||||
|
||||
private setSearchDialogVisibilityOnKeyPress(event: KeyboardEvent): void {
|
||||
protected setSearchDialogVisibilityOnKeyPress(event: KeyboardEvent): void {
|
||||
if (event.key === SEARCH_TRIGGER_KEY && (event.metaKey || event.ctrlKey)) {
|
||||
event.preventDefault();
|
||||
this.displaySearchDialog.update((display) => !display);
|
||||
@@ -111,6 +99,10 @@ export class AppComponent implements OnInit {
|
||||
}
|
||||
}
|
||||
|
||||
private updateCanonicalLink(absoluteUrl: string) {
|
||||
this.headerService.setCanonical(absoluteUrl);
|
||||
}
|
||||
|
||||
private closeSearchDialogOnNavigationSkipped(): void {
|
||||
this.router.events.pipe(filter((event) => event instanceof NavigationSkipped)).subscribe(() => {
|
||||
this.displaySearchDialog.set(false);
|
||||
|
||||
@@ -25,7 +25,6 @@ import {
|
||||
windowProvider,
|
||||
} from '@angular/docs';
|
||||
import {provideClientHydration} from '@angular/platform-browser';
|
||||
import {provideAnimationsAsync} from '@angular/platform-browser/animations/async';
|
||||
import {
|
||||
RouteReuseStrategy,
|
||||
Router,
|
||||
@@ -75,7 +74,6 @@ export const appConfig: ApplicationConfig = {
|
||||
provideExperimentalZonelessChangeDetection(),
|
||||
provideClientHydration(),
|
||||
provideHttpClient(withFetch()),
|
||||
provideAnimationsAsync(),
|
||||
provideEnvironmentInitializer(() => inject(AppScroller)),
|
||||
provideEnvironmentInitializer(() => inject(AnalyticsService)),
|
||||
{
|
||||
|
||||
@@ -119,7 +119,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<p class="docs-license">
|
||||
Super-powered by Google ©2010-2024. Code licensed under an
|
||||
Super-powered by Google ©2010-2025. Code licensed under an
|
||||
<a routerLink="/license" title="License text">MIT-style License</a>
|
||||
. Documentation licensed under
|
||||
<a href="https://creativecommons.org/licenses/by/4.0/">CC BY 4.0</a>
|
||||
|
||||
@@ -13,7 +13,6 @@
|
||||
<!-- Third, fourth and next levels of navigation -->
|
||||
@for (item of navigationItemsSlides(); track item; let level = $index) {
|
||||
<docs-navigation-list
|
||||
[@leaveAnimation]
|
||||
[collapsableLevel]="level + maxVisibleLevelsOnSecondaryNav()"
|
||||
[expandableLevel]="level + maxVisibleLevelsOnSecondaryNav() + 1"
|
||||
[navigationItems]="[item]"
|
||||
|
||||
@@ -34,7 +34,6 @@ import {SUB_NAVIGATION_DATA} from '../../../sub-navigation-data';
|
||||
import {PagePrefix} from '../../enums/pages';
|
||||
import {ActivatedRouteSnapshot, NavigationEnd, Router, RouterStateSnapshot} from '@angular/router';
|
||||
import {isPlatformBrowser} from '@angular/common';
|
||||
import {trigger, transition, style, animate} from '@angular/animations';
|
||||
import {PRIMARY_NAV_ID, SECONDARY_NAV_ID} from '../../constants/element-ids';
|
||||
|
||||
export const ANIMATION_DURATION = 500;
|
||||
@@ -45,17 +44,6 @@ export const ANIMATION_DURATION = 500;
|
||||
templateUrl: './secondary-navigation.component.html',
|
||||
styleUrls: ['./secondary-navigation.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
animations: [
|
||||
trigger('leaveAnimation', [
|
||||
transition(':leave', [
|
||||
style({transform: 'translateX(0%)'}),
|
||||
animate(
|
||||
`${ANIMATION_DURATION}ms ${ANIMATION_DURATION}ms ease-out`,
|
||||
style({transform: 'translateX(100%)'}),
|
||||
),
|
||||
]),
|
||||
]),
|
||||
],
|
||||
})
|
||||
export class SecondaryNavigation implements OnInit {
|
||||
private readonly destroyRef = inject(DestroyRef);
|
||||
@@ -69,10 +57,11 @@ export class SecondaryNavigation implements OnInit {
|
||||
this.primaryActiveRouteItem() === PagePrefix.REFERENCE ? 1 : 2,
|
||||
);
|
||||
readonly navigationItemsSlides = this.navigationState.expandedItems;
|
||||
|
||||
navigationItems: NavigationItem[] | undefined;
|
||||
|
||||
translateX = computed(() => {
|
||||
const level = this.navigationState.expandedItems()?.length ?? 0;
|
||||
const level = this.navigationState.level();
|
||||
return `translateX(${-level * 100}%)`;
|
||||
});
|
||||
transition = signal('0ms');
|
||||
|
||||
+1
-1
@@ -14,7 +14,7 @@ import {DOCUMENT} from '@angular/common';
|
||||
import {ReferenceScrollHandler} from '../services/reference-scroll-handler.service';
|
||||
import {API_SECTION_CLASS_NAME} from '../constants/api-reference-prerender.constants';
|
||||
|
||||
const HIGHLIGHTED_CARD_CLASS = 'highlighted';
|
||||
const HIGHLIGHTED_CARD_CLASS = 'docs-highlighted-card';
|
||||
|
||||
@Component({
|
||||
selector: 'adev-reference-page',
|
||||
|
||||
@@ -1919,7 +1919,7 @@ export const RECOMMENDATIONS: Step[] = [
|
||||
level: ApplicationComplexity.Medium,
|
||||
step: 'v16 createUrlTree',
|
||||
action:
|
||||
'After bug fixes in `Router.createUrlTree` you may have to readjust tests which mock `ActiveRoute`. [Read further](https://github.com/angular/angular/blob/main/CHANGELOG.md#1600-next1-2023-03-01)',
|
||||
'After bug fixes in `Router.createUrlTree` you may have to readjust tests which mock `ActivatedRoute`. [Read further](https://github.com/angular/angular/blob/main/CHANGELOG.md#1600-2023-05-03)',
|
||||
},
|
||||
{
|
||||
possibleIn: 1600,
|
||||
|
||||
@@ -238,10 +238,6 @@ export const routes: Route[] = [
|
||||
path: PagePrefix.DOCS,
|
||||
redirectTo: DefaultPage.DOCS,
|
||||
},
|
||||
{
|
||||
path: PagePrefix.TUTORIALS,
|
||||
redirectTo: DefaultPage.TUTORIALS,
|
||||
},
|
||||
{
|
||||
path: PagePrefix.REFERENCE,
|
||||
redirectTo: DefaultPage.REFERENCE,
|
||||
|
||||
@@ -859,6 +859,11 @@ const DOCS_SUB_NAVIGATION_DATA: NavigationItem[] = [
|
||||
path: 'ecosystem/web-workers',
|
||||
contentPath: 'ecosystem/web-workers',
|
||||
},
|
||||
{
|
||||
label: 'Custom build pipeline',
|
||||
path: 'ecosystem/custom-build-pipeline',
|
||||
contentPath: 'ecosystem/custom-build-pipeline',
|
||||
},
|
||||
{
|
||||
label: 'Angular Fire',
|
||||
path: 'https://github.com/angular/angularfire#readme',
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{
|
||||
"branchName": "refs/heads/main",
|
||||
"sha": "4e45bc8fc2704830d6e4099f2455d0135278b002"
|
||||
"branchName": "refs/heads/19.2.x",
|
||||
"sha": "da1d5784755500cc064c433e5a4754a1ff1946fd"
|
||||
}
|
||||
@@ -84,7 +84,7 @@
|
||||
{
|
||||
"name": "external-dependencies",
|
||||
"type": "array",
|
||||
"description": "Exclude the listed external dependencies from being bundled into the bundle. Instead, the created bundle relies on these dependencies to be available during runtime."
|
||||
"description": "Exclude the listed external dependencies from being bundled into the bundle. Instead, the created bundle relies on these dependencies to be available during runtime. Note: `@foo/bar` marks all paths within the `@foo/bar` package as external, including sub-paths like `@foo/bar/baz`."
|
||||
},
|
||||
{
|
||||
"name": "extract-licenses",
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
# Custom build pipeline
|
||||
|
||||
When building an Angular app we strongly recommend you to use the Angular CLI to leverage its structure-dependent update functionality and build system abstraction. This way your projects benefit from the latest security, performance, and API improvements and transparent build improvements.
|
||||
|
||||
This page explores the **rare use cases** when you need a custom build pipeline that does not use the Angular CLI. All listed tools below are open source build plugins that are maintained by members of the Angular community. To learn more about their support model and maintenance status look at their documentation and GitHub repository URLs.
|
||||
|
||||
## When should you use a custom build pipeline?
|
||||
|
||||
There are some niche use cases when you may want to maintain a custom build pipeline. For example:
|
||||
|
||||
* You have an existing app using a different toolchain and you’d like to add Angular to it
|
||||
* You’re strongly coupled to [module federation](https://module-federation.io/) and unable to adopt bundler-agnostic [native federation](https://www.npmjs.com/package/@angular-architects/native-federation)
|
||||
* You’d like to create an short-lived experiment using your favorite build tool
|
||||
|
||||
## What are the options?
|
||||
|
||||
Currently, there are two well supported community tools that enable you to create a custom build pipeline with a [Vite plugin](https://www.npmjs.com/package/@analogjs/vite-plugin-angular) and [Rspack plugin](https://www.npmjs.com/package/@ng-rspack/build). Both of them use underlying abstractions that power the Angular CLI. They allow you to create a flexible build pipeline and require manual maintenance and no automated update experience.
|
||||
|
||||
### Rspack
|
||||
|
||||
Rspack is a Rust-based bundler that aims to provide compatibility with the webpack plugin ecosystem.
|
||||
|
||||
If your project is tightly coupled to the webpack ecosystem, heavily relying on a custom webpack configuration you can leverage Rspack to improve your build times.
|
||||
|
||||
You can find more about Angular Rspack on the project’s [documentation website](https://angular-rspack.dev/guide/migration/from-webpack).
|
||||
|
||||
### Vite
|
||||
|
||||
Vite is a frontend build tool that aims to provide a faster and leaner development experience for modern web projects. Vite is also extensible through its plugin system that allows ecosystems to build integrations with Vite, such as Vitest for unit and browser testing, Storybook for authoring components in isolation, and more. The Angular CLI also uses Vite as its development server.
|
||||
|
||||
The [AnalogJS Vite plugin for Angular](https://www.npmjs.com/package/@analogjs/vite-plugin-angular) enables the adoption of Angular with a project or framework that uses or is built on top of Vite. This can consist of developing and building an Angular project with Vite directly, or adding Angular to an existing project or pipeline. One example is integrating Angular UI components into a documentation website using [Astro and Starlight](https://analogjs.org/docs/packages/astro-angular/overview).
|
||||
|
||||
You can learn more about AnalogJS and how to use the plugin through its [documentation page](https://analogjs.org/docs/packages/vite-plugin-angular/overview).
|
||||
@@ -90,7 +90,7 @@ describe('BannerComponent (with beforeEach)', () => {
|
||||
// #enddocregion v4-test-3
|
||||
|
||||
// #docregion v4-test-4
|
||||
it('should find the <p> with fixture.debugElement.nativeElement)', () => {
|
||||
it('should find the <p> with fixture.debugElement.nativeElement', () => {
|
||||
// #docregion debugElement-nativeElement
|
||||
const bannerDe: DebugElement = fixture.debugElement;
|
||||
const bannerEl: HTMLElement = bannerDe.nativeElement;
|
||||
|
||||
@@ -132,7 +132,7 @@ TypeScript property name as a key, rather than the alias.
|
||||
### ngOnDestroy
|
||||
|
||||
The `ngOnDestroy` method runs once just before a component is destroyed. Angular destroys a
|
||||
component when it is no longer shown on the page, such as being hidden by `NgIf` or upon navigating
|
||||
component when it is no longer shown on the page, such as being hidden by `@if` or upon navigating
|
||||
to another page.
|
||||
|
||||
#### DestroyRef
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Security
|
||||
|
||||
This topic describes Angular's built-in protections against common web-application vulnerabilities and attacks such as cross-site scripting attacks.
|
||||
This topic describes Angular's built-in protections against common web application vulnerabilities and attacks such as cross-site scripting attacks.
|
||||
It doesn't cover application-level security, such as authentication and authorization.
|
||||
|
||||
For more information about the attacks and mitigations described below, see the [Open Web Application Security Project (OWASP) Guide](https://www.owasp.org/index.php/Category:OWASP_Guide_Project).
|
||||
@@ -9,7 +9,7 @@ For more information about the attacks and mitigations described below, see the
|
||||
|
||||
<docs-callout title="Reporting vulnerabilities">
|
||||
|
||||
Angular is part of Google [Open Source Software Vulnerability Reward Program](https://bughunters.google.com/about/rules/6521337925468160/google-open-source-software-vulnerability-reward-program-rules). [For vulnerabilities in Angular, please submit your report at https://bughunters.google.com](https://bughunters.google.com/report).
|
||||
Angular is part of Google [Open Source Software Vulnerability Reward Program](https://bughunters.google.com/about/rules/6521337925468160/google-open-source-software-vulnerability-reward-program-rules). For vulnerabilities in Angular, please submit your report at [https://bughunters.google.com](https://bughunters.google.com/report).
|
||||
|
||||
For more information about how Google handles security issues, see [Google's security philosophy](https://www.google.com/about/appsecurity).
|
||||
|
||||
@@ -53,8 +53,8 @@ For this reason, it is strongly encouraged to take advantage of these features.
|
||||
|
||||
*Sanitization* is the inspection of an untrusted value, turning it into a value that's safe to insert into the DOM.
|
||||
In many cases, sanitization doesn't change a value at all.
|
||||
Sanitization depends on context:
|
||||
A value that's harmless in CSS is potentially dangerous in a URL.
|
||||
Sanitization depends on a context.
|
||||
For example, a value that's harmless in CSS is potentially dangerous in a URL.
|
||||
|
||||
Angular defines the following security contexts:
|
||||
|
||||
@@ -95,7 +95,7 @@ Avoid directly interacting with the DOM and instead use Angular templates where
|
||||
|
||||
For cases where this is unavoidable, use the built-in Angular sanitization functions.
|
||||
Sanitize untrusted values with the [DomSanitizer.sanitize](api/platform-browser/DomSanitizer#sanitize) method and the appropriate `SecurityContext`.
|
||||
That function also accepts values that were marked as trusted using the `bypassSecurityTrust` … functions, and does not sanitize them, as [described below](#trusting-safe-values).
|
||||
That function also accepts values that were marked as trusted using the `bypassSecurityTrust` functions, and does not sanitize them, as [described below](#trusting-safe-values).
|
||||
|
||||
### Trusting safe values
|
||||
|
||||
|
||||
@@ -35,7 +35,7 @@ Angular's compiler produces a [dynamic import](https://developer.mozilla.org/en-
|
||||
|
||||
This is the primary block that defines the section of content that is lazily loaded. It is not rendered initially– deferred content loads and renders once the specified [trigger](/guide/defer#triggers) occurs or the `when` condition is met.
|
||||
|
||||
By default, a @defer block is triggered when the browser state becomes [idle](/guide/defer#on-idle).
|
||||
By default, a @defer block is triggered when the browser state becomes [idle](/guide/defer#idle).
|
||||
|
||||
```angular-html
|
||||
@defer {
|
||||
|
||||
@@ -13,11 +13,11 @@ When you want to add event listeners to an HTML element, you wrap the event with
|
||||
`,
|
||||
...
|
||||
})
|
||||
export class AppComponent({
|
||||
export class AppComponent{
|
||||
updateField(): void {
|
||||
console.log('Field is updated!');
|
||||
}
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
In this example, Angular calls `updateField` every time the `<input>` element emits a `keyup` event.
|
||||
@@ -71,11 +71,11 @@ However, since this is a common scenario, Angular lets you filter the events by
|
||||
`,
|
||||
...
|
||||
})
|
||||
export class AppComponent({
|
||||
export class AppComponent{
|
||||
updateField(event: KeyboardEvent): void {
|
||||
console.log('The user pressed enter in the text field.');
|
||||
}
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
You can also add additional key modifiers:
|
||||
@@ -93,7 +93,7 @@ Angular also allows you to specify [Code values for keyboard events](https://dev
|
||||
|
||||
```angular-html
|
||||
<!-- Matches alt and left shift -->
|
||||
<input type="text" (keydown.code.alt.leftshift)="updateField($event)" />
|
||||
<input type="text" (keydown.code.alt.shiftleft)="updateField($event)" />
|
||||
```
|
||||
|
||||
This can be useful for handling keyboard events consistently across different operating systems. For example, when using the Alt key on MacOS devices, the `key` property reports the key based on the character already modified by the Alt key. This means that a combination like Alt + S reports a `key` value of `'ß'`. The `code` property, however, corresponds to the physical or virtual button pressed rather than the character produced.
|
||||
|
||||
@@ -68,7 +68,7 @@ Angular expressions additionally also support the following non-standard operato
|
||||
|
||||
| Operator | Example(s) |
|
||||
| ------------------------------- | ------------------------------ |
|
||||
| [Pipe](/guides/templates/pipes) | `{{ total \| currency }}` |
|
||||
| [Pipe](/guide/templates/pipes) | `{{ total \| currency }}` |
|
||||
| Optional chaining\* | `someObj.someProp?.nestedProp` |
|
||||
| Non-null assertion (TypeScript) | `someObj!.someProp` |
|
||||
|
||||
|
||||
@@ -68,59 +68,51 @@ export class AppComponent {
|
||||
|
||||
```angular-ts
|
||||
// './counter/counter.component.ts';
|
||||
import { Component, EventEmitter, Input, Output } from '@angular/core';
|
||||
import { Component, model } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'app-counter',
|
||||
template: `
|
||||
<button (click)="updateCount(-1)">-</button>
|
||||
<span>{{ count }}</span>
|
||||
<span>{{ count() }}</span>
|
||||
<button (click)="updateCount(+1)">+</button>
|
||||
`,
|
||||
})
|
||||
export class CounterComponent {
|
||||
@Input() count: number;
|
||||
@Output() countChange = new EventEmitter<number>();
|
||||
count = model<number>(0);
|
||||
|
||||
updateCount(amount: number): void {
|
||||
this.count += amount;
|
||||
this.countChange.emit(this.count);
|
||||
this.count.update(currentCount => currentCount + amount);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Enabling two-way binding between components
|
||||
|
||||
If we break down the example above to its core , each two-way binding for components requires the following:
|
||||
If we break down the example above to its core, each two-way binding for components requires the following:
|
||||
|
||||
The child component must contain:
|
||||
|
||||
1. An `@Input()` property
|
||||
1. A corresponding `@Output()` event emitter that has the exact same name as the input property plus "Change" at the end. The emitter must also emit the same type as the input property.
|
||||
1. A method that emits to the event emitter with the updated value of the `@Input()`.
|
||||
The child component must contain a `model` property.
|
||||
|
||||
Here is a simplified example:
|
||||
|
||||
```angular-ts
|
||||
// './counter/counter.component.ts';
|
||||
import { Component, EventEmitter, Input, Output } from '@angular/core';
|
||||
import { Component, model } from '@angular/core';
|
||||
|
||||
@Component({ // Omitted for brevity })
|
||||
export class CounterComponent {
|
||||
@Input() count: number;
|
||||
@Output() countChange = new EventEmitter<number>();
|
||||
count = model<number>(0);
|
||||
|
||||
updateCount(amount: number): void {
|
||||
this.count += amount;
|
||||
this.countChange.emit(this.count);
|
||||
this.count.update(currentCount => currentCount + amount);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The parent component must:
|
||||
|
||||
1. Wrap the `@Input()` property name in the two-way binding syntax.
|
||||
1. Specify the corresponding property to which the updated value is assigned
|
||||
1. Wrap the `model` property name in the two-way binding syntax.
|
||||
1. Assign a property or a signal to the `model` property.
|
||||
|
||||
Here is a simplified example:
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ The main advantages to removing ZoneJS as a dependency are:
|
||||
- **Improved performance**: ZoneJS uses DOM events and async tasks as indicators of when application state _might_ have updated and subsequently triggers application synchronization to run change detection on the application's views. ZoneJS does not have any insight into whether application state actually changed and so this synchronization is triggered more frequently than necessary.
|
||||
- **Improved Core Web Vitals**: ZoneJS brings a fair amount of overhead, both in payload size and in startup time cost.
|
||||
- **Improved debugging experience**: ZoneJS makes debugging code more difficult. Stack traces are harder to understand with ZoneJS. It's also difficult to understand when code breaks as a result of being outside the Angular Zone.
|
||||
- **Better ecosystem compatibility**: ZoneJS works by patching browser APIs but does not automatically have patches for every new browser API. Some APIs simply cannot be patched effectively, such as `async`/`await`, and have to be downleveled to work with ZoneJS. Sometimes libraries in the ecosystem are also incompatible with the way ZoneJS patches the native APIs. Removing ZoneJS as a dependency ensures better long-term compatibility by removing a source of complexity, monkey patching, and ongoing maintenance.
|
||||
- **Better ecosystem compatibility**: ZoneJS works by patching browser APIs but does not automatically have patches for every new browser API. Some APIs cannot be patched effectively, such as `async`/`await`, and have to be downleveled to work with ZoneJS. Sometimes libraries in the ecosystem are also incompatible with the way ZoneJS patches the native APIs. Removing ZoneJS as a dependency ensures better long-term compatibility by removing a source of complexity, monkey patching, and ongoing maintenance.
|
||||
|
||||
## Enabling Zoneless in an application
|
||||
|
||||
@@ -60,6 +60,7 @@ use [ChangeDetectionStrategy.OnPush](/best-practices/skipping-subtrees#using-onp
|
||||
|
||||
The `OnPush` change detection strategy is not required, but it is a recommended step towards zoneless compatibility for application components. It is not always possible for library components to use `ChangeDetectionStrategy.OnPush`.
|
||||
When a library component is a host for user-components which might use `ChangeDetectionStrategy.Default`, it cannot use `OnPush` because that would prevent the child component from being refreshed if it is not `OnPush` compatible and relies on ZoneJS to trigger change detection. Components can use the `Default` strategy as long as they notify Angular when change detection needs to run (calling `markForCheck`, using signals, `AsyncPipe`, etc.).
|
||||
Being a host for a user component means using an API such as `ViewContainerRef.createComponent` and not just hosting a portion of a template from a user component (i.e. content projection or a using a template ref input).
|
||||
|
||||
### Remove `NgZone.onMicrotaskEmpty`, `NgZone.onUnstable`, `NgZone.isStable`, or `NgZone.onStable`
|
||||
|
||||
@@ -85,14 +86,39 @@ are used in applications that still rely on ZoneJS.
|
||||
|
||||
If you are using SSR with Angular, you may know that it relies on ZoneJS to help determine when the application
|
||||
is "stable" and can be serialized. If there are asynchronous tasks that should prevent serialization, an application
|
||||
not using ZoneJS will need to make Angular aware of these with the `PendingTasks` service. Serialization
|
||||
not using ZoneJS must make Angular aware of these with the [PendingTasks](/api/core/PendingTasks) service. Serialization
|
||||
will wait for the first moment that all pending tasks have been removed.
|
||||
|
||||
|
||||
The two most straightforward uses of pending tasks are the `run` method:
|
||||
|
||||
```typescript
|
||||
const taskService = inject(PendingTasks);
|
||||
taskService.run(async () => {
|
||||
const someResult = await doSomeWorkThatNeedsToBeRendered();
|
||||
this.someState.set(someResult);
|
||||
});
|
||||
```
|
||||
|
||||
For more complicated use-cases, you can manuall add and remove a pending tasks:
|
||||
|
||||
```typescript
|
||||
const taskService = inject(PendingTasks);
|
||||
const taskCleanup = taskService.add();
|
||||
await doSomeWorkThatNeedsToBeRendered();
|
||||
taskCleanup();
|
||||
try {
|
||||
await doSomeWorkThatNeedsToBeRendered();
|
||||
} catch {
|
||||
// handle error
|
||||
} finally {
|
||||
taskCleanup();
|
||||
}
|
||||
```
|
||||
|
||||
In addition, the [pendingUntilEvent](/api/core/rxjs-interop/pendingUntilEvent#) helper in `rxjs-interop` ensures
|
||||
the application remains unstable until the observable emits, complets, errors, or is unsubscribed.
|
||||
|
||||
```typescript
|
||||
readonly myObservableState = someObservable.pipe(pendingUntilEvent());
|
||||
```
|
||||
|
||||
The framework uses this service internally as well to prevent serialization until asynchronous tasks are complete. These include, but are not limited to,
|
||||
@@ -122,11 +148,22 @@ scheduled change detection. Tests should ensure these notifications
|
||||
are happening and allow Angular to handle when to synchronize
|
||||
state rather than manually forcing it to happen in the test.
|
||||
|
||||
For existing test suites, using `fixture.detectChanges()` is a common pattern
|
||||
and it is likely not worth the effort of converting these to
|
||||
`await fixture.whenStable()`. `TestBed` will still enforce that the
|
||||
fixture's component is `OnPush` compatible and throws `ExpressionChangedAfterItHasBeenCheckedError`
|
||||
if it finds that template values were updated without a
|
||||
change notification (i.e. `fixture.componentInstance.someValue = 'newValue';`).
|
||||
If the component is used in production, this issue should be addressed by updating
|
||||
the component to use signals for state or call `ChangeDetectorRef.markForCheck()`.
|
||||
If the component is only used as a test wrapper and never used in an application,
|
||||
it is acceptable to use `fixture.changeDetectorRef.markForCheck()`.
|
||||
|
||||
### Debug-mode check to ensure updates are detected
|
||||
|
||||
Angular also provides an additional tool to help verify that an application is making
|
||||
updates to state in a zoneless-compatible way. `provideExperimentalCheckNoChangesForDebug`
|
||||
can be used to periodically check to ensure that no bindings have been updated
|
||||
without a notification. Angular will throw `ExpressionChangedAfterItHasBeenCheckedError`
|
||||
without a notification. Angular throws `ExpressionChangedAfterItHasBeenCheckedError`
|
||||
if there is an updated binding that would not have refreshed by the zoneless change
|
||||
detection.
|
||||
|
||||
@@ -32,11 +32,29 @@ The following guide will walk you through setting up a local Angular project.
|
||||
|
||||
Open a terminal (if you're using [Visual Studio Code](https://code.visualstudio.com/), you can open an [integrated terminal](https://code.visualstudio.com/docs/editor/integrated-terminal)) and run the following command:
|
||||
|
||||
<docs-code language="shell">
|
||||
<docs-code-multifile>
|
||||
<docs-code
|
||||
header="npm"
|
||||
>
|
||||
npm install -g @angular/cli
|
||||
</docs-code>
|
||||
<docs-code
|
||||
header="pnpm"
|
||||
>
|
||||
pnpm install -g @angular/cli
|
||||
</docs-code>
|
||||
<docs-code
|
||||
header="yarn"
|
||||
>
|
||||
yarn global add @angular/cli
|
||||
</docs-code>
|
||||
<docs-code
|
||||
header="bun"
|
||||
>
|
||||
bun install -g @angular/cli
|
||||
</docs-code>
|
||||
|
||||
npm install -g @angular/cli
|
||||
|
||||
</docs-code>
|
||||
</docs-code-multifile>
|
||||
|
||||
If you are having issues running this command in Windows or Unix, check out the [CLI docs](/tools/cli/setup-local#install-the-angular-cli) for more info.
|
||||
|
||||
|
||||
@@ -80,7 +80,7 @@ ng generate @angular/core:output-migration --path src/app/sub-folder
|
||||
## Exceptions
|
||||
|
||||
In some cases, the migration will not touch the code.
|
||||
One of these excpetions is the case where the event is used with a `pipe()` method.
|
||||
One of these exceptions is the case where the event is used with a `pipe()` method.
|
||||
The following code won't be migrated:
|
||||
|
||||
```typescript
|
||||
@@ -93,4 +93,4 @@ export class MyDialogComponent {
|
||||
this.close.pipe();
|
||||
}
|
||||
}
|
||||
```
|
||||
```
|
||||
|
||||
@@ -49,18 +49,18 @@ export class MyComponent {
|
||||
import {Component, input} from '@angular/core';
|
||||
|
||||
@Component({
|
||||
template: `Name: {{name() ?? ''}}`
|
||||
template: `Name: {{name() ?? ''}}`
|
||||
})
|
||||
export class MyComponent {
|
||||
readonly name = input<string>();
|
||||
readonly name = input<string>();
|
||||
|
||||
someMethod(): number {
|
||||
const name = this.name();
|
||||
if (name) {
|
||||
return name.length;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
someMethod(): number {
|
||||
const name = this.name();
|
||||
if (name) {
|
||||
return name.length;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
</docs-code>
|
||||
|
||||
|
||||
@@ -76,7 +76,7 @@ HELPFUL: Approximate dates are offered as general guidance and are subject to ch
|
||||
|:--------|:-------------------|
|
||||
| v19.1 | Week of 2025-01-13 |
|
||||
| v19.2 | Week of 2025-02-24 |
|
||||
| v20.0 | Week of 2025-05-19 |
|
||||
| v20.0 | Week of 2025-05-26 |
|
||||
|
||||
### Support window
|
||||
|
||||
@@ -159,10 +159,12 @@ For example, to update from version 10 to version 12:
|
||||
|
||||
Occasionally we introduce new APIs under the label of "Developer Preview". These are APIs that are fully functional and polished, but that we are not ready to stabilize under our normal deprecation policy.
|
||||
|
||||
This may be because we want to gather feedback from real applications before stabilization, or because the associated documentation or migration tooling is not fully complete.
|
||||
This may be because we want to gather feedback from real applications before stabilization, or because the associated documentation or migration tooling is not fully complete. Feedback can be provided via a [GitHub issue](https://github.com/angular/angular/issues), where developers can share their experiences, report bugs, or suggest improvements to help refine the feature.
|
||||
|
||||
The policies and practices that are described in this document do not apply to APIs marked as Developer Preview. Such APIs can change at any time, even in new patch versions of the framework. Teams should decide for themselves whether the benefits of using Developer Preview APIs are worth the risk of breaking changes outside of our normal use of semantic versioning.
|
||||
|
||||
|
||||
|
||||
## Experimental
|
||||
|
||||
These APIs might not become stable at all or have significant changes before becoming stable.
|
||||
|
||||
@@ -9,6 +9,7 @@ This table covers [Angular versions under active support](reference/releases#act
|
||||
|
||||
| Angular | Node.js | TypeScript | RxJS |
|
||||
| ------------------ | ------------------------------------ | -------------- | ------------------ |
|
||||
| 19.2.x | ^18.19.1 \|\| ^20.11.1 \|\| ^22.0.0 | >=5.5.0 <5.9.0 | ^6.5.3 \|\| ^7.4.0 |
|
||||
| 19.1.x | ^18.19.1 \|\| ^20.11.1 \|\| ^22.0.0 | >=5.5.0 <5.8.0 | ^6.5.3 \|\| ^7.4.0 |
|
||||
| 19.0.x | ^18.19.1 \|\| ^20.11.1 \|\| ^22.0.0 | >=5.5.0 <5.7.0 | ^6.5.3 \|\| ^7.4.0 |
|
||||
| 18.1.x \|\| 18.2.x | ^18.19.1 \|\| ^20.11.1 \|\| ^22.0.0 | >=5.4.0 <5.6.0 | ^6.5.3 \|\| ^7.4.0 |
|
||||
|
||||
@@ -37,11 +37,29 @@ See [Angular's version compatibility](reference/versions) guide for more informa
|
||||
|
||||
To install the Angular CLI, open a terminal window and run the following command:
|
||||
|
||||
<docs-code language="shell">
|
||||
<docs-code-multifile>
|
||||
<docs-code
|
||||
header="npm"
|
||||
>
|
||||
npm install -g @angular/cli
|
||||
</docs-code>
|
||||
<docs-code
|
||||
header="pnpm"
|
||||
>
|
||||
pnpm install -g @angular/cli
|
||||
</docs-code>
|
||||
<docs-code
|
||||
header="yarn"
|
||||
>
|
||||
yarn global add @angular/cli
|
||||
</docs-code>
|
||||
<docs-code
|
||||
header="bun"
|
||||
>
|
||||
bun install -g @angular/cli
|
||||
</docs-code>
|
||||
|
||||
npm install -g @angular/cli
|
||||
|
||||
</docs-code>
|
||||
</docs-code-multifile>
|
||||
|
||||
### Powershell execution policy
|
||||
|
||||
@@ -58,14 +76,32 @@ Carefully read the message displayed after executing the command and follow the
|
||||
|
||||
### Unix permissions
|
||||
|
||||
On some Unix-like setups, global npm scripts may be owned by the root user, so to the above command may fail with a permission error.
|
||||
On some Unix-like setups, global scripts may be owned by the root user, so to the above command may fail with a permission error.
|
||||
Run with `sudo` to execute the command as the root user and enter your password when prompted:
|
||||
|
||||
<docs-code language="sh">
|
||||
<docs-code-multifile>
|
||||
<docs-code
|
||||
header="npm"
|
||||
>
|
||||
sudo npm install -g @angular/cli
|
||||
</docs-code>
|
||||
<docs-code
|
||||
header="pnpm"
|
||||
>
|
||||
sudo pnpm install -g @angular/cli
|
||||
</docs-code>
|
||||
<docs-code
|
||||
header="yarn"
|
||||
>
|
||||
sudo yarn global add @angular/cli
|
||||
</docs-code>
|
||||
<docs-code
|
||||
header="bun"
|
||||
>
|
||||
sudo bun install -g @angular/cli
|
||||
</docs-code>
|
||||
|
||||
sudo npm install -g @angular/cli
|
||||
|
||||
</docs-code>
|
||||
</docs-code-multifile>
|
||||
|
||||
Make sure you understand the implications of running commands as root.
|
||||
|
||||
|
||||
@@ -270,10 +270,14 @@ The Angular CLI down-levels the bundle to a language level that is supported by
|
||||
|
||||
### d.ts bundling / type definition flattening
|
||||
|
||||
As of APF v8 it is now preferred to run [API Extractor](https://api-extractor.com), to bundle TypeScript definitions so that the entire API appears in a single file.
|
||||
As of APF v8, it is recommended to bundle TypeScript definitions.
|
||||
Bundling of type definitions can significantly speed up compilations for users, especially if there are many individual `.ts` source files in your library.
|
||||
|
||||
In prior APF versions each entry point would have a `src` directory next to the .d.ts entry point and this directory contained individual d.ts files matching the structure of the original source code.
|
||||
While this distribution format is still allowed and supported, it is highly discouraged because it confuses tools like IDEs that then offer incorrect autocompletion, and allows users to depend on deep-import paths which are typically not considered to be public API of a library or a package.
|
||||
Angular uses [`rollup-plugin-dts`](https://github.com/Swatinem/rollup-plugin-dts) to flatten `.d.ts` files (using `rollup`, similar to how FESM files are created).
|
||||
|
||||
Using rollup for `.d.ts` bundling is beneficial as it supports code splitting between entry-points.
|
||||
For example, consider you have multiple entrypoints relying on the same shared type, a shared `.d.ts` file would be created along with the larger flattened `.d.ts` files.
|
||||
This is desirable and avoids duplication of types.
|
||||
|
||||
### Tslib
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
"@angular/build": "^19.0.0",
|
||||
"@angular/cli": "^19.0.0",
|
||||
"@angular/compiler-cli": "^19.0.0",
|
||||
"typescript": "~5.7.0"
|
||||
"typescript": "~5.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@ampproject/remapping": {
|
||||
@@ -6538,9 +6538,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/typescript": {
|
||||
"version": "5.7.3",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.3.tgz",
|
||||
"integrity": "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==",
|
||||
"version": "5.8.2",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.2.tgz",
|
||||
"integrity": "sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"bin": {
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
"version": "0.0.0",
|
||||
"scripts": {
|
||||
"ng": "ng",
|
||||
"start": "NG_BUILD_PARALLEL_TS=0 ng serve",
|
||||
"start": "ng serve",
|
||||
"build": "ng build",
|
||||
"watch": "ng build --watch --configuration development"
|
||||
},
|
||||
@@ -23,6 +23,6 @@
|
||||
"@angular/build": "^19.0.0",
|
||||
"@angular/cli": "^19.0.0",
|
||||
"@angular/compiler-cli": "^19.0.0",
|
||||
"typescript": "~5.7.0"
|
||||
"typescript": "~5.8.0"
|
||||
}
|
||||
}
|
||||
|
||||
+738
-689
File diff suppressed because it is too large
Load Diff
@@ -3,7 +3,7 @@
|
||||
"version": "0.0.0",
|
||||
"scripts": {
|
||||
"ng": "ng",
|
||||
"start": "NG_BUILD_PARALLEL_TS=0 ng serve",
|
||||
"start": "ng serve",
|
||||
"build": "ng build",
|
||||
"watch": "ng build --watch --configuration development"
|
||||
},
|
||||
@@ -37,6 +37,6 @@
|
||||
"karma-jasmine-html-reporter": "~2.1.0",
|
||||
"protractor": "~7.0.0",
|
||||
"ts-node": "~10.9.0",
|
||||
"typescript": "~5.7.0"
|
||||
"typescript": "~5.8.0"
|
||||
}
|
||||
}
|
||||
|
||||
+527
-482
File diff suppressed because it is too large
Load Diff
@@ -3,7 +3,7 @@
|
||||
"version": "0.0.0",
|
||||
"scripts": {
|
||||
"ng": "ng",
|
||||
"start": "NG_BUILD_PARALLEL_TS=0 ng serve",
|
||||
"start": "ng serve",
|
||||
"build": "ng build",
|
||||
"watch": "ng build --watch --configuration development"
|
||||
},
|
||||
@@ -22,6 +22,6 @@
|
||||
"@angular/build": "^19.0.0",
|
||||
"@angular/cli": "^19.0.0",
|
||||
"@angular/compiler-cli": "^19.0.0",
|
||||
"typescript": "~5.7.0"
|
||||
"typescript": "~5.8.0"
|
||||
}
|
||||
}
|
||||
|
||||
+533
-488
File diff suppressed because it is too large
Load Diff
@@ -3,7 +3,7 @@
|
||||
"version": "0.0.0",
|
||||
"scripts": {
|
||||
"ng": "ng",
|
||||
"start": "NG_BUILD_PARALLEL_TS=0 ng serve",
|
||||
"start": "ng serve",
|
||||
"build": "ng build",
|
||||
"watch": "ng build --watch --configuration development"
|
||||
},
|
||||
@@ -23,6 +23,6 @@
|
||||
"@angular/build": "^19.0.0",
|
||||
"@angular/cli": "^19.0.0",
|
||||
"@angular/compiler-cli": "^19.0.0",
|
||||
"typescript": "~5.7.0"
|
||||
"typescript": "~5.8.0"
|
||||
}
|
||||
}
|
||||
|
||||
+538
-494
File diff suppressed because it is too large
Load Diff
@@ -3,7 +3,7 @@
|
||||
"version": "0.0.0",
|
||||
"scripts": {
|
||||
"ng": "ng",
|
||||
"start": "NG_BUILD_PARALLEL_TS=0 ng serve",
|
||||
"start": "ng serve",
|
||||
"build": "ng build",
|
||||
"watch": "ng build --watch --configuration development"
|
||||
},
|
||||
@@ -25,6 +25,6 @@
|
||||
"@angular/build": "^19.0.0",
|
||||
"@angular/cli": "^19.0.0",
|
||||
"@angular/compiler-cli": "^19.0.0",
|
||||
"typescript": "~5.7.0"
|
||||
"typescript": "~5.8.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -74,6 +74,10 @@
|
||||
background-color: var(--quinary-contrast) !important;
|
||||
}
|
||||
|
||||
.xterm-dim {
|
||||
color: var(--primary-contrast) !important;
|
||||
}
|
||||
|
||||
.xterm-fg-11 {
|
||||
color: var(--electric-violet) !important;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,166 @@
|
||||
# Commit Message Format
|
||||
|
||||
We have very precise rules over how our Git commit messages must be formatted.
|
||||
This format leads to **easier to read commit history** and makes it analyzable for changelog generation.
|
||||
|
||||
Each commit message consists of a **header**, a **body**, and a **footer**.
|
||||
|
||||
|
||||
```
|
||||
<header>
|
||||
<BLANK LINE>
|
||||
<body>
|
||||
<BLANK LINE>
|
||||
<footer>
|
||||
```
|
||||
|
||||
The `header` is mandatory and must conform to the [Commit Message Header](#commit-header) format.
|
||||
|
||||
The `body` is mandatory for all commits except for those of type "docs".
|
||||
When the body is present it must be at least 20 characters long and must conform to the [Commit Message Body](#commit-body) format.
|
||||
|
||||
The `footer` is optional. The [Commit Message Footer](#commit-footer) format describes what the footer is used for and the structure it must have.
|
||||
|
||||
|
||||
## <a name="commit-header"></a>Commit Message Header
|
||||
|
||||
```
|
||||
<type>(<scope>): <short summary>
|
||||
│ │ │
|
||||
│ │ └─⫸ Summary in present tense. Not capitalized. No period at the end.
|
||||
│ │
|
||||
│ └─⫸ Commit Scope: animations|bazel|benchpress|common|compiler|compiler-cli|core|
|
||||
│ elements|forms|http|language-service|localize|platform-browser|
|
||||
│ platform-browser-dynamic|platform-server|router|service-worker|
|
||||
│ upgrade|zone.js|packaging|changelog|docs-infra|migrations|
|
||||
│ devtools
|
||||
│
|
||||
└─⫸ Commit Type: build|ci|docs|feat|fix|perf|refactor|test
|
||||
```
|
||||
|
||||
The `<type>` and `<summary>` fields are mandatory, the `(<scope>)` field is optional.
|
||||
|
||||
|
||||
### Type
|
||||
|
||||
Must be one of the following:
|
||||
|
||||
| Type | Description |
|
||||
|--------------|-----------------------------------------------------------------------------------------------------|
|
||||
| **build** | Changes that affect the build system or external dependencies (example scopes: gulp, broccoli, npm) |
|
||||
| **ci** | Changes to our CI configuration files and scripts (examples: Github Actions, SauceLabs) |
|
||||
| **docs** | Documentation only changes |
|
||||
| **feat** | A new feature |
|
||||
| **fix** | A bug fix |
|
||||
| **perf** | A code change that improves performance |
|
||||
| **refactor** | A code change that neither fixes a bug nor adds a feature |
|
||||
| **test** | Adding missing tests or correcting existing tests |
|
||||
|
||||
|
||||
### <a name="scope"></a> Scope
|
||||
The scope should be the name of the npm package affected (as perceived by the person reading the changelog generated from commit messages).
|
||||
|
||||
The following is the list of supported scopes:
|
||||
|
||||
* `animations`
|
||||
* `bazel`
|
||||
* `benchpress`
|
||||
* `changelog`
|
||||
* `common`
|
||||
* `compiler`
|
||||
* `compiler-cli`
|
||||
* `core`
|
||||
* `dev-infra`
|
||||
* `devtools`
|
||||
* `docs-infra`
|
||||
* `elements`
|
||||
* `forms`
|
||||
* `http`
|
||||
* `language-service`
|
||||
* `localize`
|
||||
* `migrations`
|
||||
* `packaging`
|
||||
* `platform-browser`
|
||||
* `platform-browser-dynamic`
|
||||
* `platform-server`
|
||||
* `router`
|
||||
* `service-worker`
|
||||
* `upgrade`
|
||||
* `zone.js`
|
||||
|
||||
There are currently a few exceptions to the "use package name" rule:
|
||||
|
||||
* `packaging`: used for changes that change the npm package layout in all of our packages, e.g. public path changes, package.json changes done to all packages, d.ts file/format changes, changes to bundles, etc.
|
||||
|
||||
* `changelog`: used for updating the release notes in CHANGELOG.md
|
||||
|
||||
* `dev-infra`: used for dev-infra related changes within the directories /scripts and /tools
|
||||
|
||||
* `docs-infra`: used for docs-app (angular.dev) related changes within the /adev directory of the repo
|
||||
|
||||
* `migrations`: used for changes to the `ng update` migrations.
|
||||
|
||||
* `devtools`: used for changes in the [browser extension](../devtools/README.md).
|
||||
|
||||
* none/empty string: useful for `test` and `refactor` changes that are done across all packages (e.g. `test: add missing unit tests`) and for docs changes that are not related to a specific package (e.g. `docs: fix typo in tutorial`).
|
||||
|
||||
|
||||
### Summary
|
||||
|
||||
Use the summary field to provide a succinct description of the change:
|
||||
|
||||
* use the imperative, present tense: "change" not "changed" nor "changes"
|
||||
* don't capitalize the first letter
|
||||
* no dot (.) at the end
|
||||
|
||||
|
||||
## <a name="commit-body"></a>Commit Message Body
|
||||
|
||||
Just as in the summary, use the imperative, present tense: "fix" not "fixed" nor "fixes".
|
||||
|
||||
Explain the motivation for the change in the commit message body. This commit message should explain _why_ you are making the change.
|
||||
You can include a comparison of the previous behavior with the new behavior in order to illustrate the impact of the change.
|
||||
|
||||
|
||||
## <a name="commit-footer"></a>Commit Message Footer
|
||||
|
||||
The footer can contain information about breaking changes and deprecations and is also the place to reference GitHub issues and other PRs that this commit closes or is related to.
|
||||
For example:
|
||||
|
||||
```
|
||||
BREAKING CHANGE: <breaking change summary>
|
||||
<BLANK LINE>
|
||||
<breaking change description + migration instructions>
|
||||
<BLANK LINE>
|
||||
<BLANK LINE>
|
||||
Fixes #<issue number>
|
||||
```
|
||||
|
||||
or
|
||||
|
||||
```
|
||||
DEPRECATED: <what is deprecated>
|
||||
<BLANK LINE>
|
||||
<deprecation description + recommended update path>
|
||||
<BLANK LINE>
|
||||
<BLANK LINE>
|
||||
Closes #<pr number>
|
||||
```
|
||||
|
||||
Breaking Change section should start with the phrase `BREAKING CHANGE: ` followed by a *brief* summary of the breaking change, a blank line, and a detailed description of the breaking change that also includes migration instructions.
|
||||
|
||||
Similarly, a Deprecation section should start with `DEPRECATED: ` followed by a short description of what is deprecated, a blank line, and a detailed description of the deprecation that also mentions the recommended update path.
|
||||
|
||||
## Revert commits
|
||||
|
||||
If the commit reverts a previous commit, it should begin with `revert: `, followed by the header of the reverted commit.
|
||||
|
||||
The content of the commit message body should contain:
|
||||
|
||||
- information about the SHA of the commit being reverted in the following format: `This reverts commit <SHA>`,
|
||||
- a clear description of the reason for reverting the commit message.
|
||||
|
||||
|
||||
|
||||
|
||||
[angularjs-commit-message-format]: https://docs.google.com/document/d/1QrDFcIiPjSLDn3EL15IJygNPiHORgU1_OOAqWjiDU5Y/edit#
|
||||
@@ -44,13 +44,13 @@ describe('Viewing component metadata', () => {
|
||||
});
|
||||
|
||||
it('should display correct set of inputs', () => {
|
||||
cy.contains('.cy-inputs', '@Inputs');
|
||||
cy.contains('.cy-inputs', 'Inputs');
|
||||
cy.contains('.cy-inputs mat-tree-node:first span:first', 'inputOne');
|
||||
cy.contains('.cy-inputs mat-tree-node:last span:first', 'inputTwo');
|
||||
});
|
||||
|
||||
it('should display correct set of outputs', () => {
|
||||
cy.contains('.cy-outputs', '@Outputs');
|
||||
cy.contains('.cy-outputs', 'Outputs');
|
||||
cy.contains('.cy-outputs mat-tree-node:first span:first', 'outputOne');
|
||||
cy.contains('.cy-outputs mat-tree-node:last span:first', 'outputTwo');
|
||||
});
|
||||
|
||||
@@ -159,3 +159,18 @@ ts_library(
|
||||
"//packages/core",
|
||||
],
|
||||
)
|
||||
|
||||
ts_test_library(
|
||||
name = "component_tree_test_lib",
|
||||
srcs = ["component-tree.spec.ts"],
|
||||
deps = [
|
||||
":component_tree",
|
||||
"//packages/core",
|
||||
"@npm//jasmine",
|
||||
],
|
||||
)
|
||||
|
||||
karma_web_test_suite(
|
||||
name = "component_tree_test",
|
||||
deps = [":component_tree_test_lib"],
|
||||
)
|
||||
|
||||
@@ -225,7 +225,9 @@ const getRoutes = (messageBus: MessageBus<Events>) => {
|
||||
const rootInjector = (forest[0].resolutionPath ?? []).find((i) => i.name === 'Root');
|
||||
if (rootInjector) {
|
||||
const route = getRouterConfigFromRoot(rootInjector);
|
||||
messageBus.emit('updateRouterTree', [[route]]);
|
||||
if (route) {
|
||||
messageBus.emit('updateRouterTree', [[route]]);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -293,12 +295,17 @@ const getProviderValue = (
|
||||
}
|
||||
};
|
||||
|
||||
const getRouterConfigFromRoot = (injector: SerializedInjector): Route => {
|
||||
const getRouterConfigFromRoot = (injector: SerializedInjector): Route | void => {
|
||||
const serializedProviderRecords = getSerializedProviderRecords(injector) ?? [];
|
||||
const routerInstance = serializedProviderRecords.filter(
|
||||
const routerInstance = serializedProviderRecords.find(
|
||||
(provider) => provider.token === 'Router', // get the instance of router using token
|
||||
);
|
||||
const routerProvider = getProviderValue(injector, routerInstance[0]);
|
||||
|
||||
if (!routerInstance) {
|
||||
return;
|
||||
}
|
||||
|
||||
const routerProvider = getProviderValue(injector, routerInstance);
|
||||
|
||||
return parseRoutes(routerProvider);
|
||||
};
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google LLC All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.dev/license
|
||||
*/
|
||||
|
||||
import {Injector, ɵGlobalDevModeUtils} from '@angular/core';
|
||||
import {getInjectorFromElementNode} from './component-tree';
|
||||
|
||||
type Ng = ɵGlobalDevModeUtils['ng'];
|
||||
|
||||
describe('component-tree', () => {
|
||||
afterEach(() => {
|
||||
delete (globalThis as any).ng;
|
||||
});
|
||||
|
||||
describe('getInjectorFromElementNode', () => {
|
||||
it('returns injector', () => {
|
||||
const injector = Injector.create({
|
||||
providers: [],
|
||||
});
|
||||
|
||||
const ng: Partial<Ng> = {
|
||||
getInjector: jasmine.createSpy('getInjector').and.returnValue(injector),
|
||||
};
|
||||
(globalThis as any).ng = ng;
|
||||
|
||||
const el = document.createElement('div');
|
||||
expect(getInjectorFromElementNode(el)).toBe(injector);
|
||||
expect(ng.getInjector).toHaveBeenCalledOnceWith(el);
|
||||
});
|
||||
|
||||
it('returns `null` when `getInjector` is not supported', () => {
|
||||
(globalThis as any).ng = {};
|
||||
|
||||
const el = document.createElement('div');
|
||||
expect(getInjectorFromElementNode(el)).toBeNull();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -76,7 +76,7 @@ export function getInjectorResolutionPath(injector: Injector): Injector[] {
|
||||
}
|
||||
|
||||
export function getInjectorFromElementNode(element: Node): Injector | null {
|
||||
return ngDebugClient().getInjector(element);
|
||||
return ngDebugClient().getInjector?.(element) ?? null;
|
||||
}
|
||||
|
||||
function getDirectivesFromElement(element: HTMLElement): {
|
||||
@@ -108,9 +108,9 @@ export const getLatestComponentState = (
|
||||
|
||||
const directiveProperties: DirectivesProperties = {};
|
||||
|
||||
const injector = ngDebugClient().getInjector(node.nativeElement!);
|
||||
const injector = getInjectorFromElementNode(node.nativeElement!);
|
||||
|
||||
const injectors = getInjectorResolutionPath(injector);
|
||||
const injectors = injector ? getInjectorResolutionPath(injector) : [];
|
||||
const resolutionPathWithProviders = !ngDebugDependencyInjectionApiIsSupported()
|
||||
? []
|
||||
: injectors.map((injector) => ({
|
||||
@@ -120,11 +120,13 @@ export const getLatestComponentState = (
|
||||
const populateResultSet = (dir: DirectiveInstanceType | ComponentInstanceType) => {
|
||||
const {instance, name} = dir;
|
||||
const metadata = getDirectiveMetadata(instance);
|
||||
metadata.dependencies = getDependenciesForDirective(
|
||||
injector,
|
||||
resolutionPathWithProviders,
|
||||
instance.constructor,
|
||||
);
|
||||
if (injector) {
|
||||
metadata.dependencies = getDependenciesForDirective(
|
||||
injector,
|
||||
resolutionPathWithProviders,
|
||||
instance.constructor,
|
||||
);
|
||||
}
|
||||
|
||||
if (query.propertyQuery.type === PropertyQueryTypes.All) {
|
||||
directiveProperties[dir.name] = {
|
||||
@@ -569,13 +571,13 @@ export const updateState = (updatedStateData: UpdatedStateData): void => {
|
||||
if (updatedStateData.directiveId.directive !== undefined) {
|
||||
const directive = node.directives[updatedStateData.directiveId.directive].instance;
|
||||
mutateComponentOrDirective(updatedStateData, directive);
|
||||
ng.applyChanges(ng.getOwningComponent(directive)!);
|
||||
ng.applyChanges?.(ng.getOwningComponent(directive)!);
|
||||
return;
|
||||
}
|
||||
if (node.component) {
|
||||
const comp = node.component.instance;
|
||||
mutateComponentOrDirective(updatedStateData, comp);
|
||||
ng.applyChanges(comp);
|
||||
ng.applyChanges?.(comp);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -39,7 +39,7 @@ function createOverlay(color: RgbColor): {overlay: HTMLElement; overlayContent:
|
||||
const overlay = document.createElement('div');
|
||||
overlay.className = 'ng-devtools-overlay';
|
||||
overlay.style.backgroundColor = toCSSColor(...color, 0.35);
|
||||
overlay.style.position = 'fixed';
|
||||
overlay.style.position = 'absolute';
|
||||
overlay.style.zIndex = '2147483647';
|
||||
overlay.style.pointerEvents = 'none';
|
||||
overlay.style.display = 'flex';
|
||||
@@ -196,8 +196,8 @@ function showOverlay(
|
||||
const {width, height, top, left} = dimensions;
|
||||
overlay.style.width = ~~width + 'px';
|
||||
overlay.style.height = ~~height + 'px';
|
||||
overlay.style.top = ~~top + 'px';
|
||||
overlay.style.left = ~~left + 'px';
|
||||
overlay.style.top = ~~top + window.scrollY + 'px';
|
||||
overlay.style.left = ~~left + window.scrollX + 'px';
|
||||
|
||||
positionOverlayContent(overlayContent, dimensions, labelPosition);
|
||||
overlayContent.replaceChildren();
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
# load("//devtools/tools:typescript.bzl", "ts_library")
|
||||
load("//devtools/tools:defaults.bzl", "karma_web_test_suite")
|
||||
load("//devtools/tools:ng_module.bzl", "ng_module")
|
||||
load("//devtools/tools:typescript.bzl", "ts_test_library")
|
||||
|
||||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
@@ -13,3 +14,18 @@ ng_module(
|
||||
"//packages/core",
|
||||
],
|
||||
)
|
||||
|
||||
ts_test_library(
|
||||
name = "ng-debug-api_test_lib",
|
||||
srcs = ["ng-debug-api.spec.ts"],
|
||||
deps = [
|
||||
":ng-debug-api",
|
||||
"//packages/core",
|
||||
"@npm//jasmine",
|
||||
],
|
||||
)
|
||||
|
||||
karma_web_test_suite(
|
||||
name = "ng-debug-api_test",
|
||||
deps = [":ng-debug-api_test_lib"],
|
||||
)
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google LLC All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.dev/license
|
||||
*/
|
||||
|
||||
import {ɵGlobalDevModeUtils} from '@angular/core';
|
||||
import {ngDebugDependencyInjectionApiIsSupported} from './ng-debug-api';
|
||||
|
||||
type Ng = ɵGlobalDevModeUtils['ng'];
|
||||
|
||||
describe('ng-debug-api', () => {
|
||||
afterEach(() => {
|
||||
delete (globalThis as any).ng;
|
||||
});
|
||||
|
||||
describe('ngDebugDependencyInjectionApiIsSupported', () => {
|
||||
const goldenNg: Partial<Record<keyof Ng, () => void>> = {
|
||||
getInjector() {},
|
||||
ɵgetInjectorResolutionPath() {},
|
||||
ɵgetDependenciesFromInjectable() {},
|
||||
ɵgetInjectorProviders() {},
|
||||
ɵgetInjectorMetadata() {},
|
||||
};
|
||||
|
||||
it('returns true when required APIs are supported', () => {
|
||||
(globalThis as any).ng = goldenNg;
|
||||
|
||||
expect(ngDebugDependencyInjectionApiIsSupported()).toBeTrue();
|
||||
});
|
||||
|
||||
it('returns false when any required API is missing', () => {
|
||||
(globalThis as any).ng = {...goldenNg, getInjector: undefined};
|
||||
expect(ngDebugDependencyInjectionApiIsSupported()).toBeFalse();
|
||||
|
||||
(globalThis as any).ng = {...goldenNg, ɵgetInjectorResolutionPath: undefined};
|
||||
expect(ngDebugDependencyInjectionApiIsSupported()).toBeFalse();
|
||||
|
||||
(globalThis as any).ng = {...goldenNg, ɵgetDependenciesFromInjectable: undefined};
|
||||
expect(ngDebugDependencyInjectionApiIsSupported()).toBeFalse();
|
||||
|
||||
(globalThis as any).ng = {...goldenNg, ɵgetInjectorProviders: undefined};
|
||||
expect(ngDebugDependencyInjectionApiIsSupported()).toBeFalse();
|
||||
|
||||
(globalThis as any).ng = {...goldenNg, ɵgetInjectorMetadata: undefined};
|
||||
expect(ngDebugDependencyInjectionApiIsSupported()).toBeFalse();
|
||||
});
|
||||
});
|
||||
});
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user