Signal-Based Communication (Modern Way)
Signals are the future of Angular component communication. Combine input(), output(), model(), and service signals for a fully reactive, boilerplate-free codebase.
Signals aren't just for local state โ they transform how components communicate. The combination of input(), output(), model(), and service signals creates a fully reactive communication system with minimal boilerplate.
"Signals se communication = apna walkie-talkie โ simple, fast, no subscribe."
The signal communication stack:
- input() โ @Input as a signal (parent โ child)
- output() โ @Output as an Observable (child โ parent)
- model() โ Two-way binding with signals (โ)
- Service with signal() โ siblings / deeply nested / global state
// The signal communication stack in action
@Component({...})
export class ChildComponent {
// Signal input โ read with name()
name = input.required<string>();
// Signal output โ emit events
select = output<Biryani>();
// Model โ two-way binding
quantity = model(1);
}
Current status: model() is stable. input() and output() are available in Angular 17.3+ (input() is stable, output() is stable). Use them in new projects!
input() is the signal-based replacement for @Input() decorator. It's available in Angular 17.3+.
import { Component, input } from '@angular/core';
@Component({...})
export class BiryaniCardComponent {
// Required input โ parent MUST provide
name = input.required<string>();
// Optional input with default
price = input(0);
// Optional with transform
category = input('Non-Veg', { alias: 'cat' });
// Read the value like any signal
formattedPrice = computed(() => `โน${this.price()}`);
}
"input() = @Input ka signal version โ future standard."
Benefits over @Input:
- Value is a signal โ use in computed(), effect(), model()
- No
!non-null assertion needed - No
ngOnChangesneeded โ use effect() or computed() - Same template syntax:
<app-card [name]="value"> input.required()โ compile error if parent doesn't provide
// OLD: need ngOnChanges to detect @Input changes
@Input() name = '';
ngOnChanges(changes: SimpleChanges) {
if (changes['name']) { /* react */ }
}
// NEW: just use computed or effect
name = input.required<string>();
greeting = computed(() => `Hello ${this.name()}!`); // Auto-reactive!
effect(() => console.log('Name changed:', this.name())); // Auto-tracked!output() is the signal-based replacement for @Output() decorator. Available in Angular 17.3+.
import { Component, output } from '@angular/core';
@Component({...})
export class BiryaniCardComponent {
// Signal output โ emits values
orderPlaced = output<Order>();
favToggled = output<boolean>();
placeOrder() {
// .emit() works just like EventEmitter
this.orderPlaced.emit({ name: this.name(), qty: 1, price: this.price() });
}
toggleFav() {
this.favToggled.emit(!this.isFav);
}
}
// Parent uses the same syntax:
// <app-card (orderPlaced)="onOrder($event)" (favToggled)="onFav($event)">
"output() = @Output ka modern version โ same EventEmitter API under the hood, but cleaner."
Benefits over @Output:
- No
= new EventEmitter()boilerplate - TypeScript infers the type โ no generic needed on right side
- Same template binding syntax โ
(orderPlaced)="handler()" - Works with
outputFromObservable()for RxJS-to-output conversion
// OLD
@Output() orderPlaced = new EventEmitter<Order>();
// NEW โ same result, less code
orderPlaced = output<Order>();
// Advanced: output from RxJS Observable
import { outputFromObservable } from '@angular/core/rxjs-interop';
results$ = outputFromObservable(this.searchResults$.pipe(debounceTime(300)));Here's a complete example combining all signal communication patterns:
// โโโ SERVICE (shared state) โโโ
@Injectable({ providedIn: 'root' })
export class BiryaniService {
// Signal for shared state
private selectedItems = signal<Biryani[]>([]);
readonly items = this.selectedItems.asReadonly();
selectItem(item: Biryani) {
this.selectedItems.update(items => [...items, item]);
}
clearSelection() {
this.selectedItems.set([]);
}
}
// โโโ CHILD COMPONENT (input + output) โโโ
@Component({
selector: 'app-biryani-item',
template: `
<div class="item" [class.selected]="selected()">
<h3>{{ biryani().name }}</h3>
<p>โน{{ biryani().price }}</p>
<input [model]="quantity" type="number" min="1">
<button (click)="addToCart()">๐ Add</button>
</div>
`
})
export class BiryaniItemComponent {
// Signal inputs
biryani = input.required<Biryani>();
selected = input(false);
// Model for two-way
quantity = model(1);
// Output
add = output<{ biryani: Biryani; quantity: number }>();
addToCart() {
this.add.emit({ biryani: this.biryani(), quantity: this.quantity() });
this.quantity.set(1); // Reset
}
}
// โโโ PARENT COMPONENT โโโ
@Component({
template: `
<app-biryani-item
[biryani]="currentBiryani"
[(quantity)]="currentQty"
(add)="onAddToCart($event)" />
<p>Cart items: {{ biryaniService.items().length }}</p>
`
})
export class ParentComponent {
currentBiryani = signal({ name: 'Hyderabadi', price: 250 });
currentQty = signal(1);
biryaniService = inject(BiryaniService);
onAddToCart(event: { biryani: Biryani; quantity: number }) {
this.biryaniService.selectItem(event.biryani);
}
}
"Mix of signals + input/output/model() = current best practice for new Angular apps."
A quick decision guide for signal-based communication:
| Scenario | Now (Angular 17.3+) | Notes |
|---|---|---|
| Parent โ Child (data) | input() | input.required() for mandatory |
| Child โ Parent (event) | output() | Clean EventEmitter replacement |
| Two-way binding | model() | Stable, recommended |
| Siblings / Deep | Service with signal() | asReadonly() for encapsulation |
| Local component state | signal() | Simple and reactive |
| Derived state | computed() | Cached, auto-updating |
"Abhi mix use karo โ signals jahan possible, @Input/@Output jahan zaroori ho."
Migration path for existing apps:
- Start using
model()for all new two-way bindings - Use
output()for new output events (it's stable) - Use
input()for new inputs (stable in Angular 17.3+) - Convert service BehaviorSubjects to
signal() + asReadonly() - Keep old @Input/@Output working โ migrate component by component
Key Takeaways
- โ input() = signal-based @Input โ use computed()/effect() to react to changes
- โ output() = signal-based @Output โ cleaner EventEmitter
- โ model() for two-way binding โ stable, recommended for all new code
- โ Service with signal + asReadonly for sibling/deep communication
- โ Mix is okay โ use signals where possible, keep @Input/@Output as needed
- โ input() and output() are stable in Angular 17.3+
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