4 Runtime Performance Optimizations

In Web Development


4 Runtime Performance Optimizations - read the full article about Angular Js update, Web Development and from Angular on Qualified.One
alt
Angular
Youtube Blogger
alt

MINKO GECHEV: Hello, everyone.

My name is Minko Gechev.

Im working on Angular at Google.

Over the years, I have profiled hundreds of Angular applications.

And I have noticed that the majority of performance challenges fit into a couple of different patterns.

Today, were going to look into these patterns, and were going to learn how to resolve them.

In this video, first, youll learn how to use Angular DevTools.

After that, well identify different performance-regression patterns and learn how to resolve them.

For this purpose, well use a simple prototype of a typical business application.

At the very top of the UI, we have a bar chart.

Under the chart, we have two lists of employees from organizational Sales and R&D departments.

Each employee has a name and a numeric value associated with them that goes through a heavy computation.

In each of these lists, we can ask new employees.

As youll see in the code, there is the foundation for implementing the lead functionality, as well.

But for simplicity, it is not part of the UI.

Remember I mentioned that the numeric value for each employee goes through a heavy computation? I have mocked this computation with the Fibonacci function to keep things simple.

Notice that weve implemented a pretty inefficient version of Fibonacci so that even minor performance problems can have significant visible impacts in the examples well explore.

Implement the entire application with just two components-- EmployeeListComponent, which contains the list of employees for the corresponding department and has a text input for entering new employees, and the AppComponent, which renders two list instances of the EmployeeListComponent and a bar chart at the top.

Before we jump to the patterns, let us look at how we can profile an application by using Angular DevTools.

Angular DevTools is a Chrome DevTools extension that you can install from the Chrome Web Store.

It allows you to preview the structure of your application in the Components explorer.

For this video, well be primarily using the Profiler.

To start profiling an application, click on the Record button.

As Angular performs change detection in your app, youll see bars corresponding to the individual change detection cycles appearing in DevToolss timeline.

When we select the frame from the timeline, we can preview how much time we spend on the individual components.

Here, we spend the majority of the change-detection invocation within the MatFormField and EmployeeListComponent.

DevTools allows us to preview the Profiler output in different formats-- bar charts, tree maps, and flame graphs.

The flame graph provides a hierarchical view of the component tree.

When we click on a particular component, we can see how much time Angular spent on it.

For example, we spent about 0.1 milliseconds on detecting changes within the EmployeeListComponent.

Since our application doesnt have a complicated nested structure, well use the default bar chart view.

Now let us look at individual patterns.

Well describe the cause of the problem.

Youll learn how to identify it and resolve it.

The first pattern were going to look into is zone pollution.

Let us go back to the application and start recording with Angular DevToolss Profiler.

If we start interacting with the bar chart in the application, well see that we trigger multiple change detection cycles, each of which takes a decent amount of time.

If we explore the change detection cycles, well see that the source of the change detection is mouseup and mousemove events.

Each cycle takes more than 740 milliseconds, which significantly drops the browsers frame rate.

We spent most of the time in the two instances of the EmployeeListComponent, where each check takes more than 360 milliseconds.

Now let us resolve this problem.

The beginning of app.components template is div container where we render the bar chart.

We initialize the chart in the Ng on the [INAUDIBLE] lifecycle hook in the app.component by invoking the new plot method of the Plotly charting library.

We pass the ID of the DOM container and the data we want to render.

Given the mouseup and mousemove events that we got in the Profiler, this means that probably the initialization logic of Plotly is adding these EventListeners to the bars.

Plotly offers a standalone library that doesnt need to interact with Angular.

We can run the initialization logic outside of the Angular zone to prevent the invocation of redundant change detection cycles.

Let us go to the constructor of app.component and inject the Ng zone.

We can go back to the charts initialization logic and the rapid inside of a callback that we pass to runOutsideangular.

When we go back to the application and start the Profiler, well see that interactions with the bars in the bar chart dont trigger change detections anymore.

The zone-pollution pattern occurs when the Angular zone wraps callbacks that trigger redundant change detection cycles.

Polluting the zone happens when we run an initialization logic that uses requestAnimationFrame, setTimeout, or addEventListener.

We can identify the problem by looking for unexpected change detection cycles in the Profiler output.

In most cases, I have found that the reason is requestAnimationFrame.

The solution is usually pretty straightforward.

All you need to do is move the initialization logic outside of the Angular zone.

The following pattern well look into is the out of bounds change detection.

Let us go back to the application and enter a new employee.

Notice that the experience is pretty laggy.

When we start profiling, we notice two change detection cycles triggered on each character we enter.

The first one is on the Input event, and the second one is on keydown.

For both events, we spent more than 380 milliseconds detecting changes in the two instances of the EmployeeListComponent.

Notice that even though we are typing only in the input for the Sales department, we also check the R&D department.

Since typing in these input changes only the View state within the Sales department, detecting changes in the R&D department is redundant.

Let us fix this.

For this purpose, well update the change-detection strategy of the EmployeeListComponents to OnPush.

With OnPush, Angular will trigger change detection within the component when we pass input with a new value based on an [INAUDIBLE] check.

Well use an immutable list from immutable.js to prevent mutation of the array references and also, to ensure efficient structure of sharing of data.

Let us first change the Sales and R&D department arrays to immutable lists.

After that, well update the signatures of the Add and Remove methods in the app.component so that they can accept immutable lists of employees.

Next, we need to make sure we assign the results produced by these two methods to update the local references and pass them down the component tree to EmployeeListComponent.

We need the assignment because we are no longer mutating the lists.

Instead, Immutabe.js creates new ones.

Since were now passing immutable lists to the EmployeeListComponent, we need to update the type of its data input.

