Performance killer – setTimeout

in Front-End Web Technologies

For the last few weeks I had the opportunity to analyze the performance for a large scale Angular application.

The analysis revealed the following problematic aspects

1. Holding too large DOM tree in memory

2. Running too much dirty checking

3. Executing too much AJAX requests

I am guessing you are not surprised with the results. Most applications “suffer” the same issues. Surprisingly, I also encounter a strange aspect. There were too many setTimeout invocations.

Calling setTimeout (for Angular2) or $timeout (for Angular1) means a new dirty checking. The problematic application that I analyzed initiates more than 50 setTimeout’s per one user action !!!

If you wonder why setTimeout is being used so many time, just keep reading

Lets assume the following Angular2 view (for the app component)

 <tabset [activeTabIndex]="activeTabIndex">
     <tab *ngFor="let tab of tabs" [title]="tab.title">
         <span>Content {{tab.id}}</span>
     </tab>
 </tabset>

We use ngFor directive to create multiple tab component.

<div>
     <div class="content" [ngClass]="{'active': isActive}">
         <ng-content></ng-content>
     </div>
 </div>
  @Component({
      selector: 'tab',
  })
  export class TabComponent {
      isActive: boolean;
      @Input() title: string;
  
      constructor(private tabset: TabsetComponent) {
          this.tabset.addTab(this);
     }
 }

Each time a new tab component is created it informs its parent (the tabset component) about the change by invoking the method addTab

  @Component({
      selector: 'tabset',
      template: `<ul>
                      <li *ngFor="let tab of tabs" (click)="onClick(tab)">Tab {{tab.title}}</li>
                 </ul>
                 <ng-content></ng-content>`,
      styles: [require("./tabset.css!text")],
      encapsulation: ViewEncapsulation.None,
      changeDetection: ChangeDetectionStrategy.OnPush,
 })
 export class TabsetComponent {
     private tabs:TabComponent[];
 
     constructor() {
         this.tabs = [];
     }
     
     addTab(tab:TabComponent) {
         this.tabs.push(tab);
     }
 }

The tabset component updates its internal data structure and Angular does the magic of updating the DOM.

This code is quite straightforward. Lets make it even more realistic. For example, the tabset component may want to support a property named activeTabIndex. Using this property the application can set the active tab of the tabset.

 export class TabsetComponent {
      private tabs: TabComponent[];
      @Input() activeTabIndex: number;
  
      constructor() {
          this.tabs = [];
          this.activeTabIndex = -1;
      }
  
     ngOnChanges(args) {
         console.log("ngOnChanges", args);
 
         if(args.activeTabIndex) {
             console.log("  activeTabIndex changed to: " + this.activeTabIndex);
             this.select(this.tabs[this.activeTabIndex]);
         }
     }
 }

And the modified app component

<h1>My First Angular 2 App</h1>
 
 <button (click)="addTab()">Add Tab</button>
 
 <tabset [activeTabIndex]="activeTabIndex">
     <tab *ngFor="let tab of tabs" [title]="tab.title">
         <span>Content {{tab.id}}</span>
     </tab>
 </tabset>

  export class AppComponent {
      tabs: Tab[];
      activeTabIndex: number;
      nextTabId: number;
  
      constructor(appRef: ApplicationRef) {
          console.log(appRef);
  
          this.nextTabId = 1;
         this.tabs = [
             {id:this.nextTabId++, title: "1"},
             {id:this.nextTabId++, title: "2"}
         ];
     }
 
     addTab() {
         this.tabs.push({
             id:this.nextTabId++, title: "3"
         });
 
         this.activeTabIndex = this.tabs.length - 1;
     }
 }

the addTab method adds a new entry to the tabs array and set the active tab to be the one that was just added.

Can you detect the bug?

The problem is that Angular informs the tabset component of the activeTabIndex change before a new tab component is created. Angular detect changes according to component tree, top to bottom. First it detects that the AppComponent.tabs changes. Then it detects that the activeTabIndex changes and informs the tabset component and only then it creates the new tab component.

As result, the tabset component sees an invalid activeTabIndex and ignores it

How can we fix that ?

Easily. Just add a setTimeout call and postpone the activeTabIndex change after the TabComponent is created.

Here is the fixed code,

 addTab() {
     this.tabs.push({
         id:this.nextTabId++, title: "3"
     });
 
     setTimeout(() => {
         this.activeTabIndex = this.tabs.length - 1;
     }, 0);
 }

Running above code and everything is just great. Really ?

At first, a single setTimeout invocation feels quite minor. However, imagine a large scale Angular application where each component depends on another one. Postponing the activeTabIndex change by 0 milliseconds means that you loose control of the data flow inside your application. It is just a matter of time until you will need to introduce a second setTimeout but this time probably with a larger interval. The road eventually leads to 50 setTimeout (like the application we analyzed) and then the frustration of “Hi, my application is slow! should I switch technology?”

In most cases, the need for setTimeout is a result of bad architecture.

If you are struggling with boosting your Angular application performance then send me an email at oric@newtlogic.wpengine.com

Contact us
You might also like
Share: