When will the effect function for angular signals execute?

1.7k Views Asked by At

Why do these two functions #workingUpdate and #brokenUpdate behave differently? I actually would expect both functions to fail since the ElementRef does not exist while the constructor is called.

@Component({
  selector: 'test-app',
  standalone: true,
  imports: [CommonModule, ReactiveFormsModule],
  template: `
  <button (click)="addOne()">count+</button><br/>
  count: {{count()}}<br/>
  output1: <span #output1></span><br/>
  output2: <span #output2></span><br/>
  `,
})
export class App {
  count = signal(0);

  @ViewChild('output1') output1!: ElementRef;
  @ViewChild('output2') output2!: ElementRef;

  #workingUpdate = () => {
    const c = this.count();
    this.output1.nativeElement.innerHTML = c;
  };
  #brokenUpdate = () => {
    this.output2.nativeElement.innerHTML = this.count();
  };

  constructor() {
    effect(this.#workingUpdate);
    effect(this.#brokenUpdate);
  }

  addOne() {
    this.count.update((c) => c + 1);
  }
}

I created a simple Example. I originaly ran into this problem calling a update interface of a third party component. I use the #workingUpdate stucture to fix my problem, but now im not sure if i've just hidden the actual problem.

1

There are 1 best solutions below

4
On BEST ANSWER

To answer the specific question, from the docs:

An effect is an operation that runs whenever one or more signal values change.

Effects always run at least once. When an effect runs, it tracks any signal value reads. Whenever any of these signal values change, the effect runs again. Similar to computed signals, effects keep track of their dependencies dynamically, and only track signals which were read in the most recent execution.

Effects always execute asynchronously, during the change detection process.

Your example is very interesting.

It does work if I add console.log() and read the signal there.

  #workingUpdate = () => {
    console.log('working', this.count());
    const c = this.count();
    this.output1.nativeElement.innerHTML = c;
  };
  #brokenUpdate = () => {
    console.log('broken', this.count());
    this.output2.nativeElement.innerHTML = this.count();
  };

Almost looks like it isn't correctly registering the signal for the effect in the #brokenUpdate case?

UPDATE: I got an answer on this from the Angular team.

Basically, the brokenUpdate case doesn't ever register the signal since it throws an exception the first time. So it is never getting re-run when the signal changes.

The workingUpdate case registers the signal before throwing the error. So it is re-run when the signal changes.

One way to fix it is to make the element refs static so they are there when the effects run. Something like this:

  @ViewChild('output1', { static: true }) output1!: ElementRef;
  @ViewChild('output2', { static: true }) output2!: ElementRef;