It can be tempting today to watch a bunch of ng-conf talks and then go straight ahead and implement every tip you find. The problem is that it might not actually improve your Angular apps considerably because you are going the wrong way around the problem. If you start with the solution and try to apply it before you have identified the problem you are most likely going nowhere. This post is about how to improving performance in Angular apps by using a systematic approach to fix the most common Angular performance problems like a doctor!

Note this is a follow up on my performance optimization blog post. Read it first, if you haven’t already.

The Three Steps of Curing Bad Performance in Angular Apps

From doing training and consulting for Angular teams I have conducted three systematic steps I like to use when optimizing the Angular apps. They are as following:

Identify the biggest problems in regards to performance tuning in the app and what modules they are located to Fix the biggest problems Prevention by finding a way to prevent these problems from reoccurring

Let’s look at these steps.

Identify

The first steps should always be identification, exactly like when you go to the doctor. This is necessary to gain awareness of what the biggest problems are, where they are located in the application and propose solutions to fix them. It can be tempted to mark everything that is not aligning with best practices as “problems”. The biggest problems regarding performance optimizations are the ones that impact your customers/users most negatively. That means it is not the ones that are most against “best practices”, dirty code, not functional enough, store mutations, etc… These things can help your app design when applied in the right situation, but they are most likely not the things that are impacting your users the most.

Identify the most important use cases

Before you can determine what the biggest performance problems are you need to weight them up against the use case they are impacting. Going back to the doctor metaphor; a doctor has a good prioritization of what parts of the body are most important and will weight your symptoms up against those. He probably would not be indifferent if you have pain from your pinky toe versus from your heart.

In the same way, you must identify your “critical organs” of your application. Because just small problems with these should weight higher.

How do you identify the most important use cases? I like to just focus on finding the top 5 use cases, to begin with, as this forces you to be really focused on only the most important. You might say, that all the use cases are important, but if only your users could do five of them what should it be?

For example, for an email client application the most important use cases would probably be:

Send email Show inbox Read email Reply to email Set autoresponder

Now, this list might vary depending on who you ask but the point is that you are prioritizing the use cases after importance. The product owner of the team should be able to conduct a list of what is most important, after all, it is their job.

Identify the most severe performance problems

Now you want to identify the biggest performance problems in the application. This could be a slow page load, laggy user experience, unresponsiveness, etc. Conduct a list of these problems.

To identify the cause of the problem you can use the performance tab in Chrome dev tools to diagnose performance problems regarding the rendering and calculation performance in your application. Otherwise, slow network requests can be identified with the network tab in dev tools.

Prioritizing the performance problems

Now we have the use cases and performance problems prioritized separately in a relative manner, we could even go full analytical with this and plot it in a table which will calculate the total problem criticality score taking the use case and severity of the problem into consideration.

You first plot the performance problem and assign it to a score depending on how bad it is.

For the use cases, you should assign a number for each of your top 5 use cases, where the most important get 10 points and the others are getting a score relative to this. Together with the performance problem score, this gives a measure of the criticality.

Performance problem When sending a mail it hangs for a long time Loading an email takes a long time Problem severity score (from 1-10) 7 8 Related to use case Send email Read email Use case criticality score (from 1-10) 10 9 Total problem criticality score (use case x problem score) 7*10= 70 8*9= 72

Here we can see, that the biggest problems at hand are the long loading time for reading an email when plotting it up like this.

For some, this might be overkill, but use this table if you need a tool for quantifying the importance of different problems or convince a decision maker.

Now we have identified the problems let’s look into fixing it.

Fix

Now, with the awareness of the most important performance problems, it is time to fix them using the below steps.

Start with cleaning up before operating

The first step before you start doing the actual performance optimization is to ensure your code is designed cleanly. That is equivalent to how a doctor is cleaning up before surgery to avoid unwanted side effects of the procedure. For more about how to keep your Angular apps clean, check out this post.

Have a list of proposed solutions considered before you start fixing

From your prioritized list of performance problems, you can now add an extra column with the proposed solutions for each one. A good approach could be to consider the cheap and more expensive solution for each. I recommend discussing this solution in your front end team as part of your front end chapter meetings.

Weight the result of the solution against the required work

Now again, the right solution is a cost/benefit. Sometimes it can be smart to start with the cheap solution and upgrade later if, but more factors might play in. At the end of the day, the front end team will find consensus on what to do and take the performance optimization that fits the situation the best.

Prevention

So you just lost 20 pounds? How about celebrating it with going to McDonald’s for one month straight?

Now after these steps you have identified the performance problems and prescribed the needed solutions. This will fix the problems you have at hand but will not make sure they don’t reoccur.

The best way to assuring that you are following the right practices is through automation. One of my mottos I will say again and again is:

If it can’t be automated, don’t bother Christian Lüdemann

I might soon start to print some t-shirts you can wear so you don’t forget 😉

Automating prevention of performance problems

Here is a list of tools to use for automating the prevention of performance problems:

TSLint: Creating lint rules eg. for remember to use OnPush, break down big components, etc

Meta reducers: For ensuring you design your app for immutability the NgRx store freeze meta reducer can ensure you never mutate the store

Schematics: You can set schematics for automatically add OnPush change detection to new components that are generated with Angular CLI

