Previously, the modelContext truthiness check could be bypassed via DOM
clobbering (e.g. `<form id="modelContext">`), causing a truthy HTMLElement
to pass the guard and then throw when `registerTool` was called on it.
Replace the truthiness check with a duck-type check that asserts
`registerTool` is a function, rejecting both absent and clobbered values.
Add handling in navigation for creating and destroying injectors scoped
to `ActivatedRoute` life.
The code for creating the injectors is certainly more complicated
than it _could_ be since there's no actual feature built around this yet.
Keeps as much implementation code tree-shakeable as possible:
Raw size: +764 bytes
Gzipped size: +182 bytes
This commit addresses a scaling issue in the signal dependency graph where
the detection of duplicate dependency links would perform a linear scan across
all consumer links of all producers. The linear scan is replaced with a version
comparison of the dependency edge against the current epoch; if they are
equal the existing dependency edge is known to be valid in this epoch. This means
that the link won't be eligible for removal and therefore doesn't have to be
recreated.
`COMMENT_DISALLOWED` is matched globally, so overlapping delimiter
sequences are skipped: `<!-->` only escapes the leading `<!--` and
leaves a live `-->` that can close a programmatically created comment
node early. Drop the `^` anchors so a standalone `>`/`->` is escaped
wherever it appears, which neutralizes the trailing delimiter left
behind by an earlier match.
Adds support for `@Input` transform functions in isolated declarations mode (`emitDeclarationOnly: true`), allowing components and directives to specify `transform` functions without triggering fatal compiler errors.
Synthesizes the `ngAcceptInputType_` write type syntactically:
- For referenced functions (`transform: booleanAttribute`), emits `Parameters<typeof booleanAttribute>[0]`, relying on downstream template type checking to resolve the type.
- For inline functions (`transform: (v: string) => boolean`), extracts `parameters[0].type` directly from the local TypeScript AST.
Removes restrictions around using external references and local directives in `hostDirectives` under isolated declarations mode (`emitDeclarationOnly: true`).
By wrapping the host directive reference in a `WrappedNodeExpr`, TypeScript's declaration emitter seamlessly emits `typeof hostReference.node`, preserving existing imports or local identifiers exactly as authored. Also adds support for translating `PropertyAccessExpression` inside `WrappedNodeExpr` into `QualifiedName` for `.d.ts` emission, ensuring namespace imports (`import * as n from './dir'`) are preserved correctly.
Mark the iframe `credentialless` attribute as security-sensitive so dynamic
bindings are handled consistently with other iframe attributes that affect the
initial navigation, such as `sandbox`, `allow`, `referrerPolicy`, `csp`, and
`fetchPriority`.
Because `credentialless` must be present before the iframe starts loading to
affect the navigation’s credential mode, late dynamic updates can leave the final
DOM looking correct while the initial request was not loaded credentiallessly.
Fixes that when a listener unsubscribes from an `output` within its own callback, it was preventing subsequent listeners from running.
These changes fix the issue by not mutating the array while the emit loop is running, but replacing the listener with `null` and coming back later to remove it.
Fixes#69325.
Only cache locale data loaded from the global locale registry when an actual locale entry is found.
This prevents attacker-controlled missing locale identifiers from being retained indefinitely in SSR when locale lookup falls back to a parent locale or the built-in English locale, avoiding unbounded process memory growth in locale-aware pipes and formatters.
Previously, the compiler disallowed translation of any attribute starting with 'on' for security reasons. This incorrectly disallowed translation of the 'on' attribute itself, which is not an event handler.
This commit introduces `isPossibleEventHandler` to verify that the property name has a length greater than 2 in addition to starting with 'on'. This allows attributes like 'on' to be translated while still correctly disallowing actual event handlers like 'onerror', 'onclick', etc.
`animate.leave` was skipped — the element was removed from the DOM
synchronously instead of running its leave animation — whenever a
sibling instance of the same template entered in a different DOM parent
during the same change-detection tick (e.g. an exclusive-expansion
accordion or nav where opening section B collapses section A).
`leavingNodes` is keyed by `TNode`, which is shared by every instance of
a template. When a node was inserted, `cancelLeavingNodes` force-removed
any tracked leaving node whose DOM parent differed from the entering
node's parent (the `leavingParent !== newParent` branch added to
de-duplicate a dynamic component re-rendered into a fresh overlay pane).
For two distinct live sibling instances that merely share a `TNode`,
"different parent" is the normal situation, so the still-animating
sibling was ripped out.
Track the declaration view of each leaving element alongside it, and
only perform the cross-parent removal when the entering element belongs
to the same declaration view as the leaving one — i.e. the same logical
view re-rendered, the case the branch was written for. Two distinct
instances of a shared template have different declaration views, so
their `animate.leave` is now left to run to completion.
This preserves the dynamic-component/overlay de-duplication (#67032) and
the drag-and-drop node-move rescue (#67361), which are unchanged.
Fixes#69291
Serialize transfer cache request parameters without comma-joining repeated values so distinct HttpClient requests cannot reuse the same cached response.
Initialize zoneSymbolEventNames with Object.create(null) instead of {}.
This is hardening only. addEventListener('__proto__', fn) is not
directly attacker-controllable — its presence in an application is
itself an application bug and a prerequisite for any issue here.
Without this change, if that application bug exists, two unexpected
behaviors follow depending on environment:
Browser: zoneSymbolEventNames['__proto__'] reads the __proto__ getter
and returns Object.prototype (truthy), bypassing prepareEventNames.
symbolEventName resolves to undefined and window['undefined'] = []
throws TypeError.
Node.js + --disable-proto=throw: the assignment
zoneSymbolEventNames['__proto__'] = {} inside prepareEventNames
triggers the disabled __proto__ setter and throws.
Using Object.create(null) removes the __proto__ accessor from the
map so the key is treated as a plain missing property in both cases.
`findAnchorFromDocument` interpolates the raw url fragment into
`[name="${target}"]` for the shadow DOM lookup, so a fragment reachable
through the router when `anchorScrolling` is on can break out of the
attribute selector and make `querySelector` throw or match unrelated
nodes, and it also breaks legitimate anchor names containing a quote.
Wrap the value in `CSS.escape` so it stays a single attribute-value token.
This limit breaks ts-ignore comments when using this for our source->source transform.
Rather than overridding it there, it's just removed here since we don't care about the limit
This test flakes occasionally because it is called in production when a platform is created and unregistered when a platform is destroyed. However, not all tests properly clean up their platforms, meaning we can accidentally leak platforms between tests. If this happens, we end up have an event listener created from the production code path and a second event listener from the test. When the test emits the event, both listeners respond and it causes too many responses which fails the test.
Ideally, all tests would clean up the platforms correctly, but this seems difficult to guarantee for all Angular tests and is likely to break over time. The simplest solution is just destroy any leaked platform before the test starts. It's a bit elegant, but the safest option.
Normalize SVG animation attributeName lookup to also recognize lowercase attributename before allowing dynamic animation value bindings.
Add runtime and platform-server SSR regression coverage for lowercase attributename retargeting.
Replace `in` operator with `Object.hasOwn` in
formatDateTime to prevent prototype pollution attacks.
The `in` operator traverses the prototype chain, meaning a polluted
Object.prototype key could be picked up as a valid replacement value.
This is especially critical in SSR environments where a single
prototype pollution attack persists across all subsequent requests in
the shared Node.js process, potentially injecting malicious content
into every user's rendered HTML.
Using `Object.hasOwn` restricts the lookup to
own properties only, blocking prototype chain traversal.
Reading the form model on init is unsafe as it could depend on inputs (eg a required input). We need to delay the read by a tick (after the inputs are set) to ensure that values can be safely read.
fixes#69262
The `watchValidity` method in `AnimationInputValidityMonitor` was registering
an anonymous arrow function via `addEventListener` with no corresponding
`removeEventListener` call.
In V8, each closure is represented as a `JSFunction` holding a strong pointer
to a heap-allocated `Context` object containing captured variables
(`VariableLocation::CONTEXT` slots, decided at parse time by
`Scope::MustAllocateInContext`). In Blink, DOM event listeners are stored in
the element's `EventTargetData::event_listener_map` as `JSEventListener`
wrappers backed by a `v8::Persistent<JSFunction>` handle — a strong cross-heap
reference that keeps the function alive as long as the element is alive.
Because the callback passed to `watchValidity` closes over the calling
component/directive (which itself holds a reference back to the element), this
produced a cross-heap reference cycle:
```
HTMLInputElement (Blink/Oilpan)
└── EventTargetData → JSEventListener → v8::Persistent<JSFunction>
└── Context → callback closure
└── component → HTMLInputElement ← cycle
```
Neither V8's nor Blink's GC could independently break this cycle because it
crosses the V8/Oilpan heap boundary. The element was therefore never collected
after being removed from the DOM.
The fix stores the listener in a named local variable and registers its removal
via `DestroyRef.onDestroy`, tying cleanup to the lifetime of the component that
owns the element. This ensures `removeEventListener` is called with the exact
same `JSFunction` reference, causing Blink to drop the `v8::Persistent` handle
and allowing both the function and the element to become GC-eligible.
Guard FormGroup control-map presence checks with safe own-property checks to avoid inherited/prototype collisions from reserved keys such as hasOwnProperty and toString.
This prevents:
- crashes from shadowed hasOwnProperty access paths
- incorrect early-return and existence behavior for prototype-named controls
Adds regression tests for prototype-shadowed keys covering:
- register/add with toString
- contains/get with hasOwnProperty
- setControl/removeControl with toString
- FormRecord behavior with hasOwnProperty
Update the AI codegen resources for Angular v22:
- best-practices.md: OnPush is the default in v22+ (don't set it explicitly),
recommend Signal Forms, and recommend the @Service decorator.
- llms.txt: add a Signal Forms reference, the httpResource guide, and an
Accessibility section linking the Angular Aria overview.
Inlay hints from external templates were being incorrectly applied to
TypeScript files because the compiler was processing all templates
associated with components found in the TS file, regardless of whether
the template was inline or external. This resulted in misplaced hints
due to mismatched offsets.
This change filters the templates and host bindings processed in
getInlayHintsForTemplate to only include those that belong to the
target file being queried.
Fixes#69224
Previously, `NavigationStateManager` relied on string-based comparisons and `.substring()` to match `NavigateEvent` URLs against internal router transitions or the application root boundary. This was brittle against trailing slashes, query parameter order variations, and sibling application URLs.
This commit updates the logic to:
- Use the native `URL` object to strictly compare `origin` and `pathname` for `appRootURL` boundaries.
- Sort `searchParams` and use `Location.stripTrailingSlash()` to robustly compare the router destination against the event destination.
- Pre-compute and store `appRootUrl` as a `URL` object to avoid redundant parsing on every navigation.
The `escapeCssUrl` helper used by `NgOptimizedImage` to sanitize placeholder URLs for use in the `background-image` CSS property previously escaped only backslashes and double quotes. However, several characters that can terminate a CSS quoted string according to the CSS Syntax Level 3 specification were left unescaped, allowing a crafted placeholder URL to break out of the `url("...")` context and inject arbitrary CSS.
This change additionally escapes the following characters using CSS hex escapes:
* `U+000A` (LINE FEED) → `\A `
* `U+000D` (CARRIAGE RETURN) → `\D `
* `U+000C` (FORM FEED) → `\C `
* `U+0000` (NULL) → `\0 `
For example:
```text id="1w5vkp"
x.com/img\nx.jpg → x.com/img\A x.jpg
x.com/img\rx.jpg → x.com/img\D x.jpg
x.com/img\fx.jpg → x.com/img\C x.jpg
x.com/img\0x.jpg → x.com/img\0 x.jpg
```
The trailing space is required by the CSS tokenizer to terminate the escape sequence and prevent the following character from being interpreted as part of the escape.
The backslash replacement remains first in the chain to avoid double-escaping the backslashes introduced by subsequent replacements.