Adds a new agent skill focused on providing fundamentals to coding agents and adhering
to modern Angular code including Signals, Signal Forms and other latest updates.
(cherry picked from commit 8291b16a36)
2.8 KiB
Angular Signals Overview
Signals are the foundation of reactivity in modern Angular applications. A signal is a wrapper around a value that notifies interested consumers when that value changes.
Writable Signals (signal)
Use signal() to create state that can be directly updated.
import {signal} from '@angular/core';
// Create a writable signal
const count = signal(0);
// Read the value (always requires calling the getter function)
console.log(count());
// Update the value directly
count.set(3);
// Update based on the previous value
count.update((value) => value + 1);
Exposing as Readonly
When exposing state from a service, it is a best practice to expose a readonly version to prevent external mutation.
private readonly _count = signal(0);
// Consumers can read this, but cannot call .set() or .update()
readonly count = this._count.asReadonly();
Computed Signals (computed)
Use computed() to create read-only signals that derive their value from other signals.
- Lazily Evaluated: The derivation function doesn't run until the computed signal is read.
- Memoized: The result is cached. It only recalculates when one of the signals it depends on changes.
- Dynamic Dependencies: Only the signals actually read during the derivation are tracked.
import {signal, computed} from '@angular/core';
const count = signal(0);
const doubleCount = computed(() => count() * 2);
// doubleCount automatically updates when count changes.
Reactive Contexts
A reactive context is a runtime state where Angular monitors signal reads to establish a dependency.
Angular automatically enters a reactive context when evaluating:
computedsignalseffectcallbackslinkedSignalcomputations- Component templates
Untracked Reads (untracked)
If you need to read a signal inside a reactive context without creating a dependency (so that the context doesn't re-run when the signal changes), use untracked().
import {effect, untracked} from '@angular/core';
effect(() => {
// This effect only runs when currentUser changes.
// It does NOT run when counter changes, even though counter is read here.
console.log(`User: ${currentUser()}, Count: ${untracked(counter)}`);
});
Async Operations in Reactive Contexts
The reactive context is only active for synchronous code. Signal reads after an await will not be tracked. Always read signals before asynchronous boundaries.
// ❌ INCORRECT: theme() is not tracked because it is read after await
effect(async () => {
const data = await fetchUserData();
console.log(theme());
});
// ✅ CORRECT: Read the signal before the await
effect(async () => {
const currentTheme = theme();
const data = await fetchUserData();
console.log(currentTheme);
});