Free Webinar: How to

Implement and Control AI

in Your Dev Team.

Chapters

    TOP 5 Features of Angular 22 for Better Performance

    We know that Angular releases often move faster than most teams can keep up with. While it may seem overwhelming, it’s worth paying attention to what’s changed in the newest version, especially since Angular 22 gives your team tools to make your product faster and easier to optimize.

    That’s because in the v22 release, performance is an especially important theme. Making OnPush change detection and incremental hydration the default will encourage the adoption of practices that improve runtime efficiency without additional configuration. Meanwhile, features such as resource caching and lazy dependency injection give developers more explicit, first-class tools to further optimize applications.

    In this article, based on the newest edition of our free ebook “The Ultimate Guide to Angular Evolution,” we’ll cover the TOP 5 Angular 22 features for better performance and show you how theycan help your team build faster, more efficient Angular applications.

    Ebook "The Ultimate Guide To Angular Evolution" with version 22 and a new chapter on AI in Angular. Download for free.

    Key Takeaways:

    • Angular 22 focuses heavily on performance, with several key changes making previously optional optimizations the new default behavior.
    • OnPush is now the default change detection strategy, and the old default mode has been renamed to ChangeDetectionStrategy.Eager for clarity.
    • Incremental hydration is now enabled by default in provideClientHydration(), with an explicit opt-out available for apps that need different behavior.
    • A new experimental feature, withExperimentalAutoCleanupInjectors(), automatically destroys unused route-related injectors, particularly useful in apps with complex routing or long-lived sessions.
    • The new injectAsync helper lets developers load services lazily through dynamic imports, keeping heavy code out of the initial bundle and reducing startup costs.
    • Resource caching for SSR via TransferState allows resource() and rxResource() values resolved on the server to be reused on the client, avoiding redundant data fetching during hydration.
    • For teams ready to put these changes into practice, our Angular Performance Workshop offers hands-on training tailored to your stack and goals, with exercises built around your actual codebase

    1. OnPush as default change detection strategy

    Challenge

    Angular’s long-standing eager change detection model has been convenient, but it no longer reflects how modern Angular applications are typically built. As signals and zoneless applications have become more common, OnPush has increasingly become the preferred strategy because it avoids unnecessary checks and gives developers tighter control over when updates happen. Angular’s own roadmap describes the move toward making OnPush the default, in line with current best practices.

    Solution

    Angular now treats OnPush as the default change detection strategy. To make the alternative mode easier to understand, Angular also introduces ChangeDetectionStrategy.Eager as an explicit alias for the previous ChangeDetectionStrategy.Default. 

    This naming is clearer than OnPush, because the non-default option is no longer labeled ‘Default’. Under the hood, Angular keeps things compatible by resolving enum members by value. This is how Eager works correctly during compilation while keeping the behavior of the old eager, check-always mode.

    Benefits

    Making OnPush the default would make Angular more like the performance-oriented architecture that is now the modern standard for the framework. It makes the rendering process more predictable, reduces unnecessary work, and fits naturally with signals and zoneless applications

    At the same time, renaming the old mode to Eager improves readability and clarifies intent in the code, as developers can now explicitly choose between an on-demand and an eager strategy.

    2. Incremental hydration as default behavior

    Challenge

    Although incremental hydration improves the performance and responsiveness of server-rendered Angular applications, requiring developers to opt in means that many projects miss out on it by default. When an important change depends on extra configuration, it is natural for it to be adopted more slowly and for application setups to become less consistent across the ecosystem.

    Solution

    Angular changes provideClientHydration() so that incremental hydration is now enabled by default. At the same time, it introduces a new withNoIncrementalHydration() option for applications that need to opt out explicitly. To make the transition safer, Angular has also introduced conflict checks and provided a schematic migration to update existing codebases.

    export const appConfig: ApplicationConfig = {
      providers: [
        provideClientHydration(),
        // Add only when you want to disable the default behavior:
        // provideClientHydration(withNoIncrementalHydration())
      ],
    };

    Benefits

    This change sets a modern hydration strategy as the standard instead of making it an optional upgrade. It makes setting up applications easier, encourages the use of incremental hydration, and helps to make performance-oriented defaults more consistent across Angular projects. Developers still have control through an explicit opt-out mechanism when their application requires different behavior.

    3. Route injector cleanup (experimental)

    Challenge

    Angular does not automatically destroy the EnvironmentInjectors associated with detached routes when they are no longer active or retained by the RouteReuseStrategy. While this is usually not a problem in most applications,  in systems with complex routing structures or long-lived sessions, it can lead to unnecessary memory usage.

    Solution

    Angular introduces an experimental automatic cleanup for route-related EnvironmentInjectors. When the withExperimentalAutoCleanupInjectors() method is enabled, the router keeps track of which detached routes are still in use and destroys any unnecessary injectors. This behavior is compatible with the BaseRouteReuseStrategy.

    For applications that use a custom reuse strategy, Angular also allows developers to specify when an injector should be destroyed.

    @Injectable()
    export class CustomRouteReuseStrategy implements RouteReuseStrategy {
      // ... other methods
    
    
      shouldDestroyInjector(route: Route): boolean {
        return !route.data?.[RETAIN_INJECTOR_DATA_KEY];
      }
    }

    Benefits

    This improves memory management by releasing resources that would otherwise remain attached to unused routes. This is particularly useful in applications with advanced route reuse patterns, large route trees, or sessions that stay active for a long time. At the same time, it gives developers more control over the route lifecycle behavior and helps prevent subtle memory retention issues.

    4. Lazy service injection

    Challenge

    Not all services need to be available as soon as an application starts. Some are only required following a specific user action, within a rarely visited screen, or as part of an optional feature. While it has been possible to load such services lazily in Angular until now, it has not been especially elegant. Developers had to combine dynamic imports with additional dependency injection, making the process seem more like a workaround than an integral part of the framework.

    Solution

    Angular introduces injectAsync, a helper that makes lazy service injection feel like a natural part of the framework rather than a custom pattern built around import(). Rather than injecting a service eagerly during application startup, developers can provide a loader function that imports the service dynamically only when it is needed

    Angular still resolves the final token through its dependency injection system, so that the service is created and managed in the same way as any other injectable dependency.

    @Component({...})
    export class DemoComponent {
      private readonly _heavyTransformationService = injectAsync(() =>
        import('./heavy-transformation.service').then((m) => m.HeavyTransformationService),
      );
    
    
      async performHeavyTransformation(params: TransformationParams): Promise<void> {
        const transformationService = await this._heavyTransformationService();
        transformationService.transform(params)
      }
    }

    The API also includes InjectAsyncOptions, which gives you more control over when Angular starts fetching that dependency. In the initial implementation, the key option is prefetch, which lets the application begin loading the service ahead of time instead of waiting for the exact moment it is first requested. 

    That creates a useful middle ground: a service can remain outside the initial bundle, but still be ready before the user actually needs it. In other words, injectAsync is not only about lazy loading, but also about choosing the right loading strategy for a feature.

    Benefits

    The biggest benefit is improved performance. Since the asynchronously injected service is loaded via a dynamic import, it does not need to be included in the initial application bundle. This means that code for rarely used services can be deferred until it is needed, reducing startup costs and improving initial load performance. At the same time, developers benefit from a cleaner API for lazy loading and less boilerplate code for handling the injector, as well as a solution that is fully aligned with modern Angular architecture.

    5. Caching resource for SSR

    Challenge

    Although resources are useful in server-side rendering scenarios, without caching, the same data may need to be loaded again on the client, even if it has already been resolved on the server. This reduces the efficiency of SSR and can result in unnecessary repeated work during hydration, particularly for data that could have been transferred directly from the server to the client.

    Solution

    Angular adds the ability to cache resource() and rxResource() values for SSR using TransferState. It incorporates an id option as an identifier used to cache resource data during server-side rendering and retrieve it on the client. This identifier must be the same on both the server and the client. Internally, Angular now checks TransferState for a matching cached value before loading the resource again, and when running on the server, it saves resolved values back into TransferState.

    // Some TransferState key
    const productKey = makeStateKey('product');
    
    
    @Component({...})
    export class DemoComponent {
      private readonly _productService = inject(ProductService);
      readonly productId = input<string>();
    
    
      readonly productResource = rxResource({
        params: () => this.productId(),
        stream: ({ params: productId }) => this._productService.getProduct(productId),
        id: productKey,
      });
    }

    Benefits

    The result is that SSR caching becomes part of the resource model itself, rather than something that developers have to set up manually around every asynchronous data source. This improves the hydration, allowing already resolved resource values to be reused on the client instead of being fetched again. Consequently, Angular resources become more efficient in server-rendered applications, reducing duplicate loading.

    Private Workshops Tailored to Your Team on Angular architecture, performance, Security in Angular, Agentic Angular, and more. Contact for more info.

    Summary

    Angular 22 makes performance the framework’s default state, not an afterthought. 

    With OnPush change detection and incremental hydration, teams get faster rendering and smoother hydration without extra configuration. On top of that, tools like injectAsync, resource caching via TransferState, and experimental route injector cleanup give developers more precise control over what loads, when, and how much memory it holds onto.

    Taken together, these changes push Angular further from its Zone.js-heavy past and closer to a lean architecture built for real-world SSR and large-scale apps.

    And if you want your team to put these changes into practice:

    Our hands-on Angular Performance Workshop, led by Angular experts, is built for teams ready to move past trial-and-error and actually master the shift to Zoneless. 

    Every part of the workshop, from exercises and specific topics to form and duration, is tailored to your team’s needs and your business goals:

    Whether you’re chasing better LCP scores or tracking down SSR hydration errors, this is where you stop guessing and start applying Angular’s new reactive engine directly to your own codebase.

     

    Stop guessing. Start applying — with experts in the room.

    The Angular Performance Workshop is tailored to your stack and goals, not generic examples.

    • Remote or on-site at your office
    • Post-workshop consultations for solving your implementation challenges
    • Full VAT invoicing and documentation handled

     

    Written by
    Miłosz
    Passionate about web application development, enthusiast for clean code and best practices. Focused on becoming better at Angular every day. In his free time, he enjoys cooking and listening to podcasts.

    Social Media

     
    The latest articles