DEV Community

Cover image for Boosting Angular Speed with Better Change Detection
bytebantz
bytebantz

Posted on

Boosting Angular Speed with Better Change Detection

Angular Change detection is a feature that detects changes in the application’s data and updates the DOM accordingly, ensuring that the user interface remains up-to-date with the latest data. Imagine you have a to-do list app. When you add a new item, change detection ensures that the new item appears on the screen without you having to manually refresh the page.

Angular uses a library called Zone.js to help with change detection. Zone.js is a library that provides a mechanism for wrapping JavaScript code and executing it in a specific context or zone. In Angular, Zone.js is used to create a new zone for each component and to track changes within that component’s zone. A zone is basically an execution context for your code. When the application starts, Angular creates its own zone instance ngZone by extending the one provided by Zone.js.

By default, Angular uses a “check always” strategy, where it checks all components in the component tree for changes on every event cycle. This can be inefficient, especially for large applications with complex component trees.

Angular Change Detection runs automatically by default. However, there are times when you may want to manually trigger change detection. To manually trigger change detection in your application, you can use the detectChanges() method provided by the ChangeDetectorRef class which will run change detection on this view and its children. This method can be used within components or services

constructor(private cd:ChangeDetectorRef){}
    // Call this function when you want to manually trigger change detection 
    detectChanges(){
        this.cd.detectChanges();
 }
Enter fullscreen mode Exit fullscreen mode

We can also trigger change detection manually by using:

- ApplicationRef.tick() which triggers change detection for every components in the component tree (basically whole application)

- markForCheck() on ChangeDetectorRef which marks all OnPush ancestors to be checked once, either on the current or next detection cycle. This method does not trigger the change detector directly.

Running specific code outside the Zone allows developers to manage change detection manually, which can be beneficial in complex applications where optimizing change detection cycles is critical.

Any code that we want to run outside of the Angular zone should be placed inside runOutsideAngular().

By placing code inside runOutsideAngular(), developers can ensure that certain operations do not trigger change detection, thus improving performance.

export class AppComponent {
    constructor(private zone: NgZone) { }

    handleClick() {
        this.zone.runOutsideAngular(() => {
            // Code to run outside of the Angular zone
        });
    }
}
Enter fullscreen mode Exit fullscreen mode

Similarly any code that we want to run inside of the Angular zone should be placed inside run().

This simplifies development as developers don’t have to manually trigger change detection after every asynchronous operation.

export class AppComponent {
    constructor(private zone: NgZone) { }

    handleClick() {
        this.zone.run(() => {
            // Code to run inside the Angular zone
        });
    }
}
Enter fullscreen mode Exit fullscreen mode

Angular comes with two component change detection strategies: default and onPush.

i. Default (check always)

In the default change detection strategy, Angular automatically triggers change detection whenever an event occurs or data changes.

The process works by traversing the component tree in the application. It starts from the root component and checks for changes in each component even if no changes occured.

If you have a large number of components or nested components in your Angular application, performance issues are more likely to occur.

ii. OnPush

The OnPush change detection strategy allows you to optimize performance by only triggering change detection when input properties of a component change or when an event has been fired from that component.

This strategy significantly reduces the number of checks Angular performs, leading to better performance, especially in large and complex applications.

Developers can specify this strategy at the component level, meaning that some components can use “OnPush” while others continue to use the default strategy.

We can change the detection strategy from default to ChangeDetectionStrategy.OnPush by adding changeDetection property on @Component decorator.

import { ChangeDetectionStrategy, Component } from '@angular/core';
@Component({
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class MyComponent {}
Enter fullscreen mode Exit fullscreen mode

Since “OnPush” relies on reference changes to detect updates, mutating the properties of an object (e.g., adding or modifying elements in an array) won’t trigger change detection. Always create new instances of objects or arrays when updating them.

To treat a reference type as immutable, reassign the value all together.

In this example, calling updateName() will not trigger change detection in the ChildComponent because the reference to data remains the same.

export class ParentComponent {
    data = { name: 'John' };

    updateName() {
        this.data.name = 'Doe'; // Mutating the existing object
    }
}
Enter fullscreen mode Exit fullscreen mode

Here, updateName() creates a new object, changing the reference, which triggers change detection in the ChildComponent.

export class ParentComponent {
    data = { name: 'John' };

    updateName() {
        this.data = { ...this.data, name: 'Doe' }; // Creating a new object
    }
}
Enter fullscreen mode Exit fullscreen mode

To effectively use the “OnPush” change detection strategy in Angular, always ensure immutability

When to use OnPush

· You have a component that solely depends on immutable input parameters.

· You have a component which has heavy rendering (such as components that display charts, graphs etc.). In this case, you can use OnPush to save computation power by avoiding to render the component when it is not necessary

Conclusion

Angular’s change detection, powered by Zone.js, keeps the user interface in sync with data changes. While the default “check always” strategy ensures the UI is updated automatically, it can be inefficient for large applications. Developers can manually control change detection using methods like detectChanges() or markForCheck(). Running code outside Angular’s zone can optimize performance by reducing unnecessary change detections. Additionally, using the OnPush strategy helps improve performance by only checking for changes when necessary. By understanding and using these strategies, developers can build efficient and responsive Angular applications.

Top comments (0)