Compare commits

...

449 Commits

Author SHA1 Message Date
SkyZeroZx c60c41e29a test(core): reduce fakeAsync usage in animation tests
Use async/await in animation acceptance tests and share the animation frame helper. Remove the obsolete fakeAsync frame utility and its Bazel dependency.
2026-09-13 14:20:24 -07:00
Kam fc2f6fd67d docs: stop the disabled aria toolbar radio group from changing selection
The disabled toolbar example marks its alignment radio group `disabled`.
Toolbars are soft-disabled by default, so the widgets only receive
`aria-disabled` and stay clickable, which the guide describes as focusable
but unavailable.

Since #70516 moved selection into the app, each radio also carries a
`(click)="alignment.set(...)"` handler that never checks the disabled state.
Clicking a disabled radio therefore changes the selection. Before that change
the radios had no app listener and the library ignored clicks on disabled
items.

Drop the three click handlers from the permanently disabled group in the
basic, material and retro variants, the same way the disabled redo button in
the same template already has none.
2026-09-11 16:00:02 -07:00
aparziale f2cf65a898 fix(vscode-extension): handle template literals when detecting supported decorator fields
The client decides whether a position in a TypeScript file belongs to a supported `@Component` field (`template`, `styles`, ...) with a lightweight `ts.Scanner` loop before forwarding requests to the language server and the HTML/CSS providers. `ts.Scanner#scan` doesn't handle template substitutions on its own: the `}` closing a `${...}` substitution is reported as a plain `CloseBraceToken` and the rest of the template literal is scanned as the start of a new one, swallowing everything up to the next backtick. As a result, once a template literal with substitutions appears in a file, every position after it is considered to be inside a template string, and TypeScript completions get polluted with HTML completions.

The scanner is now driven the same way the TypeScript parser does it, re-scanning the brace that closes a substitution as a `TemplateMiddle`/`TemplateTail` token.

In addition, the property assignment context previously only ended at a terminator found at the top level of the file, so it leaked from `template:` to every string literal that followed it inside the same decorator and class body. The context now ends at the first terminator found at the nesting depth where it was entered. Two kinds of strings were only recognized thanks to that leak, and only when declared after `template`: inline `styles` and the arguments of `@HostBinding`/`@HostListener` decorators, both of which the language service supports. They are now recognized explicitly, regardless of where they appear.

Fixes #65494
2026-09-11 15:19:01 -07:00
Shuaib Hasan Akib 467b37b4b4 docs(forms): document what required() considers empty
The `required()` validator treats `null`, `undefined`, `''`, `false` and `NaN` as empty, but the API
docs never defined "empty" at all and the validation guide listed only `null` and `''`.
2026-09-11 15:16:17 -07:00
Andrew Scott 2dcdf9aae6 fix(router): mark router_resource module-level symbols as side-effect free
Top-level Symbol() calls in router_resource.ts lacked /* @__PURE__ */
annotations, preventing bundlers like esbuild and Rollup from tree-shaking
the module and its dependencies when provideRouter() is used without
withRouterResources().

Fixes #70696
2026-09-11 14:40:05 -07:00
Kam c097c0144c docs: import bootstrapApplication from platform-browser in the CSP example
The `CSP_NONCE` example in the security guide imports `bootstrapApplication`
from `@angular/core`, which does not export it, so copying the snippet fails
with "has no exported member named 'bootstrapApplication'". It is exported from
`@angular/platform-browser`, which is where every other example in the docs
imports it from.

Keep `CSP_NONCE` on `@angular/core` and import `bootstrapApplication` from
`@angular/platform-browser`.
2026-09-11 13:12:29 -07:00
Joey Perrott 1b7dcc854a ci: stop passing preview gating inputs to adev preview build
The Googler check moved out of `pack-and-upload-artifact` and into the
pull request labeling action, which runs in a privileged
`pull_request_target` context where secrets are available. The build job
runs on `pull_request` and never receives secrets, so it no longer needs
`angular-robot-key` or `triggering-label`, and the `bypassed_for_forks`
placeholder can go away. Bumps the dev-infra pins to pick up that change.
2026-09-11 13:11:59 -07:00
Andrew Scott 2c6c67bee6 fix(router): maintain frozen state on rollback until resource loading completes
Previously, rollback recovery waited on hasValueOrResolved(), which checked whether the resource already had a value. This caused resources with values (e.g. defaultValue, existing values from prior navigations, or streamed emissions) to unfreeze prematurely while still in a loading state during rollback.

This change updates the rollback recovery check in the transactional snapshot effect to be solely determined by !source.isLoading().
2026-09-11 13:06:43 -07:00
Andrew Scott a8233232f5 fix(core): update FakeNavigation to match WHATWG HTML spec
Update FakeNavigation and Navigation API type definitions to match the
latest WHATWG HTML specification
(https://html.spec.whatwg.org/multipage/nav-history-apis.html):
- Add NavigationPrecommitController.addHandler() and support dynamic
  post-commit handler registration
  (https://html.spec.whatwg.org/multipage/nav-history-apis.html#dom-navigationprecommitcontroller-addhandler).
- Include precommitHandler directly in NavigationInterceptOptions
  (https://html.spec.whatwg.org/multipage/nav-history-apis.html#dom-navigateevent-intercept).
- Add hasUAVisualTransition and sourceElement properties to NavigateEvent
  (https://html.spec.whatwg.org/multipage/nav-history-apis.html#the-navigateevent-interface).
- Fix FakeNavigationHistoryEntry dispose event name from 'disposed' to 'dispose'
  and implement ondispose
  (https://html.spec.whatwg.org/multipage/nav-history-apis.html#dom-navigationhistoryentry-ondispose).
- Implement Navigation.prototype.updateCurrentEntry
  (https://html.spec.whatwg.org/multipage/nav-history-apis.html#dom-navigation-updatecurrententry).
- Implement Navigation.prototype.reload
  (https://html.spec.whatwg.org/multipage/nav-history-apis.html#dom-navigation-reload)
  and share "performing a non-traverse navigation"
  (https://html.spec.whatwg.org/multipage/nav-history-apis.html#performing-a-non-traverse-navigation)
  logic with navigate().
- Implement standard event handler IDL properties (onnavigate,
  onnavigatesuccess, onnavigateerror, oncurrententrychange).
- Update internal specification comments and links from legacy PR draft URLs
  to official WHATWG multipage URLs.
2026-09-11 12:41:15 -07:00
Kam d0ed76c1d6 docs: fix the broken housing location template in the first-app tutorial
Step 11 of the first-app tutorial ships a `housing-location.ts` whose heading
reads `{{ housingLocatio()).name }}`, a misspelled property followed by an
extra parenthesis, so the step's source fails to compile with a template
parser error. Step 10 reuses this directory as its answer.

It came in with #61686, which migrated the tutorial to signal inputs. Use
`housingLocation().name`, matching the rest of the template and every other
step.
2026-09-11 10:59:36 -07:00
Kam 01d091bf9c fix(docs-infra): raise SSR fetch limit so the menubar guide prerenders
The aria menubar guide content is 2.9 MB, over the 2 MiB
maxResponseBodySize set in #69379. The fetch fails during prerender,
the navigation error handler redirects to /404, and the prerendered
page is saved as that redirect. Opening /guide/aria/menubar directly
or refreshing it lands on the 404 page.

Raise the limit to 4 MiB. The combobox guide was also within 27 KB of
the old limit.
2026-09-11 10:42:35 -07:00
Kam b9cbc5fbbe fix(docs-infra): match card grid row gap to column gap
The `:not(.docs-card-container) .docs-card` selector was meant to give
standalone cards a vertical margin, but no element has the
docs-card-container class, so it matched every card. Cards in a grid
without a header picked up the 1rem margin on top of the 1.25rem grid
gap, leaving rows 52px apart while columns are 20px apart (for example on
/reference/migrations, /guide/i18n and /roadmap).

Apply the margin only to cards that are not direct children of a card
grid.
2026-09-11 10:41:51 -07:00
Alan Agius 9918c81ca9 build: update @angular/ssr and add beasties dependency for adev and dev-app
In @angular/ssr 22.2.0-next.7, beasties was made an external dependency rather
than vendored internally. Because hoist: false is configured and preserveSymlinks
is enabled, rules_js does not hoist beasties into adev/node_modules and
dev-app/node_modules.

This commit adds beasties as an explicit dependency for both adev and dev-app
so that it can be resolved during bundling.
2026-09-11 10:40:29 -07:00
Angular Robot 11a66f254b docs: update cross-repo adev docs
Updated Angular adev cross repo docs files.
2026-09-11 10:39:02 -07:00
Andrew Scott 37ca679192 Revert "fix(compiler): wrap @for collection expression before appending non-null assertion"
This reverts commit ed8f16c078.

This caused a breakage in TGP because the changes (correctly) produced new
diagnostics on ` @for (a of (x | async) || []; track a) {` x is `any`. This
results in `<any> | async` which produces `unknown | null` and then the `@if`
finally narrows this down to `{}` which isn't iteratable.
2026-09-11 10:05:15 -07:00
Matthieu Riegler ae33a5f55e test(core): fix view injector integration spec
Restore ChangeDetectionStrategy.OnPush that was removed in a previous commit, causing tests to fail due to ExpressionChangedAfterItHasBeenCheckedError.
2026-09-10 16:17:53 -07:00
Matthieu Riegler e2050759a4 docs: add error boundaries entries 2026-09-10 15:36:21 -07:00
Alan Agius 29f9dd5e77 refactor(devtools): update angular optimization plugin for JavaScriptTransformer API
Update createEsbuildAngularOptimizePlugin to use the new options object signature for JavaScriptTransformer.transformFile, and update the @angular/build dependency to 22.2.0-next.7.
2026-09-10 15:06:43 -07:00
Kam b9fb456971 docs(forms): close the unterminated code fences in the compatForm docs
Two of the three `compatForm` overloads open a fenced code block in their
`@example` and never close it. The fence then runs to the end of the tag, so the
API renderer emits a plain `<pre><code>` block for those two instead of a
highlighted one.

The result is that on the compatForm API page the first overload's example is
syntax highlighted while the other two, which contain identical code, render as
flat unstyled text.

Rebuilding //adev/src/assets:content with and without this change alters 1 of
1565 pages. That page goes from 6 highlighted and 2 plain code blocks to 8
highlighted and none plain.
2026-09-10 14:53:41 -07:00
Matthieu Riegler 9090a758be refactor(compiler-cli): relax nullish coalescing non nullable diagnostics on indexed access
When noUncheckedIndexedAccess is not enabled, indexed accesses do not include undefined in the type. This relaxes the check for nullish coalescing similarly to optional chaining. Fixes #70655

fixes #70655
2026-09-10 14:31:22 -07:00
SkyZeroZx 9e5ae4b710 test(core): remove redundant change detection configuration
OnPush is now the default change detection strategy, so the explicit test and example configuration is no longer needed.
2026-09-10 14:30:31 -07:00
SkyZeroZx 4380e683d8 docs(router): update scroll restoration example
Uses modern dependency injection and signal-based change detection in the custom scroll restoration example.
2026-09-10 14:30:31 -07:00
SkyZeroZx a958d7fe30 test(router): remove redundant change detection configuration
OnPush is now the default change detection strategy, so the explicit test configuration is no longer needed.
2026-09-10 14:30:31 -07:00
Angular Robot ceba448475 build: update pnpm/action-setup action to v6.1.0
See associated pull request for more information.
2026-09-10 14:29:47 -07:00
Kam 9073469416 fix(docs-infra): escape the code example header
`buildHeaderElement` interpolated the header into a string that is then parsed
as HTML, so markup in a header became an element instead of text. The ten
captions on https://angular.dev/guide/i18n/translation-files read
`messages.fr.xlf ()`, having turned `(<trans-unit>)` into an empty element.

These are the only two headers in the guides containing markup.
2026-09-10 14:29:10 -07:00
Kam 61b353ebee fix(docs-infra): restore the xlf regions on the i18n guides
#65848 renamed `messages.fr.xlf.html` to `messages.fr.xlf` and, in the same
lines, reverted `region=` back to `visibleRegion=`, undoing #65530 from two
weeks earlier. `visibleRegion` is not read by the tokenizer, and `xlf` is not
in `REGION_MATCHERS`, so both halves of the snippet handling broke at once.

Since then https://angular.dev/guide/i18n/translation-files has shown the same
78 line file ten times where the prose describes single `<trans-unit>`
elements, and https://angular.dev/guide/i18n/manage-marked-text three times,
each carrying the `#docregion` scaffolding into the rendered code.

Mapping `xlf` to the html matcher also cleans up
https://angular.dev/guide/i18n/example, which renders the whole file on purpose
but leaked 27 marker lines into it.

`docs-code.spec.mts` already loads this fixture but only asserted the block
existed, so it stayed green throughout. It now also asserts no marker survives.
2026-09-10 14:29:10 -07:00
Andrew Scott 14dbbf9b68 fix(compiler): wrap @for collection expression before appending non-null assertion
When generating type check blocks for `@for` loops, a non-null assertion is appended to the collection expression. If the collection expression is a compound expression (e.g. binary or logical operations like a && b), the lack of outer parentheses caused the ! to bind only to the rightmost operand (a && b!), leading to typecheck errors such as TS2532.

This wraps the expression via .wrapForTypeChecker() before appending !.
2026-09-10 13:22:52 -07:00
Angular Robot 4b3f8c224b docs: update cross-repo adev docs
Updated Angular adev cross repo docs files.
2026-09-10 11:15:04 -07:00
Doug Parker 8ca879e2d8 refactor(forms): expose consequentialHint annotation
Unlike `readOnlyHint` and `untrustedContentHint`, Angular cannot infer any reasonable default value for `consequentialHint` and simply exposes it to users to specify as appropriate.

See: https://groups.google.com/a/chromium.org/g/chrome-ai-dev-preview/c/uHu5kfclbKw/m/dgorjfHPDgAJ
2026-09-10 09:50:14 -07:00
Doug Parker 96dbae53bb refactor(core): expose consequentialHint annotation
This allows `consequentialHint` to be passed to `ModelContext.registerTool`. From Angular's perspective, this is simply a pass-through option.

See: https://groups.google.com/a/chromium.org/g/chrome-ai-dev-preview/c/uHu5kfclbKw/m/dgorjfHPDgAJ
2026-09-10 09:50:14 -07:00
Alan Agius f557ead292 build: add @modelcontextprotocol/server devDependency and vendor license
Add @modelcontextprotocol/server to devDependencies in @angular/core to extract its LICENSE file via a genrule in the third_party directory, and remove the previously checked-in package.json.
2026-09-10 09:49:08 -07:00
Alan Agius 593a5df61c build: make webmcp-types sources genrule cross-platform
Replace `sed -i` in-place substitution with direct stdout redirection from the source file to destination outputs. `sed -i` behaves differently between GNU and BSD sed, which caused build failures on macOS.
2026-09-10 09:49:08 -07:00
Kam 8576f161ae fix(docs-infra): highlight home page code for the resolved theme
`CodeBlock` picked `github-light` only when the theme was exactly `light`, but
`Theme` also has `auto`, the value for anyone who has not opened the theme
menu, and `theme()` is `null` during prerendering. Both fell to `github-dark`,
so the samples in the Signals, Control Flow and Deferrable Views tabs on
https://angular.dev rendered on a dark slab inside a light page.

Resolve the theme in `ThemeManager`, which already owns the `auto` translation.
`resolvedTheme` returns `light` when `theme()` is `null`, which happens only
during prerendering, so `window` is never read. The device scheme moves into a
signal so `auto` also reacts to OS scheme changes.
2026-09-09 22:27:24 +02:00
SkyZeroZx 947f3ffaef test(core): reduce fakeAsync usage in tests
Use async/await where needed. Remove unused dependencies.
2026-09-09 22:26:29 +02:00
Edu cea6896a0f fix(core): return null when getDirectiveMetadata is called with null or undefined
Safely return null instead of throwing an unhandled TypeError when getDirectiveMetadata is called with a falsy directive or component instance.
2026-09-09 22:26:00 +02:00
Edu ef09d108f2 fix(devtools): safely handle foreign root elements in feature detection
Prevent Angular DevTools from crashing when elements matching [ng-version] in the DOM do not belong to the host application (such as third-party custom elements or browser extensions). ng.getComponent returns null for these elements, which previously caused ng.getDirectiveMetadata to throw.
2026-09-09 22:26:00 +02:00
Kristiyan Kostadinov a410e1370c release: cut the v22.2.0-next.7 release 2026-09-09 22:22:24 +02:00
Kristiyan Kostadinov 0625c1d81d release: cut the v22.2.0-next.6 release 2026-09-09 22:16:04 +02:00
Kristiyan Kostadinov 2f3afc9909 docs: release notes for the v22.1.6 release 2026-09-09 22:12:02 +02:00
Kristiyan Kostadinov 018bec1ecc docs: release notes for the v20.3.31 release 2026-09-09 22:02:37 +02:00
Kristiyan Kostadinov c62641fdb0 docs: release notes for the v21.2.23 release 2026-09-09 21:55:18 +02:00
Kristiyan Kostadinov b6517a6672 build: resolve pnpm 12 breakages
Accounts for breaking changes in pnpm 12.
2026-09-09 21:48:04 +02:00
Andrew Scott 679c50dcbe refactor(router): Remove ActivatedRouteSnapshot from resourceContext
providing the snapshot is a bit incompatible with how things are meant to work here.
The snapshot is only accurate during the setup and would be out of date
on followup navigations
2026-09-09 18:09:11 +02:00
Kristiyan Kostadinov 25228d0cc3 test: fix failing boundary test
Fixes a test that's breaking CI.
2026-09-09 17:23:07 +02:00
Angular Robot 2c71e8823a docs: update cross-repo adev docs
Updated Angular adev cross repo docs files.
2026-09-09 16:35:02 +02:00
Angular Robot 25317c3837 build: update dependency cypress to v16
See associated pull request for more information.
2026-09-09 16:33:56 +02:00
Angular Robot bc0fd20d55 build: update dependency vitest to v5
See associated pull request for more information.
2026-09-09 16:31:33 +02:00
Angular Robot 8414f90a6f build: update cross-repo angular dependencies
See associated pull request for more information.
2026-09-09 16:30:15 +02:00
Alan Agius bc3a6cda5d fix(platform-server): avoid sourcemap corruption during domino path substitution
The substitution regex `\./(.+)/third_party/domino/bundled-domino` was used to rewrite the relative execroot path emitted by Rollup for the domino external import into `../third_party/domino/bundled-domino.mjs`.

However, `ng_package` runs `text_replace` across all generated package files, including `.map` files which are serialized on a single line. The `\./` pattern unintentionally matched the `./` inside `"../../"` in the `sources` array, and the greedy `.+` wildcard matched across the rest of `sources` and the `"sourcesContent": [` declaration up to the domino import within the first source file's content. This corrupted `init.mjs.map` and `_server-chunk.mjs.map` by destroying `sourcesContent` and populating `sources` with raw file contents.

This commit updates the substitution regex to use a negative lookbehind `(?<!\.)` to prevent matching `../` sequences, and restricts the path characters to valid filesystem path characters `[a-zA-Z0-9_./-]+` rather than `.+`.

Fixes #70625
2026-09-09 16:27:18 +02:00
Angular Robot 199da47d58 build: update dependency node to v24.21.0
See associated pull request for more information.
2026-09-09 16:26:45 +02:00
Kam d0a748cc1b docs(animations): stop the query() usage notes truncating at a code sample
The `query()` usage notes contain this code sample:

    query(':self, .record:enter, .record:leave, @subTrigger', [...])

TypeScript's JSDoc parser treats whitespace followed by `@subTrigger` as the
start of a new tag, even inside a fenced code block. The extraction ends up with
a 145 character `@usageNotes` cut off mid-sample and a phantom `@subTrigger` tag
holding the remaining 2453 characters, which the renderer discards.

The result is that https://angular.dev/api/animations/query ends mid-line inside
that code block. The "Entering and Leaving Elements" and "Usage Example"
sections never render, and the two links pointing at the first of them, one in
the same JSDoc and one in guide/animations/complex-sequences, both dead-end.

Move the `@` token to the front of the selector so it follows a quote rather
than a space. Order within a comma separated selector list is irrelevant, and
the sample's own prose describes it as a set of tokens merged into one string.

The rendered page grows from 7109 to 21269 bytes and gains the
`entering-and-leaving-elements` and `usage-example` anchors. Rebuilding
//adev/src/assets:content with and without the change alters 1 of 1565 pages.
2026-09-09 16:26:08 +02:00
Andrew Scott 094bce9e3d fix(router): determine blocking state solely by resource loading status
Previously, blocking router resources waited on hasValueOrResolved(), which checked whether the resource already had a value. This caused blocking resources with initial values (e.g. defaultValue), streamed early emissions, or existing values during route reloads to resolve prematurely while still in a loading state.

This change updates the blocking resolution check to be solely determined by !underlyingRes.isLoading().
2026-09-09 16:25:24 +02:00
Jaime Burgos 8afd85c6d7 test(forms): migrate integration tests to zoneless scheduling
Use whenStable instead of manual change detection so integration tests exercise scheduled rendering. Await asynchronous callbacks and remove redundant timer waits to keep assertions within the test lifecycle.
2026-09-09 16:24:37 +02:00
Kristiyan Kostadinov 9b80d4ce9e fix(compiler): namespace @property declarations
The `@property` atrule allows users to define custom CSS variables. These changes update the compiler to account for when namespacing variables.
2026-09-09 16:23:57 +02:00
SkyZeroZx 9021e275d1 docs: use code block headers for filenames
Move filename comments into headers to keep examples focused on code.
2026-09-09 16:23:09 +02:00
Kam 8037e4ac08 docs(docs-infra): correct stale paths and routes in the tutorials README
The tutorials README documents how tutorial content, routes and the shared
common project are laid out, and several of those statements no longer match
the pipeline.

Links: eight links were written as `/src/content/...`. GitHub rewrites a
root-relative markdown link to `/<owner>/<repo>/blob/<branch>/<path>`, so they
resolved to `angular/angular/blob/main/src/content/...` and returned 404. They
now use the repo-root path, matching the convention already used in
`adev/src/app/editor/README.md`.

Routes: the step examples claimed the number prefix is dropped, giving
`/tutorials/learn-angular/components-in-angular`. `routes.mts` emits the step
directory name verbatim and uses the number only for ordering, and the
production sitemap lists only the numbered form, so the two examples and the
bullet describing the step URL are corrected.

Common project: `adev/src/content/tutorials/common` was removed in #53511 and
the shared project now lives in `adev/shared-docs/pipeline/tutorials/common`.
The section also documents the per-tutorial `common` directory that
`tutorial_index.mts` applies on top of the shared one, and drops the reference
to `app.module.ts`, which the standalone common project does not have.

Update dependencies: the script covered five of the eight projects that carry
a package-lock.json. `signals/common`, `signal-forms/common` and the shared
common are added; all eight are already updated together by lock file
maintenance.

The README is excluded from `generate_guides`, so no rendered page changes.
Building `//adev/src/content/tutorials/...` with and without this change
produces byte-identical output across all 213 generated files.
2026-09-09 16:22:32 +02:00
Shuaib Hasan Akib f53b3b6e95 docs: add CommonModule guidance to AI best practices
Clarifies that CommonModule should not be imported wholesale when narrower imports can be used instead.
2026-09-09 16:21:53 +02:00
Kam 4043800ef2 fix(docs-infra): recognise an alert that follows prose in the same paragraph
`docs-alert` was the only marked extension in the pipeline without a `start`
hook, so marked never cut `inlineText` short at an alert and swallowed any
directive that was not at the start of the inline source. Writing the alert on
its own line without a blank line before it left the literal text in the body.

On https://angular.dev/guide/http/testing two alerts render as boxes and a
third shows as `IMPORTANT:` in the paragraph text. Also affects
https://angular.dev/errors/NG3003 and the first step of the first app tutorial.

The same renderer handles JSDoc, so one API page changes too:
https://angular.dev/api/upgrade/static/downgradeModule has three `NOTE:`
continuation lines inside bullets that now render as alerts.

`docs-video` and `docs-pill` already declare `start` the same way.
2026-09-09 16:21:14 +02:00
Kam d3ccf5ca82 docs: fix the visibleLines range on the ngFor tutorial step
`visibleLines="26-131"` is not valid JSON, so `expandRangeStringValues` threw
and returned an empty list. The rendered attribute was empty, and the viewer
treats that as no range at all, so no ExampleViewer was created:
https://angular.dev/tutorials/first-app/08-ngFor showed all 128 lines of
`home.ts` with no collapse and no expand control, including the `@for` block
the reader has not written yet.

The file is 128 lines and the property this step adds ends at 127. The sibling
snippet on the same page already uses the `[start,end]` form.
2026-09-09 16:20:10 +02:00
Angular Robot a959ceda76 build: update pnpm to v12
See associated pull request for more information.
2026-09-09 16:19:17 +02:00
Kam 93f17e0bdf docs: fix the docregion marker in the apache config example
The marker was written `# docregion`, but the hash matcher expects
`# #docregion`, one hash for the comment and one for the marker. It was not
recognised, so it was never stripped and rendered as the first line of the
Apache config on https://angular.dev/guide/i18n/deploy.

The sibling `nginx.conf`, shown on the same page, already has the correct form.
2026-09-09 16:18:39 +02:00
Shuaib Hasan Akib e80c91b262 docs: render the unsupported version range as an alert 2026-09-09 16:17:46 +02:00
Shuaib Hasan Akib 1bc7e3c2c3 refactor(core): use native Promise.withResolvers() in remaining tests
Replaces the remaining hand-rolled deferred promise implementations
with the native `Promise.withResolvers()` API and removes the now
unused helper and import.

Follow-up to #69739.
2026-09-09 16:17:04 +02:00
SkyZeroZx 49a797f510 test(core): remove redundant change detection configuration
OnPush is now the default change detection strategy, so the explicit test configuration is no longer needed.

Updates outdated Bazel test targets to use the zoneless configuration.
2026-09-09 16:16:28 +02:00
Kam b7d432794e fix(docs-infra): keep an explicit theme choice when the OS scheme changes
`watchPreferredColorScheme` applied the OS scheme unconditionally, while
`setTheme`, `loadThemePreference` and the bootstrap script in index.html all
only follow it for `auto`. Choosing Light and then letting the OS switch to
dark repainted the site dark while the theme menu still reported Light, until
a reload restored it.
2026-09-09 16:15:52 +02:00
Kam b0cda277da refactor(docs-infra): validate api/cdk and api/aria links
`isKnownRoute` exempted both families behind TODOs waiting on route extraction
for those packages. That extraction had already landed when the TODOs were
written in #66254: cdk pages since #60853 and aria pages since the cross-repo
workflow. `defined-routes.json` carries 70 `api/cdk` and 39 `api/aria` routes
today, and all 45 such link targets in the guides resolve, so the build stays
green without the exemptions.

The gap was not theoretical. `guide/aria/select.md` and
`guide/aria/multiselect.md` linked `api/cdk/overlay/CdkConnectedOverlay`, which
has never been a route, and it shipped as a 404 for six months. Link validation
landed four months into that and said nothing, because of this exemption. It
took a user filing #68914 and an outside contributor fixing it in #68915.

Pointing an existing `api/cdk` link at a symbol that does not exist passes the
build today and fails it with this change.
2026-09-09 16:15:14 +02:00
Kam d3b0bb1d8c test(docs-infra): run the orphaned shared-docs pipes specs
`relative-link.pipe.spec.ts` and `is-active-navigation-item.pipe.spec.ts`
arrived with this package's BUILD file in #57132, but no test target came with
them and `lib` excludes `**/*.spec.ts`, so nothing has ever compiled them.
`bazel query` reports both as not declared in the package.

They pass unmodified. `getRelativeUrl` has no other coverage in the repo, and
both pipes are used by the search dialog, the navigation list and the not
found page.
2026-09-09 16:14:23 +02:00
SkyZeroZx c235ecef8a test(forms): remove redundant change detection configuration
OnPush is now the default change detection strategy, and tests run zoneless by default, so the explicit configuration is no longer needed.
2026-09-09 16:13:08 +02:00
Kam 0bdbbcf4a8 fix(docs-infra): only read a deprecation version from the start of the tag
`getTagSinceVersion` matched `\d+(\.\d+)?` anywhere in the tag comment. That
works for `@developerPreview`, `@experimental` and `@stable`, whose comment is
only a version, but `@deprecated` also carries a message, so any number in the
prose won. https://angular.dev/api/common/getLocaleCurrencyCode reads
"deprecated since v4217", taken from "a map of locale to ISO 4217 currency
codes", and eighteen sibling pages take v18 from the "i18n" in "relying on the
`Intl` API for i18n".

Anchoring the match, and allowing the `since`/`from`/`as of` prefixes the
comments use, leaves all seventy-nine correctly versioned comments untouched.
`generate_manifest` carries its own copy of the regex, so the API list badges
had the same values.

Those `@angular/common` comments never stated a version, so they now say `18.0`
explicitly, the release `d34c033902` (#54483) first shipped in, matching the
`@deprecated 18.0` already on `FormatWidth` in the same file. Nine tags in that
file had no version at all and were showing no badge; they are from the same
commit and now say `18.0` too.
2026-09-09 16:12:18 +02:00
Kristiyan Kostadinov 77e2a2a85c refactor(core): expose onDestroy in DirectiveFixture
Exposes the `onDestroy` method from the host component in `DirectiveFixture`. We need this for harnesses in the CDK.
2026-09-09 16:11:33 +02:00
Kam d6d51a0ef8 test(devtools): run the orphaned app component spec
`app.component.spec.ts` has been in the tree since the initial DevTools commit
in 2020, but no revision of `shell-browser/src/app/BUILD.bazel` has ever listed
it, and that file has no globs. Two of the three specs in the directory are
wired up; this one has never run.

It could not run as written. The setup declared `AppComponent` and imported
`RouterModule.forRoot([])`, neither of which applies now that the component is
standalone and injects `MessageBus` and `DEEP_LINK_INSTANCE_ID`.

It now stubs the `chrome.devtools` surface that `ngOnInit` reads and covers
both lifecycle hooks: the listeners registered on init, the two performance
track emits, the three guards on the deep link listener, and the listeners
removed on destroy.

Follow-up to #70570, which fixed the same problem in ng-devtools-backend.
2026-09-09 16:11:01 +02:00
hawkgs 635ef1bd80 refactor(devtools): contain hydration overlays functionality on the be
Move the refresh mechanism of the hydration overlays to the backend,
delegating only the feature toggling to the frontend.
2026-09-09 16:10:25 +02:00
Kam 0029c3ba5a docs: use a supported alert level for the cross-field validation note
`WARNING:` is not an `AlertSeverityLevel`, so the paragraph rendered as plain
body text on https://angular.dev/guide/forms/signals/cross-field-logic while
the three other alerts on the page rendered normally. `CRITICAL` is the level
kitchen-sink.md defines for warning the reader off a footgun.

The five `@Component({/* ... */})` collapses are the formatter's. The file has
drifted out of prettier since it landed, and `ng-dev format changed --check`
checks whole changed files.
2026-09-09 16:09:49 +02:00
SkyZeroZx 5afdd98de1 fix(docs-infra): remove space after decorator symbol
Handle the separate whitespace token emitted by Shiki when a decorator name is linked to its API reference.
2026-09-09 15:59:01 +02:00
arturovt bd9b45b5cc feat(core): allow reading Injector from a view or content query
Queries could already read `ElementRef`, `TemplateRef` and
`ViewContainerRef` from a matched node via the `read` option, but not the
node injector. Getting it required a helper directive on the element.

`{read: Injector}` now returns the node injector of the matched element,
so a component can resolve tokens as they are seen from that element. This
is useful when wrapping third-party components that project templates and
expect directives inside those templates to inject the host component.

Works for `@ViewChild`/`@ContentChild` and the signal-based
`viewChild`/`contentChild`.

Fixes #47760
2026-09-09 15:57:32 +02:00
Matthieu Riegler d470afa8b2 refactor(platform-server): ensure error boundaries work with ssr/hydration (#70463)
Added some tests to ensure error boundaries work with SSR and hydration.

PR Close #70463
2026-09-09 15:56:05 +02:00
Alex Rickabaugh f4a5650ed9 feat(language-service): add support for @boundary blocks (#70463)
Add support for the new `@boundary` and `@error` control flow blocks in the Angular Language Service.

This includes:
- Updating outlining spans to handle boundary blocks correctly.
- Adding classification visitor methods for semantic tokens.
- Adding template target visitor methods for navigation and hover support.
- Updating the TextMate grammar to recognize `@boundary` and the `when` clause.

PR Close #70463
2026-09-09 15:56:05 +02:00
Alex Rickabaugh f7597cb7d9 refactor(compiler): template type-checking support for @boundary (#70463)
Add support for `@boundary` blocks in the template type-checking pipeline.

PR Close #70463
2026-09-09 15:56:05 +02:00
Alex Rickabaugh 54ed62d240 refactor(core): implement @boundary runtime primitives and AST nodes (#70463)
Add the runtime primitives `ɵɵboundaryCreate` and `ɵɵboundaryUpdate` to
the core instructions, which handle synchronous view destruction and
provide the `ON_ERROR` interceptor hooks.

Also include the initial compiler AST representations for the new syntax
including the Lexer tokenization and HTML Parser integration. This lays
the foundational structure for `@boundary` prior to code generation.

Co-authored-by: Matthieu Riegler <kyro38@gmail.com>

PR Close #70463
2026-09-09 15:56:05 +02:00
Alex Rickabaugh f6afb807c1 feat(core): add ErrorBoundary programmatic APIs (#70463)
Implement error interception during refreshView and provide onError callback options in ViewContainerRef for programmatic rendering and encapsulation of boundary errors.

PR Close #70463
2026-09-09 15:56:05 +02:00
Matthieu Riegler f0a271c7bd fix(compiler-cli): do not flag callable objects with zero parameters in uninvoked track function check
In `@for` blocks, tracking callable objects by reference (e.g. signal forms `FieldTree`, signals, or custom callable objects) is a valid pattern when tracking by object identity. Previously, `UninvokedTrackFunctionCheck` (NG8115) flagged any property read whose type has call signatures, regardless of whether the target was an actual track function expecting arguments or a method reference.

This commit updates `UninvokedTrackFunctionCheck` to only emit a diagnostic when the target expression has call signatures that declare parameters (functions/methods expecting arguments like `(item)` or `(index, item)`) or is a method declaration. Callable objects without parameters accessed as properties are now recognized as tracked values and not flagged as uninvoked track functions.

Fixes #70207
2026-09-09 15:54:09 +02:00
arturovt 8227e5cf6d fix(router): keep detached route subtree contexts isolated and intact
When a route is detached for `RouteReuseStrategy`, its component and child
`RouterOutlet`s stay alive and keep referencing the `ChildrenOutletContexts`
they were created with. `detachAndStoreRouteSubtree` used to call
`onOutletDeactivated()`, which swaps that object's Map for an empty one, so
after re-attaching, the inner outlet and the router read from two different
context trees and deeper child routes (e.g. an `edit` route under a reused
list) never rendered.

Now, on detach: take the child contexts map as-is (destruction still calls
`onOutletDeactivated()` to prune) and give the `OutletContext` a fresh
`ChildrenOutletContexts`. This keeps the detached component rendering from
its stored map once re-attached, and prevents whatever activates next in
the same parent outlet (e.g. a sibling tab) from mutating or wiping that
stored map.

Fixes #57285
2026-09-09 15:53:28 +02:00
Angular Robot 9a58353b1b build: update cross-repo angular dependencies
See associated pull request for more information.
2026-09-04 07:52:21 -07:00
SkyZeroZx 063eda2db2 test(core): replace act with actAsync
Replaces the custom `act` helper function with the standardized `actAsync` utility
2026-09-04 07:48:11 -07:00
SkyZeroZx 318fefad30 test(forms): Add utility functions and update test files to use them
Moves common helper functions into a shared test utility to reduce duplication.
2026-09-04 07:48:11 -07:00
Kam 3dfc855381 test(devtools): run the orphaned supported-apis spec
`supported-apis.spec.ts` was added in #60585, in a commit that also edited the
`ts_test_library` three lines below the `srcs` it was left out of. No revision
of that BUILD file has ever listed it, so it has not run since March 2025. The
target goes from 12 specs to 14.

Wiring it up alone would not have worked. Every `*IsSupported` helper calls
`ngDebugClient()`, which throws when `window.ng` is undefined, and the old
`expect(supported).toBeTruthy()` set no `ng` at all. It now stubs `ng` and
checks the flag set and that each flag tracks its own debug API.

`ng-debug-api.spec.ts` adds an `[ng-version]` root and did not remove it, which
`getAppRoots()` then picks up in the other file under jasmine's random
ordering, so it now clears the DOM in its own `afterEach`.

`glob` matches the sibling `directive-forest/component-tree` target and keeps
the next spec in this directory from being dropped the same way.
2026-09-04 07:46:46 -07:00
Kam 3481b15167 docs: correct filename labels on code examples
Four labels name a different file than the block loads: three tabs on the
animations guide say `leave.*` but load `leave-parent.*` (`leave.*` is a
separate example shown earlier on the page), and one block in the reactive
forms section says `actor-form-template.component.html` but loads
`actor-form-reactive.component.html`.

The other three leak the `.1`/`.2` variant suffix, which every other
numbered-variant header in adev strips.

https://angular.dev/guide/animations
https://angular.dev/guide/legacy-animations/reusable-animations
https://angular.dev/guide/forms/form-validation
2026-09-04 07:46:12 -07:00
Lazizbek Ergashev 70756b506c fix(core): apply SkipSelf to only the starting node in embedded views
The node-by-node walk used to resolve a token through an embedded view
injector forces the `Self` flag on every node injector lookup. Combined
with `SkipSelf`, that lookup can never match, so every node between the
injection point and the view boundary was skipped and the token was
resolved from the custom injector instead of the nearest parent node.

Clear `SkipSelf` once the starting node has been checked, so only that
node is skipped. This also makes the strip at the embedded view injector
redundant.

Fixes #70547
2026-09-04 07:43:55 -07:00
Angular Robot bae170d535 build: update bazel dependencies
See associated pull request for more information.
2026-09-04 07:27:27 -07:00
Kam 26afa313f3 fix(docs-infra): correct example viewer tab state and line numbering
Five more defects in the example viewer, following #70508.

The DOM was queried before Angular rendered it. `setCodeLinesVisibility()`
walks the rendered lines but ran synchronously on tab change, so it measured
the outgoing tab and the incoming file showed in full. The selected tab was
also lost when the code block was hidden and reshown, because the recreated
tab group had no `[selectedIndex]` while `snippetCode` survived, leaving the
strip and the code disagreeing.

`expandable` was computed once at startup by counting hidden DOM nodes, so a
collapsed tab offered no way to expand it, and recomputing that count on tab
change would drop the control whenever the block was expanded, since nothing
is hidden then. Both paths now share one rule: a file is expandable when it
has a `visibleLinesRange` and either the block is expanded or the range
actually hides lines, so a range that covers its whole file still gets no
inert control.

Array indices were also mixed with 1-based line numbers: the gap check tested
`index - 1` for the preceding line, drawing a `...` separator inside
contiguous ranges, and the gutter tested `index` while the code tested
`index + 1`, shifting every line number by one.

Five new specs cover these, using the comma-separated range format the
pipeline emits; each fails with its fix reverted. The tab label also moves
from 0.8125rem to 0.875rem to match the code beside it.
2026-09-04 07:26:24 -07:00
Andrew Scott 74c1716cef refactor(compiler-cli): index pipes in template expressions
Update the template indexer to discover and record pipes used in template expressions.

This associates template pipe identifiers with their target pipe class declarations, enabling indexers and language tooling to properly resolve and cross-reference pipes.
2026-09-04 07:13:50 -07:00
Andrew Scott 3064f3f1dc feat(router): expose router resources in public API
Router resources integrate the Angular router with the Signals Resource API, allowing route-level data fetching during navigation transitions.
2026-09-04 07:10:49 -07:00
Andrew Scott caeab598c9 refactor(compiler): emit any as type argument for ɵɵInjectableDeclaration
The static `ɵprov` field emitted on `@Injectable()` classes uses `ɵɵInjectableDeclaration<T>`.
When a subclass extends a generic `@Injectable()` base class with contravariant parameters
(such as callback/transformer methods depending on generic type parameters), TypeScript's
static side inheritance check (`typeof Sub extends typeof Super`) fails with `TS2417` because
`ɵɵInjectableDeclaration<Sub>` is not assignable to `ɵɵInjectableDeclaration<Super<any>>`.

Using `any` (`o.DYNAMIC_TYPE`) in `createInjectableType` avoids strict variance checks on
static inheritance for internal Ivy definitions and aligns with other Ivy declaration types.
2026-09-04 07:05:23 -07:00
Angular Robot 98960556a6 build: update mcp-b webmcp dependencies to v5
See associated pull request for more information.
2026-09-04 07:04:49 -07:00
Matthew Beck 81c3f3a6aa release: cut the v22.2.0-next.5 release 2026-09-02 19:40:50 -07:00
Matthew Beck e4b8822c43 docs: release notes for the v22.1.5 release 2026-09-02 19:37:23 -07:00
Matthew Beck 502fa130aa fix(docs-infra): ignore external links when mapping navigation items to routes
When navigation items contain external URLs (e.g. https:// links for documentation or third-party resources), mapNavigationItemsToRoutes previously registered them as Angular Router route definitions. In recent versions of @angular/build, the static prerender worker asserts that discovered routes do not return empty content during SSG prerendering, causing production builds and CI adev-deploy to fail on these routes.

This change filters out external links in mapNavigationItemsToRoutes so only valid internal application paths are registered as Angular routes.
2026-09-02 19:07:15 -07:00
SkyZeroZx 69c87301aa fix(vscode-extension): prevent JSDoc link provider injection
Treat link targets as file paths so crafted documentation cannot select arbitrary VS Code resource providers.

Fixes https://github.com/angular/angular/issues/70512
2026-09-02 16:06:55 -07:00
arturovt 58d536bc83 fix(core): don't fail NgModule checks for a pipe that extends a base class
When an abstract base class has something like a lifecycle hook, Angular
compiles it as a directive with no selector. If a pipe extends that base
class, the pipe picks up the base class's directive definition through
normal class inheritance.

The NgModule dev-mode checks then got confused by that inherited
definition and reported the pipe as a broken directive:

  - "Directive SomePipe has no selector, please add it!"
  - or, if the base class was a default (standalone) abstract directive,
    "SomePipe is marked as standalone and can't be declared..."

Both only happened in tests (TestBed), not when running the app.

Now the selector check and the standalone check both look at the pipe's
own definition and ignore a directive definition that only came from a
base class.

Fixes #36427
2026-09-02 16:06:12 -07:00
SkyZeroZx 7168bed663 fix(core): validate SVG animation attributes outside the SVG namespace
Make the SVG animation security context depend on the tag name instead of the namespace the element was created in. An animation element declared outside an `<svg>` is created in the HTML namespace, but still animates once it ends up inside an SVG subtree, so to and `attributeName` bindings were reaching the DOM unvalidated.

Fixes  #70490
2026-09-02 16:05:47 -07:00
Andrew Scott 7137a41223 feat(router): stabilize auto cleanup injectors feature
Removes experimental tags and stabilizes withAutoCleanupInjectors, AutoCleanupInjectorsFeature,
and RouteReuseStrategy cleanup methods, while re-exporting withExperimentalAutoCleanupInjectors as deprecated.
2026-09-02 16:05:12 -07:00
Jaime Burgos af26a8c521 fix(router): avoid view transitions when the user agent provides one
Preserve hasUAVisualTransition through Location and the Router navigation pipeline. This prevents withViewTransitions from starting an author transition after the browser has already performed one, including across redirects and when using experimental platform navigation.
2026-09-02 16:04:41 -07:00
Andrew Scott cd771174b9 build(zone.js): fix release PR URL and filter changelog to zone.js scope
In recent conventional-changelog version updates, the underlying
@conventional-changelog/git-client dropped support for the raw `grep` and
`extendedRegexp` options, causing all monorepo commits to be included in the
zone.js changelog. Additionally, without `tagPrefix: 'zone.js-'`, conventional-changelog
treated monorepo release tags as release boundaries, generating changelog sections
for every intermediate monorepo release.

This change:
- Configures `tagPrefix: 'zone.js-'` and filters commits by `scope === 'zone.js'`
  via `writerOpts.transform` in the gulp changelog task.
- Fixes the PR creation link in the release script to use the cross-fork
  compare URL format on GitHub.
- Fixes the release commit SHA lookup in `cutReleaseWorkflow`.
- Updates zone.js release documentation.
2026-09-02 15:59:31 -07:00
Angular Robot a66fd64cd4 build: update cross-repo angular dependencies
See associated pull request for more information.
2026-09-02 15:52:44 -07:00
Angular Robot de91d10b80 docs: update cross-repo adev docs
Updated Angular adev cross repo docs files.
2026-09-02 15:39:44 -07:00
Cheng-Hsuan Tsai d7af4b5aca docs: decouple selection from aria toolbar examples and guide 2026-09-02 15:14:21 -07:00
Kam 45148dae44 docs: correct openFiles entries that point at missing files
Two tutorial steps list a file in `openFiles` that does not exist, and a
missing entry is dropped without complaint. `first-app/05-inputs` asks for
`housinglocations.ts` when the file is `housinglocation.ts`; since
`hiddenFiles` is everything not in `openFiles`, the interface that step
teaches was marked hidden rather than opened.
`signals/5-component-communication-with-signals` asks for
`quantity-selector.ts`, which exists in neither `src` nor `answer`. Every
openFiles entry in the tutorials now resolves.
2026-09-02 14:32:45 -07:00
Kam 90b9e081f5 fix(docs-infra): keep the collapsed code state when a code block is reshown
The code block lives inside `@if (showCode())`, so hiding it destroys the DOM and
showing it builds a fresh copy with no hidden lines. Nothing reapplied
`setCodeLinesVisibility()`, so a collapsed block came back showing the whole file.

Reapply it once the block is rendered again. It already branches on `expanded()`,
so a block that was expanded stays expanded.
2026-09-02 14:32:17 -07:00
Kam 431b170fd5 build: narrow the preview exclusions for two example apps
`reactive-forms` and `form-validation` were excluded from `embeddable` in
full, with a TODO to fix them. The examples are fine; four partial snapshots
quoted by the guides are not, since each shares the final template of the
component it precedes, which references members it does not have yet.
Excluding those four files clears all 30 errors and puts 23 app files back in
the preview pool, so both guides' final components can be previewed for the
first time.
2026-09-02 14:30:50 -07:00
Angular Robot 6fdb7f061b build: update dependency mocha to v12
See associated pull request for more information.
2026-09-02 14:30:09 -07:00
Angular Robot b8a750fb34 build: update dependency bazel to v8.8.0
See associated pull request for more information.
2026-09-02 14:28:44 -07:00
Matthew Beck 124bf44809 release: cut the zone.js-0.16.3 release 2026-09-02 14:19:15 -07:00
Matthieu Riegler f44bf0fe22 ci: move packages/private under fw-general
This doesn't need dev-infra approval as its mostly testing utils.
2026-09-01 14:42:04 -07:00
Kam c03e872ed9 docs: highlight the search tutorial @for block as Angular
The block quotes a `.ts` file but declares `language="html"`, and shiki's
HTML grammar does not know Angular control flow, so the whole `@for` line
renders unstyled. Use `angular-ts`, matching the other blocks in the
tutorial.
2026-09-01 14:41:31 -07:00
aparziale 1d9e445d1a fix(vscode-extension): handle escaped delimiters in inline template and styles highlighting
The TextMate grammars for inline templates and inline styles ended the
string at the first occurrence of the delimiter, so an escaped delimiter
(e.g. \`) terminated the highlighting prematurely and the rest of the
template was no longer highlighted as HTML.

Escape sequences are now consumed before delegating to the HTML/CSS
grammars, so escaped delimiters no longer end the string. Also removes
the stray pipes from the string delimiter character class, which
unintentionally matched a literal '|'.

Fixes #65493
2026-09-01 14:40:44 -07:00
Kristiyan Kostadinov 05c4d5a835 feat(core): add utility for testing directives
Adds `TestBed.createDirective` to make testing directives easier.

Fixes #54164.
2026-09-01 14:40:05 -07:00
Matthieu Riegler 9f440907e8 refactor(core): remove old deferredImports structure
This finishes the migration to the keyed object structure
2026-09-01 09:41:10 -07:00
Doug Parker 6e299da8cf feat(forms): hard-code readOnlyHint and untrustedContentHint for WebMCP implicit signal forms
Signal forms can safely assume `{readOnlyHint: false, untrustedContentHint: false}` given their context. A form _must_ alter page DOM (or else how would a user interact with it) and is therefore definitionally side-effectful. We also assume returned content is trusted, given it is currently hard-coded or derived from application errors.

In the future, we might want to consider cases where application errors are explicitly untrusted or where application developers choose to customize the response text with something which might be untrusted. For now, that's out of scope and we'll worry about it when a compelling use case arises.
2026-09-01 09:39:20 -07:00
Doug Parker 91a2bf8425 feat(core): support annotations in WebMCP tool declarations
This updates `declareExperimentalWebMcpTool` to allow annotations to be provided to opt-in the tool explicitly into being read only or returning untrusted content.
2026-09-01 09:39:20 -07:00
Matthieu Riegler faafd18a4c fix(compiler-cli): check uninvoked signal aliases in extended diagnostic
Extended template diagnostic interpolated_signal_not_invoked previously
only checked symbols with kind === SymbolKind.Expression. When a signal
is aliased via @let (or a template variable), getSymbolOfNode returns
a LetDeclarationSymbol (or VariableSymbol), causing the diagnostic to
skip checking uninvoked usages of signal aliases in interpolations and
bindings.

This commit updates interpolated_signal_not_invoked to also check
LetDeclarationSymbol and VariableSymbol, using the usage site's AST
name span for reporting the diagnostic.

Closes #70476
2026-09-01 09:38:21 -07:00
Kam b70edd2768 fix(docs-infra): parse docs-callout attributes correctly
Three ways a callout could be misparsed:

- A title quoted with `'` or a backtick was dropped, leaving an empty
  heading. Two callouts lose their title on angular.dev today, on
  guide/forms/template-driven-forms and guide/i18n/prepare. The first has
  to use single quotes because its title contains `"pristine"`.
- A title containing `>` was dropped, because the attribute capture
  stopped at the first `>` even inside a quoted value.
- The severity was matched anywhere in the tag, so a title such as
  "Why this is important" silently rendered an important callout.

Scan attributes with quoting in mind, accept all three quote characters
as #69268 did for docs-code-block, and match the severity flags against
the tag with attribute values removed. The i18n callout also spelled the
attribute `header`, which the extension has never read.
2026-09-01 09:36:47 -07:00
hawkgs 00ec65546b refactor(devtools): fix signal details UI
Fix different action buttons and value preview container size.
2026-09-01 09:33:35 -07:00
Kam a92f6057d3 refactor(docs-infra): remove two more unreferenced guide images
`input.svg` and `output.svg` are leftovers that #70335 missed. Their
last reference was deleted by #54829 when the AIO guides were removed,
and nothing in the repo mentions them or their directory. Removing both
empties `adev/src/assets/images/guide/inputs-outputs`.
2026-09-01 09:32:27 -07:00
Shuaib Hasan Akib b5ce15c659 refactor(core): use native Promise.withResolvers() in tests
Replaces the temporary `promiseWithResolvers` polyfill with the
native `Promise.withResolvers()` API in test files and Updates the TypeScript configuration to include the `es2024.promise`.
2026-09-01 09:31:48 -07:00
arturovt 8975b4346d docs: add NG0991 error page and document rxResource's completion contract
RESOURCE_COMPLETED_BEFORE_PRODUCING_VALUE had no guide, no JSDoc on
RxResourceOptions.stream, and — since the code was positive rather
than negative — could never get an auto-linked docs page even if one
existed. Flip it to -991, add the NG0991 reference page, and document
the "stream must emit a value or an error before completing"
requirement on stream's JSDoc and in the RxJS interop guide.

Also documents and tests that an unguarded template read of an
errored resource's .value() propagates to the global ErrorHandler,
and recommends guarding with .hasValue() as defense in depth.

httpResource can throw the same error, but for a different reason:
its internal request Observable isn't something app code writes
directly, so an empty completion there is almost always an
HttpInterceptor swallowing the response (catchError(() => EMPTY))
rather than a stream authored in the resource() call itself. The page
covers both APIs with guidance matched to what's actually going on
for each.
2026-09-01 09:31:00 -07:00
arturovt ca38305976 docs: explain that errors during early app startup miss ErrorHandler
There's a short window where Angular can't send errors to ErrorHandler
yet: while it's still building the root module or root component. It
needs that root instance to look up the ErrorHandler you provided, so
an error thrown before it exists just becomes a normal uncaught error
instead.

This mostly bites people using Angular elements, where a custom element
tag already sitting in the page gets upgraded (and its component built)
the moment you call customElements.define, which can happen very early.

Added a short section to the error handling guide explaining why this
happens and the usual ways around it: setTimeout, APP_BOOTSTRAP_LISTENER,
or moving element registration to ngDoBootstrap.

Fixes #29211
2026-09-01 09:26:44 -07:00
oerol 9fd77852fb docs: fix global target event listener example 2026-09-01 09:26:07 -07:00
Santosh Yadav e1bf5393ad docs: Update request handler to use handle method
there is no request method the correct method is hanlde
2026-09-01 09:25:24 -07:00
Angular Robot 1b0d861fe5 build: lock file maintenance
See associated pull request for more information.
2026-09-01 09:22:54 -07:00
SkyZeroZx d8e74e7d39 docs(docs-infra): improve update checkbox accessibility
Associate each recommendation with its checkbox so assistive technology has a descriptive label and non-interactive text toggles the control. Keep embedded links independently operable.
2026-08-31 20:33:18 -07:00
lazerg f72600eadd fix(migrations): skip tsconfig files of non-Angular projects
Only touch tsconfig files for targets that use an Angular builder, including community ones like Nx, so non-Angular projects in mixed workspaces are left alone.

Fixes #69837
2026-08-31 20:31:31 -07:00
Angular Robot 87d38ec3ff build: lock file maintenance
See associated pull request for more information.
2026-08-31 15:37:10 -07:00
Stefan 4c4a705ce3 fix(zone.js): preserve accessor and non-enumerable event listener options
`copyEventListenerOptions` copied the caller's options with `{...options}`
before forwarding to the native `addEventListener`. Object spread only
copies own enumerable data properties, whereas the native call reads
each dictionary member via WebIDL — a plain `[[Get]]` per member, which
invokes accessors and ignores enumerability. The copy was therefore
lossy in a way the native call is not:

- `Object.defineProperty(opts, 'passive', { get })` (the shape used by
  MDN's passive-listener feature test) — the getter was never invoked,
  so libraries that use the feature test fall back to the legacy boolean
  and register every listener as non-passive.
- `Object.defineProperty(opts, 'capture', { get: () => true })` — the
  listener was silently registered on the bubbling phase.
- `Object.defineProperty(opts, 'once', { get: () => true })` — the
  listener fired on every dispatch.

`signal` was already special-cased for `AbortController.prototype.signal`
after #54142; that patch generalises the workaround to every recognised
member.

The copy itself was the correct fix for #54142 (frozen/readonly options)
and is preserved. The fix reads each recognised member from the source
via `[[Get]]` when the spread did not, which recovers accessors and
non-enumerable properties without double-invoking any getter. The list
of recognised members is hoisted to module scope so it isn't allocated
on every `patchEventTarget` invocation.

The call site is reordered to `buildEventListenerOptions(
copyEventListenerOptions(...))` so the passive-events code path also
spreads a normalised data object rather than the caller's raw input.

Fixes #70431

Co-authored-by: Matthieu Riegler <kyro38@gmail.com>
2026-08-31 15:21:37 -07:00
Roman 0904f90b13 fix(core): cancel stale debounce timers to prevent timer leaks
The debounce() utility scheduled a setTimeout when using a numeric wait value, but the scheduled timer was never cleared when a new value arrived, the observable threw, or the injector was destroyed. This caused pending timers to fire after invalidation and leak beyond the owner's lifecycle.

Refactor timer cancellation into a dedicated helper and track the pending timer id so any stale timer is cleared on new values or teardown. Adds tests covering cancellation on new values, errors, and injector destruction.
2026-08-31 15:07:31 -07:00
Jessica Janiuk f3ed75eb6b ci: remove shared primitives pullapprove group
This group is no longer necessary as responsibilities for that are now spread amongst teams more equally.
2026-08-31 15:03:05 -07:00
Alan Agius b3bb36ad87 fix(platform-server): resolve HTTP(S) URLs without authority as relative during SSR
Under the WHATWG URL standard, HTTP and HTTPS URLs lacking an authority
(e.g., `http:/path` or `http:path`) resolve as relative paths when resolved
against an origin of the same scheme. Previously, `relativeUrlsTransformerInterceptorFn`
treated any URL with a scheme as an absolute URL, bypassing base resolution in SSR
and allowing Node fetch to parse the path as a cross-origin host.

This commit updates SSR URL resolution and the HTTP interceptor to ensure
HTTP(S) URLs without an authority are resolved against the current origin,
preventing unexpected origin changes and aligning SSR with browser behavior.

Fixes #70447
2026-08-31 13:22:53 -07:00
Kristiyan Kostadinov 38861ac41b fix(forms): avoid writing to name input on ControlValueAccessor
Fixes that signal forms were overridding the `name` input of the CVA with an auto-generated one. This can break directives like `mat-radio` that assign their own `name`.

Fixes #69677.
2026-08-31 13:21:15 -07:00
Matthieu Riegler 9cf7b44f7c build: remove explicit strict options
Those options are enabled by default, they don't need to be explicit.
2026-08-31 13:17:00 -07:00
Kam acdac1cb89 fix(docs-infra): restore the edit link on decorative header pages
The 34 pages using `<docs-decorative-header>` render their title
through `getPageTitle()` without passing the markdown file path, so
the "Edit this page" link is silently dropped. Every other page keeps
it. Compare https://v19.angular.dev/guide/components, which still has
the pencil, against https://angular.dev/guide/components, which does
not.

`filePath` was required until #63536 made it optional, so API
descriptions with no editable source could render a title without a
link. That removed the compile error forcing the decorative header to
supply it, and the argument was lost with nothing to catch it.

Pass the path again and cover both header variants with tests, since
the edit link had no coverage at all.
2026-08-31 13:14:25 -07:00
Kam d9afca095b fix(docs-infra): reject unclosed paired docs elements
An unclosed `<docs-step>` or `<docs-card>` produces no error, just wrong
output, so the two cases fixed in the previous commit went unnoticed for
years.

Validate that the paired custom elements balance before parsing. A
mismatch now names the file and the counts instead of silently
swallowing a section.
2026-08-31 11:31:02 -07:00
Kam 0752282c8f docs: close the unclosed docs-step tags in two guides
`reactive-forms.md` and `app-shell.md` each open a `<docs-step>` that is
never closed. The tokenizers match up to the *next* closing tag instead
of failing, so the unclosed tag pulls in the content that follows and
leaves the block after it unparsed in the output.

On https://angular.dev/guide/forms/reactive-forms the "Creating nested
form groups" section shows raw source: "To create a nested group in
`profileForm`, add a nested `address` element", backticks and all. Its
heading is swallowed into the unclosed step instead of being an h3, and
the page renders 20 of its 21 steps. On
https://angular.dev/ecosystem/service-workers/app-shell the third step
is missing entirely.

`reactive-forms.md` has been broken since #52536, `app-shell.md` since
#55550.
2026-08-31 11:31:02 -07:00
Kam 46b02cc181 docs: correct stale visibleLines ranges in the first-app tutorial
The tutorial's example files shifted without the README line references
following, so several steps point at the wrong code. In two cases every
requested line is past the end of the file; on the inputs step this renders
as a code block with no lines visible at all.
2026-08-31 11:26:37 -07:00
Kam cd3c8cdd3a docs: correct out-of-range highlight indices in two code blocks
Both indices point past the end of their block, so the intended lines are
never highlighted. The reactive forms block renders with no highlight at all,
and the first example in output-interop.md highlights nothing while its
sibling block is unaffected.
2026-08-31 11:25:03 -07:00
Kam 731c838dbb docs(zone.js): point the Bluebird link at the project repo
bluebirdjs.com no longer resolves. npm lists the GitHub repo as the package's
homepage, so the link now goes there.
2026-08-31 11:21:42 -07:00
Kam 519c52eea0 docs: update dead links in the contributing docs
The fork guide link 404s since GitHub retired help.github.com, and the
rules_nodejs debugging docs moved to the bazel-contrib org. Also points the
git setup link straight at its current home rather than through a redirect.
2026-08-31 11:17:37 -07:00
Angular Robot 563850e860 docs: update cross-repo adev docs
Updated Angular adev cross repo docs files.
2026-08-28 18:05:00 -07:00
Angular Robot 444b4652a9 build: update cross-repo angular dependencies
See associated pull request for more information.
2026-08-28 18:01:05 -07:00
Kam 2a4903fec1 docs(core): repoint or drop dead jsperf links in the render3 notes
jsperf.com now returns 410 for every benchmark. Two of them still exist on the
successor site and are repointed at jsperf.app; the other five are gone, so the
links are removed and the performance claims they backed are kept.
2026-08-28 18:00:36 -07:00
Angular Robot 2877c3b995 build: update dependency node to v24.20.0
See associated pull request for more information.
2026-08-28 17:59:46 -07:00
Shuaib Hasan Akib 54f67e1318 docs: correct highlight index for pipe examples in README 2026-08-28 17:58:55 -07:00
Kam 1e8dd80458 build: point the integration .gitignore comments at live GitHub docs
help.github.com/ignore-files/ 404s since GitHub retired that domain. Five of the
files still linked to it over plain http as well.
2026-08-28 17:57:15 -07:00
Michael Small 0017b87b52 docs: update link/file for JetBrains AGENTS.md 2026-08-28 17:56:37 -07:00
leonsenft 34817da735 refactor(core): defer foreign component rendering to post-update pass
Foreign components imported via foreignImports and created by the
ɵɵforeignComponent instruction were previously rendered eagerly in the
creation phase (rf & 1) of the template. This restricted which
properties could be passed to foreign component props, as parent-bound
inputs (@Input(), input(), input.required()), properties initialized in
ngOnInit(), and pull-based view queries (viewChild()) were not yet
initialized at creation time.

This change defers foreign component rendering to run as a view effect
during the update pass:
- Update ɵɵforeignComponent in core to schedule component rendering via
  createViewEffect (executed in runEffectsInView during refreshView),
  executed with setActiveConsumer(null) to prevent reactive context
  leakage and destroyed immediately upon first execution.
- Update ɵɵforeignComponent to strictly accept props as a factory function
  (() => props) or null.
- Update the compiler template pipeline to wrap foreign component props
  in an arrow function closure (() => ({ ... })).
- Hoist creation-time foreign content projection instructions
  (ɵɵforeignContent, ɵɵforeignContentFn) into creation-phase variable
  declarations before ɵɵforeignComponent so creation-time context is
  captured safely.
2026-08-27 21:05:33 -07:00
Matthieu Riegler 4c55a36a87 refactor(compiler): deduplicate explicitly deferred types to prevent syntax errors
When `@Component.deferredImports` maps blocks to arrays of dependencies, an explicitly
deferred dependency might be defined in multiple blocks (e.g. `block1: [CmpA], block2: [CmpA]`).
Previously, these were appended to the `explicitlyDeferredTypes` array without deduplication.
When generating the `setClassMetadataAsync` wrapper for development mode, the compiler
used this array to generate callback parameters for dynamic imports. This resulted in
duplicate parameter names in the callback signature `(CmpA, CmpA) => { ... }`, which
causes an `Uncaught SyntaxError` when V8 parses the module in strict mode.
This commit deduplicates `explicitlyDeferredTypes` in the `ngtsc` component handler, and
adds a secondary deduplication check in the `r3_class_metadata_compiler` generator to
ensure duplicate parameter names are never emitted.
2026-08-27 21:04:07 -07:00
Kam ad22e89d4d refactor(devtools): point the .gitignore comment at live GitHub docs
help.github.com/ignore-files/ 404s since GitHub retired that domain.
2026-08-27 13:44:43 -07:00
Andrew Scott 6f1dc268cc docs(router): Add router resource docs
Adds doc (but no navigation entry) for router resources. Feature not released yet
but getting documentation in place.
2026-08-27 12:45:27 -07:00
Andrew Scott 4fc45a9b38 refactor(compiler-cli): index bound directive inputs and outputs in templates
Update the template indexer to discover and record bound directive inputs (property bindings, static text attributes) and outputs (event bindings).

This associates template binding identifiers with their target directive or component class declarations, enabling indexers and language tooling to properly resolve and cross-reference bound directive inputs and outputs.
2026-08-27 10:23:32 -07:00
Angular Robot 6f91982c0d build: update dependency vscode-languageserver-textdocument to v1.0.14
See associated pull request for more information.
2026-08-27 10:22:46 -07:00
Kam 843a65cba0 fix(docs-infra): point the tutorial .gitignore at live GitHub docs
The common tutorial scaffold's .gitignore opens with a link to
help.github.com/ignore-files/, which 404s. That directory is copied into every
tutorial and playground, so the dead link ships to anyone who opens one.
2026-08-27 10:20:49 -07:00
Angular Robot c2d4070c36 build: update all non-major dependencies
See associated pull request for more information.
2026-08-27 07:59:47 -07:00
Angular Robot bcc0e9206f build: update cypress-io/github-action action to v7.4.3
See associated pull request for more information.
2026-08-27 07:55:41 -07:00
Angular Robot 93ca7ab071 build: update bazel dependencies
See associated pull request for more information.
2026-08-27 07:51:21 -07:00
leonsenft 133cafda42 release: cut the v22.2.0-next.4 release 2026-08-26 17:16:48 -07:00
leonsenft a2b4379bb0 docs: release notes for the v22.1.4 release 2026-08-26 17:11:47 -07:00
leonsenft 9b70174100 docs: release notes for the v21.2.22 release 2026-08-26 17:00:16 -07:00
leonsenft 575bb6e08a docs: release notes for the v20.3.30 release 2026-08-26 16:38:40 -07:00
Angular Robot 2e6a05750e docs: update cross-repo adev docs
Updated Angular adev cross repo docs files.
2026-08-26 15:50:33 -07:00
hawkgs c1ac4b750a refactor(devtools): convert remaining console calls to the custom log
Convert the remaining calls introduced by the #70254.
2026-08-26 15:38:33 -07:00
Angular Robot 8c26fc74e5 build: update cross-repo angular dependencies
See associated pull request for more information.
2026-08-26 15:37:41 -07:00
Andrew Scott 66d505e287 refactor(core): Decouple ɵɵFactoryDeclaration and ɵɵInjectableDeclaration from type parameter T.
When compiling under standalone compilation, generated runtime static declarations (static ɵfac and static ɵprov) are emitted into preprocessed TypeScript files and visible to the compiler during semantic typechecking.

When a subclass extends a base class where the subclass is not structurally subtype-compatible with the superclass (such as differing generic type constraints, contravariant method parameters, or EventEmitter<this>), TypeScript's class static side heritage check (TS2417) fails because ɵɵFactoryDeclaration<T> and ɵɵInjectableDeclaration<T> structurally referenced the instance type T.

This change updates ɵɵFactoryDeclaration to return any instead of T, and sets factory return and value types in ɵɵInjectableDeclaration to any. This decouples static side inheritance from T, resolving TS2417 errors across subclassed components and injectables while preserving .d.ts metadata indexing and assignability to ɵɵdefineInjectable. This brings ɵfac and ɵprov into alignment with other Ivy declarations (ɵcmp, ɵdir, ɵpipe, ɵinj), which already treat their generic parameters as phantom metadata.
2026-08-26 13:45:05 -07:00
Alan Agius 1d0945fa32 release: bump Angular DevTools version to 1.21.0 2026-08-26 09:56:48 +02:00
leonsenft 915a03ae85 fix(core): explicitly reject foreign components in JIT mode
Foreign components are only supported in AOT mode. Using them in
JIT mode previously resulted in silent failures or confusing runtime
errors (such as unknown element errors or crashed template ingestion).

This commit adds explicit validation in JIT compilation:
- Throws an error during component compilation if `foreignImports` is
  specified on `@Component`.
- Throws an error during standalone import verification if a foreign
  component is mistakenly passed to `@Component.imports`.
2026-08-25 16:28:32 -07:00
leonsenft a46292af26 fix(compiler-cli): default template diagnostic related message source file to template
For external templates (using `templateUrl`), primary diagnostics are
reported against the synthetic `ts.SourceFile` representing the HTML
template document. However, secondary related messages (such as those in
`foreign_component.ts` and `oob.ts`) were explicitly passing the
component's TypeScript file as `sourceFile`.

Because the character offsets (`start` and `end`) originate from the
HTML template AST, associating them with the TypeScript source file
caused IDEs and CLI diagnostics to map HTML offsets onto the `.ts` file,
resulting in corrupt or out-of-bounds source locations.

This commit resolves the issue by:
1. Making `sourceFile` optional in `makeTemplateDiagnostic` and related
   checker interfaces (`TemplateTypeChecker`, `TemplateContext`).
2. Defaulting `relatedMessage.sourceFile` to the template's source file
   (`sf` for external/indirect templates, or the component `.ts` file
   for direct inline templates) when not explicitly provided.
3. Removing explicit `sourceFile: this.sourceMapping.node.getSourceFile()`
   mappings from `foreign_component.ts` and DOM element checks in
   `oob.ts`, allowing them to automatically resolve to the template file.
4. Adding unit test coverage for external templates encountering foreign
   component conflicts with related messages.
2026-08-25 16:19:32 -07:00
arturovt 83f7695b2e fix(core): throw coded RuntimeErrors instead of crashing when hydration/rendering can't find an expected DOM node
This started from a real production crash trace:

    TypeError: Cannot read properties of null (reading 'nextSibling')

Traced through a minified bundle back to siblingAfter() in
packages/core/src/hydration/node_lookup_utils.ts. It walks forward a
fixed number of DOM siblings during hydration, based on how many the
server rendered. Its only guard, validateSiblingNodeExists(), is
gated behind ngDevMode and stripped from production builds. If the
client DOM has fewer real siblings than the server-serialized data
expects — client/server render diverged, or something outside
Angular (a browser extension, an injected script) removed a node —
the loop ran fully unguarded in production: currentNode went null
partway through, and the next iteration's currentNode.nextSibling
threw a raw, uncoded TypeError instead of a coded, debuggable one.

Reproduced first, not just theorized: added a test that server-
renders a @for loop with 3 items, removes 2 of them before hydration
runs, and confirmed it actually throws that exact TypeError against
the original code before touching anything.

Fix: add a null check at the top of the loop, matching the pattern
already used for the existing NG0500/NG0502 hydration checks in
element.ts — the dev-mode check runs first as before (a no-op in
production), and the new check is a pure safety net that only fires
once that dev-mode check has already been compiled away. In dev mode
this is provably dead code, so nothing about dev behavior changes.
Coded as NG0501 (HYDRATION_MISSING_SIBLINGS, already existed).

While investigating, found and fixed two more call sites with the
exact same shape of bug — a dev-only check that leaves production
completely unguarded:

- navigateToNode() (same file): walks an explicit "firstChild /
  nextSibling" navigation path recorded for nodes that can't rely on
  simple sibling-walking (e.g. content right after an <ng-content>
  slot). Its only guard was also ngDevMode-only. Added the same
  production safety net, both mid-loop (the raw-crash case) and
  post-loop (where it could previously return null silently despite
  the function's return type promising a non-null RNode). New code:
  NG0509 (HYDRATION_MISSING_NODE_ON_PATH).

- getParentRElement() (render3/node_manipulation.ts): typed its
  tNode parameter as always non-null, but a real production trace
  showed it can be null at runtime, crashing on tNode.parent with no
  useful information. Widened the type to TNode | null and added a
  guard that throws a coded error instead. New code: NG0510
  (PARENT_NODE_NOT_FOUND).

  Unlike the other two, this one isn't gated behind ngDevMode at all
  in the original code — it reproduces unconditionally. Decoded the
  actual production minified stack trace against this source
  (matched every frame character-for-character) to find the real
  cause instead of guessing: an @if/@switch branch's content is its
  own embedded template with its own TView, built lazily the first
  time that branch renders. If an error interrupts that first pass —
  here, a hydration mismatch on the branch's second child, after the
  first child's TNode was already created — TView.firstCreatePass
  still gets flipped to false in render.ts's `catch` block before the
  error propagates, permanently marking that TView as corrupted.
  Unlike a component's TView (rebuilt from scratch next time via
  getOrCreateComponentTView()'s incompleteFirstPass check), nothing
  rebuilds an embedded view's TView. The next time that exact branch
  is selected again, its instructions read straight from the
  corrupted tView.data instead of creating a fresh TNode, and any
  node past the interruption point is still null.

  The real test added for this (full_app_hydration_spec.ts) exercises
  that whole chain for real: a hydration mismatch on an @if branch's
  second child, then re-entering the same branch. Confirmed it throws
  the exact byte-for-byte production error message ("Cannot read
  properties of null (reading 'parent')") without the fix, and the
  coded NG0510 with it. This replaces an earlier synthetic unit test
  that just cast a variable to null to bypass the type system rather
  than reproducing the actual failure.

  Note: a more thorough fix would rebuild the corrupted embedded TView
  itself (mirroring getOrCreateComponentTView()), which does make the
  crash disappear — but doing that surfaces a second, separate bug:
  the aborted first attempt's partially-created LView/DOM is never
  torn down, so the retried branch's content gets appended alongside
  leftover orphaned nodes instead of replacing them, corrupting
  content silently instead of crashing loudly. That's a materially
  bigger fix (proper LView cleanup after a mid-creation exception) and
  is intentionally out of scope here; this commit keeps the narrower,
  safe fix (a clear coded error) rather than trading a loud crash for
  silent data corruption.

Along the way, deduplicated describeDomNode(), which existed as two
near-identical private copies (one in this file's own NG0500 check,
one in platform-browser's dom_renderer.ts insertBefore check) and
moved it into core's hydration/error_handling.ts, shared via the
private ɵdescribeDomNode export.

That function was then removed from every production code path
entirely, per review feedback: computing a human-readable node
description is debug-oriented work that shouldn't ship unconditionally
in production bundles, regardless of how cheap it is — production
should get the bare coded error only, the same as everywhere else in
this file. All three fixes above follow that: ngDevMode && 'message'
in production, no computed description. Confirmed the removal
actually shrinks output, not just assumed it: regenerated all 8
affected bundling symbol goldens, and each one shows exactly one
symbol removed — "describeDomNode" — and nothing else.
2026-08-25 16:16:22 -07:00
Matthieu Riegler 9c52dbf216 refactor(compiler-cli): ensure eager dependencies used inside defer blocks are not dropped
When `onlyExplicitDeferDependencyImports` is enabled, dependencies that are used exclusively inside a `@defer` block but are provided eagerly (i.e., in the `imports` array but omitted from `deferredImports`) were incorrectly dropped from the generated `dependencies` array.

This occurred because the template binder intentionally omits directives used only inside defer blocks from the eager directives list, and the defer block resolver only tracks dependencies explicitly listed in `deferredImports`.

This commit fixes the issue by ensuring that any template dependency used within a defer block that is not deferred is explicitly preserved as an eager dependency, ensuring it is available at runtime.
2026-08-25 16:08:58 -07:00
Kam ddfd8a8e9b refactor(docs-infra): remove example configs for tooling that no longer exists
An example-config.json marked a directory as a doc example and told aio's
example tooling which boilerplate to use and what to run against it. #56496
removed that tooling in June 2024. Eight of these fifteen files are empty and no
code has read any of them since; the name survives only in the zip and
StackBlitz exclude lists, which skip it rather than open it.
2026-08-25 15:21:49 -07:00
Andrew Scott df83a34088 refactor(router): add component input binding support for router resources
Integrates component input binding (via `withComponentInputBinding()`) with router resources.
2026-08-25 15:20:56 -07:00
Lazizbek Ergashev 3848b7f8bc docs: clarify onSameUrlNavigation reload behavior
fixes #70367
2026-08-25 11:27:10 -07:00
hawkgs 07605ba793 feat(devtools): implement CD analyzer (#70254)
Implement component highlighting on change detection cycles along with embedding
the component-specific data to the directive/component tree explorer.

Closes #59057

PR Close #70254
2026-08-25 10:35:42 -07:00
hawkgs 82882fae51 refactor(devtools): add TTL and outline style to the highlighting mechanism (#70254)
Add the ability to automatically destroy created highlights by a provided TTL;
Add the option for outline style of the highlight overlays;
Add `prefer-inset` label position;

PR Close #70254
2026-08-25 10:35:42 -07:00
Kam 90fc9475f2 refactor(docs-infra): remove example code that nothing references
The `built-in-directives` and `structural-directives` example apps are no
longer referenced. Two guide rewrites moved their pages to inline code fences
and dropped the last references without removing the apps: #69134 for
structural directives, and #69822 for built-in directives, whose page is gone
entirely. Both apps sat inside the `embeddable` glob, so they were still
compiled as preview components on every build.

The example e2e suites are excluded from every consumer by design: previews
ignore `*.e2e-spec.ts` and `*.po.ts`, and the zip and StackBlitz pipelines
both list the suffix in `EXCLUDE_FILES`. They could not run in any case,
since every spec declares a `driver` and never assigns it before calling
`driver.get('')`, left over from an abandoned protractor to selenium
migration.

The eslint configuration in the same directory is also unused. Nothing in the
repository depends on eslint, and `tsconfig.eslint.json` extends a path that
no longer exists.
2026-08-25 10:11:19 -07:00
Kam 7da60d1920 fix(docs-infra): redirect three removed pages instead of 404ing
`guide/http/security` and `reference/concepts` both still exist as content but
neither is routed, so each falls through to the 404 shell.

`guide/http/security` was navigable from November 2023 until #54365 removed its
entry in February 2024. #55029 then copied its XSRF sections into the security
guide, which already covered XSSI, and #55060 repointed the remaining links, so
it now redirects to `/best-practices/security`.

`reference/concepts` was added by #54365 and removed by #58694 in November
2024, leaving it navigable for nine months without a redirect. The same commit
also removed `guide/ngmodules`, the route its only card linked to, so both now
redirect to `/guide/ngmodules/overview`, alongside the four `guide/ngmodules/*`
paths already redirected there.

Both pages are removed, along with the Bazel package that existed only to build
the concepts page, and the stale `llms-list.md` entry for the HttpClient page.
The security guide takes its place in that list, so `llms-full.txt` keeps its
XSSI coverage and picks up CSP, Trusted Types and sanitization with it.
2026-08-25 10:08:42 -07:00
Kam a67d4027e2 refactor(docs-infra): remove the Windows tile icons and other unused icons
The `msapplication` tile configuration serves no purpose now, so the
`browserconfig.xml` it points at, the five tile images and the two meta tags
in `index.html` are removed together.

`shared-docs/icons/twitter.svg` is also unused, left behind when the footer
moved to X and Bluesky. Its three siblings in that directory are all still
referenced.
2026-08-25 10:02:43 -07:00
Alan Agius 3e924cc8db fix(platform-server): avoid stripping unicode whitespace during url resolution
Avoid trimming urlStr with String.prototype.trim() in resolveUrl to ensure URL parsing and resolution align with the WHATWG URL standard.
2026-08-25 09:59:04 -07:00
Xia Chao 7596548e9b fix(common): use locale NaN symbol in number formatting
Non-finite values all used NumberSymbol.Infinity, so formatNumber(NaN)
rendered as infinity. Locale data already defines NumberSymbol.NaN.
2026-08-25 09:58:14 -07:00
Matthieu Riegler 3be5facf04 ci: pass required inputs for adev preview artifact upload
The `pack-and-upload-artifact` action from `dev-infra` recently added conditional steps that require `triggering-label` and `angular-robot-key` to be passed. Without these inputs, the internal action steps were silently skipped, resulting in no artifact being uploaded. This caused the subsequent deploy workflow to fail when it couldn't find the `adev-preview` artifact.
2026-08-25 09:57:22 -07:00
Georgi Serev 1371c946aa refactor(devtools): use a custom logger and error
Use a custom logger that prefixes the messages with `[Angular DevTools]`;
Add ability for dev-only log messages;
Add Angular-DevTools-specific `Error`;
2026-08-25 09:55:04 -07:00
root 74b294cd51 fix(compiler-cli): retain metadata for strict standalone errors
This fix ensures that metadata is properly retained when processing
strict standalone component errors for improved error diagnostics.
2026-08-25 09:54:06 -07:00
Angular Robot 1426f5ecf5 build: update cross-repo angular dependencies
See associated pull request for more information.
2026-08-24 15:53:22 -07:00
Cameron Smick 82a767535e feat(devtools): add "Watch signal" button to signal-details component
Add a "Watch signal" button to the signal-details component to allow users to "watch" changes to the associated signal.
2026-08-24 15:35:44 -07:00
Cameron Smick d45bd2f53d refactor(core): implement toggleWatchSignal for DevTools signal debugging
Implement \`toggleWatchSignal(id)\` in \`signal_debug.ts\` to enable toggling reactive watch listeners on individual signal graph nodes.
- Create reactive \`Watch\` instances using \`createWatch\` to log signal value/state changes to the console when active.
- Use \`WeakRef\` mapping and \`FinalizationRegistry\` (\`watchCleanupRegistry\`) so active watches do not retain strong references to signal nodes or prevent garbage collection.
- Expose \`watched\` status on \`DebugSignalGraphNode\` and publish \`toggleWatchSignal\` onto \`window.ng\` in development mode.
- Add acceptance tests covering watch activation, signal mutation logging, manual disposal, GC cleanup, and safe invalid ID handling.
2026-08-24 15:35:44 -07:00
SkyZeroZx 168a324cce fix(core): prevent TransferState prototype pollution
Store transfer state and serialization callbacks in null-prototype dictionaries, and only read values that belong to the store. This keeps special state keys from changing lookup behavior or exposing inherited cache entries.

Fixes #70265
2026-08-24 15:34:07 -07:00
splincode f275d289a0 refactor(router): replace internal any types
Infer URL parameter values as strings and type preloading work as Observable<void> to match the values produced by these internal flows.
2026-08-24 15:27:51 -07:00
Andrew Scott 0d7ae16350 refactor(core): loosen return type of ɵɵFactoryDeclaration to any
When a derived class inherits from a base class, TypeScript enforces that static
properties on the derived class are assignable to those on the base class.
Because `ɵɵFactoryDeclaration<T, ...>` had a return type of `T`, static `ɵfac`
members across inheritance hierarchies could result in type incompatibilities
(for example, when dealing with generics or differing class shapes where the
derived factory return type is not compatible with the base class factory).

Updating the return type of `ɵɵFactoryDeclaration` from `T` to `any` avoids
these static type conflicts across inheriting classes.
2026-08-24 15:26:05 -07:00
Matthieu Riegler e9ba39d671 fix(compiler-cli): Produce correct tcb expression for optional chaining
The semantics of optional chaining changes when there are extra parenthesis. We need to make sure that we do not introduce some unnecessary ones.

fixes #70143
2026-08-24 15:21:17 -07:00
Angular Robot 671d8d395e build: update all non-major dependencies
See associated pull request for more information.
2026-08-24 10:49:59 -07:00
aminesbdev d953d2eda9 docs: add missing tap import and AppConfig interface to environment configuration reference 2026-08-24 10:37:13 -07:00
Kam 5e8a70a010 fix(docs-infra): fail the build on duplicate heading anchors
Two headings on the same page can generate the same anchor id, and every
link to it then resolves to whichever comes first. Nothing caught this
because the route manifest keeps anchors in a `Set`, so a repeat collapses
into one entry before any check runs, and the existing link validation only
asks whether an anchor exists, which a duplicate satisfies.

The generator now inspects each page's headings while they are still an
ordered list and fails with the offending pages and anchors. The scanning
and the duplicate check move to `heading.mts` so they are covered by tests
next to `getIdFromHeading`, which should keep this from coming back.
2026-08-24 10:36:16 -07:00
Kam 698a14d735 fix(docs-infra): give duplicated headings their own anchor ids
Eight guides have headings that generate the same anchor id twice or more.
Because an id resolves to the first element that claims it, the table of
contents lists two entries pointing at the same place, the second section
cannot be reached from the ToC at all, and the copy link button on the
later heading hands out a URL for the earlier one.

On `guide/forms/reactive-forms` the two "Display the component" steps sit
5176px apart and both ToC entries scroll to the first.

Give the later heading on each page an explicit id with the `{#custom-id}`
syntax the pipeline already supports. Only later duplicates are changed, so
every anchor that resolves today keeps pointing at the same heading.
2026-08-24 10:36:16 -07:00
Kam 355a9d83e3 refactor(docs-infra): remove images no longer referenced by any doc
Most of these came over from the angular.io docs and were orphaned as adev
rewrote or dropped the guides that used them. Two are newer: a devtools
screenshot that was added but never referenced, and the logo left behind
when the Firebase Studio launcher was removed. All of them are still
copied into the build and served from angular.dev.

No markdown, template, stylesheet, TypeScript file or build target
references any of them, by filename or through a path built at runtime,
and the images are globbed into the build rather than listed, so nothing
else needs updating. Removes 78 files, 5.15 MB in total, and empties
nineteen directories.
2026-08-24 10:35:41 -07:00
Aleksander Bodurri 434acc506e refactor(devtools): update profiler instruction text for clearing recordings
I suspect this text used to be correct but became outdated after UI changes. These is no "refresh" button.
2026-08-24 10:34:37 -07:00
Kam 3074fdb3ab docs: repair the AST link in the AOT compiler guide
The link to the Wikipedia article on abstract syntax trees spelled the
underscores as asterisks, which 404s. The same link two paragraphs
earlier in this guide is correct.
2026-08-24 10:31:51 -07:00
Kam 39da06138b fix(docs-infra): repair redirects pointing at removed pages
Five redirects sent people to the home page instead of a guide, because
their target no longer exists: the four `guide/ngmodules/*` entries point
at `/guide/ngmodules`, which has no route, and
`guide/animations/transitions-and-triggers` had a plural in a target that
is registered as `transition-and-triggers`.

Two more resolved only after a second hop, redirecting to a path that is
itself a redirect.

The spec only checked that a `redirectTo` starts with a slash, which all
seven satisfied, so it now also checks that the target is a real page and
that it is not another redirect.
2026-08-24 10:31:14 -07:00
Kristiyan Kostadinov c18261f3fd build: enable stableTypeOrdering tsconfig flag
`stableTypeOrdering` was recently enabled in the internal builds. While there aren't any breakages in Angular, these changes enable it so we can catch potential issues earlier.
2026-08-24 10:27:15 -07:00
Lazizbek Ergashev 58b0cb4735 fix(compiler): scope animations declared in minified nested rules
The regular expressions in `_scopeAnimationRule` expect an `animation` or `animation-name` property to be preceded by whitespace or a semicolon, and its value to end at a semicolon. Minified CSS breaks both assumptions. Inside an at-rule the property follows a `{`, and the last declaration of a block has no trailing semicolon, so the closing `}` lands inside the captured value. The keyframe name is then left unscoped while the `@keyframes` rule itself is renamed, so the animation does nothing in a production build.

Accept `{` as a leading boundary and stop the value at `}`. The prefix is written back unchanged, and a declaration value cannot contain an unescaped `}`.

Fixes #70316
2026-08-24 10:25:03 -07:00
Alex Rickabaugh 1e35de536d fix(forms): use dot-access for readonly rule configuration
The readonly rule previously used 'when' in configOrLogic to determine if a configuration object was provided. Under property renaming/minification, the string literal property lookup fails and causes the rule to fall back to being permanently readonly.

This change switches to the dot-access form (configOrLogic?.when), matching hidden() and ensuring property renaming works correctly.
2026-08-24 10:24:31 -07:00
Kristiyan Kostadinov 42f6641120 build: remove scorecard workflow
The scorecard workflow has been replaced at the org level so these changes remove it from our workflows.
2026-08-24 10:24:05 -07:00
aminesbdev 15cf923f88 docs: modernize creating-services reference with signal state and asReadonly 2026-08-24 10:22:10 -07:00
aparziale bdc09e8183 fix(migrations): preserve registerLocaleData calls in standalone bootstrap migration
Currently the standalone migration only copies symbols referenced from the NgModule metadata into the main file. Top-level `registerLocaleData` calls, and the default imports they depend on, are dropped silently when the module file is pruned, which breaks locale resolution at runtime with a "Missing locale data" error.

Copies top-level `registerLocaleData` calls from the file of the bootstrapped module into the main file, and adds support for carrying over default imports which were previously skipped silently by the reference resolution.

Fixes #50886
2026-08-24 10:18:58 -07:00
aparziale 0f0d52e9c1 fix(language-service): honor quote style preference when generating imports
The quick fix and completion auto-import always generated the module specifier with double quotes, ignoring the user's quote preference and the style used in the file.

The generated import now respects the `quotePreference` from the TypeScript user preferences. When the preference is `auto` (or absent), the style is inferred from the first existing import in the file, mirroring TypeScript's own behavior. Files with no imports
keep the previous double-quote default.

Fixes #67108
2026-08-24 10:18:02 -07:00
Kam c6a6ad0a44 refactor(docs-infra): drop exclude patterns for examples that no longer exist
The `embeddable` glob excludes nine example directories. Four of them,
`testing`, `ssr`, `resolution-modifiers` and `dependency-injection`, were
removed by #66753 and #61686 without updating this file, so those patterns
match nothing.

Three of the four sit under a TODO about examples that do not compile. Those
were deleted rather than fixed, which leaves `reactive-forms` and
`form-validation` as the only two the note still applies to.
2026-08-24 10:16:18 -07:00
aminesbdev 719e1ada74 docs: add self-closing tags guideline to components reference 2026-08-24 10:05:35 -07:00
Matthieu Riegler 555369960a ci: add Alan as a zone.js reviewer
This updates the zone.js owners and adds alan-agius4 to compiler approvers.
2026-08-24 10:02:14 -07:00
splincode a4a428ece2 refactor(forms): type async validator subscription
Use the Subscription type returned by Observable.subscribe instead of storing async validator subscriptions as any.
2026-08-24 10:00:19 -07:00
Matthieu Riegler 030779c4f0 build: update dependency magic-string to v1
Magic-string is now ESM only.
2026-08-24 09:59:00 -07:00
Kam 51cb07e980 docs: point the LLM indexes at current pages, not redirects
`llms-list.md` feeds the generated `llms-full.txt`. Three of its DI
entries were left behind by a page rename: two named files that no longer
exist and one named a copy that had been superseded. The generator warns
on an unreadable file and carries on, so the build stayed green while
`llms-full.txt` quietly lost three DI guides and shipped a stale fourth.

`llms.txt` linked the same old URLs, which only reach their pages through
a redirect, plus `guide/hybrid-rendering`, which redirects to `/guide/ssr`
and duplicates the line above it.

Also removes `creating-injectable-service.md`, left behind by that rename
and reachable from nothing.
2026-08-21 12:36:50 -07:00
Kam eb6570b06d refactor(docs-infra): remove the orphaned home animation component
The new home page in #63662 stopped rendering `HomeAnimationComponent`
and its directory was left behind. The animation that runs today lives
in `features/home/animation`, which replaced it.

Nothing references the class, its `adev-home-animation` selector, or any
file in the directory, and no stylesheet or build target pulls it in.
2026-08-21 12:35:12 -07:00
Kam 4997521685 refactor(docs-infra): type the API manifest in the nav entries
`getApiNavigationItems` cast the manifest to `any` behind a TODO waiting
on #66252, which was closed without merging, so the three `any`s it
guarded were never going away on their own.

The real `ApiManifestPackage` cannot be imported here: navigation
entries are built as a standalone `ts_project` so the route generator
can consume them at build time, and it has no dependency on the app
tree. Declaring the handful of fields this file reads gets rid of the
`any`s without touching the build graph.
2026-08-21 12:29:38 -07:00
Lazizbek Ergashev 3438614f5e docs: warn against using in-memory-web-api in production 2026-08-21 12:27:39 -07:00
Kam 238d8bf98a refactor(docs-infra): hold editor diagnostics in a signal
`DiagnosticsState` carried a TODO to move off `BehaviorSubject` once
zoneless was turned on. adev has been zoneless for a while now, with no
zone.js dependency and an empty `polyfills` array, so the condition is
met and the TODO can go.

Both consumers now derive from the signal instead of subscribing. The
Console tab badge becomes a `computed`, and so does the code editor's
error list: the diagnostics are produced by a `linter()` configured with
a 400ms delay rather than arriving as a stream, so the rxjs chain was
only adding a further second before they were displayed. The errors box
stays dismissable through a `linkedSignal`, which reverts as soon as the
diagnostics change.
2026-08-21 11:50:42 -07:00
Kam 593f2d7e2e refactor(docs-infra): share the angular.dev origin constant
The angular.dev origin was declared twice as a local `ANGULAR_DEV`
const and hardcoded inline in two more places. It now lives once in
`core/constants/links.ts`, next to `ANGULAR_LINKS`.
2026-08-21 11:50:02 -07:00
Kristiyan Kostadinov f3c093df24 refactor(compiler-cli): add compiler option for enabling source locations
Adds an internal config options that allows us to enable source locations.
2026-08-21 11:45:30 -07:00
root 2ab5ff56de fix(core): preserve namespace for dynamic component hosts
Forward the insertion namespace when creating dynamic component hosts inside SVG or MathML.
2026-08-21 11:41:59 -07:00
Andrew Scott fa2aca969f refactor(router): add support for blocking router resources
Extends router resource integration to support blocking resources during navigation transitions.
2026-08-21 11:38:19 -07:00
Andrew Scott ad3c16562b docs: add guidelines for Object.create(null) PRs to PR review skill 2026-08-21 11:36:46 -07:00
Kristiyan Kostadinov 7ba082e08e fix(core): avoid prototype member collisions
Switches to using an object with a `null` prototype to avoid collisions.
2026-08-21 11:32:44 -07:00
Kristiyan Kostadinov 862a0c8ab3 fix(common): avoid prototype member collisions
Switches to using an object with a `null` prototype to avoid collisions.
2026-08-21 11:32:44 -07:00
Andrew Scott 779f67777d refactor(core): widen ɵɵclassProp value parameter type to any
When Angular type-checks host bindings and template class bindings
(`[class.foo]="expr"`), the Type Check Block (TCB) emits the binding
as a standalone expression statement (e.g. `(expr);`). This verifies that
the expression itself is syntactically and semantically valid (e.g. properties
exist on the component instance), but does not constrain the
expression to `boolean` because Angular evaluates class bindings using standard
JavaScript truthiness.

In classic `ngtsc`, the runtime Ivy instructions (`ɵɵdefineComponent`) were
generated only during the JS emit phase, after TypeScript type-checking had
completed. Thus, `tsc` never validated the arguments passed to `ɵɵclassProp`.

Under standalone commpilation, Ivy definitions are generated
directly into the TypeScript AST and type-checked by `tsc`. This causes `tsc`
to check the emitted `ɵɵclassProp('foo', expr)` call against the instruction's
declared signature. Because `ɵɵclassProp` was strictly typed as
`boolean | undefined | null`, any valid truthy non-boolean expression
(e.g., `1`, `items.length`, or non-empty strings) produces a `TS2345` compiler error.

At runtime, `ɵɵclassProp` delegates to `checkStylingProperty`, which evaluates
the value via truthiness (`!!value`). Widening the parameter type to `any`
aligns the instruction's type signature with Angular's binding semantics and
prevents type-checking failures during in-place compilation.
2026-08-21 11:26:00 -07:00
Konstantin S. 045746c18c docs(core): clarify toObservable synchronization behavior 2026-08-21 11:22:53 -07:00
Alex Rickabaugh 2e2c426e76 fix(compiler-cli): deduplicate deferred imports across multiple blocks
When @Component.deferredImports is defined as an object mapping block
names to dependency arrays, deduplicate entries when aggregating block
imports into the component-level flattened scope. This prevents
duplicate directive/component collisions in DirectiveMatcher when
multiple @defer blocks share dependencies.
2026-08-21 11:14:47 -07:00
Alex Rickabaugh 7d9f55da11 feat(compiler-cli): scope type-checking of keyed defer blocks
Enforces that components, directives, and pipes in @Component.deferredImports mapped to specific block keys are only used within their designated @defer blocks during template type checking, reporting out-of-band diagnostics when used eagerly or across mismatched blocks.
2026-08-21 11:14:47 -07:00
Alex Rickabaugh b06ff75370 refactor(compiler): support block-specific deferredImports mapping
Allow `@Component.deferredImports` to be an object mapping block names to arrays of dependencies, and support `@defer (name blockName)` syntax to configure block-specific dependency lists in both standard and local compilation modes.

This enables more targeted dependency chunk generation during local compilation instead of over-eagerly loading all deferred imports together.
2026-08-21 11:14:47 -07:00
Doug Parker d609cf6e09 release: bump Angular DevTools version to 1.20.0 2026-08-19 13:22:03 -07:00
Jessica Janiuk 0e8a225f59 release: cut the v22.2.0-next.3 release 2026-08-19 13:18:44 -07:00
Jessica Janiuk 054bc6bc5c docs: release notes for the v22.1.3 release 2026-08-19 13:12:07 -07:00
Jessica Janiuk b7079c590c docs: release notes for the v20.3.29 release 2026-08-19 13:00:49 -07:00
Jessica Janiuk 1dca0588df docs: release notes for the v21.2.21 release 2026-08-19 12:54:15 -07:00
Doug Parker e041f9483e refactor(core): pass AbortSignal to WebMCP tools to handle cancellation
This forwards the `AbortSignal` natively supplied by Chrome 153.0.8009.0's update to `registerTool` executions down into the Angular context. This helps tools gracefully handle execution cancellations initiated by agents or users, preventing unnecessary background work and leaking resources.

We combine the injector/component destruction abort signals with the native WebMCP execution cancellation signals so the underlying tools correctly cancel long-running requests without producing invalid states.

Justification:
https://groups.google.com/a/chromium.org/g/chrome-ai-dev-preview/c/9291sjhIRz0/m/RFuyRrs5AAAJ
2026-08-19 12:41:09 -07:00
Angular Robot 1fb3b58abe build: update cross-repo angular dependencies to v22.2.0-next.2
See associated pull request for more information.
2026-08-19 12:38:52 -07:00
SkyZeroZx 7e39b4aedb docs: update signal forms skill to match the current implementation 2026-08-19 11:07:46 -07:00
Andrew Scott 80a04eba52 refactor(router): add support for non-blocking router resources (#70211)
Introduces support for route-level resources via the `resources` property on route definitions, enabled with `withRouterResources()`.

Router resources provide a reactive, signal-based alternative to resolvers, allowing routes to declare resources tied to route parameters and route lifecycle:
- The `resources` function executes during navigation transitions within an injection context scoped to the route (`_localInjector`).
- It receives a `ResourceContext` containing signals for `params`, `queryParams`, `fragment`, and `data`, alongside the static `snapshot`.
- For newly created routes, `resources` runs once and attaches to `ActivatedRoute.resources`. For reused routes, parameter signals update reactively to trigger new data fetches while keeping resource references stable.
- Wrapped resources (`routerResource`) provide transactional stability: snapshots are frozen during active navigations to prevent UI jitter, unfreezing on `NavigationEnd`.
- On cancelled navigations or errors, rollback recovery retains the frozen snapshot until reverted signals settle, avoiding flashes of loading state. Manual reloads are rejected while frozen.
- Local injectors are automatically cleaned up if navigations are cancelled or rolled back.

Note that this commit only implements non-blocking resources (marked with `nonBlocking()`), deferring blocking resource resolution to future work to keep the initial changeset smaller and less complex.

PR Close #70211
2026-08-19 15:28:08 +00:00
Andrew Scott 99c0f593c4 refactor(router): rename activated route injector feature to resource feature (#70211)
Activated Route Injector feature was developed for resources. This moves the internal
naming (files and vars) to reflect this now that we have landed the core bits of plumbing
and are ready to implement router resources.

PR Close #70211
2026-08-19 15:28:08 +00:00
Kam f35a3961c4 fix(docs-infra): remove stray tokens from adev illustrations
`routing.svg` and `directives.svg` each carry leftover text after a
`stroke-miterlimit` attribute, which has made them invalid XML since the
angular.dev import. They render only because decorative headers inline
them into the page, where the HTML parser turns the leftovers into empty
attributes, but the files fail anywhere they are parsed as XML,
including when served directly as `image/svg+xml`.

Neither drops anything from the drawing. The `directives.svg` leftover
repeats a path the file already declares.
2026-08-19 08:26:30 -07:00
hawkgs 023a63df8c refactor(devtools): unsubs from profiler when the perf track is disabled
Avoid the leak by unsubscribing from the profiler when the performance track is disabled.
Additionally, drop the duplicated performance track state flag.
2026-08-19 08:26:01 -07:00
Matthieu Riegler c819880b91 fix(forms): report forbidden 2way bindings on when FormField is applied
We were already reporting regular bindings but not 2way.

fixes #70219
2026-08-18 17:08:02 -07:00
Jessica Janiuk da9f3e2118 Revert "fix(compiler): preserve &ngsp; between sibling control flow blocks"
This reverts commit 53fc371142.
2026-08-18 17:03:06 -07:00
arturovt 53fc371142 fix(compiler): preserve &ngsp; between sibling control flow blocks
`findConnectedBlocks` scans siblings after an `@if` to collect connected
`@else`/`@else if` blocks. Whitespace-only text nodes encountered during
the scan were eagerly added to `processedNodes`, marking them as "do not
emit", before confirming whether a connected block actually followed.

By the time `findConnectedBlocks` runs, `WhitespaceVisitor` has already
converted `&ngsp;` (and `&nbsp;`) into a plain space character, making
them indistinguishable from insignificant whitespace via `.trim().length`.
When the next sibling was a second, unrelated `@if` instead of `@else`,
the scan stopped but the text node was already silently dropped.

Fix by deferring the `processedNodes` insertion into a pending buffer and
only committing those nodes once a connected block is confirmed to follow.

Fixes #55791
2026-08-18 15:52:42 -07:00
arturovt c2b14b7ab4 fix(core): prevent orphaned requestIdleCallback handle from re-entrant scheduling
Defer blocks that ask to run during browser idle time with the same
options get grouped into one batch ("bucket") and processed together
the next time the browser is idle. While that batch runs, if one
callback (or the change-detection check right after it) asks to
schedule more idle work for that same batch, Angular used to register
a second idle-callback handle instead of reusing the one already in
flight.

That second handle became orphaned. By the time the batch finished
running, its queue was empty, so Angular threw away the bookkeeping
for it — including the only reference that could have cancelled the
handle. If the app was destroyed before the browser got around to
calling it, it fired anyway, against a scheduler that no longer
existed.

The root cause was a marker (idleId) that IdleScheduler uses to know
"I already have a browser callback pending for this batch, don't
request another one." That marker was being cleared to null right at
the start of processing a batch, before any of its callbacks had
actually run. So a callback that re-entrantly asked to schedule more
work mid-batch saw "nothing pending" and requested a redundant handle.

The fix: leave the marker set for the entire batch instead of clearing
it up front. Only clear it once every callback in the batch has run —
at that point, if there's leftover work, it's safe to request a fresh
handle for it.

Added a test for the basic re-entrant scenario, plus five more for
edge cases: a re-entrant add() into a different batch, and re-entrant
remove() of a sibling callback that hasn't run yet versus one that
already has.
2026-08-18 15:52:07 -07:00
Aleksander Bodurri 0ddbc47e7f fix(core): expose debuggableFn for non-computed signal graph nodes
Before: getSignalGraph only exposed debuggableFn on computed and template
nodes, preventing DevTools from linking to source code for other node types.

After: debuggableFn is also populated for linkedSignal, effect, and
afterRenderEffectPhase nodes.
2026-08-18 15:51:26 -07:00
Sam Severance bb1274d6ac fix(docs-infra): refactor unit test
Replace two toContain assertions with a single toMatch
using a multiline regex, per reviewer suggestion.
2026-08-18 15:45:30 -07:00
Sam Severance 7febf40e0d fix(docs-infra): fix code block deindentation in docs-code and docs-code-block
Consolidates deindentation logic into formatCode so both docs-code and
docs-code-block are covered by a single fix. The original deindent
function incorrectly iterated over blank lines instead of non-blank
lines when computing minimum indentation, causing code blocks to render
with excessive leading whitespace. Also fixes region extraction which
had the same trim-before-deindent ordering issue.
2026-08-18 15:45:30 -07:00
arturovt f1a4c85212 fix(router): pass correct component to canDeactivate for named outlets in componentless parent routes
When a componentless parent route has children rendered into a named outlet
(e.g. `outlet: 'inner'`), the `canDeactivate` guard received `null` as the
component argument instead of the actual component instance.

The bug was in `deactivateRouteAndItsChildren`: for componentless routes,
each child was passed the same `context` inherited from the parent lookup,
which was `null` when the parent component only registered a named outlet.
The children's actual outlet contexts were never consulted.

The fix adds a `parentContexts: ChildrenOutletContexts | null` parameter so
that for componentless routes, each child is looked up by its own outlet name
in `parentContexts` (e.g. `parentContexts.getContext('inner')`). For
component routes, `context.children` is passed as the new `parentContexts`
on recursion, ensuring correct context resolution across component boundaries.

This re-addresses #34614. A previous fix (#36302) was reverted because it
passed `parentContexts` unchanged through component boundaries; this fix
updates `parentContexts` to `context.children` when descending into a
component route, preventing the wrong contexts from propagating into deeper
componentless levels.

Fixes #34614
2026-08-18 15:04:56 -07:00
brysonbw 2720362818 feat(router): add containsTree as public API
Export containsTree from @angular/router to enable direct UrlTree subset matching.
2026-08-18 12:08:17 -07:00
Matthieu Riegler d364c83c44 ci: exclude markdown files from requiring dev-infra review
613c51e wasn't enough to exclude dev-infra
2026-08-18 09:38:45 -07:00
Kristiyan Kostadinov 732e505018 fix(core): replace all hasOwnProperty usages with Object.hasOwn
We keep getting PRs that target single usages of `hasOwnProperty` and we have ~100 of them. These changes aim to address the issue centrally by swapping out all the instances and adding a lint rule against introducing new ones.
2026-08-18 09:17:18 -07:00
Angular Robot 48a0fd6e8a feat(compiler): allow template to access private props
To allow this we'll catch the errors during typechecking and discard it.

As context, when setting `isolatedDeclarations: true` this requires developers to explicitly type every property but the `private` ones. By allowing private properties to be used in templates we discard the actually for explicit typing for template only properties.
2026-08-18 09:01:25 -07:00
arturovt c658d73210 fix(core): stop running further effects once one destroys the view mid-flush
When a view has more than one effect scheduled to run, and one of them
destroys the view (e.g. by calling `componentRef.destroy()`), the
remaining effects in that same flush could still run afterward,
against a view that no longer exists. In some cases this crashed
outright with `TypeError: view[EFFECTS] is not iterable`.

Here's why: `runEffectsInView` walks a view's effects in a `for...of`
loop, wrapped in an outer `while` loop that re-checks for any effects
that became dirty as a side effect of ones that already ran. When an
effect destroys its view, `view[EFFECTS]` gets set to `null` as part
of tearing the view down.

First attempt checked for that inside the `for...of` loop, before each
effect runs. That covers a sibling effect later in the *same* pass,
but misses a second case: if the effect that destroys the view *also*
dirties another effect on that same view in the process (e.g. by
writing a signal the sibling depends on), the outer `while` loop sees
`HasChildViewsToRefresh` set and tries to restart — and immediately
crashes re-entering `for (const effect of view[EFFECTS])` on a
now-null value, before the in-loop check ever gets a chance to run.

Reproduced that exact crash with a test first: two effects on one
view, the second one writes a signal the first depends on and then
destroys the view in the same call — confirmed it throws before
touching the fix.

Fixed by checking right after `effect.run()` instead of before it,
covering both cases in one place: the remaining effects in the current
pass, and the loop trying to restart afterward. As soon as one effect
destroys the view, nothing else runs against it again.

This is intentionally narrow in scope. An earlier version of this fix
also tried to guarantee that `onCleanup()` callbacks still ran even
when registered after an effect destroyed its own view. That's been
dropped — destroying your own view and then continuing to register
more work for it isn't something the framework should have to paper
over. If you need to do both, register `onCleanup` first, then
destroy.
2026-08-18 08:59:13 -07:00
aminesbdev 560d3f0412 docs: update naming-conventions skill 2026-08-18 08:58:44 -07:00
Alon Mishne 292991e2df Revert "fix(router): limit protocol-relative URL handling to serialization"
This reverts commit 435f8b2b8b.
2026-08-18 08:58:13 -07:00
Angular Robot 48831fc5fe build: lock file maintenance
See associated pull request for more information.
2026-08-18 08:54:49 -07:00
Matthieu Riegler 613c51e324 ci: add local skills to the fw-general scope
They were caught by dev-infra which isn't the best scope for the imho
2026-08-18 08:54:16 -07:00
arturovt 46d2cb7ff0 fix(common): preserve literal key union in KeyValuePipe.transform()
Previously, when you passed an object typed like
Record<'a' | 'b', number> into the `keyvalue` pipe, TypeScript would
"forget" that the keys could only ever be 'a' or 'b', and just tell
you the key was a plain `string` instead. So code like this used to
fail to compile, even though it's correct:

```ts
  const input: Record<'a' | 'b', number> = {a: 1, b: 2};
  const result = pipe.transform(input);
  const key: 'a' | 'b' = result[0].key; // error: string is not 'a' | 'b'
```

This happened because the pipe has multiple overloaded versions of
transform(), and TypeScript checks them top to bottom, using the
first one that matches. The "number keys" overload was listed first,
and it happened to also match string-keyed objects by accident, so
it "won" before the correct "string keys" overload ever got a
chance to run.

The fix just reorders those two overloads so the string-keys one is
checked first. Nothing about runtime behavior changes — objects with
actual numeric keys (e.g. Record<1 | 2, string>) still correctly
report their keys as plain `string`, matching what Object.keys()
really returns at runtime.
2026-08-17 15:01:24 -07:00
Jaime Burgos 6f9a6bea50 fix(platform-browser): disallow event handler attributes in Meta
Prevent arbitrary MetaDefinition properties from writing on* handlers directly to meta elements. Browser events can execute these handlers, including on meta elements rendered in the document body.
2026-08-17 14:59:50 -07:00
Shayan 3f8d9d6ea6 fix(language-server): recover project for external templates in solution-style workspaces
In a composite/solution-style workspace (e.g. an Nx monorepo, where an
app's tsconfig.json only contains project references), TypeScript can
never resolve a config file for an HTML file, since HTML files are not
listed in any referenced project. angular/vscode-ng-language-service#2165
worked around this in onDidOpenTextDocument by briefly opening the
sibling TS file so the right project loads when a template is opened
first.

However, getDefaultProjectForScriptInfo - the recovery path used by
getLSAndScriptInfo and onDidChangeTextDocument when a script info has no
configured project - did not receive the same workaround. When an open
template loses its project association (e.g. its component file is
closed and the project graph updates), every subsequent request on the
template fails with "No config file" and returns null indefinitely,
until the user manually reopens the component file.

Apply the same sibling-TS best effort in getDefaultProjectForScriptInfo,
and additionally attach the template's script info to the configured
project of its component when the config lookup still comes back empty
(openClientFile does not repeat the config lookup for already-open
files).

Also skip the sibling lookup when the .ts file does not exist, so
non-component HTML files (e.g. src/index.html) do not trigger an
open/close and config search that cannot succeed.

Fixes #69768
2026-08-17 14:59:02 -07:00
Nikita Barsukov 182c371d82 docs: use transformedValue in Forms | Custom Controls | Value transformation section
The "Value transformation" section taught readers to hand-roll
transformation with `linkedSignal()` and a manual parse method, even
though `@angular/forms/signals` ships `transformedValue()` for exactly
this case. Readers ended up with a weaker version of a feature the
framework already provides — notably, no parse error reporting.

Rewrite the section around `transformedValue()` and document the parts
the manual pattern could not cover: returning `{error}` from `parse` to
surface parse errors on the field's `errors()`, and `reset()` clearing
them. This also makes good on the cross-reference from the validation
guide, which pointed here for parse error details the section never
covered.

Fixes #70206

Co-authored-by: Matthieu Riegler <kyro38@gmail.com>
2026-08-17 14:58:14 -07:00
splincode d35c17d393 refactor: correct typos in comments, docs, and error messages
Fix misspellings found across multiple packages:

- `paramters` → `parameters` (utils.spec.ts)
- `directve` → `directive` (typecheck/context.ts)
- `subscriper` → `subscriber` (zone.js rxjs test)
- `swich` → `switch` (adev animation parser test)
- `subscribtion` → `subscription` (forms/abstract_model.ts)
- `lifecyle` → `lifecycle` (ng-devtools-backend hooks)
- `compatability` → `compatibility` (tree-visualizer.ts)
- `indentifier(s)` → `identifier(s)` (compiler-cli shared.ts, i18n_helpers.ts, declaration_only_emission_spec.ts)
- `identifer` → `identifier` (platform-browser shared_styles_host.ts)
- `prcess` → `process` (standalone-migration to-standalone.ts)
2026-08-17 14:57:04 -07:00
splincode b10021c79b refactor(localize): replace any with unknown in messages and translations utils
Replace unsafe `any` type annotations with `unknown` across the localize
utility layer to improve type safety and catch potential type errors at
compile time rather than at runtime.

Changes in `messages.ts`:
- `ParsedMessage.substitutions`: `Record<string, any>` → `Record<string, unknown>`
- `parseMessage` parameter `expressions`: `readonly any[]` → `readonly unknown[]`
- Local `substitutions` variable: `{[key: string]: any}` → `Record<string, unknown>`

Changes in `translations.ts`:
- `isMissingTranslationError` parameter: `any` → `unknown`, with proper
  narrowing (`typeof e === 'object' && e !== null`) before property access
- `MissingTranslationError.type` visibility: `private` → `readonly` to allow
  access through the narrowed `unknown` type in the type guard
- `translate` parameter and return type: `readonly any[]` → `readonly unknown[]`
- `makeTemplateObject` cast: `cooked as any` → `cooked as unknown as TemplateStringsArray`

Fix in `mock_message.ts` (test helper):
- `substitutions: []` → `substitutions: {}` — the array literal was only
  assignable because the field was typed as `any`; the correct empty value
  for a `Record<string, unknown>` is an object literal
2026-08-17 14:56:31 -07:00
arturovt 38d093232c fix(forms): warn in dev mode when ngModel cannot reach parent NgForm across component boundary
NgModel injects ControlContainer with @Host(), which stops the injector at
the component host element boundary. When NgForm lives in a parent component
and ngModel lives in a child component, the injection returns null silently
and the control acts standalone — never registering with the form.

To surface this invisible failure, emit a dev-mode warning (NG01354) when
ngModel's @Host() injection finds nothing but the element Injector can still
reach a ControlContainer further up the hierarchy. The warning identifies the
cross-boundary issue and points developers to the viewProviders fix or the
standalone option.

Adds the NG01354 reference page explaining why the warning fires and providing
two remediation paths: bridging ControlContainer via viewProviders, or opting
out with [ngModelOptions]="{standalone: true}".

Fixes #47580
2026-08-17 13:59:40 -07:00
Vincent 70b252cac3 test(core): Add test to ensure Angular correctly detects paths when served from file system
This test makes sure that when Angular is served from Electron or just from the file system that the router works correctly.
2026-08-17 13:58:54 -07:00
Jaime Burgos 45ebb127e3 refactor(compiler-cli): add error guide links to diagnostics
Add the error guide URL when a compiler diagnostic uses a negative
marked error code.
2026-08-17 13:58:11 -07:00
arshiya tabasum c73a001fbf fix(animations): detect object trigger values with Object.hasOwn
StateValue and AnimationTransitionNamespace.trigger detect the {value,
params} object form of a trigger binding by calling hasOwnProperty on the
bound value. When that value is an object from untrusted data (for example
a parsed JSON payload) carrying an own hasOwnProperty key, the shadowed
property is called as a method and throws, breaking the animation flush.
Use Object.hasOwn for the check so a shadowing key no longer matters.
2026-08-17 13:53:17 -07:00
SkyZeroZx 1a006a8f97 fix(http): cancel oversized fetch response bodies
Cancel the unread response body before reporting NG02825 when its declared Content-Length exceeds the configured buffer limit. Without cancellation, SSR can finish while the underlying connection remains open.

Add regression coverage for the declared-length rejection path.
2026-08-17 13:52:12 -07:00
Matthieu Riegler ed3373868d refactor(language-service): adapt strict template suggestion
In v22, `strictTemplates` is true by default. We need to adapte the LS to online report the suggestion when the option is explicitly `false`
2026-08-17 13:51:43 -07:00
Kam 064530447e feat(docs-infra): add Angie to the routing illustration
The routing header draws a serpentine route with map pins on it but
nobody travelling it. Angie now stands on the top segment, the same way
she stands on the road in the roadmap header.

She needs more headroom than the canvas had, so the viewBox gains 12
units at the top and the illustration carries its own max-height, which
keeps the rest of the drawing at the size it rendered before.
2026-08-17 13:46:46 -07:00
SkyZeroZx 3e7094b049 docs: correct Angular developer skill guidance 2026-08-17 13:44:50 -07:00
Evgenii Fomin 8d6c6a9a0c docs: make a note regarding i18n messageIdFormat 2026-08-17 13:44:22 -07:00
Kam 1c2b0575ed feat(docs-infra): add Angie to the roadmap illustration
The roadmap header draws a road between a start pin and a destination
pin, but nothing travels it. Angie already appears on the 404 page, the
embedded editor states and the error snack bar, so put her on the road
too.

Decorative headers are inlined into the page so the road can use CSS
variables for dark mode, which means the pose has to be embedded as
paths, with its classes flattened and its ids prefixed to keep them out
of document scope.

Note this asset is also the essentials Next Steps header.
2026-08-17 13:33:29 -07:00
hawkgs 9711e86f2a refactor(devtools): collapse injected services section in the side pane by default
Collapse the section since it takes a significant portion of the vertical space,
whereas most of the times users are exploring the properties leading them to scroll.

Addiotionally, introduce some other minor UI improvements.
2026-08-17 13:30:46 -07:00
hawkgs f746463caa fix(devtools): breadcrumbs missing nav arrows
Show the missing nav arrows when you select a deeply nested component where
the breadcrumbs path is longer than the container. The nav arrows used to
appear only when the split is resized. Add some other minor improvements.
2026-08-17 13:29:05 -07:00
Aleksander Bodurri 2a2585708a fix(devtools): clarify disabled View Source tooltip and update demo app
Before: The View Source button tooltip in signal details static ('View source') even when disabled.

After: The tooltip explains 'Source location is not available for this node' when disabled.
2026-08-17 13:28:27 -07:00
Aleksander Bodurri a1fd3c45f9 fix(core): expose debuggableFn for non-computed signal graph nodes
Before: getSignalGraph only exposed debuggableFn on computed and template
nodes, preventing DevTools from linking to source code for other node types.

After: debuggableFn is also populated for linkedSignal, effect, and
afterRenderEffectPhase nodes.
2026-08-17 13:28:27 -07:00
SkyZeroZx ea3097c774 docs: add example for conditional redirects in routing 2026-08-17 13:26:49 -07:00
Angular Robot 544d084c34 docs: update cross-repo adev docs
Updated Angular adev cross repo docs files.
2026-08-17 13:24:20 -07:00
Angular Robot 5980138246 docs: update cross-repo adev docs
Updated Angular adev cross repo docs files.
2026-08-17 11:27:16 -07:00
hello 789c9c242d docs: update Angular MCP skill reference 2026-08-17 10:43:11 -07:00
Matthieu Riegler 28f323a578 ci: update github nick
ethan changed his nick, this was causing some pullapproves errors when a pr hit the primitives-shared scope.
2026-08-17 10:40:39 -07:00
Aleksander Bodurri a3a7764975 fix(devtools): prevent duplicate highlightComponent listener in injector tree
Move the message bus subscription out of afterRenderEffect into constructor initialization and clean up the listener on destroy using DestroyRef.
2026-08-17 10:37:40 -07:00
Angular Robot 3afe9e6fa3 build: update pnpm to v11.22.0
See associated pull request for more information.
2026-08-17 10:35:40 -07:00
Angular Robot ec678c0f8d build: update dependency ng-packagr to v22.2.0-next.3
See associated pull request for more information.
2026-08-14 09:29:45 -07:00
arturovt c6e4a36be1 fix(core): throw a descriptive error instead of crashing when a hydration node is missing
locateOrCreateElementNodeImpl looks up the DOM node for an element
during hydration and immediately checks its nodeType. If the
client-rendered DOM has fewer nodes than the server-rendered HTML,
the lookup returns null, and in production that null flows straight
into the nodeType check and crashes with a raw, uncoded
"Cannot read properties of null (reading 'nodeType')" TypeError.

The dev-mode check that would normally catch this (validateMatchingNode)
already handles a missing node, but it's compiled out of production
builds, so the crash only shows up outside of dev mode.

Add a null check ahead of the nodeType check that throws a coded
RuntimeError using the existing HYDRATION_MISSING_NODE (NG0502) code,
with a descriptive message in dev mode and a cheap fallback in
production. Also add a regression test that removes a server-rendered
element before hydration runs and asserts a coded RuntimeError is
thrown instead of a raw TypeError.
2026-08-14 08:30:49 -07:00
arturovt 1cb3d606bf fix(platform-browser): throw a descriptive error when insertBefore reference node is missing
Angular's internal LView/TNode bookkeeping can get out of sync with the
real DOM: manual DOM manipulation, a browser extension, or an edge case in
Angular's own view-insertion/reordering code can all leave Angular believing
a node is still attached at a given position when it isn't. The next time
Angular's renderer calls `insertBefore` relative to that stale reference
node, the native DOM API throws an opaque `NotFoundError` with no indication
of which node or component was involved, making these errors effectively
undebuggable in production:

    NotFoundError: Failed to execute 'insertBefore' on 'Node': The node
    before which the new node is to be inserted is not a child of this node.
        at Node.insertBefore (native)
        at DefaultDomRenderer2.insertBefore (packages/platform-browser/src/dom/dom_renderer.ts)
        at nativeInsertBefore (packages/core/src/render3/dom_node_manipulation.ts)
        at nativeAppendOrInsertBefore (packages/core/src/render3/dom_node_manipulation.ts)
        ... (called while Angular inserts or moves a view during change detection)

Check the reference node's actual parent against the expected parent before
calling the native `insertBefore`, and throw a descriptive `RuntimeError`
(NG05106) instead, following the same pattern already used for hydration
node mismatches.
2026-08-14 08:29:59 -07:00
Edu 12b0acd498 feat(devtools): implement persistent breakpoints on signal consumers using CDP
Implements persistent breakpoints for Angular signal consumers (computeds and effects) in Angular DevTools using Chrome DevTools Protocol (CDP).

- Adds debugger permission to Chrome and Firefox extension manifests.
- Implements CDP breakpoint orchestration in background script.
- Updates SignalDetailsComponent and SignalGraphPaneComponent with label icon toggle and persistent state sync across reloads.
2026-08-14 08:26:55 -07:00
Edu 6bfbfcc3a1 refactor(core): expose debuggableFn for effects in signal debug graph
Exposes the internal fn reference of effect reactive nodes on DebugSignalGraphNode as debuggableFn. This allows Angular DevTools to inspect and set breakpoints on effect callbacks.
2026-08-14 08:26:55 -07:00
Kristiyan Kostadinov 262fba7f7d build: resolve CI failures
Resolves some e2e failures that only happened on CI.
2026-08-14 08:22:34 -07:00
Kristiyan Kostadinov 7b74bee5b4 build: switch adev tests away from protractor
Reworks the tests in adev not to depend on Protractor.
2026-08-14 08:22:34 -07:00
Kristiyan Kostadinov 204265f9a3 build: switch examples away from protractor
Reworks the `examples` tests not to depend on Protractor.
2026-08-14 08:22:34 -07:00
Kristiyan Kostadinov 413c79c10b build: switch core tests away from protractor
Reworks the tests under `core` not to depend on Protractor.
2026-08-14 08:22:34 -07:00
Kristiyan Kostadinov 3d43067e20 build: switch playground away from protractor
Reworks the `playground` tests not to depend on Protractor.
2026-08-14 08:22:34 -07:00
Kristiyan Kostadinov 4d2dc1fb08 build: add webdriver build rule
Sets up a build rule to use Selenium Webdriver for e2e tests.
2026-08-14 08:22:34 -07:00
Angular Robot 6c6141b0ad build: update cross-repo angular dependencies
See associated pull request for more information.
2026-08-14 08:20:48 -07:00
Angular Robot d3d3bc62ea build: update cross-repo angular dependencies
See associated pull request for more information.
2026-08-13 13:35:29 -07:00
Jaime Burgos ba13a3ce22 fix(vscode-extension): prevent command URI injection in TSDK approval
VS Code parses notification text for links and permits `command:` URIs. Interpolating the workspace-controlled TSDK path can therefore let a malicious path add a command link to the approval prompt.

Remove the path from the notification instead of attempting to sanitize or escape it. This keeps the prompt static and matches VS Code's TypeScript extension workspace-version approval flow.

Fixes #70176
2026-08-13 13:34:36 -07:00
Aristeidis Bampakos 144b90c907 docs: add code style in Antigravity file reference 2026-08-13 13:33:41 -07:00
Alex Rickabaugh dc65e3656f fix(core): accept readonly arrays for setClassMetadata decorators
When reflection metadata is emitted via setClassMetadata, passing readonly arrays or const tuples for the decorators parameter causes TypeScript type checking errors because setClassMetadata previously expected decorators to be a mutable any[] or null.

This change updates the setClassMetadata type signature to accept decorators as readonly any[] or null and casts the parameter internally when mutating the class metadata property.
2026-08-13 13:33:07 -07:00
Alex Rickabaugh eee9ef4d09 fix(core): allow readonly arrays in RawScopeInfoFromDecorator
When components pass  arrays or readonly tuples to decorator metadata fields (such as ), typechecking generated decorator reflection metadata causes a TS2322 type mismatch error because  previously only accepted mutable .

This change updates  to accept , allowing  arrays and readonly tuples to be assigned without TypeScript compilation errors.
2026-08-13 13:33:07 -07:00
arturovt 4560f4fdcd fix(core): throw NG0500 instead of a raw TypeError on element hydration mismatch
When hydration locates the DOM node for an ɵɵelementStart/ɵɵdomElementStart
instruction, locateOrCreateElementNodeImpl assumed the located node was
always an Element and called hasSkipHydrationAttrOnRElement(native), which
does native.hasAttribute(...). The check that would normally catch this
class of mismatch, validateMatchingNode, is gated behind `ngDevMode &&` and
is compiled out of production builds. So when a real SSR/hydration
structural mismatch located a Text or Comment node instead of the expected
Element, production builds hit .hasAttribute on a node type that doesn't
have it and crashed with a raw, uncoded TypeError instead of a coded
hydration-mismatch RuntimeError.

Add a cheap, always-on nodeType check ahead of that call. On mismatch it
throws RuntimeError(HYDRATION_NODE_MISMATCH, ngDevMode && '...'), the same
pattern used elsewhere in the codebase, so the descriptive message is only
built in dev mode and production keeps throwing just the bare NG0500 code
without pulling validateMatchingNode's DOM-printing machinery into the
production bundle (verified via the bundling/hydration golden-symbols test,
which is unchanged).
2026-08-13 13:32:34 -07:00
Alon Mishne 5ed071d39d docs: release notes for the v22.1.2 release 2026-08-13 12:05:55 -07:00
Alon Mishne 40d23fa3ff release: cut the v22.2.0-next.2 release 2026-08-13 11:46:28 -07:00
Alon Mishne 044e69c1c4 docs: release notes for the v20.3.28 release 2026-08-13 10:14:06 -07:00
Angular Robot 16e42c39c1 build: update all non-major dependencies
See associated pull request for more information.
2026-08-13 09:05:24 -07:00
Bhuvansh855 647bf8e143 docs: note SafeResourceUrl audio src change 2026-08-13 08:55:03 -07:00
Angular Robot ab38005c6e build: update dependency node to v24
See associated pull request for more information.
2026-08-13 08:50:41 -07:00
Andrew Scott 6f848db435 build(vscode-extension): dynamically resolve upstream remote and use token for https pushes
Previously, the release script hardcoded the upstream repository URL as an unauthenticated HTTPS URL (https://github.com/angular/angular.git). Although the script verified that a GITHUB_TOKEN environment variable was present, it only used that token for REST API calls (such as creating the GitHub release) and did not provide it to Git commands. As a result, users who authenticate to GitHub via SSH (and do not have an HTTPS Git credential helper configured) were prompted interactively for GitHub login credentials when pushing the release tag.

This change dynamically resolves the upstream remote name from the user's configured remotes by checking for any remote pointing to angular/angular. When pushing over SSH, Git uses the user's existing SSH credentials. When pushing over HTTPS (or falling back), the script injects GITHUB_TOKEN into the push URL to prevent interactive authentication prompts.
2026-08-13 08:38:53 -07:00
Angular Robot e6be990f77 build: update all github actions
See associated pull request for more information.
2026-08-13 08:36:30 -07:00
Angular Robot 3002a24192 build: update dependency tar.bzl to v0.10.8
See associated pull request for more information.
2026-08-13 08:34:12 -07:00
Angular Robot b7ed8d81ac build: lock file maintenance
See associated pull request for more information.
2026-08-13 08:28:25 -07:00
Alon Mishne c9990c4354 docs: release notes for the v21.2.20 release 2026-08-12 17:08:53 -07:00
Angular Robot bf1f63476d build: update cross-repo angular dependencies to v22.2.0-next.1
See associated pull request for more information.
2026-08-12 16:13:42 -07:00
Angular Robot da8b7de52a build: update pnpm to v11.21.0
See associated pull request for more information.
2026-08-12 16:13:06 -07:00
Andrew Scott e8aa222e7d fix(compiler-cli): correctly resolve symbol for SafePropertyRead in chained optional navigation
When resolving template symbols for SafePropertyRead in TCBs emitted with optional chaining (strictSafeNavigationTypes: true), SymbolBuilder falls back to finding a TS node matching the AST expression's nameSpan. It then traverses up through parent nodes to find the enclosing expression.

Previously, the traversal loop checked isAccessExpression(node.parent) without verifying whether node was the accessed member name or the expression receiver. When multiple optional navigation expressions are chained (e.g. route?.data?.['icon']), the parent of ((route)?.data) is an access expression where ((route)?.data) is the receiver. Because isAccessExpression was true, the loop continued ascending into the outer access expression, causing symbol resolution for data to erroneously return the symbol and TCB location of icon.

This commit refines the parent traversal condition so that it only climbs into a parent PropertyAccessExpression if node is the accessed name (node.parent.name === node), preventing escape into outer receiver expressions.
2026-08-12 16:11:23 -07:00
SkyZeroZx a26fbfa641 refactor(core): distinguish animations that share a name
animate.leave waits for the longest-running animation before removing an element. When multiple animations use the same keyframe name, their animationend events have the same animationName, so a shorter animation can be mistaken for the longest one and remove the element too early.

Track the longest animation duration returned by getAnimations() and compare it with the duration from event.animation when available. Keep the existing name/property checks as a fallback for older browsers and computed-style detection.

Allow a 1ms tolerance for rounding differences between CSSOM and Web Animations and add a regression test using values equivalent to fractional calc() durations.
2026-08-12 16:09:27 -07:00
Andrew Scott c111f3fb0c refactor(router): Create transactional router resource
This commit adds an implementation of a router resource (not currently exposed for public use) which
defines the behavior of a resource dependent on the Router navigation lifecycle.
2026-08-12 16:07:05 -07:00
Andrew Scott e53bb2bca7 release: bump VSCode extension version to 22.1.0 (#70171) (#70172)
Co-authored-by: Alon Mishne <3197814+amishne@users.noreply.github.com>
Co-authored-by: Alon Mishne <amishne@users.noreply.github.com>
2026-08-12 16:05:05 -07:00
Doug Parker 9810a3e33c release: bump Angular DevTools version to 1.19.1 2026-08-12 19:47:21 +00:00
Jessica Janiuk 3d32db6a49 docs: clarify animate.leave child animation behavior
This updates the docs to be explicit about `animate.leave` and nested element removal order. It clearly specifies that `animate.leave` will only fire nested animations within the same component template.

closes: #70131
2026-08-12 11:07:14 -07:00
SkyZeroZx 870a525bd3 test(platform-server): clean up viewport hydration observer
Trigger routed viewport hydration so its cached observer is removed before later randomized specs run.
2026-08-12 11:04:40 -07:00
Matthieu Riegler f1c0c405c9 refactor(compiler): remove explicit strict: true
This flag is set by default in TS 6.0
2026-08-12 11:03:53 -07:00
SkyZeroZx 435f8b2b8b fix(router): limit protocol-relative URL handling to serialization
Preserve createUrlTree command semantics, including custom serializer inputs, while keeping the single-leading-slash guarantee at the default serialization boundary.

Expand coverage for command forms, public UrlTree values, secondary outlets, and preserved query parameters and fragments.

Fixes #69700
2026-08-11 12:07:31 -07:00
Andrew Scott b65dea4f03 feat(router): allow throwing RedirectCommand to trigger redirects
This allows developers to throw a `RedirectCommand` directly from guards and resolvers to trigger a redirect.

The primary benefit is that we no longer need to pollute the return type of functions that redirect. For example, a deeply nested helper function or a resolver can now simply throw a `RedirectCommand` to short-circuit and redirect, instead of having to return the `UrlTree` or `RedirectCommand` all the way up the call stack.

This aligns with prior art in other modern framework routers (such as Next.js, Remix, and SvelteKit), which commonly use thrown exceptions or special redirect responses to abort execution and trigger immediate redirection.
2026-08-11 10:03:43 -07:00
Kam dd29713828 fix(docs-infra): contain scroll within adev dropdown menus
Scrolling to the top or bottom of a dropdown let the scroll event chain into
the page behind it, causing the whole app to scroll. #70137 fixed this for the
version picker, but the same containment was missing on every other menu.

The API reference filter, the tutorial step list and the update guide's version
dropdowns already had a scroll container, so they only needed
overscroll-behavior.

The social and theme mini-menus had neither a height cap nor overflow, so
overscroll-behavior alone would have been inert on them. They now share the
version picker's max-height/overflow-y/overscroll-behavior, hoisted onto
.adev-mini-menu, which also keeps their items reachable when the viewport is
too short to fit the whole menu.
2026-08-11 10:00:53 -07:00
Georgi Serev 66c5ea7544 refactor(devtools): improve profiler ux
- Add info tooltips that describe the visualizations and other
parts of the UI
- Improve the frame selector by adding axes labels and a
horizontal line for frames exceeding 60 fps
- Use a precise frame rate calculations instead of approximations
- Introduce improvements to the details panel
- Improve the bar chart visualization
- And more
2026-08-11 10:00:02 -07:00
Alex Rickabaugh 8925a4ee49 fix(core): ensure i18n_util hasOwnProperty checks are safe for property renaming
`getTIcu` and `setTIcu` performed `hasOwnProperty` checks with string
literals (`'currentCaseLViewIndex'` and `'tView'`). Under Closure Compiler
property renaming optimizations, these properties are minified but the
string literals are not, causing property lookups and type discriminations
to fail.

This commit defines closure-safe property constants using
`getClosureSafeProperty` so the property names are renamed consistently.
2026-08-10 15:35:38 -07:00
splincode 8e41e97dfe docs: update Angular Aria roadmap status
Update the roadmap to reflect that Angular Aria became stable in Angular v22 while development of new patterns continues.
2026-08-10 15:34:51 -07:00
Angular Robot 4fc6e319bf build: update dependency @types/jsdom to v30
See associated pull request for more information.
2026-08-10 09:43:40 -07:00
Angular Robot 78c0cdd80d docs: update cross-repo adev docs
Updated Angular adev cross repo docs files.
2026-08-10 09:42:52 -07:00
yamanerkam 768c8c29dc fix(docs-infra): contain scroll within version picker dropdown
Scrolling to the top or bottom of the version picker's dropdown list
let the scroll event chain into the page behind it, causing the whole
app to scroll. The search dialog and search history dropdowns already
guard against this with overscroll-behavior: contain; apply the same
fix to the version picker.
2026-08-10 09:11:18 -07:00
Angular Robot b95f9e2691 build: update cross-repo angular dependencies
See associated pull request for more information.
2026-08-10 08:57:16 -07:00
Kristiyan Kostadinov 565dbb2fe5 fix(core): incorrect loop in defer blocks
Fixes that we were running a `for in` loop on an array and silently doing the wrong thing.

Fixes #70113.
2026-08-07 16:35:03 -07:00
Ady Elouej 107f6fa49d fix(compiler): remove namespaced MathML script elements
Treat MathML-namespaced script elements as scripts during template preprocessing. This prevents scripts nested in MathML HTML integration points from surviving template compilation.
2026-08-07 16:32:57 -07:00
Matthieu Riegler 03f1758e2a fix(devtools): fix regression for node serialization.
This fixes a regression introduced by #69309 where the format of `serializedId` from a coma separated string, to a JSON representation of an array.

fixes #70104
2026-08-07 16:30:22 -07:00
SkyZeroZx 4631a6e479 refactor(forms): modernize Signal Forms tests
replace change detection calls with whenStable for async tests
2026-08-07 16:22:59 -07:00
Ady Elouej 9f8e32616b fix(core): reject prefixed SVG script hosts
Use the host element's namespace-aware local name when checking for script
elements. A prefixed SVG script can expose a qualified tag name such as
"x:script" while its local name remains "script".
2026-08-07 16:19:13 -07:00
SkyZeroZx ec372d6029 refactor(platform-browser): modernize DOM renderer tests
Migrate fixtures to standalone components and await zoneless updates
2026-08-07 16:17:19 -07:00
splincode 9e37a58e14 fix(forms): keep radio inputs in sync when values change
Recompute the checked state when a reused radio input receives a new value while the form model remains unchanged.

Handle this case for both reactive forms and signal forms, and add regression tests covering reused radio elements.
2026-08-07 16:15:06 -07:00
Kristiyan Kostadinov 11b4089acf test(compiler): add test for imports with unquoted URL
Adds a test to cover the case where an `@import` might have a URL with special characters that isn't quoted.
2026-08-07 16:12:51 -07:00
Kristiyan Kostadinov a4f1a94948 fix(compiler): do not encapsulate nested selectors if parent contains ::ng-deep
Updates the nested selector encapsulation logic so that it skips encapsulating child selectors if the parent contains `::ng-deep`. The reasoning is that `:host ::ng-deep { .foo {} }` should behave as `:host ::ng-deep .foo {}`.
2026-08-07 16:12:51 -07:00
Kristiyan Kostadinov d0d7f57e08 fix(compiler): scope nested CSS rules
This is a second attempt at scoping nested CSS rules after not being able to land #50693. I took some extra precautions to try and avoid the crash that was happening last time.
2026-08-07 16:12:51 -07:00
Matthew Beck 8d6c925392 fix(compiler-cli): record class extends clause references in DeferredSymbolTracker
`DeferredSymbolTracker.lookupIdentifiersInSourceFile` prunes `ts.isTypeNode`
subtrees so that references appearing exclusively inside type annotations
do not keep static import declarations in the emitted JavaScript.

However, `ts.isTypeNode` returns `true` for `ts.ExpressionWithTypeArguments`,
which TypeScript uses to represent both `extends` and `implements` heritage
clauses. An `extends` clause on a class declaration or class expression is a
value position that survives in the emitted JavaScript output.

Because `isTypeNode` returned `true`, references to base classes imported
alongside deferred dependencies were ignored. As a result, the compiler
erroneously marked the static import statement as deferrable and deleted it
from the emitted JavaScript, leaving the `extends <Base>` clause referencing
an undeclared identifier and causing a runtime `ReferenceError`.

This commit ensures that `ExpressionWithTypeArguments` under a class `extends`
clause is not treated as an erasable type node, preserving the static import
whenever a base class is referenced.
2026-08-07 16:10:41 -07:00
Angular Robot adb770bd20 build: update bazel dependencies
See associated pull request for more information.
2026-08-07 16:03:52 -07:00
Angular Robot 0f0058900c build: update all github actions
See associated pull request for more information.
2026-08-07 16:02:04 -07:00
Angular Robot 6b1fd3463c build: update pnpm to v11.20.0
See associated pull request for more information.
2026-08-07 15:59:00 -07:00
Angular Robot 0eaf1c55d3 build: update all non-major dependencies
See associated pull request for more information.
2026-08-07 15:56:00 -07:00
Matthieu Riegler d7b03f5523 fix(compiler): Generate correct expression for optional chaning.
Optional chaining was generating expressions with included an extra pair of parenthesis which changed the semantics of the expression and threw an unexpected error from the optional chain non nullable extended diagnostic.

fixes #70085
2026-08-07 15:55:10 -07:00
SkyZeroZx 09bc90003e fix(http): always decode JSON responses as UTF-8
Keep the charset handling added in #70062 limited to text responses. JSON
bytes must stay UTF-8 regardless of the Content-Type charset.

Fetch defines Body.json() using "parse JSON from bytes". Infra specifies that
step as: "Let string be the result of running UTF-8 decode on bytes."

https://fetch.spec.whatwg.org/#dom-body-json
https://infra.spec.whatwg.org/#parse-json-bytes-to-a-javascript-value
2026-08-07 15:54:20 -07:00
Alan Agius cf1844bcce refactor(devtools): use JavaScriptTransformer for Angular optimization esbuild plugin
Refactor the devtools optimization esbuild plugin to use JavaScriptTransformer from @angular/build/private.
2026-08-07 10:30:52 -07:00
Alan Agius 573145afb5 refactor(devtools): remove async-await downleveling as devtools is zoneless
Remove the supported 'async-await': false option in esbuild-base.config.mts as Angular DevTools is zoneless and does not require downleveling async/await syntax.
2026-08-07 10:30:52 -07:00
Alan Agius 07f58006c5 refactor(devtools): rely on esbuild to downlevel async generators
Configure esbuild with supported 'async-await': false in esbuild-base.config.mts so esbuild downlevels async functions and async generators natively.

This allows removing the custom Babel plugin @babel/plugin-transform-async-generator-functions and downlevelAsyncGeneratorsIfPresent option.
2026-08-07 10:30:52 -07:00
Joey Perrott 8a40fbefdf release: cut the v22.2.0-next.1 release 2026-08-07 11:19:03 -06:00
Joey Perrott 165279fe3e docs: release notes for the v22.1.1 release 2026-08-07 10:13:12 -07:00
lazerg 96b80424c7 docs: fix supported Node.js version in installation prerequisites 2026-08-04 10:31:03 -07:00
Matthieu Riegler ef4dfead83 fix(http): avoid aborting completed requests in FetchBackend
Prevent AbortController.abort() from executing during Observable teardown when a FetchBackend HTTP request has already completed successfully or errored.
Previously, FetchBackend unconditionally called abort() upon stream termination. When requests completed normally, calling abort() after delivery caused Chromium-based browsers to mark the resolved request as net::ERR_ABORTED in DevTools due to a race condition, leading to missing response body payloads ("Failed to load response data"). By tracking whether the request has already settled—similar to XhrBackend checking for xhr.readyState !== xhr.DONE—we ensure abort() is only called for unsettled, in-flight requests upon unsubscription.

Fixes #70071
2026-08-04 09:37:35 -07:00
Angular Robot 308a361f71 build: update domino digest to e0779df
See associated pull request for more information.
2026-08-04 09:35:04 -07:00
SkyZeroZx 2f0be5bef8 refactor(common): modernize directive tests to rely on whenStable
Replace synchronous detectChanges calls with zoneless-compatible scheduling and stability waits in NgClass, NgStyle, and NgOptimizedImage tests.
2026-08-04 08:45:01 -07:00
Alan Agius 280d09b160 fix(http): strip RFC 6265 DQUOTE characters and handle URIError in parseCookieValue
Previously, `parseCookieValue` did not strip enclosing double quotes (`DQUOTE`) from quoted cookie values as specified in RFC 6265 Section 4.1.1. In addition, malformed percent-encoding in cookie values caused an unhandled `URIError` when calling `decodeURIComponent`.
2026-08-04 08:29:46 -07:00
Matthieu Riegler 6a0789dc7f fix(http): respect content-type charset in fetch backend text decoder
Extract the charset parameter from the Content-Type response header in FetchBackend and pass it to TextDecoder when decoding text and json responses. When no valid charset is provided or supported, gracefully fall back to default utf-8 decoding.

Fixes #70061
2026-08-04 08:28:23 -07:00
Angular Robot f68291515a build: lock file maintenance
See associated pull request for more information.
2026-08-04 08:25:59 -07:00
Shuaib Hasan Akib 337053ef1a refactor(compiler): remove unused code and exports
Removes unused code and exports that are no longer referenced anywhere
in the compiler codebase.
2026-08-03 09:42:08 -07:00
Suraj Yadav fb698d12cd test(platform-server): add SSR integration test for null and normal input values
Fixes #69785

Add an Angular SSR integration test in platform-server verifying that null input values do not render string attributes like value="null" during server-side rendering, while normal non-empty string values like value="hello" are properly preserved.
2026-08-03 09:30:45 -07:00
Kam efe1aae1cd fix(docs-infra): keep inline code in links as an atomic inline box
Inline code inside a link renders as a chip with its own background. Text
decoration propagates from the anchor and cannot be cancelled by a descendant,
so with the chip laid out as a regular inline box the link underline is drawn
straight across it.

Lay the chip out as an atomic inline box, which the propagated decoration is not
drawn across. This applies to the link rule only; code outside of links keeps
`display: inline` for wrapping in multiline paragraphs, and the link rule
already sets `white-space: nowrap`.
2026-08-03 09:20:15 -07:00
lazerg 36c2197539 fix(zone.js): guard against null handle when detecting refreshable timers
A timer wrapper such as a browser extension, policy, or automation harness can block a call and return null instead of a handle. patchTimer then read `.refresh` on that null value and threw synchronously.

Treat a nullish handle as non-refreshable so the patched timer keeps working.

Fixes #70044
2026-08-03 09:14:04 -07:00
SkyZeroZx c9c4f2afc6 refactor(common): modernize directive tests to rely on whenStable
Replace synchronous detectChanges calls with zoneless-compatible scheduling and stability waits. Preserve NgComponentOutlet coverage for components declared by NgModules.
2026-08-03 09:13:32 -07:00
Angular Robot 10244503d7 docs: update cross-repo adev docs
Updated Angular adev cross repo docs files.
2026-08-03 09:10:41 -07:00
Kam f2f12925bd docs: fix roadmap link text and preview-site link
Two link issues on the roadmap page:

- "Introduce deferred loading" linked next.angular.dev, the preview build of
  this same site, for a page that exists here at guide/templates/defer. Use the
  relative path so readers stay on the stable docs.
- "Local template variables" linked "`@let` docs", putting a code chip and a
  plain word in one anchor. The anchor's underline crosses the chip and the two
  halves render in different colors. Link only the symbol, as the rest of adev
  does.

Also on the same page:

- Drop a stray space before a period in the Components card.
- Give the older completion labels the same "Completed in <quarter>" wording the
  newer cards already use.
2026-08-03 08:58:35 -07:00
Angular Robot 4aacaecb96 build: update cross-repo angular dependencies
See associated pull request for more information.
2026-08-03 08:55:37 -07:00
Matthieu Riegler 1b13db7a1b refactor(animations): remove outdated comment
This isn't necessary anymore.
2026-07-31 15:34:04 -07:00
SkyZeroZx cfe2cda110 fix(core): initialize hydration triggers after late runtime activation
After incremental hydration became tree-shakable, application bootstrap could finish before a routed component activated the runtime. The one-time trigger scan was then skipped, leaving routed and nested hydration triggers uninitialized.

Coordinate application bootstrap with runtime activation and initialize once both have occurred.

Fixes #69908
2026-07-31 12:26:08 -07:00
Matthieu Riegler 3a29329191 fix(devtools): Extension name for non googlers.
For corps urls the promise is actually resolved but returns a `status:0`.

fixes #70023
2026-07-31 11:18:21 -07:00
Kam 690eecda6b docs: document deprecated allowSignalWrites option in v19 update guide
The `allowSignalWrites` option of `effect()` became a no-op and was
deprecated in v19, but the update guide never mentioned it. Applications
upgrading from v18 or earlier still carry the option at their `effect()`
call sites, where it now only produces a dev-mode warning.

Add an entry to the v19 recommendations instructing those applications to
remove it, mirroring the wording of the deprecation notice on
`CreateEffectOptions`.
2026-07-31 08:39:57 -07:00
hawkgs e144e697ff refactor(devtools): substitute remaining Angie images with ng-angie
Substitute the remaining `<img>`s with the Angie component.
2026-07-31 08:36:09 -07:00
Jaime Burgos bb78286e5e fix(http): run root interceptors in the terminal request chain
Represent withRequestsMadeViaParent() with an internal delegating backend so the interceptor handler can distinguish delegated clients from independent child configurations.
2026-07-31 08:32:14 -07:00
Angular Robot 245e6f37ea build: update cross-repo angular dependencies
See associated pull request for more information.
2026-07-31 08:23:37 -07:00
Angular Robot 8496d9c2e2 build: update pnpm to v11.18.0
See associated pull request for more information.
2026-07-31 08:22:39 -07:00
Angular Robot 93b659c455 build: update dependency node to v22.23.2
See associated pull request for more information.
2026-07-31 08:21:27 -07:00
Markus Mächler a37c79d7d4 docs: remove duplicate aria-hidden attributes in aria examples 2026-07-30 15:22:14 -07:00
Kam 5dd634d18d docs: fix self-referential link and merge intro tips in inputs guide
The "Customizing model inputs" section linked "standard input" back to
the inputs guide itself, a dead-end self-link. Point the sentence at the
relevant in-page sections instead (#required-inputs and #input-aliases).

Also merge the two stacked intro TIP callouts into a single tip.
2026-07-30 15:20:41 -07:00
Angular Robot b63b9032b4 build: update all non-major dependencies
See associated pull request for more information.
2026-07-30 09:59:54 -07:00
Aristeidis Bampakos a0f943d53d docs: replace Firebase Studio references with Antigravity 2026-07-30 09:58:07 -07:00
Angular Robot 9e5dc74b67 build: update cross-repo angular dependencies
See associated pull request for more information.

Closes #69910 as a pr takeover
2026-07-30 09:41:50 -07:00
SkyZeroZx 6deb6afcd1 refactor(forms): modernize reactive forms tests to rely on whenStable
Rely on zoneless scheduling throughout reactive forms tests instead of triggering change detection manually.
2026-07-30 09:06:29 -07:00
splincode e43eb96341 fix(core): warn when style property bindings receive invalid values
Report unsupported style property binding values in development mode while preserving existing binding behavior. Unwrap trusted style values before appending unit suffixes and link NG0318 warnings to the corresponding error guide.
2026-07-30 09:05:46 -07:00
Jaime Burgos d068fc1ea0 refactor(common): modernize pipes & non bindable tests to rely on whenStable
Rely on zoneless scheduling throughout reactive forms tests instead of triggering change detection manually.
2026-07-30 08:56:25 -07:00
splincode 5d76720e06 refactor(compiler): replace any casts with precise types
- r3_factory: use `in` operator instead of `as any` property probing
  in isDelegatedFactoryMetadata / isExpressionFactoryMetadata type guards
- r3_deferred_triggers: narrow assignment to
  `DeferredBlockTriggers[typeof name]` instead of `as any`
- defer_resolve_targets / reify: drop unnecessary `as any` on
  `op.trigger` — `DeferTriggerBase.kind` is present on all union members
- pipe_creation: replace double `as any` with `as {target?: ir.XrefId}`,
  and reuse the already-narrowed `slotHandle` variable for the call
- extractor_merger: replace legacy `<any>console` guards with a proper
  `typeof console !== 'undefined'` check
2026-07-30 08:55:04 -07:00
hawkgs 5124417370 refactor(devtools): hide search button when there is not a signal graph
The button is not functional when there isn't a graph. No need to keep it.
2026-07-30 08:49:56 -07:00
Matthieu Riegler d8a570ec4e refactor(core): update acceptance test
- remove NgModules
- remove non-necessary TestBed.configureTestingModule
- remove non-necessary Eager strategy
- Drop usages of CommonModule
2026-07-30 08:47:53 -07:00
Georgi Serev 1113faf2df refactor(devtools): create Angie component
Introduce a component that provides shared styling for Angie illustrations and predefined sizes for ease of use.
2026-07-30 08:44:59 -07:00
Kam 058c7f9b0c docs: update outdated and broken external links in adev
Several external links pointed at locations that have moved or gone away:

- ng-packagr, webpack, and node-glob renamed their default branch from
  master to main, so the deep links into those repos redirect.
- The npm package pages moved from npmjs.org to www.npmjs.com.
- The Angular Material and CDK docs moved from material.angular.io to
  material.angular.dev.
- The semver reference pointed at the defunct semver.io service; point it
  at the SemVer spec at semver.org instead.
- The MDN animationiteration event link had a typo (animationitration).
- compiler-cli's metadata/schema.ts was removed after v12; pin the link to
  the 12.2.x tag where the file still exists.
- The angularindepth.com article is gone (its domain is parked); link to its
  live home on the Angular In Depth Medium publication.
- Google moved the Lighthouse user-timing docs to developer.chrome.com.

Point the links at their current, working locations.
2026-07-30 08:43:35 -07:00
Angular Robot cc4e6119b4 build: update dependency chalk to v6
See associated pull request for more information.
2026-07-30 08:42:26 -07:00
Angular Robot 84cd4da06c build: update dependency jsdom to v30
See associated pull request for more information.
2026-07-30 08:41:46 -07:00
Kam 4b83d09a94 fix(devtools): invoke selectedNode signal in directive tree keyboard guard
`isEditingDirectiveState` used `!this.selectedNode` (a bare signal
reference, always truthy) instead of `!this.selectedNode()`, so the
guard was dead and ArrowDown with no selection threw a TypeError.
2026-07-30 08:38:28 -07:00
Angular Robot e86e12e56c build: update bazel dependencies
See associated pull request for more information.
2026-07-30 08:24:44 -07:00
Angular Robot 88a9621c30 build: update github/codeql-action action to v4.37.4
See associated pull request for more information.
2026-07-30 08:23:12 -07:00
lazerg ae303c4a05 docs: document overlay stacking change in v21 update guide 2026-07-30 08:22:05 -07:00
Alan Agius 32815d5472 ci: add branch tracking comments to dev-infra GitHub actions
Adds `# main` version comments after SHA references for `angular/dev-infra` GitHub Actions across workflows so that Renovate tracks the main branch and proposes updates when digests change.
2026-07-30 08:16:34 -07:00
Alan Agius 892ea7bec4 release: bump Angular DevTools version to 1.19.0 2026-07-30 09:33:55 +02:00
splincode 1a2bcb2295 fix(devtools): avoid initializing profiler in production mode
Defer the timing hook subscription until the timing API is explicitly enabled. Keep production module loading and development initialization covered by isolated browser test targets.
2026-07-29 16:51:09 -07:00
Alan Agius c6e4d5ad6f release: cut the v22.2.0-next.0 release 2026-07-29 22:49:39 +02:00
Alan Agius 25ddd41125 docs: release notes for the v21.2.19 release 2026-07-29 22:46:12 +02:00
Alan Agius 129b5dbd04 docs: release notes for the v22.1.0 release 2026-07-29 22:40:52 +02:00
Alex Rickabaugh 9386c89a28 docs: release notes for the v20.3.27 release 2026-07-29 12:54:52 -07:00
Kam 0d26130a4c docs: invoke signal inputs in adev examples and tutorials
Several adev example and tutorial files read a signal input as a bare
reference (this.foo) instead of invoking it (this.foo()). Because an
InputSignal is a function object, the bare reference is always truthy
and never yields the underlying value, so the surrounding guard or
binding silently did the wrong thing:

- animations open-close(.1/.3): the `!this.logging` guard in
  onAnimationEvent was always false, so the early return never fired.
- form-validation forbidden-name.directive: the `this.forbiddenName`
  ternary condition was always truthy, so validation ran even when no
  forbidden name was configured.
- first-app steps 12 and 14 housing-location: `housingLocation.photo`
  read `.photo` off the signal function (undefined), leaving the
  listing image src empty.

Invoke the signals so the examples reflect correct signal-input usage.
2026-07-29 09:59:17 -07:00
SkyZeroZx 59f6ef690b refactor(forms): modernize signal forms tests to rely on whenStable
Rely on zoneless test scheduling instead of manually triggering change detection. Keep Signals Forms tests aligned with the async-first testing pattern.
2026-07-29 09:57:37 -07:00
Shuaib Hasan Akib 21eed5f8d4 docs: fix self-referential link in defer block description
Replace broken  link with inline code
formatting. The link was pointing to the page itself, as no dedicated block concept page exists at that URL.
corrects 'everytime' to 'every time'.

Update tools/manual_api_docs/blocks/let.md

Co-authored-by: Matthieu Riegler <kyro38@gmail.com>
2026-07-29 09:45:11 -07:00
Kam e70994bdc3 fix(docs-infra): fix undefined CSS custom properties in shared-docs styles
Two custom properties in the shared-docs styles reference tokens that are
defined nowhere, so the declarations are invalid at computed-value time.

_kbd.scss sets the <kbd> text color with var(---tertiary-contrast); the extra
leading dash points at an undefined property with no fallback, so the color
resolves to the inherited value instead of the intended --tertiary-contrast.

_colors.scss builds --light-pink-to-light-purple-horizontal-gradient from
var(--light-purple), which is not defined anywhere (the sibling token is
--light-violet, defined in the same file), invalidating the gradient. The
gradient is not currently referenced, so this corrects a latent malformed
declaration rather than a visible bug.

Point both at the defined tokens: --tertiary-contrast and --light-violet.
2026-07-29 09:44:35 -07:00
Shuaib Hasan Akib ec87f04200 refactor(common): replaces the deprecated positional subscribe arguments with the
recommended observer object
2026-07-29 09:43:52 -07:00
SkyZeroZx 90ac011fa9 docs: clarify behavior of credentials option during SSR on Node.js 2026-07-29 09:41:28 -07:00
Kam 3d09713386 feat(devtools): show Angie on the remaining empty-state screens
Extend the Angie illustrations to the empty states that were still plain
text: the injector providers "No such providers" result, the injector tree
unsupported-version notice, the two profiler timeline placeholders, and the
router tree unsupported-version notice.

Adds the magnifying-glass and question poses as assets; the orthos-back
and sad poses are already in the tree.
2026-07-29 09:40:42 -07:00
Kam 91822538fe fix(docs-infra): fix malformed --webgl-page-background declaration
In the light theme block of _colors.scss, --webgl-page-background and
--webgl-gray-unfilled were written on a single line without a separating
semicolon, and --webgl-page-background wrapped --page-background in an
invalid nested var(). As a result --webgl-page-background got a garbage
value and --webgl-gray-unfilled was never defined in the light theme.

Split them into two well-formed declarations and drop the nested var(),
matching the structure already used in the dark theme block.
2026-07-29 09:04:09 -07:00
Kam 8cd4c89c29 fix(docs-infra): define missing win95 variables in aria autocomplete examples
The retro-themed aria autocomplete examples style the clear button with
var(--win95-gray), var(--win95-dark-gray), var(--win95-light), and
var(--win95-shadow), but none of the three example stylesheets defines
those custom properties. The variables resolve to nothing, so the button
falls back to inherited theme colors — color: var(--win95-shadow) becomes
the light-theme text color in dark mode, leaving the button unreadable.

Define the four variables in :host of each example, matching the values
already used by the sibling aria tree examples, so the clear button keeps
its Windows 95 look in both light and dark themes.
2026-07-29 09:02:54 -07:00
Kam 48c1e5bb0f fix(docs-infra): replace undefined --gray-50 in retro aria examples
The retro variants of the toolbar, select, and multiselect aria examples
tint the pressed button background with
color-mix(in srgb, var(--retro-button-color) 60%, var(--gray-50)), but
--gray-50 is defined nowhere. Per the CSS spec, a var() with no fallback
pointing at an undefined property invalidates the whole color-mix(), so the
declaration is dropped and the pressed-state tint never applies.

Define a local --retro-pressed-tint token in each example's :host (#fbfbfb,
the value $gray-50 resolved to) with a :host-context(.docs-dark-mode) override
to #151417, and use it for the pressed-state mix. This keeps the example
self-contained rather than depending on adev's global token scope, while
staying theme-aware in both light and dark, following the same token-flipping
pattern used in code-editor.component.scss.
2026-07-29 08:59:54 -07:00
Angular Robot 947859d029 build: update pnpm to v11.17.0
See associated pull request for more information.
2026-07-29 08:55:36 -07:00
Kristiyan Kostadinov 2a141847a5 fix(forms): add utility to assert that value is a field tree
Adds the `isFieldTree` utility that allows users to assert whether a value is a field tree. This is something that has come up on Material recently and will be useful for users as well.

Fixes #69984.
2026-07-29 08:53:24 -07:00
Matthew Beck d44b3224d9 test(compiler-cli): add compliance case for @HostListener on a property
`@HostListener` is not limited to methods — it is equally valid on a property
holding a function, which is the idiomatic way to keep `this` bound:

    @HostListener('window:beforeunload', ['$event'])
    private onUnload = (event: BeforeUnloadEvent) => {...};

Every existing host-listener compliance case declares the handler as a method,
so the property form was uncovered. This adds a case exercising both a public
and a private function-valued property, one of them with a global (`window:`)
event target, and locks in the emitted chained `ɵɵlistener` calls plus
`ɵɵresolveWindow`.

Verified against all four compliance modes (full, partial/linked,
declaration-only); GOLDEN_PARTIAL.js regenerated via the golden update rule.
2026-07-29 08:52:53 -07:00
Matthieu Riegler 36474f7011 refactor(common): remove duplicate helper function
We have `useAutoTick` in our private shared utils.
2026-07-29 08:49:15 -07:00
Maikel van Dort 9373d22a48 docs: remove semicolon text node 2026-07-29 08:48:34 -07:00
Angular Robot 4eb0ed077c build: lock file maintenance
See associated pull request for more information.
2026-07-29 08:46:37 -07:00
Matthieu Riegler c1025a0510 refactor(core): Migrate more tests off fakeAsync
This will prevent to polute the agent context with outdated/bad practices.
2026-07-29 08:46:01 -07:00
Alan Agius 2bcd12a6a5 docs: update default value for strictTemplates
Updates the strictTemplates documentation in Angular compiler options to note that the default is true, replacing the reference to the obsolete ng new --strict flag.
2026-07-29 08:42:11 -07:00
Edu 1b09130640 feat(devtools): rename DevTools tab to "Angular & Wiz" for Googlers
Conditionally update the browser DevTools tab name to "Angular & Wiz" if the user is identified as a Google employee via internal corp network detection.
2026-07-29 08:41:07 -07:00
Jaime Burgos de240a5d0e fix(http): enable xsrf for root-provided HttpClient
Include the XSRF interceptor in the root token factory so the automatically provided HttpClient retains the documented default protection without requiring provideHttpClient().
2026-07-29 08:40:09 -07:00
brysonbw d5e8b1ef7a feat(forms): allow permanent hidden fields in signal forms
Allow the hidden utility function to be called without a configuration object to make fields permanently hidden.
2026-07-29 08:39:39 -07:00
SkyZeroZx f33ee95045 fix(http): match header values exactly when deleting
Normalize value-specific HttpHeaders deletions before filtering. The string overload previously used String#indexOf and removed shorter values contained within the requested deletion value, potentially widening outgoing request metadata.

Preserve delete-all behavior only when no value is supplied, and cover string, array, and empty-string deletion.
2026-07-29 08:39:07 -07:00
SkyZeroZx ff02a16749 fix(http): preserve immutability of materialized clones
Prevent lazy HttpHeaders and HttpParams clones from reusing value arrays owned by a materialized source. Append and value-specific delete operations previously mutated those shared arrays, violating the immutable API contract and allowing request metadata to bleed into later requests.

Share value arrays until an update mutates a specific header or parameter, then copy only that array. Cover the affected append and delete paths with regression tests that materialize the source first.
2026-07-29 08:39:07 -07:00
Georgi Serev 184c4797b8 refactor(devtools): rename timing API to performance track
Rename Timing API to Performance Track to better reflect the purpose of the actual feature;
Migrate the settings data object to match the new name and drop some redundant keys.
2026-07-29 08:38:39 -07:00
Jaime Burgos 840f071566 docs: Adds HTTP communication guidance to Angular Skills 2026-07-29 08:37:14 -07:00
SkyZeroZx f57d5d5c8c fix(core): account for namespaces in host binding sanitization (#69558)
Make runtime URL sanitizer selection namespace-aware so SVG and MathML host bindings match the security schema.

Cover SVG href/xlink:href and MathML href host binding cases, including dynamic hostElement resolution.

PR Close #69558
2026-07-29 08:36:32 -07:00
SkyZeroZx d06e3748b7 fix(core): sanitize host bindings on concrete hosts (#69558)
Host binding sanitization previously used the declaring directive or component selector to choose a compile-time security context. The same host binding can execute on a different concrete element through hostDirectives, inherited host bindings, dynamic directives, or createComponent hostElement usage.

Compute host binding security contexts against possible concrete hosts and defer URL versus ResourceURL selection to runtime when necessary. Resolve dynamic root host TNodes to their native tag before sanitizer and security-sensitive attribute checks.

Fixes angular#69550

PR Close #69558
2026-07-29 08:36:32 -07:00
Suraj Yadav 5ad8231397 fix(migrations): correctly detect then/else keywords in control flow migration
The control flow migration determines whether an `*ngIf` uses a `then`
and/or `else` clause by regex matching the raw microsyntax string for
the literal keywords `then`/`else`. The regexes only checked that the
keyword was preceded by a non-word character, but not that it was
followed by one.

As a result, a template reference name that merely starts with `then`
(e.g. `else thenBlock`) or `else` was misidentified as the `then`/`else`
keyword itself. This caused the migration to take the wrong code path
(e.g. then+else instead of else-only), which in turn made
`getTemplateName()` compute a `slice(start, end)` with `start > end`,
producing an empty template name. That empty placeholder was never
resolved and was silently emitted as an invalid
`<ng-template [ngTemplateOutlet]=""></ng-template>`, dropping the
original template content without any warning.

Add a negative lookahead `(?![\w\d])` to both regexes so `then`/`else`
are only matched as whole keywords, not as a prefix of a longer
template reference name.

Fixes #69914
2026-07-24 13:56:17 -07:00
Matthew Beck 5245ca5ba7 test(compiler-cli): format compliance TEST_CASES.json with prettier
Reformats the TEST_CASES.json files touched by the following change so they
satisfy the repo's prettier check (short inputFiles/files arrays collapsed to a
single line). Pure formatting; the parsed JSON is unchanged. Split into its own
commit so the coverage change that follows is easy to review.
2026-07-24 13:55:33 -07:00
Kristiyan Kostadinov e606a020e9 fix(language-service): account for strictTemplates being enabled by default
We were raising the suggestion about enabling `strictTemplates` when `strictTemplates` is ommitted, however the option is now enabled by default.

Fixes #69905.
2026-07-24 13:45:24 -07:00
Angular Robot 7cbf5e3c2b build: update all github actions
See associated pull request for more information.
2026-07-24 13:43:29 -07:00
Ben Hong 17845308ea docs: modernize directives guides (#69822)
Co-authored-by: Matthieu Riegler <kyro38@gmail.com>

PR Close #69822
2026-07-24 10:36:58 -07:00
Ben Hong 4e7aaa48f5 docs: smooth out directives guide narrative flow (#69822)
PR Close #69822
2026-07-24 10:36:58 -07:00
Kam 28d59e8d63 docs: use https for external links in adev
Several guides, examples, and the update-guide recommendations linked to
external resources over insecure http. Switch them to https.
2026-07-24 08:28:08 -07:00
mfstapert 738b8fe9d2 docs: update attribute testing guide to include canonical example with local component 2026-07-24 08:27:15 -07:00
Jens Kuehlers 25dbe79507 docs: add v23 release and change to yearly release cycle
## Summary

This PR adds v22.x and v23 release dates. It also changes Angular's release cadence to a yearly cycle.

## Why we are making this change

The community has long requested less frequent major releases due to the impact of breaking changes and upgrades for their projects as well as for enterprise customers. Additionally, a longer release cycle provides increased API stability for developers using agentic workflows, while still delivering a reasonable cadence of API upgrades and migrations.
2026-07-24 08:26:32 -07:00
Kristiyan Kostadinov d79b3b65e9 refactor(core): align navigation types with built in ones
Aligns our clone of the navigation API types with the built-in TypeScript types. This is related to an internal issue.
2026-07-24 08:26:02 -07:00
Doug Parker e779309930 release: bump Angular DevTools version to 1.18.0 2026-07-23 12:02:11 -07:00
Pawel Kozlowski 3bf24306be docs: release notes for the v22.1.0-rc.0 release 2026-07-22 16:45:32 +02:00
Pawel Kozlowski c855a5689f release: bump the next branch to v22.2.0-next.0 2026-07-22 16:45:32 +02:00
1554 changed files with 73976 additions and 70807 deletions
+2 -1
View File
@@ -13,7 +13,8 @@ When reviewing a pull request for the `angular` repository, follow these essenti
2. **Key Focus Areas**:
- **Comprehensive Reviews**: You **MUST always** perform a deep, comprehensive review of the _entire_ pull request. If the user asks you to look into a specific issue, file, or area of concern, you must investigate that specific area _in addition to_ reviewing the rest of the PR's substantive changes. Do not terminate your review after addressing only the user's focal point.
- **Package-Specific Guidelines**: Check if there are specific guidelines for the package being modified in the `reference/` directory (e.g., `reference/router.md`). Always prioritize these rules for their respective packages.
- **Package-Specific & Topic Guidelines**: Check if there are specific guidelines for the package or topic being modified in the `reference/` directory (e.g., `reference/router.md` or `reference/object_create_null.md`). Always prioritize these rules for their respective areas.
- **Prototype Collision & `Object.create(null)` PRs**: When reviewing PRs that swap `{}` for `Object.create(null)`, consult `reference/object_create_null.md` for technical evaluation criteria and rules.
- **Commit Messages**: Evaluate the quality of commit messages. They should explain the _why_ behind the change, not just the _what_. Someone should be able to look at the commit history years from now and clearly understand the context and reasoning for the change.
- **Code Cleanliness**: Ensure the code is readable, maintainable, and follows Angular's project standards.
- **Performance**: Look out for code that might negatively impact runtime performance or bundle size, particularly in hot paths like change detection or rendering.
@@ -0,0 +1,38 @@
# Rules for `Object.create(null)` and Prototype Collision Prevention
This guide outlines the technical rules and evaluation criteria for using `Object.create(null)` versus standard object literals (`{}`) or `Map` in the Angular codebase.
---
## 1. When `Object.create(null)` is Appropriate
Using `Object.create(null)` (or `Map`) is appropriate when **all** of the following conditions are met:
1. The object is used as an **internal key-value lookup map or set**.
2. The keys are **arbitrary or untrusted dynamic strings** (e.g., URL query parameters in `$locationShim`, HTML sanitizer tag sets, or `jsaction` DOM event-type resolvers).
3. Property existence is checked via direct indexing or key checks (e.g., `map[key] !== undefined` or `key in map`), where a key matching an `Object.prototype` member (such as `'toString'`, `'constructor'`, or `'hasOwnProperty'`) causes false positive matches or incorrect behavior.
---
## 2. Handling Public API and Boundary Objects
If an object receives untrusted dynamic keys **and** is exposed to public consumers or third-party code (e.g., `SimpleChanges` in `ngOnChanges`):
- **Do NOT blindly change the object to `Object.create(null)`**: Stripping `Object.prototype` from public objects is a breaking API change. Consumer code calling `.hasOwnProperty()`, `.toString()`, `.valueOf()`, or using string interpolation (`` `${obj}` ``) will fail at runtime (`TypeError: obj.hasOwnProperty is not a function`).
- **Safe Alternatives for Public Objects:**
- **`Object.hasOwn(obj, key)`**: Use `Object.hasOwn` for internal framework property lookups instead of direct index or `in` checks. This prevents prototype collision during internal reads without breaking the object's prototype for consumers.
- **Input Key Sanitization**: Filter or delete dangerous key names (`__proto__`, `constructor`, `prototype`) when populating the object.
- **`Map` or Custom Classes**: For new public APIs requiring key-value stores with dynamic keys, prefer `Map<K, V>` or dedicated classes with explicit `.get()` and `.has()` methods.
- **Deprecation / Breaking Change Process**: If changing a public object's prototype to `null` is unavoidable, it must follow Angular's formal deprecation and major version breaking change process.
---
## 3. When `Object.create(null)` Should NOT Be Used
Do not replace `{}` with `Object.create(null)` in the following scenarios:
1. **Fixed-Shape Structs and DTOs:** Objects with hardcoded static property names (e.g., `let sortedBreakpoints: {breakpoints?: number[]} = {}`). `Object.assign({}, ...)` only copies _own_ enumerable properties, so prototype properties on sources are never copied.
2. **Numeric-Key Maps:** Objects indexed by numbers (e.g., `tasksByHandleId: {[id: number]: Task}`). Numeric keys do not collide with `Object.prototype` string members.
3. **Reference Sentinels:** Objects used purely for reference identity checks (e.g., `const EMPTY_OBJECT = {}` or `const IN_PROGRESS_RESOLUTION = {}`).
4. **Internal Compiler AST and Visitor State:** Temporary objects with internally generated keys where untrusted user input cannot poison key names.
5. **Hot Performance Paths and Size-Critical Bundles:** Standard `{}` literals use V8 fast hidden classes and monomorphic inline caching. `Object.create(null)` forces V8 dictionary mode and increases minified bundle size (e.g., in inline polyfills like `event-dispatch-contract` or SSR hydration bundles).
@@ -5,7 +5,7 @@ description: Explains the mental model and architecture of the code under `packa
# Signal Forms Architecture
The `packages/forms/signals` directory contains an experimental, signal-based forms API for Angular.
The `packages/forms/signals` directory contains the signal-based forms API for Angular.
This system differs significantly from the existing Reactive and Template-driven forms.
## Mental Model
@@ -36,27 +36,27 @@ The central internal class representing a single field in the form graph. It agg
- `structure`: Manages parent/child relationships and signal slicing.
- `validationState`: Computes `valid`, `invalid`, `errors` signals.
- `nodeState`: Tracks `touched`, `dirty`, `pristine`.
- `nodeState`: Tracks `touched`, `dirty`, and derived logical state.
- `metadataState`: Stores metadata like `min`, `max`, `required`.
- `submitState`: Tracks submission status and server errors.
### 2. `ValidationState` (`src/field/validation.ts`)
### 2. `FieldValidationState` (`src/field/validation.ts`)
Manages the complexity of validation:
Implements `ValidationState` and manages the complexity of validation:
- **Synchronous Errors**: Derived from schema rules.
- **Asynchronous Errors**: Handled via signals, including 'pending' states.
- **Tree Errors**: Errors that bubble up or are targeted at specific fields.
- **Submission Errors**: Server-side errors injected imperatively via `submit()`.
### 3. `FormField` Directive (`src/directive/form_field_directive.ts`)
### 3. `FormField` Directive (`src/directive/form_field.ts`)
The bridge between the `FieldNode` and the DOM.
- Selector: `[formField]`
- It supports:
- **Native Elements**: `<input>`, `<select>`, `<textarea>`.
- **Custom Controls**: Components implementing `FormUiControl` or `FormValueControl`.
- **Custom Controls**: Components implementing `FormValueControl` or `FormCheckboxControl`.
- **Legacy Interop**: Components implementing `ControlValueAccessor` (via `InteropNgControl`).
### 4. `Schema` (`src/api/structure.ts` & `src/api/rules`)
@@ -69,7 +69,7 @@ Defines the behavior.
## Data Flow
1. **Read**: `form.field.value()` reads directly from the underlying signal (projected to the specific path).
1. **Read**: `form.field().value()` reads directly from the underlying signal (projected to the specific path).
2. **Write**: Writing to the form (e.g., via UI) updates the underlying signal.
3. **Validation**: A computed effect observes the value signal and runs validators defined in the schema.
@@ -97,7 +97,7 @@ const userForm = form(user, userRules); // OR apply(userForm, userRules)
- `packages/forms/signals/src/api/structure.ts`: Public API entry points (`form`, `apply`).
- `packages/forms/signals/src/api/control.ts`: Interfaces for custom controls (`FormUiControl`).
- `packages/forms/signals/src/field/node.ts`: The `FieldNode` implementation.
- `packages/forms/signals/src/directive/form_field_directive.ts`: The `[formField]` directive.
- `packages/forms/signals/src/directive/form_field.ts`: The `[formField]` directive.
## Supplemental Information
@@ -2,9 +2,9 @@
This document explains how the Signal Forms system hooks into the Angular compiler and runtime to provide seamless type-checking and efficient updates.
## 1. Compiler-CLI Integration (Type Checking)
## 1. Compiler Integration (Type Checking)
The `packages/compiler-cli` package contains specific logic to support `[formField]`. This is primarily handled in `src/ngtsc/typecheck/src/ops/signal_forms.ts`.
The `packages/compiler` package contains specific logic to support `[formField]`. This is primarily handled in `src/typecheck/ops/signal_forms.ts`.
### Key Mechanisms:
@@ -22,7 +22,7 @@ The `packages/compiler-cli` package contains specific logic to support `[formFie
### Relevant Files:
- `packages/compiler-cli/src/ngtsc/typecheck/src/ops/signal_forms.ts`: The core logic for `TcbNativeFieldOp` and `SignalFormFieldOp`.
- `packages/compiler/src/typecheck/ops/signal_forms.ts`: The core Signal Forms type-checking logic, including `TcbNativeFieldOp`.
## 2. Core Runtime Integration
@@ -30,8 +30,8 @@ The `packages/core` package provides the low-level instructions that power the `
### Key Mechanisms:
- **`ɵngControlCreate` Hook**: The `FormField` directive defines a special method `ɵngControlCreate`.
- **`ɵɵcontrol` Instructions**: When the compiler sees `ɵngControlCreate`, it emits:
- **`ɵngControlCreate` Hook**: The `FormField` directive defines a special method `ɵngControlCreate`, which causes the compiler to install `ɵɵControlFeature`.
- **`ɵɵcontrol` Instructions**: A `formField` binding causes the compiler to emit:
- `ɵɵcontrolCreate`: Called during the creation phase.
- `ɵɵcontrol`: Called during the update phase.
- **`ControlDirectiveHost`**: These instructions provide the directive with a `ControlDirectiveHost`. This is a privileged interface that allows the `FormField` directive to:
@@ -43,4 +43,4 @@ The `packages/core` package provides the low-level instructions that power the `
### Relevant Files:
- `packages/core/src/render3/instructions/control.ts`: Implementation of `ɵɵcontrol` instructions.
- `packages/forms/signals/src/directive/form_field_directive.ts`: The directive that implements the hook.
- `packages/forms/signals/src/directive/form_field.ts`: The directive that implements the hook.
+1 -1
View File
@@ -1 +1 @@
8.7.0
8.8.0
File diff suppressed because it is too large Load Diff
@@ -5,7 +5,6 @@
"types": ["node"],
"lib": ["es2021"],
"experimentalDecorators": true,
"strict": true,
"skipLibCheck": true,
"noImplicitOverride": true,
"esModuleInterop": true,
@@ -13,4 +12,4 @@
"declaration": true,
"sourceMap": true
}
}
}
+4 -4
View File
@@ -21,17 +21,17 @@ jobs:
(github.event.action == 'synchronize' && contains(github.event.pull_request.labels.*.name, 'adev: preview'))
steps:
- name: Initialize environment
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@442c2fcbf06a321b5196b4c5fc70e78a49242958
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@4a09f9588769980eacb41fcc76e2c76d048eac42 # main
- name: Setup Bazel
uses: angular/dev-infra/github-actions/bazel/setup@442c2fcbf06a321b5196b4c5fc70e78a49242958
uses: angular/dev-infra/github-actions/bazel/setup@4a09f9588769980eacb41fcc76e2c76d048eac42 # main
- name: Setup Bazel RBE
uses: angular/dev-infra/github-actions/bazel/configure-remote@442c2fcbf06a321b5196b4c5fc70e78a49242958
uses: angular/dev-infra/github-actions/bazel/configure-remote@4a09f9588769980eacb41fcc76e2c76d048eac42 # main
- name: Install node modules
run: pnpm install --frozen-lockfile
- name: Build adev
# `snapshot-build` config is used to stamp the exact version with sha in the footer.
run: pnpm bazel build //adev:build.production --config=snapshot-build
- uses: angular/dev-infra/github-actions/previews/pack-and-upload-artifact@442c2fcbf06a321b5196b4c5fc70e78a49242958
- uses: angular/dev-infra/github-actions/previews/pack-and-upload-artifact@4a09f9588769980eacb41fcc76e2c76d048eac42 # main
with:
workflow-artifact-name: 'adev-preview'
pull-number: '${{github.event.pull_request.number}}'
+3 -2
View File
@@ -8,6 +8,7 @@
name: Deploying adev preview to Firebase
on:
# zizmor: ignore[dangerous-triggers] - {Trigger is safe as workflow does not checkout untrusted code}
workflow_run:
workflows: ['Build adev for preview deployment']
types: [completed]
@@ -29,7 +30,7 @@ jobs:
runs-on: ubuntu-latest
if: ${{ github.event.workflow_run.conclusion == 'success' }}
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
with:
token: '${{secrets.GITHUB_TOKEN}}'
persist-credentials: false
@@ -45,7 +46,7 @@ jobs:
npx -y firebase-tools@15.15.0 target:clear --config adev/firebase.json --project ${{env.PREVIEW_PROJECT}} hosting angular-docs
npx -y firebase-tools@15.15.0 target:apply --config adev/firebase.json --project ${{env.PREVIEW_PROJECT}} hosting angular-docs ${{env.PREVIEW_SITE}}
- uses: angular/dev-infra/github-actions/previews/upload-artifacts-to-firebase@442c2fcbf06a321b5196b4c5fc70e78a49242958
- uses: angular/dev-infra/github-actions/previews/upload-artifacts-to-firebase@4a09f9588769980eacb41fcc76e2c76d048eac42 # main
with:
github-token: '${{secrets.GITHUB_TOKEN}}'
workflow-artifact-name: 'adev-preview'
@@ -2,6 +2,7 @@ name: DevInfra
on:
push:
# zizmor: ignore[dangerous-triggers] - {Trigger is safe as workflow does not checkout untrusted code}
pull_request_target:
types: [opened, synchronize, reopened, ready_for_review, labeled]
@@ -14,9 +15,9 @@ jobs:
runs-on: ubuntu-latest
if: github.event.repository.fork == false
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- uses: angular/dev-infra/github-actions/branch-manager@442c2fcbf06a321b5196b4c5fc70e78a49242958
- uses: angular/dev-infra/github-actions/branch-manager@4a09f9588769980eacb41fcc76e2c76d048eac42 # main
with:
angular-robot-key: ${{ secrets.ANGULAR_ROBOT_PRIVATE_KEY }}
+4 -4
View File
@@ -28,7 +28,7 @@ jobs:
- uses: alessbell/pull-request-comment-branch@ef3408c9757d05f89cb525036383033a313758a0 # v2.1.0
id: comment-branch
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
# Specify repository as the PR branch might be from a fork.
repository: ${{steps.comment-branch.outputs.head_owner}}/${{steps.comment-branch.outputs.head_repo}}
@@ -45,14 +45,14 @@ jobs:
node-version-file: '.nvmrc'
- name: Install pnpm
uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9
uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0
- run: pnpm install --frozen-lockfile
- name: Setup Bazel
uses: angular/dev-infra/github-actions/bazel/setup@442c2fcbf06a321b5196b4c5fc70e78a49242958
uses: angular/dev-infra/github-actions/bazel/setup@4a09f9588769980eacb41fcc76e2c76d048eac42 # main
- uses: angular/dev-infra/github-actions/bazel/configure-remote@442c2fcbf06a321b5196b4c5fc70e78a49242958
- uses: angular/dev-infra/github-actions/bazel/configure-remote@4a09f9588769980eacb41fcc76e2c76d048eac42 # main
with:
bazelrc: ./.bazelrc.user
+26 -26
View File
@@ -21,7 +21,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Initialize environment
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@442c2fcbf06a321b5196b4c5fc70e78a49242958
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@4a09f9588769980eacb41fcc76e2c76d048eac42 # main
- name: Install node modules
run: pnpm install --frozen-lockfile
- name: Check code lint
@@ -41,13 +41,13 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Initialize environment
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@442c2fcbf06a321b5196b4c5fc70e78a49242958
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@4a09f9588769980eacb41fcc76e2c76d048eac42 # main
with:
disable-package-manager-cache: true
- name: Setup Bazel
uses: angular/dev-infra/github-actions/bazel/setup@442c2fcbf06a321b5196b4c5fc70e78a49242958
uses: angular/dev-infra/github-actions/bazel/setup@4a09f9588769980eacb41fcc76e2c76d048eac42 # main
- name: Setup Bazel RBE
uses: angular/dev-infra/github-actions/bazel/configure-remote@442c2fcbf06a321b5196b4c5fc70e78a49242958
uses: angular/dev-infra/github-actions/bazel/configure-remote@4a09f9588769980eacb41fcc76e2c76d048eac42 # main
with:
google_credential: ${{ secrets.RBE_TRUSTED_BUILDS_USER }}
- name: Install node modules
@@ -57,7 +57,7 @@ jobs:
- name: Test build
run: pnpm devtools:build:chrome
- name: Cypress run
uses: cypress-io/github-action@fa4a118725a8f001170d49631ea89e5d66fee626 # v7.4.1
uses: cypress-io/github-action@09090944bd8aaa2a517cefb6e38bf1aae336b42b # v7.4.3
with:
command: pnpm devtools:test:e2e
start: pnpm bazel run //devtools/src:devserver
@@ -69,11 +69,11 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Initialize environment
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@442c2fcbf06a321b5196b4c5fc70e78a49242958
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@4a09f9588769980eacb41fcc76e2c76d048eac42 # main
- name: Setup Bazel
uses: angular/dev-infra/github-actions/bazel/setup@442c2fcbf06a321b5196b4c5fc70e78a49242958
uses: angular/dev-infra/github-actions/bazel/setup@4a09f9588769980eacb41fcc76e2c76d048eac42 # main
- name: Setup Bazel Remote Caching
uses: angular/dev-infra/github-actions/bazel/configure-remote@442c2fcbf06a321b5196b4c5fc70e78a49242958
uses: angular/dev-infra/github-actions/bazel/configure-remote@4a09f9588769980eacb41fcc76e2c76d048eac42 # main
with:
google_credential: ${{ secrets.RBE_TRUSTED_BUILDS_USER }}
- name: Install node modules
@@ -85,11 +85,11 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Initialize environment
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@442c2fcbf06a321b5196b4c5fc70e78a49242958
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@4a09f9588769980eacb41fcc76e2c76d048eac42 # main
- name: Setup Bazel
uses: angular/dev-infra/github-actions/bazel/setup@442c2fcbf06a321b5196b4c5fc70e78a49242958
uses: angular/dev-infra/github-actions/bazel/setup@4a09f9588769980eacb41fcc76e2c76d048eac42 # main
- name: Setup Bazel Remote Caching
uses: angular/dev-infra/github-actions/bazel/configure-remote@442c2fcbf06a321b5196b4c5fc70e78a49242958
uses: angular/dev-infra/github-actions/bazel/configure-remote@4a09f9588769980eacb41fcc76e2c76d048eac42 # main
with:
google_credential: ${{ secrets.RBE_TRUSTED_BUILDS_USER }}
- name: Install node modules
@@ -102,11 +102,11 @@ jobs:
labels: ubuntu-latest
steps:
- name: Initialize environment
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@442c2fcbf06a321b5196b4c5fc70e78a49242958
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@4a09f9588769980eacb41fcc76e2c76d048eac42 # main
- name: Setup Bazel
uses: angular/dev-infra/github-actions/bazel/setup@442c2fcbf06a321b5196b4c5fc70e78a49242958
uses: angular/dev-infra/github-actions/bazel/setup@4a09f9588769980eacb41fcc76e2c76d048eac42 # main
- name: Setup Bazel RBE
uses: angular/dev-infra/github-actions/bazel/configure-remote@442c2fcbf06a321b5196b4c5fc70e78a49242958
uses: angular/dev-infra/github-actions/bazel/configure-remote@4a09f9588769980eacb41fcc76e2c76d048eac42 # main
with:
google_credential: ${{ secrets.RBE_TRUSTED_BUILDS_USER }}
- name: Install node modules
@@ -121,11 +121,11 @@ jobs:
labels: ubuntu-latest
steps:
- name: Initialize environment
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@442c2fcbf06a321b5196b4c5fc70e78a49242958
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@4a09f9588769980eacb41fcc76e2c76d048eac42 # main
- name: Setup Bazel
uses: angular/dev-infra/github-actions/bazel/setup@442c2fcbf06a321b5196b4c5fc70e78a49242958
uses: angular/dev-infra/github-actions/bazel/setup@4a09f9588769980eacb41fcc76e2c76d048eac42 # main
- name: Setup Bazel RBE
uses: angular/dev-infra/github-actions/bazel/configure-remote@442c2fcbf06a321b5196b4c5fc70e78a49242958
uses: angular/dev-infra/github-actions/bazel/configure-remote@4a09f9588769980eacb41fcc76e2c76d048eac42 # main
with:
google_credential: ${{ secrets.RBE_TRUSTED_BUILDS_USER }}
- name: Install node modules
@@ -138,11 +138,11 @@ jobs:
labels: ubuntu-latest
steps:
- name: Initialize environment
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@442c2fcbf06a321b5196b4c5fc70e78a49242958
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@4a09f9588769980eacb41fcc76e2c76d048eac42 # main
- name: Setup Bazel
uses: angular/dev-infra/github-actions/bazel/setup@442c2fcbf06a321b5196b4c5fc70e78a49242958
uses: angular/dev-infra/github-actions/bazel/setup@4a09f9588769980eacb41fcc76e2c76d048eac42 # main
- name: Setup Bazel RBE
uses: angular/dev-infra/github-actions/bazel/configure-remote@442c2fcbf06a321b5196b4c5fc70e78a49242958
uses: angular/dev-infra/github-actions/bazel/configure-remote@4a09f9588769980eacb41fcc76e2c76d048eac42 # main
- name: Install node modules
run: pnpm install --frozen-lockfile
- run: echo "https://${{secrets.SNAPSHOT_BUILDS_GITHUB_TOKEN}}:@github.com" > ${HOME}/.git_credentials
@@ -154,11 +154,11 @@ jobs:
labels: ubuntu-latest
steps:
- name: Initialize environment
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@442c2fcbf06a321b5196b4c5fc70e78a49242958
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@4a09f9588769980eacb41fcc76e2c76d048eac42 # main
- name: Setup Bazel
uses: angular/dev-infra/github-actions/bazel/setup@442c2fcbf06a321b5196b4c5fc70e78a49242958
uses: angular/dev-infra/github-actions/bazel/setup@4a09f9588769980eacb41fcc76e2c76d048eac42 # main
- name: Setup Bazel RBE
uses: angular/dev-infra/github-actions/bazel/configure-remote@442c2fcbf06a321b5196b4c5fc70e78a49242958
uses: angular/dev-infra/github-actions/bazel/configure-remote@4a09f9588769980eacb41fcc76e2c76d048eac42 # main
with:
google_credential: ${{ secrets.RBE_TRUSTED_BUILDS_USER }}
- name: Install node modules
@@ -198,11 +198,11 @@ jobs:
runs-on: ubuntu-latest-8core
steps:
- name: Initialize environment
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@442c2fcbf06a321b5196b4c5fc70e78a49242958
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@4a09f9588769980eacb41fcc76e2c76d048eac42 # main
- name: Setup Bazel
uses: angular/dev-infra/github-actions/bazel/setup@442c2fcbf06a321b5196b4c5fc70e78a49242958
uses: angular/dev-infra/github-actions/bazel/setup@4a09f9588769980eacb41fcc76e2c76d048eac42 # main
- name: Setup Bazel RBE
uses: angular/dev-infra/github-actions/bazel/configure-remote@442c2fcbf06a321b5196b4c5fc70e78a49242958
uses: angular/dev-infra/github-actions/bazel/configure-remote@4a09f9588769980eacb41fcc76e2c76d048eac42 # main
- name: Install node modules
run: pnpm install --frozen-lockfile
- name: Build adev
+1 -1
View File
@@ -24,7 +24,7 @@ jobs:
steps:
- name: Checkout the repository
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
# Setting `persist-credentials: false` prevents the github-action account from being the
# account that is attempted to be used for authentication, instead the remote is set to
+4 -3
View File
@@ -1,6 +1,7 @@
name: DevInfra
on:
# zizmor: ignore[dangerous-triggers] - {Trigger is safe as workflow does not checkout untrusted code}
pull_request_target:
types: [opened, synchronize, reopened]
issues:
@@ -15,7 +16,7 @@ jobs:
if: github.event_name == 'pull_request_target'
runs-on: ubuntu-latest
steps:
- uses: angular/dev-infra/github-actions/labeling/pull-request@649c3afeaa46674507b9625537e49de54a695e2b
- uses: angular/dev-infra/github-actions/labeling/pull-request@4a09f9588769980eacb41fcc76e2c76d048eac42 # main
with:
angular-robot-key: ${{ secrets.ANGULAR_ROBOT_PRIVATE_KEY }}
labels: '{"requires: TGP": ["packages/core/primitives/**/{*,.*}"]}'
@@ -23,14 +24,14 @@ jobs:
if: github.event_name == 'pull_request_target'
runs-on: ubuntu-latest
steps:
- uses: angular/dev-infra/github-actions/post-approval-changes@649c3afeaa46674507b9625537e49de54a695e2b
- uses: angular/dev-infra/github-actions/post-approval-changes@4a09f9588769980eacb41fcc76e2c76d048eac42 # main
with:
angular-robot-key: ${{ secrets.ANGULAR_ROBOT_PRIVATE_KEY }}
issue_labels:
if: github.event_name == 'issues'
runs-on: ubuntu-latest
steps:
- uses: angular/dev-infra/github-actions/labeling/issue@649c3afeaa46674507b9625537e49de54a695e2b
- uses: angular/dev-infra/github-actions/labeling/issue@4a09f9588769980eacb41fcc76e2c76d048eac42 # main
with:
angular-robot-key: ${{ secrets.ANGULAR_ROBOT_PRIVATE_KEY }}
google-generative-ai-key: ${{ secrets.GOOGLE_GENERATIVE_AI_KEY }}
+3 -2
View File
@@ -1,6 +1,7 @@
name: Google Internal Tests Enforcement
on:
# zizmor: ignore[dangerous-triggers] - {Trigger is safe as workflow does not checkout untrusted code}
pull_request_target:
types: [opened, reopened, synchronize]
@@ -13,8 +14,8 @@ jobs:
statuses: write
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- uses: angular/dev-infra/github-actions/google-internal-tests@442c2fcbf06a321b5196b4c5fc70e78a49242958
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: angular/dev-infra/github-actions/google-internal-tests@4a09f9588769980eacb41fcc76e2c76d048eac42 # main
with:
run-tests-guide-url: http://go/angular-g3sync-start
github-token: ${{ secrets.GITHUB_TOKEN }}
+4 -2
View File
@@ -1,6 +1,8 @@
name: Merge Ready
on: pull_request_target
on:
# zizmor: ignore[dangerous-triggers] - {Trigger is safe as workflow does not checkout untrusted code}
pull_request_target:
# Declare default permissions as read only.
permissions: {}
@@ -9,6 +11,6 @@ jobs:
status:
runs-on: ubuntu-latest
steps:
- uses: angular/dev-infra/github-actions/unified-status-check@442c2fcbf06a321b5196b4c5fc70e78a49242958
- uses: angular/dev-infra/github-actions/unified-status-check@4a09f9588769980eacb41fcc76e2c76d048eac42 # main
with:
angular-robot-key: ${{ secrets.ANGULAR_ROBOT_PRIVATE_KEY }}
+10 -5
View File
@@ -7,7 +7,6 @@ on:
permissions:
contents: 'read'
id-token: 'write'
defaults:
run:
@@ -21,7 +20,7 @@ jobs:
workflows: ${{ steps.workflows.outputs.workflows }}
steps:
- name: Initialize environment
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@442c2fcbf06a321b5196b4c5fc70e78a49242958
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@4a09f9588769980eacb41fcc76e2c76d048eac42 # main
- name: Install node modules
run: pnpm install --frozen-lockfile
- id: workflows
@@ -31,14 +30,17 @@ jobs:
timeout-minutes: 30
runs-on: ubuntu-latest
needs: list
permissions:
contents: 'read'
id-token: 'write'
strategy:
matrix:
workflow: ${{ fromJSON(needs.list.outputs.workflows) }}
steps:
- name: Initialize environment
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@442c2fcbf06a321b5196b4c5fc70e78a49242958
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@4a09f9588769980eacb41fcc76e2c76d048eac42 # main
- name: Setup Bazel
uses: angular/dev-infra/github-actions/bazel/setup@442c2fcbf06a321b5196b4c5fc70e78a49242958
uses: angular/dev-infra/github-actions/bazel/setup@4a09f9588769980eacb41fcc76e2c76d048eac42 # main
- name: Install node modules
run: pnpm install --frozen-lockfile
# We utilize the google-github-actions/auth action to allow us to get an active credential using workflow
@@ -50,4 +52,7 @@ jobs:
project_id: 'internal-200822'
workload_identity_provider: 'projects/823469418460/locations/global/workloadIdentityPools/measurables-tracking/providers/angular'
service_account: 'measures-uploader@internal-200822.iam.gserviceaccount.com'
- run: pnpm ng-dev perf workflows --name ${{ matrix.workflow }} --commit-sha ${{github.sha}}
- run: pnpm ng-dev perf workflows --name "$WORKFLOW" --commit-sha "$COMMIT_SHA"
env:
WORKFLOW: ${{ matrix.workflow }}
COMMIT_SHA: ${{ github.sha }}
+21 -21
View File
@@ -19,7 +19,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Initialize environment
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@442c2fcbf06a321b5196b4c5fc70e78a49242958
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@4a09f9588769980eacb41fcc76e2c76d048eac42 # main
- name: Install node modules
run: pnpm install --frozen-lockfile
- name: Check code lint
@@ -39,7 +39,7 @@ jobs:
- name: Check code format
run: pnpm ng-dev format changed --check ${{ github.event.pull_request.base.sha }}
- name: Check Package Licenses
uses: angular/dev-infra/github-actions/linting/licenses@442c2fcbf06a321b5196b4c5fc70e78a49242958
uses: angular/dev-infra/github-actions/linting/licenses@4a09f9588769980eacb41fcc76e2c76d048eac42 # main
with:
allow-dependencies-licenses: 'pkg:npm/google-protobuf@'
@@ -47,13 +47,13 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Initialize environment
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@442c2fcbf06a321b5196b4c5fc70e78a49242958
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@4a09f9588769980eacb41fcc76e2c76d048eac42 # main
with:
disable-package-manager-cache: true
- name: Setup Bazel
uses: angular/dev-infra/github-actions/bazel/setup@442c2fcbf06a321b5196b4c5fc70e78a49242958
uses: angular/dev-infra/github-actions/bazel/setup@4a09f9588769980eacb41fcc76e2c76d048eac42 # main
- name: Setup Bazel RBE
uses: angular/dev-infra/github-actions/bazel/configure-remote@442c2fcbf06a321b5196b4c5fc70e78a49242958
uses: angular/dev-infra/github-actions/bazel/configure-remote@4a09f9588769980eacb41fcc76e2c76d048eac42 # main
- name: Install node modules
run: pnpm install --frozen-lockfile
- name: Run unit tests
@@ -61,7 +61,7 @@ jobs:
- name: Test build
run: pnpm devtools:build:chrome
- name: Cypress run
uses: cypress-io/github-action@fa4a118725a8f001170d49631ea89e5d66fee626 # v7.4.1
uses: cypress-io/github-action@09090944bd8aaa2a517cefb6e38bf1aae336b42b # v7.4.3
with:
command: pnpm devtools:test:e2e
start: pnpm bazel run //devtools/src:devserver
@@ -73,11 +73,11 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Initialize environment
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@442c2fcbf06a321b5196b4c5fc70e78a49242958
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@4a09f9588769980eacb41fcc76e2c76d048eac42 # main
- name: Setup Bazel
uses: angular/dev-infra/github-actions/bazel/setup@442c2fcbf06a321b5196b4c5fc70e78a49242958
uses: angular/dev-infra/github-actions/bazel/setup@4a09f9588769980eacb41fcc76e2c76d048eac42 # main
- name: Setup Bazel Remote Caching
uses: angular/dev-infra/github-actions/bazel/configure-remote@442c2fcbf06a321b5196b4c5fc70e78a49242958
uses: angular/dev-infra/github-actions/bazel/configure-remote@4a09f9588769980eacb41fcc76e2c76d048eac42 # main
- name: Install node modules
run: pnpm install --frozen-lockfile
- name: Run CI tests for framework
@@ -97,11 +97,11 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Initialize environment
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@442c2fcbf06a321b5196b4c5fc70e78a49242958
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@4a09f9588769980eacb41fcc76e2c76d048eac42 # main
- name: Setup Bazel
uses: angular/dev-infra/github-actions/bazel/setup@442c2fcbf06a321b5196b4c5fc70e78a49242958
uses: angular/dev-infra/github-actions/bazel/setup@4a09f9588769980eacb41fcc76e2c76d048eac42 # main
- name: Setup Bazel Remote Caching
uses: angular/dev-infra/github-actions/bazel/configure-remote@442c2fcbf06a321b5196b4c5fc70e78a49242958
uses: angular/dev-infra/github-actions/bazel/configure-remote@4a09f9588769980eacb41fcc76e2c76d048eac42 # main
- name: Install node modules
run: pnpm install --frozen-lockfile
- name: Run integration CI tests for framework
@@ -112,11 +112,11 @@ jobs:
labels: ubuntu-latest
steps:
- name: Initialize environment
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@442c2fcbf06a321b5196b4c5fc70e78a49242958
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@4a09f9588769980eacb41fcc76e2c76d048eac42 # main
- name: Setup Bazel
uses: angular/dev-infra/github-actions/bazel/setup@442c2fcbf06a321b5196b4c5fc70e78a49242958
uses: angular/dev-infra/github-actions/bazel/setup@4a09f9588769980eacb41fcc76e2c76d048eac42 # main
- name: Setup Bazel RBE
uses: angular/dev-infra/github-actions/bazel/configure-remote@442c2fcbf06a321b5196b4c5fc70e78a49242958
uses: angular/dev-infra/github-actions/bazel/configure-remote@4a09f9588769980eacb41fcc76e2c76d048eac42 # main
- name: Install node modules
run: pnpm install --frozen-lockfile
- name: Run tests
@@ -129,11 +129,11 @@ jobs:
labels: ubuntu-latest
steps:
- name: Initialize environment
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@442c2fcbf06a321b5196b4c5fc70e78a49242958
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@4a09f9588769980eacb41fcc76e2c76d048eac42 # main
- name: Setup Bazel
uses: angular/dev-infra/github-actions/bazel/setup@442c2fcbf06a321b5196b4c5fc70e78a49242958
uses: angular/dev-infra/github-actions/bazel/setup@4a09f9588769980eacb41fcc76e2c76d048eac42 # main
- name: Setup Bazel RBE
uses: angular/dev-infra/github-actions/bazel/configure-remote@442c2fcbf06a321b5196b4c5fc70e78a49242958
uses: angular/dev-infra/github-actions/bazel/configure-remote@4a09f9588769980eacb41fcc76e2c76d048eac42 # main
- name: Install node modules
run: pnpm install --frozen-lockfile
- name: Run tests
@@ -144,11 +144,11 @@ jobs:
labels: ubuntu-latest
steps:
- name: Initialize environment
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@442c2fcbf06a321b5196b4c5fc70e78a49242958
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@4a09f9588769980eacb41fcc76e2c76d048eac42 # main
- name: Setup Bazel
uses: angular/dev-infra/github-actions/bazel/setup@442c2fcbf06a321b5196b4c5fc70e78a49242958
uses: angular/dev-infra/github-actions/bazel/setup@4a09f9588769980eacb41fcc76e2c76d048eac42 # main
- name: Setup Bazel RBE
uses: angular/dev-infra/github-actions/bazel/configure-remote@442c2fcbf06a321b5196b4c5fc70e78a49242958
uses: angular/dev-infra/github-actions/bazel/configure-remote@4a09f9588769980eacb41fcc76e2c76d048eac42 # main
- name: Install node modules
run: pnpm install --frozen-lockfile
- run: |
+1 -1
View File
@@ -18,7 +18,7 @@ permissions:
jobs:
release:
uses: angular/dev-infra/.github/workflows/reusable-release.yml@a8a341bd2b49b931c92408d497588b6acd012eeb
uses: angular/dev-infra/.github/workflows/reusable-release.yml@4a09f9588769980eacb41fcc76e2c76d048eac42 # main
secrets:
wombot-token: ${{ secrets.WOMBOT_TOKEN }}
angular-robot-key: ${{ secrets.ANGULAR_ROBOT_PRIVATE_KEY }}
-52
View File
@@ -1,52 +0,0 @@
name: OpenSSF Scorecard
on:
branch_protection_rule:
schedule:
- cron: '0 0 * * 0'
push:
branches: [main]
workflow_dispatch:
# Declare default permissions as read only.
permissions:
contents: read
jobs:
analysis:
name: Scorecards analysis
runs-on: ubuntu-latest
permissions:
# Needed to upload the results to code-scanning dashboard.
security-events: write
# Needed to publish results
id-token: write
actions: read
contents: read
steps:
- name: 'Checkout code'
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
- name: 'Run analysis'
uses: ossf/scorecard-action@4eaacf0543bb3f2c246792bd56e8cdeffafb205a # v2.4.3
with:
results_file: results.sarif
results_format: sarif
repo_token: ${{ secrets.GITHUB_TOKEN }}
publish_results: true
# Upload the results as artifacts.
- name: 'Upload artifact'
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: SARIF file
path: results.sarif
retention-days: 5
# Upload the results to GitHub's code scanning dashboard.
- name: 'Upload to code-scanning'
uses: github/codeql-action/upload-sarif@7188fc363630916deb702c7fdcf4e481b751f97a # v4.37.1
with:
sarif_file: results.sarif
-1
View File
@@ -1,6 +1,5 @@
{
"compilerOptions": {
"strict": true,
"target": "es2020",
"module": "Node16",
"noEmit": true,
+1 -1
View File
@@ -1 +1 @@
22.23.1
24.21.0
+1 -1
View File
@@ -53,7 +53,7 @@ function readPackage(pkg, context) {
if (pkg.version === '0.0.0-PLACEHOLDER' && localAngularPackages.has(key)) {
pkg.dependencies = {
...pkg.dependencies,
[key]: 'workspace: *',
[key]: 'workspace:*',
};
delete pkg.peerDependencies[key];
+8 -19
View File
@@ -120,6 +120,7 @@ groups:
conditions:
- >
contains_any_globs(files.exclude('packages/core/primitives/*'), [
'.agent/skills/**/{*,.*}',
'contributing-docs/public-api-surface.md',
'dev-app/**/{*,.*}',
'integration/**/{*,.*}',
@@ -137,6 +138,7 @@ groups:
'packages/platform-browser/**/{*,.*}',
'packages/platform-browser-dynamic/**/{*,.*}',
'packages/platform-server/**/{*,.*}',
'packages/private/**/{*,.*}',
'packages/ssr/**/{*,.*}',
'packages/router/**/{*,.*}',
'packages/service-worker/**/{*,.*}',
@@ -206,6 +208,7 @@ groups:
- crisbeto
- devversion
- kirjs
- alan-agius4
- ~thePunderWoman
- ~pkozlowski-opensource
- ~amishne
@@ -300,9 +303,10 @@ groups:
conditions:
- >
contains_any_globs(files
.exclude('.pullapprove.yml')
.exclude('.pullapprove.yml')
.exclude('tools/manual_api_docs/blocks/*.md')
.exclude('tools/manual_api_docs/elements/*.md'),
.exclude('tools/manual_api_docs/elements/*.md')
.exclude('.agent/**/*.md'),
[
'{*,.*}',
'.agent/**/{*,.*}',
@@ -319,7 +323,6 @@ groups:
'modules/{*,.*}',
'packages/{*,.*}',
'packages/examples/test-utils/**/{*,.*}',
'packages/private/**/{*,.*}',
'packages/examples/{*,.*}',
'scripts/**/{*,.*}',
'third_party/**/{*,.*}',
@@ -481,23 +484,9 @@ groups:
- ~thePunderWoman # Jessica Janiuk
- ~AndrewKushnir # Andrew Kushnir
- atscott # Andrew Scott
# External team required reviews
primitives-shared:
<<: *defaults
conditions:
- >
contains_any_globs(files.exclude('packages/core/primitives/**/*spec.ts'), [
'packages/core/primitives/**/{*,.*}',
])
reviewers:
users:
- csmick # Cameron Smick
- mturco # Matt Turco
- iteriani # Thomas Nguyen
- tbondwilkinson # Tom Wilkinson
- rahatarmanahmed # Rahat Ahmed
- ENAML # Ethan Cline
- e-cline # Ethan Cline
- rockymeza # Rocky Meza
####################################################################################
# Override managed result groups
+628 -235
View File
@@ -1,3 +1,631 @@
<a name="22.2.0-next.7"></a>
# 22.2.0-next.7 (2026-09-09)
No user facing changes in this release
<!-- CHANGELOG SPLIT MARKER -->
<a name="22.2.0-next.6"></a>
# 22.2.0-next.6 (2026-09-09)
### compiler
| Commit | Type | Description |
| -- | -- | -- |
| [9b80d4ce9e](https://github.com/angular/angular/commit/9b80d4ce9e00d50d471c1a1a98ab67ec53fbce5f) | fix | namespace `@property` declarations |
### compiler-cli
| Commit | Type | Description |
| -- | -- | -- |
| [f0a271c7bd](https://github.com/angular/angular/commit/f0a271c7bd4be000c52592034573cda9866f26bb) | fix | do not flag callable objects with zero parameters in uninvoked track function check |
### core
| Commit | Type | Description |
| -- | -- | -- |
| [f6afb807c1](https://github.com/angular/angular/commit/f6afb807c1e62d26b8b665f2b4a9a52c2433a673) | feat | add ErrorBoundary programmatic APIs ([#70463](https://github.com/angular/angular/pull/70463)) |
| [bd9b45b5cc](https://github.com/angular/angular/commit/bd9b45b5cc1dd904cc4a5de45f6de8e1564b70b6) | feat | allow reading `Injector` from a view or content query |
| [70756b506c](https://github.com/angular/angular/commit/70756b506c508faa908fe8917e1f6937bcd6df77) | fix | apply SkipSelf to only the starting node in embedded views |
### language-service
| Commit | Type | Description |
| -- | -- | -- |
| [f4a5650ed9](https://github.com/angular/angular/commit/f4a5650ed9c71a8ee1dbd3003e13900464827757) | feat | add support for `@boundary` blocks ([#70463](https://github.com/angular/angular/pull/70463)) |
### platform-server
| Commit | Type | Description |
| -- | -- | -- |
| [bc3a6cda5d](https://github.com/angular/angular/commit/bc3a6cda5d7c4ef9207ee6c6adbcd55b7bd7dcab) | fix | avoid sourcemap corruption during domino path substitution |
### router
| Commit | Type | Description |
| -- | -- | -- |
| [3064f3f1dc](https://github.com/angular/angular/commit/3064f3f1dccd78177bf3b86f8ea231102884f0d7) | feat | expose router resources in public API |
| [094bce9e3d](https://github.com/angular/angular/commit/094bce9e3defe7e9fd56f951641f3bb74248d06a) | fix | determine blocking state solely by resource loading status |
| [8227e5cf6d](https://github.com/angular/angular/commit/8227e5cf6d157d6e472f963da68a804813ad26ed) | fix | keep detached route subtree contexts isolated and intact |
<!-- CHANGELOG SPLIT MARKER -->
<a name="22.1.6"></a>
# 22.1.6 (2026-09-09)
### compiler
| Commit | Type | Description |
| -- | -- | -- |
| [cbb8702143](https://github.com/angular/angular/commit/cbb8702143bd04b68253758dbc2c4a8fb49c0237) | fix | namespace `@property` declarations |
### compiler-cli
| Commit | Type | Description |
| -- | -- | -- |
| [aea121e532](https://github.com/angular/angular/commit/aea121e53217a7f74d2a8e4a7a6bf0f3a96b7c49) | fix | do not flag callable objects with zero parameters in uninvoked track function check |
### core
| Commit | Type | Description |
| -- | -- | -- |
| [c19b948ef8](https://github.com/angular/angular/commit/c19b948ef8d34e5a15e41cd5a88cdd4bda848f8f) | fix | apply SkipSelf to only the starting node in embedded views |
### platform-server
| Commit | Type | Description |
| -- | -- | -- |
| [d20a379583](https://github.com/angular/angular/commit/d20a37958318c898a72109480cee556e388cd78b) | fix | avoid sourcemap corruption during domino path substitution |
### router
| Commit | Type | Description |
| -- | -- | -- |
| [95c01e9cde](https://github.com/angular/angular/commit/95c01e9cdea251e09d321a01e9b982b2fe1b7873) | fix | keep detached route subtree contexts isolated and intact |
<!-- CHANGELOG SPLIT MARKER -->
<a name="20.3.31"></a>
# 20.3.31 (2026-09-09)
### platform-server
| Commit | Type | Description |
| -- | -- | -- |
| [3db26e2544](https://github.com/angular/angular/commit/3db26e2544964d07a520836ad01f02f36ec88e25) | fix | update domino to latest version |
<!-- CHANGELOG SPLIT MARKER -->
<a name="21.2.23"></a>
# 21.2.23 (2026-09-09)
### platform-server
| Commit | Type | Description |
| -- | -- | -- |
| [fc2e8fbc0b](https://github.com/angular/angular/commit/fc2e8fbc0bda2f5c11585e8a0cbd88c074f5e567) | fix | update domino to latest version |
<!-- CHANGELOG SPLIT MARKER -->
<a name="22.2.0-next.5"></a>
# 22.2.0-next.5 (2026-09-02)
### compiler-cli
| Commit | Type | Description |
| -- | -- | -- |
| [faafd18a4c](https://github.com/angular/angular/commit/faafd18a4c0c5db161e5d7056e9abfb8675904cb) | fix | check uninvoked signal aliases in extended diagnostic |
### core
| Commit | Type | Description |
| -- | -- | -- |
| [05c4d5a835](https://github.com/angular/angular/commit/05c4d5a8354228100b51176f295ed5dee4f3febc) | feat | add utility for testing directives |
| [91a2bf8425](https://github.com/angular/angular/commit/91a2bf84250f9b0a28a8a68e1eded6e9506bc5e9) | feat | support annotations in WebMCP tool declarations |
| [0904f90b13](https://github.com/angular/angular/commit/0904f90b13ac2a1bad22a2b43fb232957dba44c3) | fix | cancel stale debounce timers to prevent timer leaks |
| [58d536bc83](https://github.com/angular/angular/commit/58d536bc8362f3942c929e58be551d871b00cea7) | fix | don&apos;t fail NgModule checks for a pipe that extends a base class |
| [7168bed663](https://github.com/angular/angular/commit/7168bed663ba2a53f087a68a251e8ccfd938eb97) | fix | validate SVG animation attributes outside the SVG namespace |
### forms
| Commit | Type | Description |
| -- | -- | -- |
| [6e299da8cf](https://github.com/angular/angular/commit/6e299da8cfc7df49029d5d8bb7d5739560e1aaa4) | feat | hard-code `readOnlyHint` and `untrustedContentHint` for WebMCP implicit signal forms |
| [38861ac41b](https://github.com/angular/angular/commit/38861ac41bb373870e961f41626de6b6b31120f1) | fix | avoid writing to `name` input on ControlValueAccessor |
### migrations
| Commit | Type | Description |
| -- | -- | -- |
| [f72600eadd](https://github.com/angular/angular/commit/f72600eaddfa6a299526e922eb9ad5356be821a6) | fix | skip tsconfig files of non-Angular projects |
### platform-server
| Commit | Type | Description |
| -- | -- | -- |
| [b3bb36ad87](https://github.com/angular/angular/commit/b3bb36ad87334f7a86908c1b2b4353c1cc514c2e) | fix | resolve HTTP(S) URLs without authority as relative during SSR |
### router
| Commit | Type | Description |
| -- | -- | -- |
| [7137a41223](https://github.com/angular/angular/commit/7137a41223079b4b172aeccb5031347fcc947b79) | feat | stabilize auto cleanup injectors feature |
| [af26a8c521](https://github.com/angular/angular/commit/af26a8c52178a0e972c2841051db44f9d04ac371) | fix | avoid view transitions when the user agent provides one |
<!-- CHANGELOG SPLIT MARKER -->
<a name="22.1.5"></a>
# 22.1.5 (2026-09-02)
### compiler-cli
| Commit | Type | Description |
| -- | -- | -- |
| [d90698dae7](https://github.com/angular/angular/commit/d90698dae759f21f29052e521ffaec6c3a79f6d1) | fix | check uninvoked signal aliases in extended diagnostic |
### core
| Commit | Type | Description |
| -- | -- | -- |
| [2ceeb27078](https://github.com/angular/angular/commit/2ceeb27078d890514a1e87b0d99f366d9a4a796f) | fix | cancel stale debounce timers to prevent timer leaks |
| [ed2e401d2d](https://github.com/angular/angular/commit/ed2e401d2da9a6dd5c87006573a8e42934f58f62) | fix | don&apos;t fail NgModule checks for a pipe that extends a base class |
| [c65d378bb4](https://github.com/angular/angular/commit/c65d378bb4fded436f197aa7bc52c03f507b8bd4) | fix | validate SVG animation attributes outside the SVG namespace |
### forms
| Commit | Type | Description |
| -- | -- | -- |
| [43aaeaec29](https://github.com/angular/angular/commit/43aaeaec29e15d7b2504e8930c92c94b898ca200) | fix | avoid writing to `name` input on ControlValueAccessor |
### migrations
| Commit | Type | Description |
| -- | -- | -- |
| [d85288d75f](https://github.com/angular/angular/commit/d85288d75f3f85dc6b10bf813c805b1b7fed7329) | fix | skip tsconfig files of non-Angular projects |
### platform-server
| Commit | Type | Description |
| -- | -- | -- |
| [3b8723ce3d](https://github.com/angular/angular/commit/3b8723ce3d88b2bf871185736bc33e3cf9a189f4) | fix | resolve HTTP(S) URLs without authority as relative during SSR |
### router
| Commit | Type | Description |
| -- | -- | -- |
| [4340a63c52](https://github.com/angular/angular/commit/4340a63c52fca4d08ef258a3c053f50932eab27e) | fix | avoid view transitions when the user agent provides one |
<!-- CHANGELOG SPLIT MARKER -->
<a name="22.2.0-next.4"></a>
# 22.2.0-next.4 (2026-08-26)
### common
| Commit | Type | Description |
| -- | -- | -- |
| [862a0c8ab3](https://github.com/angular/angular/commit/862a0c8ab3403242eeac3b2fb80f7cf63cf0c892) | fix | avoid prototype member collisions |
| [7596548e9b](https://github.com/angular/angular/commit/7596548e9b836a5d12e966bf296a4dbba044b78f) | fix | use locale NaN symbol in number formatting |
### compiler
| Commit | Type | Description |
| -- | -- | -- |
| [58b0cb4735](https://github.com/angular/angular/commit/58b0cb47358861de0a512711addb8e29f984a57e) | fix | scope animations declared in minified nested rules |
### compiler-cli
| Commit | Type | Description |
| -- | -- | -- |
| [7d9f55da11](https://github.com/angular/angular/commit/7d9f55da11319da8f273d9edcd38ff2983bdbb0c) | feat | scope type-checking of keyed defer blocks |
| [2e2c426e76](https://github.com/angular/angular/commit/2e2c426e7609de551dafb6360e6373333a278c4e) | fix | deduplicate deferred imports across multiple blocks |
| [a46292af26](https://github.com/angular/angular/commit/a46292af260f241da2f9f4e4cbb7d40b14a678f1) | fix | default template diagnostic related message source file to template |
| [e9ba39d671](https://github.com/angular/angular/commit/e9ba39d671a3645ef5ca8233c96bacc94f02d155) | fix | Produce correct tcb expression for optional chaining |
| [74b294cd51](https://github.com/angular/angular/commit/74b294cd51b0f73e3a71e0cd13a176b1d0f3a89f) | fix | retain metadata for strict standalone errors |
### core
| Commit | Type | Description |
| -- | -- | -- |
| [7ba082e08e](https://github.com/angular/angular/commit/7ba082e08e59eadec4594ad80661d5c1ec08cb2a) | fix | avoid prototype member collisions |
| [915a03ae85](https://github.com/angular/angular/commit/915a03ae856a595bd5fa0f364928869db8daec19) | fix | explicitly reject foreign components in JIT mode |
| [2ab5ff56de](https://github.com/angular/angular/commit/2ab5ff56ded8c6246c0035d9df62a0c7fc9e1308) | fix | preserve namespace for dynamic component hosts |
| [168a324cce](https://github.com/angular/angular/commit/168a324cce0f133ed92953374c2510a75868b790) | fix | prevent TransferState prototype pollution |
| [83f7695b2e](https://github.com/angular/angular/commit/83f7695b2e4e1cab347f7817da4ae4262dade415) | fix | throw coded RuntimeErrors instead of crashing when hydration/rendering can&apos;t find an expected DOM node |
### forms
| Commit | Type | Description |
| -- | -- | -- |
| [1e35de536d](https://github.com/angular/angular/commit/1e35de536d8332dfcbb3d89d01ca84b50aa56bd2) | fix | use dot-access for readonly rule configuration |
### language-service
| Commit | Type | Description |
| -- | -- | -- |
| [0f0d52e9c1](https://github.com/angular/angular/commit/0f0d52e9c1154f8db9a4a8d9d0ff73f0afdc7031) | fix | honor quote style preference when generating imports |
### migrations
| Commit | Type | Description |
| -- | -- | -- |
| [bdc09e8183](https://github.com/angular/angular/commit/bdc09e8183a3280d25bfc93b4d5ae4452cc551ec) | fix | preserve registerLocaleData calls in standalone bootstrap migration |
### platform-server
| Commit | Type | Description |
| -- | -- | -- |
| [3e924cc8db](https://github.com/angular/angular/commit/3e924cc8dbbb57f23b262cb8f0d7e2bd0673034c) | fix | avoid stripping unicode whitespace during url resolution |
<!-- CHANGELOG SPLIT MARKER -->
<a name="22.1.4"></a>
# 22.1.4 (2026-08-26)
### common
| Commit | Type | Description |
| -- | -- | -- |
| [60a874c3fb](https://github.com/angular/angular/commit/60a874c3fbedc2ce216bcf783325f4e1291edcb0) | fix | avoid prototype member collisions |
| [e8378dfeab](https://github.com/angular/angular/commit/e8378dfeab39fca5bcdc47126b7859c8fdab7a28) | fix | use locale NaN symbol in number formatting |
### compiler
| Commit | Type | Description |
| -- | -- | -- |
| [05c7c725a5](https://github.com/angular/angular/commit/05c7c725a534f4ea14c718503966423133474bbf) | fix | scope animations declared in minified nested rules |
### compiler-cli
| Commit | Type | Description |
| -- | -- | -- |
| [afe8499a14](https://github.com/angular/angular/commit/afe8499a14c1bf8ab42e6e30b35917a873745540) | fix | default template diagnostic related message source file to template |
| [079a846263](https://github.com/angular/angular/commit/079a846263599daf38cc1903c46ccea99c5758f8) | fix | Produce correct tcb expression for optional chaining |
| [55eeb46418](https://github.com/angular/angular/commit/55eeb46418080e2faf64c84eac389c21abea6df8) | fix | retain metadata for strict standalone errors |
### core
| Commit | Type | Description |
| -- | -- | -- |
| [3da35cbab9](https://github.com/angular/angular/commit/3da35cbab94777230871a3e9289455089b80fe7f) | fix | avoid prototype member collisions |
| [b199bdfa2a](https://github.com/angular/angular/commit/b199bdfa2ad8b3fe189e7b4cef9dbea600cd4732) | fix | explicitly reject foreign components in JIT mode |
| [9a8e4826b9](https://github.com/angular/angular/commit/9a8e4826b9db4359c3f0e7185e9582b7bd08a3ef) | fix | preserve namespace for dynamic component hosts |
| [7c752d4815](https://github.com/angular/angular/commit/7c752d4815db41ee04e43d38cc826dd63214432b) | fix | prevent TransferState prototype pollution |
| [7546b7a805](https://github.com/angular/angular/commit/7546b7a80549c9620d52692cb0f5cab4c69b546c) | fix | throw coded RuntimeErrors instead of crashing when hydration/rendering can&apos;t find an expected DOM node |
### forms
| Commit | Type | Description |
| -- | -- | -- |
| [c8eb7f0056](https://github.com/angular/angular/commit/c8eb7f00564beceb21445b1f0374c93854b513d1) | fix | use dot-access for readonly rule configuration |
### language-service
| Commit | Type | Description |
| -- | -- | -- |
| [93d7f718d2](https://github.com/angular/angular/commit/93d7f718d2e057e37deda42d197e46672fe7f7f5) | fix | honor quote style preference when generating imports |
### migrations
| Commit | Type | Description |
| -- | -- | -- |
| [85c8829ac1](https://github.com/angular/angular/commit/85c8829ac189b0f47b865b036dad98905e576dc9) | fix | preserve registerLocaleData calls in standalone bootstrap migration |
### platform-server
| Commit | Type | Description |
| -- | -- | -- |
| [71e52d1396](https://github.com/angular/angular/commit/71e52d1396b9cef98652929b73e08c4cde645970) | fix | avoid stripping unicode whitespace during url resolution |
<!-- CHANGELOG SPLIT MARKER -->
<a name="21.2.22"></a>
# 21.2.22 (2026-08-26)
### platform-server
| Commit | Type | Description |
| -- | -- | -- |
| [5aa6d97deb](https://github.com/angular/angular/commit/5aa6d97deb9ef1de14e23748b7fa74f97d183132) | fix | avoid stripping unicode whitespace during url resolution |
| [73d8bbd27c](https://github.com/angular/angular/commit/73d8bbd27cb46495426d4132975a1355b47ad915) | fix | update domino to latest version |
<!-- CHANGELOG SPLIT MARKER -->
<a name="20.3.30"></a>
# 20.3.30 (2026-08-26)
### platform-server
| Commit | Type | Description |
| -- | -- | -- |
| [9339a7a2de](https://github.com/angular/angular/commit/9339a7a2de437ed93f9cc3da7f32d0100412d599) | fix | avoid stripping unicode whitespace during url resolution |
| [89b20568df](https://github.com/angular/angular/commit/89b20568dfaee1ec8e0b3bcf1872acdddd2f4fef) | fix | update domino to latest version |
<!-- CHANGELOG SPLIT MARKER -->
<a name="22.2.0-next.3"></a>
# 22.2.0-next.3 (2026-08-19)
### animations
| Commit | Type | Description |
| -- | -- | -- |
| [c73a001fbf](https://github.com/angular/angular/commit/c73a001fbf766b350fab4e64244cfaee23fdc8bc) | fix | detect object trigger values with Object.hasOwn |
### common
| Commit | Type | Description |
| -- | -- | -- |
| [46d2cb7ff0](https://github.com/angular/angular/commit/46d2cb7ff049dc4549080f64dceb1541b3a87cee) | fix | preserve literal key union in KeyValuePipe.transform() |
### compiler
| Commit | Type | Description |
| -- | -- | -- |
| [48a0fd6e8a](https://github.com/angular/angular/commit/48a0fd6e8a8d14bdc1d901ee5615f4b0ab698fe8) | feat | allow template to access private props |
### core
| Commit | Type | Description |
| -- | -- | -- |
| [dc65e3656f](https://github.com/angular/angular/commit/dc65e3656ff56fc6c5c03cf59991a6e8760b5279) | fix | accept readonly arrays for setClassMetadata decorators |
| [eee9ef4d09](https://github.com/angular/angular/commit/eee9ef4d09c091e9066ed6074a19c0eee0e84da2) | fix | allow readonly arrays in RawScopeInfoFromDecorator |
| [0ddbc47e7f](https://github.com/angular/angular/commit/0ddbc47e7f6a4eced3a1a986b260747dc37ee9c9) | fix | expose debuggableFn for non-computed signal graph nodes |
| [c2b14b7ab4](https://github.com/angular/angular/commit/c2b14b7ab44003d6fd1154300c5cc483644d80e6) | fix | prevent orphaned requestIdleCallback handle from re-entrant scheduling |
| [732e505018](https://github.com/angular/angular/commit/732e5050180190df24768ace7aa3e785ca0dcc0a) | fix | replace all hasOwnProperty usages with Object.hasOwn |
| [c658d73210](https://github.com/angular/angular/commit/c658d73210d1a2e808cfe659dddd48e8389086c7) | fix | stop running further effects once one destroys the view mid-flush |
| [c6e4a36be1](https://github.com/angular/angular/commit/c6e4a36be1246e0fbe06d591a69ac79cd74348d2) | fix | throw a descriptive error instead of crashing when a hydration node is missing |
| [4560f4fdcd](https://github.com/angular/angular/commit/4560f4fdcda4d55c9234f71fda2e73938cb55ed6) | fix | throw NG0500 instead of a raw TypeError on element hydration mismatch |
### forms
| Commit | Type | Description |
| -- | -- | -- |
| [c819880b91](https://github.com/angular/angular/commit/c819880b91bf5ff1c5aa71afd314ae7cae241065) | fix | report forbidden 2way bindings on when `FormField` is applied |
| [38d093232c](https://github.com/angular/angular/commit/38d093232c6d2fad99f2e361e9f6e31923507658) | fix | warn in dev mode when ngModel cannot reach parent NgForm across component boundary |
### http
| Commit | Type | Description |
| -- | -- | -- |
| [1a006a8f97](https://github.com/angular/angular/commit/1a006a8f9794240a950c388387bd8b9a671801da) | fix | cancel oversized fetch response bodies |
### language-server
| Commit | Type | Description |
| -- | -- | -- |
| [3f8d9d6ea6](https://github.com/angular/angular/commit/3f8d9d6ea6a0a0e7edbc12baf08b1f6db8c0f6b8) | fix | recover project for external templates in solution-style workspaces |
### platform-browser
| Commit | Type | Description |
| -- | -- | -- |
| [6f9a6bea50](https://github.com/angular/angular/commit/6f9a6bea50944fff894eb5629075b13f8c3090d7) | fix | disallow event handler attributes in Meta |
| [1cb3d606bf](https://github.com/angular/angular/commit/1cb3d606bf5bb01ec9c9e9289703a79f64c76e89) | fix | throw a descriptive error when insertBefore reference node is missing |
### router
| Commit | Type | Description |
| -- | -- | -- |
| [2720362818](https://github.com/angular/angular/commit/2720362818cdeb2a940171e4ab6f21cf78c6a302) | feat | add containsTree as public API |
| [f1a4c85212](https://github.com/angular/angular/commit/f1a4c8521296d7414a1eab84352ca7db966f35e2) | fix | pass correct component to canDeactivate for named outlets in componentless parent routes |
<!-- CHANGELOG SPLIT MARKER -->
<a name="22.1.3"></a>
# 22.1.3 (2026-08-19)
### animations
| Commit | Type | Description |
| -- | -- | -- |
| [d9620e0f1b](https://github.com/angular/angular/commit/d9620e0f1b85ff5896e4d725c52c5ad6c30e1af9) | fix | detect object trigger values with Object.hasOwn |
### common
| Commit | Type | Description |
| -- | -- | -- |
| [b3c78a5081](https://github.com/angular/angular/commit/b3c78a50816af5685b2a6ed51166d08941c09946) | fix | preserve literal key union in KeyValuePipe.transform() |
### compiler
| Commit | Type | Description |
| -- | -- | -- |
| [94f0b9a371](https://github.com/angular/angular/commit/94f0b9a3710ad000cf927caf5333db3582db1126) | fix | preserve &amp;ngsp; between sibling control flow blocks |
### core
| Commit | Type | Description |
| -- | -- | -- |
| [afe529cb2d](https://github.com/angular/angular/commit/afe529cb2d97e766b4f9014c3b6864420b7c8deb) | fix | accept readonly arrays for setClassMetadata decorators |
| [2c72fe3797](https://github.com/angular/angular/commit/2c72fe3797ff92ead7a2531d32665fac370b4688) | fix | allow readonly arrays in RawScopeInfoFromDecorator |
| [ef2ce9a098](https://github.com/angular/angular/commit/ef2ce9a09802faa5ffff0860f473c4018f25f2ac) | fix | expose debuggableFn for non-computed signal graph nodes |
| [7bcce260f5](https://github.com/angular/angular/commit/7bcce260f51dc8a099c8e63f1e1e3ff8f580dcf1) | fix | prevent orphaned requestIdleCallback handle from re-entrant scheduling |
| [44137117b3](https://github.com/angular/angular/commit/44137117b361f5bbad64e3d488e9aefd2f9ec5cb) | fix | replace all hasOwnProperty usages with Object.hasOwn |
| [85f12a5a13](https://github.com/angular/angular/commit/85f12a5a130aa7fd5cd21fce3eb786a2262480e1) | fix | stop running further effects once one destroys the view mid-flush |
| [c04931c88b](https://github.com/angular/angular/commit/c04931c88b1c7959cb95abd28cf5b3614d97e168) | fix | throw a descriptive error instead of crashing when a hydration node is missing |
| [601d1f6576](https://github.com/angular/angular/commit/601d1f6576b296748f56ffb50646bb1808a7c0fc) | fix | throw NG0500 instead of a raw TypeError on element hydration mismatch |
### forms
| Commit | Type | Description |
| -- | -- | -- |
| [83450d2924](https://github.com/angular/angular/commit/83450d292443cae811e87ab61020f3ed72d27d77) | fix | report forbidden 2way bindings on when `FormField` is applied |
| [5cb4ea7e35](https://github.com/angular/angular/commit/5cb4ea7e355bb9ea43efa15a03dc38c53d2e4faf) | fix | warn in dev mode when ngModel cannot reach parent NgForm across component boundary |
### http
| Commit | Type | Description |
| -- | -- | -- |
| [0cd635e9e2](https://github.com/angular/angular/commit/0cd635e9e2c409a045eaeaf920a226ef54aecc48) | fix | cancel oversized fetch response bodies |
### language-server
| Commit | Type | Description |
| -- | -- | -- |
| [14fbe04612](https://github.com/angular/angular/commit/14fbe04612d29460c5d01d6c65cde579f0a42e95) | fix | recover project for external templates in solution-style workspaces |
### platform-browser
| Commit | Type | Description |
| -- | -- | -- |
| [3ddcb1a101](https://github.com/angular/angular/commit/3ddcb1a10168ead616e523bbe044f4c39ada4cca) | fix | disallow event handler attributes in Meta |
| [640460d606](https://github.com/angular/angular/commit/640460d6065cdcd8fe407c7b35883f81f5045473) | fix | throw a descriptive error when insertBefore reference node is missing |
### router
| Commit | Type | Description |
| -- | -- | -- |
| [abe019d505](https://github.com/angular/angular/commit/abe019d5050532c87179cf46e840965e70a78b1b) | fix | pass correct component to canDeactivate for named outlets in componentless parent routes |
<!-- CHANGELOG SPLIT MARKER -->
<a name="20.3.29"></a>
# 20.3.29 (2026-08-19)
### platform-browser
| Commit | Type | Description |
| -- | -- | -- |
| [7538744c10](https://github.com/angular/angular/commit/7538744c1048701d12e86e992704ea5fa13df4f4) | fix | disallow event handler attributes in Meta |
<!-- CHANGELOG SPLIT MARKER -->
<a name="21.2.21"></a>
# 21.2.21 (2026-08-19)
### platform-browser
| Commit | Type | Description |
| -- | -- | -- |
| [c19a36c2fb](https://github.com/angular/angular/commit/c19a36c2fbc51f41ce974ea1fd87f9f56b3e2444) | fix | disallow event handler attributes in Meta |
<!-- CHANGELOG SPLIT MARKER -->
<a name="22.1.2"></a>
# 22.1.2 (2026-08-13)
### compiler
| Commit | Type | Description |
| -- | -- | -- |
| [76dff307b4](https://github.com/angular/angular/commit/76dff307b4c34bc4ceefcb1c7aa86c39daa5ea45) | fix | Generate correct expression for optional chaning. |
| [6f9a64e6f5](https://github.com/angular/angular/commit/6f9a64e6f5b1e3faada6514e8b090f9d61aa8e54) | fix | remove namespaced MathML script elements |
### compiler-cli
| Commit | Type | Description |
| -- | -- | -- |
| [e9660b1801](https://github.com/angular/angular/commit/e9660b1801a0280fa5ba41e7c330cb6d597199ec) | fix | correctly resolve symbol for SafePropertyRead in chained optional navigation |
| [ec6deea513](https://github.com/angular/angular/commit/ec6deea51329b0c8410c75943d90ce823081e216) | fix | record class extends clause references in DeferredSymbolTracker |
### core
| Commit | Type | Description |
| -- | -- | -- |
| [0df9d5eb65](https://github.com/angular/angular/commit/0df9d5eb6516106188b879536f7f813a9ca8811c) | fix | ensure i18n_util hasOwnProperty checks are safe for property renaming |
| [4d985a179e](https://github.com/angular/angular/commit/4d985a179e66428d46e60b4622435f88c39e1d2b) | fix | incorrect loop in defer blocks |
| [7b40456792](https://github.com/angular/angular/commit/7b404567924840aabdb955704a9bc04778b14f73) | fix | reject prefixed SVG script hosts |
### forms
| Commit | Type | Description |
| -- | -- | -- |
| [3b5c798072](https://github.com/angular/angular/commit/3b5c798072a3cf43b5e0ec0b3f4abc18bc6196a5) | fix | keep radio inputs in sync when values change |
### http
| Commit | Type | Description |
| -- | -- | -- |
| [4f7e9987fa](https://github.com/angular/angular/commit/4f7e9987fa35888de43895bfd859382dfa436e51) | fix | always decode JSON responses as UTF-8 |
### router
| Commit | Type | Description |
| -- | -- | -- |
| [2f82601662](https://github.com/angular/angular/commit/2f82601662a958b3accd4d22dff7ad4ec7c56d77) | fix | limit protocol-relative URL handling to serialization |
<!-- CHANGELOG SPLIT MARKER -->
<a name="22.2.0-next.2"></a>
# 22.2.0-next.2 (2026-08-13)
### compiler
| Commit | Type | Description |
| -- | -- | -- |
| [a4f1a94948](https://github.com/angular/angular/commit/a4f1a949486d20ce2bf9add427eea6629228b28b) | fix | do not encapsulate nested selectors if parent contains ::ng-deep |
| [d7b03f5523](https://github.com/angular/angular/commit/d7b03f5523d220c7935426d23ff5f103c96b00d9) | fix | Generate correct expression for optional chaning. |
| [107f6fa49d](https://github.com/angular/angular/commit/107f6fa49d00ec250b94a8d5f484bae2fc3d71c9) | fix | remove namespaced MathML script elements |
| [d0d7f57e08](https://github.com/angular/angular/commit/d0d7f57e0810a24ba16dbb1f2ab9f079a096fa3d) | fix | scope nested CSS rules |
### compiler-cli
| Commit | Type | Description |
| -- | -- | -- |
| [e8aa222e7d](https://github.com/angular/angular/commit/e8aa222e7d98d2e54c5604107a929a11cb1ef62d) | fix | correctly resolve symbol for SafePropertyRead in chained optional navigation |
| [8d6c925392](https://github.com/angular/angular/commit/8d6c925392c7230fbe6b39abdad05381672631b1) | fix | record class extends clause references in DeferredSymbolTracker |
### core
| Commit | Type | Description |
| -- | -- | -- |
| [8925a4ee49](https://github.com/angular/angular/commit/8925a4ee491aebaf6a1a74880c73dfb20e0a4ba1) | fix | ensure i18n_util hasOwnProperty checks are safe for property renaming |
| [565dbb2fe5](https://github.com/angular/angular/commit/565dbb2fe5406f2a86d43a5b3c6993434b7124a7) | fix | incorrect loop in defer blocks |
| [9f8e32616b](https://github.com/angular/angular/commit/9f8e32616b79892785051fab1c3120a770f5f682) | fix | reject prefixed SVG script hosts |
### forms
| Commit | Type | Description |
| -- | -- | -- |
| [9e37a58e14](https://github.com/angular/angular/commit/9e37a58e14bd7955d40255e42e20aa84fcde02f4) | fix | keep radio inputs in sync when values change |
### http
| Commit | Type | Description |
| -- | -- | -- |
| [09bc90003e](https://github.com/angular/angular/commit/09bc90003ed6e9f6c166e885503728af49d1af67) | fix | always decode JSON responses as UTF-8 |
### router
| Commit | Type | Description |
| -- | -- | -- |
| [b65dea4f03](https://github.com/angular/angular/commit/b65dea4f03e5fc01093a718c990c72ae9165c43f) | feat | allow throwing RedirectCommand to trigger redirects |
| [435f8b2b8b](https://github.com/angular/angular/commit/435f8b2b8b67277fc69888911251d14b32705828) | fix | limit protocol-relative URL handling to serialization |
<!-- CHANGELOG SPLIT MARKER -->
<a name="20.3.28"></a>
# 20.3.28 (2026-08-13)
### core
| Commit | Type | Description |
| -- | -- | -- |
| [2f96c8020f](https://github.com/angular/angular/commit/2f96c8020f85ccb715a76de4b79a0c680c2c7264) | fix | sanitize host bindings on concrete hosts |
### http
| Commit | Type | Description |
| -- | -- | -- |
| [969133d426](https://github.com/angular/angular/commit/969133d426723336eb04ab839ff30d884ce54dd9) | fix | match header values exactly when deleting |
| [29dd26bd71](https://github.com/angular/angular/commit/29dd26bd7115f90ea059a1a5d174203255033e36) | fix | preserve immutability of materialized clones |
| [e4c416c20a](https://github.com/angular/angular/commit/e4c416c20a1cb222ce73d29c035452b257380c56) | fix | run root interceptors in the terminal request chain |
<!-- CHANGELOG SPLIT MARKER -->
<a name="21.2.20"></a>
# 21.2.20 (2026-08-12)
### core
| Commit | Type | Description |
| -- | -- | -- |
| [6afe6fa781](https://github.com/angular/angular/commit/6afe6fa781c2f0931f0aedd729b9884a8fe212ee) | fix | sanitize host bindings on concrete hosts |
### http
| Commit | Type | Description |
| -- | -- | -- |
| [fec5977df4](https://github.com/angular/angular/commit/fec5977df4dda3a10d5ce2923e3e06d86ba11ee7) | fix | match header values exactly when deleting |
| [e33d69a71c](https://github.com/angular/angular/commit/e33d69a71c5beb8fe5785b53fd6b37658334e8e0) | fix | preserve immutability of materialized clones |
| [caf616670f](https://github.com/angular/angular/commit/caf616670fd20d528aa69e0131cc17d60f0cc27d) | fix | run root interceptors in the terminal request chain |
<!-- CHANGELOG SPLIT MARKER -->
<a name="22.2.0-next.1"></a>
# 22.2.0-next.1 (2026-08-07)
### core
| Commit | Type | Description |
| -- | -- | -- |
| [cfe2cda110](https://github.com/angular/angular/commit/cfe2cda1100bb84e2ecf954687126c4d30212d07) | fix | initialize hydration triggers after late runtime activation |
| [e43eb96341](https://github.com/angular/angular/commit/e43eb96341b692525743a1f3aa1380ce21a9dd02) | fix | warn when style property bindings receive invalid values |
### http
| Commit | Type | Description |
| -- | -- | -- |
| [ef4dfead83](https://github.com/angular/angular/commit/ef4dfead83ea8b3843b98a17ac572f2a380b1c46) | fix | avoid aborting completed requests in FetchBackend |
| [6a0789dc7f](https://github.com/angular/angular/commit/6a0789dc7f0ac9d1249c4d9f315e83424867169b) | fix | respect content-type charset in fetch backend text decoder |
| [bb78286e5e](https://github.com/angular/angular/commit/bb78286e5e729d1e1fcfd2733d88351a38083ef5) | fix | run root interceptors in the terminal request chain |
| [280d09b160](https://github.com/angular/angular/commit/280d09b16011e4f53f9e696fd95964943c6dcbfa) | fix | strip RFC 6265 DQUOTE characters and handle URIError in parseCookieValue |
<!-- CHANGELOG SPLIT MARKER -->
<a name="22.1.1"></a>
# 22.1.1 (2026-08-06)
### core
| Commit | Type | Description |
| -- | -- | -- |
| [aa6d3189c1](https://github.com/angular/angular/commit/aa6d3189c141a9247615ed14d856813ea1b95c8d) | fix | initialize hydration triggers after late runtime activation |
| [deecb301c8](https://github.com/angular/angular/commit/deecb301c8aab4c22b80eaa505042311ee938b67) | fix | warn when style property bindings receive invalid values |
### http
| Commit | Type | Description |
| -- | -- | -- |
| [ac3728e79f](https://github.com/angular/angular/commit/ac3728e79fd8fae7a4b38e20d152c5a734a6e3fc) | fix | avoid aborting completed requests in FetchBackend |
| [688a0a7118](https://github.com/angular/angular/commit/688a0a7118215bcab10004a3bf2667773fefdf74) | fix | respect content-type charset in fetch backend text decoder |
| [a13b968451](https://github.com/angular/angular/commit/a13b9684510dfd04e18f3e1d6686d556c051f34e) | fix | run root interceptors in the terminal request chain |
| [32af9b525e](https://github.com/angular/angular/commit/32af9b525efe005998a9cd02aebfcc08e6793cf2) | fix | strip RFC 6265 DQUOTE characters and handle URIError in parseCookieValue |
<!-- CHANGELOG SPLIT MARKER -->
<a name="22.2.0-next.0"></a>
# 22.2.0-next.0 (2026-07-29)
### forms
| Commit | Type | Description |
| -- | -- | -- |
| [d5e8b1ef7a](https://github.com/angular/angular/commit/d5e8b1ef7a02c84d4fd70a6b4d748ead9ff815bf) | feat | allow permanent hidden fields in signal forms |
<!-- CHANGELOG SPLIT MARKER -->
<a name="21.2.19"></a>
# 21.2.19 (2026-07-29)
### compiler
| Commit | Type | Description |
| -- | -- | -- |
| [e2660c3dee](https://github.com/angular/angular/commit/e2660c3deeccb86471f9c11b7e65ebcc89d84e9e) | fix | disallow i18n event attributes |
| [7b884f585a](https://github.com/angular/angular/commit/7b884f585abf50480c271d234c892c141eb61e2c) | fix | restrict possible event handler check to property names longer than 2 characters |
### http
| Commit | Type | Description |
| -- | -- | -- |
| [948a8d6831](https://github.com/angular/angular/commit/948a8d6831e8920b54663ec79421da95210e0e35) | fix | distinguish repeated transfer cache params |
| [9949dccce1](https://github.com/angular/angular/commit/9949dccce164638597496a6b3881043117dfd1df) | fix | enable xsrf for root-provided HttpClient |
### platform-server
| Commit | Type | Description |
| -- | -- | -- |
| [f34a93c946](https://github.com/angular/angular/commit/f34a93c946c017eff526cfb0c8d17f51e97f01e4) | fix | update domino to latest version |
<!-- CHANGELOG SPLIT MARKER -->
<a name="22.1.0"></a>
# 22.1.0 (2026-07-29)
## Deprecations
### http
- `HttpClient.jsonp`, `HttpClientJsonpModule`, and related JSONP classes/functions are deprecated. Use standard HTTP requests instead.
### common
| Commit | Type | Description |
| -- | -- | -- |
| [1ad6824d0d](https://github.com/angular/angular/commit/1ad6824d0dd17a0515ad0544cf435d1e3af6982e) | fix | skip transfer cache for uncacheable HTTP traffic ([#69017](https://github.com/angular/angular/pull/69017)) |
### compiler
| Commit | Type | Description |
| -- | -- | -- |
| [af5e4e1131](https://github.com/angular/angular/commit/af5e4e113130427ba8152166b4d904dd177c982e) | feat | Add an error for --global-foo cases ([#68846](https://github.com/angular/angular/pull/68846)) |
| [d579ecaf73](https://github.com/angular/angular/commit/d579ecaf733db325f628cc11a28688fa0dad85ee) | feat | Disable &apos;--global-&apos; error outside of g3 ([#68846](https://github.com/angular/angular/pull/68846)) |
| [f98547675c](https://github.com/angular/angular/commit/f98547675cd2026bb755671b28dea4e6d48eb7bb) | feat | Namespace CSS variables to the app ([#68846](https://github.com/angular/angular/pull/68846)) |
| [8c8b2f7783](https://github.com/angular/angular/commit/8c8b2f77831236b23ca99ccb62e6197ff43b400d) | feat | Support css var namespacing in properties ([#68846](https://github.com/angular/angular/pull/68846)) |
| [292199aa4d](https://github.com/angular/angular/commit/292199aa4dd4cbf09f14c20bd7049d6ab55d83f4) | fix | permissive whitespace parsing in default never blocks |
| [25c744c4d0](https://github.com/angular/angular/commit/25c744c4d0abd3bfe863ed2d528fea756ec5801b) | fix | support foreign components defined outside top-level scope |
| [5bd00add07](https://github.com/angular/angular/commit/5bd00add071e404ddab318eea82db81b5715a194) | fix | support foreign components inside control flow blocks ([#69674](https://github.com/angular/angular/pull/69674)) |
| [2e442f7876](https://github.com/angular/angular/commit/2e442f787627a1f44d8b6463a1cb979ef6f7e049) | perf | do not emit tag name when control flow root is foreign component ([#69674](https://github.com/angular/angular/pull/69674)) |
### compiler-cli
| Commit | Type | Description |
| -- | -- | -- |
| [aeb55c8bc1](https://github.com/angular/angular/commit/aeb55c8bc101dc313dc64f83f4428ddfad28d54e) | fix | allow passing uninvoked signals as foreign component props |
| [7c60a98b3c](https://github.com/angular/angular/commit/7c60a98b3ccc2090f3bfb3c76d6207f37a5df0ff) | fix | support import aliases in foreignImports ([#68674](https://github.com/angular/angular/pull/68674)) |
### core
| Commit | Type | Description |
| -- | -- | -- |
| [124ba10ead](https://github.com/angular/angular/commit/124ba10ead58c9f93b0b74c4102022c4674db1f5) | feat | add custom set option to linkedSignal |
| [eab4847a8b](https://github.com/angular/angular/commit/eab4847a8bd1b47ba630ca9b20c85770f990c717) | feat | Adds deep linking from Performance panel to DevTools |
| [091456a214](https://github.com/angular/angular/commit/091456a2146d81b225fa9322a9ebde24695aaad0) | fix | account for namespaces in host binding sanitization ([#69558](https://github.com/angular/angular/pull/69558)) |
| [b3748e9fe4](https://github.com/angular/angular/commit/b3748e9fe4a4f9f56f9843b448db59674ad01452) | fix | correct container anchor collection order to match DOM layout |
| [11b206b919](https://github.com/angular/angular/commit/11b206b919074149f2db6e3640a55284aecfe48a) | fix | introduce disposal mechanism for Angular views in foreign `@content` |
| [56607967db](https://github.com/angular/angular/commit/56607967dbba25e63f86fd128ec0c1c4ce3e5232) | fix | introduce logical-only containers for foreign content |
| [23cf1a828b](https://github.com/angular/angular/commit/23cf1a828be0d5bd29ce84cb904b5df9ed67f544) | fix | sanitize host bindings on concrete hosts ([#69558](https://github.com/angular/angular/pull/69558)) |
| [0c07356c5c](https://github.com/angular/angular/commit/0c07356c5c9dc76a532d7d4ecf43d8fce81b405b) | fix | set current tnode in foreign component instruction on reuse |
### forms
| Commit | Type | Description |
| -- | -- | -- |
| [bbbd357bd2](https://github.com/angular/angular/commit/bbbd357bd2b29f0b04578e3fa651418df4aefc67) | fix | add utility to assert that value is a field tree |
### http
| Commit | Type | Description |
| -- | -- | -- |
| [e3630c23c5](https://github.com/angular/angular/commit/e3630c23c59445fc8bace5117283f20d6379dded) | feat | add options to allow caching of credentialed and non-cacheable HTTP requests |
| [ec16a3d6c6](https://github.com/angular/angular/commit/ec16a3d6c6785992197bc0ae81afb1e699232f64) | fix | enable xsrf for root-provided HttpClient |
| [39e362eea5](https://github.com/angular/angular/commit/39e362eea5603fa36bda436c9f03d9304960c1ea) | fix | match header values exactly when deleting |
| [be46ca8696](https://github.com/angular/angular/commit/be46ca869642aefebb0236fd87843c9e2367ea81) | fix | preserve immutability of materialized clones |
| [c0cbd46bd7](https://github.com/angular/angular/commit/c0cbd46bd77443749444f816de1e0df4ede28c96) | fix | skip transfer cache for fetch credentialed requests ([#69017](https://github.com/angular/angular/pull/69017)) |
| [af04e266cc](https://github.com/angular/angular/commit/af04e266cc9df4502802b565ccbb9928f10f51f5) | refactor | deprecate jsonp support |
### language-service
| Commit | Type | Description |
| -- | -- | -- |
| [7f0265e43a](https://github.com/angular/angular/commit/7f0265e43ab9f6b1ab9ad0ae84e70a40db417417) | feat | compile non-exported classes if standalone ([#68454](https://github.com/angular/angular/pull/68454)) |
| [4f9c824dd9](https://github.com/angular/angular/commit/4f9c824dd9ec4462d29ed07b5e7916be86c19e84) | feat | Typecheck templates which would require inline typecheck blocks ([#68454](https://github.com/angular/angular/pull/68454)) |
| [a99fb915c0](https://github.com/angular/angular/commit/a99fb915c09b352ac67f24e71af84cda30bcbf3e) | fix | account for strictTemplates being enabled by default |
### migrations
| Commit | Type | Description |
| -- | -- | -- |
| [c75ff0255c](https://github.com/angular/angular/commit/c75ff0255c3d5ee16f94a9969cd611ab2e02d4a2) | feat | add migration from injectable to service |
| [5d5b2ea72d](https://github.com/angular/angular/commit/5d5b2ea72d03ffc2350e66e202cbca0bf491a5f2) | fix | correctly detect `then`/`else` keywords in control flow migration |
### platform-server
| Commit | Type | Description |
| -- | -- | -- |
| [cf9d7fa0f8](https://github.com/angular/angular/commit/cf9d7fa0f8090bae61bb4fa2f50d6f0570412e6e) | fix | harden platform location origin validation during SSR ([#69184](https://github.com/angular/angular/pull/69184)) |
### router
| Commit | Type | Description |
| -- | -- | -- |
| [97a3fd6a55](https://github.com/angular/angular/commit/97a3fd6a55eccabf7c9adfb33f745c8d1da6d3e9) | feat | handle null and undefined inputs in RouterLinkActive |
<!-- CHANGELOG SPLIT MARKER -->
<a name="20.3.27"></a>
# 20.3.27 (2026-07-29)
### compiler
| Commit | Type | Description |
| -- | -- | -- |
| [5dbcd0ee16](https://github.com/angular/angular/commit/5dbcd0ee16011369bec00e10e22cbcae6ebabd7a) | fix | disallow i18n event attributes |
| [db0d4a1a39](https://github.com/angular/angular/commit/db0d4a1a39db2dc0773f3bc9d7a0e1cfc59fa251) | fix | restrict possible event handler check to property names longer than 2 characters |
### http
| Commit | Type | Description |
| -- | -- | -- |
| [a64e2883e9](https://github.com/angular/angular/commit/a64e2883e9dc4abdac70209129be303de79e5b2b) | fix | distinguish repeated transfer cache params |
### platform-server
| Commit | Type | Description |
| -- | -- | -- |
| [6f80cca0b8](https://github.com/angular/angular/commit/6f80cca0b8af23b37f24fb8ee0229b6901d01df2) | fix | update domino to latest version |
<!-- CHANGELOG SPLIT MARKER -->
<a name="22.0.8"></a>
# 22.0.8 (2026-07-22)
### common
@@ -24,50 +652,6 @@
<!-- CHANGELOG SPLIT MARKER -->
<a name="22.1.0-next.6"></a>
# 22.1.0-next.6 (2026-07-15)
### common
| Commit | Type | Description |
| -- | -- | -- |
| [359fb503b8](https://github.com/angular/angular/commit/359fb503b8f262db7a053febc18c416f966e8185) | fix | avoid prototype lookups in date format caches |
### compiler
| Commit | Type | Description |
| -- | -- | -- |
| [da52137724](https://github.com/angular/angular/commit/da52137724b8cce79b036026fa1aa07bc730389c) | fix | parsing of an empty template literal interpolation |
| [5bd00add07](https://github.com/angular/angular/commit/5bd00add071e404ddab318eea82db81b5715a194) | fix | support foreign components inside control flow blocks ([#69674](https://github.com/angular/angular/pull/69674)) |
| [2e442f7876](https://github.com/angular/angular/commit/2e442f787627a1f44d8b6463a1cb979ef6f7e049) | perf | do not emit tag name when control flow root is foreign component ([#69674](https://github.com/angular/angular/pull/69674)) |
### compiler-cli
| Commit | Type | Description |
| -- | -- | -- |
| [ae0ec7315c](https://github.com/angular/angular/commit/ae0ec7315c6fbc45ce40581a08a8243a008a91ec) | fix | re-tag SourceFiles after TsCreateProgramDriver.updateFiles() |
### core
| Commit | Type | Description |
| -- | -- | -- |
| [9b9b0e93c9](https://github.com/angular/angular/commit/9b9b0e93c90c0ecfc7f075a64908cce00eb03f38) | fix | allow static attributes for explicit input transforms |
| [68ac204074](https://github.com/angular/angular/commit/68ac20407466f0d3e6f5c5016f1273672b5d2b34) | fix | ignore processing instruction syntax in templates |
| [0ae6d81ed2](https://github.com/angular/angular/commit/0ae6d81ed2a83b1ebbb75dabc53af77b8db86c41) | fix | preserve explicit input transform write type |
### forms
| Commit | Type | Description |
| -- | -- | -- |
| [5cb8c733a3](https://github.com/angular/angular/commit/5cb8c733a39a0128eada61061e8d9ff352a967d0) | fix | allow multiple async validators |
| [2e32872720](https://github.com/angular/angular/commit/2e32872720ee95f24029d20e24e91edbc2054b1e) | fix | preserve intermediate number values in signal forms |
| [2e0cb52dbf](https://github.com/angular/angular/commit/2e0cb52dbf0f519ffc5c99b95e5f4b965fa332a7) | fix | prevent stale CVA writeback during debounce |
### http
| Commit | Type | Description |
| -- | -- | -- |
| [6d043f8657](https://github.com/angular/angular/commit/6d043f86573e317f20039ccec031d6e63c01b656) | fix | prevent interceptor signal reads from leaking into calling reactive contexts |
### localize
| Commit | Type | Description |
| -- | -- | -- |
| [5de0ea5f23](https://github.com/angular/angular/commit/5de0ea5f23f12542ebdd9d28f225e1a224021d92) | fix | build runtime translations map with a null prototype |
| [2a4f582731](https://github.com/angular/angular/commit/2a4f58273112d0ed508fac624e56f56d97e511fa) | fix | use Object.hasOwn for placeholder lookup in translate |
### platform-browser
| Commit | Type | Description |
| -- | -- | -- |
| [fb6b354fbd](https://github.com/angular/angular/commit/fb6b354fbd3ecf6c2cdf211ca054fb44226e587b) | fix | prevent ReDoS in SOURCEMAP_URL_REGEXP |
<!-- CHANGELOG SPLIT MARKER -->
<a name="22.0.7"></a>
# 22.0.7 (2026-07-15)
### common
@@ -156,50 +740,6 @@
<!-- CHANGELOG SPLIT MARKER -->
<a name="22.1.0-next.5"></a>
# 22.1.0-next.5 (2026-07-08)
### compiler
| Commit | Type | Description |
| -- | -- | -- |
| [af5e4e1131](https://github.com/angular/angular/commit/af5e4e113130427ba8152166b4d904dd177c982e) | feat | Add an error for --global-foo cases ([#68846](https://github.com/angular/angular/pull/68846)) |
| [d579ecaf73](https://github.com/angular/angular/commit/d579ecaf733db325f628cc11a28688fa0dad85ee) | feat | Disable &apos;--global-&apos; error outside of g3 ([#68846](https://github.com/angular/angular/pull/68846)) |
| [f98547675c](https://github.com/angular/angular/commit/f98547675cd2026bb755671b28dea4e6d48eb7bb) | feat | Namespace CSS variables to the app ([#68846](https://github.com/angular/angular/pull/68846)) |
| [8c8b2f7783](https://github.com/angular/angular/commit/8c8b2f77831236b23ca99ccb62e6197ff43b400d) | feat | Support css var namespacing in properties ([#68846](https://github.com/angular/angular/pull/68846)) |
| [997b772f28](https://github.com/angular/angular/commit/997b772f28f92de64b416263bb6dc02dc9e7598d) | fix | use regular optional chaining expression for safe function calls in TCBs |
### compiler-cli
| Commit | Type | Description |
| -- | -- | -- |
| [eb2a8ff63f](https://github.com/angular/angular/commit/eb2a8ff63f858110c6c77289f3c0150e49216624) | fix | apply debugName transform to required signal queries |
| [eac363e92e](https://github.com/angular/angular/commit/eac363e92e00d26137a93d91ab3e2127f3519b1d) | fix | detect uninvoked signals in bound expressions using ternary |
### core
| Commit | Type | Description |
| -- | -- | -- |
| [eab4847a8b](https://github.com/angular/angular/commit/eab4847a8bd1b47ba630ca9b20c85770f990c717) | feat | Adds deep linking from Performance panel to DevTools |
### forms
| Commit | Type | Description |
| -- | -- | -- |
| [953cdfd66a](https://github.com/angular/angular/commit/953cdfd66a08e0234f7db80ba7ecaab5e3067c62) | fix | allow multiple async validators |
### forms/signals
| Commit | Type | Description |
| -- | -- | -- |
| [b542302a23](https://github.com/angular/angular/commit/b542302a23c74a131ebcef064cc73d8693c696a1) | fix | make extractValue reactive for compat AbstractControl values |
### http
| Commit | Type | Description |
| -- | -- | -- |
| [e3630c23c5](https://github.com/angular/angular/commit/e3630c23c59445fc8bace5117283f20d6379dded) | feat | add options to allow caching of credentialed and non-cacheable HTTP requests |
### migrations
| Commit | Type | Description |
| -- | -- | -- |
| [0a5a2b768c](https://github.com/angular/angular/commit/0a5a2b768c5f7f17c65488c5279549d22785488c) | fix | preserve transitive NgModule references when pruning |
| [fbb705e6fd](https://github.com/angular/angular/commit/fbb705e6fd1f217a9576dfb76b193d08da1a2195) | fix | remove stale model import in model-output migration |
### router
| Commit | Type | Description |
| -- | -- | -- |
| [cbbb1d8ba1](https://github.com/angular/angular/commit/cbbb1d8ba12ca36d27a2523edfbd1466458c5036) | fix | handle outlet named __proto__ in segment group maps |
| [e5c37f21de](https://github.com/angular/angular/commit/e5c37f21de3d9801859641b2ffb180d90b3bc00d) | fix | use safe hasOwnProperty when parsing query params |
<!-- CHANGELOG SPLIT MARKER -->
<a name="22.0.6"></a>
# 22.0.6 (2026-07-08)
### compiler
@@ -228,32 +768,6 @@
<!-- CHANGELOG SPLIT MARKER -->
<a name="22.1.0-next.4"></a>
# 22.1.0-next.4 (2026-07-01)
### common
| Commit | Type | Description |
| -- | -- | -- |
| [311aff05aa](https://github.com/angular/angular/commit/311aff05aa2cd55d49a0c259e7b240266ecee7bd) | fix | use Object.hasOwn in I18nSelectPipe to handle null-prototype and shadowed mappings |
### compiler
| Commit | Type | Description |
| -- | -- | -- |
| [292199aa4d](https://github.com/angular/angular/commit/292199aa4dd4cbf09f14c20bd7049d6ab55d83f4) | fix | permissive whitespace parsing in default never blocks |
### compiler-cli
| Commit | Type | Description |
| -- | -- | -- |
| [165995285c](https://github.com/angular/angular/commit/165995285ca95302b2a2f01f5b42645b0a9eb022) | fix | include toSignal in debugName transform |
### core
| Commit | Type | Description |
| -- | -- | -- |
| [74638cab84](https://github.com/angular/angular/commit/74638cab841c553fc85fc23a0e5052931a636531) | fix | improve input writes migration in best effort mode |
| [d7f70616a0](https://github.com/angular/angular/commit/d7f70616a03aa00274f0a91c49f7a6fe4b2e3535) | fix | reject dynamic script host elements |
### router
| Commit | Type | Description |
| -- | -- | -- |
| [a74801c59c](https://github.com/angular/angular/commit/a74801c59cd453382ac177e85bbd51f20fbc2ad5) | fix | fix malformed jsdoc comment for RouterLinkWithHref export |
<!-- CHANGELOG SPLIT MARKER -->
<a name="22.0.5"></a>
# 22.0.5 (2026-07-01)
### common
@@ -276,15 +790,6 @@
<!-- CHANGELOG SPLIT MARKER -->
<a name="22.1.0-next.3"></a>
# 22.1.0-next.3 (2026-06-26)
### migrations
| Commit | Type | Description |
| -- | -- | -- |
| [26b0c719ef](https://github.com/angular/angular/commit/26b0c719effc6b4ff649803033e2e6f4b66c99f7) | fix | resolve migration failure when tsconfig specifies rootDir |
<!-- CHANGELOG SPLIT MARKER -->
<a name="22.0.4"></a>
# 22.0.4 (2026-06-26)
### migrations
@@ -294,44 +799,6 @@
<!-- CHANGELOG SPLIT MARKER -->
<a name="22.1.0-next.2"></a>
# 22.1.0-next.2 (2026-06-25)
### compiler
| Commit | Type | Description |
| -- | -- | -- |
| [ecd047578e](https://github.com/angular/angular/commit/ecd047578e1eb3a4cdfee7bfee9b1422e5a5db2c) | fix | account for NgModule dependencies in JIT-compiled partial declarations |
### compiler-cli
| Commit | Type | Description |
| -- | -- | -- |
| [8b2785b597](https://github.com/angular/angular/commit/8b2785b597ccf11dae3cd3f2e7ee97480233e4d6) | fix | report diagnostic instead of crashing on malformed host binding |
### core
| Commit | Type | Description |
| -- | -- | -- |
| [91d168e74b](https://github.com/angular/angular/commit/91d168e74b7cdcd1292772b1052888b83de5f3af) | fix | avoid uncaught promise errors in injectAsync prefetching |
### http
| Commit | Type | Description |
| -- | -- | -- |
| [f76e8a98c1](https://github.com/angular/angular/commit/f76e8a98c1005827e562811d5d08ffd35493f9f5) | fix | prevent caching of responses with Set-Cookie headers |
### migrations
| Commit | Type | Description |
| -- | -- | -- |
| [c75ff0255c](https://github.com/angular/angular/commit/c75ff0255c3d5ee16f94a9969cd611ab2e02d4a2) | feat | add migration from injectable to service |
### router
| Commit | Type | Description |
| -- | -- | -- |
| [97a3fd6a55](https://github.com/angular/angular/commit/97a3fd6a55eccabf7c9adfb33f745c8d1da6d3e9) | feat | handle null and undefined inputs in RouterLinkActive |
### service-worker
| Commit | Type | Description |
| -- | -- | -- |
| [716f9eb032](https://github.com/angular/angular/commit/716f9eb032baea351c94352f8e36dac8bc82468e) | fix | preserve referrer in asset requests |
| [6f98f98f1f](https://github.com/angular/angular/commit/6f98f98f1f410e5343852e3c8f98c32267b29e01) | fix | preserve referrer policy in asset requests |
### upgrade
| Commit | Type | Description |
| -- | -- | -- |
| [8d31b82116](https://github.com/angular/angular/commit/8d31b82116c4128a00302abd7c4dfc035c77789e) | fix | support model() signals in downgradeComponent |
<!-- CHANGELOG SPLIT MARKER -->
<a name="22.0.3"></a>
# 22.0.3 (2026-06-25)
### compiler
@@ -363,37 +830,6 @@
<!-- CHANGELOG SPLIT MARKER -->
<a name="22.1.0-next.1"></a>
# 22.1.0-next.1 (2026-06-17)
### common
| Commit | Type | Description |
| -- | -- | -- |
| [a69e56df71](https://github.com/angular/angular/commit/a69e56df718ccf139f0b3f8f528c87a6818d9ba2) | fix | escape anchor fragment in shadow DOM name selector |
### compiler
| Commit | Type | Description |
| -- | -- | -- |
| [417a4071a7](https://github.com/angular/angular/commit/417a4071a776464d549509ed3aec121dbd2fda5e) | fix | restrict possible event handler check to property names longer than 2 characters |
### core
| Commit | Type | Description |
| -- | -- | -- |
| [98f42eaaae](https://github.com/angular/angular/commit/98f42eaaaeea9eb5ca5e8fc905a456df25b403e9) | fix | avoid caching missing locale data |
| [ea1a3ed64c](https://github.com/angular/angular/commit/ea1a3ed64c538fa768fff586796ece6ffb8b6598) | fix | escape overlapping comment delimiters in escapeCommentText |
| [8cf7731468](https://github.com/angular/angular/commit/8cf7731468f2afcfa03cbddefe90b57dff680d30) | fix | guard against DOM clobbering in declareExperimentalWebMcpTool |
| [6b5616b2c7](https://github.com/angular/angular/commit/6b5616b2c7745ff4777f8d2ea038ebd297aeeb38) | fix | preserve leave animation for sibling instances sharing a TNode |
| [28cb15a2bb](https://github.com/angular/angular/commit/28cb15a2bb79a072fce661b14e87954096ee9366) | fix | prevent unsubscribe during emit from throwing off other listeners |
| [0152e3cbdf](https://github.com/angular/angular/commit/0152e3cbdf5d31a5283059374e4a6352782fcd0f) | fix | treat iframe credentialless as security-sensitive |
| [327744ac17](https://github.com/angular/angular/commit/327744ac171b30e54ee46d20bb13482fd1904f9a) | perf | detect existing signal dependency without checking all producer links |
### http
| Commit | Type | Description |
| -- | -- | -- |
| [a6c7fc5c13](https://github.com/angular/angular/commit/a6c7fc5c13e6e494a4c9bd8e773b8d4b2a99b20c) | fix | distinguish repeated transfer cache params |
### migrations
| Commit | Type | Description |
| -- | -- | -- |
| [58efd86c78](https://github.com/angular/angular/commit/58efd86c78f244689b1bad5285aed9418a0772d1) | fix | migration skip any target are not build or test |
<!-- CHANGELOG SPLIT MARKER -->
<a name="22.0.2"></a>
# 22.0.2 (2026-06-17)
### common
@@ -427,49 +863,6 @@
<!-- CHANGELOG SPLIT MARKER -->
<a name="22.1.0-next.0"></a>
# 22.1.0-next.0 (2026-06-10)
## Deprecations
### http
- `HttpClient.jsonp`, `HttpClientJsonpModule`, and related JSONP classes/functions are deprecated. Use standard HTTP requests instead.
### common
| Commit | Type | Description |
| -- | -- | -- |
| [1ad6824d0d](https://github.com/angular/angular/commit/1ad6824d0dd17a0515ad0544cf435d1e3af6982e) | fix | skip transfer cache for uncacheable HTTP traffic ([#69017](https://github.com/angular/angular/pull/69017)) |
### compiler
| Commit | Type | Description |
| -- | -- | -- |
| [25c744c4d0](https://github.com/angular/angular/commit/25c744c4d0abd3bfe863ed2d528fea756ec5801b) | fix | support foreign components defined outside top-level scope |
### compiler-cli
| Commit | Type | Description |
| -- | -- | -- |
| [aeb55c8bc1](https://github.com/angular/angular/commit/aeb55c8bc101dc313dc64f83f4428ddfad28d54e) | fix | allow passing uninvoked signals as foreign component props |
| [7c60a98b3c](https://github.com/angular/angular/commit/7c60a98b3ccc2090f3bfb3c76d6207f37a5df0ff) | fix | support import aliases in foreignImports ([#68674](https://github.com/angular/angular/pull/68674)) |
### core
| Commit | Type | Description |
| -- | -- | -- |
| [124ba10ead](https://github.com/angular/angular/commit/124ba10ead58c9f93b0b74c4102022c4674db1f5) | feat | add custom set option to linkedSignal |
| [b3748e9fe4](https://github.com/angular/angular/commit/b3748e9fe4a4f9f56f9843b448db59674ad01452) | fix | correct container anchor collection order to match DOM layout |
| [11b206b919](https://github.com/angular/angular/commit/11b206b919074149f2db6e3640a55284aecfe48a) | fix | introduce disposal mechanism for Angular views in foreign `@content` |
| [56607967db](https://github.com/angular/angular/commit/56607967dbba25e63f86fd128ec0c1c4ce3e5232) | fix | introduce logical-only containers for foreign content |
| [0c07356c5c](https://github.com/angular/angular/commit/0c07356c5c9dc76a532d7d4ecf43d8fce81b405b) | fix | set current tnode in foreign component instruction on reuse |
### http
| Commit | Type | Description |
| -- | -- | -- |
| [c0cbd46bd7](https://github.com/angular/angular/commit/c0cbd46bd77443749444f816de1e0df4ede28c96) | fix | skip transfer cache for fetch credentialed requests ([#69017](https://github.com/angular/angular/pull/69017)) |
| [af04e266cc](https://github.com/angular/angular/commit/af04e266cc9df4502802b565ccbb9928f10f51f5) | refactor | deprecate jsonp support |
### language-service
| Commit | Type | Description |
| -- | -- | -- |
| [7f0265e43a](https://github.com/angular/angular/commit/7f0265e43ab9f6b1ab9ad0ae84e70a40db417417) | feat | compile non-exported classes if standalone ([#68454](https://github.com/angular/angular/pull/68454)) |
| [4f9c824dd9](https://github.com/angular/angular/commit/4f9c824dd9ec4462d29ed07b5e7916be86c19e84) | feat | Typecheck templates which would require inline typecheck blocks ([#68454](https://github.com/angular/angular/pull/68454)) |
### platform-server
| Commit | Type | Description |
| -- | -- | -- |
| [cf9d7fa0f8](https://github.com/angular/angular/commit/cf9d7fa0f8090bae61bb4fa2f50d6f0570412e6e) | fix | harden platform location origin validation during SSR ([#69184](https://github.com/angular/angular/pull/69184)) |
<!-- CHANGELOG SPLIT MARKER -->
<a name="22.0.1"></a>
# 22.0.1 (2026-06-10)
## Deprecations
+22 -22
View File
@@ -4,42 +4,42 @@ module(
name = "angular",
)
bazel_dep(name = "rules_pkg", version = "1.2.0")
bazel_dep(name = "rules_pkg", version = "1.3.0")
bazel_dep(name = "rules_nodejs", version = "6.7.5")
bazel_dep(name = "aspect_rules_ts", version = "3.9.2")
bazel_dep(name = "aspect_rules_js", version = "3.2.3")
bazel_dep(name = "aspect_rules_esbuild", version = "0.26.0")
bazel_dep(name = "aspect_rules_ts", version = "3.10.1")
bazel_dep(name = "aspect_rules_js", version = "3.4.1")
bazel_dep(name = "aspect_rules_esbuild", version = "0.27.0")
bazel_dep(name = "aspect_rules_jasmine", version = "2.0.4")
bazel_dep(name = "aspect_rules_rollup", version = "2.0.1")
bazel_dep(name = "bazel_skylib", version = "1.9.0")
bazel_dep(name = "bazel_lib", version = "3.5.0")
bazel_dep(name = "tar.bzl", version = "0.10.7")
bazel_dep(name = "yq.bzl", version = "0.3.6")
bazel_dep(name = "bazel_skylib", version = "1.9.2")
bazel_dep(name = "bazel_lib", version = "3.7.2")
bazel_dep(name = "tar.bzl", version = "0.10.8")
bazel_dep(name = "yq.bzl", version = "0.4.0")
bazel_dep(name = "rules_angular")
git_override(
module_name = "rules_angular",
commit = "db445dccfe48aad20be6ee3df456df91660ff217",
commit = "5fa856469c642490a6e381a05d7f58f60fad1913",
remote = "https://github.com/angular/rules_angular.git",
)
bazel_dep(name = "devinfra")
git_override(
module_name = "devinfra",
commit = "daa0b5ecd15668d12163f70d4c9836ada70808c9",
commit = "4a09f9588769980eacb41fcc76e2c76d048eac42",
remote = "https://github.com/angular/dev-infra.git",
)
bazel_dep(name = "rules_sass")
git_override(
module_name = "rules_sass",
commit = "f153cb9242233738c3e6eca6554ae4cb864b0def",
commit = "bf9d6564f57b4df94b4915086ad0e7ad49a95f37",
remote = "https://github.com/angular/rules_sass.git",
)
bazel_dep(name = "rules_browsers")
git_override(
module_name = "rules_browsers",
commit = "3a6bd40f9be945dc51c976a21424c656448f9b09",
commit = "c37398d63f5e990d618d02f4c6fb2fefe40f1a79",
remote = "https://github.com/angular/rules_browsers.git",
)
@@ -49,15 +49,15 @@ use_repo(yq, "yq_toolchains")
node = use_extension("@rules_nodejs//nodejs:extensions.bzl", "node")
node.toolchain(
node_repositories = {
"22.23.1-darwin_arm64": ("node-v22.23.1-darwin-arm64.tar.gz", "node-v22.23.1-darwin-arm64", "ef28d8fab2c0e4314522d4bb1b7173270aa3937e93b92cb7de79c112ac1fa953"),
"22.23.1-darwin_amd64": ("node-v22.23.1-darwin-x64.tar.gz", "node-v22.23.1-darwin-x64", "b8da981b8a0b1241b70249204916da76c63573ddf5814dbd2d1e41069105cb81"),
"22.23.1-linux_arm64": ("node-v22.23.1-linux-arm64.tar.xz", "node-v22.23.1-linux-arm64", "0294e8b915ab75f92c7513d2fcb830ae06e10684e6c603e99a87dbf8835389c1"),
"22.23.1-linux_ppc64le": ("node-v22.23.1-linux-ppc64le.tar.xz", "node-v22.23.1-linux-ppc64le", "a645bcb7bbc498d09ba9249b5c6414aa6d0a461d8854afc61e32096646ba5e66"),
"22.23.1-linux_s390x": ("node-v22.23.1-linux-s390x.tar.xz", "node-v22.23.1-linux-s390x", "f16b5636ba925b462e627c5e0ef47de3255bf4a7b38d56db58e2cf165df19c3c"),
"22.23.1-linux_amd64": ("node-v22.23.1-linux-x64.tar.xz", "node-v22.23.1-linux-x64", "9749e988f437343b7fa832c69ded82a312e41a03116d766797ac14f6f9eee578"),
"22.23.1-windows_amd64": ("node-v22.23.1-win-x64.zip", "node-v22.23.1-win-x64", "7df0bc9375723f4a86b3aa1b7cc73342423d9677a8df4538aca31a049e309c29"),
"24.21.0-darwin_arm64": ("node-v24.21.0-darwin-arm64.tar.gz", "node-v24.21.0-darwin-arm64", "bed7eea5325e1108f32ce5228ddd6a5f0f08a499ee42aa7442aea583702f6057"),
"24.21.0-darwin_amd64": ("node-v24.21.0-darwin-x64.tar.gz", "node-v24.21.0-darwin-x64", "1462cb3b3046b815cf8ea436d3da450ec1a9f11dac7e5a46b0ada5305d7e8097"),
"24.21.0-linux_arm64": ("node-v24.21.0-linux-arm64.tar.xz", "node-v24.21.0-linux-arm64", "6ad1325edbdb5649c379b75a237147a666c95d4f9ae8d340fef2d1575d289ad2"),
"24.21.0-linux_ppc64le": ("node-v24.21.0-linux-ppc64le.tar.xz", "node-v24.21.0-linux-ppc64le", "1936fd64623a2f98d1fb31b456686d10c30e92639899cfefdefa8835d22adccd"),
"24.21.0-linux_s390x": ("node-v24.21.0-linux-s390x.tar.xz", "node-v24.21.0-linux-s390x", "2ef7e2ecbf7a6c2f3d08d106f6b2f279419c6dac89fe91b8cad193af7890082c"),
"24.21.0-linux_amd64": ("node-v24.21.0-linux-x64.tar.xz", "node-v24.21.0-linux-x64", "fd8e59d5a511510f6a298afb548f18c7d2b1be404d8b4a27d94fbe49f56cb2d6"),
"24.21.0-windows_amd64": ("node-v24.21.0-win-x64.zip", "node-v24.21.0-win-x64", "158f7685b44de51f6c0df1d153526cbcd3e1bc739a8dfc607721cef75de9e541"),
},
node_version = "22.23.1",
node_version = "24.21.0",
)
use_repo(node, "nodejs_toolchains")
use_repo(node, "nodejs_darwin_amd64")
@@ -71,8 +71,8 @@ use_repo(node, "nodejs_windows_amd64")
pnpm = use_extension("@aspect_rules_js//npm:extensions.bzl", "pnpm")
pnpm.pnpm(
name = "pnpm",
pnpm_version = "11.15.1",
pnpm_version_integrity = "sha512-gTULB+U8lTigLx8jA7QpD6LXvgTlbiqXDEzEtBfcdh3hlu2r1J1Vx9yVgNuBAHxEFD5OPX5GKzAA0jwlUSLQZQ==",
pnpm_version = "11.24.0",
pnpm_version_integrity = "sha512-vSfjRel23LC+C3oSKCF7BJqBfiGx81XJDb59xGZxiVqLwebQbCRVRQXqk+oLRfSJon7Bv7yN5qlln8oPFvoAAA==",
)
use_repo(pnpm, "pnpm")
+265 -2925
View File
File diff suppressed because it is too large Load Diff
+1
View File
@@ -84,6 +84,7 @@ APPLICATION_DEPS = [
":node_modules/@algolia/client-common",
":node_modules/@algolia/requester-browser-xhr",
":node_modules/@algolia/requester-node-http",
":node_modules/beasties",
":node_modules/@lezer/css",
":node_modules/@lezer/html",
":node_modules/@lezer/lr",
+27 -26
View File
@@ -1,45 +1,45 @@
{
"dependencies": {
"@algolia/client-common": "5.56.0",
"@algolia/client-search": "5.56.0",
"@algolia/requester-browser-xhr": "5.56.0",
"@algolia/requester-node-http": "5.56.0",
"@algolia/client-common": "5.57.0",
"@algolia/client-search": "5.57.0",
"@algolia/requester-browser-xhr": "5.57.0",
"@algolia/requester-node-http": "5.57.0",
"@angular/animations": "workspace:*",
"@angular/aria": "22.1.0-next.4",
"@angular/build": "22.1.0-next.4",
"@angular/cdk": "22.1.0-next.4",
"@angular/cli": "22.1.0-next.4",
"@angular/aria": "22.2.0-next.5",
"@angular/build": "22.2.0-next.7",
"@angular/cdk": "22.2.0-next.5",
"@angular/cli": "22.2.0-next.7",
"@angular/common": "workspace:*",
"@angular/compiler": "workspace:*",
"@angular/compiler-cli": "workspace:*",
"@angular/core": "workspace:*",
"@angular/docs": "workspace:*",
"@angular/forms": "workspace:*",
"@angular/material": "22.1.0-next.4",
"@angular/material": "22.2.0-next.5",
"@angular/platform-browser": "workspace:*",
"@angular/platform-server": "workspace:*",
"@angular/router": "workspace:*",
"@angular/ssr": "22.1.0-next.4",
"@angular/ssr": "22.2.0-next.7",
"@codemirror/autocomplete": "6.20.3",
"@codemirror/commands": "6.10.4",
"@codemirror/commands": "6.11.0",
"@codemirror/lang-angular": "0.1.4",
"@codemirror/lang-css": "6.3.1",
"@codemirror/lang-html": "6.4.11",
"@codemirror/lang-html": "6.4.12",
"@codemirror/lang-javascript": "6.2.5",
"@codemirror/lang-sass": "6.0.2",
"@codemirror/language": "6.12.4",
"@codemirror/lint": "6.9.7",
"@codemirror/search": "6.7.1",
"@codemirror/state": "6.7.1",
"@codemirror/view": "6.43.6",
"@codemirror/view": "6.43.9",
"@lezer/common": "1.5.2",
"@lezer/css": "1.3.4",
"@lezer/css": "1.3.6",
"@lezer/highlight": "1.2.3",
"@lezer/html": "1.3.13",
"@lezer/javascript": "1.5.4",
"@lezer/lr": "1.4.10",
"@lezer/sass": "1.1.0",
"@marijn/find-cluster-break": "1.0.3",
"@marijn/find-cluster-break": "1.0.4",
"@shikijs/core": "^4.0.0",
"@shikijs/engine-javascript": "^4.0.0",
"@shikijs/engine-oniguruma": "^4.0.0",
@@ -51,14 +51,15 @@
"@stackblitz/sdk": "1.11.1",
"@types/dom-navigation": "1.0.7",
"@types/jasmine": "6.0.0",
"@types/jsdom": "28.0.3",
"@types/jsdom": "30.0.0",
"@types/node": "24.13.3",
"@typescript/vfs": "1.6.4",
"@webcontainer/api": "1.6.4",
"@xterm/addon-fit": "0.11.0",
"@xterm/xterm": "6.0.0",
"algoliasearch": "5.56.0",
"algoliasearch": "5.57.0",
"angular-split": "20.0.0",
"beasties": "0.5.4",
"ccount": "^2.0.1",
"character-entities-html4": "^2.1.0",
"character-entities-legacy": "^3.0.0",
@@ -70,22 +71,22 @@
"hast-util-to-html": "^9.0.5",
"hast-util-whitespace": "^3.0.0",
"html-void-elements": "^3.0.0",
"jsdom": "29.1.1",
"jasmine-core": "6.3.0",
"jsdom": "30.0.1",
"karma-chrome-launcher": "3.2.0",
"karma-coverage": "2.2.1",
"karma-jasmine": "5.1.0",
"karma-jasmine-html-reporter": "2.2.0",
"marked": "18.0.6",
"mermaid": "11.16.0",
"karma-jasmine-html-reporter": "2.3.0",
"marked": "18.0.11",
"mermaid": "11.17.2",
"ngx-progressbar": "14.0.0",
"playwright-core": "1.61.1",
"preact": "10.29.7",
"playwright-core": "1.62.1",
"preact": "10.29.8",
"preact-render-to-string": "6.7.0",
"prettier": "3.9.5",
"prettier": "3.9.6",
"property-information": "^7.1.0",
"rxjs": "7.8.2",
"shiki": "4.3.1",
"shiki": "4.4.3",
"space-separated-tokens": "^2.0.2",
"stringify-entities": "^4.0.4",
"style-mod": "4.1.3",
@@ -98,7 +99,7 @@
"devDependencies": {
"autoprefixer": "10.5.4",
"karma": "~6.4.4",
"postcss": "8.5.20",
"postcss": "8.5.26",
"tailwindcss": "3.4.19"
}
}
+21 -22
View File
@@ -7,7 +7,7 @@
*/
import {ALL_ITEMS} from '../../src/app/routing/navigation-entries/index.js';
import {getIdFromHeading} from '../../shared-docs/pipeline/shared/heading.mjs';
import {extractHeadingIds, findDuplicateIds} from '../../shared-docs/pipeline/shared/heading.mjs';
import {NavigationItem} from '@angular/docs';
import {writeFileSync, readFileSync} from 'fs';
import {join, resolve} from 'path';
@@ -20,7 +20,7 @@ const contentRoot = resolve(process.cwd(), '../../src/content');
* in a JSON file. This file then used by other bazel targets to know which routes are valid.
*/
function extractRoutes(items: NavigationItem[]): string[] {
function extractRoutes(items: NavigationItem[], duplicatesByPage: Map<string, string[]>): string[] {
const routes: string[] = [];
for (const item of items) {
if (item.path && !item.path.startsWith('http')) {
@@ -29,7 +29,11 @@ function extractRoutes(items: NavigationItem[]): string[] {
const content = readFileSync(join(contentRoot, `${item.contentPath}.md`), {
encoding: 'utf-8',
});
const headings = extractHeadings(content);
const headings = extractHeadingIds(content);
const duplicates = findDuplicateIds(headings);
if (duplicates.length > 0) {
duplicatesByPage.set(`${item.contentPath}.md`, duplicates);
}
routes.push(
...headings.map(
(heading) => `${item.path}#${heading.toLowerCase().replace(/\s+/g, '-')}`,
@@ -38,33 +42,28 @@ function extractRoutes(items: NavigationItem[]): string[] {
}
}
if (item.children) {
routes.push(...extractRoutes(item.children));
routes.push(...extractRoutes(item.children, duplicatesByPage));
}
}
return routes;
}
function extractHeadings(content: string): string[] {
const headings = content
.split('\n')
// Top level heading (H1) are used for the page title only
// and yes, headings can have leading spaces
.filter((line) => line.trim().startsWith('##'))
.map((line) => line.replace(/^#+\s*/, '').trim());
const stepRegex = /<docs-step[^>]*title="([^"]*)"/g;
let match;
while ((match = stepRegex.exec(content)) !== null) {
headings.push(match[1]);
}
return headings.map((heading: string) => getIdFromHeading(heading));
}
function main() {
const allRoutes: string[] = [];
const duplicatesByPage = new Map<string, string[]>();
allRoutes.push(...extractRoutes(ALL_ITEMS));
allRoutes.push(...extractRoutes(ALL_ITEMS, duplicatesByPage));
if (duplicatesByPage.size > 0) {
const details = Array.from(duplicatesByPage)
.map(([page, ids]) => ` ${page}: ${ids.map((id) => `#${id}`).join(', ')}`)
.join('\n');
throw new Error(
`Headings must produce a unique anchor id within a page, otherwise every link to the ` +
`anchor resolves to the first heading that claims it. Give the later heading its own ` +
`id with the \`{#custom-id}\` syntax.\n${details}`,
);
}
const uniqueRoutes = Array.from(new Set(allRoutes.filter((r) => !!r)));
-1
View File
@@ -2,7 +2,6 @@
"compileOnSave": false,
"compilerOptions": {
"forceConsistentCasingInFileNames": true,
"strict": true,
"noImplicitOverride": true,
"noPropertyAccessFromIndexSignature": true,
"noImplicitReturns": true,
@@ -171,6 +171,7 @@
flex-direction: column;
max-height: 12rem;
overflow: auto;
overscroll-behavior: contain;
padding: 0.25rem;
}
@@ -5,6 +5,7 @@
#codeTabs
animationDuration="0ms"
mat-stretch-tabs="false"
[selectedIndex]="selectedTabIndex()"
(selectedTabChange)="onTabIndexChange($event.index)"
>
@for (tab of tabs(); track tab) {
@@ -21,7 +22,7 @@
[class.docs-example-code-toggle__show]="!showCode()"
[attr.aria-checked]="showCode()"
[attr.aria-label]="showCode() ? 'Hide code' : 'Show code'"
(click)="showCode.set(!showCode())"
(click)="toggleCodeVisibility()"
[matTooltip]="showCode() ? 'Hide code' : 'Show code'"
matTooltipPosition="above"
>
@@ -166,6 +166,147 @@ describe('ExampleViewer', () => {
expect(hiddenLine).toBeNull();
});
it('keeps the expand control after switching tabs while expanded', async () => {
const collapsible = {
name: 'example.ts',
sanitizedContent:
'<pre><code><div class="line">one</div><div class="line">two</div></code></pre>',
visibleLinesRange: '1',
};
componentRef.setInput(
'metadata',
getMetadata({files: [collapsible, {name: 'example.css', sanitizedContent: ''}]}),
);
await component.renderExample();
await fixture.whenStable();
expect(component.expandable()).toBeTrue();
component.toggleExampleVisibility();
await fixture.whenStable();
const tabGroup = await loader.getHarness(MatTabGroupHarness);
await (await tabGroup.getTabs())[1].select();
await fixture.whenStable();
await (await tabGroup.getTabs())[0].select();
await fixture.whenStable();
expect(component.expandable()).toBeTrue();
});
it('hides only the lines outside the visible range, and adds no gap within one range', async () => {
componentRef.setInput(
'metadata',
getMetadata({
files: [
{
name: 'example.ts',
sanitizedContent:
'<pre><code><div class="line">one</div><div class="line">two</div><div class="line">three</div></code></pre>',
visibleLinesRange: '2,3',
},
],
}),
);
await component.renderExample();
await fixture.whenStable();
const lines = fixture.debugElement
.queryAll(By.css('.line'))
.map((line) => (line.nativeElement as HTMLElement).className);
expect(lines).toEqual(['line hidden', 'line', 'line']);
});
it('hides the line numbers that pair with the hidden lines', async () => {
const numbered = (n: number, text: string) =>
`<span class="shiki-ln-number">${n}</span><div class="line">${text}</div>`;
componentRef.setInput(
'metadata',
getMetadata({
files: [
{
name: 'example.ts',
sanitizedContent: `<pre><code>${numbered(1, 'one')}${numbered(2, 'two')}${numbered(3, 'three')}</code></pre>`,
visibleLinesRange: '2,3',
},
],
}),
);
await component.renderExample();
await fixture.whenStable();
const visibleNumbers = fixture.debugElement
.queryAll(By.css('.shiki-ln-number'))
.filter((el) => !(el.nativeElement as HTMLElement).classList.contains('hidden'))
.map((el) => (el.nativeElement as HTMLElement).textContent);
expect(visibleNumbers).toEqual(['2', '3']);
});
it('restores the selected tab and its collapsed lines after hiding and reshowing the code', async () => {
componentRef.setInput(
'metadata',
getMetadata({
files: [
{name: 'example.ts', sanitizedContent: ''},
{
name: 'example.css',
sanitizedContent:
'<pre><code><div class="line">one</div><div class="line">two</div></code></pre>',
visibleLinesRange: '2',
},
],
}),
);
await component.renderExample();
await fixture.whenStable();
const tabGroup = await loader.getHarness(MatTabGroupHarness);
await (await tabGroup.getTabs())[1].select();
await fixture.whenStable();
component.toggleCodeVisibility();
await fixture.whenStable();
component.toggleCodeVisibility();
await fixture.whenStable();
expect(
await (await (await loader.getHarness(MatTabGroupHarness)).getSelectedTab()).getLabel(),
).toBe('CSS');
const lines = fixture.debugElement
.queryAll(By.css('.line'))
.map((line) => (line.nativeElement as HTMLElement).className);
expect(lines).toEqual(['line hidden', 'line']);
});
it('offers no expand control when the range covers the whole file', async () => {
componentRef.setInput(
'metadata',
getMetadata({
files: [
{
name: 'example.ts',
sanitizedContent:
'<pre><code><div class="line">one</div><div class="line">two</div></code></pre>',
visibleLinesRange: '1,2',
},
],
}),
);
await component.renderExample();
await fixture.whenStable();
expect(component.expandable()).toBeFalse();
});
it('should set exampleComponent when metadata contains path and preview is true', async () => {
exampleContentSpy.loadPreview.and.resolveTo(ExampleComponent);
componentRef.setInput(
@@ -69,6 +69,7 @@ export class ExampleViewer {
readonly expandable = signal<boolean>(false);
readonly expanded = signal<boolean>(false);
readonly snippetCode = signal<Snippet | undefined>(undefined);
readonly selectedTabIndex = signal<number>(0);
readonly showCode = signal<boolean>(true);
readonly tabs = computed(() =>
this.exampleMetadata()?.files.map((file) => ({
@@ -101,10 +102,7 @@ export class ExampleViewer {
`example-${this.exampleMetadata()?.id.toString()!}`,
);
const lines = this.getHiddenCodeLines();
const lineNumbers = this.getHiddenCodeLineNumbers();
this.expandable.set(lines.length > 0 || lineNumbers.length > 0);
this.updateExpandable();
},
{injector: this.injector},
);
@@ -116,6 +114,15 @@ export class ExampleViewer {
this.setCodeLinesVisibility();
}
toggleCodeVisibility(): void {
const showCode = !this.showCode();
this.showCode.set(showCode);
if (showCode) {
afterNextRender(() => this.setCodeLinesVisibility(), {injector: this.injector});
}
}
copyLink(): void {
// Reconstruct the URL using `origin + pathname` so we drop any pre-existing hash.
const fullUrl =
@@ -128,8 +135,25 @@ export class ExampleViewer {
}
protected onTabIndexChange(index: number): void {
this.selectedTabIndex.set(index);
this.snippetCode.set(this.exampleMetadata()?.files[index]);
this.setCodeLinesVisibility();
afterNextRender(
() => {
this.setCodeLinesVisibility();
this.updateExpandable();
},
{injector: this.injector},
);
}
// A file is expandable when its range hides lines. While the block is expanded nothing is
// hidden, so trust the presence of a range instead of the rendered state.
private updateExpandable(): void {
this.expandable.set(
!!this.snippetCode()?.visibleLinesRange &&
(this.expanded() || this.getHiddenCodeLines().length > 0),
);
}
private getFileExtension(name: string): string {
@@ -188,13 +212,13 @@ export class ExampleViewer {
for (const [index, line] of lines.entries()) {
if (!linesToDisplay.includes(index + 1)) {
line.classList.add(HIDDEN_CLASS_NAME);
} else if (!linesToDisplay.includes(index - 1)) {
} else if (!linesToDisplay.includes(index)) {
appendGapBefore.push(line);
}
}
for (const [index, lineNumber] of lineNumbers.entries()) {
if (!linesToDisplay.includes(index)) {
if (!linesToDisplay.includes(index + 1)) {
lineNumber.classList.add(HIDDEN_CLASS_NAME);
}
}
-3
View File
@@ -1,3 +0,0 @@
<svg width="17" height="16" viewBox="0 0 17 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M0.04145 0.04432l6.56351 8.77603L0 15.95564h1.48651l5.78263-6.24705 4.6722 6.24705h5.05865l-6.9328-9.26967L16.21504.04432h-1.48651l-5.32552 5.75341L5.1001.04432H.04145Zm2.18602 1.09497h2.32396l10.26221 13.72122h-2.32396L2.22747 1.13928Z" fill="#A39FA9"/>
</svg>

Before

Width:  |  Height:  |  Size: 367 B

+9 -9
View File
@@ -2,14 +2,14 @@
"name": "@angular/docs",
"version": "0.0.0-PLACEHOLDER",
"peerDependencies": {
"@angular/cdk": "^22.1.0-next",
"@angular/common": "^22.1.0-next",
"@angular/core": "^22.1.0-next",
"@angular/forms": "^22.1.0-next",
"@angular/material": "^22.1.0-next",
"@angular/platform-browser": "^22.1.0-next",
"@angular/router": "^22.1.0-next",
"@angular/ssr": "^22.1.0-next",
"@angular/cdk": "^22.2.0-next",
"@angular/common": "^22.2.0-next",
"@angular/core": "^22.2.0-next",
"@angular/forms": "^22.2.0-next",
"@angular/material": "^22.2.0-next",
"@angular/platform-browser": "^22.2.0-next",
"@angular/router": "^22.2.0-next",
"@angular/ssr": "^22.2.0-next",
"algoliasearch": "^5.0.0",
"rxjs": "^7.8.1"
},
@@ -18,7 +18,7 @@
"diff": "~9.0.0",
"emoji-regex": "~10.6.0",
"fflate": "^0.8.2",
"jsdom": "~29.1.0",
"jsdom": "~30.0.0",
"marked": "~18.0.0",
"mermaid": "^11.0.0",
"shiki": "^4.0.0",
@@ -66,7 +66,7 @@ function getTagSinceVersion(
}
// In case of deprecated tag we need to separate the version from the deprecation message.
const version = tag.comment.match(/\d+(\.\d+)?/)?.[0];
const version = tag.comment.match(/^\s*(?:(?:since|from|as of)\s+)?v?(\d+(?:\.\d+)?)/i)?.[1];
return {version};
}
@@ -191,7 +191,7 @@ export function getTagSinceVersion<T extends HasJsDocTags>(
}
// In case of deprecated tag we need to separate the version from the deprecation message.
const version = tag.comment.match(/\d+(\.\d+)?/)?.[0];
const version = tag.comment.match(/^\s*(?:(?:since|from|as of)\s+)?v?(\d+(?:\.\d+)?)/i)?.[1];
return {version};
}
@@ -34,16 +34,17 @@ export function replaceKeywordFromShikiHtml(
shikiHtml: string,
replaceWith: string,
): string {
const keywordElement = `<[^>]*>(?:${keyword})<\\/\\w+>`;
return (
shikiHtml
// remove the leading space of the element after the keyword
.replace(
new RegExp(`(<[^>]*>(?:${keyword})<\\/\\w+><[^>]*>)(\\s)(\\w+<\\/\\w+>)`, 'g'),
'$1$3',
)
// Linking an identifier causes Shiki to emit the space before it as a separate element.
.replace(new RegExp(`(${keywordElement})<[^>]*>\\s+<\\/\\w+>`, 'g'), '$1')
// Without a link, Shiki includes the space in the identifier's element instead.
.replace(new RegExp(`(${keywordElement}<[^>]*>)\\s+(?=[^<\\s]+<\\/\\w+>)`, 'g'), '$1')
// Shiki requires the keywords (eg. function,interface) for highlighting signatures
// here we are replacing it
.replace(new RegExp(`<[^>]*>(?:${keyword})<\\/\\w+>`, 'g'), replaceWith)
.replace(new RegExp(keywordElement, 'g'), replaceWith)
);
}
@@ -1,6 +1,24 @@
{
"moduleName": "@angular/core",
"entries": [
{
"name": "Component",
"decoratorType": "class",
"entryType": "decorator",
"members": [
{
"name": "selector",
"type": "string | undefined",
"memberType": "property",
"memberTags": ["optional"],
"description": "The CSS selector that identifies this component in a template.",
"jsdocTags": []
}
],
"description": "Decorator that marks a class as an Angular component.",
"jsdocTags": [],
"rawComment": ""
},
{
"name": "NgTemplateOutlet",
"isAbstract": false,
@@ -33,6 +33,7 @@ describe('renderable', () => {
const symbols = Object.fromEntries([
['AfterRenderPhase', 'core'],
['afterRender', 'core'],
['Component', 'core'],
['EmbeddedViewRef', 'core'],
['ChangeDetectionStrategy', 'core'],
['ChangeDetectorRef', 'core'],
@@ -79,4 +80,13 @@ describe('renderable', () => {
expect(anchor.getAttribute('href')).toBe(`#${id}`);
}
});
it('should render decorators without whitespace after the @ symbol', () => {
const component = entries.get('Component')!;
const html = renderEntry(component);
const fragment = JSDOM.fragment(html);
const api = fragment.querySelector('.docs-reference-api-section code')!;
expect(api.textContent).toContain('@Component({');
});
});
File diff suppressed because it is too large Load Diff
@@ -10,21 +10,21 @@
},
"private": true,
"dependencies": {
"@angular/animations": "^22.1.0-next",
"@angular/common": "^22.1.0-next",
"@angular/compiler": "^22.1.0-next",
"@angular/core": "^22.1.0-next",
"@angular/forms": "^22.1.0-next",
"@angular/platform-browser": "^22.1.0-next",
"@angular/router": "^22.1.0-next",
"@angular/animations": "^22.2.0-next",
"@angular/common": "^22.2.0-next",
"@angular/compiler": "^22.2.0-next",
"@angular/core": "^22.2.0-next",
"@angular/forms": "^22.2.0-next",
"@angular/platform-browser": "^22.2.0-next",
"@angular/router": "^22.2.0-next",
"rxjs": "~7.8.0",
"tslib": "^2.3.0",
"zone.js": "~0.16.0"
},
"devDependencies": {
"@angular/build": "^22.1.0-next",
"@angular/cli": "^22.1.0-next",
"@angular/compiler-cli": "^22.1.0-next",
"@angular/build": "^22.2.0-next",
"@angular/cli": "^22.2.0-next",
"@angular/compiler-cli": "^22.2.0-next",
"@types/node": "^20.14.8",
"typescript": "~6.0.3"
}
@@ -16,20 +16,16 @@
"noUnusedParameters": false,
"noUnusedLocals": false,
"useDefineForClassFields": false,
"strictNullChecks": true,
"noImplicitReturns": true,
"strictFunctionTypes": true,
"noImplicitOverride": true,
"noFallthroughCasesInSwitch": true,
"noImplicitAny": true,
"noImplicitThis": true,
"strictBindCallApply": true,
"esModuleInterop": true
},
"angularCompilerOptions": {
"enableI18nLegacyMessageIdFormat": false,
"strictInjectionParameters": true,
"strictInputAccessModifiers": true,
"strictTemplates": true
"strictInputAccessModifiers": true
}
}
@@ -27,3 +27,51 @@ export function getIdFromHeading(heading: string): string {
.replace(/\s|\//g, '-') // replace spaces and slashes with dashes
.replace(/[^\p{L}\d\-]/gu, ''); // only keep letters, digits & dashes
}
/**
* Extracts the anchor ID of every heading on a page, in document order.
* Covers markdown headings below H1, which is reserved for the page title,
* and `<docs-step>` titles, which are rendered as headings too. Lines inside
* fenced code blocks are ignored so that samples cannot contribute headings.
*/
export function extractHeadingIds(content: string): string[] {
const headings: string[] = [];
let insideCodeBlock = false;
for (const line of content.split('\n')) {
const trimmedLine = line.trim();
if (trimmedLine.startsWith('```')) {
insideCodeBlock = !insideCodeBlock;
continue;
}
// Headings can have leading spaces.
if (!insideCodeBlock && trimmedLine.startsWith('##')) {
headings.push(trimmedLine.replace(/^#+\s*/, '').trim());
}
}
const stepRegex = /<docs-step[^>]*title="([^"]*)"/g;
let match;
while ((match = stepRegex.exec(content)) !== null) {
headings.push(match[1]);
}
return headings.map((heading) => getIdFromHeading(heading));
}
/** Returns the IDs that appear more than once, each reported once. */
export function findDuplicateIds(ids: string[]): string[] {
const seen = new Set<string>();
const duplicates = new Set<string>();
for (const id of ids) {
if (seen.has(id)) {
duplicates.add(id);
}
seen.add(id);
}
return Array.from(duplicates);
}
@@ -34,9 +34,15 @@ const tokenMatcher = new RegExp(
's',
);
const tokenStartMatcher = new RegExp(`\n(?:${alertSeverityLevels.join('|')}): `);
export const docsAlertExtension: TokenizerAndRendererExtension = {
name: 'docs-alert',
level: 'inline',
start(src: string) {
const index = src.match(tokenStartMatcher)?.index;
return index === undefined ? undefined : index + 1;
},
tokenizer(this: TokenizerThis, src: string): DocsAlertToken | undefined {
const execMatch = tokenMatcher.exec(src);
if (execMatch === null) {
@@ -27,11 +27,13 @@ interface DocsCalloutToken extends Tokens.Generic {
// Capture group 1: all attributes on the opening tag
// Capture group 2: all content between the open and close tags
const calloutRule = /^<docs-callout([^>]*)>((?:.(?!\/docs-callout))*)<\/docs-callout>/s;
const calloutRule =
/^<docs-callout((?:[^>"'`]|"[^"]*"|'[^']*'|`[^`]*`)*)>((?:.(?!\/docs-callout))*)<\/docs-callout>/s;
const titleRule = /title="([^"]*)"/;
const isImportantRule = /important/;
const isCriticalRule = /critical/;
const titleRule = /title=(['"`])(.*?)\1/; // The 2nd capture matters here
const attributeValueRule = /=(['"`])(?:.*?)\1/gs;
const isImportantRule = /\bimportant\b/;
const isCriticalRule = /\bcritical\b/;
export const docsCalloutExtension = {
name: 'docs-callout',
@@ -46,9 +48,12 @@ export const docsCalloutExtension = {
const attr = match[1].trim();
const title = titleRule.exec(attr);
// Severity is a bare word on the tag, so it must not be matched inside an attribute value.
const flags = attr.replace(attributeValueRule, '');
let severityLevel = CalloutSeverityLevel.HELPFUL;
if (isImportantRule.exec(attr)) severityLevel = CalloutSeverityLevel.IMPORTANT;
if (isCriticalRule.exec(attr)) severityLevel = CalloutSeverityLevel.CRITICAL;
if (isImportantRule.exec(flags)) severityLevel = CalloutSeverityLevel.IMPORTANT;
if (isCriticalRule.exec(flags)) severityLevel = CalloutSeverityLevel.CRITICAL;
const body = match[2].trim();
@@ -56,7 +61,7 @@ export const docsCalloutExtension = {
type: 'docs-callout',
raw: match[0],
severityLevel: severityLevel,
title: title ? title[1] : '',
title: title ? title[2] : '',
titleTokens: [],
body: body ?? '',
bodyTokens: [],
@@ -48,7 +48,7 @@ export const docsCodeBlockExtension = {
const token: DocsCodeBlock = {
raw: match[0],
type: 'docs-code-block',
code: deindent(match[3]),
code: match[3],
language: match[1],
header: headerRule.exec(metadataStr)?.[2],
highlight: highlightRule.exec(metadataStr)?.[1],
@@ -68,20 +68,3 @@ export const docsCodeBlockExtension = {
return formatCode(token, (this.parser.renderer as AdevDocsRenderer).context);
},
};
/**
* Removes leading indentation from code blocks.
*/
function deindent(str: string): string {
const lines = str.split('\n');
let minIndent = Infinity;
for (const line of lines) {
if (!line.trim()) {
minIndent = Math.min(line.match(/^(\s*)/)?.[1].length ?? 0, minIndent);
}
}
if (minIndent === Infinity || minIndent === 0) {
return str;
}
return lines.map((line) => line.slice(minIndent)).join('\n');
}
@@ -59,7 +59,7 @@ export const docsCodeExtension = {
const classes = classRule.exec(attr);
const style = preferRule.exec(attr);
let code = match[2]?.trim() ?? '';
let code = match[2] ?? '';
if (path && path[1]) {
code = loadWorkspaceRelativeFile(path[1]);
// Remove ESLint Comments
@@ -52,6 +52,7 @@ export function formatCode(token: CodeToken, context: RendererContext): string {
}
extractRegions(token);
token.code = deindent(token.code).trim();
highlightCode(context.highlighter, token, context);
const containerEl = JSDOM.fragment(`
@@ -95,6 +96,11 @@ export function processForApiLinks(fragment: Element, apiEntries: ApiEntries): v
});
}
/** Escapes text that is interpolated into an HTML string. */
function escapeHtml(text: string): string {
return text.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
}
/** Build the header element if a header is provided in the token. */
function buildHeaderElement(token: CodeToken) {
let header = '';
@@ -103,7 +109,7 @@ function buildHeaderElement(token: CodeToken) {
}
if (token.header) {
header += `<h3>${token.header}</h3>`;
header += `<h3>${escapeHtml(token.header)}</h3>`;
}
if (!header) return '';
@@ -153,3 +159,20 @@ function applyContainerAttributesAndClasses(el: Element, token: CodeToken) {
el.classList.add(...token.classes);
}
}
/**
* Removes leading indentation from code blocks.
*/
function deindent(str: string): string {
const lines = str.split('\n');
let minIndent = Infinity;
for (const line of lines) {
if (line.trim()) {
minIndent = Math.min(line.match(/^(\s*)/)?.[1].length ?? 0, minIndent);
}
}
if (minIndent === Infinity || minIndent === 0) {
return str;
}
return lines.map((line) => line.slice(minIndent)).join('\n');
}
@@ -24,6 +24,6 @@ export function extractRegions(token: CodeToken) {
if (!region) {
throw new Error(`Cannot find ${token.region} in ${token.path}!`);
}
token.code = region.lines.join('\n').trim();
token.code = region.lines.join('\n');
}
}
@@ -8,6 +8,7 @@
import {TokenizerThis, Tokens, RendererThis} from 'marked';
import {loadWorkspaceRelativeFile} from '../helpers.mjs';
import {AdevDocsRenderer} from '../renderer.mjs';
import {getPageTitle} from '../transformations/heading.mjs';
interface DocsDecorativeHeaderToken extends Tokens.Generic {
@@ -75,7 +76,7 @@ function getStandardDecorativeHeader(renderer: RendererThis, token: DocsDecorati
<div class="docs-header-content">
<docs-breadcrumb></docs-breadcrumb>
${getPageTitle(token.title)}
${getPageTitle(token.title, (renderer.parser.renderer as AdevDocsRenderer).context.markdownFilePath)}
<p>${token.body}</p>
</div>
@@ -97,7 +98,7 @@ function getGradientDecorativeHeader(renderer: RendererThis, token: DocsDecorati
<div class="docs-decorative-header-container">
<div class="docs-decorative-gradient-header">
<div class="docs-header-content">
${getPageTitle(token.title)}
${getPageTitle(token.title, (renderer.parser.renderer as AdevDocsRenderer).context.markdownFilePath)}
<p>${token.body}</p>
</div>
<!-- illustration -->
@@ -6,10 +6,10 @@
* found in the LICENSE file at https://angular.dev/license
*/
import {DocsCodeToken} from '../extensions/docs-code/docs-code.mjs';
import {chromium} from 'playwright-core';
import {Mermaid, MermaidConfig} from 'mermaid';
import {resolve, join} from 'path';
import {join, resolve} from 'path';
import {chromium} from 'playwright-core';
import {DocsCodeToken} from '../extensions/docs-code/docs-code.mjs';
const runfilesDir = process.env['JS_BINARY__RUNFILES']!;
// Declare mermarid in the context of this file so that typescript doesn't get upset when we
@@ -62,7 +62,7 @@ export async function processMermaidCodeBlock(token: DocsCodeToken) {
/** The generated SVG element string for the provided token's code. */
let {svg} = await page.evaluate(
({diagram, config}) => {
mermaid.initialize(config);
mermaid.initialize(config as MermaidConfig);
return mermaid.render('mermaid-generated-diagram', diagram);
},
@@ -24,6 +24,7 @@ import {docsCodeExtension, DocsCodeToken} from './extensions/docs-code/docs-code
import {docsCodeMultifileExtension} from './extensions/docs-code/docs-code-multifile.mjs';
import {docsTabGroupExtension, docsTabExtension} from './extensions/docs-tabs.mjs';
import {docsImageExtension} from './extensions/docs-image.mjs';
import {validatePairedTags} from './validate-paired-tags.mjs';
let markedInstance: typeof marked;
const extensions = [
@@ -51,11 +52,13 @@ export async function parseMarkdownAsync(
markdownContent: string,
context: Partial<RendererContext>,
): Promise<string> {
validatePairedTags(markdownContent, context.markdownFilePath);
markedInstance ??= marked.use({extensions, walkTokens, async: true});
return markedInstance.parse(markdownContent, {renderer: new AdevDocsRenderer(context)});
}
export function parseMarkdown(markdownContent: string, context: Partial<RendererContext>): string {
validatePairedTags(markdownContent, context.markdownFilePath);
markedInstance ??= marked.use({extensions, walkTokens});
return markedInstance.parse(markdownContent, {renderer: new AdevDocsRenderer(context)}) as string;
}
@@ -75,10 +75,6 @@ export class AdevDocsRenderer extends Renderer {
route.startsWith('#') || // Anchor link within the same page
route.startsWith('mailto:') || // Should we have a regex to exclude any protocol?
route.startsWith('playground') ||
// TODO: Extract routes from the CDK as well
route.startsWith('api/cdk') ||
// TODO: Extract routes from Aria as well
route.startsWith('api/aria') ||
route.startsWith('tutorials') ||
route.startsWith('extended-diagnostics')
) {
@@ -17,4 +17,7 @@ IMPORTANT: Use Important for information that's crucial to comprehending the tex
HELPFUL: Use Best practice to call out practices that are known to be successful or better than alternatives.
Some prose that runs straight into the alert with no blank line.
TIP: THIS TIP FOLLOWS PROSE ON THE NEXT LINE
NOTE: THIS NOTE WITHOUT A LINE RETURN
@@ -35,6 +35,14 @@ describe('markdown to html', () => {
expect(noteEl?.textContent?.trim()).toContain(`This is a multiline note`);
});
it(`should handle an alert that follows prose in the same paragraph`, () => {
const tipEls = markdownDocument.querySelectorAll(`.docs-alert-tip`);
expect(tipEls[tipEls.length - 1]?.textContent?.trim()).toContain(
`THIS TIP FOLLOWS PROSE ON THE NEXT LINE`,
);
});
it(`should handle alerts without a line return`, () => {
const noteEl = markdownDocument.querySelector(`.docs-alert-note:last-of-type`);
@@ -25,4 +25,56 @@ describe('markdown to html', () => {
markdownDocument.querySelector('#default-marker')!.parentElement?.parentElement;
calloutDiv?.classList.contains('docs-callout-helpful');
});
const parse = (markdown: string) => JSDOM.fragment(parseMarkdown(markdown, rendererContext));
it('reads a title quoted with any of the supported quote characters', () => {
for (const quote of ['"', "'", '`']) {
expect(
parse(`<docs-callout title=${quote}Quoted${quote}>Body</docs-callout>`).querySelector('h3')
?.textContent,
)
.withContext(`quoted with ${quote}`)
.toBe('Quoted');
}
});
it('reads a title that contains the other quote characters', () => {
expect(
parse(
`<docs-callout title='Illustrating the "pristine" state'>Body</docs-callout>`,
).querySelector('h3')?.textContent,
).toBe('Illustrating the "pristine" state');
expect(
parse(`<docs-callout title="It's here">Body</docs-callout>`).querySelector('h3')?.textContent,
).toBe("It's here");
});
it('reads a title that contains a closing angle bracket', () => {
expect(
parse('<docs-callout title="Migrate a > b">Body</docs-callout>').querySelector('h3')
?.textContent,
).toBe('Migrate a > b');
});
it('takes the severity from the tag, not from the title text', () => {
expect(
parse('<docs-callout title="Why this is important">Body</docs-callout>').querySelector(
'.docs-callout',
)?.className,
).toBe('docs-callout docs-callout-helpful');
expect(
parse('<docs-callout title="A critical note">Body</docs-callout>').querySelector(
'.docs-callout',
)?.className,
).toBe('docs-callout docs-callout-helpful');
expect(
parse('<docs-callout important title="X">Body</docs-callout>').querySelector('.docs-callout')
?.className,
).toBe('docs-callout docs-callout-important');
expect(
parse('<docs-callout critical title="X">Body</docs-callout>').querySelector('.docs-callout')
?.className,
).toBe('docs-callout docs-callout-critical');
});
});
@@ -35,7 +35,8 @@ describe('markdown to html', () => {
it('should deindent code blocks correctly', () => {
const codeBlock = markdownDocument.querySelectorAll('code')[1];
expect(codeBlock.innerHTML).toContain(` // bar`);
expect(codeBlock.textContent).not.toContain(` if(foo)`);
expect(codeBlock.textContent).toContain(` // bar`);
});
it('should handle code blocks without language', () => {
@@ -14,3 +14,9 @@ const form = {
</docs-code>
<docs-code hideDollar code="echo 'hello world'" />
<docs-code language="typescript">
if (foo) {
// bar
}
</docs-code>
@@ -46,6 +46,7 @@ describe('markdown to html', () => {
it('should load header and html code', () => {
const codeBlock = markdownDocument.querySelectorAll('code')[3];
expect(codeBlock).toBeTruthy();
expect(codeBlock?.textContent).not.toContain('docregion');
});
it('should not link property names in object literals', () => {
@@ -57,4 +58,9 @@ describe('markdown to html', () => {
const codeBlock = markdownDocument.querySelectorAll('.docs-code')[5];
expect(codeBlock.getAttribute('hideDollar')).toBe('true');
});
it('should deindent inline code blocks correctly', () => {
const codeBlock = markdownDocument.querySelectorAll('.docs-code')[6]?.querySelector('code');
expect(codeBlock?.textContent).toMatch(/^ \/\/ bar/m);
});
});
@@ -33,4 +33,19 @@ describe('markdown to html', () => {
it('passes the header text to the content', () => {
expect(markdownDocument.querySelector('p')?.textContent?.trim()).toBe('This is header text');
});
for (const gradientBackground of [false, true]) {
it(`links to the source file on GitHub (gradientBackground=${gradientBackground})`, () => {
const markdownDocument = JSDOM.fragment(
parseMarkdown(
`<docs-decorative-header title="Custom Title" gradientBackground="${gradientBackground}"></docs-decorative-header>`,
{...rendererContext, markdownFilePath: 'adev/src/content/overview.md'},
),
);
expect(markdownDocument.querySelector('.docs-github-links')?.getAttribute('href')).toBe(
'https://github.com/angular/angular/edit/main/adev/src/content/overview.md',
);
});
}
});
@@ -0,0 +1,80 @@
/**
* @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 {parseMarkdown} from '../../parse.mjs';
import {rendererContext, setHighlighter} from '../renderer-context.mjs';
describe('paired tag validation', () => {
beforeAll(async () => {
await setHighlighter();
});
const parse = (markdown: string) =>
parseMarkdown(markdown, {...rendererContext, markdownFilePath: 'guide/example.md'});
it('throws when a step is left unclosed', () => {
expect(() =>
parse(`
<docs-workflow>
<docs-step title="First">
First step.
<docs-step title="Second">
Second step.
</docs-step>
</docs-workflow>`),
).toThrowError(
/Unbalanced <docs-step> in guide\/example\.md: 2 opening tag\(s\) and 1 closing/,
);
});
it('throws when a workflow is left unclosed', () => {
expect(() =>
parse(`
<docs-workflow>
<docs-step title="Only">
Only step.
</docs-step>`),
).toThrowError(/Unbalanced <docs-workflow>/);
});
it('accepts balanced tags', () => {
expect(() =>
parse(`
<docs-workflow>
<docs-step title="Only">
Only step.
</docs-step>
</docs-workflow>`),
).not.toThrow();
});
it('ignores tags inside code blocks, inline code and comments', () => {
expect(() =>
parse(
[
'```html',
'<docs-step title="In a fence">',
'```',
'',
'Steps are written as `<docs-step>` elements.',
'',
'<!-- <docs-workflow> -->',
].join('\n'),
),
).not.toThrow();
});
it('does not confuse docs-card with docs-card-container', () => {
expect(() =>
parse(`
<docs-card-container>
<docs-card title="One">Body</docs-card>
</docs-card-container>`),
).not.toThrow();
});
});
@@ -0,0 +1,44 @@
/*!
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.dev/license
*/
/** Custom elements that are always written as a matching pair of open and close tags. */
const PAIRED_TAGS = [
'docs-callout',
'docs-card',
'docs-card-container',
'docs-decorative-header',
'docs-step',
'docs-tab',
'docs-tab-group',
'docs-workflow',
];
/**
* Throws when a paired custom element is left unclosed.
*
* The tokenizers match up to the next closing tag rather than failing, so an unclosed tag pulls
* the content that follows it into the block and leaves the next block unparsed in the output.
*/
export function validatePairedTags(markdown: string, filePath?: string): void {
const content = markdown
.replace(/```[\s\S]*?```/g, '')
.replace(/<!--[\s\S]*?-->/g, '')
.replace(/`[^`\n]*`/g, '');
for (const tag of PAIRED_TAGS) {
const opened = content.match(new RegExp(`<${tag}(?=[\\s>])(?![^>]*/>)`, 'g'))?.length ?? 0;
const closed = content.match(new RegExp(`</${tag}>`, 'g'))?.length ?? 0;
if (opened !== closed) {
throw new Error(
`Unbalanced <${tag}> in ${filePath || 'markdown'}: ` +
`${opened} opening tag(s) and ${closed} closing tag(s).`,
);
}
}
}
@@ -27,6 +27,7 @@ const REGION_MATCHERS = {
es6: inlineC,
html: html,
svg: html,
xlf: html,
css: blockC,
conf: inlineHash,
yaml: inlineHash,
@@ -13,6 +13,7 @@ export type FileType =
| 'es6'
| 'html'
| 'svg'
| 'xlf'
| 'css'
| 'conf'
| 'yaml'
@@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.dev/license
*/
import {getIdFromHeading} from '../heading.mjs';
import {extractHeadingIds, findDuplicateIds, getIdFromHeading} from '../heading.mjs';
describe('getIdFromHeading', () => {
it('should generate id from simple text', () => {
@@ -29,3 +29,65 @@ describe('getIdFromHeading', () => {
expect(getIdFromHeading('Duplicate Heading {#unique-id}')).toBe('unique-id');
});
});
describe('extractHeadingIds', () => {
it('should collect ids for every heading below the page title', () => {
const content = [
'# Page title',
'## First section',
'### A subsection',
'#### Deeper still',
].join('\n');
expect(extractHeadingIds(content)).toEqual(['first-section', 'a-subsection', 'deeper-still']);
});
it('should collect ids for headings with leading spaces', () => {
expect(extractHeadingIds(' ## Indented heading')).toEqual(['indented-heading']);
});
it('should collect ids for docs-step titles', () => {
const content = [
'## A section',
'<docs-step title="Install the package">',
'</docs-step>',
].join('\n');
expect(extractHeadingIds(content)).toEqual(['a-section', 'install-the-package']);
});
it('should use the custom id when a heading declares one', () => {
expect(extractHeadingIds('## Before {#migration-before}')).toEqual(['migration-before']);
});
it('should ignore headings inside fenced code blocks', () => {
const content = [
'## A real heading',
'```md',
'## Not a heading',
'### Also not a heading',
'```',
'## Another real heading',
].join('\n');
expect(extractHeadingIds(content)).toEqual(['a-real-heading', 'another-real-heading']);
});
});
describe('findDuplicateIds', () => {
it('should return an empty array when every id is unique', () => {
expect(findDuplicateIds(['one', 'two', 'three'])).toEqual([]);
});
it('should return an id that appears twice', () => {
expect(findDuplicateIds(['one', 'two', 'one'])).toEqual(['one']);
});
it('should report an id repeated many times only once', () => {
expect(findDuplicateIds(['one', 'one', 'one', 'one'])).toEqual(['one']);
});
it('should return every duplicated id', () => {
expect(findDuplicateIds(['before', 'after', 'before', 'after'])).toEqual(['before', 'after']);
});
});
@@ -1,4 +1,4 @@
# See http://help.github.com/ignore-files/ for more about ignoring files.
# See https://docs.github.com/en/get-started/git-basics/ignoring-files for more about ignoring files.
# Compiled output
/dist
File diff suppressed because it is too large Load Diff
@@ -9,18 +9,18 @@
},
"private": true,
"dependencies": {
"@angular/common": "^22.1.0-next",
"@angular/compiler": "^22.1.0-next",
"@angular/core": "^22.1.0-next",
"@angular/platform-browser": "^22.1.0-next",
"@angular/common": "^22.2.0-next",
"@angular/compiler": "^22.2.0-next",
"@angular/core": "^22.2.0-next",
"@angular/platform-browser": "^22.2.0-next",
"rxjs": "~7.8.0",
"tslib": "^2.3.0",
"zone.js": "~0.16.0"
},
"devDependencies": {
"@angular/build": "^22.1.0-next",
"@angular/cli": "^22.1.0-next",
"@angular/compiler-cli": "^22.1.0-next",
"@angular/build": "^22.2.0-next",
"@angular/cli": "^22.2.0-next",
"@angular/compiler-cli": "^22.2.0-next",
"typescript": "~6.0.3"
}
}
@@ -4,7 +4,6 @@
"compilerOptions": {
"outDir": "./dist/out-tsc",
"forceConsistentCasingInFileNames": true,
"strict": true,
"noImplicitOverride": true,
"noPropertyAccessFromIndexSignature": true,
"noImplicitReturns": true,
@@ -22,8 +21,6 @@
"angularCompilerOptions": {
"enableI18nLegacyMessageIdFormat": false,
"strictInjectionParameters": true,
"strictInputAccessModifiers": true,
"strictTemplates": true,
"_enabledBlockTypes": ["if", "for"]
"strictInputAccessModifiers": true
}
}
+18 -1
View File
@@ -1,4 +1,4 @@
load("//adev/shared-docs:defaults.bzl", "ng_project", "ts_project")
load("//adev/shared-docs:defaults.bzl", "ng_project", "ts_project", "zoneless_web_test_suite")
package(default_visibility = ["//visibility:private"])
@@ -30,3 +30,20 @@ ng_project(
"//adev/shared-docs/utils",
],
)
ts_project(
name = "test_lib",
testonly = True,
srcs = glob(
["*.spec.ts"],
),
deps = [
":lib",
"//adev/shared-docs/interfaces",
],
)
zoneless_web_test_suite(
name = "test",
deps = [":test_lib"],
)
+3 -2
View File
@@ -131,7 +131,7 @@ $gray-50: oklch(98.81% 0 0); // #fbfbfb
--light-pink-to-light-purple-horizontal-gradient: linear-gradient(
90deg,
var(--light-pink) 0%,
var(--light-purple) 100%
var(--light-violet) 100%
);
--light-vivid-pink-to-light-electric-violet-horizontal-gradient: linear-gradient(
@@ -232,7 +232,8 @@ $gray-50: oklch(98.81% 0 0); // #fbfbfb
// for the "unfilled" portion of the word that hasn't
// been highlighted by the gradient
--gray-unfilled: #{$gray-400};
--webgl-page-background: var(var(--page-background)) --webgl-gray-unfilled: #{$gray-400};
--webgl-page-background: var(--page-background);
--webgl-gray-unfilled: #{$gray-400};
}
@mixin dark-mode-definitions() {
+1 -1
View File
@@ -2,7 +2,7 @@
// We only target non-nested kbd elements
kbd:not(:has(kbd)) {
position: relative;
color: var(---tertiary-contrast);
color: var(--tertiary-contrast);
border: 1px solid var(--quinary-contrast);
box-shadow:
0 1px 0 rgba(0, 0, 0, 0.2),
+1 -1
View File
@@ -304,7 +304,7 @@
active-label-text-color: transparent,
label-text-font: 'InterTight, sans-serif',
label-text-tracking: -0.00875rem,
label-text-size: 0.8125rem,
label-text-size: 0.875rem,
)
);
+1 -1
View File
@@ -122,7 +122,7 @@
}
}
:not(.docs-card-container) .docs-card {
:not(.docs-card-grid) > .docs-card {
margin: 1rem 0;
}
+1
View File
@@ -87,6 +87,7 @@ $code-font-size: 0.875rem;
width: 100%;
a:not(.docs-anchor) > & {
display: inline-block;
position: relative;
padding: 0 0.3rem;
white-space: nowrap;
+1 -3
View File
@@ -4,7 +4,6 @@
"module": "ESNext",
"skipLibCheck": true,
"moduleResolution": "bundler",
"strict": true,
"declaration": true,
"sourceMap": true,
"lib": ["ES2022", "dom"],
@@ -18,7 +17,6 @@
},
"angularCompilerOptions": {
"strictInjectionParameters": true,
"strictInputAccessModifiers": true,
"strictTemplates": true
"strictInputAccessModifiers": true
}
}
+18 -1
View File
@@ -1,4 +1,4 @@
load("//adev/shared-docs:defaults.bzl", "ts_project")
load("//adev/shared-docs:defaults.bzl", "ts_project", "zoneless_web_test_suite")
package(default_visibility = ["//visibility:private"])
@@ -21,3 +21,20 @@ ts_project(
"//adev/shared-docs/providers",
],
)
ts_project(
name = "test_lib",
testonly = True,
srcs = glob(
["*.spec.ts"],
),
deps = [
":utils",
"//adev/shared-docs/interfaces",
],
)
zoneless_web_test_suite(
name = "test",
deps = [":test_lib"],
)
@@ -0,0 +1,54 @@
/*!
* @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 {NavigationItem} from '../interfaces/index';
import {isExternalLink, mapNavigationItemsToRoutes} from './navigation.utils';
describe('navigation.utils', () => {
describe('isExternalLink', () => {
it('should return true for http and https links', () => {
expect(isExternalLink('http://example.com')).toBeTrue();
expect(isExternalLink('https://example.com')).toBeTrue();
expect(isExternalLink('https://github.com/angular/angularfire#readme')).toBeTrue();
});
it('should return false for internal relative or absolute paths', () => {
expect(isExternalLink('guide/signals')).toBeFalse();
expect(isExternalLink('/guide/signals')).toBeFalse();
expect(isExternalLink('overview')).toBeFalse();
});
});
describe('mapNavigationItemsToRoutes', () => {
it('should map internal navigation items to routes and ignore external links', () => {
const items: NavigationItem[] = [
{
label: 'Overview',
path: 'overview',
},
{
label: 'AngularFire',
path: 'https://github.com/angular/angularfire#readme',
},
{
label: 'Google Maps',
path: 'https://github.com/angular/components/tree/main/src/google-maps#readme',
},
{
label: 'Guide',
path: 'guide/components',
},
];
const routes = mapNavigationItemsToRoutes(items, {});
expect(routes.length).toBe(2);
expect(routes.map((r) => r.path)).toEqual(['overview', 'guide/components']);
});
});
});
+4 -1
View File
@@ -96,7 +96,10 @@ export const mapNavigationItemsToRoutes = (
additionalRouteProperties: Partial<Route>,
): Route[] =>
navigationItems
.filter((route): route is NavigationItem & {path: string} => Boolean(route.path))
.filter(
(route): route is NavigationItem & {path: string} =>
Boolean(route.path) && !isExternalLink(route.path!),
)
.map((navigationItem) => {
const route = {
path: navigationItem.path,
+2 -1
View File
@@ -22,6 +22,7 @@ import {Footer} from './core/layout/footer/footer.component';
import {Navigation} from './core/layout/navigation/navigation.component';
import {ProgressBarComponent} from './core/layout/progress-bar/progress-bar.component';
import {SecondaryNavigation} from './core/layout/secondary-navigation/secondary-navigation.component';
import {ANGULAR_DEV} from './core/constants/links';
import {HeaderService} from './core/services/header.service';
@Component({
@@ -101,7 +102,7 @@ export class AppComponent {
// In debug this shortcut allows us to open the same page on adev
// Helpful to compare differences
event.preventDefault();
window.open(`https://angular.dev/${location.pathname}`, '_blank');
window.open(`${ANGULAR_DEV}/${location.pathname}`, '_blank');
}
}
+1 -1
View File
@@ -13,7 +13,7 @@ import {appConfig} from './app.config';
const serverConfig: ApplicationConfig = {
providers: [
provideServerRendering(
{maxResponseBodySize: 2 * 1024 * 1024},
{maxResponseBodySize: 4 * 1024 * 1024},
withRoutes([{path: '**', renderMode: RenderMode.Prerender}]),
),
],
+2
View File
@@ -6,6 +6,8 @@
* found in the LICENSE file at https://angular.dev/license
*/
export const ANGULAR_DEV = 'https://angular.dev';
export const ANGULAR_LINKS = {
GITHUB: 'https://github.com/angular/angular',
X: 'https://x.com/angular',
@@ -11,6 +11,9 @@
border-radius: 0.25rem;
z-index: var(--z-index-mini-menu);
box-shadow: 10px 4px 40px 0 rgba(0, 0, 0, 0.075);
max-height: 70dvh;
overflow-y: auto;
overscroll-behavior: contain;
// Animation for mini-menu appearance
animation: menuFadeIn 0.2s ease-out forwards;
@@ -107,8 +110,6 @@
}
.adev-version-picker {
overflow-y: auto;
max-height: 70dvh;
margin-inline-start: 10px;
li {
+1 -1
View File
@@ -9,7 +9,7 @@
import {DOCUMENT} from '@angular/common';
import {inject, Service} from '@angular/core';
const ANGULAR_DEV = 'https://angular.dev';
import {ANGULAR_DEV} from '../constants/links';
/**
* Information about the deployment of this application.
@@ -7,7 +7,7 @@
*/
import {DOCUMENT, isPlatformBrowser} from '@angular/common';
import {PLATFORM_ID, Service, inject, signal} from '@angular/core';
import {PLATFORM_ID, Service, computed, inject, signal} from '@angular/core';
import {LOCAL_STORAGE} from '@angular/docs';
// Keep these constants in sync with the code in index.html
@@ -26,12 +26,21 @@ export class ThemeManager {
private readonly platformId = inject(PLATFORM_ID);
readonly theme = signal<Theme | null>(this.getThemeFromLocalStorageValue());
private readonly osScheme = signal<'dark' | 'light'>('light');
readonly resolvedTheme = computed<'dark' | 'light'>(() => {
const theme = this.theme();
if (theme === null) {
return 'light';
}
return theme === 'auto' ? this.osScheme() : theme;
});
constructor() {
if (!isPlatformBrowser(this.platformId)) {
return;
}
this.osScheme.set(preferredScheme());
this.loadThemePreference();
this.watchPreferredColorScheme();
}
@@ -39,7 +48,7 @@ export class ThemeManager {
setTheme(theme: Theme): void {
this.theme.set(theme);
this.setThemeInLocalStorage();
this.setThemeBodyClasses(theme === 'auto' ? preferredScheme() : theme);
this.setThemeBodyClasses(this.resolvedTheme());
}
// 1. Read theme preferences stored in localStorage
@@ -49,7 +58,7 @@ export class ThemeManager {
const useTheme = savedUserPreference ?? 'auto';
this.theme.set(useTheme);
this.setThemeBodyClasses(useTheme === 'auto' ? preferredScheme() : useTheme);
this.setThemeBodyClasses(this.resolvedTheme());
}
// Set theme classes on the body element
@@ -77,8 +86,11 @@ export class ThemeManager {
private watchPreferredColorScheme() {
window.matchMedia(PREFERS_COLOR_SCHEME_DARK).addEventListener('change', (event) => {
const preferredScheme = event.matches ? 'dark' : 'light';
this.setThemeBodyClasses(preferredScheme);
this.osScheme.set(event.matches ? 'dark' : 'light');
if (this.theme() !== 'auto') {
return;
}
this.setThemeBodyClasses(this.resolvedTheme());
});
}
}
@@ -10,6 +10,7 @@ import {httpResource} from '@angular/common/http';
import {DOCUMENT, Service, VERSION, computed, inject} from '@angular/core';
import versionJson from '../../../assets/others/versions.json';
import {ANGULAR_DEV} from '../constants/links';
export interface Version {
displayName: string;
@@ -60,7 +61,7 @@ export class VersionManager {
// which is the most up-to-date anyway.
remoteVersions = httpResource(
() => ({
url: 'https://angular.dev/assets/others/versions.json',
url: `${ANGULAR_DEV}/assets/others/versions.json`,
transferCache: false,
cache: 'no-cache',
}),
@@ -13,9 +13,11 @@ import {
ElementRef,
EnvironmentInjector,
afterRenderEffect,
computed,
effect,
inject,
input,
linkedSignal,
signal,
untracked,
viewChild,
@@ -23,12 +25,13 @@ import {
import {takeUntilDestroyed} from '@angular/core/rxjs-interop';
import {MatTab, MatTabGroup, MatTabLabel} from '@angular/material/tabs';
import {Title} from '@angular/platform-browser';
import {debounceTime, from, map, switchMap} from 'rxjs';
import {from, switchMap} from 'rxjs';
import {TerminalType} from '../terminal/terminal-handler.service';
import {CdkMenu, CdkMenuItem, CdkMenuTrigger} from '@angular/cdk/menu';
import {IconComponent} from '@angular/docs';
import {ANGULAR_DEV} from '../../core/constants/links';
import {MatTooltip} from '@angular/material/tooltip';
import {DownloadManager} from '../download-manager.service';
import {LoadingStep} from '../enums/loading-steps';
@@ -44,8 +47,6 @@ export const REQUIRED_FILES = new Set([
'src/app/app.component.ts',
]);
const ANGULAR_DEV = 'https://angular.dev';
@Component({
selector: 'docs-tutorial-code-editor',
templateUrl: './code-editor.component.html',
@@ -82,25 +83,22 @@ export class CodeEditor {
private readonly location = inject(Location);
private readonly environmentInjector = inject(EnvironmentInjector);
private readonly errors$ = this.diagnosticsState.diagnostics$.pipe(
// Display errors one second after code update
debounceTime(1000),
map((diagnosticsItem) =>
diagnosticsItem
.filter((item) => item.severity === 'error')
.sort((a, b) =>
a.lineNumber != b.lineNumber
? a.lineNumber - b.lineNumber
: a.characterPosition - b.characterPosition,
),
),
takeUntilDestroyed(this.destroyRef),
);
readonly TerminalType = TerminalType;
protected readonly displayErrorsBox = signal<boolean>(false);
protected readonly errors = signal<DiagnosticWithLocation[]>([]);
protected readonly errors = computed(() =>
this.nodeRuntimeState.loadingStep() !== LoadingStep.READY
? []
: this.diagnosticsState
.diagnostics()
.filter((item) => item.severity === 'error')
.sort((a, b) =>
a.lineNumber != b.lineNumber
? a.lineNumber - b.lineNumber
: a.characterPosition - b.characterPosition,
),
);
protected readonly displayErrorsBox = linkedSignal(() => this.errors().length > 0);
protected readonly files = this.codeMirrorEditor.openFiles;
protected readonly isCreatingFile = signal<boolean>(false);
protected readonly isRenamingFile = signal<boolean>(false);
@@ -121,7 +119,6 @@ export class CodeEditor {
untracked(() => {
this.codeMirrorEditor.init(parent);
this.listenToDiagnosticsChange();
this.listenToTabChange();
this.setSelectedTabOnTutorialChange();
@@ -309,16 +306,6 @@ export class CodeEditor {
return true;
}
private listenToDiagnosticsChange(): void {
this.errors$.subscribe((diagnostics) => {
if (this.nodeRuntimeState.loadingStep() !== LoadingStep.READY) {
return;
}
this.errors.set(diagnostics);
this.displayErrorsBox.set(diagnostics.length > 0);
});
}
private setSelectedTabOnTutorialChange() {
// Using `from` to prevent injecting the embedded tutorial manager once the
// injector is destroyed (this may happen in unit tests when the test ends
@@ -6,13 +6,23 @@
* found in the LICENSE file at https://angular.dev/license
*/
import {WritableSignal} from '@angular/core';
import {TestBed} from '@angular/core/testing';
import {DiagnosticsState} from './diagnostics-state.service';
import {DiagnosticWithLocation, DiagnosticsState} from './diagnostics-state.service';
describe('DiagnosticsState', () => {
let service: DiagnosticsState;
const diagnostic = (message: string): DiagnosticWithLocation => ({
from: 0,
to: 1,
severity: 'error',
message,
lineNumber: 1,
characterPosition: 0,
});
beforeEach(() => {
service = TestBed.inject(DiagnosticsState);
});
@@ -20,4 +30,27 @@ describe('DiagnosticsState', () => {
it('should be created', () => {
expect(service).toBeTruthy();
});
it('should start with no diagnostics', () => {
expect(service.diagnostics()).toEqual([]);
});
it('should expose the diagnostics it is given', () => {
const diagnostics = [diagnostic('first'), diagnostic('second')];
service.setDiagnostics(diagnostics);
expect(service.diagnostics()).toEqual(diagnostics);
});
it('should replace the previous diagnostics', () => {
service.setDiagnostics([diagnostic('first')]);
service.setDiagnostics([]);
expect(service.diagnostics()).toEqual([]);
});
it('should not expose a way to write to the diagnostics signal', () => {
expect((service.diagnostics as Partial<WritableSignal<unknown>>).set).toBeUndefined();
});
});
@@ -6,9 +6,8 @@
* found in the LICENSE file at https://angular.dev/license
*/
import {Service} from '@angular/core';
import {Service, signal} from '@angular/core';
import {Diagnostic} from '@codemirror/lint';
import {BehaviorSubject, distinctUntilChanged} from 'rxjs';
export interface DiagnosticWithLocation extends Diagnostic {
lineNumber: number;
@@ -17,12 +16,11 @@ export interface DiagnosticWithLocation extends Diagnostic {
@Service()
export class DiagnosticsState {
private readonly _diagnostics$ = new BehaviorSubject<DiagnosticWithLocation[]>([]);
private readonly _diagnostics = signal<DiagnosticWithLocation[]>([]);
// TODO: use signals when zoneless will be turned off
diagnostics$ = this._diagnostics$.asObservable().pipe(distinctUntilChanged());
readonly diagnostics = this._diagnostics.asReadonly();
setDiagnostics(diagnostics: DiagnosticWithLocation[]): void {
this._diagnostics$.next(diagnostics);
this._diagnostics.set(diagnostics);
}
}
@@ -20,10 +20,8 @@ import {
signal,
viewChild,
} from '@angular/core';
import {toSignal} from '@angular/core/rxjs-interop';
import {IconComponent, TutorialType} from '@angular/docs';
import {MatTab, MatTabGroup, MatTabLabel} from '@angular/material/tabs';
import {map} from 'rxjs';
import {MAX_RECOMMENDED_WEBCONTAINERS_INSTANCES} from './alert-manager.service';
@@ -93,10 +91,9 @@ export class EmbeddedEditor {
!this.nodeRuntimeState.isResetting(),
);
private readonly errorsCount$ = this.diagnosticsState.diagnostics$.pipe(
map((diagnosticsItem) => diagnosticsItem.filter((item) => item.severity === 'error').length),
protected readonly errorsCount = computed(
() => this.diagnosticsState.diagnostics().filter((item) => item.severity === 'error').length,
);
protected readonly errorsCount = toSignal(this.errorsCount$, {initialValue: 0});
constructor() {
if (!isPlatformBrowser(this.platformId)) {
@@ -90,7 +90,7 @@ describe('CSS Value Parser Utils', () => {
expect(output).toEqual('translate(42px)');
});
it('should stringify a transform value with a function with multiple paramters', () => {
it('should stringify a transform value with a function with multiple parameters', () => {
const output = stringifyParsedValue({
type: 'transform',
values: new Map([
@@ -34,7 +34,7 @@ export class CodeBlock {
.codeToHtml(this.code(), {
cssVariablePrefix: '--shiki-',
lang: this.language(),
theme: this.theme.theme() === 'light' ? 'github-light' : 'github-dark',
theme: this.theme.resolvedTheme() === 'dark' ? 'github-dark' : 'github-light',
})
.then((hightlightedHtml) => {
return this.sanitizer.bypassSecurityTrustHtml(hightlightedHtml);
@@ -1,437 +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
*/
import {AnimationDefinition, Styles} from '../../animation';
import {AnimationRule} from '../../animation/types';
/**
* CONSTANTS
*/
// Represents percentage of the total.
// Avoid using large waves (the total shouldn't be too big as well).
// Check meteorShower function for more details.
const FIRST_WAVE_METEORS = 0.05;
const SECOND_WAVE_METEORS = 0.15;
const THIRD_WAVE_METEORS = 0.25;
// Use to increase or decrease the animation duration (i.e. a fine tuning parameter).
// Employed by `timeframe()` and `at()`.
const TIMING_MULTIPLIER = 1.55;
export const ANIM_TIMESTEP = 10; // In milliseconds
/**
* SELECTORS
*/
const BANNERS_LAYER_ID = 'banners';
const ADEV_BANNER = `${BANNERS_LAYER_ID} >> .adev-banner`;
const LEARN_ANGULAR_BTN = `${BANNERS_LAYER_ID} >> .learn-angular`;
const LOGO_LAYER_ID = 'logo';
const LOGO = `${LOGO_LAYER_ID} >> .logo`;
const SHIELD = `${LOGO_LAYER_ID} >> .shield`;
const SHIELD_MIDDLE = `${LOGO_LAYER_ID} >> .shield-middle`;
const SHIELD_BOTTOM_A_ARC = `${LOGO_LAYER_ID} >> .shield-bottom-a-arc`;
const SHIELD_BOTTOM_EXTENSION = `${LOGO_LAYER_ID} >> .shield-bottom-extension`;
const CAPITAL_A_LETTER = `${LOGO_LAYER_ID} >> .capt-a-letter`;
const N_LETTER = `${LOGO_LAYER_ID} >> .n-letter`;
const G_LETTER = `${LOGO_LAYER_ID} >> .g-letter`;
const U_LETTER = `${LOGO_LAYER_ID} >> .u-letter`;
const L_LETTER = `${LOGO_LAYER_ID} >> .l-letter`;
const A_LETTER = `${LOGO_LAYER_ID} >> .a-letter`;
const R_LETTER = `${LOGO_LAYER_ID} >> .r-letter`;
const UWU_LAYER_ID = 'uwu';
const WORKS_AT_ANY_SCALE_LAYER_ID = 'works-at-any-scale';
const METEOR_FIELD_LAYER_ID = 'meteor-field';
const METEOR_FIELD = `${METEOR_FIELD_LAYER_ID} >> .field`;
const METEORS = `${METEOR_FIELD_LAYER_ID} >> .meteor`;
const METEOR_ID = (id: number) => `${METEOR_FIELD_LAYER_ID} >> .mt-${id}`;
const LOVED_BY_MILLIONS_LAYER_ID = 'loved-by-millions';
const BUILD_FOR_EVERYONE_LAYER_ID = 'build-for-everyone';
const BUILD_FOR_EVERYONE_TITLE = `${BUILD_FOR_EVERYONE_LAYER_ID} >> .title`;
/**
* ANIMATION/HELPER FUNCTIONS
*/
// Timing functions (they employ `TIMING_MULTIPLIER`) – Use when setting the time of a rule.
const at = (at: number): number => at * TIMING_MULTIPLIER;
const timeframe = (from: number, to: number): [number, number] => [
from * TIMING_MULTIPLIER,
to * TIMING_MULTIPLIER,
];
/** Duration: 1 second */
function hideLetter(selector: string, startTime: number): AnimationRule<Styles> {
return {
selector,
timeframe: timeframe(startTime, startTime + 1),
from: {
opacity: '1',
},
to: {
opacity: '0',
},
};
}
/** Duration: 1 to 2 seconds */
function showMeteor(selector: string, startTime: number): AnimationRule<Styles> {
const randomizedStartTime = startTime + Math.random(); // Up to +1 second (excl.)
return {
selector,
timeframe: timeframe(randomizedStartTime, randomizedStartTime + 1),
from: {
opacity: '0',
transform: 'translate(200%, 200%) scale(0.3)',
},
to: {
opacity: '1',
transform: 'translate(0, 0) scale(1)',
},
};
}
/** Duration: 1 to 2 seconds */
function meteorShower(
startTime: number,
size: number,
total: number,
inUse: Set<number>,
): AnimationDefinition {
const animations: AnimationRule<Styles>[] = [];
while (animations.length < size) {
// We pick a random meteor ID.
// If `inUse` is nearly full relative to `total`,
// we might run into a excessive amount of iterations
// until we fill `animation`. This is why we should keep
// the wave sizes (and their total) relatively small.
const id = Math.round(Math.random() * (total - 1) + 1);
if (!inUse.has(id)) {
animations.push(showMeteor(METEOR_ID(id), startTime));
inUse.add(id);
}
}
return animations;
}
/**
* DEFINITION
*/
/** Generate the animation definition for the home page animation. */
export function generateHomeAnimationDefinition(
isUwu: boolean,
meteorCount: number,
): AnimationDefinition {
// Banners and buttons layer
// *************************
const bannersLayerAnim: AnimationDefinition = [
{
selector: ADEV_BANNER,
timeframe: timeframe(2, 3),
from: {
transform: 'translateY(0)',
},
to: {
transform: 'translateY(-200px)',
},
},
{
selector: LEARN_ANGULAR_BTN,
timeframe: timeframe(2.5, 3.5),
from: {
opacity: '1',
},
to: {
opacity: '0',
},
},
{
selector: LEARN_ANGULAR_BTN,
at: at(4),
styles: {
visibility: 'hidden',
},
},
];
// Logo layer animation
// ********************
const logoLayerAnim: AnimationDefinition = [
{
selector: LOGO,
timeframe: timeframe(0, 5),
from: {
transform: 'translateX(0)',
},
to: {
transform: 'translateX(467px)', // Value based on the 1280x400 SVG view box
},
},
hideLetter(R_LETTER, 1),
hideLetter(A_LETTER, 1.5),
hideLetter(L_LETTER, 2),
hideLetter(U_LETTER, 2.5),
hideLetter(G_LETTER, 3),
hideLetter(N_LETTER, 3.5),
// Make sure that the last letter disappears at the end of layer transition,
// i.e. 4 + 1 = 5th second end time
hideLetter(CAPITAL_A_LETTER, 4),
{
selector: SHIELD_MIDDLE,
timeframe: timeframe(5.5, 5.6),
from: {
transform: 'scale(1)',
},
to: {
transform: 'scale(0)',
},
},
{
selector: SHIELD_BOTTOM_A_ARC,
timeframe: timeframe(5.5, 5.6),
from: {
transform: 'scaleY(1)',
},
to: {
transform: 'scaleY(0)',
},
},
{
selector: SHIELD_BOTTOM_EXTENSION,
timeframe: timeframe(5.5, 5.6),
from: {
transform: 'scale(0)',
},
to: {
transform: 'scale(1)',
},
},
{
selector: SHIELD,
timeframe: timeframe(5.5, 10),
from: {
transform: 'scale(1) rotate(0deg)',
},
to: {
transform: 'scale(50) rotate(-360deg)',
},
},
];
// "UwU logo" layer animation
// **************************
const uwuLayerAnimation: AnimationDefinition = [
{
selector: UWU_LAYER_ID,
timeframe: timeframe(0, 5.5),
from: {
transform: 'scale(1)',
},
to: {
transform: 'scale(0)',
},
},
{
selector: UWU_LAYER_ID,
timeframe: timeframe(4, 5.5),
from: {
opacity: '1',
},
to: {
opacity: '0',
},
},
];
// "Works at any scale" layer animation
// ************************************
const waasLayerAnim: AnimationDefinition = [
{
selector: WORKS_AT_ANY_SCALE_LAYER_ID,
timeframe: timeframe(5.7, 8), // Make sure it appears after SHIELD_MIDDLE disappears.
from: {
transform: 'scale(0.1)',
opacity: '0',
},
to: {
transform: 'scale(1)',
opacity: '1',
},
},
{
selector: WORKS_AT_ANY_SCALE_LAYER_ID,
timeframe: timeframe(11, 12.5),
from: {
transform: 'scale(1)',
opacity: '1',
},
to: {
transform: 'scale(1.5)',
opacity: '0',
},
},
];
// Meteor field layer animation
// ****************************
const firstWaveSize = meteorCount * FIRST_WAVE_METEORS;
const secondWaveSize = meteorCount * SECOND_WAVE_METEORS;
const thirdWaveSize = meteorCount * THIRD_WAVE_METEORS;
const meteorsInUse = new Set<number>();
const firstWave = meteorShower(8, firstWaveSize, meteorCount, meteorsInUse);
const secondWave = meteorShower(10, secondWaveSize, meteorCount, meteorsInUse);
const thirdWave = meteorShower(12, thirdWaveSize, meteorCount, meteorsInUse);
const lastWaveStart = 16;
// For the last wave, just use the remaining meteors (don't use `meteorShower`).
const lastWave: AnimationRule<Styles>[] = [];
for (let id = 1; id <= meteorCount; id++) {
if (!meteorsInUse.has(id)) {
lastWave.push(showMeteor(METEOR_ID(id), lastWaveStart));
}
}
const meteorFieldLayerAnim: AnimationDefinition = [
{
selector: METEOR_FIELD,
at: at(7),
styles: {
display: 'flex',
},
},
{
selector: METEOR_FIELD,
timeframe: timeframe(8, 18),
from: {
transform: 'scale(1.42)',
},
to: {
transform: 'scale(1)',
},
},
...firstWave,
...secondWave,
...thirdWave,
...lastWave,
{
selector: METEORS,
timeframe: timeframe(19.5, 21),
from: {
transform: 'translate(0, 0) scale(1)',
},
to: {
transform: 'translate(-200%, -200%) scale(0.3)',
},
},
{
selector: METEOR_FIELD,
timeframe: timeframe(19.5, 21),
from: {
opacity: '1',
},
to: {
opacity: '0',
},
},
{
selector: METEOR_FIELD,
at: at(22),
styles: {
display: 'none',
},
},
];
// "Loved by millions" layer animation
// ***********************************
const lovedByMillionsAnim: AnimationDefinition = [
{
selector: LOVED_BY_MILLIONS_LAYER_ID,
timeframe: timeframe(14, 15.5),
from: {
transform: 'scale(0.75)',
opacity: '0',
},
to: {
transform: 'scale(1)',
opacity: '1',
},
},
{
selector: LOVED_BY_MILLIONS_LAYER_ID,
timeframe: timeframe(19, 20.5),
from: {
transform: 'scale(1)',
opacity: '1',
},
to: {
transform: 'scale(1.5)',
opacity: '0',
},
},
];
// "Build for everyone" layer
// **************************
const buildForEveryoneAnim: AnimationDefinition = [
{
selector: BUILD_FOR_EVERYONE_LAYER_ID,
timeframe: timeframe(22, 25),
from: {
transform: 'scale(0.75)',
opacity: '0',
},
to: {
transform: 'scale(1)',
opacity: '1',
},
},
{
selector: BUILD_FOR_EVERYONE_TITLE,
timeframe: timeframe(23, 25),
from: {
'background-position-x': '100%',
},
to: {
'background-position-x': '0',
},
},
{
selector: BUILD_FOR_EVERYONE_LAYER_ID,
timeframe: timeframe(29, 31.5),
from: {
opacity: '1',
},
to: {
opacity: '0',
},
},
];
return [
...bannersLayerAnim,
...(!isUwu ? logoLayerAnim : uwuLayerAnimation),
...waasLayerAnim,
...meteorFieldLayerAnim,
...lovedByMillionsAnim,
...buildForEveryoneAnim,
];
}
@@ -1,122 +0,0 @@
<div class="animation" [class.reduced-motion]="reducedMotion()">
@if (!isUwu()) {
<!-- Angular logo -->
<div adevAnimationLayer layerId="logo" class="logo-layer layer">
<svg class="svg" viewBox="0 0 1280 400" xmlns="http://www.w3.org/2000/svg">
<defs>
<!-- Do not remove `gradientTransform` attribute. Safari needs it in order to start the animation. -->
<linearGradient
id="gradient"
gradientUnits="userSpaceOnUse"
gradientTransform="rotate(0 640 125)"
>
<stop offset="0" stop-color="#ed0100" />
<stop offset="0.25" stop-color="#e449b4" />
<stop offset="0.5" stop-color="#ae3bfc" />
<stop offset="1" stop-color="#7f15fd" />
<animateTransform
attributeName="gradientTransform"
type="rotate"
from="0 640 125"
to="360 640 125"
dur="6.5s"
repeatCount="indefinite"
/>
</linearGradient>
</defs>
<g class="wrapper" fill="url(#gradient)">
<g class="logo">
<g class="shield">
<path
class="shield-base"
d="M311.8,101.7L302,255.6L208.3,53.8L311.8,101.7ZM246.9,300.7L176.1,341.1L105.3,300.7L246.9,300.7ZM50.1,255.6L40.4,101.7L143.9,53.8L50.1,255.6Z"
/>
<path class="shield-middle" d="M176.1,130.4L213.2,220.6L139,220.6L176.1,130.4Z" />
<path
class="shield-bottom-a-arc"
d="M106.419,301.339L105.3,300.7L119.7,265.8L232.5,265.8L246.9,300.7L245.781,301.339L106.419,301.339Z"
/>
<path
class="shield-bottom-extension"
d="M245.78,301.339L106.42,301.339L68.889,279.923L283.311,279.923L245.78,301.339Z"
/>
</g>
<path
class="capt-a-letter letter"
d="M515.4,288L553.1,288L484.4,92.7L440.7,92.7L372,288L409.7,288L425.8,239.9L499.2,239.9L515.4,288ZM435.4,211.4L461.8,132.8L463.3,132.8L489.7,211.4L435.4,211.4Z"
/>
<path
class="n-letter letter"
d="M590.4,202.2L590.4,288L555.9,288L555.9,141.6L588.9,141.6L588.9,166.5L590.6,166.5C594,158.3 599.4,151.8 606.8,147C614.2,142.2 623.4,139.8 634.3,139.8C644.4,139.8 653.2,142 660.8,146.3C668.3,150.6 674.2,156.9 678.3,165.1C682.5,173.3 684.5,183.2 684.4,194.9L684.4,288L649.9,288L649.9,200.1C649.9,190.3 647.4,182.7 642.3,177.1C637.2,171.6 630.3,168.8 621.4,168.8C615.4,168.8 610,170.1 605.3,172.8C600.6,175.4 597,179.2 594.3,184.2C591.8,189.1 590.4,195.1 590.4,202.2Z"
/>
<path
class="g-letter letter"
d="M760.5,345.9C748.1,345.9 737.5,344.2 728.6,340.9C719.7,337.6 712.6,333.1 707.2,327.5C701.8,321.9 698,315.7 696,308.9L727.1,301.4C728.5,304.3 730.5,307.1 733.2,309.9C735.9,312.7 739.5,315.1 744,317C748.5,318.9 754.3,319.8 761.2,319.8C771,319.8 779.1,317.4 785.5,312.7C791.9,308 795.1,300.2 795.1,289.4L795.1,261.7L793.4,261.7C791.6,265.3 789,268.9 785.6,272.7C782.2,276.5 777.7,279.6 772.2,282.1C766.6,284.6 759.7,285.9 751.3,285.9C740.1,285.9 729.9,283.2 720.7,277.9C711.6,272.6 704.3,264.6 699,254.1C693.6,243.5 690.9,230.3 690.9,214.3C690.9,198.2 693.6,184.6 699,173.5C704.4,162.4 711.6,154 720.8,148.2C730,142.4 740.2,139.6 751.4,139.6C760,139.6 767.1,141 772.6,143.9C778.2,146.8 782.6,150.3 785.9,154.3C789.2,158.3 791.7,162.1 793.4,165.7L795.3,165.7L795.3,141.5L829.3,141.5L829.3,290.3C829.3,302.8 826.3,313.2 820.3,321.4C814.3,329.6 806.2,335.7 795.8,339.8C785.4,343.9 773.6,345.9 760.5,345.9ZM760.8,258.8C768.1,258.8 774.3,257 779.5,253.5C784.6,249.9 788.6,244.8 791.2,238.2C793.9,231.5 795.2,223.5 795.2,214.2C795.2,205 793.9,196.9 791.2,190C788.6,183.1 784.7,177.7 779.6,173.8C774.5,170 768.2,168 760.8,168C753.1,168 746.7,170 741.5,174C736.4,178 732.5,183.5 729.9,190.4C727.3,197.4 726,205.3 726,214.1C726,223.1 727.3,230.9 730,237.6C732.6,244.3 736.5,249.5 741.7,253.2C746.8,256.9 753.2,258.8 760.8,258.8Z"
/>
<path
class="u-letter letter"
d="M933.8,226.4L933.8,141.6L968.3,141.6L968.3,288L934.8,288L934.8,262L933.3,262C930,270.2 924.6,276.9 917,282.1C909.5,287.3 900.2,289.9 889.2,289.9C879.6,289.9 871.1,287.8 863.8,283.5C856.5,279.2 850.7,273 846.6,264.7C842.5,256.5 840.4,246.5 840.4,234.8L840.4,141.6L875,141.6L875,229.5C875,238.8 877.5,246.2 882.6,251.6C887.7,257.1 894.4,259.8 902.6,259.8C907.7,259.8 912.6,258.6 917.4,256.1C922.2,253.6 926.1,249.9 929.2,245C932.3,240 933.8,233.8 933.8,226.4Z"
/>
<rect class="l-letter letter" x="979.6" y="92.7" width="34.5" height="195.3" />
<path
class="a-letter letter"
d="M1068.9,290.9C1059.6,290.9 1051.3,289.2 1043.9,285.9C1036.5,282.6 1030.6,277.6 1026.4,271.1C1022.1,264.6 1020,256.5 1020,246.9C1020,238.6 1021.5,231.8 1024.6,226.4C1027.7,221 1031.8,216.7 1037.1,213.4C1042.4,210.2 1048.3,207.7 1055,206C1061.6,204.3 1068.5,203.1 1075.6,202.3C1084.2,201.4 1091.1,200.6 1096.5,199.9C1101.8,199.2 1105.7,198 1108.2,196.5C1110.6,194.9 1111.9,192.5 1111.9,189.2L1111.9,188.6C1111.9,181.4 1109.8,175.9 1105.5,171.9C1101.2,168 1095.1,166 1087.1,166C1078.6,166 1072,167.8 1067,171.5C1062.1,175.2 1058.8,179.5 1057,184.6L1024.8,180C1027.3,171.1 1031.5,163.7 1037.4,157.6C1043.2,151.6 1050.4,147.1 1058.8,144.1C1067.3,141.1 1076.6,139.6 1086.8,139.6C1093.9,139.6 1100.9,140.4 1107.9,142.1C1114.9,143.8 1121.3,146.5 1127.1,150.3C1132.9,154.1 1137.5,159.2 1141.1,165.7C1144.6,172.2 1146.4,180.3 1146.4,190L1146.4,288L1113.2,288L1113.2,267.9L1112.1,267.9C1110,272 1107.1,275.8 1103.3,279.3C1099.5,282.8 1094.8,285.7 1089.1,287.8C1083.2,289.9 1076.6,290.9 1068.9,290.9ZM1077.9,265.6C1084.8,265.6 1090.8,264.2 1095.9,261.5C1101,258.7 1104.9,255.1 1107.7,250.5C1110.5,245.9 1111.8,240.9 1111.8,235.5L1111.8,218.2C1110.7,219.1 1108.9,219.9 1106.3,220.7C1103.7,221.5 1100.9,222.1 1097.7,222.7C1094.5,223.3 1091.4,223.8 1088.3,224.2C1085.2,224.6 1082.5,225 1080.2,225.3C1075.1,226 1070.4,227.1 1066.4,228.7C1062.3,230.3 1059.1,232.5 1056.8,235.3C1054.4,238.1 1053.3,241.8 1053.3,246.2C1053.3,252.6 1055.6,257.4 1060.3,260.6C1064.8,264 1070.7,265.6 1077.9,265.6Z"
/>
<path
class="r-letter letter"
d="M1156.9,288L1156.9,141.6L1190.4,141.6L1190.4,166L1191.9,166C1194.6,157.5 1199.2,151 1205.7,146.4C1212.2,141.8 1219.7,139.5 1228.1,139.5C1230,139.5 1232.2,139.6 1234.5,139.7C1236.9,139.9 1238.9,140.1 1240.5,140.4L1240.5,172.1C1239,171.6 1236.7,171.1 1233.6,170.7C1230.5,170.3 1227.4,170.1 1224.5,170.1C1218.2,170.1 1212.6,171.5 1207.6,174.2C1202.6,176.9 1198.7,180.6 1195.8,185.4C1192.9,190.2 1191.5,195.7 1191.5,201.9L1191.5,288L1156.9,288Z"
/>
</g>
</g>
</svg>
</div>
} @else {
<!-- UwU logo -->
<div adevAnimationLayer layerId="uwu" class="uwu-layer layer">
<img src="assets/images/uwu.png" alt="Angular logo" />
</div>
}
<!-- Works at any scale -->
<div adevAnimationLayer layerId="works-at-any-scale" class="works-at-any-scale-layer layer">
<h2>Works at any scale</h2>
<p>
Angular lets you start small on a well-lit path and supports you as your team and apps grow.
</p>
</div>
<!-- Meteor field -->
@if (meteorFieldData(); as meteorFieldData) {
<div adevAnimationLayer layerId="meteor-field" class="meteor-field-layer layer">
<div
class="field"
[style.width]="meteorFieldData.width + 'px'"
[style.height]="meteorFieldData.height + 'px'"
[style.marginLeft]="meteorFieldData.marginLeft + 'px'"
[style.marginTop]="meteorFieldData.marginTop + 'px'"
>
@for (type of meteors(); track $index) {
<div [class]="['meteor', 'type-' + type, 'mt-' + ($index + 1)]"></div>
}
</div>
</div>
}
<!-- Loved by millions -->
<div adevAnimationLayer layerId="loved-by-millions" class="loved-by-millions-layer layer">
<h2>Loved by millions</h2>
<p>
Join the millions of developers all over the world building with Angular in a thriving and
friendly community.
</p>
</div>
<!-- Build for everyone -->
<div adevAnimationLayer layerId="build-for-everyone" class="build-for-everyone-layer layer">
<h2 class="title">Build for everyone</h2>
<p>
Rely on Angular's built-in hydration, internationalization, security, and accessibility
support to build for everyone around the world.
</p>
</div>
</div>
@@ -1,403 +0,0 @@
@use 'sass:math';
@use '@angular/docs/styles/media-queries' as mq;
/*
* Global transition.
*
* Scrolling via mouse scroll wheel will result in a non-continuous scrollY,
* unlike a touchpad, which in turn results in "gaps" in the animation calculations.
* Therefore, in order to keep things smooth, we are systematically adding CSS
* transitions to the CSS properties that else look jagged when scrolling.
*/
$transition: 200ms linear;
:host {
display: block;
position: relative;
.animation {
position: fixed;
top: 0;
bottom: 0;
right: 0;
left: 0;
.layer {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
h2 {
font-size: 4vw;
font-weight: 600;
white-space: nowrap;
margin-top: 0;
margin-bottom: 0.5em;
z-index: 1;
@include mq.for-tablet-landscape-down {
& {
font-size: 2rem;
}
}
}
p {
font-weight: 400;
color: var(--quaternary-contrast);
font-size: clamp(1rem, 1vw, 2rem);
line-height: 1.5;
width: clamp(345px, 50%, 600px);
margin: 0 auto;
}
h2,
p {
background-color: var(--page-background);
box-shadow: 0 0 20px 20px var(--page-background);
@include mq.for-desktop-down {
box-shadow: 0 0 10px 10px var(--page-background);
}
}
}
.banners-layer {
z-index: 10;
.adev-banner-container {
display: flex;
flex-direction: column;
gap: 0.5rem;
position: absolute;
transition:
background 0.3s ease,
border 0.3s ease,
transform $transition;
top: var(--layout-padding);
left: calc(var(--layout-padding) + var(--primary-nav-width));
@include mq.for-tablet-landscape-down {
top: 6rem;
left: var(--layout-padding);
/* Assuming the container width is identical to the viewport width (mobile device). */
max-width: calc(100% - var(--layout-padding) * 2);
}
@include mq.for-phone-only {
& {
top: 5rem;
}
}
}
.adev-banner {
display: flex;
align-items: center;
flex-wrap: wrap;
gap: 0.5rem;
border: 1px solid var(--senary-contrast);
background: var(--page-background);
border-radius: 0.25rem;
padding: 10px;
max-width: 100%;
width: fit-content;
box-sizing: border-box;
h1,
p {
display: inline;
font-size: 0.875rem;
margin: 0;
background-image: var(--red-to-pink-to-purple-horizontal-gradient);
background-clip: text;
color: transparent;
width: fit-content;
font-weight: 500;
box-shadow: none;
position: relative;
&.adev-banner-cta {
color: var(--tertiary-contrast);
&::after {
content: '';
position: absolute;
width: 100%;
transform: scaleX(0);
height: 1px;
bottom: -2px;
left: 0;
background: var(--red-to-pink-to-purple-horizontal-gradient);
transform-origin: bottom right;
transition: transform 0.3s ease;
}
}
}
&:hover {
.adev-banner-cta {
&::after {
transform: scaleX(1);
transform-origin: bottom left;
}
}
}
}
.learn-angular {
position: absolute;
left: 50%;
bottom: 5%;
transform: translateX(-50%);
transition: opacity $transition;
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
gap: 50px;
button {
font-size: 1rem;
padding: 1rem 1.75rem;
&::after {
font-size: 1rem;
}
}
.adev-arrow {
transform: rotate(45deg);
border: solid var(--primary-contrast);
border-width: 0 2px 2px 0;
display: inline-block;
padding: 7px;
}
}
}
.logo-layer {
user-select: none;
pointer-events: none;
.svg {
position: absolute;
width: 100%;
height: 100%;
fill-rule: evenodd;
clip-rule: evenodd;
stroke-linejoin: round;
stroke-miterlimit: 2;
}
.wrapper {
transform: scale(0.55);
transform-origin: 50% 50%;
@include mq.for-tablet-down {
& {
transform: scale(0.8);
}
}
@include mq.for-extra-large-desktop-up {
& {
transform: scale(0.5);
}
}
.logo {
transition: transform $transition;
.shield {
/* `transform-origin` values are calculated based on the 1280x400 SVG view box */
&,
.shield-middle {
transform-origin: 177px 200px;
transition: transform $transition;
}
.shield-bottom-a-arc {
transform-origin: 0 302px;
transition: transform $transition;
}
.shield-bottom-extension {
transform-origin: 177px 340px;
transition: transform $transition;
transform: scale(0);
}
}
.letter {
transition: opacity $transition;
}
}
}
}
.uwu-layer {
display: flex;
align-items: center;
justify-content: center;
transition:
transform $transition,
opacity $transition;
img {
user-select: none;
width: max(calc(700 * min(100vw, 2560px) / 1470), 350px);
height: auto;
}
}
.works-at-any-scale-layer,
.loved-by-millions-layer,
.build-for-everyone-layer {
user-select: none;
pointer-events: none;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
text-align: center;
opacity: 0;
z-index: 1;
transition:
transform $transition,
opacity $transition;
}
.works-at-any-scale-layer {
transform: scale(0);
}
.loved-by-millions-layer,
.build-for-everyone-layer {
transform: scale(0.75);
}
.build-for-everyone-layer {
.title {
color: transparent;
display: inline-block;
background: linear-gradient(110deg, #f31a5b 0, #8737e9 50%, #3a373f 0%);
background-size: 205% 100%;
background-clip: text;
background-size: 205% 100%;
background-position-x: 100%;
transition: background-position-x $transition;
}
}
.meteor-field-layer {
overflow: hidden;
.field {
--math-pi: #{math.$pi}rad;
position: absolute;
flex-wrap: wrap;
align-content: flex-start;
gap: var(--meteor-gap);
display: none;
transform: scale(1.42);
top: 0;
left: 0;
transform-origin: 50% 50%;
transition:
opacity $transition,
transform $transition;
.meteor {
position: relative;
width: var(--meteor-width);
height: var(--meteor-height);
opacity: 0;
transform: translate(200%, 200%) scale(0.3);
transition:
opacity $transition,
transform $transition;
&::after {
content: '';
position: absolute;
width: 4px;
height: var(--meteor-tail-length);
border-radius: 2px;
transform-origin: top center;
transform: rotate(var(--meteor-tilt-angle));
top: 0;
left: 0;
}
@mixin meteor-gradient($startColor, $endColor) {
background: linear-gradient(
calc(var(--math-pi) + var(--meteor-tilt-angle)),
$startColor 0,
$endColor 66%,
transparent 100%
);
}
&.type-1::after {
@include meteor-gradient(rgb(228, 49, 85), rgb(219, 64, 219));
}
&.type-2::after {
@include meteor-gradient(rgb(152, 56, 226), rgb(217, 18, 167));
}
&.type-3::after {
@include meteor-gradient(rgb(214, 53, 150), rgb(229, 91, 229));
}
}
}
}
&.reduced-motion {
position: relative;
.banners-layer {
height: 100vh;
@include mq.for-phone-only {
.adev-banner {
top: 6rem;
}
}
@include mq.for-tablet-landscape-up {
.adev-banner {
left: var(--layout-padding);
}
}
}
.logo-layer {
position: relative;
height: 100vh;
@include mq.for-tablet-landscape-down {
margin-top: -75px;
}
}
.works-at-any-scale-layer,
.loved-by-millions-layer,
.build-for-everyone-layer {
position: relative;
height: 120vh;
opacity: 1;
transform: scale(1);
}
.build-for-everyone-layer > .title {
background-position-x: 0;
}
}
}
}
@@ -1,170 +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
*/
import {
afterNextRender,
Component,
DestroyRef,
ElementRef,
inject,
Injector,
input,
output,
signal,
viewChildren,
} from '@angular/core';
import {isIos, shouldReduceMotion, WINDOW} from '@angular/docs';
import {RouterLink} from '@angular/router';
import {AnimationCreatorService, AnimationLayerDirective} from '../../animation';
import {AnimationScrollHandler} from '../../animation/plugins/animation-scroll-handler';
import {ANIM_TIMESTEP, generateHomeAnimationDefinition} from './animation-definition';
export const METEOR_HW_RATIO = 1.42; // Height to width ratio
export const METEOR_GAP_RATIO = 1.33; // Use 0.7 for WebGL-like field. Renders a lot of elements though.
// A map with screen size to meteor width
export const METEOR_WIDTH_MAP = [
[800, 60],
[1100, 90],
];
export const METEOR_WIDTH_DEFAULT = 120; // For screens larger than 1100px
type MeteorDimensions = {
width: number;
height: number;
tailLength: number;
gap: number;
tiltAngle: number; // In radians
};
type MeteorFieldData = {
width: number;
height: number;
count: number;
marginLeft: number;
marginTop: number;
};
@Component({
selector: 'adev-home-animation',
imports: [AnimationLayerDirective, RouterLink],
templateUrl: './home-animation.component.html',
styleUrl: './home-animation.component.scss',
})
export class HomeAnimationComponent {
private readonly win = inject(WINDOW);
private readonly animCreator = inject(AnimationCreatorService);
private readonly injector = inject(Injector);
private readonly elementRef = inject(ElementRef);
private readonly destroyRef = inject(DestroyRef);
readonly animationLayers = viewChildren(AnimationLayerDirective);
readonly ctaLink = isIos ? 'overview' : 'tutorials/learn-angular';
readonly isUwu = input.required<boolean>();
readonly ready = output<boolean>();
readonly reducedMotion = signal<boolean>(shouldReduceMotion());
readonly meteorFieldData = signal<MeteorFieldData | null>(null);
readonly meteors = signal<number[]>([]);
constructor() {
if (!this.reducedMotion()) {
this.initAnimation();
} else {
this.ready.emit(true);
}
}
private initAnimation() {
// Limitation: Meteor dimensions won't change on page resize
const meteorDimensions = this.calculateMeteorDimensions();
const data = this.calculateMeteorFieldData(meteorDimensions);
this.setCssVariables(meteorDimensions);
this.meteorFieldData.set(data);
// Generate a meteor field. The number represents the type [1, 3]
this.meteors.set(new Array(data.count).fill(1).map(() => Math.round(Math.random() * 2 + 1)));
afterNextRender({
read: () => {
const animation = this.animCreator
.createAnimation(this.animationLayers(), {timestep: ANIM_TIMESTEP})
.define(generateHomeAnimationDefinition(this.isUwu(), this.meteors().length))
.addPlugin(new AnimationScrollHandler(this.elementRef, this.injector));
this.ready.emit(true);
this.destroyRef.onDestroy(() => animation.dispose());
},
});
}
/** Calculate the dimensions and sizes of a meteor – width, height, tail, tilt angle, etc. */
private calculateMeteorDimensions(): MeteorDimensions {
let width = METEOR_WIDTH_DEFAULT;
for (const [screenSize, meteorWidth] of METEOR_WIDTH_MAP) {
if (this.win.innerWidth <= screenSize) {
width = meteorWidth;
}
}
const height = width * METEOR_HW_RATIO;
const gap = width * METEOR_GAP_RATIO;
// Pythagorean theorem + some trigonometry
const tailLength = Math.sqrt(width * width + height * height);
const tiltAngle = -Math.asin(width / tailLength);
return {
width,
height,
gap,
tailLength,
tiltAngle,
};
}
/** Calculate the number of meteors and size of the field. */
private calculateMeteorFieldData(meteorDim: MeteorDimensions): MeteorFieldData {
const mW = meteorDim.width + meteorDim.gap;
const mH = meteorDim.height + meteorDim.gap;
let rows = 1;
let cols = 1;
while (cols * mW - meteorDim.gap <= this.win.innerWidth) {
cols++;
}
while (rows * mH - meteorDim.gap <= this.win.innerHeight) {
rows++;
}
const width = cols * mW - meteorDim.gap;
const height = rows * mH - meteorDim.gap;
return {
count: rows * cols,
width,
height,
marginLeft: -(width - this.win.innerWidth) / 2,
marginTop: -(height - this.win.innerHeight) / 2,
};
}
private setCssVariables({width, height, tailLength, tiltAngle, gap}: MeteorDimensions) {
const styleRef = this.elementRef.nativeElement.style;
styleRef.setProperty('--meteor-width', width + 'px');
styleRef.setProperty('--meteor-height', height + 'px');
styleRef.setProperty('--meteor-tail-length', tailLength + 'px');
styleRef.setProperty('--meteor-tilt-angle', tiltAngle + 'rad');
styleRef.setProperty('--meteor-gap', gap + 'px');
}
}
@@ -6,6 +6,7 @@
list-style: none;
overflow-y: auto;
overflow-x: hidden;
overscroll-behavior: contain;
height: 100vh;
padding: 0;
margin: 0;

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