Compare commits

...

99 Commits

Author SHA1 Message Date
Miles Malerba d9e022dae2 release: cut the v19.2.1 release 2025-03-05 11:12:18 -08:00
arturovt 48d899f423 docs(zone.js): update NON-STANDARD-APIS.md (#58098)
This commit makes some grammatical changes to the existing documentation.

PR Close #58098
2025-03-05 17:30:43 +00:00
arturovt 19b6564cb2 docs(zone.js): update STANDARD-APIS.md (#58091)
This commit makes some grammatical changes to the existing documentation and adds additional
information about events.

PR Close #58091
2025-03-05 17:28:41 +00:00
arturovt f24246ef25 refactor(core): inline isFactory within getNodeInjectable (#59824)
This commit inlines the `isFactory` function body directly within `getNodeInjectable` because it is only used once. ESBuild does not inline its body within the function, which can be observed when running the build with `NG_BUILD_MANGLE=0`. The results after inlining are as follows:

```
getNodeInjectable x 70,397,377 ops/sec ±3.88% (52 runs sampled)
getNodeInjectable_inlined x 77,834,432 ops/sec ±3.13% (60 runs sampled)
```

PR Close #59824
2025-03-05 17:24:53 +00:00
Doug Parker 65d2ca9ed3 refactor(devtools): ignore ng.applyChanges if not supported (#60207)
Previously Angular DevTools would throw if `ng.applyChanges` was not defined. Now DevTools silently ignores the issue, assuming `mutateComponentOrDirective` was sufficient to update the application.

PR Close #60207
2025-03-05 16:53:57 +00:00
Matthieu Riegler a2db87ef81 docs(docs-infra): fix secondary menu (#60212)
fixes #58897

PR Close #60212
2025-03-05 11:05:07 +01:00
Matthieu Riegler 94ed3c3630 docs(docs-infra): AppComponent cleanup (#60212)
PR Close #60212
2025-03-05 11:05:07 +01:00
Pawel Kozlowski a64a5755e8 refactor(docs-infra): AppComponent cleanups (#58897)
This change contains multiple cleanups in the AppComponent:
- remove unused code;
- use router input bindings instead of manual read from
  the active route;
- remove isBrowser checks from even handlers (click events
  should not be invoked on the server, right?)

PR Close #58897
2025-03-04 22:20:49 +00:00
Jeevan Mahesha 20add2738c docs: update Angular CLI installation instructions for npm, pnpm, yarn, and bun (#59832)
PR Close #59832
2025-03-04 22:19:47 +00:00
Jeevan Mahesha e27a319261 docs: extend Angular CLI installation instructions for pnpm, yarn, and bun (#59832)
PR Close #59832
2025-03-04 22:19:47 +00:00
Mithilesh Zavar fbbe5d55b1 docs(migrations): Fix indentation in documentation for signal inputs migration (#60204)
PR Close #60204
2025-03-04 22:08:29 +00:00
Matthieu Riegler 507ed58152 refactor(core): extract dirty and markForRefresh from the private ViewRef. (#59122)
This allows better tree shaking from projects without the `element` package.

PR Close #59122
2025-03-04 22:05:41 +00:00
Matthieu Riegler 31bbbe9665 docs(docs-infra): Throw error at build time for invalid links (#59162)
PR Close #59162
2025-03-04 20:07:24 +00:00
Samuel Perez 83abe5f606 docs: fix all brokens links on the API pages (#59162)
This PR fix is for the broken links on the API
pages mentioned in issue #57591.

PR Close #59162
2025-03-04 20:07:24 +00:00
Matthieu Riegler 26b3a8f206 refactor(http): add context property to httpResource (#60188)
This was an oversight, `context` can be supported out of the box.

PR Close #60188
2025-03-04 19:57:22 +00:00
Matthieu Riegler edae26e117 docs(docs-infra): Don't truncate types in symbol extraction (#59909)
PR Close #59909
2025-03-04 19:54:58 +00:00
Alexis 8579ff8352 docs: Remove misleading default value DEFAULT_CURRENCY_CODE (#59977)
PR Close #59977
2025-03-04 19:37:57 +00:00
Matthieu Riegler 2bffacbe5e refactor(core): throw an Cyclic Dependency Error in prod mode (#60118)
Prior to this change, cyclic injection didn't trigger any error in prod mode, resulting into injecting the `CIRCULAR` object.
This could lead to strange errors where no method would be found on the token.

fixes #60074

PR Close #60118
2025-03-04 19:37:19 +00:00
naaajii f24a8c60ad docs: update license year (#59883)
updates the year in footer and placeholder for generated files

PR Close #59883
2025-03-04 19:36:48 +00:00
kirjs 036ee75684 docs: mention that log format makes it analyzable for changelog generation. (#58786)
PR Close #58786
2025-03-04 19:35:55 +00:00
kirjs 33a826b196 docs: sync the scope lists and drop ve (#58786)
PR Close #58786
2025-03-04 19:35:55 +00:00
kirjs 114658cf8c docs: address comments (#58786)
PR Close #58786
2025-03-04 19:35:55 +00:00
kirjs d30ea1b89c docs: move commit message guidelies to a separate file (#58786)
PR Close #58786
2025-03-04 19:35:54 +00:00
michael-small aff6d3d135 docs: update version table for 19.2 (#60173)
PR Close #60173
2025-03-04 17:47:10 +00:00
hawkgs 1c31c0d141 docs(docs-infra): API doc content rendering fixes (#60116)
The PR introduces a few doc content rendering fixes:
- Fix highlighted section heading styles (regression from #59965).
- Convert JSDoc links within 'Usage Notes' sections to HTML and render them.
- Add IDs to doc content headings. This, by itself, makes these headings available in the page ToC.

PR Close #60116
2025-03-04 17:46:31 +00:00
Andrew Scott 3f0116607d fix(language-service): Forward the tags for quick info from the type definition (#59524)
Prior to this commit, the tags from the type definition were dropped.
Tags may include, but are not limited to, deprecation information from
the jsdoc.

PR Close #59524
2025-03-04 17:37:23 +00:00
Lang 080fb08b76 docs: fix the wrong way to get a signal value (#59194)
PR Close #59194
2025-03-04 17:28:09 +00:00
Lang d3dde17693 docs: fix typo (#59194)
Co-authored-by: Enea Jahollari <jahollarienea14@gmail.com>
PR Close #59194
2025-03-04 17:28:09 +00:00
Lang 9ae9d9adde docs: use model API in two-way binding example (#59194)
PR Close #59194
2025-03-04 17:28:08 +00:00
Miles Malerba c615bdcde9 docs(docs-infra): Improve support for @link within the jsdoc. (#60201)
PR Close #60201
2025-03-04 17:13:43 +00:00
hawkgs 79c337448e docs: fix AnimationGroupMetadata description (#59733)
Fix the `animations/group()` link.

PR Close #59733
2025-03-04 17:12:11 +00:00
Jeevan Mahesha c67c551fb8 docs: update template syntax to new control flow (#59760)
PR Close #59760
2025-03-04 17:07:06 +00:00
Matthieu Riegler 1149686da5 docs(docs-infra): hide github link for external repos. (#60114)
fixes #60113

PR Close #60114
2025-03-04 17:02:37 +00:00
Matthieu Riegler 5c3be24712 docs(docs-infra): remove animation package (#60165)
PR Close #60165
2025-03-04 17:01:47 +00:00
Angular Robot 444671ac5b build: update dependency @types/chrome to ^0.0.307 (#60195)
See associated pull request for more information.

PR Close #60195
2025-03-04 17:00:22 +00:00
Matthieu Riegler d1233ebfa8 docs(docs-infra): wait for the DOM to be rendered (#60163)
the code inside `renderExample` is explicitly querying the DOM. We need to wait for it to be rendered for those functions to work.

PR Close #60163
2025-03-04 16:58:14 +00:00
Kristiyan Kostadinov 1c328ac261 build: update to TypeScript 5.8 final (#60176)
Updates the repo to stable version of TypeScript 5.8.

PR Close #60176
2025-03-04 16:57:36 +00:00
vladboisa cd31bfcc3e docs: change broke link (#60189)
Change the broked link to the working one

PR Close #60189
2025-03-03 22:20:53 +00:00
Andrew Scott 27174165a6 refactor(core): Update push/replace navigation to not trigger popstate (#60028)
This matches the spec, though there is a bug in chrome that does trigger these events.

PR Close #60028
2025-03-03 18:40:59 +00:00
Andrew Scott bf89792f8b refactor(core): Update fake navigation to more closely follow the spec (#60028)
This commit updates the implementation of the fake navigation to more
closely follow the spec. This includes links to spec parts as well as
some reorganization to have the flow of the code more exactly follow the
corresponding steps in the spec. This makes it easier to verify that th
behavior is correct.

PR Close #60028
2025-03-03 18:40:59 +00:00
Andrew Scott 49e3ec0dcf refactor(core): Ensure fake navigation is not aborted after it succeeds (#60028)
The navigate event was only ever being cleared when a new navigation
happened. That new navigation would abort the previous one, even if it
had already finished successfully.

PR Close #60028
2025-03-03 18:40:59 +00:00
arturovt c2de5f68b3 fix(common): clean up onUrlChange listener when root scope is destroyed (#60004)
This cleans up the `onUrlChange` listener once the root scope is destroyed.

PR Close #60004
2025-03-03 17:14:33 +00:00
Doug Parker dd70675893 release: bump Angular DevTools version to 1.0.24 (#60148)
PR Close #60148
2025-03-03 17:12:23 +00:00
Andrew Scott 1dd94476b3 fix(compiler-cli): ensure template IDs are not reused if a source file changes (#60152)
This commit fixes a bug where nodes are reused across incremental
compilations. The source file's next template ID is lost because a new
source file is created but nodes may still be retained.

PR Close #60152
2025-02-28 19:53:06 +00:00
Vlad Boisa c288a037b5 docs: fix not working links (#60160)
Remove JSDocs link, and replace the automatic linking with a regular markdown link

Fixes #59983

PR Close #60160
2025-02-28 19:52:33 +00:00
iteriani b81ea4f672 refactor(core): Move getCurrentInjector/setCurrentInjector to primitives package. (#60090)
This change casts the injector back and forth since all instances of
injector currently don't implement the `retrieve` method. Note that
the retrieve method is seen as optional, so that Angular can revert back to
inject if necessary.

PR Close #60090
2025-02-28 18:47:09 +00:00
Kristiyan Kostadinov 1b3b05bf72 fix(core): cache ComponentRef inputs and outputs (#60156)
The set of inputs and outputs of a component is static, but the getter for the `inputs` and `outputs` property was re-computing them every time which the user might not expect. These changes add a couple of lines to cache them instead.

PR Close #60156
2025-02-28 16:31:05 +00:00
Jessica Janiuk cfad089cc3 fix(core): prevents event replay from being called on comment nodes (#60130)
In some rare cases with directives, it is possible that the stash function might be called on a comment node. This actually verifies that the node is an element and exits otherwise.

fixes: #60070

PR Close #60130
2025-02-28 16:30:28 +00:00
Jessica Janiuk 95d227e5ef refactor(core): clean up event replay tests (#60130)
This updates the event replay tests to make them share the structure and utility functions used by hydration tests. This also resolves some soft errors in the event replay tests.

PR Close #60130
2025-02-28 16:30:28 +00:00
Matthieu Riegler ddfaf0cd46 refactor(migrations): Don't migrate twice the same file on the self-closing tag migration (#60065)
This commit fixes an issue when ts files are referenced multiple times (and thus analyzed multiple times) for example from a `tsconfig.json` and `tsconfig.spec.json`.

PR Close #60065
2025-02-28 16:27:40 +00:00
Hakeem b09f4a5c79 docs(docs-infra): add a tooltip to show the full text when nav item text is lengthy (#59038)
PR Close #59038
2025-02-27 18:25:19 +00:00
Hakeem 6c0c012e08 docs(docs-infra): handle long names in the sub nav (#59038)
PR Close #59038
2025-02-27 18:25:19 +00:00
Angular Robot a16dfb1b36 build: update actions/cache digest to d4323d4 (#60145)
See associated pull request for more information.

PR Close #60145
2025-02-27 18:23:42 +00:00
arturovt e549bdec8c refactor(animations): tree-shake regular expressions (#60095)
Adds a pure annotation to regular expressions because if the animations package is indirectly referenced in the code, it would include regular expressions in the bundle, even if they're unused (since `new` is side-effectful).

PR Close #60095
2025-02-27 17:53:25 +00:00
Lang 16ca676db0 docs: replace NgIf with new template syntax @if (#59159)
PR Close #59159
2025-02-27 17:41:54 +00:00
ColinJolivet 187758ccca refactor(docs-infra): remove expand button in example viewer if there is no hidden lines (#59086)
Remove the expand button of an example viewer if there is no hidden lines in it, in order not to confuse people

PR Close #59086
2025-02-27 15:48:02 +00:00
Jessica Janiuk 330c24aed9 fix(core): prevent invoking replay listeners on disconnected nodes (#60103)
This prevents replay from happening on nodes that have been removed from the DOM.

fixes: #59841

PR Close #60103
2025-02-27 15:45:14 +00:00
Matthieu Riegler ada75b605e docs(docs-infra): remove tutorial redirection (#60138)
PR Close #60138
2025-02-27 15:41:19 +00:00
Hryhorii Avcharov b06ed3cbf8 docs: fix mermaid event node text visibility in light mode (#60124)
PR Close #60124
2025-02-27 00:27:12 +00:00
Jessica Janiuk e012cf0acf build: add ignore revs file for git blame (#60117)
This allows for setting git and vscode gitlens to ignore specific commit hashes for bulk changes.

PR Close #60117
2025-02-26 16:19:26 -05:00
kirjs 8e9463bffb release: cut the v19.2.0 release 2025-02-26 16:10:05 -05:00
Matthieu Riegler 9b4727587a docs(docs-infra): remove explicit dectectChanges() (#60098)
PR Close #60098
2025-02-26 12:01:00 -05:00
Matthieu Riegler ee2f07fcff refactor(http): rename map to parse on HttpResourceOptions (#60112)
This reflects more the feature intended for this callback.

PR Close #60112
2025-02-26 11:52:44 -05:00
arturovt f76fd48b4a refactor(core): tree-shake REF_EXTRACTOR_REGEXP (#60081)
The `REF_EXTRACTOR_REGEXP` is a `new` expression that has side effects and is not dropped in production, even if it is unused.

PR Close #60081
2025-02-26 11:17:51 -05:00
arturovt 5de4b0cb70 refactor(core): inline depPath in throwCyclicDependencyError to be dropped (#60084)
Inlines the `depPath` within the `throwCyclicDependencyError`, because it's not being tree-shaken in production.

PR Close #60084
2025-02-26 11:10:38 -05:00
Jens Kuehlers fca21320e6 docs: move release date for v20 by one week (#60111)
PR Close #60111
2025-02-26 11:00:44 -05:00
arturovt c4126c7b9a refactor(forms): tree-shake _checkParentType in production (#60041)
In this commit, we move `_checkParentType` to a separate function to avoid a redundant prototype method in production.

PR Close #60041
2025-02-25 14:12:44 -05:00
Andrew Scott 176475f0fc fix(language-service): provide correct rename info for elements (#60088)
This commit ensures we do not block element rename if it is supported by
other rename providers.

fixes https://github.com/angular/vscode-ng-language-service/issues/2077

PR Close #60088
2025-02-25 14:10:55 -05:00
Kristiyan Kostadinov 7d6636087d fix(common): make types for HttpClient more readable (#59901)
`HttpClient` has a lot of overloads to achieve proper type checking, however each overload is also very long which makes it hard to read on adev. These changes replace the object literal types with `Record` to make them a bit more concise.

PR Close #59901
2025-02-25 13:01:11 -05:00
Pawel Kozlowski ce3a9a24d7 refactor(core): remove performance mark feature for signals (#59991)
Remove the performance mark feature from the Angular signal impl
so more code can be shared between primitives and other frameworks.

PR Close #59991
2025-02-25 11:10:09 -05:00
Kristiyan Kostadinov ef17371ab6 refactor(core): add infrastructure for setting inputs on specific directives (#60075)
Sets up the infrastructure that will allow to write only to a specific directive and its host directives as a base for future functionality.

I've also renamed `setInputsForProperty` to be a bit more explicit that its sets all inputs.

PR Close #60075
2025-02-25 10:58:51 -05:00
Kristiyan Kostadinov 6473214384 refactor(core): avoid memory allocations if there are no host directives (#60075)
Currently the host directive logic disassembles and re-assembles the array of directive matches, in case there are host directives which in most cases produces an identical array.

These changes add some logic so that we only need to allocate the additional memory if we actually need it.

PR Close #60075
2025-02-25 10:58:51 -05:00
Kristiyan Kostadinov f23946abe9 refactor(core): move component logic out of host directives resolution (#60075)
In order to mark a TNode as a component, we need to store the index of the component definition. Currently this happens in the logic that resolves host directives, because the component's host directives can move affect the index.

These changes move the logic out into the directive initialization logic since it doesn't have much to do with host directives.

PR Close #60075
2025-02-25 10:58:51 -05:00
Kristiyan Kostadinov fa6d9c22ac refactor(core): track match index of directives (#60075)
If we want to target an input write to a directive, we have to know the index at which its instance is stored. Technically we can already find this by looking through `TView.data`, but that'll require a linear lookup for each write which can get slow.

These changes introduce the new `TNode.directiveToIndex` map which allows us to quickly find the index of a directive based on its definition, as well as any host directives that its might've brought in.

PR Close #60075
2025-02-25 10:58:51 -05:00
Angular Robot d5f467e601 build: update all non-major dependencies (#60093)
See associated pull request for more information.

PR Close #60093
2025-02-25 10:49:25 -05:00
Angular Robot b5f28006b3 build: update io_bazel_rules_sass digest to 3b3667f (#60092)
See associated pull request for more information.

PR Close #60092
2025-02-25 10:45:36 -05:00
Angular Robot aac3e8acf6 build: update scorecard action dependencies (#60094)
See associated pull request for more information.

PR Close #60094
2025-02-25 10:43:28 -05:00
mgechev 10fb8722fe docs: add a page for custom build pipeline (#60058)
Add a documentation page for creating a custom build pipeline
with community plugins.

PR Close #60058
2025-02-25 10:40:36 -05:00
Aristeidis Bampakos 93c1918ab1 docs: update security guide (#59885)
PR Close #59885
2025-02-25 10:24:27 -05:00
Kristiyan Kostadinov ebfbaeba54 fix(core): capture stack for HMR errors (#60067)
Currently we send the `message` of an error thrown during HMR. That's usually not enough so now we also capture the stack trace.

Relates to https://github.com/angular/angular-cli/issues/29695.

PR Close #60067
2025-02-24 11:56:17 -05:00
hawkgs 9c5a6a6442 refactor(devtools): drop @ from inputs and outputs label (#60053)
Drop '@' from inputs and outputs label to fit in with the new signal-based API.

PR Close #60053
2025-02-24 10:49:13 -05:00
Evgeniy Aksyonov 67366d2f93 docs: fix typo in outputs.md for migrations section (#60055)
PR Close #60055
2025-02-24 10:46:23 -05:00
hawkgs 225e602176 docs(docs-infra): fix the top position of the search dialog (#60012)
Instead of centering the dialog, fix the top position in such way that when the results container is full, the dialog looks centered. This prevents the dialog from "jumping" when you type and the results change.

PR Close #60012
2025-02-24 10:44:19 -05:00
Charles Lyding 02a768e1d2 docs: use @angular/build directly in example/tutorial projects (#60046)
By using the `@angular/build` package directly within projects for each
example and tutorial, the total install size can be reduced. This lowers the amount
of time required to be spent setting up the dependencies in browser
before the example is displayed.

The `@angular/build@19.2.0-next.2` package currently has a total unpacked size of ~115 MB.
The `@angular-devkit/build-angular@19.2.0-next.2` package currently has a total unpacked size of ~291 MB.

This also removes the now unneeded `NG_BUILD_PARALLEL_TS=0` environment variable usage.

PR Close #60046
2025-02-24 10:14:18 -05:00
Kristiyan Kostadinov 35d20cb4b4 refactor(core): do not store input/output public names (#60036)
Reworks the `TNode.inputs` and `TNode.outputs` to not store the public names of bindings. The only reason they were stored was for host directive re-aliasing which is handled through a different data structure now.

PR Close #60036
2025-02-21 14:07:35 -05:00
Kristiyan Kostadinov 352021e1d2 refactor(core): separate host directive inputs from selector-matched ones (#60036)
Currently `TNode.inputs`/`TNode.outputs` store all of the available bindings on that node, no matter if they came from a directive that the user applied directly or from a host directive. This has a couple of drawbacks:
1. We need to store more information that necessary. For example, the only reason we have strings in the arrays is to facilitate host directive aliasing.
2. It doesn't allow us to distinguish which host directives belong to which selector-matched directives.

These changes are a step towards resolving both issues by storing the host directive binding information in separate data structures.

PR Close #60036
2025-02-21 14:07:35 -05:00
Kristiyan Kostadinov 285abbb38f refactor(core): rework how inputs/outputs are initialized (#60036)
Reworks the functions that create the `initialInputs`, `inputs` and `outputs` structures to initilize them within the function, instead of returning them to be initialized later. This will simplify future refactors where they'll produce more than one piece of information.

PR Close #60036
2025-02-21 14:07:35 -05:00
Jessica Janiuk 3fd6551cae docs: remove todo from jsdoc (#60042)
This removes TODO from a JSDoc block that made it show up on adev.

PR Close #60042
2025-02-21 11:16:48 -05:00
Pawel Kozlowski ea8662ab3e refactor(core): remove duplicated checks for ngNonBindable (#60048)
This refactoring consolidates logic around detecting ngNonBindable
mode - previously those checks were done in two separate places.
By doing the check in one place we can simplify the directive resolution
logic.

PR Close #60048
2025-02-21 11:08:50 -05:00
Jessica Janiuk 1d28a42815 refactor(core): clean up when blocks fail to fetch or hydrate (#59740)
There are cases where resources fail to fetch or the DOM has changed due to an if block. This should clean up the remaining promises and any registry references to those blocks in that case.

PR Close #59740
2025-02-21 11:08:04 -05:00
arturovt 21740846b5 refactor(core): drop platform check in ImagePerformanceWarning (#59809)
Replaces `PLATFORM_ID` checks with `ngServerMode`.

PR Close #59809
2025-02-20 12:32:23 -05:00
hawkgs 4fde15438f refactor(devtools): fix component inspector highlighting (#59995)
Use `position: absolute` instead of `position: fixed`. This fixes the odd behavior that can be reproduced when scrolling.

PR Close #59995
2025-02-20 11:56:09 -05:00
Trevor Florence 7a1bf0610e fix(benchpress): Ensure future-proof correct initialization order (#60025)
Future changes to initialization order can cause this previously OK code to start having compiler erroring like: `TS2729: Property 'foo' is used before its initialization.`

PR Close #60025
2025-02-20 11:21:55 -05:00
muhammadali1658 195d0b1ff5 docs: Fix typo ')' in banner-initial.component.spec.ts (#60032)
PR Close #60032
2025-02-20 11:03:11 -05:00
Joey Perrott 053593ef3e ci: update deploy doc site script (#60021)
Update the checked in version of the deploy script

PR Close #60021
2025-02-19 21:01:32 +00:00
Miles Malerba 347c3d5761 build: remove circular deps goldens (#60021)
Removes the circular dependencies golden file and cleans up the
associated pullapprove group. All circular dependencies have been
removed and no new ones may be added.

PR Close #60021
2025-02-19 21:01:32 +00:00
Miles Malerba 8449c071da build: update to latest @angular/ng-dev version (#60021)
Update to the latest version to gain circular deps updates.

PR Close #60021
2025-02-19 21:01:32 +00:00
Andrew Scott f88ed8e470 refactor(core): Add fake navigation to primitives for code sharing (#59857)
This moves the `FakeNavigation` implementation to the primitives folder
so its implementation can be shared with Wiz. This class was initially
copied directly from the Wiz implementation, with some small modifications.
There will still need to be some work done to align the implementations
and fix anything internally that needs adjusting.

PR Close #59857
2025-02-19 20:09:10 +00:00
Jessica Janiuk 11ea0a0f7d release: cut the v19.2.0-rc.0 release 2025-02-19 14:02:38 -05:00
212 changed files with 8462 additions and 13132 deletions
+21
View File
@@ -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 -1
View File
@@ -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)
+1 -1
View File
@@ -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(
+1 -1
View File
@@ -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/
+1 -1
View File
@@ -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/
+3 -3
View File
@@ -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@b56ba49b26e50535fa1e7f7db0f4f7b4bf65d80d # v3.28.10
with:
sarif_file: results.sarif
+1 -1
View File
@@ -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.
#
+2
View File
@@ -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',
-23
View File
@@ -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/**/{*,.*}',
@@ -430,28 +429,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
####################################################################################
+8 -5
View File
@@ -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"
]
}
+71 -101
View File
@@ -1,3 +1,74 @@
<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)) |
### 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 |
| -- | -- | -- |
| [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 +86,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 +108,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 +119,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 +149,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
View File
@@ -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/building-and-testing-angular.md
[github]: https://github.com/angular/angular
[discord]: https://discord.gg/angular
[individual-cla]: https://cla.developers.google.com/about/google-individual
+3 -3
View File
@@ -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",
],
)
@@ -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);
@@ -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.
@@ -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}`,
),
)
);
}
}
@@ -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}`;
}
}
@@ -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"
}
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"
}
}
@@ -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 -2
View File
@@ -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 {
+2
View File
@@ -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;
+1 -2
View File
@@ -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;
}
}
+2 -2
View File
@@ -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,
+18 -26
View File
@@ -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);
-2
View File
@@ -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');
@@ -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',
-4
View File
@@ -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,
+5
View File
@@ -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',
@@ -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
+5 -5
View File
@@ -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
+1 -1
View File
@@ -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 {
@@ -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:
+22 -4
View File
@@ -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>
+1 -1
View File
@@ -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
+1
View File
@@ -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 |
+45 -9
View File
@@ -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.
@@ -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"
},
@@ -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"
},
@@ -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"
},
@@ -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"
},
@@ -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"
},
@@ -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');
});
@@ -569,13 +569,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();
@@ -57,14 +57,14 @@ export class PropertyViewBodyComponent {
>(() => {
return [
{
title: '@Inputs',
title: 'Inputs',
hidden: this.directiveInputControls().dataSource.data.length === 0,
controls: this.directiveInputControls(),
documentation: 'https://angular.dev/api/core/input',
class: 'cy-inputs',
},
{
title: '@Outputs',
title: 'Outputs',
hidden: this.directiveOutputControls().dataSource.data.length === 0,
controls: this.directiveOutputControls(),
documentation: 'https://angular.dev/api/core/output',
@@ -3,8 +3,8 @@
"short_name": "Angular DevTools",
"name": "Angular DevTools",
"description": "Angular DevTools extends Chrome DevTools adding Angular specific debugging and profiling capabilities.",
"version": "1.0.23",
"version_name": "1.0.23",
"version": "1.0.24",
"version_name": "1.0.24",
"minimum_chrome_version": "102",
"content_security_policy": {
"extension_pages": "script-src 'self'; object-src 'self'"
@@ -3,7 +3,7 @@
"short_name": "Angular DevTools",
"name": "Angular DevTools",
"description": "Angular DevTools extends Firefox DevTools adding Angular specific debugging and profiling capabilities.",
"version": "1.0.23",
"version": "1.0.24",
"content_security_policy": "script-src 'self' 'unsafe-eval'; object-src 'self'",
"icons": {
"16": "assets/icon16.png",
-13
View File
@@ -10,16 +10,3 @@ yarn public-api:check
yarn public-api:update
```
### *`packages-circular-deps.json`*
This golden file contains a list of all circular dependencies in the project. As part of the
lint CI job we compare the current circular dependencies against this golden to ensure that
we don't add more cycles. If cycles have been fixed, this file is also updated so that we can
slowly burn down the number of cycles in the project.
To check or update the golden, run the following commands:
```bash
yarn ts-circular-deps:check
yarn ts-circular-deps:approve
```
-1
View File
@@ -1 +0,0 @@
[]
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,41 @@
## API Report File for "@angular/core_primitives_di"
> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
```ts
// @public
export function getCurrentInjector(): Injector | undefined | null;
// @public
export interface InjectionToken<T> extends Type<T> {
// (undocumented)
ɵprov: ɵɵInjectableDeclaration<T>;
}
// @public (undocumented)
export interface Injector {
// (undocumented)
retrieve?<T>(token: InjectionToken<T>, options?: unknown): T | NotFound;
}
// @public
export function isNotFound(e: unknown): e is NotFound;
// @public
export const NOT_FOUND: unique symbol;
// @public
export type NotFound = typeof NOT_FOUND | NotFoundError;
// @public
export class NotFoundError extends Error {
constructor(message: string);
}
// @public (undocumented)
export function setCurrentInjector(injector: Injector | null | undefined): Injector | undefined | null;
// (No @packageDocumentation comment for this package)
```
+1 -1
View File
@@ -20,7 +20,7 @@
"@angular/upgrade": "file:../../dist/packages-dist/upgrade",
"@types/jasmine": "file:../../node_modules/@types/jasmine",
"rxjs": "file:../../node_modules/rxjs",
"typescript": "5.8.1-rc",
"typescript": "5.8.2",
"zone.js": "0.14.10"
},
"scripts": {
+100 -52
View File
@@ -11,19 +11,19 @@
"@jridgewell/trace-mapping" "^0.3.24"
"@angular/animations@file:../../dist/packages-dist/animations":
version "19.2.0-next.2"
version "20.0.0-next.0"
dependencies:
tslib "^2.3.0"
"@angular/common@file:../../dist/packages-dist/common":
version "19.2.0-next.2"
version "20.0.0-next.0"
dependencies:
tslib "^2.3.0"
"@angular/compiler-cli@file:../../dist/packages-dist/compiler-cli":
version "19.2.0-next.2"
version "20.0.0-next.0"
dependencies:
"@babel/core" "7.26.0"
"@babel/core" "7.26.9"
"@jridgewell/sourcemap-codec" "^1.4.14"
chokidar "^4.0.0"
convert-source-map "^1.5.1"
@@ -33,65 +33,65 @@
yargs "^17.2.1"
"@angular/compiler@file:../../dist/packages-dist/compiler":
version "19.2.0-next.2"
version "20.0.0-next.0"
dependencies:
tslib "^2.3.0"
"@angular/core@file:../../dist/packages-dist/core":
version "19.2.0-next.2"
version "20.0.0-next.0"
dependencies:
tslib "^2.3.0"
"@angular/elements@file:../../dist/packages-dist/elements":
version "19.2.0-next.2"
version "20.0.0-next.0"
dependencies:
tslib "^2.3.0"
"@angular/forms@file:../../dist/packages-dist/forms":
version "19.2.0-next.2"
version "20.0.0-next.0"
dependencies:
tslib "^2.3.0"
"@angular/localize@file:../../dist/packages-dist/localize":
version "19.2.0-next.2"
version "20.0.0-next.0"
dependencies:
"@babel/core" "7.26.0"
"@babel/core" "7.26.9"
"@types/babel__core" "7.20.5"
fast-glob "3.3.3"
yargs "^17.2.1"
"@angular/platform-browser-dynamic@file:../../dist/packages-dist/platform-browser-dynamic":
version "19.2.0-next.2"
version "20.0.0-next.0"
dependencies:
tslib "^2.3.0"
"@angular/platform-browser@file:../../dist/packages-dist/platform-browser":
version "19.2.0-next.2"
version "20.0.0-next.0"
dependencies:
tslib "^2.3.0"
"@angular/platform-server@file:../../dist/packages-dist/platform-server":
version "19.2.0-next.2"
version "20.0.0-next.0"
dependencies:
tslib "^2.3.0"
xhr2 "^0.2.0"
"@angular/router@file:../../dist/packages-dist/router":
version "19.2.0-next.2"
version "20.0.0-next.0"
dependencies:
tslib "^2.3.0"
"@angular/service-worker@file:../../dist/packages-dist/service-worker":
version "19.2.0-next.2"
version "20.0.0-next.0"
dependencies:
tslib "^2.3.0"
"@angular/upgrade@file:../../dist/packages-dist/upgrade":
version "19.2.0-next.2"
version "20.0.0-next.0"
dependencies:
tslib "^2.3.0"
"@babel/code-frame@^7.25.9", "@babel/code-frame@^7.26.0":
"@babel/code-frame@^7.25.9", "@babel/code-frame@^7.26.2":
version "7.26.2"
resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.26.2.tgz#4b5fab97d33338eff916235055f0ebc21e573a85"
integrity sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==
@@ -100,33 +100,33 @@
js-tokens "^4.0.0"
picocolors "^1.0.0"
"@babel/compat-data@^7.25.9":
version "7.26.2"
resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.26.2.tgz#278b6b13664557de95b8f35b90d96785850bb56e"
integrity sha512-Z0WgzSEa+aUcdiJuCIqgujCshpMWgUpgOxXotrYPSA53hA3qopNaqcJpyr0hVb1FeWdnqFA35/fUtXgBK8srQg==
"@babel/compat-data@^7.26.5":
version "7.26.8"
resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.26.8.tgz#821c1d35641c355284d4a870b8a4a7b0c141e367"
integrity sha512-oH5UPLMWR3L2wEFLnFJ1TZXqHufiTKAiLfqw5zkhS4dKXLJ10yVztfil/twG8EDTA4F/tvVNw9nOl4ZMslB8rQ==
"@babel/core@7.26.0":
version "7.26.0"
resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.26.0.tgz#d78b6023cc8f3114ccf049eb219613f74a747b40"
integrity sha512-i1SLeK+DzNnQ3LL/CswPCa/E5u4lh1k6IAEphON8F+cXt0t9euTshDru0q7/IqMa1PMPz5RnHuHscF8/ZJsStg==
"@babel/core@7.26.9":
version "7.26.9"
resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.26.9.tgz#71838542a4b1e49dfed353d7acbc6eb89f4a76f2"
integrity sha512-lWBYIrF7qK5+GjY5Uy+/hEgp8OJWOD/rpy74GplYRhEauvbHDeFB8t5hPOZxCZ0Oxf4Cc36tK51/l3ymJysrKw==
dependencies:
"@ampproject/remapping" "^2.2.0"
"@babel/code-frame" "^7.26.0"
"@babel/generator" "^7.26.0"
"@babel/helper-compilation-targets" "^7.25.9"
"@babel/code-frame" "^7.26.2"
"@babel/generator" "^7.26.9"
"@babel/helper-compilation-targets" "^7.26.5"
"@babel/helper-module-transforms" "^7.26.0"
"@babel/helpers" "^7.26.0"
"@babel/parser" "^7.26.0"
"@babel/template" "^7.25.9"
"@babel/traverse" "^7.25.9"
"@babel/types" "^7.26.0"
"@babel/helpers" "^7.26.9"
"@babel/parser" "^7.26.9"
"@babel/template" "^7.26.9"
"@babel/traverse" "^7.26.9"
"@babel/types" "^7.26.9"
convert-source-map "^2.0.0"
debug "^4.1.0"
gensync "^1.0.0-beta.2"
json5 "^2.2.3"
semver "^6.3.1"
"@babel/generator@^7.25.9", "@babel/generator@^7.26.0":
"@babel/generator@^7.25.9":
version "7.26.2"
resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.26.2.tgz#87b75813bec87916210e5e01939a4c823d6bb74f"
integrity sha512-zevQbhbau95nkoxSq3f/DC/SC+EEOUZd3DYqfSkMhY2/wfSeaHV1Ew4vk8e+x8lja31IbyuUa2uQ3JONqKbysw==
@@ -137,12 +137,23 @@
"@jridgewell/trace-mapping" "^0.3.25"
jsesc "^3.0.2"
"@babel/helper-compilation-targets@^7.25.9":
version "7.25.9"
resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.25.9.tgz#55af025ce365be3cdc0c1c1e56c6af617ce88875"
integrity sha512-j9Db8Suy6yV/VHa4qzrj9yZfZxhLWQdVnRlXxmKLYlhWUVB1sB2G5sxuWYXk/whHD9iW76PmNzxZ4UCnTQTVEQ==
"@babel/generator@^7.26.9":
version "7.26.9"
resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.26.9.tgz#75a9482ad3d0cc7188a537aa4910bc59db67cbca"
integrity sha512-kEWdzjOAUMW4hAyrzJ0ZaTOu9OmpyDIQicIh0zg0EEcEkYXZb2TjtBhnHi2ViX7PKwZqF4xwqfAm299/QMP3lg==
dependencies:
"@babel/compat-data" "^7.25.9"
"@babel/parser" "^7.26.9"
"@babel/types" "^7.26.9"
"@jridgewell/gen-mapping" "^0.3.5"
"@jridgewell/trace-mapping" "^0.3.25"
jsesc "^3.0.2"
"@babel/helper-compilation-targets@^7.26.5":
version "7.26.5"
resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.26.5.tgz#75d92bb8d8d51301c0d49e52a65c9a7fe94514d8"
integrity sha512-IXuyn5EkouFJscIDuFF5EsiSolseme1s0CZB+QxVugqJLYmKdxI1VfIBOst0SUu4rnk2Z7kqTwmoO1lp3HIfnA==
dependencies:
"@babel/compat-data" "^7.26.5"
"@babel/helper-validator-option" "^7.25.9"
browserslist "^4.24.0"
lru-cache "^5.1.1"
@@ -190,13 +201,13 @@
resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.25.9.tgz#86e45bd8a49ab7e03f276577f96179653d41da72"
integrity sha512-e/zv1co8pp55dNdEcCynfj9X7nyUKUXoUEwfXqaZt0omVOmDe9oOTdKStH4GmAw6zxMFs50ZayuMfHDKlO7Tfw==
"@babel/helpers@^7.26.0":
version "7.26.0"
resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.26.0.tgz#30e621f1eba5aa45fe6f4868d2e9154d884119a4"
integrity sha512-tbhNuIxNcVb21pInl3ZSjksLCvgdZy9KwJ8brv993QtIVKJBBkYXz4q4ZbAv31GdnC+R90np23L5FbEBlthAEw==
"@babel/helpers@^7.26.9":
version "7.26.9"
resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.26.9.tgz#28f3fb45252fc88ef2dc547c8a911c255fc9fef6"
integrity sha512-Mz/4+y8udxBKdmzt/UjPACs4G3j5SshJJEFFKxlCGPydG4JAHXxjWjAwjd09tf6oINvl1VfMJo+nB7H2YKQ0dA==
dependencies:
"@babel/template" "^7.25.9"
"@babel/types" "^7.26.0"
"@babel/template" "^7.26.9"
"@babel/types" "^7.26.9"
"@babel/parser@^7.1.0", "@babel/parser@^7.20.7":
version "7.25.6"
@@ -205,13 +216,20 @@
dependencies:
"@babel/types" "^7.25.6"
"@babel/parser@^7.25.9", "@babel/parser@^7.26.0", "@babel/parser@^7.26.2":
"@babel/parser@^7.25.9", "@babel/parser@^7.26.2":
version "7.26.2"
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.26.2.tgz#fd7b6f487cfea09889557ef5d4eeb9ff9a5abd11"
integrity sha512-DWMCZH9WA4Maitz2q21SRKHo9QXZxkDsbNZoVD62gusNtNBBqDg9i7uOhASfTfIGNzW+O+r7+jAlM8dwphcJKQ==
dependencies:
"@babel/types" "^7.26.0"
"@babel/parser@^7.26.9":
version "7.26.9"
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.26.9.tgz#d9e78bee6dc80f9efd8f2349dcfbbcdace280fd5"
integrity sha512-81NWa1njQblgZbQHxWHpxxCzNsa3ZwvFqpUg7P+NNUU6f3UU2jBEg4OlF/J6rl8+PQGh1q6/zWScd001YwcA5A==
dependencies:
"@babel/types" "^7.26.9"
"@babel/template@^7.25.9":
version "7.25.9"
resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.25.9.tgz#ecb62d81a8a6f5dc5fe8abfc3901fc52ddf15016"
@@ -221,6 +239,15 @@
"@babel/parser" "^7.25.9"
"@babel/types" "^7.25.9"
"@babel/template@^7.26.9":
version "7.26.9"
resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.26.9.tgz#4577ad3ddf43d194528cff4e1fa6b232fa609bb2"
integrity sha512-qyRplbeIpNZhmzOysF/wFMuP9sctmh2cFzRAZOn1YapxBsE1i9bJIY586R/WBLfLcmcBlM8ROBiQURnnNy+zfA==
dependencies:
"@babel/code-frame" "^7.26.2"
"@babel/parser" "^7.26.9"
"@babel/types" "^7.26.9"
"@babel/traverse@^7.25.9":
version "7.25.9"
resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.25.9.tgz#a50f8fe49e7f69f53de5bea7e413cd35c5e13c84"
@@ -234,6 +261,19 @@
debug "^4.3.1"
globals "^11.1.0"
"@babel/traverse@^7.26.9":
version "7.26.9"
resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.26.9.tgz#4398f2394ba66d05d988b2ad13c219a2c857461a"
integrity sha512-ZYW7L+pL8ahU5fXmNbPF+iZFHCv5scFak7MZ9bwaRPLUhHh7QQEMjZUg0HevihoqCM5iSYHN61EyCoZvqC+bxg==
dependencies:
"@babel/code-frame" "^7.26.2"
"@babel/generator" "^7.26.9"
"@babel/parser" "^7.26.9"
"@babel/template" "^7.26.9"
"@babel/types" "^7.26.9"
debug "^4.3.1"
globals "^11.1.0"
"@babel/types@^7.0.0", "@babel/types@^7.20.7", "@babel/types@^7.25.6":
version "7.25.6"
resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.25.6.tgz#893942ddb858f32ae7a004ec9d3a76b3463ef8e6"
@@ -251,6 +291,14 @@
"@babel/helper-string-parser" "^7.25.9"
"@babel/helper-validator-identifier" "^7.25.9"
"@babel/types@^7.26.9":
version "7.26.9"
resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.26.9.tgz#08b43dec79ee8e682c2ac631c010bdcac54a21ce"
integrity sha512-Y3IR1cRnOxOCDvMmNiym7XpXQ93iGDDPHx+Zj+NM+rg0fBaShfQLkg+hKPaZCEvg5N/LeCo4+Rj/i3FuJsIQaw==
dependencies:
"@babel/helper-string-parser" "^7.25.9"
"@babel/helper-validator-identifier" "^7.25.9"
"@jridgewell/gen-mapping@^0.3.5":
version "0.3.5"
resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz#dcce6aff74bdf6dad1a95802b69b04a2fcb1fb36"
@@ -338,7 +386,7 @@
"@babel/types" "^7.20.7"
"@types/jasmine@file:../../node_modules/@types/jasmine":
version "5.1.5"
version "5.1.7"
ansi-regex@^5.0.1:
version "5.0.1"
@@ -596,7 +644,7 @@ run-parallel@^1.1.9:
queue-microtask "^1.2.2"
"rxjs@file:../../node_modules/rxjs":
version "7.8.1"
version "7.8.2"
dependencies:
tslib "^2.1.0"
@@ -643,10 +691,10 @@ tslib@^2.1.0, tslib@^2.3.0:
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.7.0.tgz#d9b40c5c40ab59e8738f297df3087bf1a2690c01"
integrity sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==
typescript@5.8.1-rc:
version "5.8.1-rc"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.8.1-rc.tgz#678b7586bf1d7fdec0dbf35708ab7bb39ce13c3d"
integrity sha512-D8IlSOUk1E08jpFdK81reYkA1a/4XtEdV6MElOGdbu/uOy1RpEDqNO/onWmqUaLkTyeHmmU/QlWvjcM9cqF85g==
typescript@5.8.2:
version "5.8.2"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.8.2.tgz#8170b3702f74b79db2e5a96207c15e65807999e4"
integrity sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ==
update-browserslist-db@^1.1.1:
version "1.1.1"
+5 -6
View File
@@ -1,6 +1,6 @@
{
"name": "angular-srcs",
"version": "19.2.0-next.3",
"version": "19.2.1",
"private": true,
"description": "Angular - a web framework for modern web apps",
"homepage": "https://github.com/angular/angular",
@@ -35,7 +35,6 @@
"symbol-extractor:check": "node tools/symbol-extractor/run_all_symbols_extractor_tests.js test",
"symbol-extractor:update": "node tools/symbol-extractor/run_all_symbols_extractor_tests.js accept",
"ts-circular-deps:check": "yarn -s ng-dev ts-circular-deps check --config ./packages/circular-deps-test.conf.js",
"ts-circular-deps:approve": "yarn -s ng-dev ts-circular-deps approve --config ./packages/circular-deps-test.conf.js",
"check-tooling-setup": "yarn tsc --project .ng-dev/tsconfig.json && yarn tsc --project scripts/tsconfig.json",
"devtools:devserver": "ibazel run //devtools/src:devserver",
"devtools:devserver:demo-standalone": "ibazel run //devtools/projects/demo-standalone/src:devserver",
@@ -78,7 +77,7 @@
"@types/babel__core": "7.20.5",
"@types/babel__generator": "7.6.8",
"@types/bluebird": "^3.5.27",
"@types/chrome": "^0.0.304",
"@types/chrome": "^0.0.307",
"@types/convert-source-map": "^2.0.0",
"@types/diff": "^7.0.0",
"@types/dom-view-transitions": "^1.0.1",
@@ -136,7 +135,7 @@
"rollup-plugin-sourcemaps": "^0.6.3",
"rxjs": "^7.0.0",
"selenium-webdriver": "3.5.0",
"selenium-webdriver4": "npm:selenium-webdriver@4.28.1",
"selenium-webdriver4": "npm:selenium-webdriver@4.29.0",
"semver-dsl": "^1.0.1",
"shelljs": "^0.8.5",
"source-map": "0.7.4",
@@ -148,7 +147,7 @@
"tslib": "^2.3.0",
"tslint": "6.1.3",
"tsx": "^4.7.2",
"typescript": "5.8.1-rc",
"typescript": "5.8.2",
"webtreemap": "^2.0.1",
"ws": "^8.15.0",
"xhr2": "0.2.1",
@@ -163,7 +162,7 @@
"@angular/animations": "^19.2.0-next",
"@angular/build-tooling": "https://github.com/angular/dev-infra-private-build-tooling-builds.git#ce04ec6cf7604014191821a637e60964a1a3bb4a",
"@angular/core": "^19.2.0-next",
"@angular/ng-dev": "https://github.com/angular/dev-infra-private-ng-dev-builds.git#e29eb33d02c473598361716df343c13016a6c943",
"@angular/ng-dev": "https://github.com/angular/dev-infra-private-ng-dev-builds.git#252589f7bc8fe6ca13b31e02d506ec52e826cdd2",
"@bazel/bazelisk": "^1.7.5",
"@bazel/buildifier": "^8.0.0",
"@bazel/ibazel": "0.16.2",
@@ -78,7 +78,7 @@ import {AnimationDslVisitor} from './animation_dsl_visitor';
import {parseTransitionExpr} from './animation_transition_expr';
const SELF_TOKEN = ':self';
const SELF_TOKEN_REGEX = new RegExp(`s*${SELF_TOKEN}s*,?`, 'g');
const SELF_TOKEN_REGEX = /* @__PURE__ */ new RegExp(`s*${SELF_TOKEN}s*,?`, 'g');
/*
* [Validation]
@@ -47,9 +47,9 @@ import {ElementInstructionMap} from './element_instruction_map';
const ONE_FRAME_IN_MILLISECONDS = 1;
const ENTER_TOKEN = ':enter';
const ENTER_TOKEN_REGEX = new RegExp(ENTER_TOKEN, 'g');
const ENTER_TOKEN_REGEX = /* @__PURE__ */ new RegExp(ENTER_TOKEN, 'g');
const LEAVE_TOKEN = ':leave';
const LEAVE_TOKEN_REGEX = new RegExp(LEAVE_TOKEN, 'g');
const LEAVE_TOKEN_REGEX = /* @__PURE__ */ new RegExp(LEAVE_TOKEN, 'g');
/*
* The code within this file aims to generate web-animations-compatible keyframes from Angular's
+1 -1
View File
@@ -174,7 +174,7 @@ export function validateStyleParams(
}
}
const PARAM_REGEX = new RegExp(
const PARAM_REGEX = /* @__PURE__ */ new RegExp(
`${SUBSTITUTION_EXPR_START}\\s*(.+?)\\s*${SUBSTITUTION_EXPR_END}`,
'g',
);
@@ -54,7 +54,7 @@ export declare type AnimateTimings = {
*
* - `transition()`
* - `sequence()`
* - `{@link animations/group group()}`
* - `{@link /api/animations/group group()}`
* - `query()`
* - `animation()`
* - `useAnimation()`
@@ -118,7 +118,7 @@ export enum AnimationMetadataType {
Sequence = 2,
/**
* Contains a set of animation steps.
* See `{@link animations/group group()}`
* See `{@link /api/animations/group group()}`
*/
Group = 3,
/**
@@ -403,7 +403,7 @@ export interface AnimationSequenceMetadata extends AnimationMetadata {
/**
* Encapsulates an animation group.
* Instantiated and returned by the `{@link animations/group group()}` function.
* Instantiated and returned by the `group()` function.
*
* @publicApi
*/
@@ -636,7 +636,7 @@ export function trigger(name: string, definitions: AnimationMetadata[]): Animati
* @returns An object that encapsulates the animation step.
*
* @usageNotes
* Call within an animation `sequence()`, `{@link animations/group group()}`, or
* Call within an animation `sequence()`, {@link /api/animations/group group()}, or
* `transition()` call to specify an animation step
* that applies given style data to the parent animation for a given amount of time.
*
@@ -740,10 +740,10 @@ export function group(
* @usageNotes
* When you pass an array of steps to a
* `transition()` call, the steps run sequentially by default.
* Compare this to the `{@link animations/group group()}` call, which runs animation steps in
* Compare this to the {@link /api/animations/group group()} call, which runs animation steps in
*parallel.
*
* When a sequence is used within a `{@link animations/group group()}` or a `transition()` call,
* When a sequence is used within a {@link /api/animations/group group()} or a `transition()` call,
* execution continues to the next instruction only after each of the inner animation
* steps have completed.
*
@@ -65,6 +65,10 @@ describe('@angular/core ng_package', () => {
types: './index.d.ts',
default: './fesm2022/core.mjs',
},
'./primitives/di': {
types: './primitives/di/index.d.ts',
default: './fesm2022/primitives/di.mjs',
},
'./primitives/event-dispatch': {
types: './primitives/event-dispatch/index.d.ts',
default: './fesm2022/primitives/event-dispatch.mjs',
@@ -70,7 +70,7 @@ License: MIT
/**
* @license Angular v0.0.0
* (c) 2010-2024 Google LLC. https://angular.io/
* (c) 2010-2025 Google LLC. https://angular.io/
* License: MIT
*/
@@ -120,7 +120,7 @@ Hello
/**
* @license Angular v0.0.0
* (c) 2010-2024 Google LLC. https://angular.io/
* (c) 2010-2025 Google LLC. https://angular.io/
* License: MIT
*/
@@ -149,7 +149,7 @@ export { A11yModule };
/**
* @license Angular v0.0.0
* (c) 2010-2024 Google LLC. https://angular.io/
* (c) 2010-2025 Google LLC. https://angular.io/
* License: MIT
*/
@@ -190,7 +190,7 @@ export { MyService };
/**
* @license Angular v0.0.0
* (c) 2010-2024 Google LLC. https://angular.io/
* (c) 2010-2025 Google LLC. https://angular.io/
* License: MIT
*/
@@ -220,7 +220,7 @@ export { SecondaryModule, a };
/**
* @license Angular v0.0.0
* (c) 2010-2024 Google LLC. https://angular.io/
* (c) 2010-2025 Google LLC. https://angular.io/
* License: MIT
*/
@@ -249,7 +249,7 @@ export { MyModule };
/**
* @license Angular v0.0.0
* (c) 2010-2024 Google LLC. https://angular.io/
* (c) 2010-2025 Google LLC. https://angular.io/
* License: MIT
*/
@@ -275,7 +275,7 @@ export { }
/**
* @license Angular v0.0.0
* (c) 2010-2024 Google LLC. https://angular.io/
* (c) 2010-2025 Google LLC. https://angular.io/
* License: MIT
*/
@@ -331,7 +331,7 @@ export { }
/**
* @license Angular v0.0.0
* (c) 2010-2024 Google LLC. https://angular.io/
* (c) 2010-2025 Google LLC. https://angular.io/
* License: MIT
*/
@@ -54,7 +54,7 @@ License: MIT
/**
* @license Angular v0.0.0
* (c) 2010-2024 Google LLC. https://angular.io/
* (c) 2010-2025 Google LLC. https://angular.io/
* License: MIT
*/
@@ -68,7 +68,7 @@ export { VERSION };
/**
* @license Angular v0.0.0
* (c) 2010-2024 Google LLC. https://angular.io/
* (c) 2010-2025 Google LLC. https://angular.io/
* License: MIT
*/
@@ -98,7 +98,7 @@ export { PortalModule, a };
/**
* @license Angular v0.0.0
* (c) 2010-2024 Google LLC. https://angular.io/
* (c) 2010-2025 Google LLC. https://angular.io/
* License: MIT
*/
@@ -114,7 +114,7 @@ export { dispatchFakeEvent };
/**
* @license Angular v0.0.0
* (c) 2010-2024 Google LLC. https://angular.io/
* (c) 2010-2025 Google LLC. https://angular.io/
* License: MIT
*/
@@ -157,7 +157,7 @@ export { }
/**
* @license Angular v0.0.0
* (c) 2010-2024 Google LLC. https://angular.io/
* (c) 2010-2025 Google LLC. https://angular.io/
* License: MIT
*/
@@ -179,7 +179,7 @@ export { }
/**
* @license Angular v0.0.0
* (c) 2010-2024 Google LLC. https://angular.io/
* (c) 2010-2025 Google LLC. https://angular.io/
* License: MIT
*/
@@ -46,9 +46,11 @@ export class JsonFileReporter extends Reporter {
@Inject(Options.NOW) private _now: Function,
) {
super();
this.textReporter = new TextReporterBase(this._columnWidth, this._description);
}
private textReporter = new TextReporterBase(this._columnWidth, this._description);
private textReporter: TextReporterBase;
override reportMeasureValues(measureValues: MeasureValues): Promise<any> {
return Promise.resolve(null);
-3
View File
@@ -10,10 +10,7 @@ const path = require('path');
module.exports = {
baseDir: '../',
goldenFile: '../goldens/circular-deps/packages.json',
glob: `./**/*.ts`,
// Command that will be displayed if the golden needs to be updated.
approveCommand: 'yarn ts-circular-deps:approve',
resolveModule: resolveModule,
ignoreTypeOnlyChecks: true,
};
+1
View File
@@ -30,6 +30,7 @@ ng_module(
),
deps = [
"//packages/core",
"//packages/core/primitives/dom-navigation",
"@npm//rxjs",
],
)
File diff suppressed because it is too large Load Diff
+3 -3
View File
@@ -239,7 +239,7 @@ function makeHttpResourceFn<TRaw>(responseType: 'arraybuffer' | 'blob' | 'json'
injector,
() => normalizeRequest(request, responseType),
options?.defaultValue,
options?.map as (value: unknown) => TResult,
options?.parse as (value: unknown) => TResult,
) as HttpResourceRef<TResult>;
};
}
@@ -312,7 +312,7 @@ class HttpResourceImpl<T>
injector: Injector,
request: () => HttpRequest<T> | undefined,
defaultValue: T,
map?: (value: unknown) => T,
parse?: (value: unknown) => T,
) {
super(
request,
@@ -342,7 +342,7 @@ class HttpResourceImpl<T>
this._headers.set(event.headers);
this._statusCode.set(event.status);
try {
send({value: map ? map(event.body) : (event.body as T)});
send({value: parse ? parse(event.body) : (event.body as T)});
} catch (error) {
send({error});
}
+8 -2
View File
@@ -10,6 +10,7 @@ import type {Injector, ResourceRef, Signal, ValueEqualityFn, WritableResource} f
import type {HttpHeaders} from './headers';
import type {HttpParams} from './params';
import type {HttpProgressEvent} from './response';
import {HttpContext} from './context';
/**
* The structure of an `httpResource` request which will be sent to the backend.
@@ -50,6 +51,11 @@ export interface HttpResourceRequest {
*/
headers?: HttpHeaders | Record<string, string | ReadonlyArray<string>>;
/**
* Context of the request stored in a dictionary of key-value pairs.
*/
context?: HttpContext;
/**
* If `true`, progress events will be enabled for the request and delivered through the
* `HttpResource.progress` signal.
@@ -81,12 +87,12 @@ export interface HttpResourceOptions<TResult, TRaw> {
/**
* Transform the result of the HTTP request before it's delivered to the resource.
*
* `map` receives the value from the HTTP layer as its raw type (e.g. as `unknown` for JSON data).
* `parse` receives the value from the HTTP layer as its raw type (e.g. as `unknown` for JSON data).
* It can be used to validate or transform the type of the resource, and return a more specific
* type. This is also useful for validating backend responses using a runtime schema validation
* library such as Zod.
*/
map?: (value: TRaw) => TResult;
parse?: (value: TRaw) => TResult;
/**
* Value that the resource will take when in Idle, Loading, or Error states.
+1 -1
View File
@@ -154,7 +154,7 @@ describe('httpResource', () => {
},
{
injector: TestBed.inject(Injector),
map: (value) => JSON.stringify(value),
parse: (value) => JSON.stringify(value),
},
);
TestBed.flushEffects();
@@ -23,7 +23,7 @@ import {
} from '@angular/core';
/**
* Instantiates a {@link Component} type and inserts its Host View into the current View.
* Instantiates a {@link /api/core/Component Component} type and inserts its Host View into the current View.
* `NgComponentOutlet` provides a declarative approach for dynamic component creation.
*
* `NgComponentOutlet` requires a component type, if a falsy value is set the view will clear and
@@ -26,8 +26,8 @@ import {DOCUMENT} from '../dom_tokens';
* when they need to interact with the DOM APIs like pushState, popState, etc.
*
* {@link LocationStrategy} in turn is used by the {@link Location} service which is used directly
* by the {@link Router} in order to navigate between routes. Since all interactions between {@link
* Router} /
* by the {@link /api/router/Router Router} in order to navigate between routes. Since all interactions between
* {@link /api/router/Router Router} /
* {@link Location} / {@link LocationStrategy} and DOM APIs flow through the `PlatformLocation`
* class, they are all platform-agnostic.
*
@@ -1,181 +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
*/
export interface NavigationEventMap {
navigate: NavigateEvent;
navigatesuccess: Event;
navigateerror: ErrorEvent;
currententrychange: NavigationCurrentEntryChangeEvent;
}
export interface NavigationResult {
committed: Promise<NavigationHistoryEntry>;
finished: Promise<NavigationHistoryEntry>;
}
export declare class Navigation extends EventTarget {
entries(): NavigationHistoryEntry[];
readonly currentEntry: NavigationHistoryEntry | null;
updateCurrentEntry(options: NavigationUpdateCurrentEntryOptions): void;
readonly transition: NavigationTransition | null;
readonly canGoBack: boolean;
readonly canGoForward: boolean;
navigate(url: string, options?: NavigationNavigateOptions): NavigationResult;
reload(options?: NavigationReloadOptions): NavigationResult;
traverseTo(key: string, options?: NavigationOptions): NavigationResult;
back(options?: NavigationOptions): NavigationResult;
forward(options?: NavigationOptions): NavigationResult;
onnavigate: ((this: Navigation, ev: NavigateEvent) => any) | null;
onnavigatesuccess: ((this: Navigation, ev: Event) => any) | null;
onnavigateerror: ((this: Navigation, ev: ErrorEvent) => any) | null;
oncurrententrychange: ((this: Navigation, ev: NavigationCurrentEntryChangeEvent) => any) | null;
addEventListener<K extends keyof NavigationEventMap>(
type: K,
listener: (this: Navigation, ev: NavigationEventMap[K]) => any,
options?: boolean | AddEventListenerOptions,
): void;
addEventListener(
type: string,
listener: EventListenerOrEventListenerObject,
options?: boolean | AddEventListenerOptions,
): void;
removeEventListener<K extends keyof NavigationEventMap>(
type: K,
listener: (this: Navigation, ev: NavigationEventMap[K]) => any,
options?: boolean | EventListenerOptions,
): void;
removeEventListener(
type: string,
listener: EventListenerOrEventListenerObject,
options?: boolean | EventListenerOptions,
): void;
}
export declare class NavigationTransition {
readonly navigationType: NavigationTypeString;
readonly from: NavigationHistoryEntry;
readonly finished: Promise<void>;
}
export interface NavigationHistoryEntryEventMap {
dispose: Event;
}
export declare class NavigationHistoryEntry extends EventTarget {
readonly key: string;
readonly id: string;
readonly url: string | null;
readonly index: number;
readonly sameDocument: boolean;
getState(): unknown;
ondispose: ((this: NavigationHistoryEntry, ev: Event) => any) | null;
addEventListener<K extends keyof NavigationHistoryEntryEventMap>(
type: K,
listener: (this: NavigationHistoryEntry, ev: NavigationHistoryEntryEventMap[K]) => any,
options?: boolean | AddEventListenerOptions,
): void;
addEventListener(
type: string,
listener: EventListenerOrEventListenerObject,
options?: boolean | AddEventListenerOptions,
): void;
removeEventListener<K extends keyof NavigationHistoryEntryEventMap>(
type: K,
listener: (this: NavigationHistoryEntry, ev: NavigationHistoryEntryEventMap[K]) => any,
options?: boolean | EventListenerOptions,
): void;
removeEventListener(
type: string,
listener: EventListenerOrEventListenerObject,
options?: boolean | EventListenerOptions,
): void;
}
type NavigationTypeString = 'reload' | 'push' | 'replace' | 'traverse';
export interface NavigationUpdateCurrentEntryOptions {
state: unknown;
}
export interface NavigationOptions {
info?: unknown;
}
export interface NavigationNavigateOptions extends NavigationOptions {
state?: unknown;
history?: 'auto' | 'push' | 'replace';
}
export interface NavigationReloadOptions extends NavigationOptions {
state?: unknown;
}
export declare class NavigationCurrentEntryChangeEvent extends Event {
constructor(type: string, eventInit?: NavigationCurrentEntryChangeEventInit);
readonly navigationType: NavigationTypeString | null;
readonly from: NavigationHistoryEntry;
}
export interface NavigationCurrentEntryChangeEventInit extends EventInit {
navigationType?: NavigationTypeString | null;
from: NavigationHistoryEntry;
}
export declare class NavigateEvent extends Event {
constructor(type: string, eventInit?: NavigateEventInit);
readonly navigationType: NavigationTypeString;
readonly canIntercept: boolean;
readonly userInitiated: boolean;
readonly hashChange: boolean;
readonly destination: NavigationDestination;
readonly signal: AbortSignal;
readonly formData: FormData | null;
readonly downloadRequest: string | null;
readonly info?: unknown;
intercept(options?: NavigationInterceptOptions): void;
scroll(): void;
}
export interface NavigateEventInit extends EventInit {
navigationType?: NavigationTypeString;
canIntercept?: boolean;
userInitiated?: boolean;
hashChange?: boolean;
destination: NavigationDestination;
signal: AbortSignal;
formData?: FormData | null;
downloadRequest?: string | null;
info?: unknown;
}
export interface NavigationInterceptOptions {
handler?: () => Promise<void>;
focusReset?: 'after-transition' | 'manual';
scroll?: 'after-transition' | 'manual';
}
export declare class NavigationDestination {
readonly url: string;
readonly key: string | null;
readonly id: string | null;
readonly index: number;
readonly sameDocument: boolean;
getState(): unknown;
}
@@ -6,20 +6,19 @@
* found in the LICENSE file at https://angular.dev/license
*/
import {Injectable} from '@angular/core';
import {
NavigateEvent,
Navigation,
NavigationCurrentEntryChangeEvent,
NavigationHistoryEntry,
NavigationNavigateOptions,
NavigationOptions,
NavigationReloadOptions,
NavigationResult,
NavigationTransition,
NavigationUpdateCurrentEntryOptions,
} from './navigation_types';
Injectable,
ɵNavigateEvent as NavigateEvent,
ɵNavigation as Navigation,
ɵNavigationCurrentEntryChangeEvent as NavigationCurrentEntryChangeEvent,
ɵNavigationHistoryEntry as NavigationHistoryEntry,
ɵNavigationNavigateOptions as NavigationNavigateOptions,
ɵNavigationOptions as NavigationOptions,
ɵNavigationReloadOptions as NavigationReloadOptions,
ɵNavigationResult as NavigationResult,
ɵNavigationTransition as NavigationTransition,
ɵNavigationUpdateCurrentEntryOptions as NavigationUpdateCurrentEntryOptions,
} from '@angular/core';
/**
* This class wraps the platform Navigation API which allows server-specific and test
+1
View File
@@ -10,6 +10,7 @@ ng_module(
deps = [
"//packages/common",
"//packages/core",
"//packages/core/testing",
"@npm//rxjs",
],
)
@@ -6,972 +6,4 @@
* found in the LICENSE file at https://angular.dev/license
*/
import {
NavigateEvent,
Navigation,
NavigationCurrentEntryChangeEvent,
NavigationDestination,
NavigationHistoryEntry,
NavigationInterceptOptions,
NavigationNavigateOptions,
NavigationOptions,
NavigationReloadOptions,
NavigationResult,
NavigationTransition,
NavigationTypeString,
NavigationUpdateCurrentEntryOptions,
} from './navigation_types';
/**
* Fake implementation of user agent history and navigation behavior. This is a
* high-fidelity implementation of browser behavior that attempts to emulate
* things like traversal delay.
*/
export class FakeNavigation implements Navigation {
/**
* The fake implementation of an entries array. Only same-document entries
* allowed.
*/
private readonly entriesArr: FakeNavigationHistoryEntry[] = [];
/**
* The current active entry index into `entriesArr`.
*/
private currentEntryIndex = 0;
/**
* The current navigate event.
*/
private navigateEvent: InternalFakeNavigateEvent | undefined = undefined;
/**
* A Map of pending traversals, so that traversals to the same entry can be
* re-used.
*/
private readonly traversalQueue = new Map<string, InternalNavigationResult>();
/**
* A Promise that resolves when the previous traversals have finished. Used to
* simulate the cross-process communication necessary for traversals.
*/
private nextTraversal = Promise.resolve();
/**
* A prospective current active entry index, which includes unresolved
* traversals. Used by `go` to determine where navigations are intended to go.
*/
private prospectiveEntryIndex = 0;
/**
* A test-only option to make traversals synchronous, rather than emulate
* cross-process communication.
*/
private synchronousTraversals = false;
/** Whether to allow a call to setInitialEntryForTesting. */
private canSetInitialEntry = true;
/** `EventTarget` to dispatch events. */
private eventTarget: EventTarget;
/** The next unique id for created entries. Replace recreates this id. */
private nextId = 0;
/** The next unique key for created entries. Replace inherits this id. */
private nextKey = 0;
/** Whether this fake is disposed. */
private disposed = false;
/** Equivalent to `navigation.currentEntry`. */
get currentEntry(): FakeNavigationHistoryEntry {
return this.entriesArr[this.currentEntryIndex];
}
get canGoBack(): boolean {
return this.currentEntryIndex > 0;
}
get canGoForward(): boolean {
return this.currentEntryIndex < this.entriesArr.length - 1;
}
constructor(
private readonly window: Window,
startURL: `http${string}`,
) {
this.eventTarget = this.window.document.createElement('div');
// First entry.
this.setInitialEntryForTesting(startURL);
}
/**
* Sets the initial entry.
*/
private setInitialEntryForTesting(
url: `http${string}`,
options: {historyState: unknown; state?: unknown} = {historyState: null},
) {
if (!this.canSetInitialEntry) {
throw new Error(
'setInitialEntryForTesting can only be called before any ' + 'navigation has occurred',
);
}
const currentInitialEntry = this.entriesArr[0];
this.entriesArr[0] = new FakeNavigationHistoryEntry(new URL(url).toString(), {
index: 0,
key: currentInitialEntry?.key ?? String(this.nextKey++),
id: currentInitialEntry?.id ?? String(this.nextId++),
sameDocument: true,
historyState: options?.historyState,
state: options.state,
});
}
/** Returns whether the initial entry is still eligible to be set. */
canSetInitialEntryForTesting(): boolean {
return this.canSetInitialEntry;
}
/**
* Sets whether to emulate traversals as synchronous rather than
* asynchronous.
*/
setSynchronousTraversalsForTesting(synchronousTraversals: boolean) {
this.synchronousTraversals = synchronousTraversals;
}
/** Equivalent to `navigation.entries()`. */
entries(): FakeNavigationHistoryEntry[] {
return this.entriesArr.slice();
}
/** Equivalent to `navigation.navigate()`. */
navigate(url: string, options?: NavigationNavigateOptions): FakeNavigationResult {
const fromUrl = new URL(this.currentEntry.url!);
const toUrl = new URL(url, this.currentEntry.url!);
let navigationType: NavigationTypeString;
if (!options?.history || options.history === 'auto') {
// Auto defaults to push, but if the URLs are the same, is a replace.
if (fromUrl.toString() === toUrl.toString()) {
navigationType = 'replace';
} else {
navigationType = 'push';
}
} else {
navigationType = options.history;
}
const hashChange = isHashChange(fromUrl, toUrl);
const destination = new FakeNavigationDestination({
url: toUrl.toString(),
state: options?.state,
sameDocument: hashChange,
historyState: null,
});
const result = new InternalNavigationResult();
this.userAgentNavigate(destination, result, {
navigationType,
cancelable: true,
canIntercept: true,
// Always false for navigate().
userInitiated: false,
hashChange,
info: options?.info,
});
return {
committed: result.committed,
finished: result.finished,
};
}
/** Equivalent to `history.pushState()`. */
pushState(data: unknown, title: string, url?: string): void {
this.pushOrReplaceState('push', data, title, url);
}
/** Equivalent to `history.replaceState()`. */
replaceState(data: unknown, title: string, url?: string): void {
this.pushOrReplaceState('replace', data, title, url);
}
private pushOrReplaceState(
navigationType: NavigationTypeString,
data: unknown,
_title: string,
url?: string,
): void {
const fromUrl = new URL(this.currentEntry.url!);
const toUrl = url ? new URL(url, this.currentEntry.url!) : fromUrl;
const hashChange = isHashChange(fromUrl, toUrl);
const destination = new FakeNavigationDestination({
url: toUrl.toString(),
sameDocument: true,
historyState: data,
});
const result = new InternalNavigationResult();
this.userAgentNavigate(destination, result, {
navigationType,
cancelable: true,
canIntercept: true,
// Always false for pushState() or replaceState().
userInitiated: false,
hashChange,
skipPopState: true,
});
}
/** Equivalent to `navigation.traverseTo()`. */
traverseTo(key: string, options?: NavigationOptions): FakeNavigationResult {
const fromUrl = new URL(this.currentEntry.url!);
const entry = this.findEntry(key);
if (!entry) {
const domException = new DOMException('Invalid key', 'InvalidStateError');
const committed = Promise.reject(domException);
const finished = Promise.reject(domException);
committed.catch(() => {});
finished.catch(() => {});
return {
committed,
finished,
};
}
if (entry === this.currentEntry) {
return {
committed: Promise.resolve(this.currentEntry),
finished: Promise.resolve(this.currentEntry),
};
}
if (this.traversalQueue.has(entry.key)) {
const existingResult = this.traversalQueue.get(entry.key)!;
return {
committed: existingResult.committed,
finished: existingResult.finished,
};
}
const hashChange = isHashChange(fromUrl, new URL(entry.url!, this.currentEntry.url!));
const destination = new FakeNavigationDestination({
url: entry.url!,
state: entry.getState(),
historyState: entry.getHistoryState(),
key: entry.key,
id: entry.id,
index: entry.index,
sameDocument: entry.sameDocument,
});
this.prospectiveEntryIndex = entry.index;
const result = new InternalNavigationResult();
this.traversalQueue.set(entry.key, result);
this.runTraversal(() => {
this.traversalQueue.delete(entry.key);
this.userAgentNavigate(destination, result, {
navigationType: 'traverse',
cancelable: true,
canIntercept: true,
// Always false for traverseTo().
userInitiated: false,
hashChange,
info: options?.info,
});
});
return {
committed: result.committed,
finished: result.finished,
};
}
/** Equivalent to `navigation.back()`. */
back(options?: NavigationOptions): FakeNavigationResult {
if (this.currentEntryIndex === 0) {
const domException = new DOMException('Cannot go back', 'InvalidStateError');
const committed = Promise.reject(domException);
const finished = Promise.reject(domException);
committed.catch(() => {});
finished.catch(() => {});
return {
committed,
finished,
};
}
const entry = this.entriesArr[this.currentEntryIndex - 1];
return this.traverseTo(entry.key, options);
}
/** Equivalent to `navigation.forward()`. */
forward(options?: NavigationOptions): FakeNavigationResult {
if (this.currentEntryIndex === this.entriesArr.length - 1) {
const domException = new DOMException('Cannot go forward', 'InvalidStateError');
const committed = Promise.reject(domException);
const finished = Promise.reject(domException);
committed.catch(() => {});
finished.catch(() => {});
return {
committed,
finished,
};
}
const entry = this.entriesArr[this.currentEntryIndex + 1];
return this.traverseTo(entry.key, options);
}
/**
* Equivalent to `history.go()`.
* Note that this method does not actually work precisely to how Chrome
* does, instead choosing a simpler model with less unexpected behavior.
* Chrome has a few edge case optimizations, for instance with repeated
* `back(); forward()` chains it collapses certain traversals.
*/
go(direction: number): void {
const targetIndex = this.prospectiveEntryIndex + direction;
if (targetIndex >= this.entriesArr.length || targetIndex < 0) {
return;
}
this.prospectiveEntryIndex = targetIndex;
this.runTraversal(() => {
// Check again that destination is in the entries array.
if (targetIndex >= this.entriesArr.length || targetIndex < 0) {
return;
}
const fromUrl = new URL(this.currentEntry.url!);
const entry = this.entriesArr[targetIndex];
const hashChange = isHashChange(fromUrl, new URL(entry.url!, this.currentEntry.url!));
const destination = new FakeNavigationDestination({
url: entry.url!,
state: entry.getState(),
historyState: entry.getHistoryState(),
key: entry.key,
id: entry.id,
index: entry.index,
sameDocument: entry.sameDocument,
});
const result = new InternalNavigationResult();
this.userAgentNavigate(destination, result, {
navigationType: 'traverse',
cancelable: true,
canIntercept: true,
// Always false for go().
userInitiated: false,
hashChange,
});
});
}
/** Runs a traversal synchronously or asynchronously */
private runTraversal(traversal: () => void) {
if (this.synchronousTraversals) {
traversal();
return;
}
// Each traversal occupies a single timeout resolution.
// This means that Promises added to commit and finish should resolve
// before the next traversal.
this.nextTraversal = this.nextTraversal.then(() => {
return new Promise<void>((resolve) => {
setTimeout(() => {
resolve();
traversal();
});
});
});
}
/** Equivalent to `navigation.addEventListener()`. */
addEventListener(
type: string,
callback: EventListenerOrEventListenerObject,
options?: AddEventListenerOptions | boolean,
) {
this.eventTarget.addEventListener(type, callback, options);
}
/** Equivalent to `navigation.removeEventListener()`. */
removeEventListener(
type: string,
callback: EventListenerOrEventListenerObject,
options?: EventListenerOptions | boolean,
) {
this.eventTarget.removeEventListener(type, callback, options);
}
/** Equivalent to `navigation.dispatchEvent()` */
dispatchEvent(event: Event): boolean {
return this.eventTarget.dispatchEvent(event);
}
/** Cleans up resources. */
dispose() {
// Recreate eventTarget to release current listeners.
// `document.createElement` because NodeJS `EventTarget` is incompatible with Domino's `Event`.
this.eventTarget = this.window.document.createElement('div');
this.disposed = true;
}
/** Returns whether this fake is disposed. */
isDisposed() {
return this.disposed;
}
/** Implementation for all navigations and traversals. */
private userAgentNavigate(
destination: FakeNavigationDestination,
result: InternalNavigationResult,
options: InternalNavigateOptions,
) {
// The first navigation should disallow any future calls to set the initial
// entry.
this.canSetInitialEntry = false;
if (this.navigateEvent) {
this.navigateEvent.cancel(new DOMException('Navigation was aborted', 'AbortError'));
this.navigateEvent = undefined;
}
const navigateEvent = createFakeNavigateEvent({
navigationType: options.navigationType,
cancelable: options.cancelable,
canIntercept: options.canIntercept,
userInitiated: options.userInitiated,
hashChange: options.hashChange,
signal: result.signal,
destination,
info: options.info,
sameDocument: destination.sameDocument,
skipPopState: options.skipPopState,
result,
userAgentCommit: () => {
this.userAgentCommit();
},
});
this.navigateEvent = navigateEvent;
this.eventTarget.dispatchEvent(navigateEvent);
navigateEvent.dispatchedNavigateEvent();
if (navigateEvent.commitOption === 'immediate') {
navigateEvent.commit(/* internal= */ true);
}
}
/** Implementation to commit a navigation. */
private userAgentCommit() {
if (!this.navigateEvent) {
return;
}
const from = this.currentEntry;
if (!this.navigateEvent.sameDocument) {
const error = new Error('Cannot navigate to a non-same-document URL.');
this.navigateEvent.cancel(error);
throw error;
}
if (
this.navigateEvent.navigationType === 'push' ||
this.navigateEvent.navigationType === 'replace'
) {
this.userAgentPushOrReplace(this.navigateEvent.destination, {
navigationType: this.navigateEvent.navigationType,
});
} else if (this.navigateEvent.navigationType === 'traverse') {
this.userAgentTraverse(this.navigateEvent.destination);
}
this.navigateEvent.userAgentNavigated(this.currentEntry);
const currentEntryChangeEvent = createFakeNavigationCurrentEntryChangeEvent({
from,
navigationType: this.navigateEvent.navigationType,
});
this.eventTarget.dispatchEvent(currentEntryChangeEvent);
if (!this.navigateEvent.skipPopState) {
const popStateEvent = createPopStateEvent({
state: this.navigateEvent.destination.getHistoryState(),
});
this.window.dispatchEvent(popStateEvent);
}
}
/** Implementation for a push or replace navigation. */
private userAgentPushOrReplace(
destination: FakeNavigationDestination,
{navigationType}: {navigationType: NavigationTypeString},
) {
if (navigationType === 'push') {
this.currentEntryIndex++;
this.prospectiveEntryIndex = this.currentEntryIndex;
}
const index = this.currentEntryIndex;
const key = navigationType === 'push' ? String(this.nextKey++) : this.currentEntry.key;
const entry = new FakeNavigationHistoryEntry(destination.url, {
id: String(this.nextId++),
key,
index,
sameDocument: true,
state: destination.getState(),
historyState: destination.getHistoryState(),
});
if (navigationType === 'push') {
this.entriesArr.splice(index, Infinity, entry);
} else {
this.entriesArr[index] = entry;
}
}
/** Implementation for a traverse navigation. */
private userAgentTraverse(destination: FakeNavigationDestination) {
this.currentEntryIndex = destination.index;
}
/** Utility method for finding entries with the given `key`. */
private findEntry(key: string) {
for (const entry of this.entriesArr) {
if (entry.key === key) return entry;
}
return undefined;
}
set onnavigate(_handler: ((this: Navigation, ev: NavigateEvent) => any) | null) {
throw new Error('unimplemented');
}
get onnavigate(): ((this: Navigation, ev: NavigateEvent) => any) | null {
throw new Error('unimplemented');
}
set oncurrententrychange(
_handler: ((this: Navigation, ev: NavigationCurrentEntryChangeEvent) => any) | null,
) {
throw new Error('unimplemented');
}
get oncurrententrychange():
| ((this: Navigation, ev: NavigationCurrentEntryChangeEvent) => any)
| null {
throw new Error('unimplemented');
}
set onnavigatesuccess(_handler: ((this: Navigation, ev: Event) => any) | null) {
throw new Error('unimplemented');
}
get onnavigatesuccess(): ((this: Navigation, ev: Event) => any) | null {
throw new Error('unimplemented');
}
set onnavigateerror(_handler: ((this: Navigation, ev: ErrorEvent) => any) | null) {
throw new Error('unimplemented');
}
get onnavigateerror(): ((this: Navigation, ev: ErrorEvent) => any) | null {
throw new Error('unimplemented');
}
get transition(): NavigationTransition | null {
throw new Error('unimplemented');
}
updateCurrentEntry(_options: NavigationUpdateCurrentEntryOptions): void {
throw new Error('unimplemented');
}
reload(_options?: NavigationReloadOptions): NavigationResult {
throw new Error('unimplemented');
}
}
/**
* Fake equivalent of the `NavigationResult` interface with
* `FakeNavigationHistoryEntry`.
*/
interface FakeNavigationResult extends NavigationResult {
readonly committed: Promise<FakeNavigationHistoryEntry>;
readonly finished: Promise<FakeNavigationHistoryEntry>;
}
/**
* Fake equivalent of `NavigationHistoryEntry`.
*/
export class FakeNavigationHistoryEntry implements NavigationHistoryEntry {
readonly sameDocument;
readonly id: string;
readonly key: string;
readonly index: number;
private readonly state: unknown;
private readonly historyState: unknown;
ondispose: ((this: NavigationHistoryEntry, ev: Event) => any) | null = null;
constructor(
readonly url: string | null,
{
id,
key,
index,
sameDocument,
state,
historyState,
}: {
id: string;
key: string;
index: number;
sameDocument: boolean;
historyState: unknown;
state?: unknown;
},
) {
this.id = id;
this.key = key;
this.index = index;
this.sameDocument = sameDocument;
this.state = state;
this.historyState = historyState;
}
getState(): unknown {
// Budget copy.
return this.state ? JSON.parse(JSON.stringify(this.state)) : this.state;
}
getHistoryState(): unknown {
// Budget copy.
return this.historyState ? JSON.parse(JSON.stringify(this.historyState)) : this.historyState;
}
addEventListener(
type: string,
callback: EventListenerOrEventListenerObject,
options?: AddEventListenerOptions | boolean,
) {
throw new Error('unimplemented');
}
removeEventListener(
type: string,
callback: EventListenerOrEventListenerObject,
options?: EventListenerOptions | boolean,
) {
throw new Error('unimplemented');
}
dispatchEvent(event: Event): boolean {
throw new Error('unimplemented');
}
}
/** `NavigationInterceptOptions` with experimental commit option. */
export interface ExperimentalNavigationInterceptOptions extends NavigationInterceptOptions {
commit?: 'immediate' | 'after-transition';
}
/** `NavigateEvent` with experimental commit function. */
export interface ExperimentalNavigateEvent extends NavigateEvent {
intercept(options?: ExperimentalNavigationInterceptOptions): void;
commit(): void;
}
/**
* Fake equivalent of `NavigateEvent`.
*/
export interface FakeNavigateEvent extends ExperimentalNavigateEvent {
readonly destination: FakeNavigationDestination;
}
interface InternalFakeNavigateEvent extends FakeNavigateEvent {
readonly sameDocument: boolean;
readonly skipPopState?: boolean;
readonly commitOption: 'after-transition' | 'immediate';
readonly result: InternalNavigationResult;
commit(internal?: boolean): void;
cancel(reason: Error): void;
dispatchedNavigateEvent(): void;
userAgentNavigated(entry: FakeNavigationHistoryEntry): void;
}
/**
* Create a fake equivalent of `NavigateEvent`. This is not a class because ES5
* transpiled JavaScript cannot extend native Event.
*/
function createFakeNavigateEvent({
cancelable,
canIntercept,
userInitiated,
hashChange,
navigationType,
signal,
destination,
info,
sameDocument,
skipPopState,
result,
userAgentCommit,
}: {
cancelable: boolean;
canIntercept: boolean;
userInitiated: boolean;
hashChange: boolean;
navigationType: NavigationTypeString;
signal: AbortSignal;
destination: FakeNavigationDestination;
info: unknown;
sameDocument: boolean;
skipPopState?: boolean;
result: InternalNavigationResult;
userAgentCommit: () => void;
}) {
const event = new Event('navigate', {bubbles: false, cancelable}) as {
-readonly [P in keyof InternalFakeNavigateEvent]: InternalFakeNavigateEvent[P];
};
event.canIntercept = canIntercept;
event.userInitiated = userInitiated;
event.hashChange = hashChange;
event.navigationType = navigationType;
event.signal = signal;
event.destination = destination;
event.info = info;
event.downloadRequest = null;
event.formData = null;
event.sameDocument = sameDocument;
event.skipPopState = skipPopState;
event.commitOption = 'immediate';
let handlerFinished: Promise<void> | undefined = undefined;
let interceptCalled = false;
let dispatchedNavigateEvent = false;
let commitCalled = false;
event.intercept = function (
this: InternalFakeNavigateEvent,
options?: ExperimentalNavigationInterceptOptions,
): void {
interceptCalled = true;
event.sameDocument = true;
const handler = options?.handler;
if (handler) {
handlerFinished = handler();
}
if (options?.commit) {
event.commitOption = options.commit;
}
if (options?.focusReset !== undefined || options?.scroll !== undefined) {
throw new Error('unimplemented');
}
};
event.scroll = function (this: InternalFakeNavigateEvent): void {
throw new Error('unimplemented');
};
event.commit = function (this: InternalFakeNavigateEvent, internal = false) {
if (!internal && !interceptCalled) {
throw new DOMException(
`Failed to execute 'commit' on 'NavigateEvent': intercept() must be ` +
`called before commit().`,
'InvalidStateError',
);
}
if (!dispatchedNavigateEvent) {
throw new DOMException(
`Failed to execute 'commit' on 'NavigateEvent': commit() may not be ` +
`called during event dispatch.`,
'InvalidStateError',
);
}
if (commitCalled) {
throw new DOMException(
`Failed to execute 'commit' on 'NavigateEvent': commit() already ` + `called.`,
'InvalidStateError',
);
}
commitCalled = true;
userAgentCommit();
};
// Internal only.
event.cancel = function (this: InternalFakeNavigateEvent, reason: Error) {
result.committedReject(reason);
result.finishedReject(reason);
};
// Internal only.
event.dispatchedNavigateEvent = function (this: InternalFakeNavigateEvent) {
dispatchedNavigateEvent = true;
if (event.commitOption === 'after-transition') {
// If handler finishes before commit, call commit.
handlerFinished?.then(
() => {
if (!commitCalled) {
event.commit(/* internal */ true);
}
},
() => {},
);
}
Promise.all([result.committed, handlerFinished]).then(
([entry]) => {
result.finishedResolve(entry);
},
(reason) => {
result.finishedReject(reason);
},
);
};
// Internal only.
event.userAgentNavigated = function (
this: InternalFakeNavigateEvent,
entry: FakeNavigationHistoryEntry,
) {
result.committedResolve(entry);
};
return event as InternalFakeNavigateEvent;
}
/** Fake equivalent of `NavigationCurrentEntryChangeEvent`. */
export interface FakeNavigationCurrentEntryChangeEvent extends NavigationCurrentEntryChangeEvent {
readonly from: FakeNavigationHistoryEntry;
}
/**
* Create a fake equivalent of `NavigationCurrentEntryChange`. This does not use
* a class because ES5 transpiled JavaScript cannot extend native Event.
*/
function createFakeNavigationCurrentEntryChangeEvent({
from,
navigationType,
}: {
from: FakeNavigationHistoryEntry;
navigationType: NavigationTypeString;
}) {
const event = new Event('currententrychange', {
bubbles: false,
cancelable: false,
}) as {
-readonly [P in keyof NavigationCurrentEntryChangeEvent]: NavigationCurrentEntryChangeEvent[P];
};
event.from = from;
event.navigationType = navigationType;
return event as FakeNavigationCurrentEntryChangeEvent;
}
/**
* Create a fake equivalent of `PopStateEvent`. This does not use a class
* because ES5 transpiled JavaScript cannot extend native Event.
*/
function createPopStateEvent({state}: {state: unknown}) {
const event = new Event('popstate', {
bubbles: false,
cancelable: false,
}) as {-readonly [P in keyof PopStateEvent]: PopStateEvent[P]};
event.state = state;
return event as PopStateEvent;
}
/**
* Fake equivalent of `NavigationDestination`.
*/
export class FakeNavigationDestination implements NavigationDestination {
readonly url: string;
readonly sameDocument: boolean;
readonly key: string | null;
readonly id: string | null;
readonly index: number;
private readonly state?: unknown;
private readonly historyState: unknown;
constructor({
url,
sameDocument,
historyState,
state,
key = null,
id = null,
index = -1,
}: {
url: string;
sameDocument: boolean;
historyState: unknown;
state?: unknown;
key?: string | null;
id?: string | null;
index?: number;
}) {
this.url = url;
this.sameDocument = sameDocument;
this.state = state;
this.historyState = historyState;
this.key = key;
this.id = id;
this.index = index;
}
getState(): unknown {
return this.state;
}
getHistoryState(): unknown {
return this.historyState;
}
}
/** Utility function to determine whether two UrlLike have the same hash. */
function isHashChange(from: URL, to: URL): boolean {
return (
to.hash !== from.hash &&
to.hostname === from.hostname &&
to.pathname === from.pathname &&
to.search === from.search
);
}
/** Internal utility class for representing the result of a navigation. */
class InternalNavigationResult {
committedResolve!: (entry: FakeNavigationHistoryEntry) => void;
committedReject!: (reason: Error) => void;
finishedResolve!: (entry: FakeNavigationHistoryEntry) => void;
finishedReject!: (reason: Error) => void;
readonly committed: Promise<FakeNavigationHistoryEntry>;
readonly finished: Promise<FakeNavigationHistoryEntry>;
get signal(): AbortSignal {
return this.abortController.signal;
}
private readonly abortController = new AbortController();
constructor() {
this.committed = new Promise<FakeNavigationHistoryEntry>((resolve, reject) => {
this.committedResolve = resolve;
this.committedReject = reject;
});
this.finished = new Promise<FakeNavigationHistoryEntry>(async (resolve, reject) => {
this.finishedResolve = resolve;
this.finishedReject = (reason: Error) => {
reject(reason);
this.abortController.abort(reason);
};
});
// All rejections are handled.
this.committed.catch(() => {});
this.finished.catch(() => {});
}
}
/** Internal options for performing a navigate. */
interface InternalNavigateOptions {
navigationType: NavigationTypeString;
cancelable: boolean;
canIntercept: boolean;
userInitiated: boolean;
hashChange: boolean;
info?: unknown;
skipPopState?: boolean;
}
export {ɵFakeNavigation as FakeNavigation} from '@angular/core/testing';

Some files were not shown because too many files have changed in this diff Show More