Compare commits

...

163 Commits

Author SHA1 Message Date
Andrew Kushnir 7529512e7b release: cut the v19.2.2 release 2025-03-12 09:42:52 -07:00
Andrew Scott 05729f4116 refactor(router): split remainder of describes in integration test file (#60313)
to more easily navigate and identify different suites of integration tests,
this commit splits them into several different files

PR Close #60313
2025-03-11 17:13:28 -07:00
Matthieu Riegler 41fd77664e docs(docs-infra): Bump tutorial/playground to 19.2 (#60337)
fixes #60335

PR Close #60337
2025-03-11 16:54:32 -07:00
Paul Gschwendtner 044dac98c9 feat(bazel): support bundling .d.ts with code splitting (#60321) (#60332)
Instead of relying on Microsoft's API extractor for `d.ts` bundling,
we are switching to Rollup-based `.d.ts` bundling.

This allows us to support code spliting, even for `.d.ts` files,
allowing for relative imports to be used between entry-points, without
ending up duplicating `.d.ts` definitions in two files. This would otherwise cause
problems with assignability of types.

It also nicely integrates into our existing rollup configuration, and
overall simplifies the `ng_package` rule even further!

Notably `tsup` also uses this rollup plugin, and it seems to work well.
Keep in mind that Microsoft's API extractor is pretty hard to integrate,
caused many problems in the past, and isn't capable of code splitting.
This aligns our d.ts bundling with the .mjs bundling (great alignment).

PR Close #60321

PR Close #60332
2025-03-11 15:33:05 -07:00
Kristiyan Kostadinov 56b551d273 fix(compiler): incorrect spans for template literals (#60323) (#60331)
Fixes that we were producing zero-length spans for template literals and template literal elements.

Fixes #60320.
Fixes #60319.

PR Close #60323

PR Close #60331
2025-03-11 15:02:17 -07:00
Matthieu Riegler 705fb0d236 docs: add mention of GH issue for devPreview feedback (#60010)
fixes #59902

PR Close #60010
2025-03-11 14:26:25 -07:00
arturovt b6f22a7526 refactor(core): inline standalone check to be dropped (#60305)
Inlines `standalone` check into the message to be dropped.

PR Close #60305
2025-03-11 13:01:44 -07:00
Matthieu Riegler 8ed63bc1bb docs: remove mention of #59443 in 19.2 changelog (#60329)
#59443 was reverted for breaking projects in G3.

PR Close #60329
2025-03-11 12:58:16 -07:00
AlirezaEbrahimkhani e98facebac docs: change xss doc url to angular new documentation (#59915)
PR Close #59915
2025-03-11 12:57:48 -07:00
Juhmer-Tena f9b6bf91be docs: fix commit message guideline link (#60296)
Fixes the link for the commit message guideline in CONTRIBUTING.md
pointing to the building and testing angular guide instead of the
commit message guidelines

Fixes #60925

PR Close #60296
2025-03-11 11:08:24 -07:00
Kristiyan Kostadinov c41b3c83ca build: update public-api approvers (#60328)
Adds myself and Paul to the public API reviewers to help with the reviews.

PR Close #60328
2025-03-11 11:07:19 -07:00
Doug Parker e59432cf07 refactor(devtools): refactors property view drag-and-drop behavior (#60286)
The main goal of this change is to remove `categoryOrder` which effectively hard-codes the supported length of `panels`. Adding another item to `panels` is not rendered unless that is added to `categoryOrder`.

My solution to this is to make the set of categories a signal, with each category able to produce the data inside it. This allow `CdkDragDrop` to rearrange categories but then still produce the correct data in the template without needing a separate array to track order.

Also removed `hidden` and inlined it in the template, since the logic was the same for every panel.

`moveItemInArray` is unfortunately an in-place move, so I needed to manually clone the array to ensure `panels` observes an immutable update which works better with signals and change detection.

PR Close #60286
2025-03-11 09:27:04 -07:00
aparzi b144dd946e fix(core): fix removal of a container reference used in the component file (#60210)
During migration a container reference was deleted even though it was used in the component file.

PR Close #60210
2025-03-11 09:26:14 -07:00
Vlad Boisa 64bdffb97a docs: fix one broke link by #57591 (#60237)
Fix 1 link in adev toolchain `{@link forRoot}`
PR Close #60237
2025-03-11 09:24:03 -07:00
Matthieu Riegler 7832be5905 docs: fix event listener key modifier example (#60280)
PR Close #60280
2025-03-11 09:23:02 -07:00
Angular Robot 4e1f28eddf build: update github/codeql-action action to v3.28.11 (#60316)
See associated pull request for more information.

PR Close #60316
2025-03-11 09:21:44 -07:00
Angular Robot 36d740d6ef build: update dependency typescript to ~5.8.0 (#60317)
See associated pull request for more information.

PR Close #60317
2025-03-11 09:21:07 -07:00
Matthieu Riegler 30f6db37a0 refactor(core): log a warning instead of throwing error on OutputRef.emit when the directive is destroyed. (#60293)
This should not be a hard error, as nothing bad happens but the users should be warned that no event will be emitted.

fixes #60110

PR Close #60293
2025-03-10 16:08:53 -07:00
Matthieu Riegler 442ef134b8 docs(docs-infra): Extract class constructors (#60302)
fixes #59180

PR Close #60302
2025-03-10 16:05:03 -07:00
Andrew Scott b1d0d20240 refactor(router): split integration test file (#60311)
to more easily navigate and identify different suites of integration tests,
this commit splits them into several different files

PR Close #60311
2025-03-10 16:03:54 -07:00
Jessica Janiuk 4e87b58fd1 ci: fix timer test flakiness (#60310)
This replaces the TimerScheduler entirely and ensures the callback is called immediately. This should prevent any further flakiness.

PR Close #60310
2025-03-10 15:08:54 -07:00
arturovt ec65169dfb refactor(core): replace PLATFORM_ID in initTransferState (#60299)
Replaces `PLATFORM_ID` check with the `ngServerMode`.

PR Close #60299
2025-03-10 13:49:24 -07:00
Paul Gschwendtner 15c42969fc fix(platform-server): add missing peer dependency for rxjs (#60308)
The server package imports from `rxjs`, but the dependency was not
specified as `peerDependency`, nor `dependency`. This surfaces as an
error in strict dependency environments, like with pnpm's symlinked node
modules structure.

This commit fixes this. It presumably doesn't fail with e.g. Yarn, or
npm because of node modules hoisting.

PR Close #60308
2025-03-10 11:41:06 -07:00
Doug Parker cfdadb562f release: bump Angular DevTools version to 1.0.26 (#60309)
PR Close #60309
2025-03-10 11:40:27 -07:00
Kristiyan Kostadinov 23ca88522b fix(compiler-cli): handle transformed classes when generating HMR code (#60298)
We had several places where we were trying to get the source file of a class for which we're generating HMR-related code. These calls will fail if the class was transformed so we have to get its source file through the original node.

Fixes #60287.

PR Close #60298
2025-03-10 09:58:53 -07:00
Doug Parker ea8008d95e fix(devtools): handle case where directive metadata has undefined dependencies (#60256)
This fixes a bug introduced by [#60206](https://github.com/angular/angular/pull/60206/files#diff-c07b9ecd59f6e0ed636ac9672b9644178d9a999f04bc8b8198b13e86589dfa5b). Previously `DirectiveMetadata.prototype.dependencies` was always set, but in that PR `dependencies` is only set if an `Injector` is found (which only happens when `ng.getInjector` is *not* defined). This causes an error when evaluating `dependencies.length` in the template.

Fix is just to check for existance before calling `length`.

PR Close #60256
2025-03-10 09:57:26 -07:00
Doug Parker cef606aa8e refactor(devtools): remove input/output/property links (#60284)
These links aren't that helpful in the context of Angular DevTools for a few reasons:
1. Users of the extension should already have a general understanding of core Angular concepts, inputs and outputs included.
2. The input and output links go to API documentation which isn't useful for someone who doesn't actually understand the core concepts anyways.
3. These links point to signals documentation even though DevTools shows non-signal inputs and outputs.
4. Properties linked to template binding docs, which doesn't *really* have anything to do with the plain JS properties being shown in DevTools anyways.

PR Close #60284
2025-03-10 09:56:20 -07:00
Joey Perrott 44155c9209 refactor(bazel): remove primary_bundle_name as attr for ng_package (#60306)
Remove primary_bundle_name as an attr for ng_package as it is unused.

PR Close #60306
2025-03-10 09:31:20 -07:00
Joey Perrott 43b89e7bb1 refactor(bazel): hardcode esm as the ouput for rollup (#60306)
Remove the option as an arg for creating a rollup config since it is static.

PR Close #60306
2025-03-10 09:31:20 -07:00
Hryhorii Avcharov 5e2f312321 fix(docs-infra): terminal light mode text color (#60246)
This commit changes the text color in terminal light mode to improve
visibility and user experience. The previous color was too light and
made it difficult to read the text.

PR Close #60246
2025-03-10 09:23:20 -07:00
François bd15db3960 docs: fix typo and link in v16 update guide (#60277)
PR Close #60277
2025-03-10 09:22:34 -07:00
Paul Gschwendtner bde1b37e0b build: properly compile tests in core with Angular compiler (#60268)
Previously we never could use relative imports to import e.g. `Component`
in e.g. the `core/tests/bundling` folder. This was necessary because otherwise the
Angular compiler wouldn't process those files as it wouldn't recognize
the Angular decorator as the one from `@angular/core`.

Notably this still isn't a large issue because relative imports still
work for most core tests, that are JIT compiled!

For bundling tests though, or some smaller targets, our new upcoming
guidelines for using relative imports inside the full package; fall
apart. This commit unblocks this effort and allows us to use relative
imports in all tests of `packages/core`. This is achieved by leveraging
the existing `isCore` functionality of the compiler, and fixing a few
instances that were missing before.

PR Close #60268
2025-03-07 11:00:48 -08:00
Vlad Boisa 5131794cde docs: fix typo in desription (#60261)
Fix typos in description of `styleUrl`

Fixes #60258
PR Close #60261
2025-03-07 10:55:38 -08:00
Paul Gschwendtner 70fb3bba4b refactor: add fast-glob to bazel dependencies (#60253)
This will be necessary for downstream (private) consumers
of the `@angular/bazel` package.

PR Close #60253
2025-03-06 12:50:29 -08:00
Jessica Janiuk 5db2df8aad ci: fix flakey timer tests (#60254)
This adjusts the tests to have a longer await time and removes the click portion of the test. These tests should only pass if the timer has triggered hydration.

PR Close #60254
2025-03-06 12:49:48 -08:00
Jessica Janiuk bb12b30d52 fix(core): ensures immediate trigger fires properly with lazy loaded routes (#60203)
In the case that a route was lazy loaded, some triggers would never properly finish hydrating due to things firing before the route finished resolving.
This will find the topmost parent defer block and ensure the registry knows about it before trying to hydrate.
In the case that the registry does not yet know, just the affected triggers await app stability before initializing.
fixes #59997

PR Close #60203
2025-03-06 12:47:47 -08:00
Angular Robot 0c105476a1 docs: update Angular CLI help [19.2.x] (#60249)
Updated Angular CLI help contents.

PR Close #60249
2025-03-06 11:27:46 -08:00
Kristiyan Kostadinov 48dc0d6dc7 refactor(language-service): rename internal symbols to accommodate type checking outside a template (#60191)
Currently the language service has some template-specific terminology around type checking, because that's the only place where we had TCB support. These changes make it more generic to accommodate future functionality.

PR Close #60191
2025-03-06 10:39:32 -08:00
Kristiyan Kostadinov 9d8e943e62 refactor(compiler-cli): rework resource handling to allow directives (#60191)
Currently only components can have resources, because they're the only symbol kinds being type checked. Since we want to add directives to it, these changes rework the resource handling to accommodate them.

PR Close #60191
2025-03-06 10:39:32 -08:00
Kristiyan Kostadinov 1a71167b5c refactor(compiler-cli): rework source manager to accommodate directives (#60191)
Currently the `TemplateSourceManager` is set up to specifically cater to component templates. These changes make it more generic so we can reuse it for directives.

PR Close #60191
2025-03-06 10:39:32 -08:00
Kristiyan Kostadinov 83218bd0d1 refactor(compiler-cli): rework type checking internals to accommodate more than components (#60191)
Currently a lot of the internal type checking data structures are set up specifically for components, because we only support type checking of templates. Since this will change in future commits, these changes prepare for it by renaming various methods and separating out component-specific data.

PR Close #60191
2025-03-06 10:39:32 -08:00
Kristiyan Kostadinov 5cc80dc72f refactor(compiler-cli): change TemplateId terminology (#60191)
Renames the `TemplateId` and terminology related to it, because we'll be using it for more than just templates.

PR Close #60191
2025-03-06 10:39:32 -08:00
Kristiyan Kostadinov 9cdf950108 refactor(compiler): allow binder to apply to more than one set of nodes (#60191)
Currently `R3TargetBinder.bind` gets a set of data back from `DirectiveBinder.apply` and `TemplateBinder.applyWithScope`. This will be annoying  if we have multiple sources of data, because we'd have to do merge them at the end.

These changes switch to constructing the various data structures ahead of time and passing them into the binders to populate them instead.

I also extracted some of the less trivial types into type aliases so we don't have to repeat them.

PR Close #60191
2025-03-06 10:39:31 -08:00
kw 279e6b846f docs: Correct comments for FormControl (#58352)
PR Close #58352
2025-03-06 10:36:47 -08:00
Doug Parker 07cc97ff9b release: bump Angular DevTools version to 1.0.25 (#60235)
PR Close #60235
2025-03-06 10:32:02 -08:00
Paul Gschwendtner 684e837758 feat(bazel): support shared chunks in ng_package (#60241)
Historically we've had to be VERY cautious about the way we import
things between entry-points. That is because the `ng_package` rule
bundling is subject to silently introducing code duplication, breaking
singletons etc. We've had this surface a couple of times already, and
dev-infra tried to help detect such cases by adding safety analysis into
`ng_package`.

Long-term we want to get to an approach where it's easy to simply share
code between chunks. Precisely, with the upcoming `rules_js` migration,
this will be necessary as we will have different import "guidelines"
that would currently, before this commit, result in code duplication, or
trigger our "safety check/lint".

This commit prepares `ng_package` to support relative imports between
entry-points, so that we only need the safety check for cross-package
imports/exports. The result is that `ng_package`/APF is now smartly able
to generate shared chunks for things that are needed between multiple
entry-points. Yay!

Note that those shared chunks still remain private, and are guarded by
our `package.json` "exports"; so no new public API surface is
exposed.

PR Close #60241
2025-03-06 10:29:05 -08:00
Matthieu Riegler dad02c6535 refactor(http): Make sur to pass context & transferCache from httpResource to the underlying request. (#60228)
Prior to this change, both were accepted as argument but never passed to the client.

PR Close #60228
2025-03-06 07:59:07 -08:00
Matthieu Riegler dfc8c432db docs(docs-infra): fix initial TOC selection (#60240)
PR Close #60240
2025-03-06 07:55:43 -08:00
Andrew Kushnir b967ca6b3b Revert "fix(router): update symbols (#60233)" (#60236)
This reverts commit 7bcdf7c143.
The original change causing CI failures was affecting the main branch only, reverting from the patch branch (since it causes CI failures).

PR Close #60236
2025-03-06 10:22:01 +01:00
Jessica Janiuk 7bcdf7c143 fix(router): update symbols (#60233)
This updates the goldens for the router.

PR Close #60233
2025-03-05 15:34:05 -08:00
arturovt 484e0cf43e refactor(platform-browser): drop unsupported event target error message in production (#60016)
Drops unsupported event target error message in production and replaces it with an existing error code.

PR Close #60016
2025-03-05 14:45:50 -08:00
arturovt 6b019ae958 refactor(core): add token to missing injection context error message (#60009)
In this commit, we add injector token information to the error message to improve debugging and context awareness, because it is hard to capture the `inject()` stack trace in asynchronous contexts.

PR Close #60009
2025-03-05 14:44:44 -08:00
arturovt 6dc41265fd fix(core): check whether application is destroyed before initializing event replay (#59789)
In this commit, we check whether the application is destroyed before initializing event replay. The application may be destroyed before it becomes stable, so when the `whenStable` resolves, the injector might already be in a destroyed state. As a result, calling `injector.get` would throw an error indicating that the injector has already been destroyed.

PR Close #59789
2025-03-05 14:43:48 -08:00
arturovt 3076254567 refactor(common): drop ngIf assert template error in production (#59705)
This commit removes the `assertTemplate` error message in production.

PR Close #59705
2025-03-05 14:42:19 -08:00
cexbrayat 90a16a1088 fix(common): support equality function in httpResource (#60026)
The `equal` option was not passed to the underlying resource.

PR Close #60026
2025-03-05 14:41:15 -08:00
iteriani 8d770eccee refactor(core): Remove the optional flag from Injector and all casts. (#60154)
Note that this does NOT use the retrieve method yet. I believe we need to move the logic for notFoundValue into the inject implementation.

PR Close #60154
2025-03-05 13:44:00 -08:00
Taygan Caldwell 699733fcc3 refactor: add untracked to primitives (#60105)
add untracked to primitives to allow Wiz to use it

PR Close #60105
2025-03-05 12:11:40 -08:00
Vlad Boisa b76af47966 docs: change broke Pipe link (#60226)
Change broke link, swap from /guides  to /guide
PR Close #60226
2025-03-05 12:11:06 -08:00
Matthieu Riegler 6accd1b0f5 docs(docs-infra): Use intersectionObserver for TOC (#60166)
Using IntersectionObservers simplfies the code and doesn't rely on APIs that will trigger a reflow.

PR Close #60166
2025-03-05 12:09:45 -08:00
Andrew Scott f08dbe0970 docs: Add more pending tasks documentation in zoneless readme (#59689)
This commit expands on the pending tasks documentation in the zoneless readme

PR Close #59689
2025-03-05 12:08:58 -08:00
Andrew Scott 6c7493e39d docs(core): Update zoneless test documentation to clarify OnPush requirement (#59689)
This updates the zoneless documentation to mention that components under
test a required to be onpush compatible. This includes any wrapper
components only used in tests.

fixes #59082

PR Close #59689
2025-03-05 12:08:58 -08:00
Andrew Scott cc97128760 docs(core): Clarify what it means to be a host for user components (#59689)
Adds a note to clarify what it means for a component to host a user
component in the context of `OnPush` restrictions.

fixes #58648

PR Close #59689
2025-03-05 12:08:58 -08:00
Doug Parker 39e8bd0f4b refactor(devtools): ignore getInjector when it is not supported (#60206)
Previously Angular DevTools would throw when run on an application which does not support `getInjector`, now it safely ignores it and assumes dependency injection is not supported.

PR Close #60206
2025-03-05 12:05:46 -08:00
AleksanderBodurri ba4b3dbed7 fix(devtools): handle case where router tree does not exist (#60221)
Previously this was throwing errors in applications with no Router token.

Now it skips emitting events for the router tree when it is unable to find the Router token.

Note: If these events don't emit, DevTools treats the RouterTree feature as disabled.

PR Close #60221
2025-03-05 12:05:02 -08:00
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
381 changed files with 22853 additions and 25705 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@6bb031afdd8eb862ea3fc1848194185e076637e5 # v3.28.11
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',
+2 -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/**/{*,.*}',
@@ -395,6 +394,8 @@ groups:
- pkozlowski-opensource
- kirjs
- mmalerba
- crisbeto
- devversion
- ~iteriani
- ~tbondwilkinson
- ~rahatarmanahmed
@@ -430,28 +431,6 @@ groups:
required: 1 # Require that 1 person approve
reviewed_for: required
# ================================================
# Circular dependencies
# ================================================
circular-dependencies:
<<: *defaults
conditions:
- *no-groups-above-this-pending
- *no-groups-above-this-rejected
- >
contains_any_globs(files, [
'goldens/circular-deps/packages.json'
])
reviewers:
users:
- alxhub
- AndrewKushnir
- atscott
- ~jelbourn
- thePunderWoman
- pkozlowski-opensource
- kirjs
####################################################################################
# Special Cases
####################################################################################
+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"
]
}
+98 -101
View File
@@ -1,3 +1,101 @@
<a name="19.2.2"></a>
# 19.2.2 (2025-03-12)
### common
| Commit | Type | Description |
| -- | -- | -- |
| [90a16a1088](https://github.com/angular/angular/commit/90a16a10888eee37d8a61cdbfad070e002a3cfdf) | fix | support equality function in httpResource ([#60026](https://github.com/angular/angular/pull/60026)) |
### compiler
| Commit | Type | Description |
| -- | -- | -- |
| [56b551d273](https://github.com/angular/angular/commit/56b551d273a0978e1f2e2ef914c1d7ae942a28a8) | fix | incorrect spans for template literals ([#60323](https://github.com/angular/angular/pull/60323)) ([#60331](https://github.com/angular/angular/pull/60331)) |
### compiler-cli
| Commit | Type | Description |
| -- | -- | -- |
| [23ca88522b](https://github.com/angular/angular/commit/23ca88522bbc23e24a2b20e48e62edcce3a42eb6) | fix | handle transformed classes when generating HMR code ([#60298](https://github.com/angular/angular/pull/60298)) |
### core
| Commit | Type | Description |
| -- | -- | -- |
| [6dc41265fd](https://github.com/angular/angular/commit/6dc41265fd47df3fd0462bcd8f451eb6aea972ef) | fix | check whether application is destroyed before initializing event replay ([#59789](https://github.com/angular/angular/pull/59789)) |
| [bb12b30d52](https://github.com/angular/angular/commit/bb12b30d5213912f50f53aff60a11e6d47349c82) | fix | ensures immediate trigger fires properly with lazy loaded routes ([#60203](https://github.com/angular/angular/pull/60203)) |
| [b144dd946e](https://github.com/angular/angular/commit/b144dd946e38e52ce716aca7c6ba7c1a1a02f13d) | fix | fix removal of a container reference used in the component file ([#60210](https://github.com/angular/angular/pull/60210)) |
### platform-server
| Commit | Type | Description |
| -- | -- | -- |
| [15c42969fc](https://github.com/angular/angular/commit/15c42969fc42c76e3bd593201164183fb82d70f6) | fix | add missing peer dependency for `rxjs` ([#60308](https://github.com/angular/angular/pull/60308)) |
### router
| Commit | Type | Description |
| -- | -- | -- |
| [7bcdf7c143](https://github.com/angular/angular/commit/7bcdf7c1435f766b2b0bbd383358ef2ddabf217a) | fix | update symbols ([#60233](https://github.com/angular/angular/pull/60233)) |
<!-- CHANGELOG SPLIT MARKER -->
<a name="19.2.1"></a>
# 19.2.1 (2025-03-05)
### common
| Commit | Type | Description |
| -- | -- | -- |
| [c2de5f68b3](https://github.com/angular/angular/commit/c2de5f68b3d3cd8a0c43b3c4325a1a1db874e132) | fix | clean up `onUrlChange` listener when root scope is destroyed ([#60004](https://github.com/angular/angular/pull/60004)) |
### compiler-cli
| Commit | Type | Description |
| -- | -- | -- |
| [1dd94476b3](https://github.com/angular/angular/commit/1dd94476b35d08e753766b1d0c5d8af5faa017a9) | fix | ensure template IDs are not reused if a source file changes ([#60152](https://github.com/angular/angular/pull/60152)) |
### core
| Commit | Type | Description |
| -- | -- | -- |
| [1b3b05bf72](https://github.com/angular/angular/commit/1b3b05bf7294963aa50f83b726db620b250b1a5c) | fix | cache ComponentRef inputs and outputs ([#60156](https://github.com/angular/angular/pull/60156)) |
| [330c24aed9](https://github.com/angular/angular/commit/330c24aed92e4b916fb7ac6a91fb17c3ae728869) | fix | prevent invoking replay listeners on disconnected nodes ([#60103](https://github.com/angular/angular/pull/60103)) |
| [cfad089cc3](https://github.com/angular/angular/commit/cfad089cc359cddf08d462662d2a6bf3acab9b22) | fix | prevents event replay from being called on comment nodes ([#60130](https://github.com/angular/angular/pull/60130)) |
### language-service
| Commit | Type | Description |
| -- | -- | -- |
| [3f0116607d](https://github.com/angular/angular/commit/3f0116607dc3ad7e31cb4d895a56094f77c82f5d) | fix | Forward the tags for quick info from the type definition ([#59524](https://github.com/angular/angular/pull/59524)) |
<!-- CHANGELOG SPLIT MARKER -->
<a name="19.2.0"></a>
# 19.2.0 (2025-02-26)
### common
| Commit | Type | Description |
| -- | -- | -- |
| [3e39da593a](https://github.com/angular/angular/commit/3e39da593a0a0c047a2a03b8d5fcabf9dbace40f) | feat | introduce experimental `httpResource` ([#59876](https://github.com/angular/angular/pull/59876)) |
### compiler
| Commit | Type | Description |
| -- | -- | -- |
| [5b20bab96d](https://github.com/angular/angular/commit/5b20bab96d20fe89b5cc4b4af28edbaae2604da1) | feat | Add Skip Hydration diagnostic. ([#59576](https://github.com/angular/angular/pull/59576)) |
| [fe8a68329b](https://github.com/angular/angular/commit/fe8a68329b50363f914a728579392f3fc68670a6) | feat | support untagged template literals in expressions ([#59230](https://github.com/angular/angular/pull/59230)) |
### core
| Commit | Type | Description |
| -- | -- | -- |
| [2588985f43](https://github.com/angular/angular/commit/2588985f433b20a6a5a8d239347291f5d6fb2451) | feat | pass signal node to throwInvalidWriteToSignalErrorFn ([#59600](https://github.com/angular/angular/pull/59600)) |
| [168516462a](https://github.com/angular/angular/commit/168516462a9673b158fcaa38b8ce17bf684a8ac9) | feat | support default value in `resource()` ([#59655](https://github.com/angular/angular/pull/59655)) |
| [bc2ad7bfd3](https://github.com/angular/angular/commit/bc2ad7bfd37a61992b550943de5da0eab2eec98b) | feat | support streaming resources ([#59573](https://github.com/angular/angular/pull/59573)) |
| [146ab9a76e](https://github.com/angular/angular/commit/146ab9a76e6b4d8db7d08d34e2571ba5207f8756) | feat | support TypeScript 5.8 ([#59830](https://github.com/angular/angular/pull/59830)) |
| [6c92d65349](https://github.com/angular/angular/commit/6c92d653493404a5f13aa59cde390bcbed973fb6) | fix | add `hasValue` narrowing to `ResourceRef` ([#59708](https://github.com/angular/angular/pull/59708)) |
| [96e602ebe9](https://github.com/angular/angular/commit/96e602ebe9cdf7355befad22c11f9f91e0436e01) | fix | cancel in-progress request when same value is assigned ([#59280](https://github.com/angular/angular/pull/59280)) |
| [6789c7ef94](https://github.com/angular/angular/commit/6789c7ef947952551d7598fe37a3d86093b75720) | fix | Defer afterRender until after first CD ([#59455](https://github.com/angular/angular/pull/59455)) ([#59551](https://github.com/angular/angular/pull/59551)) |
| [c87e581dd9](https://github.com/angular/angular/commit/c87e581dd9e240c88cea50f222942873bdccd01d) | fix | Don't run effects in check no changes pass ([#59455](https://github.com/angular/angular/pull/59455)) ([#59551](https://github.com/angular/angular/pull/59551)) |
| [127fc0dc84](https://github.com/angular/angular/commit/127fc0dc847a4e8b62be36cdd980a067c4da974f) | fix | fix `resource()`'s `previous.state` ([#59708](https://github.com/angular/angular/pull/59708)) |
| [b592b1b051](https://github.com/angular/angular/commit/b592b1b0516786c52c7d0638c4e7545b0de8a545) | fix | fix race condition in resource() ([#59851](https://github.com/angular/angular/pull/59851)) |
| [a299e02e91](https://github.com/angular/angular/commit/a299e02e9141cdc4d74185deb58308fa010bb36e) | fix | preserve tracing snapshot until tick finishes ([#59796](https://github.com/angular/angular/pull/59796)) |
### forms
| Commit | Type | Description |
| -- | -- | -- |
| [fa0c3e3210](https://github.com/angular/angular/commit/fa0c3e3210885a36e5c9e9eb76e821032f5cd215) | feat | support type set in form validators ([#45793](https://github.com/angular/angular/pull/45793)) |
### migrations
| Commit | Type | Description |
| -- | -- | -- |
| [1cd3a7db83](https://github.com/angular/angular/commit/1cd3a7db83e1d05a31d23324676420b614cdabe2) | feat | add migration to convert templates to use self-closing tags ([#57342](https://github.com/angular/angular/pull/57342)) |
### platform-browser
| Commit | Type | Description |
| -- | -- | -- |
| [e6cb411e43](https://github.com/angular/angular/commit/e6cb411e4393a4b1f5852d3d7c5b9622504399b1) | fix | automatically disable animations on the server ([#59762](https://github.com/angular/angular/pull/59762)) |
### platform-server
| Commit | Type | Description |
| -- | -- | -- |
| [fc5d187da5](https://github.com/angular/angular/commit/fc5d187da5e8895d60caa35b7b59e234998eddf0) | fix | decouple server from animations module ([#59762](https://github.com/angular/angular/pull/59762)) |
<!-- CHANGELOG SPLIT MARKER -->
<a name="19.1.7"></a>
# 19.1.7 (2025-02-19)
### common
@@ -15,30 +113,6 @@
<!-- CHANGELOG SPLIT MARKER -->
<a name="19.2.0-next.3"></a>
# 19.2.0-next.3 (2025-02-13)
### compiler
| Commit | Type | Description |
| -- | -- | -- |
| [9e847fc60d](https://github.com/angular/angular/commit/9e847fc60d4eef47e665e789ccd2d4f0b4bb94ea) | fix | handle tracking expressions requiring temporary variables ([#58520](https://github.com/angular/angular/pull/58520)) |
### compiler-cli
| Commit | Type | Description |
| -- | -- | -- |
| [5cd26a9420](https://github.com/angular/angular/commit/5cd26a94206dfe8aabdf0dd15bfc09e7a8c606da) | fix | handle deferred blocks with shared dependencies correctly ([#59926](https://github.com/angular/angular/pull/59926)) |
### core
| Commit | Type | Description |
| -- | -- | -- |
| [6789c7ef94](https://github.com/angular/angular/commit/6789c7ef947952551d7598fe37a3d86093b75720) | fix | Defer afterRender until after first CD ([#59455](https://github.com/angular/angular/pull/59455)) ([#59551](https://github.com/angular/angular/pull/59551)) |
| [c87e581dd9](https://github.com/angular/angular/commit/c87e581dd9e240c88cea50f222942873bdccd01d) | fix | Don't run effects in check no changes pass ([#59455](https://github.com/angular/angular/pull/59455)) ([#59551](https://github.com/angular/angular/pull/59551)) |
| [b0266bda4a](https://github.com/angular/angular/commit/b0266bda4ad4efd19710fd0363a50984f48269dc) | fix | invalidate HMR component if replacement throws an error ([#59854](https://github.com/angular/angular/pull/59854)) |
### migrations
| Commit | Type | Description |
| -- | -- | -- |
| [aa285c548c](https://github.com/angular/angular/commit/aa285c548c164917ebb5760802484843c6830daf) | fix | account for let declarations in control flow migration ([#59861](https://github.com/angular/angular/pull/59861)) |
| [1119f85ca9](https://github.com/angular/angular/commit/1119f85ca935f43641f53ee8bc50efa47ad09717) | fix | count used dependencies inside existing control flow ([#59861](https://github.com/angular/angular/pull/59861)) |
<!-- CHANGELOG SPLIT MARKER -->
<a name="19.1.6"></a>
# 19.1.6 (2025-02-12)
### compiler
@@ -61,27 +135,6 @@
<!-- CHANGELOG SPLIT MARKER -->
<a name="19.2.0-next.2"></a>
# 19.2.0-next.2 (2025-02-06)
### compiler-cli
| Commit | Type | Description |
| -- | -- | -- |
| [a97136052f9](https://github.com/angular/angular/commit/a97136052f9cfdb696a3b335ed7a1b9fb994d408) | fix | gracefully fall back if const enum cannot be passed through ([#59815](https://github.com/angular/angular/pull/59815)) |
| [bae94b82fda](https://github.com/angular/angular/commit/bae94b82fda1669a6aafad975a3a0eb6f2743039) | fix | handle const enums used inside HMR data ([#59815](https://github.com/angular/angular/pull/59815)) |
| [03bcd30e348](https://github.com/angular/angular/commit/03bcd30e3487443983d450a5dfea2d68d551bbd3) | fix | handle enum members without initializers in partial evaluator ([#59815](https://github.com/angular/angular/pull/59815)) |
### core
| Commit | Type | Description |
| -- | -- | -- |
| [146ab9a76e6](https://github.com/angular/angular/commit/146ab9a76e6b4d8db7d08d34e2571ba5207f8756) | feat | support TypeScript 5.8 ([#59830](https://github.com/angular/angular/pull/59830)) |
| [b592b1b0516](https://github.com/angular/angular/commit/b592b1b0516786c52c7d0638c4e7545b0de8a545) | fix | fix race condition in resource() ([#59851](https://github.com/angular/angular/pull/59851)) |
| [a299e02e914](https://github.com/angular/angular/commit/a299e02e9141cdc4d74185deb58308fa010bb36e) | fix | preserve tracing snapshot until tick finishes ([#59796](https://github.com/angular/angular/pull/59796)) |
### forms
| Commit | Type | Description |
| -- | -- | -- |
| [fa0c3e32108](https://github.com/angular/angular/commit/fa0c3e3210885a36e5c9e9eb76e821032f5cd215) | feat | support type set in form validators ([#45793](https://github.com/angular/angular/pull/45793)) |
<!-- CHANGELOG SPLIT MARKER -->
<a name="19.1.5"></a>
# 19.1.5 (2025-02-06)
### compiler-cli
@@ -93,45 +146,6 @@
<!-- CHANGELOG SPLIT MARKER -->
<a name="19.2.0-next.1"></a>
# 19.2.0-next.1 (2025-01-29)
### core
| Commit | Type | Description |
| -- | -- | -- |
| [168516462a9](https://github.com/angular/angular/commit/168516462a9673b158fcaa38b8ce17bf684a8ac9) | feat | support default value in `resource()` ([#59655](https://github.com/angular/angular/pull/59655)) |
| [6c92d653493](https://github.com/angular/angular/commit/6c92d653493404a5f13aa59cde390bcbed973fb6) | fix | add `hasValue` narrowing to `ResourceRef` ([#59708](https://github.com/angular/angular/pull/59708)) |
| [96e602ebe9c](https://github.com/angular/angular/commit/96e602ebe9cdf7355befad22c11f9f91e0436e01) | fix | cancel in-progress request when same value is assigned ([#59280](https://github.com/angular/angular/pull/59280)) |
| [cf9054248d1](https://github.com/angular/angular/commit/cf9054248d1b8b5658c5f8b582cea71535f081c2) | fix | check whether application is destroyed before printing hydration stats ([#59716](https://github.com/angular/angular/pull/59716)) |
| [82876242e55](https://github.com/angular/angular/commit/82876242e557abbced793cff06c4d68c4721e6d2) | fix | ensure type is preserved during HMR ([#59700](https://github.com/angular/angular/pull/59700)) |
| [127fc0dc847](https://github.com/angular/angular/commit/127fc0dc847a4e8b62be36cdd980a067c4da974f) | fix | fix `resource()`'s `previous.state` ([#59708](https://github.com/angular/angular/pull/59708)) |
| [f862ace1819](https://github.com/angular/angular/commit/f862ace18191d7fda3b12fc3c6486c035a6b431d) | fix | fixes test timer-based test flakiness in CI ([#59674](https://github.com/angular/angular/pull/59674)) |
### elements
| Commit | Type | Description |
| -- | -- | -- |
| [0c1ecb453c8](https://github.com/angular/angular/commit/0c1ecb453c8b01447fb5a9f38fdfe9936a60abe5) | fix | not setting initial value on signal-based input ([#59773](https://github.com/angular/angular/pull/59773)) |
### platform-browser
| Commit | Type | Description |
| -- | -- | -- |
| [e6cb411e439](https://github.com/angular/angular/commit/e6cb411e4393a4b1f5852d3d7c5b9622504399b1) | fix | automatically disable animations on the server ([#59762](https://github.com/angular/angular/pull/59762)) |
| [6b09716754b](https://github.com/angular/angular/commit/6b09716754b979c98489a2710eda977e51fe92d0) | fix | prepend `baseHref` to `sourceMappingURL` in CSS content ([#59730](https://github.com/angular/angular/pull/59730)) |
| [fd7ee47bf70](https://github.com/angular/angular/commit/fd7ee47bf702029e481ad40362684f0a01ef2ec0) | fix | Update pseudoevent created by createMouseSpecialEvent to populate `_originalEvent` property ([#59690](https://github.com/angular/angular/pull/59690)) |
| [65cf061feb4](https://github.com/angular/angular/commit/65cf061feb402e1532cd06d0fda5d2b7749ac421) | fix | Update pseudoevent created by createMouseSpecialEvent to populate `_originalEvent` property ([#59690](https://github.com/angular/angular/pull/59690)) |
| [af9d74710c4](https://github.com/angular/angular/commit/af9d74710c460b0aaeaf3226533e9e544025523c) | fix | Update pseudoevent created by createMouseSpecialEvent to populate `_originalEvent` property ([#59690](https://github.com/angular/angular/pull/59690)) |
### platform-server
| Commit | Type | Description |
| -- | -- | -- |
| [fc5d187da5e](https://github.com/angular/angular/commit/fc5d187da5e8895d60caa35b7b59e234998eddf0) | fix | decouple server from animations module ([#59762](https://github.com/angular/angular/pull/59762)) |
### router
| Commit | Type | Description |
| -- | -- | -- |
| [c7b6e1107c6](https://github.com/angular/angular/commit/c7b6e1107c6fcc7ed1dbb7c7b8698b09bfa8f1cc) | fix | prevent error handling when injector is destroyed ([#59457](https://github.com/angular/angular/pull/59457)) |
### service-worker
| Commit | Type | Description |
| -- | -- | -- |
| [e2987a1d4ab](https://github.com/angular/angular/commit/e2987a1d4ab09b1abf1f42597c40f2055db116e1) | fix | add missing `rxjs` peer dependency ([#59747](https://github.com/angular/angular/pull/59747)) |
<!-- CHANGELOG SPLIT MARKER -->
<a name="19.1.4"></a>
# 19.1.4 (2025-01-29)
### core
@@ -162,23 +176,6 @@
<!-- CHANGELOG SPLIT MARKER -->
<a name="19.2.0-next.0"></a>
# 19.2.0-next.0 (2025-01-22)
### compiler
| Commit | Type | Description |
| -- | -- | -- |
| [fe8a68329b](https://github.com/angular/angular/commit/fe8a68329b50363f914a728579392f3fc68670a6) | feat | support untagged template literals in expressions ([#59230](https://github.com/angular/angular/pull/59230)) |
### compiler-cli
| Commit | Type | Description |
| -- | -- | -- |
| [ed705a856a](https://github.com/angular/angular/commit/ed705a856a164f91d33e2173b4f371329d07c472) | feat | detect missing structural directive imports ([#59443](https://github.com/angular/angular/pull/59443)) |
### core
| Commit | Type | Description |
| -- | -- | -- |
| [bc2ad7bfd3](https://github.com/angular/angular/commit/bc2ad7bfd37a61992b550943de5da0eab2eec98b) | feat | support streaming resources ([#59573](https://github.com/angular/angular/pull/59573)) |
<!-- CHANGELOG SPLIT MARKER -->
<a name="19.1.3"></a>
# 19.1.3 (2025-01-22)
### compiler
+6 -155
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/commit-message-guidelines.md
[github]: https://github.com/angular/angular
[discord]: https://discord.gg/angular
[individual-cla]: https://cla.developers.google.com/about/google-individual
+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
View File
@@ -10,7 +10,6 @@ ng_module(
module_name = "@angular/docs",
deps = [
"//adev/shared-docs/components",
"//adev/shared-docs/constants",
"//adev/shared-docs/directives",
"//adev/shared-docs/interfaces",
"//adev/shared-docs/pipes",
@@ -1,5 +1,5 @@
load("//tools:defaults.bzl", "karma_web_test_suite", "ng_module", "ts_library")
load("@io_bazel_rules_sass//:defs.bzl", "sass_binary")
load("//tools:defaults.bzl", "karma_web_test_suite", "ng_module", "ts_library")
package(default_visibility = ["//visibility:private"])
@@ -23,6 +23,7 @@ ng_module(
"//packages/common",
"//packages/core",
"//packages/router",
"@npm//@angular/material",
],
)
@@ -7,8 +7,15 @@
<li class="docs-faceted-list-item">
@if (item.path) {
@if (item.isExternal) {
<a [href]="item.path" target="_blank">
<span [class.docs-external-link]="item.isExternal">{{ item.label }}</span>
<a
[href]="item.path" target="_blank"
[matTooltip]="item.label"
[matTooltipDisabled]="item.label.length < 27"
matTooltipPosition="after"
[attr.aria-label]="item.label"
[matTooltipClass]="'API-tooltip'"
>
<span [class.docs-external-link]="item.isExternal" class="docs-faceted-list-item-text">{{ item.label }}</span>
@if (item.children && item.level! > 1 && !item.isExpanded) {
<docs-icon>chevron_right</docs-icon>
}
@@ -25,8 +32,13 @@
}"
routerLinkActive="docs-faceted-list-item-active"
(click)="emitClickOnLink()"
[matTooltip]="item.label"
[matTooltipDisabled]="item.label.length < 27"
matTooltipPosition="after"
[attr.aria-label]="item.label"
[matTooltipClass]="'API-tooltip'"
>
<span>{{item.label}}</span>
<span class="docs-faceted-list-item-text">{{item.label}}</span>
@if (item.children && !item.isExpanded) {
<docs-icon>chevron_right</docs-icon>
}
@@ -35,8 +47,14 @@
} @else {
<!-- Nav Section Header -->
@if (item.level !== collapsableLevel() && item.level !== expandableLevel()) {
<div class="docs-secondary-nav-header">
<span>{{item.label}}</span>
<div class="docs-secondary-nav-header"
[matTooltip]="item.label"
[matTooltipDisabled]="item.label.length < 27"
matTooltipPosition="after"
[attr.aria-label]="item.label"
[matTooltipClass]="'API-tooltip'"
>
<span class="docs-faceted-list-item-text">{{item.label}}</span>
</div>
}
@@ -54,13 +72,17 @@
[class.docs-expanded-button]="item.children && item.level == collapsableLevel()"
[class.docs-not-expanded-button]="item.children && item.level === expandableLevel()"
[class.docs-nav-item-has-icon]="
item.children && item.level === expandableLevel() && !item.isExpanded
"
item.children && item.level === expandableLevel() && !item.isExpanded"
[matTooltip]="item.label"
[matTooltipDisabled]="item.label.length < 27"
matTooltipPosition="after"
[attr.aria-label]="item.label"
[matTooltipClass]="'API-tooltip'"
>
@if (item.children && item.level === collapsableLevel()) {
<docs-icon>arrow_back</docs-icon>
}
<span>{{ item.label }}</span>
<span class="docs-faceted-list-item-text">{{ item.label }}</span>
</button>
}
}
@@ -45,6 +45,12 @@
border: 0;
}
.docs-faceted-list-item-text {
max-width: calc(100% - 1rem);
overflow: hidden;
text-overflow: ellipsis;
}
.docs-nav-item-has-icon {
&::after {
// FIXME: for some reason this disappears when transformed
@@ -115,7 +121,7 @@ a,
// Add padding-bottom to last item in the list
.docs-navigation-list {
width: 100%;
width: 15rem;
li:last-of-type {
ul:last-of-type {
@@ -144,3 +150,12 @@ a,
margin-inline-end: 0.4rem;
}
}
::ng-deep .API-tooltip{
.mat-mdc-tooltip-surface{
max-width: unset;
width:auto;
word-break: keep-all;
white-space: pre;
}
}
@@ -13,10 +13,18 @@ import {RouterLink, RouterLinkActive} from '@angular/router';
import {IconComponent} from '../icon/icon.component';
import {IsActiveNavigationItem} from '../../pipes/is-active-navigation-item.pipe';
import {NgTemplateOutlet} from '@angular/common';
import {MatTooltipModule} from '@angular/material/tooltip';
@Component({
selector: 'docs-navigation-list',
imports: [RouterLink, RouterLinkActive, IconComponent, IsActiveNavigationItem, NgTemplateOutlet],
imports: [
RouterLink,
RouterLinkActive,
IconComponent,
IsActiveNavigationItem,
NgTemplateOutlet,
MatTooltipModule,
],
templateUrl: './navigation-list.component.html',
styleUrls: ['./navigation-list.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
@@ -2,6 +2,8 @@ dialog {
background-color: transparent;
border: none;
padding-block-end: 3rem;
margin: 0 auto;
top: 15vh;
&::backdrop {
backdrop-filter: blur(5px);
@@ -6,26 +6,18 @@
<ul class="docs-faceted-list">
<!-- TODO: Hide li elements with class docs-toc-item-h3 for laptop, table and phone screen resolutions -->
@for (item of tableOfContentItems(); track item.id) {
<li
class="docs-faceted-list-item"
[class.docs-toc-item-h2]="item.level === TableOfContentsLevel.H2"
[class.docs-toc-item-h3]="item.level === TableOfContentsLevel.H3"
>
<!-- Not using routerLink + fragment because of: https://github.com/angular/angular/issues/30139 -->
<a
[href]="location.path() + '#' + item.id"
[class.docs-faceted-list-item-active]="item.id === activeItemId()"
>
{{ item.title }}
</a>
</li>
<li class="docs-faceted-list-item" [class.docs-toc-item-h2]="item.level === TableOfContentsLevel.H2"
[class.docs-toc-item-h3]="item.level === TableOfContentsLevel.H3">
<!-- Not using routerLink + fragment because of: https://github.com/angular/angular/issues/30139 -->
<a [href]="location.path() + '#' + item.id" [class.docs-faceted-list-item-active]="item.id === activeItemId()">
{{ item.title }}
</a>
</li>
}
</ul>
</nav>
@if (shouldDisplayScrollToTop()) {
<button type="button" (click)="scrollToTop()">
<docs-icon role="presentation">arrow_upward_alt</docs-icon>
Back to the top
</button>
}
</aside>
<button type="button" (click)="scrollToTop()">
<docs-icon role="presentation">arrow_upward_alt</docs-icon>
Back to the top
</button>
</aside>
@@ -11,8 +11,8 @@ import {
Component,
DestroyRef,
input,
computed,
inject,
afterNextRender,
} from '@angular/core';
import {Location} from '@angular/common';
import {TableOfContentsLevel} from '../../interfaces/index';
@@ -39,12 +39,15 @@ export class TableOfContents {
tableOfContentItems = this.tableOfContentsLoader.tableOfContentItems;
activeItemId = this.scrollSpy.activeItemId;
shouldDisplayScrollToTop = computed(() => !this.scrollSpy.scrollbarThumbOnTop());
TableOfContentsLevel = TableOfContentsLevel;
ngAfterViewInit() {
this.tableOfContentsLoader.buildTableOfContent(this.contentSourceElement());
this.scrollSpy.startListeningToScroll(this.contentSourceElement(), this.destroyRef);
constructor() {
afterNextRender({
read: () => {
this.tableOfContentsLoader.buildTableOfContent(this.contentSourceElement());
this.scrollSpy.setupActiveItemListener(this.contentSourceElement(), this.destroyRef);
},
});
}
scrollToTop(): void {
@@ -121,7 +121,7 @@ describe('DocViewer', () => {
expect(checkIcon.nativeElement.innerHTML).toBe('check');
});
it('should display example viewer in multi file mode when user clicks expand', async () => {
it('should display example viewer in multi file mode when provided example is multi file snippet', async () => {
const fixture = TestBed.createComponent(DocViewer);
fixture.componentRef.setInput(
'docContent',
@@ -131,10 +131,6 @@ describe('DocViewer', () => {
await fixture.whenStable();
const exampleViewer = fixture.debugElement.query(By.directive(ExampleViewer));
const expandButton = fixture.debugElement.query(
By.css('button[aria-label="Expand code example"]'),
);
expandButton.nativeElement.click();
expect(exampleViewer).not.toBeNull();
expect(exampleViewer.componentInstance.view()).toBe(CodeExampleViewMode.MULTI_FILE);
@@ -196,13 +196,13 @@ export class DocViewer implements OnChanges {
const exampleRef = this.viewContainer.createComponent(ExampleViewer);
this.countOfExamples++;
exampleRef.instance.metadata = {
exampleRef.setInput('metadata', {
title: title ?? firstCodeSnippetTitle,
path,
files: snippets,
preview,
id: this.countOfExamples,
};
});
exampleRef.instance.githubUrl = `${GITHUB_CONTENT_URL}/${snippets[0].name}`;
exampleRef.instance.stackblitzUrl = `${ASSETS_EXAMPLES_PATH}/${snippets[0].name}.html`;
@@ -293,9 +293,6 @@ export class DocViewer implements OnChanges {
}
}
// Trigger change detection after setting inputs.
componentRef.changeDetectorRef.detectChanges();
// Attach a view to the ApplicationRef for change detection
// purposes and for hydration serialization to pick it up
// during SSG.
@@ -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}`,
),
)
);
}
}
-28
View File
@@ -1,28 +0,0 @@
load("//tools:defaults.bzl", "ts_library")
package(default_visibility = ["//visibility:private"])
ts_library(
name = "constants",
srcs = [
"index.ts",
],
visibility = [
"//adev/shared-docs:__subpackages__",
],
deps = [
":lib",
],
)
ts_library(
name = "lib",
srcs = glob(
[
"*.ts",
],
exclude = [
"index.ts",
],
),
)
-13
View File
@@ -1,13 +0,0 @@
/*!
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.dev/license
*/
// Used for both the table of contents and the home animation
export const RESIZE_EVENT_DELAY = 500;
// Used for the home animation
export const WEBGL_LOADED_DELAY = 250;
-1
View File
@@ -12,7 +12,6 @@
export * from './directives/index';
export * from './components/index';
export * from './constants/index';
export * from './interfaces/index';
export * from './providers/index';
export * from './services/index';
@@ -19,6 +19,4 @@ export interface TableOfContentsItem {
level: TableOfContentsLevel;
/** The unique title for this document page. */
title: string;
/** The top offset px of the heading */
top: number;
}
@@ -8,6 +8,7 @@
import {Renderer, Tokens} from 'marked';
import {codeToHtml} from '../shiki/shiki';
import {SECTION_HEADING, SECTION_SUB_HEADING} from '../styling/css-classes';
/**
* Custom renderer for marked that will be used to transform markdown files to HTML
@@ -55,21 +56,39 @@ export const renderer: Partial<Renderer> = {
<div class="docs-table docs-scroll-track-transparent">
<table>
<thead>
${this.tablerow({
text: header.map((cell) => this.tablecell(cell)).join(''),
})}
${this.tablerow({text: header.map((cell) => this.tablecell(cell)).join('')})}
</thead>
<tbody>
${rows
.map((row) =>
this.tablerow({
text: row.map((cell) => this.tablecell(cell)).join(''),
}),
)
.map((row) => this.tablerow({text: row.map((cell) => this.tablecell(cell)).join('')}))
.join('')}
</tbody>
</table>
</div>
`;
},
heading(this: Renderer, {text, depth, tokens}: Tokens.Heading) {
const id = text
.toLowerCase()
.replaceAll(' ', '-')
.replace(/[^a-z0-9-]/g, '');
// Since we have a code transformer `addApiLinksToHtml` which adds anchors
// to code blocks of known symbols, we add an additional `data-skip-anchor`
// attribute that prevents the transformation. This is needed since nested
// anchor tags are illegal and break the HTML.
const textRenderer = new Renderer();
textRenderer.codespan = ({text}) => `<code data-skip-anchor>${text}</code>`;
const parsedText = this.parser.parseInline(tokens, textRenderer);
// The template matches templates/section-heading.tsx
return `
<h${depth} id="${id}" class="${SECTION_HEADING} ${SECTION_SUB_HEADING}">
<a href="#${id}" aria-label="Link to ${text} section" tabIndex="-1">
${parsedText}
</a>
</h${depth}>
`;
},
};
@@ -27,3 +27,4 @@ export const HEADER_ENTRY_LABEL = 'docs-api-item-label';
export const SECTION_CONTAINER = 'docs-reference-section';
export const SECTION_HEADING = 'docs-reference-section-heading';
export const SECTION_SUB_HEADING = 'docs-reference-section-heading--sub';
@@ -38,8 +38,6 @@ export function getCurrentSymbol(): string | undefined {
return currentSymbol;
}
export function logUnknownSymbol(link: string, symbol: string): void {
console.warn(
`WARNING: {@link ${link}} is invalid, ${symbol} or ${currentSymbol}.${symbol} is unknown in this context`,
);
export function unknownSymbolMessage(link: string, symbol: string): string {
return `WARNING: {@link ${link}} is invalid, ${symbol} or ${currentSymbol}.${symbol} is unknown in this context`;
}
@@ -25,9 +25,7 @@ export function HeaderApi(props: {entry: DocEntryRenderable; showFullDescription
// When ADEV is not deployed on the main branch branch anymore,
// We should update it to point to the tag of the released version which ADEV runs on.
const sourceUrl = isDocEntryWithSourceInfo(entry)
? `https://github.com/angular/angular/blob/main${entry.source.filePath}#L${entry.source.startLine}-L${entry.source.endLine}`
: null;
const sourceUrl = sourceUrlForEntry(entry);
return (
<header className={HEADER_CLASS_NAME}>
@@ -40,9 +38,7 @@ export function HeaderApi(props: {entry: DocEntryRenderable; showFullDescription
{getEntryTypeDisplayName(entry.entryType)}
</div>
{entry.isDeprecated && (
<div className={`${HEADER_ENTRY_LABEL} type-deprecated full`}>
Deprecated
</div>
<div className={`${HEADER_ENTRY_LABEL} type-deprecated full`}>Deprecated</div>
)}
{entry.isDeveloperPreview && (
<div className={`${HEADER_ENTRY_LABEL} type-developer_preview full`}>
@@ -95,3 +91,16 @@ function getEntryTypeDisplayName(entryType: EntryType | string): string {
}
return entryType;
}
function sourceUrlForEntry(entry: DocEntryRenderable): string | null {
if (!isDocEntryWithSourceInfo(entry)) {
return null;
}
if (entry.source.filePath.includes('node_modules')) {
// We don't know the source path in external repos link the CLI
return null;
} else {
return `https://github.com/angular/angular/blob/main${entry.source.filePath}#L${entry.source.startLine}-L${entry.source.endLine}`;
}
}
@@ -754,6 +754,525 @@
"startLine": 112,
"endLine": 115
}
},
{
"name": "HttpRequest",
"isAbstract": false,
"entryType": "undecorated_class",
"members": [
{
"name": "constructor",
"signatures": [
{
"name": "constructor",
"params": [
{
"name": "method",
"description": "",
"type": "\"GET\" | \"HEAD\"",
"isOptional": false,
"isRestParam": false
},
{
"name": "url",
"description": "",
"type": "string",
"isOptional": false,
"isRestParam": false
},
{
"name": "init",
"description": "",
"type": "{ headers?: HttpHeaders | undefined; context?: HttpContext | undefined; reportProgress?: boolean | undefined; params?: HttpParams | undefined; responseType?: \"arraybuffer\" | \"blob\" | \"text\" | \"json\" | undefined; withCredentials?: boolean | undefined; transferCache?: boolean | { includeHeaders?: string[] | undefined; } | undefined; } | undefined",
"isOptional": true,
"isRestParam": false
}
],
"returnType": "foobar",
"description": "",
"entryType": "function",
"jsdocTags": [],
"rawComment": "",
"generics": [],
"isNewType": false
},
{
"name": "constructor",
"params": [
{
"name": "method",
"description": "",
"type": "\"DELETE\" | \"JSONP\" | \"OPTIONS\"",
"isOptional": false,
"isRestParam": false
},
{
"name": "url",
"description": "",
"type": "string",
"isOptional": false,
"isRestParam": false
},
{
"name": "init",
"description": "",
"type": "{ headers?: HttpHeaders | undefined; context?: HttpContext | undefined; reportProgress?: boolean | undefined; params?: HttpParams | undefined; responseType?: \"arraybuffer\" | \"blob\" | \"text\" | \"json\" | undefined; withCredentials?: boolean | undefined; } | undefined",
"isOptional": true,
"isRestParam": false
}
],
"returnType": "foobar",
"description": "",
"entryType": "function",
"jsdocTags": [],
"rawComment": "",
"generics": [],
"isNewType": false
},
{
"name": "constructor",
"params": [
{
"name": "method",
"description": "",
"type": "\"POST\"",
"isOptional": false,
"isRestParam": false
},
{
"name": "url",
"description": "",
"type": "string",
"isOptional": false,
"isRestParam": false
},
{
"name": "body",
"description": "",
"type": "T | null",
"isOptional": false,
"isRestParam": false
},
{
"name": "init",
"description": "",
"type": "{ headers?: HttpHeaders | undefined; context?: HttpContext | undefined; reportProgress?: boolean | undefined; params?: HttpParams | undefined; responseType?: \"arraybuffer\" | \"blob\" | \"text\" | \"json\" | undefined; withCredentials?: boolean | undefined; transferCache?: boolean | { includeHeaders?: string[] | undefined; } | undefined; } | undefined",
"isOptional": true,
"isRestParam": false
}
],
"returnType": "foobar",
"description": "",
"entryType": "function",
"jsdocTags": [],
"rawComment": "",
"generics": [],
"isNewType": false
},
{
"name": "constructor",
"params": [
{
"name": "method",
"description": "",
"type": "\"PUT\" | \"PATCH\"",
"isOptional": false,
"isRestParam": false
},
{
"name": "url",
"description": "",
"type": "string",
"isOptional": false,
"isRestParam": false
},
{
"name": "body",
"description": "",
"type": "T | null",
"isOptional": false,
"isRestParam": false
},
{
"name": "init",
"description": "",
"type": "{ headers?: HttpHeaders | undefined; context?: HttpContext | undefined; reportProgress?: boolean | undefined; params?: HttpParams | undefined; responseType?: \"arraybuffer\" | \"blob\" | \"text\" | \"json\" | undefined; withCredentials?: boolean | undefined; } | undefined",
"isOptional": true,
"isRestParam": false
}
],
"returnType": "foobar",
"description": "",
"entryType": "function",
"jsdocTags": [],
"rawComment": "",
"generics": [],
"isNewType": false
},
{
"name": "constructor",
"params": [
{
"name": "method",
"description": "",
"type": "string",
"isOptional": false,
"isRestParam": false
},
{
"name": "url",
"description": "",
"type": "string",
"isOptional": false,
"isRestParam": false
},
{
"name": "body",
"description": "",
"type": "T | null",
"isOptional": false,
"isRestParam": false
},
{
"name": "init",
"description": "",
"type": "{ headers?: HttpHeaders | undefined; context?: HttpContext | undefined; reportProgress?: boolean | undefined; params?: HttpParams | undefined; responseType?: \"arraybuffer\" | \"blob\" | \"text\" | \"json\" | undefined; withCredentials?: boolean | undefined; transferCache?: boolean | { includeHeaders?: string[] | undefined; } | undefined; } | undefined",
"isOptional": true,
"isRestParam": false
}
],
"returnType": "foobar",
"description": "",
"entryType": "function",
"jsdocTags": [],
"rawComment": "",
"generics": [],
"isNewType": false
},
{
"name": "constructor",
"params": [
{
"name": "method",
"description": "",
"type": "string",
"isOptional": false,
"isRestParam": false
},
{
"name": "url",
"description": "",
"type": "string",
"isOptional": false,
"isRestParam": false
},
{
"name": "third",
"description": "",
"type": "T | { headers?: HttpHeaders | undefined; context?: HttpContext | undefined; reportProgress?: boolean | undefined; params?: HttpParams | undefined; responseType?: \"arraybuffer\" | \"blob\" | \"text\" | \"json\" | undefined; withCredentials?: boolean | undefined; transferCache?: boolean | { includeHeaders?: string[] | undefined; } | undefined; } | null | undefined",
"isOptional": true,
"isRestParam": false
},
{
"name": "fourth",
"description": "",
"type": "{ headers?: HttpHeaders | undefined; context?: HttpContext | undefined; reportProgress?: boolean | undefined; params?: HttpParams | undefined; responseType?: \"arraybuffer\" | \"blob\" | \"text\" | \"json\" | undefined; withCredentials?: boolean | undefined; transferCache?: boolean | { includeHeaders?: string[] | undefined; } | undefined; } | undefined",
"isOptional": true,
"isRestParam": false
}
],
"returnType": "foobar",
"description": "",
"entryType": "function",
"jsdocTags": [],
"rawComment": "",
"generics": [],
"isNewType": false
}
],
"implementation": {
"params": [
{
"name": "method",
"description": "",
"type": "string",
"isOptional": false,
"isRestParam": false
},
{
"name": "url",
"description": "",
"type": "string",
"isOptional": false,
"isRestParam": false
},
{
"name": "third",
"description": "",
"type": "T | { headers?: HttpHeaders | undefined; context?: HttpContext | undefined; reportProgress?: boolean | undefined; params?: HttpParams | undefined; responseType?: \"arraybuffer\" | \"blob\" | \"text\" | \"json\" | undefined; withCredentials?: boolean | undefined; transferCache?: boolean | { includeHeaders?: string[] | undefined; } | undefined; } | null | undefined",
"isOptional": true,
"isRestParam": false
},
{
"name": "fourth",
"description": "",
"type": "{ headers?: HttpHeaders | undefined; context?: HttpContext | undefined; reportProgress?: boolean | undefined; params?: HttpParams | undefined; responseType?: \"arraybuffer\" | \"blob\" | \"text\" | \"json\" | undefined; withCredentials?: boolean | undefined; transferCache?: boolean | { includeHeaders?: string[] | undefined; } | undefined; } | undefined",
"isOptional": true,
"isRestParam": false
}
],
"isNewType": false,
"returnType": "HttpRequest<T>",
"generics": [],
"name": "constructor",
"description": "",
"entryType": "function",
"jsdocTags": [],
"rawComment": ""
},
"entryType": "function",
"description": "",
"jsdocTags": [],
"rawComment": "",
"memberType": "method",
"memberTags": []
},
{
"name": "body",
"type": "T | null",
"memberType": "property",
"memberTags": ["readonly"],
"description": "The request body, or `null` if one isn't set.\n\nBodies are not enforced to be immutable, as they can include a reference to any\nuser-defined data type. However, interceptors should take care to preserve\nidempotence by treating them as such.",
"jsdocTags": []
},
{
"name": "headers",
"type": "HttpHeaders",
"memberType": "property",
"memberTags": ["readonly"],
"description": "Outgoing headers for this request.",
"jsdocTags": []
},
{
"name": "context",
"type": "HttpContext",
"memberType": "property",
"memberTags": ["readonly"],
"description": "Shared and mutable context that can be used by interceptors",
"jsdocTags": []
},
{
"name": "reportProgress",
"type": "boolean",
"memberType": "property",
"memberTags": ["readonly"],
"description": "Whether this request should be made in a way that exposes progress events.\n\nProgress events are expensive (change detection runs on each event) and so\nthey should only be requested if the consumer intends to monitor them.\n\nNote: The `FetchBackend` doesn't support progress report on uploads.",
"jsdocTags": []
},
{
"name": "withCredentials",
"type": "boolean",
"memberType": "property",
"memberTags": ["readonly"],
"description": "Whether this request should be sent with outgoing credentials (cookies).",
"jsdocTags": []
},
{
"name": "responseType",
"type": "\"arraybuffer\" | \"blob\" | \"text\" | \"json\"",
"memberType": "property",
"memberTags": ["readonly"],
"description": "The expected response type of the server.\n\nThis is used to parse the response appropriately before returning it to\nthe requestee.",
"jsdocTags": []
},
{
"name": "method",
"type": "string",
"memberType": "property",
"memberTags": ["readonly"],
"description": "The outgoing HTTP request method.",
"jsdocTags": []
},
{
"name": "params",
"type": "HttpParams",
"memberType": "property",
"memberTags": ["readonly"],
"description": "Outgoing URL parameters.\n\nTo pass a string representation of HTTP parameters in the URL-query-string format,\nthe `HttpParamsOptions`' `fromString` may be used. For example:\n\n```ts\nnew HttpParams({fromString: 'angular=awesome'})\n```",
"jsdocTags": []
},
{
"name": "urlWithParams",
"type": "string",
"memberType": "property",
"memberTags": ["readonly"],
"description": "The outgoing URL with all URL parameters set.",
"jsdocTags": []
},
{
"name": "transferCache",
"type": "boolean | { includeHeaders?: string[] | undefined; } | undefined",
"memberType": "property",
"memberTags": ["readonly", "optional"],
"description": "The HttpTransferCache option for the request",
"jsdocTags": []
},
{
"name": "serializeBody",
"signatures": [
{
"name": "serializeBody",
"entryType": "function",
"description": "Transform the free-form body into a serialized format suitable for\ntransmission to the server.",
"generics": [],
"isNewType": false,
"jsdocTags": [],
"params": [],
"rawComment": "/**\n * Transform the free-form body into a serialized format suitable for\n * transmission to the server.\n */",
"returnType": "string | ArrayBuffer | Blob | FormData | URLSearchParams | null"
}
],
"implementation": {
"params": [],
"isNewType": false,
"returnType": "string | ArrayBuffer | Blob | FormData | URLSearchParams | null",
"generics": [],
"name": "serializeBody",
"description": "Transform the free-form body into a serialized format suitable for\ntransmission to the server.",
"entryType": "function",
"jsdocTags": [],
"rawComment": "/**\n * Transform the free-form body into a serialized format suitable for\n * transmission to the server.\n */"
},
"entryType": "function",
"description": "Transform the free-form body into a serialized format suitable for\ntransmission to the server.",
"jsdocTags": [],
"rawComment": "/**\n * Transform the free-form body into a serialized format suitable for\n * transmission to the server.\n */",
"memberType": "method",
"memberTags": []
},
{
"name": "detectContentTypeHeader",
"signatures": [
{
"name": "detectContentTypeHeader",
"entryType": "function",
"description": "Examine the body and attempt to infer an appropriate MIME type\nfor it.\n\nIf no such type can be inferred, this method will return `null`.",
"generics": [],
"isNewType": false,
"jsdocTags": [],
"params": [],
"rawComment": "/**\n * Examine the body and attempt to infer an appropriate MIME type\n * for it.\n *\n * If no such type can be inferred, this method will return `null`.\n */",
"returnType": "string | null"
}
],
"implementation": {
"params": [],
"isNewType": false,
"returnType": "string | null",
"generics": [],
"name": "detectContentTypeHeader",
"description": "Examine the body and attempt to infer an appropriate MIME type\nfor it.\n\nIf no such type can be inferred, this method will return `null`.",
"entryType": "function",
"jsdocTags": [],
"rawComment": "/**\n * Examine the body and attempt to infer an appropriate MIME type\n * for it.\n *\n * If no such type can be inferred, this method will return `null`.\n */"
},
"entryType": "function",
"description": "Examine the body and attempt to infer an appropriate MIME type\nfor it.\n\nIf no such type can be inferred, this method will return `null`.",
"jsdocTags": [],
"rawComment": "/**\n * Examine the body and attempt to infer an appropriate MIME type\n * for it.\n *\n * If no such type can be inferred, this method will return `null`.\n */",
"memberType": "method",
"memberTags": []
},
{
"name": "clone",
"signatures": [
{
"name": "clone",
"entryType": "function",
"description": "",
"generics": [],
"isNewType": false,
"jsdocTags": [],
"params": [],
"rawComment": "",
"returnType": "HttpRequest<T>"
},
{
"name": "clone",
"entryType": "function",
"description": "",
"generics": [],
"isNewType": false,
"jsdocTags": [],
"params": [
{
"name": "update",
"description": "",
"type": "{ headers?: HttpHeaders | undefined; context?: HttpContext | undefined; reportProgress?: boolean | undefined; params?: HttpParams | undefined; responseType?: \"arraybuffer\" | \"blob\" | \"text\" | \"json\" | undefined; withCredentials?: boolean | undefined; transferCache?: boolean | { includeHeaders?: string[] | undefined; } | undefined; body?: T | null | undefined; method?: string | undefined; url?: string | undefined; setHeaders?: { [name: string]: string | string[]; } | undefined; setParams?: { [param: string]: string; } | undefined; }",
"isOptional": false,
"isRestParam": false
}
],
"rawComment": "",
"returnType": "HttpRequest<T>"
},
{
"name": "clone",
"entryType": "function",
"description": "",
"generics": [{"name": "V"}],
"isNewType": false,
"jsdocTags": [],
"params": [
{
"name": "update",
"description": "",
"type": "{ headers?: HttpHeaders | undefined; context?: HttpContext | undefined; reportProgress?: boolean | undefined; params?: HttpParams | undefined; responseType?: \"arraybuffer\" | \"blob\" | \"text\" | \"json\" | undefined; withCredentials?: boolean | undefined; transferCache?: boolean | { includeHeaders?: string[] | undefined; } | undefined; body?: V | null | undefined; method?: string | undefined; url?: string | undefined; setHeaders?: { [name: string]: string | string[]; } | undefined; setParams?: { [param: string]: string; } | undefined; }",
"isOptional": false,
"isRestParam": false
}
],
"rawComment": "",
"returnType": "HttpRequest<V>"
}
],
"implementation": {
"params": [
{
"name": "update",
"description": "",
"type": "{ headers?: HttpHeaders | undefined; context?: HttpContext | undefined; reportProgress?: boolean | undefined; params?: HttpParams | undefined; responseType?: \"arraybuffer\" | \"blob\" | \"text\" | \"json\" | undefined; withCredentials?: boolean | undefined; transferCache?: boolean | { includeHeaders?: string[] | undefined; } | undefined; body?: any; method?: string | undefined; url?: string | undefined; setHeaders?: { [name: string]: string | string[]; } | undefined; setParams?: { [param: string]: string; } | undefined; }",
"isOptional": true,
"isRestParam": false
}
],
"isNewType": false,
"returnType": "HttpRequest<any>",
"generics": [],
"name": "clone",
"description": "",
"entryType": "function",
"jsdocTags": [],
"rawComment": ""
},
"entryType": "function",
"description": "",
"jsdocTags": [],
"rawComment": "",
"memberType": "method",
"memberTags": []
}
],
"generics": [{"name": "T"}],
"description": "An outgoing HTTP request with an optional typed body.\n\n`HttpRequest` represents an outgoing request, including URL, method,\nheaders, body, and other request configuration options. Instances should be\nassumed to be immutable. To modify a `HttpRequest`, the `clone`\nmethod should be used.",
"jsdocTags": [{"name": "publicApi", "comment": ""}],
"rawComment": "/**\n * An outgoing HTTP request with an optional typed body.\n *\n * `HttpRequest` represents an outgoing request, including URL, method,\n * headers, body, and other request configuration options. Instances should be\n * assumed to be immutable. To modify a `HttpRequest`, the `clone`\n * method should be used.\n *\n * @publicApi\n */",
"implements": [],
"source": {
"filePath": "/packages/common/http/src/request.ts",
"startLine": 131,
"endLine": 578
}
}
]
}
@@ -34,6 +34,13 @@ describe('markdown to html', () => {
const symbols = new Map<string, string>([
['AfterRenderPhase', 'core'],
['afterRender', 'core'],
['EmbeddedViewRef', 'core'],
['ChangeDetectionStrategy', 'core'],
['ChangeDetectorRef', 'core'],
['withNoHttpTransferCache', 'platform-browser'],
['withHttpTransferCacheOptions', 'platform-browser'],
['withI18nSupport', 'platform-browser'],
['withEventReplay', 'platform-browser'],
]);
setSymbols(symbols);
for (const entry of entryJson.entries) {
@@ -12,6 +12,7 @@ import {getRenderable} from '../processing';
import {DocEntryRenderable} from '../entities/renderables';
import {initHighlighter} from '../shiki/shiki';
import {configureMarkedGlobally} from '../marked/configuration';
import {setSymbols} from '../symbol-context';
// Note: The tests will probably break if the schema of the api extraction changes.
// All entries in the fake-entries are extracted from Angular's api.
@@ -28,6 +29,19 @@ describe('renderable', () => {
encoding: 'utf-8',
});
const entryJson = JSON.parse(entryContent) as any;
const symbols = new Map<string, string>([
['AfterRenderPhase', 'core'],
['afterRender', 'core'],
['EmbeddedViewRef', 'core'],
['ChangeDetectionStrategy', 'core'],
['ChangeDetectorRef', 'core'],
['withNoHttpTransferCache', 'platform-browser'],
['withHttpTransferCacheOptions', 'platform-browser'],
['withI18nSupport', 'platform-browser'],
['withEventReplay', 'platform-browser'],
]);
setSymbols(symbols);
for (const entry of entryJson.entries) {
const renderableJson = getRenderable(entry, '@angular/fakeentry') as DocEntryRenderable;
entries.set(entry['name'], renderableJson);
@@ -66,6 +66,14 @@ describe('jsdoc transforms', () => {
name: 'see',
comment: '{@link https://angular.dev}',
},
{
name: 'see',
comment: '{@link /cli/build ng build}',
},
{
name: 'see',
comment: '{@link /ecosystem/rxjs-interop/output-interop Output Interop}',
},
],
moduleName: 'test',
});
@@ -117,5 +125,30 @@ describe('jsdoc transforms', () => {
label: 'angular.dev',
url: 'https://angular.dev',
});
expect(entry.additionalLinks[10]).toEqual({
label: 'ng build',
url: '/cli/build',
});
expect(entry.additionalLinks[11]).toEqual({
label: 'Output Interop',
url: '/ecosystem/rxjs-interop/output-interop',
});
});
it('should throw on invalid relatie @link', () => {
const entryFn = () =>
addHtmlAdditionalLinks({
jsdocTags: [
{
name: 'see',
comment: '{@link cli/build ng build}',
},
],
moduleName: 'test',
});
expect(entryFn).toThrowError(/Forbidden relative link: cli\/build ng build/);
});
});
@@ -476,10 +476,11 @@ function appendPrefixAndSuffix(entry: DocEntry, codeTocData: CodeTableOfContents
*/
export function addApiLinksToHtml(htmlString: string): string {
const result = htmlString.replace(
// This regex looks for span/code blocks not wrapped by an anchor block.
// This regex looks for span/code blocks not wrapped by an anchor block
// or the tag doesn't contain `data-skip-anchor` attribute.
// Their content are then replaced with a link if the symbol is known
// The captured content ==> vvvvvvvv
/(?<!<a[^>]*>)(<(?:(?:span)|(?:code))[^>]*>\s*)([^<]*?)(\s*<\/(?:span|code)>)/g,
// The captured content ==> vvvvvvvv
/(?<!<a[^>]*>)(<(?:(?:span)|(?:code))(?!\sdata-skip-anchor)[^>]*>\s*)([^<]*?)(\s*<\/(?:span|code)>)/g,
(type: string, span1: string, potentialSymbolName: string, span2: string) => {
let [symbol, subSymbol] = potentialSymbolName.split(/(?:#|\.)/) as [string, string?];
@@ -31,7 +31,7 @@ import {
import {getLinkToModule} from './url-transforms';
import {addApiLinksToHtml} from './code-transforms';
import {getCurrentSymbol, getModuleName, logUnknownSymbol} from '../symbol-context';
import {getCurrentSymbol, getModuleName, unknownSymbolMessage} from '../symbol-context';
export const JS_DOC_REMARKS_TAG = 'remarks';
export const JS_DOC_USAGE_NOTES_TAG = 'usageNotes';
@@ -58,16 +58,10 @@ export function addHtmlDescription<T extends HasDescription & HasModuleName>(
const description = !!entry.description ? entry.description : jsDocDescription;
const shortTextMatch = description.match(firstParagraphRule);
const htmlDescription = getHtmlForJsDocText(description, entry).trim();
const shortHtmlDescription = getHtmlForJsDocText(
shortTextMatch ? shortTextMatch[0] : '',
entry,
).trim();
return {
...entry,
htmlDescription,
shortHtmlDescription,
};
const htmlDescription = getHtmlForJsDocText(description).trim();
const shortHtmlDescription = getHtmlForJsDocText(shortTextMatch ? shortTextMatch[0] : '').trim();
return {...entry, htmlDescription, shortHtmlDescription};
}
/**
@@ -81,7 +75,7 @@ export function addHtmlJsDocTagComments<T extends HasJsDocTags & HasModuleName>(
...entry,
jsdocTags: entry.jsdocTags.map((tag) => ({
...tag,
htmlComment: getHtmlForJsDocText(tag.comment, entry),
htmlComment: getHtmlForJsDocText(tag.comment),
})),
};
}
@@ -100,20 +94,16 @@ export function addHtmlUsageNotes<T extends HasJsDocTags>(entry: T): T & HasHtml
const usageNotesTag = entry.jsdocTags.find(
({name}) => name === JS_DOC_USAGE_NOTES_TAG || name === JS_DOC_REMARKS_TAG,
);
const htmlUsageNotes = usageNotesTag
? (marked.parse(wrapExampleHtmlElementsWithCode(usageNotesTag.comment)) as string)
: '';
const transformedHtml = addApiLinksToHtml(htmlUsageNotes);
const htmlUsageNotes = usageNotesTag ? getHtmlForJsDocText(usageNotesTag.comment) : '';
return {
...entry,
htmlUsageNotes: transformedHtml,
htmlUsageNotes,
};
}
/** Given a markdown JsDoc text, gets the rendered HTML. */
function getHtmlForJsDocText<T extends HasModuleName>(text: string, entry: T): string {
function getHtmlForJsDocText(text: string): string {
const parsed = marked.parse(convertLinks(wrapExampleHtmlElementsWithCode(text))) as string;
return addApiLinksToHtml(parsed);
}
@@ -126,7 +116,7 @@ export function setEntryFlags<T extends HasJsDocTags & HasModuleName>(
...entry,
isDeprecated: isDeprecatedEntry(entry),
deprecationMessage: deprecationMessage
? getHtmlForJsDocText(deprecationMessage, entry)
? getHtmlForJsDocText(deprecationMessage)
: deprecationMessage,
isDeveloperPreview: isDeveloperPreview(entry),
isExperimental: isExperimental(entry),
@@ -195,10 +185,16 @@ function parseAtLink(link: string): {label: string; url: string} {
let [rawSymbol, description] = link.split(/\s(.+)/);
if (rawSymbol.startsWith('#')) {
rawSymbol = rawSymbol.substring(1);
} else if (rawSymbol.startsWith('http://') || rawSymbol.startsWith('https://')) {
} else if (rawSymbol.includes('/')) {
if (!rawSymbol.startsWith('/') && !rawSymbol.startsWith('http')) {
throw Error(
`Forbidden relative link: ${link}. Links should be absolute and start with a slash`,
);
}
return {
url: rawSymbol,
label: rawSymbol.split('/').pop()!,
label: description ?? rawSymbol.split('/').pop()!,
};
}
@@ -214,11 +210,9 @@ function parseAtLink(link: string): {label: string; url: string} {
moduleName = getModuleName(`${currentSymbol}.${symbol}`);
if (!moduleName || !currentSymbol) {
// TODO: remove the links that generate this error
// TODO: throw an error when there are no more warning generated
logUnknownSymbol(link, symbol);
return {label, url: '#'};
throw unknownSymbolMessage(link, symbol);
}
subSymbol = symbol;
symbol = currentSymbol;
}
@@ -21,7 +21,7 @@
"prefix": "app",
"architect": {
"build": {
"builder": "@angular-devkit/build-angular:application",
"builder": "@angular/build:application",
"options": {
"outputPath": "dist/example-app",
"index": "src/index.html",
@@ -66,7 +66,7 @@
"defaultConfiguration": "production"
},
"serve": {
"builder": "@angular-devkit/build-angular:dev-server",
"builder": "@angular/build:dev-server",
"configurations": {
"production": {
"browserTarget": "example-app:build:production"
@@ -78,13 +78,13 @@
"defaultConfiguration": "development"
},
"extract-i18n": {
"builder": "@angular-devkit/build-angular:extract-i18n",
"builder": "@angular/build:extract-i18n",
"options": {
"browserTarget": "example-app:build"
}
},
"test": {
"builder": "@angular-devkit/build-angular:karma",
"builder": "@angular/build:karma",
"options": {
"main": "src/test.ts",
"polyfills": ["zone.js", "zone.js/testing"],
@@ -23,7 +23,7 @@
"zone.js": "~0.13.0"
},
"devDependencies": {
"@angular-devkit/build-angular": "^19.0.0",
"@angular/build": "^19.0.0",
"@angular/cli": "^19.0.0",
"@angular/compiler-cli": "^19.0.0",
"@types/jasmine": "~3.10.0",
@@ -39,7 +39,7 @@
"prefix": "app",
"architect": {
"build": {
"builder": "@angular-devkit/build-angular:application",
"builder": "@angular/build:application",
"options": {
"outputPath": "dist/first-app",
"index": "src/index.html",
@@ -76,7 +76,7 @@
"defaultConfiguration": "production"
},
"serve": {
"builder": "@angular-devkit/build-angular:dev-server",
"builder": "@angular/build:dev-server",
"configurations": {
"production": {
"buildTarget": "first-app:build:production"
@@ -88,7 +88,7 @@
"defaultConfiguration": "development"
},
"extract-i18n": {
"builder": "@angular-devkit/build-angular:extract-i18n",
"builder": "@angular/build:extract-i18n",
"options": {
"buildTarget": "first-app:build"
}
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"
}
}
-1
View File
@@ -25,7 +25,6 @@ ng_module(
],
),
deps = [
"//adev/shared-docs/constants",
"//adev/shared-docs/interfaces",
"//adev/shared-docs/providers",
"//adev/shared-docs/utils",
@@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.dev/license
*/
import {Injectable, inject, signal} from '@angular/core';
import {Injectable, inject, linkedSignal, signal} from '@angular/core';
import {NavigationItem} from '../interfaces/index';
import {Router} from '@angular/router';
@@ -19,11 +19,13 @@ export class NavigationState {
private readonly _activeNavigationItem = signal<NavigationItem | null>(null);
private readonly _expandedItems = signal<NavigationItem[]>([]);
private readonly _isMobileNavVisible = signal<boolean>(false);
private readonly _level = linkedSignal(() => this._expandedItems().length);
primaryActiveRouteItem = signal<string | null>(null);
activeNavigationItem = this._activeNavigationItem.asReadonly();
expandedItems = this._expandedItems.asReadonly();
isMobileNavVisible = this._isMobileNavVisible.asReadonly();
level = this._level.asReadonly();
async toggleItem(item: NavigationItem): Promise<void> {
if (!item.children) {
@@ -59,7 +61,7 @@ export class NavigationState {
return;
}
// Returns item when parent node was already expanded
const parentItem = this._expandedItems().find(
const parentItem = this.actualExpandedItems().find(
(expandedItem) =>
item.parent?.label === expandedItem.label && item.parent?.path === expandedItem.path,
);
@@ -100,13 +102,25 @@ export class NavigationState {
private expand(item: NavigationItem): void {
// Add item to the expanded items list
this._expandedItems.update((expandedItems) => {
return [...(expandedItems ?? []), {...item, isExpanded: true}];
return [...(this.actualExpandedItems() ?? []), {...item, isExpanded: true}];
});
// No need to update the level here, this is handled by linkedSignal already
}
private collapse(item: NavigationItem): void {
item.isExpanded = false;
this._expandedItems.update((expandedItems) => expandedItems.slice(0, -1));
// We won't remove the item, just update the level,
// this allows animation on the items to hide them without destroying them
this._level.set(this.actualExpandedItems().length - 1);
}
/**
* return the actual navigation items, that is to say the one that match the current level
*/
private actualExpandedItems() {
return this.expandedItems().slice(0, this.level());
}
private async navigateToFirstPageOfTheCategory(path: string): Promise<boolean> {
@@ -6,10 +6,11 @@
* found in the LICENSE file at https://angular.dev/license
*/
import {DOCUMENT, isPlatformBrowser} from '@angular/common';
import {inject, signal, Injectable, PLATFORM_ID} from '@angular/core';
import {DOCUMENT} from '@angular/common';
import {inject, signal, Injectable, PLATFORM_ID, DestroyRef} from '@angular/core';
import {TableOfContentsItem, TableOfContentsLevel} from '../interfaces/index';
import {on} from 'events';
/**
* Name of an attribute that is set on an element that should be
@@ -29,38 +30,19 @@ export class TableOfContentsLoader {
readonly tableOfContentItems = signal([] as TableOfContentsItem[]);
private readonly document = inject(DOCUMENT);
private readonly platformId = inject(PLATFORM_ID);
buildTableOfContent(docElement: Element): void {
const headings = this.getHeadings(docElement);
const tocList: TableOfContentsItem[] = headings.map((heading) => ({
id: heading.id,
level: heading.tagName.toLowerCase() as TableOfContentsLevel,
title: this.getHeadingTitle(heading),
top: this.calculateTop(heading),
}));
this.tableOfContentItems.set(tocList);
}
// Update top value of heading, it should be executed after window resize
updateHeadingsTopValue(element: HTMLElement): void {
const headings = this.getHeadings(element);
const updatedTopValues = new Map<string, number>();
for (const heading of headings) {
updatedTopValues.set(heading.id, this.calculateTop(heading));
}
this.tableOfContentItems.update((oldItems) => {
let newItems = [...oldItems];
for (const item of newItems) {
item.top = updatedTopValues.get(item.id) ?? 0;
}
return newItems;
});
}
private getHeadingTitle(heading: HTMLHeadingElement): string {
const div: HTMLDivElement = this.document.createElement('div');
div.innerHTML = heading.innerHTML;
@@ -79,11 +61,37 @@ export class TableOfContentsLoader {
);
}
private calculateTop(heading: HTMLHeadingElement): number {
if (!isPlatformBrowser(this.platformId)) return 0;
return (
Math.floor(heading.offsetTop > 0 ? heading.offsetTop : heading.getClientRects()[0]?.top) -
this.toleranceThreshold
);
/**
* The methods setups several IntersectionObservers to determine when a heading is at the top of
* the viewport. Using an IntersectionObserver is more efficient than reading DOM position
* as it won't trigger any reflow.
*/
setupIntersectionObserver(
element: Element,
destroyRef: DestroyRef,
onActiveId: (id: string) => void,
): void {
// If we're at the top the we need the default active id to be the first heading.
const headings = this.getHeadings(element);
onActiveId(headings[0].id);
headings.forEach((heading) => {
const ioConfiguration = {
/**
* This rootMargin creates a horizontal line at 5% from the top of the viewport
* that will help trigger an intersection at that the very point.
*/
rootMargin: '0% 0% -95% 0%',
/** 0 is the default */
threshold: 0,
};
const observer = new IntersectionObserver((entries, o) => {
if (entries[0].isIntersecting) {
onActiveId(entries[0].target.id);
}
}, ioConfiguration);
observer.observe(heading);
destroyRef.onDestroy(() => observer.disconnect());
});
}
}
@@ -6,153 +6,32 @@
* found in the LICENSE file at https://angular.dev/license
*/
import {DOCUMENT, ViewportScroller} from '@angular/common';
import {
DestroyRef,
EnvironmentInjector,
Injectable,
afterNextRender,
inject,
signal,
} from '@angular/core';
import {RESIZE_EVENT_DELAY} from '../constants/index';
import {takeUntilDestroyed} from '@angular/core/rxjs-interop';
import {auditTime, debounceTime, fromEvent, startWith} from 'rxjs';
import {WINDOW} from '../providers/index';
import {ViewportScroller} from '@angular/common';
import {DestroyRef, Injectable, inject, signal} from '@angular/core';
import {TableOfContentsLoader} from './table-of-contents-loader.service';
export const SCROLL_EVENT_DELAY = 20;
@Injectable({providedIn: 'root'})
// The service is responsible for listening for scrolling and resizing,
// thanks to which it sets the active item in the Table of contents
export class TableOfContentsScrollSpy {
private readonly tableOfContentsLoader = inject(TableOfContentsLoader);
private readonly document = inject(DOCUMENT);
private readonly window = inject(WINDOW);
private readonly viewportScroller = inject(ViewportScroller);
private readonly injector = inject(EnvironmentInjector);
private contentSourceElement: HTMLElement | null = null;
private lastContentWidth = 0;
activeItemId = signal<string | null>(null);
scrollbarThumbOnTop = signal<boolean>(true);
startListeningToScroll(
contentSourceElement: HTMLElement | null,
// `destroyRef` is required because the caller may invoke `startListeningToScroll`
// multiple times. Without it, previous event listeners would not be disposed of,
// leading to the accumulation of new event listeners.
destroyRef: DestroyRef,
) {
this.contentSourceElement = contentSourceElement;
this.lastContentWidth = this.getContentWidth();
this.setScrollEventHandlers(destroyRef);
this.setResizeEventHandlers(destroyRef);
destroyRef.onDestroy(() => {
// We also need to clean up the source element once the view that calls
// `startListeningToScroll` is destroyed, as this will keep a reference
// to an element that has been removed from the DOM.
this.contentSourceElement = null;
});
}
scrollToTop(): void {
this.viewportScroller.scrollToPosition([0, 0]);
}
// After window resize, we should update top value of each table content item
private setResizeEventHandlers(destroyRef: DestroyRef) {
fromEvent(this.window, 'resize')
.pipe(debounceTime(RESIZE_EVENT_DELAY), takeUntilDestroyed(destroyRef), startWith())
.subscribe(() => {
this.updateHeadingsTopAfterResize();
});
// We need to observe the height of the docs-viewer because it may change after the
// assets (fonts, images) are loaded. They can (and will) change the y-position of the headings.
const docsViewer = this.document.querySelector('docs-viewer');
if (docsViewer) {
const ref = afterNextRender(
() => {
const resizeObserver = new ResizeObserver(() => this.updateHeadingsTopAfterResize());
resizeObserver.observe(docsViewer);
destroyRef.onDestroy(() => resizeObserver.disconnect());
setupActiveItemListener(contentSourceElement: HTMLElement, destroyRef: DestroyRef): void {
if (contentSourceElement) {
this.tableOfContentsLoader.setupIntersectionObserver(
contentSourceElement,
destroyRef,
(id) => {
this.activeItemId.set(id);
},
{injector: this.injector, manualCleanup: true},
);
destroyRef.onDestroy(() => ref.destroy());
}
}
private updateHeadingsTopAfterResize(): void {
this.lastContentWidth = this.getContentWidth();
const contentElement = this.contentSourceElement;
if (contentElement) {
this.tableOfContentsLoader.updateHeadingsTopValue(contentElement);
this.setActiveItemId();
}
}
private setScrollEventHandlers(destroyRef: DestroyRef): void {
const scroll$ = fromEvent(this.document, 'scroll').pipe(
auditTime(SCROLL_EVENT_DELAY),
takeUntilDestroyed(destroyRef),
);
scroll$.subscribe(() => this.setActiveItemId());
}
private setActiveItemId(): void {
const tableOfContentItems = this.tableOfContentsLoader.tableOfContentItems();
if (tableOfContentItems.length === 0) return;
// Resize could emit scroll event, in that case we could stop setting active item until resize will be finished
if (this.lastContentWidth !== this.getContentWidth()) {
return;
}
const scrollOffset = this.getScrollOffset();
if (scrollOffset === null) return;
for (const [i, currentLink] of tableOfContentItems.entries()) {
const nextLink = tableOfContentItems[i + 1];
// A link is considered active if the page is scrolled past the
// anchor without also being scrolled passed the next link.
const isActive =
scrollOffset >= currentLink.top && (!nextLink || nextLink.top >= scrollOffset);
// When active item was changed then trigger change detection
if (isActive && this.activeItemId() !== currentLink.id) {
this.activeItemId.set(currentLink.id);
return;
}
}
if (scrollOffset < tableOfContentItems[0].top && this.activeItemId() !== null) {
this.activeItemId.set(null);
}
const scrollOffsetZero = scrollOffset === 0;
if (scrollOffsetZero !== this.scrollbarThumbOnTop()) {
// we want to trigger change detection only when the value changes
this.scrollbarThumbOnTop.set(scrollOffsetZero);
}
}
// Gets the scroll offset of the scroll container
private getScrollOffset(): number {
return this.window.scrollY;
}
private getContentWidth(): number {
return this.document.body.clientWidth || Number.MAX_SAFE_INTEGER;
}
}
+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;
}
}
+1 -1
View File
@@ -8,7 +8,7 @@
declare global {
interface Window {
gtag: (...args: any[]) => void;
gtag?(...args: any[]): void;
}
}
+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',
@@ -1919,7 +1919,7 @@ export const RECOMMENDATIONS: Step[] = [
level: ApplicationComplexity.Medium,
step: 'v16 createUrlTree',
action:
'After bug fixes in `Router.createUrlTree` you may have to readjust tests which mock `ActiveRoute`. [Read further](https://github.com/angular/angular/blob/main/CHANGELOG.md#1600-next1-2023-03-01)',
'After bug fixes in `Router.createUrlTree` you may have to readjust tests which mock `ActivatedRoute`. [Read further](https://github.com/angular/angular/blob/main/CHANGELOG.md#1600-2023-05-03)',
},
{
possibleIn: 1600,
-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',
+2 -2
View File
@@ -1,4 +1,4 @@
{
"branchName": "refs/heads/main",
"sha": "4e45bc8fc2704830d6e4099f2455d0135278b002"
"branchName": "refs/heads/19.2.x",
"sha": "da1d5784755500cc064c433e5a4754a1ff1946fd"
}
+1 -1
View File
@@ -84,7 +84,7 @@
{
"name": "external-dependencies",
"type": "array",
"description": "Exclude the listed external dependencies from being bundled into the bundle. Instead, the created bundle relies on these dependencies to be available during runtime."
"description": "Exclude the listed external dependencies from being bundled into the bundle. Instead, the created bundle relies on these dependencies to be available during runtime. Note: `@foo/bar` marks all paths within the `@foo/bar` package as external, including sub-paths like `@foo/bar/baz`."
},
{
"name": "extract-licenses",
@@ -0,0 +1,33 @@
# Custom build pipeline
When building an Angular app we strongly recommend you to use the Angular CLI to leverage its structure-dependent update functionality and build system abstraction. This way your projects benefit from the latest security, performance, and API improvements and transparent build improvements.
This page explores the **rare use cases** when you need a custom build pipeline that does not use the Angular CLI. All listed tools below are open source build plugins that are maintained by members of the Angular community. To learn more about their support model and maintenance status look at their documentation and GitHub repository URLs.
## When should you use a custom build pipeline?
There are some niche use cases when you may want to maintain a custom build pipeline. For example:
* You have an existing app using a different toolchain and you’d like to add Angular to it
* You’re strongly coupled to [module federation](https://module-federation.io/) and unable to adopt bundler-agnostic [native federation](https://www.npmjs.com/package/@angular-architects/native-federation)
* You’d like to create an short-lived experiment using your favorite build tool
## What are the options?
Currently, there are two well supported community tools that enable you to create a custom build pipeline with a [Vite plugin](https://www.npmjs.com/package/@analogjs/vite-plugin-angular) and [Rspack plugin](https://www.npmjs.com/package/@ng-rspack/build). Both of them use underlying abstractions that power the Angular CLI. They allow you to create a flexible build pipeline and require manual maintenance and no automated update experience.
### Rspack
Rspack is a Rust-based bundler that aims to provide compatibility with the webpack plugin ecosystem.
If your project is tightly coupled to the webpack ecosystem, heavily relying on a custom webpack configuration you can leverage Rspack to improve your build times.
You can find more about Angular Rspack on the project’s [documentation website](https://angular-rspack.dev/guide/migration/from-webpack).
### Vite
Vite is a frontend build tool that aims to provide a faster and leaner development experience for modern web projects. Vite is also extensible through its plugin system that allows ecosystems to build integrations with Vite, such as Vitest for unit and browser testing, Storybook for authoring components in isolation, and more. The Angular CLI also uses Vite as its development server.
The [AnalogJS Vite plugin for Angular](https://www.npmjs.com/package/@analogjs/vite-plugin-angular) enables the adoption of Angular with a project or framework that uses or is built on top of Vite. This can consist of developing and building an Angular project with Vite directly, or adding Angular to an existing project or pipeline. One example is integrating Angular UI components into a documentation website using [Astro and Starlight](https://analogjs.org/docs/packages/astro-angular/overview).
You can learn more about AnalogJS and how to use the plugin through its [documentation page](https://analogjs.org/docs/packages/vite-plugin-angular/overview).
@@ -90,7 +90,7 @@ describe('BannerComponent (with beforeEach)', () => {
// #enddocregion v4-test-3
// #docregion v4-test-4
it('should find the <p> with fixture.debugElement.nativeElement)', () => {
it('should find the <p> with fixture.debugElement.nativeElement', () => {
// #docregion debugElement-nativeElement
const bannerDe: DebugElement = fixture.debugElement;
const bannerEl: HTMLElement = bannerDe.nativeElement;
@@ -132,7 +132,7 @@ TypeScript property name as a key, rather than the alias.
### ngOnDestroy
The `ngOnDestroy` method runs once just before a component is destroyed. Angular destroys a
component when it is no longer shown on the page, such as being hidden by `NgIf` or upon navigating
component when it is no longer shown on the page, such as being hidden by `@if` or upon navigating
to another page.
#### DestroyRef
+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 {
@@ -13,11 +13,11 @@ When you want to add event listeners to an HTML element, you wrap the event with
`,
...
})
export class AppComponent({
export class AppComponent{
updateField(): void {
console.log('Field is updated!');
}
})
}
```
In this example, Angular calls `updateField` every time the `<input>` element emits a `keyup` event.
@@ -71,11 +71,11 @@ However, since this is a common scenario, Angular lets you filter the events by
`,
...
})
export class AppComponent({
export class AppComponent{
updateField(event: KeyboardEvent): void {
console.log('The user pressed enter in the text field.');
}
})
}
```
You can also add additional key modifiers:
@@ -93,7 +93,7 @@ Angular also allows you to specify [Code values for keyboard events](https://dev
```angular-html
<!-- Matches alt and left shift -->
<input type="text" (keydown.code.alt.leftshift)="updateField($event)" />
<input type="text" (keydown.code.alt.shiftleft)="updateField($event)" />
```
This can be useful for handling keyboard events consistently across different operating systems. For example, when using the Alt key on MacOS devices, the `key` property reports the key based on the character already modified by the Alt key. This means that a combination like Alt + S reports a `key` value of `'ß'`. The `code` property, however, corresponds to the physical or virtual button pressed rather than the character produced.
@@ -68,7 +68,7 @@ Angular expressions additionally also support the following non-standard operato
| Operator | Example(s) |
| ------------------------------- | ------------------------------ |
| [Pipe](/guides/templates/pipes) | `{{ total \| currency }}` |
| [Pipe](/guide/templates/pipes) | `{{ total \| currency }}` |
| Optional chaining\* | `someObj.someProp?.nestedProp` |
| Non-null assertion (TypeScript) | `someObj!.someProp` |
@@ -68,59 +68,51 @@ export class AppComponent {
```angular-ts
// './counter/counter.component.ts';
import { Component, EventEmitter, Input, Output } from '@angular/core';
import { Component, model } from '@angular/core';
@Component({
selector: 'app-counter',
template: `
<button (click)="updateCount(-1)">-</button>
<span>{{ count }}</span>
<span>{{ count() }}</span>
<button (click)="updateCount(+1)">+</button>
`,
})
export class CounterComponent {
@Input() count: number;
@Output() countChange = new EventEmitter<number>();
count = model<number>(0);
updateCount(amount: number): void {
this.count += amount;
this.countChange.emit(this.count);
this.count.update(currentCount => currentCount + amount);
}
}
```
### Enabling two-way binding between components
If we break down the example above to its core , each two-way binding for components requires the following:
If we break down the example above to its core, each two-way binding for components requires the following:
The child component must contain:
1. An `@Input()` property
1. A corresponding `@Output()` event emitter that has the exact same name as the input property plus "Change" at the end. The emitter must also emit the same type as the input property.
1. A method that emits to the event emitter with the updated value of the `@Input()`.
The child component must contain a `model` property.
Here is a simplified example:
```angular-ts
// './counter/counter.component.ts';
import { Component, EventEmitter, Input, Output } from '@angular/core';
import { Component, model } from '@angular/core';
@Component({ // Omitted for brevity })
export class CounterComponent {
@Input() count: number;
@Output() countChange = new EventEmitter<number>();
count = model<number>(0);
updateCount(amount: number): void {
this.count += amount;
this.countChange.emit(this.count);
this.count.update(currentCount => currentCount + amount);
}
}
```
The parent component must:
1. Wrap the `@Input()` property name in the two-way binding syntax.
1. Specify the corresponding property to which the updated value is assigned
1. Wrap the `model` property name in the two-way binding syntax.
1. Assign a property or a signal to the `model` property.
Here is a simplified example:
+42 -5
View File
@@ -7,7 +7,7 @@ The main advantages to removing ZoneJS as a dependency are:
- **Improved performance**: ZoneJS uses DOM events and async tasks as indicators of when application state _might_ have updated and subsequently triggers application synchronization to run change detection on the application's views. ZoneJS does not have any insight into whether application state actually changed and so this synchronization is triggered more frequently than necessary.
- **Improved Core Web Vitals**: ZoneJS brings a fair amount of overhead, both in payload size and in startup time cost.
- **Improved debugging experience**: ZoneJS makes debugging code more difficult. Stack traces are harder to understand with ZoneJS. It's also difficult to understand when code breaks as a result of being outside the Angular Zone.
- **Better ecosystem compatibility**: ZoneJS works by patching browser APIs but does not automatically have patches for every new browser API. Some APIs simply cannot be patched effectively, such as `async`/`await`, and have to be downleveled to work with ZoneJS. Sometimes libraries in the ecosystem are also incompatible with the way ZoneJS patches the native APIs. Removing ZoneJS as a dependency ensures better long-term compatibility by removing a source of complexity, monkey patching, and ongoing maintenance.
- **Better ecosystem compatibility**: ZoneJS works by patching browser APIs but does not automatically have patches for every new browser API. Some APIs cannot be patched effectively, such as `async`/`await`, and have to be downleveled to work with ZoneJS. Sometimes libraries in the ecosystem are also incompatible with the way ZoneJS patches the native APIs. Removing ZoneJS as a dependency ensures better long-term compatibility by removing a source of complexity, monkey patching, and ongoing maintenance.
## Enabling Zoneless in an application
@@ -60,6 +60,7 @@ use [ChangeDetectionStrategy.OnPush](/best-practices/skipping-subtrees#using-onp
The `OnPush` change detection strategy is not required, but it is a recommended step towards zoneless compatibility for application components. It is not always possible for library components to use `ChangeDetectionStrategy.OnPush`.
When a library component is a host for user-components which might use `ChangeDetectionStrategy.Default`, it cannot use `OnPush` because that would prevent the child component from being refreshed if it is not `OnPush` compatible and relies on ZoneJS to trigger change detection. Components can use the `Default` strategy as long as they notify Angular when change detection needs to run (calling `markForCheck`, using signals, `AsyncPipe`, etc.).
Being a host for a user component means using an API such as `ViewContainerRef.createComponent` and not just hosting a portion of a template from a user component (i.e. content projection or a using a template ref input).
### Remove `NgZone.onMicrotaskEmpty`, `NgZone.onUnstable`, `NgZone.isStable`, or `NgZone.onStable`
@@ -85,14 +86,39 @@ are used in applications that still rely on ZoneJS.
If you are using SSR with Angular, you may know that it relies on ZoneJS to help determine when the application
is "stable" and can be serialized. If there are asynchronous tasks that should prevent serialization, an application
not using ZoneJS will need to make Angular aware of these with the `PendingTasks` service. Serialization
not using ZoneJS must make Angular aware of these with the [PendingTasks](/api/core/PendingTasks) service. Serialization
will wait for the first moment that all pending tasks have been removed.
The two most straightforward uses of pending tasks are the `run` method:
```typescript
const taskService = inject(PendingTasks);
taskService.run(async () => {
const someResult = await doSomeWorkThatNeedsToBeRendered();
this.someState.set(someResult);
});
```
For more complicated use-cases, you can manuall add and remove a pending tasks:
```typescript
const taskService = inject(PendingTasks);
const taskCleanup = taskService.add();
await doSomeWorkThatNeedsToBeRendered();
taskCleanup();
try {
await doSomeWorkThatNeedsToBeRendered();
} catch {
// handle error
} finally {
taskCleanup();
}
```
In addition, the [pendingUntilEvent](/api/core/rxjs-interop/pendingUntilEvent#) helper in `rxjs-interop` ensures
the application remains unstable until the observable emits, complets, errors, or is unsubscribed.
```typescript
readonly myObservableState = someObservable.pipe(pendingUntilEvent());
```
The framework uses this service internally as well to prevent serialization until asynchronous tasks are complete. These include, but are not limited to,
@@ -122,11 +148,22 @@ scheduled change detection. Tests should ensure these notifications
are happening and allow Angular to handle when to synchronize
state rather than manually forcing it to happen in the test.
For existing test suites, using `fixture.detectChanges()` is a common pattern
and it is likely not worth the effort of converting these to
`await fixture.whenStable()`. `TestBed` will still enforce that the
fixture's component is `OnPush` compatible and throws `ExpressionChangedAfterItHasBeenCheckedError`
if it finds that template values were updated without a
change notification (i.e. `fixture.componentInstance.someValue = 'newValue';`).
If the component is used in production, this issue should be addressed by updating
the component to use signals for state or call `ChangeDetectorRef.markForCheck()`.
If the component is only used as a test wrapper and never used in an application,
it is acceptable to use `fixture.changeDetectorRef.markForCheck()`.
### Debug-mode check to ensure updates are detected
Angular also provides an additional tool to help verify that an application is making
updates to state in a zoneless-compatible way. `provideExperimentalCheckNoChangesForDebug`
can be used to periodically check to ensure that no bindings have been updated
without a notification. Angular will throw `ExpressionChangedAfterItHasBeenCheckedError`
without a notification. Angular throws `ExpressionChangedAfterItHasBeenCheckedError`
if there is an updated binding that would not have refreshed by the zoneless change
detection.
+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>
+4 -2
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
@@ -159,10 +159,12 @@ For example, to update from version 10 to version 12:
Occasionally we introduce new APIs under the label of "Developer Preview". These are APIs that are fully functional and polished, but that we are not ready to stabilize under our normal deprecation policy.
This may be because we want to gather feedback from real applications before stabilization, or because the associated documentation or migration tooling is not fully complete.
This may be because we want to gather feedback from real applications before stabilization, or because the associated documentation or migration tooling is not fully complete. Feedback can be provided via a [GitHub issue](https://github.com/angular/angular/issues), where developers can share their experiences, report bugs, or suggest improvements to help refine the feature.
The policies and practices that are described in this document do not apply to APIs marked as Developer Preview. Such APIs can change at any time, even in new patch versions of the framework. Teams should decide for themselves whether the benefits of using Developer Preview APIs are worth the risk of breaking changes outside of our normal use of semantic versioning.
## Experimental
These APIs might not become stable at all or have significant changes before becoming stable.
+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.
@@ -270,10 +270,14 @@ The Angular CLI down-levels the bundle to a language level that is supported by
### d.ts bundling / type definition flattening
As of APF v8 it is now preferred to run [API Extractor](https://api-extractor.com), to bundle TypeScript definitions so that the entire API appears in a single file.
As of APF v8, it is recommended to bundle TypeScript definitions.
Bundling of type definitions can significantly speed up compilations for users, especially if there are many individual `.ts` source files in your library.
In prior APF versions each entry point would have a `src` directory next to the .d.ts entry point and this directory contained individual d.ts files matching the structure of the original source code.
While this distribution format is still allowed and supported, it is highly discouraged because it confuses tools like IDEs that then offer incorrect autocompletion, and allows users to depend on deep-import paths which are typically not considered to be public API of a library or a package.
Angular uses [`rollup-plugin-dts`](https://github.com/Swatinem/rollup-plugin-dts) to flatten `.d.ts` files (using `rollup`, similar to how FESM files are created).
Using rollup for `.d.ts` bundling is beneficial as it supports code splitting between entry-points.
For example, consider you have multiple entrypoints relying on the same shared type, a shared `.d.ts` file would be created along with the larger flattened `.d.ts` files.
This is desirable and avoids duplication of types.
### Tslib
@@ -22,7 +22,7 @@
"@angular/build": "^19.0.0",
"@angular/cli": "^19.0.0",
"@angular/compiler-cli": "^19.0.0",
"typescript": "~5.7.0"
"typescript": "~5.8.0"
}
},
"node_modules/@ampproject/remapping": {
@@ -6538,9 +6538,9 @@
}
},
"node_modules/typescript": {
"version": "5.7.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.3.tgz",
"integrity": "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==",
"version": "5.8.2",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.2.tgz",
"integrity": "sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ==",
"dev": true,
"license": "Apache-2.0",
"bin": {
@@ -3,7 +3,7 @@
"version": "0.0.0",
"scripts": {
"ng": "ng",
"start": "NG_BUILD_PARALLEL_TS=0 ng serve",
"start": "ng serve",
"build": "ng build",
"watch": "ng build --watch --configuration development"
},
@@ -23,6 +23,6 @@
"@angular/build": "^19.0.0",
"@angular/cli": "^19.0.0",
"@angular/compiler-cli": "^19.0.0",
"typescript": "~5.7.0"
"typescript": "~5.8.0"
}
}
File diff suppressed because it is too large Load Diff
@@ -3,7 +3,7 @@
"version": "0.0.0",
"scripts": {
"ng": "ng",
"start": "NG_BUILD_PARALLEL_TS=0 ng serve",
"start": "ng serve",
"build": "ng build",
"watch": "ng build --watch --configuration development"
},
@@ -37,6 +37,6 @@
"karma-jasmine-html-reporter": "~2.1.0",
"protractor": "~7.0.0",
"ts-node": "~10.9.0",
"typescript": "~5.7.0"
"typescript": "~5.8.0"
}
}
File diff suppressed because it is too large Load Diff
@@ -3,7 +3,7 @@
"version": "0.0.0",
"scripts": {
"ng": "ng",
"start": "NG_BUILD_PARALLEL_TS=0 ng serve",
"start": "ng serve",
"build": "ng build",
"watch": "ng build --watch --configuration development"
},
@@ -22,6 +22,6 @@
"@angular/build": "^19.0.0",
"@angular/cli": "^19.0.0",
"@angular/compiler-cli": "^19.0.0",
"typescript": "~5.7.0"
"typescript": "~5.8.0"
}
}
File diff suppressed because it is too large Load Diff
@@ -3,7 +3,7 @@
"version": "0.0.0",
"scripts": {
"ng": "ng",
"start": "NG_BUILD_PARALLEL_TS=0 ng serve",
"start": "ng serve",
"build": "ng build",
"watch": "ng build --watch --configuration development"
},
@@ -23,6 +23,6 @@
"@angular/build": "^19.0.0",
"@angular/cli": "^19.0.0",
"@angular/compiler-cli": "^19.0.0",
"typescript": "~5.7.0"
"typescript": "~5.8.0"
}
}
File diff suppressed because it is too large Load Diff
@@ -3,7 +3,7 @@
"version": "0.0.0",
"scripts": {
"ng": "ng",
"start": "NG_BUILD_PARALLEL_TS=0 ng serve",
"start": "ng serve",
"build": "ng build",
"watch": "ng build --watch --configuration development"
},
@@ -25,6 +25,6 @@
"@angular/build": "^19.0.0",
"@angular/cli": "^19.0.0",
"@angular/compiler-cli": "^19.0.0",
"typescript": "~5.7.0"
"typescript": "~5.8.0"
}
}
+4
View File
@@ -74,6 +74,10 @@
background-color: var(--quinary-contrast) !important;
}
.xterm-dim {
color: var(--primary-contrast) !important;
}
.xterm-fg-11 {
color: var(--electric-violet) !important;
}
@@ -0,0 +1,166 @@
# Commit Message Format
We have very precise rules over how our Git commit messages must be formatted.
This format leads to **easier to read commit history** and makes it analyzable for changelog generation.
Each commit message consists of a **header**, a **body**, and a **footer**.
```
<header>
<BLANK LINE>
<body>
<BLANK LINE>
<footer>
```
The `header` is mandatory and must conform to the [Commit Message Header](#commit-header) format.
The `body` is mandatory for all commits except for those of type "docs".
When the body is present it must be at least 20 characters long and must conform to the [Commit Message Body](#commit-body) format.
The `footer` is optional. The [Commit Message Footer](#commit-footer) format describes what the footer is used for and the structure it must have.
## <a name="commit-header"></a>Commit Message Header
```
<type>(<scope>): <short summary>
│ │ │
│ │ └─⫸ Summary in present tense. Not capitalized. No period at the end.
│ │
│ └─⫸ Commit Scope: animations|bazel|benchpress|common|compiler|compiler-cli|core|
│ elements|forms|http|language-service|localize|platform-browser|
│ platform-browser-dynamic|platform-server|router|service-worker|
│ upgrade|zone.js|packaging|changelog|docs-infra|migrations|
│ devtools
│
└─⫸ Commit Type: build|ci|docs|feat|fix|perf|refactor|test
```
The `<type>` and `<summary>` fields are mandatory, the `(<scope>)` field is optional.
### Type
Must be one of the following:
| Type | Description |
|--------------|-----------------------------------------------------------------------------------------------------|
| **build** | Changes that affect the build system or external dependencies (example scopes: gulp, broccoli, npm) |
| **ci** | Changes to our CI configuration files and scripts (examples: Github Actions, SauceLabs) |
| **docs** | Documentation only changes |
| **feat** | A new feature |
| **fix** | A bug fix |
| **perf** | A code change that improves performance |
| **refactor** | A code change that neither fixes a bug nor adds a feature |
| **test** | Adding missing tests or correcting existing tests |
### <a name="scope"></a> Scope
The scope should be the name of the npm package affected (as perceived by the person reading the changelog generated from commit messages).
The following is the list of supported scopes:
* `animations`
* `bazel`
* `benchpress`
* `changelog`
* `common`
* `compiler`
* `compiler-cli`
* `core`
* `dev-infra`
* `devtools`
* `docs-infra`
* `elements`
* `forms`
* `http`
* `language-service`
* `localize`
* `migrations`
* `packaging`
* `platform-browser`
* `platform-browser-dynamic`
* `platform-server`
* `router`
* `service-worker`
* `upgrade`
* `zone.js`
There are currently a few exceptions to the "use package name" rule:
* `packaging`: used for changes that change the npm package layout in all of our packages, e.g. public path changes, package.json changes done to all packages, d.ts file/format changes, changes to bundles, etc.
* `changelog`: used for updating the release notes in CHANGELOG.md
* `dev-infra`: used for dev-infra related changes within the directories /scripts and /tools
* `docs-infra`: used for docs-app (angular.dev) related changes within the /adev directory of the repo
* `migrations`: used for changes to the `ng update` migrations.
* `devtools`: used for changes in the [browser extension](../devtools/README.md).
* none/empty string: useful for `test` and `refactor` changes that are done across all packages (e.g. `test: add missing unit tests`) and for docs changes that are not related to a specific package (e.g. `docs: fix typo in tutorial`).
### Summary
Use the summary field to provide a succinct description of the change:
* use the imperative, present tense: "change" not "changed" nor "changes"
* don't capitalize the first letter
* no dot (.) at the end
## <a name="commit-body"></a>Commit Message Body
Just as in the summary, use the imperative, present tense: "fix" not "fixed" nor "fixes".
Explain the motivation for the change in the commit message body. This commit message should explain _why_ you are making the change.
You can include a comparison of the previous behavior with the new behavior in order to illustrate the impact of the change.
## <a name="commit-footer"></a>Commit Message Footer
The footer can contain information about breaking changes and deprecations and is also the place to reference GitHub issues and other PRs that this commit closes or is related to.
For example:
```
BREAKING CHANGE: <breaking change summary>
<BLANK LINE>
<breaking change description + migration instructions>
<BLANK LINE>
<BLANK LINE>
Fixes #<issue number>
```
or
```
DEPRECATED: <what is deprecated>
<BLANK LINE>
<deprecation description + recommended update path>
<BLANK LINE>
<BLANK LINE>
Closes #<pr number>
```
Breaking Change section should start with the phrase `BREAKING CHANGE: ` followed by a *brief* summary of the breaking change, a blank line, and a detailed description of the breaking change that also includes migration instructions.
Similarly, a Deprecation section should start with `DEPRECATED: ` followed by a short description of what is deprecated, a blank line, and a detailed description of the deprecation that also mentions the recommended update path.
## Revert commits
If the commit reverts a previous commit, it should begin with `revert: `, followed by the header of the reverted commit.
The content of the commit message body should contain:
- information about the SHA of the commit being reverted in the following format: `This reverts commit <SHA>`,
- a clear description of the reason for reverting the commit message.
[angularjs-commit-message-format]: https://docs.google.com/document/d/1QrDFcIiPjSLDn3EL15IJygNPiHORgU1_OOAqWjiDU5Y/edit#
@@ -44,13 +44,13 @@ describe('Viewing component metadata', () => {
});
it('should display correct set of inputs', () => {
cy.contains('.cy-inputs', '@Inputs');
cy.contains('.cy-inputs', 'Inputs');
cy.contains('.cy-inputs mat-tree-node:first span:first', 'inputOne');
cy.contains('.cy-inputs mat-tree-node:last span:first', 'inputTwo');
});
it('should display correct set of outputs', () => {
cy.contains('.cy-outputs', '@Outputs');
cy.contains('.cy-outputs', 'Outputs');
cy.contains('.cy-outputs mat-tree-node:first span:first', 'outputOne');
cy.contains('.cy-outputs mat-tree-node:last span:first', 'outputTwo');
});
@@ -159,3 +159,18 @@ ts_library(
"//packages/core",
],
)
ts_test_library(
name = "component_tree_test_lib",
srcs = ["component-tree.spec.ts"],
deps = [
":component_tree",
"//packages/core",
"@npm//jasmine",
],
)
karma_web_test_suite(
name = "component_tree_test",
deps = [":component_tree_test_lib"],
)
@@ -225,7 +225,9 @@ const getRoutes = (messageBus: MessageBus<Events>) => {
const rootInjector = (forest[0].resolutionPath ?? []).find((i) => i.name === 'Root');
if (rootInjector) {
const route = getRouterConfigFromRoot(rootInjector);
messageBus.emit('updateRouterTree', [[route]]);
if (route) {
messageBus.emit('updateRouterTree', [[route]]);
}
}
};
@@ -293,12 +295,17 @@ const getProviderValue = (
}
};
const getRouterConfigFromRoot = (injector: SerializedInjector): Route => {
const getRouterConfigFromRoot = (injector: SerializedInjector): Route | void => {
const serializedProviderRecords = getSerializedProviderRecords(injector) ?? [];
const routerInstance = serializedProviderRecords.filter(
const routerInstance = serializedProviderRecords.find(
(provider) => provider.token === 'Router', // get the instance of router using token
);
const routerProvider = getProviderValue(injector, routerInstance[0]);
if (!routerInstance) {
return;
}
const routerProvider = getProviderValue(injector, routerInstance);
return parseRoutes(routerProvider);
};
@@ -0,0 +1,42 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.dev/license
*/
import {Injector, ɵGlobalDevModeUtils} from '@angular/core';
import {getInjectorFromElementNode} from './component-tree';
type Ng = ɵGlobalDevModeUtils['ng'];
describe('component-tree', () => {
afterEach(() => {
delete (globalThis as any).ng;
});
describe('getInjectorFromElementNode', () => {
it('returns injector', () => {
const injector = Injector.create({
providers: [],
});
const ng: Partial<Ng> = {
getInjector: jasmine.createSpy('getInjector').and.returnValue(injector),
};
(globalThis as any).ng = ng;
const el = document.createElement('div');
expect(getInjectorFromElementNode(el)).toBe(injector);
expect(ng.getInjector).toHaveBeenCalledOnceWith(el);
});
it('returns `null` when `getInjector` is not supported', () => {
(globalThis as any).ng = {};
const el = document.createElement('div');
expect(getInjectorFromElementNode(el)).toBeNull();
});
});
});
@@ -76,7 +76,7 @@ export function getInjectorResolutionPath(injector: Injector): Injector[] {
}
export function getInjectorFromElementNode(element: Node): Injector | null {
return ngDebugClient().getInjector(element);
return ngDebugClient().getInjector?.(element) ?? null;
}
function getDirectivesFromElement(element: HTMLElement): {
@@ -108,9 +108,9 @@ export const getLatestComponentState = (
const directiveProperties: DirectivesProperties = {};
const injector = ngDebugClient().getInjector(node.nativeElement!);
const injector = getInjectorFromElementNode(node.nativeElement!);
const injectors = getInjectorResolutionPath(injector);
const injectors = injector ? getInjectorResolutionPath(injector) : [];
const resolutionPathWithProviders = !ngDebugDependencyInjectionApiIsSupported()
? []
: injectors.map((injector) => ({
@@ -120,11 +120,13 @@ export const getLatestComponentState = (
const populateResultSet = (dir: DirectiveInstanceType | ComponentInstanceType) => {
const {instance, name} = dir;
const metadata = getDirectiveMetadata(instance);
metadata.dependencies = getDependenciesForDirective(
injector,
resolutionPathWithProviders,
instance.constructor,
);
if (injector) {
metadata.dependencies = getDependenciesForDirective(
injector,
resolutionPathWithProviders,
instance.constructor,
);
}
if (query.propertyQuery.type === PropertyQueryTypes.All) {
directiveProperties[dir.name] = {
@@ -569,13 +571,13 @@ export const updateState = (updatedStateData: UpdatedStateData): void => {
if (updatedStateData.directiveId.directive !== undefined) {
const directive = node.directives[updatedStateData.directiveId.directive].instance;
mutateComponentOrDirective(updatedStateData, directive);
ng.applyChanges(ng.getOwningComponent(directive)!);
ng.applyChanges?.(ng.getOwningComponent(directive)!);
return;
}
if (node.component) {
const comp = node.component.instance;
mutateComponentOrDirective(updatedStateData, comp);
ng.applyChanges(comp);
ng.applyChanges?.(comp);
return;
}
};
@@ -39,7 +39,7 @@ function createOverlay(color: RgbColor): {overlay: HTMLElement; overlayContent:
const overlay = document.createElement('div');
overlay.className = 'ng-devtools-overlay';
overlay.style.backgroundColor = toCSSColor(...color, 0.35);
overlay.style.position = 'fixed';
overlay.style.position = 'absolute';
overlay.style.zIndex = '2147483647';
overlay.style.pointerEvents = 'none';
overlay.style.display = 'flex';
@@ -196,8 +196,8 @@ function showOverlay(
const {width, height, top, left} = dimensions;
overlay.style.width = ~~width + 'px';
overlay.style.height = ~~height + 'px';
overlay.style.top = ~~top + 'px';
overlay.style.left = ~~left + 'px';
overlay.style.top = ~~top + window.scrollY + 'px';
overlay.style.left = ~~left + window.scrollX + 'px';
positionOverlayContent(overlayContent, dimensions, labelPosition);
overlayContent.replaceChildren();
@@ -1,5 +1,6 @@
# load("//devtools/tools:typescript.bzl", "ts_library")
load("//devtools/tools:defaults.bzl", "karma_web_test_suite")
load("//devtools/tools:ng_module.bzl", "ng_module")
load("//devtools/tools:typescript.bzl", "ts_test_library")
package(default_visibility = ["//visibility:public"])
@@ -13,3 +14,18 @@ ng_module(
"//packages/core",
],
)
ts_test_library(
name = "ng-debug-api_test_lib",
srcs = ["ng-debug-api.spec.ts"],
deps = [
":ng-debug-api",
"//packages/core",
"@npm//jasmine",
],
)
karma_web_test_suite(
name = "ng-debug-api_test",
deps = [":ng-debug-api_test_lib"],
)
@@ -0,0 +1,51 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.dev/license
*/
import {ɵGlobalDevModeUtils} from '@angular/core';
import {ngDebugDependencyInjectionApiIsSupported} from './ng-debug-api';
type Ng = ɵGlobalDevModeUtils['ng'];
describe('ng-debug-api', () => {
afterEach(() => {
delete (globalThis as any).ng;
});
describe('ngDebugDependencyInjectionApiIsSupported', () => {
const goldenNg: Partial<Record<keyof Ng, () => void>> = {
getInjector() {},
ɵgetInjectorResolutionPath() {},
ɵgetDependenciesFromInjectable() {},
ɵgetInjectorProviders() {},
ɵgetInjectorMetadata() {},
};
it('returns true when required APIs are supported', () => {
(globalThis as any).ng = goldenNg;
expect(ngDebugDependencyInjectionApiIsSupported()).toBeTrue();
});
it('returns false when any required API is missing', () => {
(globalThis as any).ng = {...goldenNg, getInjector: undefined};
expect(ngDebugDependencyInjectionApiIsSupported()).toBeFalse();
(globalThis as any).ng = {...goldenNg, ɵgetInjectorResolutionPath: undefined};
expect(ngDebugDependencyInjectionApiIsSupported()).toBeFalse();
(globalThis as any).ng = {...goldenNg, ɵgetDependenciesFromInjectable: undefined};
expect(ngDebugDependencyInjectionApiIsSupported()).toBeFalse();
(globalThis as any).ng = {...goldenNg, ɵgetInjectorProviders: undefined};
expect(ngDebugDependencyInjectionApiIsSupported()).toBeFalse();
(globalThis as any).ng = {...goldenNg, ɵgetInjectorMetadata: undefined};
expect(ngDebugDependencyInjectionApiIsSupported()).toBeFalse();
});
});
});

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