Video details

Angular Performance Patterns | Minko Gechev | EnterpriseNG 2021

Angular
07.24.2022
English

Angular DevTools is a Google Chrome extension that provides debugging and profiling capabilities for Angular applications. In this session, I will show you how you can leverage this tool to determine what is slowing down your applications and how to quickly resolve the bug and deploy a faster app.
ng-conf is a three-day Angular conference focused on delivering the highest quality training in the Angular JavaScript framework. 1500+ developers from across the globe converge on Salt Lake City, UT every year to attend talks and workshops by the Angular team and community experts.
Join the Angular Community: http://www.ng-conf.org/ Get your ng-conf tickets: https://ti.to/ng-conf Follow Us: https://twitter.com/ngconf Learn More: https://ng-conf-school.teachable
Read More: https://medium.com/ngconf
Hear More: http://theangularshow.com
Official Website: https://www.ng-conf.org/

Transcript

Hey there. I'm Joe Williams. Before we get into the video, I want to remind you about Ng Cough happening on August 31 in person. So head over to Ngcoff.org to check out the speakers, check out the talks, and get your ticket. It's time to get it back together. Hello everyone. My name is Mikugaetchev. I'm 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 we are going to look into these patterns and we're going to learn how to resolve them in this video. First, you'll learn how to use Angular dev tools. After that, we'll identify different performance regression patterns and learn how to resolve them. For this purpose, we 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 RNA 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 add new employees. As you see in the code, there is the foundation for implementing Delete 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 heavy computation. I have mocked this competition with the Fibonacci function. To keep things simple, notice that we've implemented Pretty inefficient version of Fibonacci so that even minor performance problems can have a significant visible impact. In the examples we explore, I recommend the entire application with just two components employee List component, which contains the list of employees for the corresponding department and has a text input for entering new employees and the App component, which renders two lists instances of the Employee List component 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 Component Explorer. For this video, we'll be primarily using the profiler to start profiling an application. Click on the Record button as Anger performs. Change detection. In your app, you'll see bars corresponding to the individual change detection cycles appearing in DevTools timeline. When we select a 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 Mat form field and Employee List component. DevTools allows us to preview the profile or output in different formats bar chart, 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 employee list component. Since our application doesn't have a complicated nested structure, we will use the default bar chart view. Now let us look at the individual patterns. We'll describe the cause of the problem. You'll learn how to identify it and resolve it. The first pattern we're going to look into is Zone Pollution. Let's go back to the application and start recording with Angular devtos profiler. If we start interacting with the bar chart in the application, we'll see that we trigger multiple change detection cycles, each of which takes a decent amount of time. If we explore the change detection cycles, we'll see that the source of a change detection is mouse up and mouse move events. Each cycle takes more than 740 milliseconds, which significantly drops the browser's frame rate. We spend most of the time in the two instances of the employee list component where each check takes more than 360 milliseconds. Now let's resolve this problem. The beginning of App components template is the div container where we render the bar chart. We initialize the chart in the Ng on initial lifecycle hook in the app component by invoking the new Plot method of the Plotly charging library. We pass the ID of the Dom container and the data we want to render. Given the mouse up and mouse move event that we got in the profiler. This means that probably the initialization logic of Plotly is adding these event listeners to the bars. Plotle offers a standalone library that doesn't need to interact with angular. We can run the utilization 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 engine zone we can go back to the charge in utilization logic and wrap it inside of a callback that we pass to run outside angular. When we go back to the application and start the profiler, we'll see that interactions with the bars in the bar chart don't trigger change detections anymore. The zone pollution pattern occurs when the angular zone wraps. Callbacks that trigger redundant changed action cycles polluting the zone happens when we run initialization logic that uses Request animation frame, set time out or Add event listener. We can identify the problem by looking for unexpected change detection cycles in the profile or output. In most cases, I have found that the reason is Request Animation Frame. The solution is usually pretty straightforward. All you need to do is move the initialisation logic outside of the angular zone. The following pattern we'll look into is the out of bounds change detection. Let's 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 spend more than 380 milliseconds detecting changes in the two instances of the employee list component. Notice that even though we are typing only in the input for the Sales department, we also check the RND department. Since typing in this input changes only, the view state within the Sales department detecting changes in the RNC department is redundant. Let us fix this. For this purpose, we'll update the change detection strategy of the employee list components to on push. With on push, Angular will trigger change detection within the component. When we pass input with a new value based on an equility check, we'll use an Immutable list from Invertebrojs to prevent mutation of the array references and also to ensure efficient structure of sharing of data. Let us first change the Sales and RN department race to Immutable lists. After that, we'll 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 employeelist component. We need the assignment because we are no longer mutating the lists. Instead, immutable JS creates new ones. Since we're now passing Immutable List to the employee list component, 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 change in action strategy to on push. Now, let's 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 stick this. We'll do this as part of the refactoring of outofbounds change detection when we start typing into the input, anger performs change detection pretty regularly. It checks the whole employee list component and reevaluates each employee's heavy computation twice on input and on key down events, even though none of the values have changed. This happens because typing into the input triggers events that bypass the on push change detection strategy. When an event within a component with on push change detection strategy occurs, angular will detect those component for changes even if it hasn't received new inputs. The problem here is that we are only changing the local state of the input but not updating the individual employees, which means that it is safe to completely skip change detection for them. To improve the performance, here we'll refactor the component tree. Currently, the app component renders two instances of the employee list component. At the end of the section, the employee list component will use the name input components to get new employees names and list component to render the list of employees, we will use on push change detection strategy for the list component. So this way, events happening in the sibling component the name input component will not trigger any redundant reevaluations for employees. Let us first go to the directory of the employee List component 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 handle key method, and copy the app output. Let us also remove the oninit lifecycle hook. We can use the name input component within the employee List Components template, handling the app output as a next step. Now let us move the rest of the Mat List part of the template to the List component. We should also carry the Calculate method and remove the on init lifecycle hook implementation because we're simply not using it. Next, let us move the Fuminatze 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 on push. Let us use the List component in the employee List Components template, passing the corresponding input and handling the remove output. When we go back to the application, notice that the typing experience is without any noticeable lack 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 component tree. We can identify the problem by inspecting the profiler's 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 set the components with expensive change detection checks to use On Push the third pattern we're 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 don't change, we call it referentially transparent. This means that we don't have to recalculate the expressions between change infection cycles unless there are input change. Let us go back to the application and add a new employee. Notice that we got a pretty expensive translation cycle triggered by the key down event. We dropped the browser's frame rate and Anger spent most of its change detection cycles within the List component. When we add a new employee to the Sales List, we invoke the app method of the app component, which creates an Immutable List. The Immutable List passes the new Immutable List to the List component, triggering its change detection and your goals through the individual employees and recalculates their numeric values even though they didn't change. Ideally, we would want to calculate the value only for a new employee. We can improve the application's performance here. With a little bit of refactoring, we can create a new pipe called Calculate. Let us import the pipe decorator and set the pipe name to Calculate. Here we explicitly set the pipe to be pure for readability, even though that's the properties default value. As a next step, we can implement the Calculate pipe class, adding a transform method and implementing the pipe transform interface. Now let us move the Fibonacci implementation and remove the redundant Calculate method from the List component. Let us add the Calculate pipe to the declarations in the employee list module. Finally, we need to update the template of the list components to use the pipe, rather than a method called 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 any ordeal tools by noticing change detection cycles that don't 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 we're going to look at is large component trees. Even if we have optimized each component and don't 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 the scenario, I added a few hundreds of employees to each list. When we add a new employee, we notice that detecting the List component for changes takes over 25 milliseconds, which causes raindrops. Improving the performance in large component trees involves making the component trees smaller. Standard techniques are on demand rendering, such as virtualization or Pagination. For our purposes, we're going to use the Angular CDKs virtual scrolling component. In the template of the List component. We will use the specific virtual scroll viewport with item size equal to 50. And we're also going to add the CSS class viewport so that we can style this component. Instead of using Ng four in the Mat List item, we'll use the CDK virtual four. We will also set the height of the container to 800 pixels. And that's all. Now, when we add a new item, the change detection in the List component takes one fifth 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 template combined could still be very expensive during change detection. You can identify this problem in Android dev tools. If you find many components that just take a fraction of the overall change detection cycle, or when there is one component with 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. That's pretty much everything I have for you today. We first went through the fundamentals of using Angular dev tools, 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 cutting. Hi, I'm Joe Williams. Thanks for checking out this video from Enterprise Ng 2021. Online conferences were great, but it's time to get back in person, see your old friends, make some new ones, and take your career to the next level. Head over to Ngcoff.org to get your ticket. See you there.