Looking at the Google Search Console metrics of most enterprise-level Angular applications created between 2018 and 2023 will show you a sea of red.
The change of metrics from First Input Delay to Interaction to Next Paint was a rude awakening for web developers. INP requires that the browser renders a frame every time the user makes an interaction within 200ms.
Most legacy Angular apps are not meeting this requirement.
The problem does not arise from the developers of Angular writing poor code. It arises from the very architecture of the original Angular being intrinsically opposed to the browser’s primary thread. This is the exact structure of an INP problem, along with how the Zoneless architecture fixes it.
1. The Anatomy of an INP Failure
The first step in analyzing why applications built using Angular fail to pass the INP test lies in the Change Detection Tax.
A typical Angular application using zone.js doesn’t know what changed; it only knows that there’s been a change.
Picture this scenario: a user is looking at an intricate eCommerce dashboard with 5,000 DOM elements. They click the “Like” button on one product card.
Here is what happens on the main thread:
- The Click: User clicks on the heart button.
- The Interception: zone.js intercepts the event.
- The Execution: Your component performs this.isLiked = true.
- The Tax: Zone.js asks Angular to perform a full top-down change detection cycle.
- The Freeze: Angular goes through from the root AppComponent to check whether there have been any changes within the header, sidebar, footer, and all 5,000 nodes in the dashboard.
- The Paint: The browser gets around to rendering the red heart.
If that traversal (step 5) took 250 milliseconds, then your application failed the INP test. You taxed the main thread with thousands of unnecessary checks only to update a CSS class.
2. The OnPush Band-Aid
The Angular team used to recommend a particular fix that went like this: “Use ChangeDetectionStrategy.OnPush.”
Adding the OnPush tag to your components was like saying to Angular, “Do not consider the components in this cycle if their @Input() properties haven’t been updated.”
However, it was only a temporary measure, which could lead to major issues. Should a junior developer forget to add OnPush for a heavyweight table component, the change detection cycle was bound to spread, and your INP score would be compromised. Not only that, OnPush would need to rely on zone.js to execute the cycle in the first place.
3. The Cure: The Zoneless Paradigm
Zone.js is not optimized in 2026. Rather, we remove it.
True Zoneless Angular makes changes to the way the rendering process works. Rather than working on a “pull-based” method where everything has to be checked to see what changed, the process is now “push-based” with the exact knowledge of what to update.
Enabling it involves removing zone.js from the polyfills and adding an experimental provider in the app.config.ts file.
import { ApplicationConfig, provideExperimentalZonelessChangeDetection } from '@angular/core';
export const appConfig: ApplicationConfig = {
providers: [
provideExperimentalZonelessChangeDetection()
]
};
4. How Signals Fix INP
When you become Zoneless, the interaction of your application will depend only on Signals.
As a reactive primitive, a Signal will monitor its own consumers. Let’s recall our case with the “Like” button in modern terms.
import { Component, signal } from '@angular/core';
@Component({
selector: 'app-product-card',
template: `
<button (click)="toggleLike()">
<icon [class.text-red]="isLiked()" />
</button>
`
})
export class ProductCardComponent {
isLiked = signal(false);
toggleLike() {
this.isLiked.set(!this.isLiked());
}
}
Here is what happens on the main thread now:
- The click: The user clicks on the icon
- The Execution: toggleLike() executes, updating the Signal.
- The notification: The Signal immediately notifies the particular view that has subscribed to it (app-product-card template).
- The painting: Angular updates only the particular DOM node and then paints it.
There is no zone.js interception. There is no global traversal. The AppComponent is not checked.
What used to take 250ms back in 2021 now takes only 2ms. Your INP score will drop to zero.
Summary
In case your Angular application fails the Google INP metric, then cease any attempts to optimize RxJS operators or customize Webpack configuration in any way.
- Face the problem head-on: top-down change detection does not comply mathematically with the strict criteria of INP for complex pages.
- Implement Signals: transform synchronous UI states to signal().
- Embrace Zonelessness: eliminate zone.js completely via providingExperimentalZonelessChangeDetection().
Thus, with a single architectural change, you eliminate the Change Detection Tax forever, ensuring that the INP score will be perfect, regardless of how big the DOM tree gets.
Comments
Loading comments…