Immutable lists have size rather than a length property, so we need to update the property access in the template and set the changeDetection strategy to OnPush.

Now, let us get back to the application.

Notice that entering a new employee now is a little faster, even though it still looks pretty laggy.

Let us fix this.

Well do this as part of the refactoring of out of bounds change detection.

When we start typing into the input, Angular performs change detection pretty regularly.

It checks the whole EmployeeListComponent and evaluates each employees heavy computation twice-- on input and on keydown events, even though none of the values have changed.

This happens because typing into the input triggers events that bypass the OnPush change-detection strategy.

And when an event within a component with OnPush change-detection strategy occurs, Angular will detect this component for changes, even if it hasnt received new inputs.

The problem here is that were only changing the local state of the input but not updating the individual employees, which means that it is safe to completely skip change direction for them.

To improve the performance here, well refactor the component tree.

Currently, the app component renders two instances of the EmployeeListComponent.

At the end of this section, the EmployeeListComponent will use the NameInputComponents to get new employees names and ListComponent to render the list of employees.

Well use OnPush change-detection strategy for the list component.

So this way, events happening in the sibling component, the NameInputComponent, will not trigger any redundant revaluations for employees.

Let us first go to the directory of the EmployeeListComponent and create the name-input.component and the list.component.

As the next step, we can extract the input field from the employee-list.component to the template of the name-input.component.

We can also move the corresponding styles, the label property, and the handleKey method and copy the out output.

Let us also remove the OnInit lifecycle hook.

We can use the name-input.component within the employee-list.components template, handling the apps output as the next step.

Now let us move the rest of the mat-list part of the template to the ListComponent.

We should also carry the calculate method and remove the OnInit lifecycle hook implementation because were simply not using it.

Next, let us move the Fibonacci function.

We can now move the data input.

Finally, we can copy the remove output.

To ensure the UI looks crisp, we can move the styles associated with the list visualization to the styles of the list component.

Finally, we can set the change-detection strategy of the list.components to OnPush.

Let us use the list.component in the employee-list.components template, passing the corresponding input and handling the remove output.

When you go back to the application, notice that the typing experience is without any noticeable lag.

To recap, this performance problem occurs when an action that only impacts the local state of a particular component triggers change detection in unrelated parts of the componentry.

We can identify the problem by inspecting the Profilers output and finding the components that are not supposed to be affected by a particular interaction.

To resolve the issue, we can isolate the component which triggers frequent local state changes and sets the components with expensive change detection checks to use OnPush.

The third pattern were going to look at is the recalculation of referentially transparent expressions.

If we have an expression in a template that could be replaced with its value when its parameters dont change, we call it referentially transparent.

This means that we dont have to recalculate the expressions between change-detection cycles unless their input change.

Let us go back to the application and add an employee.

Notice that we got a pretty expensive change-station cycle triggered by the keydown event.

We dropped the browsers frame rate and Angular spent most of its change detection cycles within the list component.

When we add a new employee to the Sales list, we invoke the add methods of the app component, which creates an immutable list.

The immutable list passes the new immutable list to the ListComponent, triggering its change detection.

Angular goes through the individual employees and recalculates their numeric values, even though they didnt change.

Ideally, you would want to calculate the value only for a new employee.

We can improve the applications performance here with a little bit of refactoring.

We can create a new pipe called calculate.

Let us import the pipe decorator and sets the pipe name to "calculate." Here, we explicitly said the pipe to be pure for readability, even though thats the propertys default value.

As a next step, we can implement the CalculatePipe class, adding a transform method and implementing the PipeTransform interface.

Now let us move the Fibonacci implementation and remove the redundant calculate method from the ListComponent.

Let us add the CalculatePipe to the declarations in the employee-list.module.

Finally, we need to update the template of the ListComponent to use the pipe rather than a method call.

Notice that now we can add an employee without any visible delays.

To recap, this problem occurs when Angular recalculates the same template expression repeatedly, even though they depend only on parameters that value does not change.

We can identify the problem with Angular DevTools by noticing change detection cycles that dont take time proportional to the changes we made in the UI.

The solution usually involves either moving a calculation to a pure pipe or caching it via memorization.

The final pattern were going to look at is large component trees.

Even if we have optimized each component and dont perform redundant calculations, we can still observe frame drops if we have thousands of component instances.

To show how we can improve the performance in this scenario, I added a few hundred of employees to each list.

When we add a new employee, we notice that detecting the ListComponent for changes takes over 25 milliseconds, which causes frame drops.

Improving the performance in large component trees involves making the component tree smaller.

Standard techniques are on-demand rendering, such as virtualization or pagination.

For our purposes, were going to use the Angular cdks virtual scrolling component.

In the template of the ListComponent, we will use the cdk_virtual_scroll_viewport with item size equal to 50.

And were also going to add the CSS class "viewport" so that we can style this component.

Instead of using ngFor in the mat-list item, well use the cdkVirtualFor.

Well also set the height of the container to 800 pixels.

And thats all.

Now when we add a new item, the change section in the ListComponent takes 1/5 of the time it used to take before.

Keep in mind that even if you have highly optimized components and render thousands of them, their templates combined could still be very expensive during change detection.

You can identify this problem in your DevTools if you find many components that just take a fraction of the overall change detection cycle or when there is one component with a really large view that takes a lot of time to be checked.

The solution involves on-demand rendering of components to trim the component tree.

Thats pretty much everything I had for you today.

We first went through the fundamentals of using Angular DevTools, focusing on the profiler.

After that, we explored four patterns that can help you identify and resolve common performance issues-- zone pollution, out of bounds, change detection, recalculation of referentially transparent expressions, and large component trees.

Thank you very much for watching this video.

See you next, time and happy coding.

Angular: 4 Runtime Performance Optimizations - Web Development