12

It seems using getters inside templates causes Angular change detection to go into a loop (getter gets called hundreds of times). After reading up a ton on similar issues out there I cannot seem to get a clear answer.

Background info:

I do believe using getters inside the template is the cleanest approach from a maintainability point of view, however seemingly because Angular cannot know if the getter value changed until it calls it, it just calls it all the time. I so far found three alternatives:

  1. Stop using getters, make all properties public and access them directly
  2. Store the value of each getter into a public property on the component and bind that instead of the getter in the template
  3. Change the changeDetection mode of Angular from default to onPush

Option 1 would seem counterintuitive to the benefit of using Typescript classes. Option 2 would seem like unnecessary code duplication and reduce maintainability, option 3 would require a significant refactoring.

Here is an example (simplified for illustrative purpose)

Model:

export class UserModel { 
  private id: string;

get getId() {
    console.log('Getting id');
    return this.id; 
}
set setId(id) {
   this.id = id;
}
constructor() {
}
}

Component.html

<h1>Test</h1>
<p>{{user.getId}}</p>

Component.ts

  import {Component, OnInit} from '@angular/core';
    import {TestModel} from './test.model';
    
    @Component({
      selector: 'app-root',
      templateUrl: './app.component.html',
      styleUrls: ['./app.component.css']
    })
    export class AppComponent implements OnInit {
      public user: UserModel;
    
    ngOnDestroy() {
      if (this.userObserver) { this.userObserver.unsubscribe(); }
    }

    ngOnInit() {
      this.userObserver = this.userObservable.subscribe(
      (data: UserModel) => {
        this.user = data;
      },
      (err: any) => {
        console.error(err);
      });
    }
  }

Will output the following console log: console.log output

Can anyone recommend the best practice to avoid unnecessary loops while working with complex models in Angular? Or even a proper way to debug this type of behavior, as it stands now I am console.logging the getter methods and watching for memmory usage spikes.

EDIT (Answer) After more time investigating, digging through stack traces I found out the infinite change detection loop was actually being caused by a service we inject called 'Sentry'. Apparently it causes an issue when console.log is used that triggers change detection. Found a github issue about it here: github.com/getsentry/sentry-javascript/issues/1883 Did not find a solution (seems inherently incompatible), will update if I find a fix for it.

Youp Bernoulli
  • 5,303
  • 5
  • 39
  • 59
d_stack
  • 123
  • 1
  • 5
  • 4
    Whether you use getters or not doesn't change anything: Angular still needs to detect changes, and it thus still needs to evaluate expressions. Using a getter allows you to notice because you can add a logging stateent, whereas you're not noticing it when using fields, due to the absence of the logging statements. But the same amount of change detections and expression evaluations will happen whether or not you use a getter. – JB Nizet Mar 22 '19 at 14:02
  • As a side note: the getter and the setter should have the same name (the name of the property). For example: `get id() { return this._id; } set id(value) { this._id = value; }`. – ConnorsFan Mar 22 '19 at 14:09
  • 1
    @JBNizet, thanks for confirming that, got quite confusing as several stack overflow comments mentioned using methods might cause it. After more time investigating, digging through stack traces I found out the infinite change detection loop was actually being caused by a service we inject called 'Sentry'. Apparently it causes an issue when console.log is used that triggers change detection. Found a github issue about it here: https://github.com/getsentry/sentry-javascript/issues/1883 Did not find a solution (seems inherently incompatible), will update if I find a fix for it. – d_stack Mar 23 '19 at 17:25
  • @d_stack found this using `@sentry/browser@5.19.2` on Angular 8.2.7. could you update with an actual answer with what you found was the best solution to the change-detaction loop? – whallz Jul 07 '21 at 17:30

1 Answers1

1

Use ChangeDetectionStrategy.onPush

Run the following command to make this the default for your project when creating new components via the CLI.

ng config schematics.@schematics/angular.component.changeDetection OnPush

Generally speaking try to avoid complex getters or calling functions from within a template. If you need to transform data consider using Pipes, which are memoized.

glued
  • 2,579
  • 1
  • 25
  • 40