Catalysing your Angular 4 app Performance

Param Singh
paramsingh_66174
Published in
8 min readJul 7, 2017

As important it is to finish your project on time, the performance of the developed product matters a lot too. Further, the stakes are high when a lot of users are dependent on your piece of development. With great power n freedom comes great responsibility. While the JS world has provided us with voluminous libraries and frameworks, its the responsibility of the developer ultimately to make the most out of it and match up with the business needs while providing a slick user interface experience to its users.

Similar has been my experience and learning. Delivering a colossal product with sated customers has never been an easy job. But over time, we have thrived to meet our users needs at par with other apps in the market using the cutting edge front-end technologies. Some of which are Angular 2, Redux, ImmutableJS, webpack etc. So, below are some caveats that I have learnt so far for revving up your Angular app and would like to share with you.

Keep track of the loops

Any app you’ll take will definitely embody a lot of *ngFor loops. So, just target these loops in the very first place because any silly booboo you’re gonna make in any of the repeating entity or component will cost you a good toll on performance.

A very prime way to optimise your loops is to keep track of *ngFor them using the trackBy property.

And then you can create a trackBy function in your ts file like this:

So, you can either track your song list data through the indices or through a primary key in your data model like a usual id. Specifying trackBy in Angular loops helps it to identify the rows being added or removed when the model/data of the application changes. Since, DOM operations are quite expensive, it would be wise to not have Angular create all the siblings nodes in a collection if a single node is being added, removed or modified. Tracking mechanism helps Angular avoid such a mishap. If you’re from a ReactJS background, it’s synonymous to keys.

Go Fully Immutable

To understand this point, we need to understand what makes frameworks slow. Can you guess?? Yes, it the re-renders. In the declarative style frameworks like Angular wherein you bind the dynamic model values in your template like {{valueName}}, the framework has to do a lot of work every time the value of a bound variable changes from anywhere in the code. And as your app grows, you can imagine the number of such re-renders due to such bindings! So, does that mean we shouldn’t use bindings. No, that’s an absurd suggestion.

The Domino Effect

Instead, lets peep into how Change Detection in Angular works. Discussing it would deviate us from our current discussion. So, I leave it to you to understand how it works from this fabulous explanation by rangle folks.

The crux of the above reference turns out that we should supply Immutable inputs to our components and use the ChangeDetectionStrategy.OnPush

Hence, except from the primitive datatype input properties, always resort to using Immutable Inputs. In fact, all the data communication in your app should happen using Immutable Data Structures. One way you can enforce Immutability is by using this Facebook’s library called ImmutableJS. It’ll reduce the number of re-renders and you may end up improving your app performance smartly.

Be vigilant of the Scroll Handlers

While talking about the app performance or slick User Interfaces, one of the key factors that determines a good user experience is the Scroll Performance. Nothing’s more frustrating than a jerky scroll experience. So, have you ever pondered why that jerk happen after all? You need to determine the cause of the problem for reaching it’s solution. Here’s a nice article by Paul Lewis you can refer to.

So, in most of the cases scroll performance suffers due to some expensive scroll event listeners. Since scroll event is something that going to be triggered very frequently and if you’re doing some heavy DOM operation inside a scroll handler, definitely you’re gonna experience a nasty judder while scrolling your page. You can try it out by placing some DOM computation logic of a heavy or dynamic element inside your scroll handlers like this.

In my case, I have encountered such problems in places where I were to implement Infinite Scroll i.e when I have to make an api call automatically to load next set of data in a list when the user has reached the bottom of the page.

I have an infinite Scroll Directive in my Angular app that applies a scroll event handler to the given element like this

which I’m using in a scrollable container div like this

The problem is that on every little scroll, the logic for “reachedBottom?” is being checked. Though, in this example it’s still fine but consider the case you want to use function like getBoundingClientRect() or getComputedStyle. You’ll observe serious effect on your scroll perf.

Longer frames causing Jank

Does that mean we can’t use such logic inside our handlers? Well, you can if you can do it smartly by taking a pinch of RxJS ingredient as follows.