Log HTTP request times and alert the team if requests are getting to slow. This will also give you a list of the slowest requests in the system and how many users they are affecting

Set budgets for bundle sizes, to ensure you are not getting too big bundles

Example of using these steps to solve common Angular performance problems

Alright, enough talk, let’s get down to business!

The is my opinionated list of the most common performance problems in Angular apps and know we know the process to fix them.

Too much unnecessary retriggering of methods making the app slow Slow HTTP requests Slow page load Unnecessary recomputation of CPU bound computations Too much unnecessary and heavy bootstrap logic

How to fix the most common performance problems

Note that I have already done a lot of this investigation exercises and weighted the best solutions to come up with these solutions:

1) Too much unnecessary change detection triggering making the app slow

To fix the problems with too much unnecessary triggering of methods, the steps are the following:

Add onPush change detection to components (make sure inputs are immutable) Use pure pipes or async pipe (subscribe to observables) instead of using template methods Use trackBy in ngFor for dynamic lists Detach change detection for extreme cases, where you want to be very specific about when to run change detection (using detectChanges )

Start with number 1 and go down the list until the change detection problems are fixed.

2) Slow HTTP requests

Slow HTTP requests are a common problem. Even though this is related to the back end, which should keep their endpoints performant, we can still do some things on the front end:

Use Angular PWA to cache HTTP requests using either a performance or freshness caching strategy. If it changes rarely you might use performance and if it changes often, use freshness caching strategy. Use shareReplay(1) on the HttpCient ‘s observable response to avoid unnecessary repeating HTTP calls when you subscribe. Use a service facade/aggregator like GraphQL to fix under/overfetch problems. This can especially be a benefit for companies with a heavy firewall to call through (people working in banks will know what I am talking about), as you only need to pass the firewall once to get all the data you need.

3) Slow page load

Slow page load can be a big problem as users today are not very tolerant to wait. Especially if the site has a conversion purpose, just a few milliseconds increases in page load times are proven to cause lost conversion = lost $$$. My recommended approach to fix this is:

Optimize the hosting using: GZip compression and use a fast CDN. If you are not currently doing this, this is an easy approach that doesn’t require coding to see a drastic improvement Use PWA static content caching: Caching the static content (js, CSS, images, etc.) is an easy and effective way to boost performance in modern browsers Optimize the bundle sizes: Optimize the bundle sizes by lazy loading everything that doesn’t need to be shown on the first request. Minko Gechev has created a couple of resources for this with his predictive fetching and a quick links preloading strategy if you really want to optimize this to the extreme Angular universal: Angular universal can be used to prerender the Angular pages given both a performance boost and SEO benefits. This will require the Angular app is rendered from eg. a NodeJS server

4) Unnecessary recomputation of CPU bound computations

This might sound related to the first problem with too much change detection triggering but it is actually expanding upon these solutions by adding caching to avoid recomputing the heavy methods

Use caching on expensive pure methods using eg. Lodash’s Memoize function. This will make sure to remember output for a given input and won’t run the method again if it already has a result. To utilize this the methods needs to be pure (side effect free) as a pure method always get the same output given a specific input Run more logic on the back end: Doing this will give two benefits. First, it will centralize the logic in one place, that can be used by multiple clients. Second, it might run faster than in a JS client because it can run in a binary execution which might support parallelization and more efficient execution runtime and hardware.

5) Too much unnecessary and heavy bootstrap logic

Again, we want to optimize for a good first impression so the users don’t bounce. Once we make sure the app paints as fast as possible, we also want to ensure we are handling the bootstrap logic in a smart way. This is my recommended approaches to optimize this:

Make sure to only run the needed logic for the given pages. This might sound obvious but it is not uncommon to see a lot of logic run on the first load, that are not related to the current page. The problem might be that you need to move more logic to the lazy modules as they are not needed on every page load. Normalize the Redux store and use selectors to hydrate/denormalize the needed view data. It is often seen, that on app load the app fires a lot of HTTP requests, that needs to be reduced to a denormalize nested data structure, so it is ready to be read. This both adds a lot of reducer logic as well as slowing down the initial load and is making the store design unflexible. A better approach is to normalize the store, that is, using id’s to reference other parts of the store and use the selectors to hydrate the needed view data when it is needed. Use an entity map or NgRx entity to do performant lookups when referencing entities in the store. This will make sure to only do the data denormalization when the data is actually needed as well as making the design more simple.

Conclusion

Wow, what a mouthful!

We went in this post through the processes of fixing performance problems in Angular apps by approaching it like a doctor with a three-step process: investigate, prescribe and prevent.

We also looked at my top 5 performance problems in Angular apps and how to fix them:

Too much unnecessary change detection triggering making the app slow Slow HTTP requests Slow page load Unnecessary recomputation of CPU bound computations Too much unnecessary and heavy bootstrap logic

Remember to always start with the problem in mind, do the investigation and then apply the right tool for the job.

Enjoy your faster Angular apps!

Do you want to become an Angular architect? Check out Angular Architect Accelerator.

Hi there! I’m Christian, a freelance software developer helping people with Angular development. If you like my posts, make sure to follow me on Twitter.

Like this: Like Loading...