Mastering Angular 19’s Model Input Signals: A Simpler Alternative to @Input() and @Output()

Balram Chavan
4 min readJan 2, 2025

Angular 19 introduces a powerful feature that simplifies the traditional @Input() and @Output() decorators: Model Input signals. This blog explores this feature, showing how it reduces boilerplate code, enhances reactivity, and improves readability in your Angular applications.

Check out my YouTube video where I demonstrate this feature with hands-on examples and compare it with traditional approaches.

Why Model Input Signals?

Traditionally, Angular components use @Input() to receive data and @Output() to emit changes. While effective, this approach can become verbose, especially when managing two-way binding. The new Model Input signals streamline this process by replacing these decorators with a single reactive model, making the interaction between parent and child components more intuitive.

Traditional Approach with @Input() and @Output()

Let’s first review the traditional implementation using @Input() and @Output() decorators.

QuantityComponent

import { Component, EventEmitter, Input, Output } from '@angular/core';

@Component({
selector: 'app-quantity',
template: `
<h3>Quantity</h3>
<button (click)="decrement()">-</button>
<span> {{ quantity }} </span>
<button (click)="increment()">+</button>
`,
})
export class QuantityComponent {
@Input() quantity: number = 0;
@Output() quantityChange = new EventEmitter<number>();

public increment(): void {
this.quantity = this.quantity + 1;
this.quantityChange.emit(this.quantity);
}

public decrement(): void {
if (this.quantity > 0) {
this.quantity = this.quantity - 1;
this.quantityChange.emit(this.quantity);
}
}
}

AppComponent

@Component({
selector: 'app-root',
template: `
<h1>Traditional @Input() and @Output()</h1>
<h3>Quantity: {{ quantity }}</h3>
<app-quantity [(quantity)]="quantity"></app-quantity>
`,
})
export class AppComponent {
public quantity = 10;
}

While this approach works well, it requires managing EventEmitter instances and explicitly emitting changes. Now, let’s explore how Model Input signals simplify this.

Simplified Approach with Model Input Signals

In Angular 19, the new model() function creates a signal for data binding, eliminating the need for @Input() and @Output().

QuantityComponent

import {
ChangeDetectionStrategy,
Component,
model
} from '@angular/core';

@Component({
selector: 'app-quantity',
template: `
<h3>Quantity</h3>
<button (click)="decrement()">-</button>
<span> {{ quantity() }} </span>
<button (click)="increment()">+</button>
`,
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class QuantityComponent {
public quantity = model(0);

public increment(): void {
this.quantity.update(oldQuantity => oldQuantity + 1);
}

public decrement(): void {
this.quantity.update(oldQuantity => Math.max(0, oldQuantity - 1));
}
}

Key Improvements with Model Input Signals

  1. Reduced Boilerplate:
  • No need for @Input() and @Output() decorators.
  • Eliminates EventEmitter management.

2. Enhanced Reactivity:

  • The model() function provides a reactive wrapper around the value.
  • Updates propagate automatically with update().

3. Improved Readability:

  • Code is more concise and easier to follow.
  • The interaction between parent and child is straightforward.

Exploring Angular 19’s Model Input Signals: Simplifying @Input() and @Output() Decorators

Angular 19 introduces a powerful feature that simplifies the traditional @Input() and @Output() decorators: Model Input signals. This blog explores this feature, showing how it reduces boilerplate code, enhances reactivity, and improves readability in your Angular applications.

Why Model Input Signals?

Traditionally, Angular components use @Input() to receive data and @Output() to emit changes. While effective, this approach can become verbose, especially when managing two-way binding. The new Model Input signals streamline this process by replacing these decorators with a single reactive model, making the interaction between parent and child components more intuitive.

Traditional Approach with @Input() and @Output()

Let’s first review the traditional implementation using @Input() and @Output() decorators.

QuantityComponent

import { Component, EventEmitter, Input, Output } from '@angular/core';
@Component({
selector: 'app-quantity',
template: `
<h3>Quantity</h3>
<button (click)="decrement()">-</button>
<span> {{ quantity }} </span>
<button (click)="increment()">+</button>
`,
})
export class QuantityComponent {
@Input() quantity: number = 0;
@Output() quantityChange = new EventEmitter<number>();
public increment(): void {
this.quantity = this.quantity + 1;
this.quantityChange.emit(this.quantity);
}
public decrement(): void {
if (this.quantity > 0) {
this.quantity = this.quantity - 1;
this.quantityChange.emit(this.quantity);
}
}
}

AppComponent

@Component({
selector: 'app-root',
template: `
<h1>Traditional @Input() and @Output()</h1>
<h3>Quantity: {{ quantity }}</h3>
<app-quantity [(quantity)]="quantity"></app-quantity>
`,
})
export class AppComponent {
public quantity = 10;
}

While this approach works well, it requires managing EventEmitter instances and explicitly emitting changes. Now, let’s explore how Model Input signals simplify this.

Simplified Approach with Model Input Signals

In Angular 19, the new model() function creates a signal for data binding, eliminating the need for @Input() and @Output().

QuantityComponent

import {
ChangeDetectionStrategy,
Component,
model
} from '@angular/core';
@Component({
selector: 'app-quantity',
template: `
<h3>Quantity</h3>
<button (click)="decrement()">-</button>
<span> {{ quantity() }} </span>
<button (click)="increment()">+</button>
`,
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class QuantityComponent {
public quantity = model(0);
public increment(): void {
this.quantity.update(oldQuantity => oldQuantity + 1);
}
public decrement(): void {
this.quantity.update(oldQuantity => Math.max(0, oldQuantity - 1));
}
}

AppComponent

import { Component } from '@angular/core';
import { QuantityComponent } from './quantity/quantity.component';
@Component({
selector: 'app-root',
imports: [QuantityComponent],
template: `
<h1>Model Input</h1>
<h3>Quantity: {{ quantity }}</h3>
<app-quantity [(quantity)]="quantity"></app-quantity>
`,
})
export class AppComponent {
public quantity = 10;
}

Key Improvements with Model Input Signals

  1. Reduced Boilerplate:
  • No need for @Input() and @Output() decorators.
  • Eliminates EventEmitter management.

2. Enhanced Reactivity:

  • The model() function provides a reactive wrapper around the value.
  • Updates propagate automatically with update().

3. Improved Readability:

  • Code is more concise and easier to follow.
  • The interaction between parent and child is straightforward.

Summary

Angular 19’s Model Input signals are a game-changer for managing two-way binding. By replacing @Input() and @Output() decorators with a reactive model, they simplify code and improve reactivity. Start experimenting with this feature today and experience how it transforms your Angular development workflow!

Cheers!

--

--

Balram Chavan
Balram Chavan

Written by Balram Chavan

Google Developer Expert Angular | Architect | Author | http://www.youtube.com/@angularcloud

No responses yet