← Angular: components, reactivity and application decisions
04 / 10 · 65 MIN

Lifecycle, effects and reactive cleanup

Relate initialization, input changes and destruction to reactive resources. Choose computed, effect and render callbacks according to responsibility, and define who cancels pending work.

Inputs and initialization are different events

ngOnInit runs once after Angular initializes component inputs. When ngOnChanges is also implemented, its first invocation occurs before ngOnInit. Later input changes can cause further ngOnChanges calls without repeating one-time initialization.

SimpleChanges exposes previous value, current value and whether this is the first change. If input value has alias publicValue, the record key remains the TypeScript property name value. An alias used by the template does not rename the internal observation API.

Destruction and cleanup responsibility

DestroyRef registers callbacks through onDestroy near the code preparing a resource. When injected in a component or directive scope, cleanup follows that owner’s destruction; in other scopes it follows the corresponding injector. The value returned by onDestroy is a function unregistering the callback.

Calling it neither destroys the component nor executes registered cleanup. This matters when a resource ends before its owner: the application can release it and unregister a callback that should no longer run, according to that resource’s contract.

Derive values before introducing effects

computed expresses a value calculated from signals. In the example, label depends on selected and needs no separate writable signal synchronized through an effect. Effects are useful when reactive reads should trigger imperative work in a non-reactive API, such as the local notification shown.

An effect tracks reads actually performed during its execution; it is not an automatic list of all class properties. Duplicating derived state introduces more write points and can create cycles or inconsistent moments. First identify whether you need a value or an action.

Clean up before the next execution

The function passed to effect receives onCleanup, registering work before the effect reruns or is destroyed. In the example, each run captures its own id and timer. A new run cancels the previous still-pending timer, preventing the old notification in that case.

Cancellation does not remove messages already emitted. Effects created in component context are normally destroyed with it. Choosing manualCleanup: true assumes responsibility for retaining the EffectRef and calling destroy when its contract ends.

Merely retaining the reference without destroying it does not satisfy that responsibility.

Subscriptions need a scope

takeUntilDestroyed associates subscription completion with destruction of the specified scope. Inside an injection context it can obtain DestroyRef implicitly. In an event-triggered method that context normally no longer exists: retain a reference obtained in a field initializer and pass it explicitly to takeUntilDestroyed.

This avoids turning cleanup into a DI resolution error. The chosen lifetime also matters: using DestroyRef from a longer-lived instance can retain a subscription after the consuming component disappears. Define the resource owner before choosing the reference.

Work depending on updated DOM

An ordinary effect does not guarantee execution after a DOM update. For a reactive integration requiring that state, consider afterRenderEffect according to its documented contract. afterNextRender and afterEveryRender register callbacks after application rendering and must be called in an injection context.

These callbacks do not run during server rendering or prerendering. Do not use them as the sole location of a transformation required by the server response. Distinguish data calculation, input observation and access to rendered elements before choosing a hook by name.

Late responses and cancellation limits

DestroyRef.destroyed lets code check whether its owner has ended before applying a late update. This is useful when an asynchronous callback may arrive after destruction, but does not automatically cancel a Promise or remote operation. An HTTP success response likewise does not prove the local instance still exists.

During incident review, record operation start, local destruction, result receipt and the decision to apply or ignore it separately. The cleanup explanation then remains faithful to what was interrupted and what may still have produced effects.

// Reading example: synthetic selection notifications; no application bootstrap.
import {Component, computed, effect, signal} from '@angular/core';

@Component({
  selector: 'selection-notice', standalone: true,
  template: `<p>{{ label() }}</p>
    <button type="button" (click)="toggle()">Change selection</button>`,
})
export class SelectionNotice {
  readonly selected = signal('A');
  readonly label = computed(() => 'Selected: ' + this.selected());
  constructor() {
    effect(onCleanup => {
      const id = this.selected();
      const timer = setTimeout(() => console.info('Selection notice:', id), 1000);
      onCleanup(() => clearTimeout(timer));
    });
  }
  toggle() { this.selected.update(id => id === 'A' ? 'B' : 'A'); }
}
IN PRACTICE

label derives from selection without duplicating writable state. The effect creates a delayed local notification for the id it read; onCleanup cancels that timer before the next execution or destruction. If the timer has already fired, clearing its identifier does not erase the emitted message.

The relative order of clicks, effect execution and timer must be an explicit scenario premise.

Common pitfalls

Using ngOnInit for all later changes; copying derived values through effects; assuming an ordinary effect sees updated DOM; implicitly calling inject in a handler; forgetting destroy with manualCleanup; attributing remote rollback to local cleanup.

Related topics: Signals, inputs and output contracts · Dependency injection and service lifetimes · HTTP, streams and response acceptance

Take this idea with you

Choose the mechanism by the observed event: inputs, reactive dependencies, rendering or destruction. Then identify resource lifetime and cleanup action. Cleanup prevents future work covered by its contract; it does not erase effects that already occurred.

Create account

Reference: Input initialization and changes · Angular 22; standalone components