This content originally appeared on DEV Community and was authored by Mridu Dixit
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!
This content originally appeared on DEV Community and was authored by Mridu Dixit