Signals
Signals
Signals in Angular are a new reactivity model that let components automatically respond to state changes without relying on Zone.js or manual change detection.
A signal is a special reactive variable that stores a value and notifies any dependent computations or templates when it changes. When you update a signal, Angular knows exactly which parts of the UI depend on it and updates only those parts — making change detection faster, more predictable, and easier to reason about.
They're used to:
- Replace
@Inputbindings, RxJS, or manualChangeDetectorRefpatterns for local reactive state. - Improve performance by running fine-grained reactivity instead of full component tree checks.
- Simplify code, since templates automatically re-render when a signal's value changes.
Difference between signals and regular variables
Regular variables in Angular are not reactive — when you change them, Angular has no built-in way to know that something in the template depends on them. The UI only updates after Angular's change detection cycle runs (for example, after a template event, async operation, or a manual trigger).
Signals, on the other hand, are reactive primitives. When you read a signal in a template or a computed function, Angular automatically tracks that dependency. When the signal's value changes, Angular marks only the affected components or expressions for update — no full tree check or manual notification needed.
In short:
- Regular variables: Passive data; UI updates only when change detection happens for other reasons.
- Signals: Active, tracked state; directly trigger minimal, targeted updates when they change.
This makes signals both more predictable and more efficient than relying on traditional change detection alone.
An example
Only regular variables
- Create an Angular 20+ zoneless app and create a new
counter-component
import { Component } from "@angular/core";
@Component({
selector: "app-counter-component",
imports: [],
templateUrl: "./counter-component.html",
styleUrl: "./counter-component.css",
})
export class CounterComponent {
countV = 0;
doubleV = this.countV * 2;
incV() {
this.countV = this.countV + 1;
}
decV() {
this.countV = this.countV - 1;
}
resetV() {
this.countV = 0;
}
}
<div class="card">
<h3>Counter regular variable</h3>
<p>countV: <strong>{{ countV }}</strong></p>
<p>doubleV: <strong>{{ doubleV }}</strong></p>
<button (click)="incV()">+1</button>
<button (click)="decV()">-1</button>
<button (click)="resetV()">Reset</button>
</div>
.card {
border: 1px solid #ddd;
padding: 1rem;
border-radius: 0.75rem;
}
<!-- app.html -->
<h1>Signals & Change Detection</h1>
<section>
<h2>Counter</h2>
<app-counter-component></app-counter-component>
</section>
- Clicking
+1triggers change detection (template event):counterVin template is updated - Clicking
-1triggers change detection (template event):counterVin template is updated doubleVstays 0, no change detection triggered- Clicking
Resettriggers change detection (template event):counterVin template is updated
With signals
import { Component, computed, effect, signal } from "@angular/core";
@Component({
selector: "app-counter-component",
imports: [],
templateUrl: "./counter-component.html",
styleUrl: "./counter-component.css",
})
export class CounterComponent {
countV = 0;
countS = signal(0);
doubleV = this.countV * 2;
doubleS = computed(() => this.countS() * 2);
incV() {
this.countV = this.countV + 1;
}
decV() {
this.countV = this.countV - 1;
}
resetV() {
this.countV = 0;
}
incS() {
this.countS.update((n) => n + 1);
}
decS() {
this.countS.update((n) => n - 1);
}
resetS() {
this.countS.set(0);
}
}
<div class="card">
<h3>Counter regular variable</h3>
<p>countV: <strong>{{ countV }}</strong></p>
<p>doubleV: <strong>{{ doubleV }}</strong></p>
<button (click)="incV()">+1</button>
<button (click)="decV()">-1</button>
<button (click)="resetV()">Reset</button>
</div>
<div class="card">
<h3>Counter signal</h3>
<p>countS(): <strong>{{ countS() }}</strong></p>
<p>doubleS(): <strong>{{ doubleS() }}</strong></p>
<button (click)="incS()">+1</button>
<button (click)="decS()">-1</button>
<button (click)="resetS()">Reset</button>
</div>
- Clicking
+1triggers change detection (updateon signal):counterSin template is updated - Clicking
-1triggers change detection (updateon signal):counterSin template is updated doubleSin template is updated whenevercounterSis updated because it is acomputedsignal- Clicking
Resettriggers change detection (seton signal andcomputedsignal):counterSanddoubleSin template are updated
Signals and computed values
A signal is created with the signal() function — it holds a reactive value that automatically updates the UI when changed. This is a writable signal!
A computed is a derived signal whose value depends on other signals and recalculates automatically when they change.
In our example:
countS()reads the signal's current value.countS.set()orcountS.update()changes it.computed()automatically recalculates when its dependencies change.
Computed signals are not writable signals
doubleS = computed(() => this.countS() * 2); // OK
doubleS.set(24); // NOT OK = compilation error
Effects
Signals are useful because they notify interested consumers when they change. An effect is an operation that runs whenever one or more signal values change. You can create an effect with the effect function:
// side-effect that reacts to signal changes
log = effect(() => {
// This will run on init, and whenever `count` changes
console.log("[effect] count is", this.countS());
});
- Effects always run at least once
- An effect tracks any signal value. If any of these signal values change, the effect runs again.
- Effects always execute asynchronously, during the change detection process.
Use cases for effects
Effects are rarely needed in most application code, but may be useful in specific circumstances. Here are some examples of situations where an effect might be a good solution:
- Logging data being displayed and when it changes, either for analytics or as a debugging tool.
- Keeping data in sync with
window.localStorage. - Adding custom DOM behavior that can't be expressed with template syntax.
- Performing custom rendering to a
<canvas>, charting library, or other third party UI library.
When not to use effects
Avoid using effects for propagation of state changes. This can result in ExpressionChangedAfterItHasBeenChecked errors, infinite circular updates, or unnecessary change detection cycles.
Instead, use computed or linked signals to model state that depends on other state.
LinkedSignals
A linked signal is a hybrid reactive primitive: it combines the reactivity of a computed() signal (i.e. it automatically recalculates when its dependencies change) with the writability of a normal signal(). It lets you express derived state that can also be overridden or locally mutated.
So in architecture terms: when you have a piece of component/local state that depends on some upstream state (e.g., input props, store signals, fetched data) but also needs to allow local user modifications — that's a strong candidate for linkedSignal.
Common use cases for linked signals are:
- Reset/initialisation semantics: If upstream changes (e.g., the selected item changed), you want to reset a dependent field to a default value. But if the user had already modified that field and the upstream still supports it, you might want to preserve the user's value.
- Multiple source signals: You might have a dependent piece of state that depends on two or more upstream signals (for example filter criteria from dropdown + text input). Using
linkedSignalyou can derive from several signals elegantly. - Writable derived state: Sometimes you want a derived value but also allow it to be overridden by user. For example a “percentage” derived from two signals but user may also edit the percentage manually. A
computed()alone wouldn’t allow write.
Signal-based inputs
Angular 20 introduces a new way to define component inputs using Signals — a more reactive and fine-grained mechanism than traditional @Input() bindings.
Traditional Inputs
In traditional Angular, inputs are defined using the @Input() decorator:
@Component({
selector: "child-cmp",
template: `<p>Value: {{ value }}</p>`,
})
export class ChildCmp {
@Input() value!: number;
}
And passed from the parent component like this:
@Component({
selector: "parent-cmp",
template: `<child-cmp [value]="counter"></child-cmp>`,
})
export class ParentCmp {
counter = 1;
}
Problems with this approach:
ngOnChanges()is needed to detect input changes- Derived values require manual recomputation
A click event will trigger change detection, but an aysnchronous change of the value (like a timer, Observable, ...) won't!
import { Component } from "@angular/core";
import { CounterComponent } from "./counter-component/counter-component";
import { ScoreComponent } from "./score-component/score-component";
import { ProductSelectionComponent } from "./product-selection-component/product-selection-component";
import { ChildComponent } from "./child-component/child-component";
import { interval, tap } from "rxjs";
@Component({
selector: "app-root",
imports: [
CounterComponent,
ScoreComponent,
ProductSelectionComponent,
ChildComponent,
],
templateUrl: "./app.html",
styleUrl: "./app.css",
})
export class App {
traditionalInputCounter = 1;
constructor() {
interval(1000)
.pipe(
tap(() => {
// asynchronous change of data won't trigger change detection
console.log(this.traditionalInputCounter);
this.incrementTraditionalInputCounter();
})
)
.subscribe();
}
// button click will trigger change detection
incrementTraditionalInputCounter() {
this.traditionalInputCounter += 1;
}
}
Signal-based inputs
Angular's Signals API provides a new reactive foundation for components. A signal is a reactive variable that automatically updates any expressions that depend on it. In Angular 20+, you can now define signal-based inputs using the input() function directly.
@Component({
selector: "child-cmp",
template: `
<p>Value: {{ value() }}</p>
<p>Double: {{ double() }}</p>
`,
})
export class ChildCmp {
value = input<number>(); // Signal input
double = computed(() => this.value() * 2);
constructor() {
effect(() => console.log("Value changed:", this.value()));
}
}
@Component({
selector: "parent-cmp",
template: `
<child-cmp [value]="counter()"></child-cmp>
<button (click)="increment()">Increment</button>
`,
})
export class ParentCmp {
counter = signal(1);
increment() {
this.counter.update((v) => v + 1);
}
}
import { Component, signal } from "@angular/core";
import { CounterComponent } from "./counter-component/counter-component";
import { ScoreComponent } from "./score-component/score-component";
import { ProductSelectionComponent } from "./product-selection-component/product-selection-component";
import { ChildComponent } from "./child-component/child-component";
import { interval, tap } from "rxjs";
@Component({
selector: "app-root",
imports: [
CounterComponent,
ScoreComponent,
ProductSelectionComponent,
ChildComponent,
],
templateUrl: "./app.html",
styleUrl: "./app.css",
})
export class App {
singalInputCounter = signal(1);
constructor() {
interval(1000)
.pipe(
tap(() => {
this.singalInputCounter.update((n) => n + 1);
})
)
.subscribe();
}
}
Each time the signal gets a new value, change detection is triggered for the component. That's why the UI rerenders every second.
httpResource
The Angular team introduced a new experimental API called httpResource, built on top of the core resource primitive and the standard HttpClient.
Key motivations:
- Provide a declarative, signal-based approach to fetching HTTP data: you no longer need to manually subscribe, manage loading/error/value states, or cancel obsolete requests.
- Leverage
HttpClientunderneath, so you still benefit from interceptors, angular testing facilities, existing HTTP infrastructure. - Simplify boilerplate and integrate more seamlessly with Angular's Signals and reactive model.
httpResource exposes its result through Signals.
Caution
It is marked experimental in Angular 20. The team cautions that the API may change before it becomes stable.
In the following example you can see what we can access (as signals) from the httpResource:
// Service
export class ProductService {
private productsUrl = "api/products";
// Retrieve data into a signal
productsResource = httpResource<Product[]>(() => this.productsUrl, {
defaultValue: [],
});
}
// Component
@Component({
selector: "app-http-resource-component",
imports: [],
templateUrl: "./http-resource-component.html",
styleUrl: "./http-resource-component.css",
})
export class HttpResourceComponent {
private productService = inject(ProductService);
productsResource = this.productService.productsResource;
}
<!-- Template -->
<h2>httpResource</h2>
@if (productsResource.isLoading()) {
<p>Loading products...</p>
} @else if (productsResource.error()) {
<p>Error: {{ productsResource.error()?.message }}</p>
} @else {
<ul>
@for (product of productsResource.value(); track $index) {
<li>{{ product.productName }}</li>
}
</ul>
}
isLoading(): boolean signal indicating whether this resource is loading a new value (or reloading the existing one).error():Errorsignal, when in the error state, this returns the last known error from theResourceerror()?.message: The actual error message when in error statevalue(): The current value of theResource
Remember: these are all signals: when the value changes, the template will update!