Solving slow template rendering in loklak search

I was working on one of the issues which was to render top hashtags of the last 24 hours on the home-page of loklak search. The loklak search.json API took just milliseconds to return the response. However, it took about 3 minutes to render the HTML. Generally, API response acts as a Rate Determining Step but here, template rendering process was slow so, that optimization became crucial.

This blog will explain how I improved slow template rendering problem using some APIs of Angular. I would be explaining common mistakes we often make and solution about how to quickly render HTML after data is received from the APIs.

Previous Code

In this code, we have made an API request with query ‘since:day’ on the initialization of the Home page component. Next, we have subscribed to the store which will return an observable of hashtags. Then, we are subscribing to the observable of hashtags and extract the data from it.

In the HTML file, now we are presenting hashtags using *ngFor and *ngIf property.

This code working is fine. The only fault is that the template is not aware if some input is received or some state property has changed. Therefore, we need a trigger to do detect the change and render the HTML.

TypeScript File

export class HomeComponent implements OnInit, OnDestroy {
    public apiResponseHashtags$: Observable<Array<{ tag: string, count: number }>>;
    public apiResponseHashtags: <Array<{ tag: string, count: number }>>;

constructor(private store: Store<fromRoot.State>)
ngOnInit() {
this.getTopHashtags();
this.getDataFromStore();}
}

    private getTopHashtags() {
    this.store.dispatch(new queryAction.RelocationAfterQuerySetAction());
this.store.dispatch(new queryAction.InputValueChangeAction('since:day'));
    }

    private getDataFromStore() {
this.apiResponseHashtags$ =  this.store.select(fromRoot.getApiResponseTags);
    this.apiResponseHashtags$.subscribe((data) => {
this.apiResponseHashtags = data;
});
    }
}

HTML File

<div class="top-hashtags">
    <div *ngIf="apiResponseHashtags.length !== 0">
        <span *ngFor ="let item of apiResponseHashtags">
            <a [routerLink]="['/search']" [queryParams]="{ query : '#' + item.tag }">#{{item.tag}}</a>&nbsp;
        </span>
    </div>
</div>

Solution

These are the following changes that need to be introduced for quick HTML rendering.

  • Here, we will be using ‘Change Detection Strategy: OnPushwhile defining component for pushing changes in the template. Using ‘OnPush’ will make changes to the template as soon as some input is received.

 

  • Instead of subscribing to the observables of hashtags, we need to use the async pipe to extract data from observable in the HTML file. This is because, in the previous code, we were using subscribed value for hashtags. Now, the observable of hashtags will work like an input for the template and  ‘Change Detection Strategy: OnPush’ will act as a trigger.

TypeScript file

@Component({
    selector: 'app-home',
    templateUrl: './home.component.html',
    styleUrls: ['./home.component.scss'],
    changeDetection: ChangeDetectionStrategy.OnPush
})
export class HomeComponent implements OnInit, OnDestroy {
public apiResponseHashtags$: Observable<Array<{ tag: string, count: number }>>;

constructor(private store: Store<fromRoot.State>)
ngOnInit() {
this.getTopHashtags();
this.getDataFromStore();}
}


private getTopHashtags() {
    this.store.dispatch(new queryAction.RelocationAfterQuerySetAction());
this.store.dispatch(new queryAction.InputValueChangeAction('since:day'));
    }

    private getDataFromStore() {
this.apiResponseHashtags$ = this.store.select(fromRoot.getApiResponseTags);
    }
}

HTML File

<div class="top-hashtags">
    <div *ngIf="(apiResponseHashtags$ | async).length !== 0">
        <span *ngFor ="let item of (apiResponseHashtags$ | async)">
            <a [routerLink]="['/search']" [queryParams]="{ query : '#' + item.tag }">#{{item.tag}}</a>&nbsp;
        </span>
    </div>
</div>

Conclusion

Whenever the template data depends on the API response or user interaction, it is beneficial to use ChangeDetectionStrategy  and reduce template rendering time. Moreover, instead of subscribing to observables of data received from the API, we will be using an async pipe to make observable as an input for the template.

Resources

  • https://angular-2-training-book.rangle.io/handout/change-detection/change_detection_strategy_onpush.html
  • https://stackoverflow.com/questions/39795634/angular-2-change-detection-and-changedetectionstrategy-onpush/39802466#39802466
  • https://www.youtube.com/watch?v=X0DLP_rktsc