πŸ“š Best Practices

Angular & TypeScript guidance, patterns, and anti-patterns

Signals (Angular 16+)

Feb 2026

🐞 Gotcha: Signal Update Loops in Effects

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!

❌ Bad: Read & Update Same Signal

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.

βœ… Good: Use untracked() or Defer

import { 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.

Why this matters: Angular signals (and most reactive systems) don’t allow mutation during an active reaction. Fixing this prevents subtle reactivity bugs and infinite loops.