May 10, 2019
Optimize the performance of large lists in Angular
Challenge
If you are working with lists or tables which have a big amount of rows and columns, the performance can get worse if you don’t watch out. Especially if you have a large table with a lot of FormGroups inside. The following steps should clean up your code and increase the performance of your grid measurably. In this post we will show you eight ways to optimize the performance of large lists in Angular.
1. Change detection strategy: OnPush
Most of the time, the change detection of Angular feels like a nice magic, that makes your life much easier. But if you have a large app with a big amount of values, you will realize, that the change detection is a complex thing, that makes your app sometimes a bit slow.
If a value in your template is changed, Angular checks each value in the view for updates. Doing this, Angular compares the old value with a supposed new value. Depending on the amount of data this can take a lot of time. To fix this problem, it is possible to change the change detection strategy of Angular on the component level.
1 2 3 4 5 6 |
@Component({ selector: 'app-my-table', templateUrl: './my-table.component.html', styleUrls: ['./my-table.component.scss'], changeDetection: ChangeDetectionStrategy.OnPush }) |
After this change, angular only goes for a deep check, if the input (@Input) reference of a value has changed. Otherwise Angular will only do a references check. If the references of the variable changed, Angular will compare both values like mentioned before.
Now you have to care to use immutable objects. Make sure to always create a new object instead of manipulating the old one, so the object gets a new reference and the change detection will work as expected. Using the OnPush strategy should significantly reduce the change detection calls.
Angular also provides a way to trigger the change detection programmatically. Maybe it could be useful to trigger the change detection every few seconds programmatically, instead of triggering the change detection automatically every time a value has changed.
Therefor take a look at this snippet:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, OnInit } from '@angular/core'; @Component({ selector: 'app-my-table', templateUrl: './my-table.component.html', styleUrls: ['./my-table.component.scss'], changeDetection: ChangeDetectionStrategy.OnPush, }) export class MyTableComponent implements OnInit { constructor(private cdr: ChangeDetectorRef) { } ngOnInit() { // deactivate the change detection for this component and its children this.cdr.detach(); // interval for doing the change detection every 5 seconds setInterval(() => { this.cdr.detectChanges(); }, 5000); } } |
For more information read here.
2. The updateOn property
Each FormGroup has a property called updateOn. This property determines when the form validations for the current form group is called. Possible values are change (default), submit and blur.
default: Form validations is always called when the value is changed
blur: Form validation is called, when you are leaving the input field
submit: Form validation is called, when you submit the form
We change the updateOn property to blur and as result after leaving the input field the change detection is called the first time. Otherwise Angular calls the form validation every time we make changes inside the input field. Depending on the use case of the application the submit or blur property could be the better choice and will offer always a better performance.
3. Form validators
The construction of form validations for a table is based on one parent form group containing one form group for each row in the table.
Pay attention, that you attach the validation function to the hierarchically lowest point that is possible. For example, there is a field with the name age. This field cannot be zero. Now you should attach your custom form validator to the age field and not to the whole form group it belongs to.
The required function only runs, if the value of the age field is changes. If we attach the validator to the complete group, the form validation function is called when any value of the form group is changed.
4. Pure pipes instead of functions calls
Sometimes you might want to manipulate data in the template directly. We want to avoid unnecessary calls, so we should choose to implement a pipe instead of call a function inside the template.
Angular just calls pure pipes if the reference (Objects or Arrays) or the value (string/number/boolean) of the current field changes.
If you use a function or an impure pipe, the function or impure pipe calculates a value in every change detection lifecycle.
Check this out for a detailed explanation and a clean example.
5. Virtual scrolling
Angular provides the opportunity, to render only the part of the table, that is currently in the view. The Application needs less Resources for rendering the full table, more resources are available to calculate other stuff.
Check this out for a full implementation.
6. Infinite scrolling
Virtual scrolling only loads the elements, that are visible in the viewport. But this comes at the cost of reloading the previously loaded elements, when you scroll up again. For this use case we have infinite scrolling. Infinite scrolling loads the list elements laziliy. So if you want to keep the already loaded elements in the DOM, infinite scrolling is your method. One famous example are the rabbit holes of Facebook, Instagram or Youtube that seem to deliver infinite content. Your application loads the content when you reach a certain threshold on the bottom of the loaded list.
One Angular implementation for infinite scrolling you can try, is the package ngx-infinite-scroll.
Because of the lazy nature of infinite and virtual scrolling, these two methods are two of the most effective ways to optimize the performance of large lists in Angular.
7. trackBy function
For example you have a dropDown list with a lot of items (using *ngFor). Now you are constantly adding and removing items to the list. Angular renders the full list, because Angular doesn’t know which item has changed. To avoid unnecessary rendering you can use the trackBy function.
If you want to read more about trackBy click here.
8. Always unsubscribe
Always make sure to unsubscribe to avoid memory leaks. For example if you use a subscription for the valueChanges of your form group, pay attention to unsubscribe at the latest in the ngOnDestroy function. If you don’t, Angular will not throw away the subscription until all subscribers are unsubscribed. You can also use a library, that takes care about it.
So as you can see, there are several ways to optimize the performance of large lists in Angular. Which one you want to use depends on your specific Requirements and context.