skip to content

In Angular, how does the timing of an effect created in a component differ from one created in a root service?

level: middleimportance: should knowfreq 40%

answer

  1. two kinds, chosen by injector
  2. one tied to a view
  3. one flushed before any view
  4. before the component's template
  5. v19 reworked the schedule

basics

~20 s

A view effect, created by a component, directive or component-provided service, runs just before that component's template is checked. A root effect, created by a root service, is flushed at the start of each application tick, before any view is checked.

solid answer

~50 s

Angular decides the kind from the injector at creation. If the injector belongs to a view, meaning a component, a directive or a service provided in a component's `providers`, it is a **view effect**: it runs during change detection of the view that hosts the component, after the component's `ngOnChanges`/`ngOnInit`/`ngDoCheck` and before its own template is checked, so it sees fresh inputs. If not, as in a `providedIn: 'root'` service, it is a **root effect**: dirty root effects are flushed at the start of each application tick, before any component is checked. In both cases, if the effect changes one of its own dependencies, it re-runs before change detection moves on. View effects are destroyed with their component; root effects with their injector, which for root means the application. This scheduling dates from v19; earlier previews ran effects as microtasks.

code

ts · 25 lines
ts
import {Component, Injectable, effect, inject, input, signal} from '@angular/core';

@Injectable({providedIn: 'root'})
export class ThemeStore {
  readonly theme = signal<'light' | 'dark'>('light');

  constructor() {
    // Root effect: flushed at the start of each tick, before any view is checked.
    effect(() => localStorage.setItem('app-theme', this.theme()));
  }
}

@Component({
  selector: 'app-theme-badge',
  template: `<span [class]="cls()">{{ store.theme() }}</span>`,
})
export class ThemeBadge {
  readonly store = inject(ThemeStore);
  readonly cls = input('badge');

  constructor() {
    // View effect: runs after inputs are set and ngOnInit, before this template is checked.
    effect(() => console.log('badge class', this.cls(), 'theme', this.store.theme()));
  }
}

go deeper

for a junior

Recall that effects created in components and effects created in root services are different kinds, and that both run during change detection.

for a middle

Explain how the injector decides the kind, that view effects run before the component's template after ngOnInit, and that root effects flush before any view.

for a senior

Show how timing and lifetime change when an effect moves between a component, a component-provided service and a root service, and how to test each.

for a principal

Decide where long-lived synchronisation effects live, weighing tree-bound lifetimes against application-wide ones and the ordering each gives.

## Two kinds of effect, one API Every `effect()` in Angular 22.2 is one of two kinds, and you never choose it explicitly. Angular inspects the **injector** the effect is created with: - If that injector can see a **view context**, the effect is a **view effect** (the API docs also call it a component effect). - Otherwise it is a **root effect**. | Created in | Kind | Destroyed when | |---|---|---| | component or directive constructor / field | view effect | the component's view is destroyed | | service listed in a component's `providers` | view effect | that component is destroyed | | `providedIn: 'root'` service | root effect | the root injector, meaning the app, is destroyed | | service in a route or other environment injector | root effect | that injector is destroyed | The deciding question is "does this effect belong to a place in the component tree?", not "which class called `effect()`". ## When a view effect runs Change detection walks the component tree top-down. For each view it refreshes, Angular: 1. executes that view's template update pass, which sets child components' inputs; 2. runs the pre-order lifecycle hooks of the components declared there: `ngOnChanges`, `ngOnInit`, `ngDoCheck`; 3. runs the **dirty view effects** attached to that view; 4. then descends into the child components, checking their templates. So an effect a component creates runs **after the component's inputs are set and `ngOnInit` has run, and before the component's own template is checked**. That is what makes view effects safe for reading input signals and for work that must happen before the template renders. If the effect makes another effect in the same view dirty, Angular loops over that view's effects until none are dirty before continuing. ## When a root effect runs A root effect has no place in the tree. When one of its dependencies changes, it is queued with the root effect scheduler and change detection is requested. At the start of each application synchronization pass, **all dirty root effects are flushed, in creation order**, before any view is checked. If they write signals that templates read, those views are then checked in the same pass. A root theme store that writes `localStorage` therefore runs once per tick in which the theme changed, before any component renders. ## Shared rules - Both kinds **always run at least once** and never synchronously at creation. - Both run **as part of change detection**, which under zoneless (the default since v21) is scheduled by the signal write itself. - If an effect changes one of its own dependencies while running, it **re-runs before change detection continues**. This is correct only if the value settles; an effect that always changes what it reads will not. - Several writes between two passes collapse into **one** run with the latest values. ## Why the distinction matters in practice - **Reading inputs.** A view effect reading `this.userId()`, a signal input, sees the value set in this pass. Before v19's rework, effects ran as microtasks, which could observe inputs at a different moment; answers written for the preview era can be wrong about this. - **Lifetime.** Moving an effect from a component into a root service silently changes its lifetime from "while the component exists" to "while the app exists". Cleanup that relied on the component going away stops happening. - **Ordering across the app.** Root effects run before any view in a pass, so a root effect that normalises state (a logged-in user, a theme) is applied before components read it. - **Tests.** An effect runs only when change detection runs: `fixture.detectChanges()` or `TestBed.tick()` in a test. An assertion made right after a `set()` sees the old side effect. ## What changed in v19 The v19 release notes list a breaking timing change for effects, then still in developer preview: effects triggered outside change detection now run **as part of change detection instead of as a microtask**, and effects triggered during change detection, for example by input signals, run **earlier, before the component's template**. The same release made signal writes inside effects allowed by default. `effect()` became stable in v20 with this model.

  • In Angular, is an effect created in a service listed in a component's providers a root or a view effect?
    A view effect. The service's injector is the component's node injector, which has a view context, so the effect is attached to that view, runs with it during change detection and is destroyed when the component is destroyed.
  • In Angular, what happens when an effect writes to a signal it also read during the same run?
    The write marks the effect dirty again, and Angular re-runs it before moving on with change detection. If the second run writes the same value, the equality check stops the cycle; if it always writes a new value, the effect keeps re-running and the page hangs.

Root effects are the building's morning announcements, read out before any classroom starts; view effects are each teacher's own notes, read at the classroom door after the register is taken and before the lesson begins.

saying these in an interview costs you the question

  • Every effect runs on a microtask right after the signal write.
  • An effect in a component's providers service is a root effect.
  • Component effects run after the component's template has been checked.
  • Root and view effects run at exactly the same point in change detection.
  • Moving an effect into a root service does not change its lifetime.