← Back to Chapters

Effects in Angular

⚡Effects in Angular

? Quick Overview

Effects in Angular’s new reactivity system let you perform side effects whenever reactive values (signals or computed values) change. Instead of writing logic directly in templates, you attach it to effects that automatically re-run when their dependencies update.

  • Great for logging, analytics, API calls, and DOM-related tasks.
  • Automatically track which signals they use and re-run when those signals change.
  • Keep templates clean and move logic into TypeScript where it’s easier to test and maintain.

? Key Concepts

  • Signal – a reactive state container, e.g. count = signal(0).
  • Effect – runs a function whenever one of its used signals changes.
  • Side Effect – anything that touches the “outside world”: logs, network calls, DOM changes, etc.
  • Separation of concerns – use computed() for pure calculations and effect() for side effects.

? Syntax / Theory

An effect is created by calling effect() and passing a function that reads one or more signals. Angular automatically tracks those signals and re-runs the function whenever they change.

? View Basic Effect Syntax
// Basic effect reacting to a signal change
import { effect, signal } from '@angular/core';

const count = signal(0);

const logEffect = effect(() => {
  console.log('Count is now:', count());
});

count.set(5);      // Triggers effect
count.update(c => c + 1); // Triggers effect again

? What happens here?

  • count is a signal holding a numeric value.
  • effect() registers a reactive side effect that reads count().
  • Every time count changes, the effect runs and logs the new value.

? Example 1: Counter with Effect

This example shows how an effect can log changes to a counter signal whenever the user clicks a button.

? View Code Example
// Angular component using an effect to watch a counter signal
import { Component, effect, signal } from '@angular/core';

@Component({
  selector: 'app-root',
  template: `<h2>Count: {{ count() }}</h2>
             <button (click)="increment()">Increment</button>`
})
export class AppComponent {
  count = signal(0);

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

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

? Explanation

  • signal(0) creates a reactive state variable count with initial value 0.
  • The effect() reads this.count(), so Angular tracks it as a dependency.
  • Each time increment() is called, count updates and the effect logs the new value.
  • No extra wiring is required: reactivity is automatic based on which signals are read inside the effect.

? Example 2: API Call with Effects

Effects can also be used to handle async operations like fetching data from an API and updating a signal.

? View Code Example
// Using an effect to fetch users and keep the UI in sync
import { Component, effect, signal } from '@angular/core';

@Component({
  selector: 'user-list',
  template: `<h3>Users</h3>
             <ul>
               <li *ngFor="let user of users()">{{ user.name }}</li>
             </ul>`
})
export class UserListComponent {
  users = signal<any[]>([]);

  constructor() {
    effect(async () => {
      const res = await fetch('https://jsonplaceholder.typicode.com/users');
      this.users.set(await res.json());
    });
  }
}

? Explanation

  • users is a signal that stores an array of user objects.
  • The effect() performs an async fetch() call to load user data.
  • Once the response is received, the signal is updated using this.users.set(...).
  • The template reads users(), so Angular re-renders the list whenever the signal changes.

✅ Tips & Best Practices

  • Use effects for side effects only: API calls, logging, analytics, timers, subscriptions, etc.
  • Keep heavy business logic and pure calculations inside computed() or regular functions.
  • Always remember to call signals like count() instead of count when reading them.
  • Avoid overloading a single effect; use multiple smaller effects to separate concerns (logging vs. data fetching).
  • When using async code in effects, add proper error handling (try/catch or .catch() on promises).

? Try It Yourself

  • Create a counter component with a signal and an effect that logs every count change to the console.
  • Build a search box where an effect triggers an API call whenever the search query signal changes.
  • Add two separate effects to a component:
    • One for logging signal changes.
    • Another for sending analytics or saving preferences (e.g., last visited tab).
  • Refactor an existing component that uses template-heavy logic and move that logic into signals + effects.