kdocs
GitHub
Lang - Web
Lang - Web
  • Base
    • Css
    • Javascript
    • Typescript
      • New Project
  • Frameworks
    • Angular
      • Directives
      • Components
      • Templates
        • Bindings
        • Control Flow
        • Variables
      • Signals
      • Pipes
      • Services
        • Dependency Injection
      • Forms
        • Reactive Form
        • Template-Driven Form
      • Router
      • HTTP Client
      • Observables RxJS
      • Testing
        • Components
        • Directives
        • Pipes
        • Services
      • Optimization & Performance
      • Security
Powered by GitBook
On this page
  • About
  • Best Practices
  • Writable Signals
  • signal()
  • Computed Signals
  • computed()
  • Effects
  • effect()
  1. Frameworks
  2. Angular

Signals

PreviousVariablesNextPipes

Last updated 5 months ago

About

A signal is a wrapper around a value that notifies interested consumers when that value changes. (Values may be primitive or complex data structures)

Signals gives Angular a new way of handling data changes and updating the UI, instead of just Change Detection.

You read a signal's value by calling its getter function, which allows Angular to track where the signal is used.

Best Practices

  • Avoid using effect() for propagation of state changes.

    • This might lead to infinite circular updates, or unnecessary change detection cycles.

    • Angular will forbid you from change signal states inside effect().

  • Don't mutate signal values when updating them, always give them new values.

Writable Signals

signal()

Can be created with signal(value).

You may update their values with:

  • set(newValue) to just set a new value.

  • update((oldValue) => newValue), to access the old value.

<!-- To Output a signal, just execute it's getter -->
<p>{{ counter() }}</p>
@Component({ ... })
export class Component {
    counter: WritableSignal<number> = signal(0);
    // Or
    counter = signal<number>(0);

    actions = signal<string[]>([]);

    show() {
        console.log(this.counter());
    }

    increment() {
        this.counter.update((oldValue: number) => oldValue + 1);

        // NEVER mutate Signal values, instead always give it a new value
        this.actions.update((oldActions) => [...oldActions, 'new Value']);
    }

    decrement() {
        this.counter.update((oldValue: number) => oldValue - 1);
    }
}

Computed Signals

computed()

Are readonly signals that derive their value from other signals.

Created with computed(() => this.otherSignal() + 1).

Computed signals depend on other signals, so computed signals are only updated when their dependee signals update.

Lazy and Memoized

Computed signals are both lazily evaluated and memoized.

  • lazily evaluated means that they do not run to calculate its value until the first time you read their value.

  • memoized means that its value is cached, so they wont be recalculated all the time. (Only when their dependee signal value changes)

As a result of this, you can safely perform computationally expensive derivations in them, like filtering arrays.

Dynamic dependencies

Computed signals dependencies are dynamic.

  • Only signals that were actually read during the derivation are tracked. (And they can change on runtime)

const showCount = signal(false);
const count = signal(0);
const conditionalCount = computed(() => {
    if (showCount()) {
        return `The count is ${count()}.`;
    } else {
        return "Nothing to see here!";
    }
});

In this example:

  • When showCount is false, updating count will not trigger a recomputation of the computed signal.

    • So only showCount change will trigger.

  • But when showCount is true, count signal can be read, and now the computed signal will recalculate itself on both showCount and count updates.

  • Just remeber that the computed signal won't recalculate immediatly after the dependee signals update, but only when conditionalCount is read again.

Effects

effect()

Effects are operations that runs whenever on or more signal values change.

  • They also have dependee signals, which are the signals used inside them.

  • When these dependee signals values change, the effect re-runs.

Create them with effect(() => {}).

Effect always run at least once.

Effects also keep track of their dependencies dynamically.

So they will only track signals that were read during the last execution.

Effects always execute asynchronously, during the change detection process.

By default Angular prevents updates to signals inside effects.

Effects are good for:

  • Logging data.

  • Keeping data in sync with window.localStorage.

  • Performing custom rendering to a <canvas>, charting library, or other third party UI library.

Don't use Signals for propagation of state changes, potentially resulting in infinite circular updates, or unnecessary change detection cycles.

Injection Context

Effects are destroyed automatically when their Components, Directives, Services, etc are destroyed.

export class Component {
    constructor() {
        effect(() => { ... });
    }
}

You can create effect outside of a constructor or assign specific names to it by:

export class Component {
    // Give a descriptive name to the effect
    private cEffect = effect(() => { ... });
    
    // To create an Effect outside of the constructor, get the Injector dependency
    constructor(private injector: Injector) {}
    
    log() {
        effect(() => { ... }, { injector: this.injector });
    }
}

Avoid signal tracking

To avoid re-run of effects when signal values are updated, wrap the signals with untracked().

export class Component {
    counter = signal(0);
    constructor() {
        effect(() => {
            console.log(`Counter is ${ untracked(counter) }`);
        });
    }
}

untracked is also useful when an effect needs to invoke some external code which shouldn't be treated as a dependency.

effect(() => {
    const user = currentUser();
    untracked(() => {
        // If the `loggingService` reads signals, they won't be counted as dependencies of this effect.
        this.loggingService.log(`User set to ${user}`);
    });
});

Effect cleanup functions

Effect might start long-running operations, which you should cancel if the effect is destroyed or runs again before the first operation finished.

When creating effects, you can optionally pass an onCleanup function as its first parameter. And this onCleanup accepts a callback function that is invoked before the next run of the effect begins, or when the effect is destroyed.

effect((onCleanup) => {
    const user = currentUser();
    const timer = setTimeout(() => {
        console.log(`1 second ago, the user became ${user}`);
    }, 1000);
    onCleanup(() => {
        clearTimeout(timer);
    });
});

By deafult, you can only create effect() within an .

AngularAngular
guide/signals
Injection Context
Logo