Chapter 10.3โ˜• 15 min read

Effect โ€” Side Effects with effect()

effect() runs a function whenever signals it reads change. Use it for side effects: logging, localStorage, API calls, DOM manipulation, syncing with non-Angular libraries.

01What is effect()

effect() is a function that runs a callback whenever the signals it reads change. Think of it as a "watcher" โ€” when signal A changes, do action B.

"Effect = watcher โ€” signal badla toh kuch karo automatically."

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

const count = signal(0);

// Register an effect
effect(() => {
  console.log('Count changed to:', count());
  localStorage.setItem('count', count().toString());
});

// When we change the signal, the effect runs automatically
count.set(5);
// Console: "Count changed to: 5"
// localStorage: "count" = "5"

Key characteristics of effect():

  • Runs automatically when registered โ€” and again whenever read signals change
  • Tracks signals automatically โ€” any signal you read inside effect() becomes a dependency
  • Cleaned up automatically โ€” when the component is destroyed, effects are destroyed too
  • Runs during change detection โ€” not immediately, but before Angular renders

โš ๏ธ IMPORTANT: effect() is for SIDE EFFECTS, NOT for deriving values. If you need a value to use in template, use computed(). Effect does NOT return a value.

02Creating an Effect

Creating an effect is simple โ€” call effect() in the component's constructor or field initializer.

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

@Component({...})
export class SettingsComponent {
  theme = signal('dark');
  fontSize = signal(16);

  // Effect in field initializer
  private saveSettings = effect(() => {
    localStorage.setItem('theme', this.theme());
    localStorage.setItem('fontSize', this.fontSize().toString());
    console.log('Settings saved:', this.theme(), this.fontSize());
  });

  changeTheme(newTheme: string) {
    this.theme.set(newTheme);
    // โ†’ effect runs: saves to localStorage, logs
  }
}

Angular automatically tracks which signals you read inside the effect. When theme() or fontSize() changes, the effect re-runs.

"effect automatically tracks which signals you read inside it โ€” jitne bhi signals padho, sab dependencies ban jayenge."

Effect runs in injection context: effect() must be called in an injection context (constructor, field initializer, or a function called from one). This is because it needs access to DestroyRef for auto-cleanup.

03Effect vs Computed โ€” When to Use What

This is the most important distinction in signals: computed() is for values, effect() is for actions.

๐Ÿ“Š computed vs effect โ€” The Golden Rule

computed()effect()
Returnsโœ… A value (Signal)โŒ Nothing (void)
PurposeDerive statePerform side effects
Pure?โœ… Yes โ€” no side effectsโŒ No โ€” side effects expected
Used in templateโœ… Yes: {{ fullName() }}โŒ No
Runs whenDependency changes + someone reads itDependency changes
PerformanceCached, lazyRuns eagerly
// โœ… CORRECT: computed for deriving values
total = computed(() => price() * quantity());
// Template: {{ total() }}

// โœ… CORRECT: effect for side effects
effect(() => localStorage.setItem('total', this.total().toString()));

// โŒ WRONG: computed with side effect
bad = computed(() => {
  localStorage.setItem('count', this.count().toString()); // Side effect!
  return this.count() * 2;
});

// โŒ WRONG: effect for deriving value
effect(() => {
  this.double = this.count() * 2; // Should use computed!
});

"computed = calculator (returns value), effect = alarm (does action)."

Rule: If you need a return value โ†’ computed(). If you need to DO something โ†’ effect(). Never use effect() to set another signal โ€” use computed() or .update() instead.

04Cleanup in Effect

effects can have cleanup โ€” a function that runs BEFORE the effect re-runs and when the component is destroyed.

import { effect } from '@angular/core';

effect((onCleanup) => {
  const timer = setInterval(() => {
    console.log('Tick:', count());
  }, 1000);

  // Cleanup function โ€” runs before next effect run and on destroy
  onCleanup(() => {
    clearInterval(timer);
    console.log('Timer cleaned up');
  });
});

"Cleanup = mopping โ€” pehle purana saaf karo, phir naya lagao."

Why cleanup matters:

  • Prevents memory leaks โ€” intervals, subscriptions, event listeners
  • Prevents stale data โ€” old API response vs new response
  • Prevents multiple instances โ€” count changes sets interval again without clearing old one
// Without cleanup โ€” BUG!
effect(() => {
  setInterval(() => console.log(count()), 1000);
  // Every time count changes, NEW interval starts!
  // Old ones keep running โ€” memory leak!
});

// With cleanup โ€” CORRECT!
effect((onCleanup) => {
  const id = setInterval(() => console.log(count()), 1000);
  onCleanup(() => clearInterval(id));
  // Old interval is cleared before new one starts
});
05Practical Use Cases

Here are real-world use cases where effect() shines:

1. Auto-save to localStorage

effect(() => {
  const settings = this.settings();
  localStorage.setItem('app-settings', JSON.stringify(settings));
});

2. Sync with non-Angular library (Chart.js, Leaflet)

chartEffect = effect(() => {
  const data = this.chartData();
  this.chartInstance.data.datasets[0].data = data;
  this.chartInstance.update(); // Re-render chart
});

3. Console logging for debugging

logEffect = effect(() => {
  console.log('๐Ÿ”„ User:', this.user(), '| Theme:', this.theme());
});

4. Show toast notification

notificationEffect = effect(() => {
  const err = this.errorMessage();
  if (err) {
    this.toastService.show(err, { duration: 3000 });
  }
});

5. Document title update

titleEffect = effect(() => {
  document.title = `${this.pageTitle()} โ€” BiryaniHub`;
});

"effect = bridge between Angular signals and outside world โ€” localStorage, DOM, libraries, APIs โ€” sab kuch."

โš ๏ธ Don't overuse effect! If a template can handle it (show/hide, class toggle), don't use effect. Effects are for IMPERATIVE actions that the template can't handle declaratively.

Key Takeaways

  • โœ… effect() runs a callback when signals it reads change โ€” for side effects
  • โœ… NOT for deriving values โ€” use computed() for that
  • โœ… Auto-tracks dependencies โ€” signals read inside effect() are tracked
  • โœ… onCleanup() โ€” runs before effect re-runs and on component destroy
  • โœ… Use cases: localStorage, logging, DOM updates, library sync
  • โœ… DON'T write to signals inside effect that are part of the same dependency chain
Course Search
Search across all chapters & stages
๐Ÿ“–

Search the course

Type any topic โ€” branching, stash, rebase, hooks โ€” and jump straight to that chapter.

merge branchesgit stashundo commitrebase