Skip to content
intermediate Phase 11 · Signals and Change Detection

Effects and Dependency Tracking

Run side effects whenever tracked signal values change.

40m
0 problems
Topic Progress 0%

Creating Effects

Effects run side effects whenever tracked signal values change. They are the signal-based alternative to ngOnChanges and subscribe.

Basic Effect

import { Component, signal, effect } from '@angular/core';

@Component({
  selector: 'app-counter',
  template: `<p>Count: {{ count() }}</p>`,
})
export class CounterComponent {
  count = signal(0);

  constructor() {
    effect(() => {
      console.log('Count changed:', this.count());
    });
  }
}

Automatic Dependency Tracking

Effects automatically track which signals are read inside them:

constructor() {
  effect(() => {
    // This effect depends on count() and name()
    console.log(`${this.name()} has count ${this.count()}`);
  });
}

When count or name changes, the effect re-runs. No manual subscription needed.

Effect with Cleanup

constructor() {
  effect((onCleanup) => {
    const intervalId = setInterval(() => {
      console.log('Count:', this.count());
    }, 1000);

    onCleanup(() => {
      clearInterval(intervalId);
    });
  });
}

The onCleanup callback runs before the effect re-runs and when the effect is destroyed.

Practical Effect Patterns

Effects are useful for DOM manipulation, logging, syncing with external libraries, and triggering side effects.

DOM Manipulation

constructor() {
  effect(() => {
    document.title = `${this.count()} items`;
  });
}

Syncing with External Libraries

constructor() {
  effect(() => {
    this.chart.update(this.data());
  });
}

Logging and Debugging

constructor() {
  effect(() => {
    console.log('User changed:', this.user());
  });
}

Effect in Injection Context

Effects must be created in an injection context (constructor, field initializer, or factory):

// GOOD: Constructor
constructor() {
  effect(() => { ... });
}

// GOOD: Field initializer
private logger = effect(() => { ... });

// BAD: Not in injection context
someMethod() {
  effect(() => { ... }); // Error!
}

Destroying Effects

private myEffect = effect(() => {
  console.log('Value:', this.value());
});

// Later, to stop the effect
this.myEffect.destroy();

Effects created in a component constructor are automatically destroyed when the component is destroyed.

Effect vs Computed

Feature effect() computed()
Returns Nothing (void) A signal value
Purpose Side effects Derived values
Read signals Yes Yes
Can write signals Yes (careful!) No (read-only)
Use for DOM, logging, syncing Derived state

Quiz

1. What does effect() do?

Question 1 options

2. How do you clean up an effect?

Question 2 options

3. What is the difference between effect() and computed()?

Question 3 options

Flashcards

Question

What does effect() do?

Answer

Runs a side effect when tracked signals change. Auto-tracks dependencies.

Question

How do you destroy an effect?

Answer

effect.destroy() — stops the effect. Auto-destroyed in components.

Question

effect() vs computed()?

Answer

effect() runs side effects (void). computed() derives a signal value.

Revision Notes

Key Takeaways

  • 1. effect() runs side effects when signals change
  • 2. Dependencies are automatically tracked
  • 3. effect.destroy() stops the effect
  • 4. Use computed() for derived values, effect() for side effects

Interview Tips

  • • Explain automatic dependency tracking
  • • Be ready to describe effect cleanup
  • • Mention the difference between effect() and computed()

Cheat Sheet

Effects Cheat Sheet

Create effect:

constructor() {
  effect(() => {
    console.log('Value:', this.count());
  });
}

With cleanup:

effect((onCleanup) => {
  const id = setInterval(() => {}, 1000);
  onCleanup(() => clearInterval(id));
});

Destroy:

this.myEffect.destroy();

Rules:

  • Must be in injection context
  • Auto-destroyed in components
  • Use for side effects, not derived values

Continue Learning