Omar Olivo
Omar Olivo

Reputation: 45

How can we leverage angular change detection to improve performance?

I'm creating my first open source project, a school gradebook. I'm new to angular. Question: How can I improve the runtime performance of the gradebook application? The form controls are generated dynamically based on students and assignments. The rendering is taking more time than desired.

I tried to detach the change detector but still taking too much time when change detection is triggered.

When I look into chrome dev tools, this is where the majority of the time is spend doing change detection.

  private createFormControls( ) {

    const gradeGroup = this.gradeControls;
    this.clearAllGradeControls(gradeGroup);

      for (let i = 0; i < this.gradebook.students.length; i++) {
        const student = this.gradebook.students[i];
        const studentGrades = this.gradebook.grades.filter( g => g.userId == student.userId);

        for (let j = 0; j < this.gradebook.assignments.length; j++) {
          const assignment = this.gradebook.assignments[j];
          const key = this.createKey(student.userId, assignment.id);
          const grade = studentGrades.find( g => g.assignmentId == assignment.id );
          let fg = new FormGroup({
              grade: new FormControl(grade ?  grade.grade : ''),
              userId: new FormControl(student.userId),
              assignmentId: new FormControl(assignment.id),
           });
            gradeGroup.addControl(key,  fg);
        }      
      }   


  }

Stackblitz https://stackblitz.com/github/omarolivo/Gradebook

Github: https://github.com/omarolivo/Gradebook

How can I dynamically generate rows and columns of form controls with better performance?

UPDATE

After looking into whats causing the main problem in the overall performance, change detection is for the most part slowing down the component. I tried to convert the array of data into RxJS observable. I'm still struggling with change detection.

Any suggestions on the "correct" pattern/architecture to efficiently use change Detection?

Upvotes: 0

Views: 844

Answers (1)

Darren Ruane
Darren Ruane

Reputation: 2485

I was surprised to think that ChangeDetection was causing this issue for you, until I realised it wasn't. What you're seeing there is a false positive. The real bottleneck is in the assignment/rendering of the heavy FormGroup to the <form> element (which just so happens to happen when change detection runs and the value is updated).

I was going to suggest using Angular's Resolve Guards to 'bypass' all of the change detection that occurs when modifying the FormGroup but that won't make much of a difference (I tested it and then realised the real problem was elsewhere). I would actually suggest still doing this, not for performance reasons, but architectural ones. I'd move all the logic to a service, and then have the guard inject that service and use it to invoke a resolve call.

The real issue is in the number of FormGroups your form contains. You're looking at 25 people by 39 columns = 975 FormGroups. Each of those FormGroups then contain 3 FormControls, totalling to almost 3 thousand. That is enormously large and I do not believe that there are any out-of-the-box solutions for this.

What I would suggest doing is to try to minimise the number of visible fields at any given time (as nobody wants to see 975 fields up-front anyway). You could split out your *ngFor elements into smaller, divided *ngFor elements and then wrap some of them in some *ngIf logic so they aren't always rendered (perhaps the *ngIf will evaluate to true when the current scroll position is near the top of the element for example).

Another thing you could do is look into more sophisticated lazy loading of *ngFor elements. I found an interesting article where the writer runs you through creating a 'lazy' *ngFor directive. I can't vouch for the quality (or the feasability) of this, but it is definitely worth a try.

As always, OnPush change detection is pretty much a must and that will help performance a bit in this case.

If I think of anything else I'll come back and edit this post.

Good luck!

EDIT:

The Angular CDK has support for Virtual Scrolling built into it. You can create a scrollable container using the <cdk-virtual-scroll-viewport> tag and then replacing your *ngFor directives with *cdkVirtualFor. The rest of the API is the same.

To briefly quote the docs:

The <cdk-virtual-scroll-viewport> displays large lists of elements performantly by only rendering the items that fit on-screen. Loading hundreds of elements can be slow in any browser; virtual scrolling enables a performant way to simulate all items being rendered by making the height of the container element the same as the height of total number of elements to be rendered, and then only rendering the items in view.

Upvotes: 2

Related Questions