The First Step Toward Signal-Based Change Detection
A recently merged pull request will land in the upcoming v17 release. It marks the initial move toward fine-grained reactivity and signal-driven components.
You can find the full pull request details below:
perf(core): Update LView consumer to only mark component for check — #52302
— atscott posted on
The commit modifies reactive template and host binding consumers so they only flag their declaring component for refresh, leaving ancestors untouched.
It also updates the AfterViewChecked hook to fire when a component is refreshed during change detection even if its host isn't. Expecting ngAfterViewChecked to run after a signal update triggers a component refresh is reasonable, since hooks usually run when the host is refreshed. Without this adjustment, the decision to skip marking ancestors dirty would have suppressed ngAfterViewChecked.
This resolves #14628 and #22646.
Regarding #34347 — it's not directly about that issue, but forcing change detection has generally been necessary only to sync updated values to the DOM. Signal-based values automatically mark the component for check, so accessing a child's ChangeDetectorRef becomes unnecessary. Another part of that request was eliminating the need to "mark all views for checking since it wouldn't affect anything but itself." That is exactly what this PR addresses — updating a signal read in a child's template won't trigger refreshes in ancestors or the entire view tree.
The core idea is to refresh only the component where the change originated, though two conditions must be met:
- All components must use OnPush.
- The change must come from a Signal.
Let's look at why both are required for local change detection to work.
Understanding Change Detection
Angular relies on Zone.js to detect changes and schedule a render. Because Zone.js has no visibility into where a change occurred, Angular rerenders from the top down — starting at the RootComponent and moving through every child. While fast, this is expensive since even trivial updates cause the entire component tree to rerender.
ChangeDetectionStrategy.OnPush was introduced to make the cycle more efficient. Marking a component for check also marks all of its ancestors, after which Angular runs a top-down pass and refreshes only those flagged components.
With v16, Angular introduced Signals, a reactive API that pinpoints exactly where a change happens. Angular can now determine precisely which components need a refresh. A Signal marks the owning component as dirty, without affecting ancestors. When the top-down change detection runs, only that one component gets refreshed.
Important Considerations
- Angular must retain its top-down change detection for backward compatibility, especially with components that still depend on Zone.js and don't use Signals.
- Granularity is currently limited to the component level. Going finer could break how non-signal components are refreshed. Finer granularity may come later, but it will never reach the level of individual bindings. See the RFC for more.
Wrapping Up
This change primarily benefits apps that embrace both Signals and OnPush.
- Signals let Angular locate the change source and flag only the affected component as dirty.
- OnPush is required because Angular still does top-down checks, and it only refreshes OnPush components that have been marked. Default components will always be refreshed.
Upgrade to v17 and start adopting Signals — this is only the beginning of what they can do for your Angular app.
Find me on Twitter or Github. Feel free to reach out with any questions.
Thanks to Enea for reviewing.
