How Do Signals Interact with Change Detection in Angular?
What is it?
Angular Signals integrate directly with Angular's change-detection system.
When a template reads a Signal, Angular tracks that dependency. When the Signal's value changes, Angular knows that the view consuming that Signal needs to be refreshed.
In simple terms:
Signal changes → Angular is notified → dependent view is refreshed → DOM is updated if necessary.
Signal value changes
↓
Angular knows which view uses the Signal
↓
That view is marked for refresh
↓
Change detection/rendering runs
↓
Template reads the new Signal value
↓
DOM updated
This is more precise than relying only on broad asynchronous-event detection.
Full Simple Sample Program
counter.ts
import {
ChangeDetectionStrategy,
Component,
signal
} from '@angular/core';
@Component({
selector: 'app-counter',
standalone: true,
templateUrl: './counter.html',
changeDetection: ChangeDetectionStrategy.OnPush
})
export class Counter {
count = signal(0);
increment(): void {
this.count.update(value => value + 1);
}
decrement(): void {
this.count.update(value => value - 1);
}
}
counter.html
<h2>Signal Change Detection</h2>
<p>Count: {{ count() }}</p>
<button (click)="increment()">
Increment
</button>
<button (click)="decrement()">
Decrement
</button>
Program Explanation
Step 1: Create the Signal
count = signal(0);
This creates a writable Signal whose initial value is:
0
Unlike a normal property:
count = 0;
a Signal is reactive state that can notify Angular when its value changes.
Step 2: Template Reads the Signal
<p>Count: {{ count() }}</p>
Notice:
count()
Signals are read by calling them like a function.
When Angular renders this template, it knows:
This view depends on
↓
count
Conceptually:
Counter Component View
│
│ reads
↓
count Signal
Angular tracks this relationship.
Step 3: User Clicks Increment
The user clicks:
<button (click)="increment()">
Angular executes:
increment(): void {
this.count.update(value => value + 1);
}
Suppose:
count = 0
It becomes:
count = 1
Step 4: Signal Notifies Angular
Because count is a Signal:
count = signal(0);
Angular knows that its value changed.
More importantly, Angular knows that the template consumed this Signal:
{{ count() }}
Therefore, the dependent view needs refreshing.
count
│
│ changed
↓
Angular
│
│ knows Counter view depends on count
↓
Counter view marked for refresh
Step 5: Angular Refreshes the View
Angular runs the necessary change-detection/rendering work.
The template is evaluated again:
{{ count() }}
Previously:
0
Now:
1
Angular updates the DOM.
Before:
Count: 0
After:
Count: 1
Complete Flow
User clicks Increment
↓
increment()
↓
count.update(...)
↓
Signal changes
0 → 1
↓
Signal notifies Angular
↓
Angular knows which view consumed count
↓
View marked for refresh
↓
Change detection/rendering
↓
Template reads
count()
↓
New value = 1
↓
DOM updated
↓
Count: 1
Normal Property vs Signal
This distinction is important.
Normal Property
count = 0;
increment(): void {
this.count++;
}
count itself is simply a JavaScript property.
It does not independently provide Angular with reactive dependency information.
Angular needs its normal change-detection notification mechanisms to know that synchronization work should occur.
Signal
count = signal(0);
increment(): void {
this.count.update(value => value + 1);
}
The Signal participates directly in Angular's reactive system.
Angular can track:
Which Signal changed?
+
Which views consume that Signal?
That makes Signals particularly useful for efficient UI updates.
Signals with OnPush
Signals work especially well with:
ChangeDetectionStrategy.OnPush
Consider:
@Component({
selector: 'app-counter',
standalone: true,
template: `
<p>{{ count() }}</p>
`,
changeDetection: ChangeDetectionStrategy.OnPush
})
export class Counter {
count = signal(0);
}
You might think:
"Because this component uses
OnPush, how will Angular know whencountchanges?"
The Signal handles that.
When:
this.count.set(10);
or:
this.count.update(x => x + 1);
changes a Signal that the template reads, Angular marks the consuming view so that it can be refreshed.
Therefore:
OnPush Component
+
Signal
↓
Signal changes
↓
Dependent view marked
↓
View refreshed
You generally don't need to manually call:
changeDetectorRef.markForCheck();
just because a template-consumed Signal changed.
Example with Multiple Signals
Suppose:
name = signal('Syed');
city = signal('Erode');
count = signal(0);
Template:
<h2>{{ name() }}</h2>
<p>{{ city() }}</p>
<p>{{ count() }}</p>
Angular tracks the Signal dependencies of the view.
If:
count.set(10);
changes count, Angular knows that a consumer of count needs refreshing.
Conceptually:
name Signal ─────┐
│
city Signal ─────┼──► Component View
│
count Signal ────┘
↑
│
changed
This dependency tracking is one of the major advantages of Signals.
Signals with computed()
Signals also work with computed Signals.
Example
import { Component, computed, signal } from '@angular/core';
@Component({
selector: 'app-price',
standalone: true,
template: `
<p>Price: {{ price() }}</p>
<p>Quantity: {{ quantity() }}</p>
<h3>Total: {{ total() }}</h3>
<button (click)="increaseQuantity()">
Add
</button>
`
})
export class Price {
price = signal(100);
quantity = signal(1);
total = computed(() =>
this.price() * this.quantity()
);
increaseQuantity(): void {
this.quantity.update(q => q + 1);
}
}
Here:
total = computed(() =>
this.price() * this.quantity()
);
depends on:
price
quantity
So:
quantity changes
↓
computed total becomes invalid
↓
Template needs updated total
↓
Angular refreshes view
↓
New total displayed
For example:
price = 100
quantity = 1
total = 100
After clicking:
quantity = 2
then:
total = 200
The UI updates automatically.
Signals and effect()
effect() is different.
An effect executes side-effect code when Signals it reads change.
For example:
count = signal(0);
constructor() {
effect(() => {
console.log('Count:', this.count());
});
}
When:
count.set(5);
the effect will run according to Angular's effect scheduling.
count changes
↓
effect depends on count
↓
effect executes
↓
Console: Count: 5
However, don't think of effect() as the normal way to update the UI.
For UI rendering, simply use the Signal in the template:
{{ count() }}
Angular handles the view dependency automatically.
Signals vs Zone.js
This is an important modern Angular concept.
Traditionally, Angular applications commonly used Zone.js to know that asynchronous activity occurred.
Click / Timer / Async operation
↓
Zone.js
↓
Angular knows synchronization may be needed
↓
Change Detection
Signals provide more explicit information:
Signal changes
↓
Angular knows reactive state changed
↓
Angular knows which view consumes it
↓
View can be refreshed
Signals are therefore an important part of Angular's modern zoneless-compatible change-detection model.
But don't say:
"Signals completely replace change detection."
That is incorrect.
Signals work with change detection/rendering; they help Angular know what reactive state changed and which consumers may require refreshing.
Signals Do Not Directly Manipulate the DOM
Another important distinction:
When you execute:
count.set(10);
the Signal itself does not do:
Find <p>
Replace text with 10
Instead:
count.set(10)
↓
Signal state changes
↓
Angular notified
↓
View scheduled/marked
↓
Angular rendering/change detection
↓
DOM updated
So Signals and change detection cooperate.
Key Points
- Signals provide Angular with reactive state-change notifications.
- Angular tracks Signals that are read by templates.
- When a template-consumed Signal changes, Angular marks the appropriate view for refresh.
- Angular then performs the necessary change-detection/rendering work.
- Signals work particularly well with
OnPush. - You usually don't need
markForCheck()when a Signal consumed by the template changes. computed()creates derived reactive state from other Signals.effect()performs side effects when its Signal dependencies change.- Signals don't directly manipulate the DOM.
- Signals are an important part of Angular's modern zoneless-compatible architecture.
Interview Questions and Answers
1. How do Signals interact with Angular change detection?
When a template reads a Signal, Angular tracks that Signal as a dependency of the view. When the Signal changes, Angular marks the consuming view so it can be refreshed during rendering/change detection.
2. Do Signals trigger DOM updates directly?
No.
Signals notify Angular about reactive state changes. Angular's rendering/change-detection system performs the actual DOM updates.
3. Do Signals work with OnPush?
Yes. They work particularly well together.
If an OnPush component's template reads a Signal, changing that Signal marks the consuming view for refresh.
count = signal(0);
count.set(10);
4. Do we need markForCheck() when using Signals?
Normally, not for a Signal that is read by the template. Angular already tracks that dependency and marks the view when the Signal changes.
5. Does Angular know which component uses a Signal?
Angular tracks Signal reads in reactive consumers such as template views. This allows it to know which views depend on changed reactive state.
6. Do Signals replace Angular change detection?
No.
Signals integrate with change detection. They provide precise reactive notifications, while Angular's rendering/change-detection system still performs the view update.
7. What happens when signal.set() is called?
count.set(10);
The Signal value changes, its consumers are notified, affected views can be marked for refresh, and Angular updates the rendered UI as necessary.
8. What is the difference between a normal property and a Signal for change detection?
count = 0;
is an ordinary JavaScript property and does not itself provide reactive notifications.
count = signal(0);
is reactive state that tracks consumers and notifies Angular when its value changes.
Interview Quick Answer
When an Angular template reads a Signal, Angular tracks that Signal as a dependency of the view. When the Signal changes, Angular marks the consuming view for refresh. Angular then runs the necessary rendering/change-detection work and updates the DOM. This works especially well with
OnPushand supports Angular's modern zoneless-compatible change-detection model.