Writable Signals โ signal() and .set() .update()
Signals are Angular's reactive primitive for state. A signal wraps a value and notifies Angular when it changes โ enabling fine-grained reactivity without Zone.js.
Signals are Angular's built-in reactive primitive for state management, introduced in Angular 16.
A signal is a wrapper around a value that notifies consumers when the value changes. Think of it as a "reactive variable" โ when you change it, everything that reads it automatically knows.
"Signal = LED display board โ value badlo toh sabko pata chal jayega."
Why signals matter:
- Replaces BehaviorSubject for most component/service state
- Replaces ngOnChanges for detecting input changes (future)
- Replaces Zone.js โ no more automatic change detection scanning
- NOT RxJS โ no operators, no subscribe, no unsubscribe
- Signals are synchronous โ value is available immediately
// Old way: BehaviorSubject + subscribe + unsubscribe
private count$ = new BehaviorSubject(0);
count$.subscribe(v => this.count = v);
// New way: signal โ just a reactive variable
count = signal(0);
// Read anywhere, even in template:
// {{ count() }}
Key concept: Signals are functions. You call them to READ the value. You call .set() or .update() to WRITE the value.
Creating a signal is simple โ call signal() with an initial value.
import { signal } from '@angular/core';
count = signal(0); // number
name = signal('Hyderabadi Biryani'); // string
isReady = signal(false); // boolean
biryanis = signal<Biryani[]>([]); // array
user = signal<User | null>(null); // union type
"signal() = signal banao โ initial value do, TypeScript type automatically infer karega."
TypeScript inference:
- signal(0) โ Signal<number>
- signal('hello') โ Signal<string>
- signal({ name: 'Biryani' }) โ Signal<{ name: string }>
- For complex types, use generic: signal<Biryani[]>([])
Location matters โ where to declare signals:
- Inside components: at class field level (preferred) or in constructor
- Inside services: for shared state
- Inside directives: for UI state
@Component({...})
export class BiryaniComponent {
// Best practice: declare as class fields
biryaniName = signal('Hyderabadi Biryani');
price = signal(250);
rating = signal(4.5);
}Reading a signal is like calling a function โ just use parentheses.
const count = signal(0);
// READ โ call the signal like a function
console.log(count()); // 0
Writing uses .set() or .update():
// .set() โ replace the entire value
count.set(10); // count is now 10
// .update() โ transform based on previous value
count.update(val => val + 1); // count is now 11 (10 + 1)
// .set() for simple replacement
name.set('Chicken Biryani');
// .update() for increment/toggle/transform
isOpen.update(val => !val); // toggle boolean
indices.update(arr => [...arr, newId]); // add to array
.set() vs .update():
.set()= purana plate hata ke naya rakh do โ complete replacement.update()= purane mein kuch add karo โ transform based on previous
โ ๏ธ Important: For objects/arrays, you must create a NEW reference for change detection to fire:
items = signal(['Biryani', 'Kebabs']);
// โ WRONG โ mutating the same array
items().push('Curry'); // No change detection!
// โ
RIGHT โ create new array
items.update(arr => [...arr, 'Curry']);Signals in templates are just function calls โ use () just like in TypeScript.
@Component({
selector: 'app-biryani',
template: `
<h1>{{ biryaniName() }}</h1>
<p>Price: โน{{ price() }}</p>
@if (isAvailable()) {
<button (click)="order()">Order Now</button>
} @else {
<p class="sold-out">Sold Out!</p>
}
<div class="items">
@for (item of menu(); track item.id) {
<span>{{ item.name }}</span>
}
</div>
`
})
export class BiryaniComponent {
biryaniName = signal('Hyderabadi Biryani');
price = signal(250);
isAvailable = signal(true);
menu = signal<MenuItem[]>([]);
}
Angular tracks which signals are read in the template. When a signal changes, ONLY the parts of the template that read that signal re-render โ not the whole component!
"Template mein bhi () lagao โ signal hai function ki tarah. Angular track karta hai kaunsa signal kahan read ho raha hai."
This is called fine-grained reactivity โ instead of Zone.js scanning the whole component, Angular knows exactly which DOM nodes to update, on what signal change.
Why are signals better than plain class properties?
๐ก Signal vs Property โ The Key Difference
| Property | Signal |
|---|---|
| this.count = 5 | this.count.set(5) |
| Angular doesn't know it changed | Angular KNOWS it changed |
| Need ChangeDetectorRef | No ChangeDetectorRef needed |
| Zone.js scans entire tree | Only consumers of that signal update |
| Template: {{ count }} | Template: {{ count() }} |
"Property = notice board jo koi nahi dekhta. Signal = WhatsApp group โ change hua toh notification sabko milta hai."
Benefits in real projects:
- No more ChangeDetectorRef.detectChanges() hacks
- No more markForCheck() calls
- Better performance โ Angular only re-renders what changed
- Future: Zone.js becomes optional โ signals handle change detection
Zone.js vs Signals future: In Angular 18+, you can disable Zone.js with provideExperimentalZonelessChangeDetection(). Signals become essential โ without Zone.js, only signal changes trigger UI updates. This makes apps faster (no zone pollution) and gives you predictable change detection.
Key Takeaways
- โ Signal = reactive variable โ wraps a value, notifies consumers on change
- โ Create: const x = signal(initialValue) โ TypeScript infers type
- โ Read: x() โ call the signal as a function
- โ Write .set(): x.set(newValue) โ complete replacement
- โ Write .update(): x.update(prev => prev + 1) โ transform based on previous
- โ Templates: use () โ {{ x() }}, @if (x()), @for of x()
- โ Always create NEW reference for objects/arrays โ no mutation!
Want to track your progress?
Log in to save your place and pick up where you left off.
Progress track karna chahte ho?
Login karo apni progress save karne ke liye aur jahan chhoda tha wahan se shuru karo.
Login