In my first post, I have already shared the memory optimization approach for angular application, now in this consecutive thread, I am going to share my real-time experience in optimizing angular application from a runtime perspective.
Let me re-iterate the major requirements which as a team we had to support during our regular development.
- The client wants every page of the application to be rendered with-in 90ms
- This web application will remain active and usable for 24x7 days.
I have already shared here about how we met the second requirement, now let me share the approach we used to meet the first requirement.
I used chrome a lot for debugging, memory profiling, and Network analysis. There are multiple charts/tabs which we can use to analyze the runtime performance of our application. Let me share some of the important charts/tabs we should look into.
In the “Performance” tab we can quickly Analyze the runtime performance of our application. Mainly we need to check the FPS (Frame per second) mark there.
Whenever you see a red bar above FPS, it means that the framerate dropped so low that it’s probably harming the user experience. In general, the higher the green bar, the higher the FPS.
Below the FPS chart, you also see the CPU chart. The colors in the CPU chart correspond to the colors in the Summary tab, placed at the bottom of the Performance panel.
To understand the CPU usage we can check the Summary and Bottom-up tab and analyze the performance pattern.
As I analyzed all these charts, I found we are doing frequent rendering in our application event many times it was not at all required. In the application, we were getting data pushed from a websocket, as a result whenever Angular founds a new event (Asyn data push, user action,etc)it starts the rendering of the page all over again.
I found that we are using the default rendering approach to render a component, see below, if you have not defined the change detection strategy explicitly, your component will be using the default change detection strategy.
As we found one of the major root causes, we immediately added onPush change detection strategy. Because of OnPush, during the re-render phase, Angular will limit its dirty checking cycle by only checking the object reference. Read more here https://blog.angular-university.io/onpush-change-detection-how-it-works/
We observed, after this change our application performance has improved by 50%. It was a real boost for us and now we wanted to explore some other item to get more juice out.
Similar to the above, we found one more interesting thing in our component templates. we were calling methods and having conditional logic written there using *ngIf.
Methods in a template will get triggered every time a component gets retendered. Even with onPush change detection. Avoid calling methods or some other form of logic in ngIf, this also helps in doing better unit testing.
for methods, we can evaluate if we can move them to pure pipes.
Pure Pipes are like pure functions, which don't have an internal state. They are cached by Angular and provide better optimization. Even though there are two usages in the template Angular will create only one pipe instance which can be shared between the usages.
With all these changes we were able to bring down the rendering number from 400ms to 120ms~. we all were very happy to achieve this milestone. There was still some gap between the client requirement and our rendering numbers, for that, we used a couple of options and successfully achieved the landmark.
● Used NgZoneOutsideAngular to save unwanted CD cycles
● Used *ngIf instead of hidden attributes in the component templates to conditionally hide any block
● Used trackBy in ngFor
● Minimized the use of heavy operations like table filter, sort, object iteration as much we can
● matMenu was eagerly loaded at the time of rendering, we made it lazy to save rendering time
● check, if you are using translation in your application, translation adds the time in overall rendering. see if you can move it to pure pipes
with all the above changes we were able to achieve the final goal.
I strongly believe if we take correct decisions at the time of architecture blueprinting we can avoid all these issues. During the start only we need to analyze the data rendering requirements, is it high, medium, or low. Accordingly, we need to use a change detection strategy. Similarly, we should also analyze how the page has been designed, if it's too busy with many components, we should ask our designers to do the refactoring.
Finally, runtime optimization is not a big challenge if you plan and decide the rules at the start. But it becomes super complex if you need to implement this in the middle. So plan early, enjoy lately!!