Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

Improve Front-End Performance Using Angular Change Detection Strategy

DZone's Guide to

Improve Front-End Performance Using Angular Change Detection Strategy

Learn how to improve your performance using the change detection functionality that comes with Angular out of the box.

· Web Dev Zone ·
Free Resource

Access over 20 APIs and mobile SDKs, up to 250k transactions free with no credit card required

Angular change detection helps us reflect our component data to the user. If you don't use it properly, you can easily hurt your front-end performance.

In this post, I'll use Chrome Dev Tools for measuring the performance.

What We Have Now

We have an app component with a drop-down list to change the number of items per page. Inside the app component, we have a membership component to display a list of our users and their level of membership.

App component

The membership component will receive input data (a variable named "members") from the app component and display it. This is the high-level code for our demonstration application:

Component-level code

The function heavyCalculation inside of the calcMemberShipLevel function will simulate each membership-level calculation and take 10ms to finish. In a real use case, it can be a heavy calculation or make you end up waiting for the result to be returned from other sources.

Address the Problem

Let's open the console and open the drop-down list:

Image title

You see the function gets called many times, even though we still have not selected an item in this dropdown list.

Even worse, if I choose to display five items per page, the performance becomes really bad. As shown below, it gets only 4FPS and that function takes more than 200ms to finish.

Image title

Explaining Our Problem

Angular change detection will be triggered when there's an event that happens, like a mouse click, and it will check all individual components, even though there's no change in the data. As in our case, we click on the drop-down list and the function gets called because Angular cannot detect whether the result of thecalcMemberShipLevel function is changed until it runs the function.

Solution

First Attempt

Now we'll try to make the change detection on the membership component only get triggered when it gets new input data.

With OnPush, change detection will be triggered when the component gets new input. 'New' here means a new reference to a data object.

With that in mind, let's open the membership component and add a setting for the OnPush strategy as shown in line 4 below:

 @Component({
  selector: "app-membership",
  templateUrl: "./membership.component.html",
  changeDetection: ChangeDetectionStrategy.OnPush,
  styleUrls: ["./membership.component.scss"]
})

Next, I'll use the Immutable library to accomplish this step. The Immutable library helps us simplify the process of copying new objects and add memory optimization by reusing the data without completely copying all the data to the new list. For more about this library, check it out at its home page: Immutable.

Firstly, we'll declare our variable with the List type from Immutable:

import { List } from 'immutable';
...
members :List ;
membersInCurrentPage: List;  

Then we're able to use methods from the List type as shown in line 3:

changeNumberOfItem(itemPerPage: number) {
    this.itemPerPage = itemPerPage;
    this.membersInCurrentPage = this.members.setSize(itemPerPage);
 }

The setSize method returns a new List with size as its input. Now, the change detection only gets triggered when we actually change the data of the list. It's time to check the performance:

Image title

You'll see that calcMemberShipLevel only gets called after we click and it takes more than 100ms to finish; the FPS rate increases to 7 (almost 50% improved), but it's still really bad.

Still, there's one more technique we can apply to improve it.

Second Attempt

If our function is pure, which means it has one input, we'll always get the same returned result. We can apply Angular Pipe to cache the result next time we use it; this will help us save a lot of effort. For more about Pipe, check it out in my another post here.

Now we’ll create a pipe:

import { Pipe, PipeTransform } from '@angular/core';

@Pipe({
  name: 'calcMemberShipLevel',
  pure: true
})
export class CalcMemberShipLevelPipe implements PipeTransform {
  transform(value: number, args?: any): any {
    return this.getMemberShipLevel(value);
  }
  getMemberShipLevel(point: number): String{
    console.info("---calcMemberShipLevel runs---");
    this.heavyCalculation(10);    
    if(point > 900){
      return 'Platinum';
    }else if(point > 700){
      return 'Gold';
    }else if(point > 500){
      return 'Silver';
    }
    return 'Basic';
  }
}

And use it as in line 7:

<tbody>
      <tr *ngFor="let member of members; index as i">
        <th scope="row">{{ i + 1 }}</th>
        <td>{{ member.firstName }}</td>
        <td>{{ member.lastName }}</td>
        <td>{{ member.point }}</td>
        <td>{{ member.point | calcMemberShipLevel }}</td>
      </tr>
</tbody>

Let's check our performance again:

Angular Performance

Now with the result in cache, our performance is really improved as in picture 45 FPS.

That's really cool!

Conclusion

We used OnPush change detection to make Angular change detection only get triggered when needed.

We used Pipe to cache the result of our function in the template if it’s a pure function.

Last, but not least, always remember to measure to make sure your performance doesn't hurt the user experience.

Don't forget to check out the source code here.

#1 for location developers in quality, price and choice, switch to HERE.

Topics:
web dev ,web application performance ,web app performance ,angular tutorial ,front-end development

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}