If there’s a way to trigger the scroll handler only when like the user has paused for a few milliseconds, we’re set. And yes, RxJS is a panacea offering an operator called debounceTime.

What it does basically is it doesn’t triggers the event at all while the event is happening over time but when there’s a pause of more than the specified debounceTime like 20ms in the above case, it triggers the event. So, you see the user only pauses for some activity to happen and 20 ms pause is quite fast, so there won’t be any negative effects on your current experience. Instead, the experience would improve because earlier when the same event was happening for every little scroll sliding, now it’s happening only in that pause duration.

Problem solved 😀! Let’s audit the performance timeline.

Chrome Performance Timline — Still long frames

Wait what?? 😵 Even after applying debounce, still there are those nasty red frames causing jank! And the cause seems to be lot of scripting as evident from those canary sand dunes.

Meh! What’s causing so much scripting now? Let’s dig more deep into these dunes to figure out the cause.

Flame Chart of a single scripting frame

Eureka! It’s the Angular 2 Change Detection. But why it’s being called on every Scroll Event. We aren’t making any change in any local variable until there’s a pause. Are we?

To reason about this, we need to understand how and when change detection triggers and trickles down the Change detection process in the whole Angular’s component tree. Here’s a beautiful explanation on this.

As it says,

Zones monkey-patches global asynchronous operations such as setTimeout() and addEventListener(), which is why Angular can easily find out, when to update the DOM.

Did you get the clue now? Actually, when we did this:

Observable.fromEvent(this.el.nativeElement, 'scroll')

It internally added a scroll event listener and somehow zone js had been triggering the change detection cycle on every scroll event.

So, we found the culprit. Now, what’s the solution? Stick around for a little longer.

NgZone service of Angular which is a wrapper around ZoneJS provides us with a method runOutsideAngular.

It allows you to escape Angular’s zone and do work that doesn’t trigger Angular change-detection or is subject to Angular’s error handling.

So, after wrapping up my handler inside the NgZone runOutsideAngular, my perf timeline looks something like this

Chrome Performance Timeline: After runOutsideAngular

Aloha Greenery! 🍃😍 Finally, now we can kiss those nasty red frames a goodbye. You know what it means, a blazing fast silky smooth scrolling experience.

Spruce up the “Dirt”

In our ts or js files we end up subscribing or observing to Observables or Event Listeners but often forget to unsubscribe ’em from there which not only gifts us with unexpected side effects called bugs but also eats up a good share in performance specially if that component is being repeated in loop, you’re screwed.

Hence, as I said with great freedom comes great responsibility, you must always clean up the subscriptions, timers, event listeners in the destructor which happens to be the ngOnDestroy lifecycle hook of an Angular 2 app component.

Fend off too much piping

Avoid making recurring use of async pipe in your Angular Components. What I mean by that is avoid something like doing this

As you can see from the above example (data | async) is being used so rampantly. Now, mind this thing that as many async pipes you’re gonna write, there’ll be that number of observable subscriptions too which means more number of handlers to process anytime a single change happens. So, avoid doing such things. If you find yourself in such a scenario, you can and should create a child component; a dumb or stateless component if I say in Redux jargon, and pass around the data as an immutable input property after async piping it in there.

Conclusion

There are a lot of other reasons why your web app could be sluggish. This article just lists the causes and their solutions if you app is built using Angular 2 or above. In some cases the load time of the app could be high due to a longer CRP (Critical Rendering Path) or it could be poor programming practices like ignoring the above mentioned nook and corners. So, now you can go ahead and at least optimise the latter part causing your app to impede the user experience or maybe dwindling number of users. While working on a small project, it’s fine to overlook these things but for larger projects I highly recommend employing these practices out for a nimble user experience.

Thank You! Happy Coding! 😎

--

--

Param Singh
paramsingh_66174

Senior Front-end Engineer at AWS, Ex-Revolut, Flipkart. JS enthusiast. Cynophile. Environmentalist