For years, Angular developers have relied on @Input() and @Output() decorators for parent-child communication. While powerful, they often added boilerplate and confusion, especially in larger apps.
With Angular 20, things are changing dramatically. 🎉
The framework now embraces native Signals and introduces the model() API for cleaner, more reactive state flow.
🔴 Why Move Away from @Input & @Output?
- Too much ceremony: separate properties for input/output.
- Easy to create spaghetti state flow in big projects.
- Not aligned with Angular’s new reactive primitives (Signals).
🟢 Angular 20: Inputs & Outputs with Signals
Instead of using decorators, Angular 20 provides functions for defining component bindings:
- input() → replaces @Input()
- output() → replaces @Output()
- model() → combines input + output into a single two-way binding
📝 Code Examples
🟢 Old Way (Angular 19 with @Input and @Output)
// child.component.ts
import { Component, Input, Output, EventEmitter } from '@angular/core';
@Component({
selector: 'app-child',
template: `
<p>{{ title }}</p>
<button (click)="notify()">Notify Parent</button>
`
})
export class ChildComponent {
@Input() title!: string;
@Output() notifyParent = new EventEmitter<string>();
notify() {
this.notifyParent.emit('Hello from Child!');
}
}
// parent.component.ts
@Component({
selector: 'app-parent',
template: `
<app-child
[title]="parentTitle"
(notifyParent)="onNotify($event)">
</app-child>
`
})
export class ParentComponent {
parentTitle = 'Parent says Hi!';
onNotify(msg: string) {
console.log(msg);
}
}
🔵 New Way (Angular 20 with Signals)
// child.component.ts
import { Component, input, output } from '@angular/core';
@Component({
selector: 'app-child',
template: `
<p>{{ title() }}</p>
<button (click)="notify.emit('Hello from Child!')">Notify Parent</button>
`
})
export class ChildComponent {
title = input<string>();
notify = output<string>();
}
// parent.component.ts
@Component({
selector: 'app-parent',
template: `
<app-child
[title]="parentTitle"
(notify)="onNotify($event)">
</app-child>
`
})
export class ParentComponent {
parentTitle = 'Parent says Hi!';
onNotify(msg: string) {
console.log(msg);
}
}
💡 Notice how much cleaner and signal-friendly this new API looks!
🟣Two-Way Binding with model()
The real game-changer is the model() API, which fuses input + output into a single signal.
// child.component.ts
import { Component, model } from '@angular/core';
@Component({
selector: 'app-child',
template: `
<input [value]="title()" (input)="title.set($any($event.target).value)" />
<p>You typed: {{ title() }}</p>
`
})
export class ChildComponent {
title = model<string>(''); // two-way bound
}
// parent.component.ts
import { Component } from '@angular/core';
@Component({
selector: 'app-parent',
template: `
<h2>Parent Component</h2>
<app-child [(title)]="parentTitle"></app-child>
<p>Parent Value: {{ parentTitle }}</p>
`
})
export class ParentComponent {
parentTitle = 'Hello World';
}
💡 The model() API eliminates the need for separate @Input() and @Output(), giving us true two-way binding out of the box.
🌟 Why This Matters
- 🚀 Less boilerplate, more readable code
- 🔄 Seamless two-way binding with model()
- ⚡ Reactive-first architecture, aligning with Angular Signals
- 🧩 Better developer experience and performance
🏁 Conclusion
Angular 20 signals a major shift in how components talk to each other.
With input(), output(), and especially model(), we no longer need @Input() & @Output() decorators.
This isn’t just an API change—it’s a step toward a fully reactive Angular ecosystem.
👉 Stay tuned for more deep dives into Angular 20’s new features!
Top comments (0)