Angular & TypeScript guidance, patterns, and anti-patterns
NEVER synchronously update a signal you just read inside a reaction or effect.
If you need to, wrap the update in untracked() or defer it with queueMicrotask.
This avoids mutation-during-calculation bugs and infinite loops!
count = signal(0);
constructor() {
effect(() => {
if (this.count() < 5) {
this.count.set(this.count() + 1); // π¨ BAD: causes error/loop!
}
});
}
This throws or loops forever! Youβre mutating state while still inside a reaction that's reading it.
untracked() or Deferimport { untracked } from '@angular/core';
constructor() {
effect(() => {
if (this.count() < 5) {
untracked(() => this.count.set(this.count() + 1)); // SAFE
}
});
}
// βΒ OR β
constructor() {
effect(() => {
if (this.count() < 5) {
queueMicrotask(() => this.count.set(this.count() + 1));
}
});
}
Wrap updates in untracked() (recommended), or use queueMicrotask to defer outside the current reaction.