How to Simplify Angular Component Communication with model() and Signals
I was very busy in September, but now I'm working on creating a workshop about signals and developing ideas on how signals can simplify our code. Recently, I've been using @Input() and @Output for component communication. However, Angular 17 introduced input signals for communication, which work well but the Angular Team is also working on model
signals, a great alternative for bidirectional communication between components with less code. Let's explore it!
Note: The
model
is in developer preview but we can play with it today!
But you might ask, why do I need another way to communicate if I have input and output? That's a good question. Let me show you with a real-world example.
We want to show a list of movies and rate each one. To make it easy to rate my movies, I will use Kendo UI for Angular Rating and implementing input/output signals for bidirectional data binding, then migrate to model(), an easy approach.
Let’s do it!
Setup Project
I will use the latest version of Angular. The easiest way is by running the following command in the terminal. Answer yes for default questions.
npx -p @angular/cli signal-model-rating-kendo
Need to install the following packages:
@angular/cli@18.2.6
Ok to proceed? (y) y
.....
Next, move to the project folder cd signal-model-rating-kendo
and use Kendo UI Schematics to install Kendo Input Rating.
ng add @progress/kendo-angular-inputs
✔ Determining Package Manager
› Using package manager: npm
✔ Searching for compatible package version
› Found compatible package version: @progress/kendo-angular-inputs@16.10.0.
✔ Loading package information from registry
✔ Confirming installation
✔ Installing package
UPDATE package.json (1656 bytes)
UPDATE angular.json (3071 bytes)
The Rating Using Input/Output Signals
First, let's create a custom component to rate movies using Kendo UI's kendo-rating
component. We'll bind the movie's rating using input/output signals to demonstrate bidirectional data binding.
ng g c movie-rating
signal-model-rating-kendo % ng g c componentes/movie-rating
CREATE src/app/componentes/movie-rating/movie-rating.component.scss (0 bytes)
CREATE src/app/componentes/movie-rating/movie-rating.component.html (27 bytes)
CREATE src/app/componentes/movie-rating/movie-rating.component.spec.ts (628 bytes)
CREATE src/app/componentes/movie-rating/movie-rating.component.ts (258 bytes)
Let’s use the kendo-rating
component and set the value by using the input signal that will receive and emits the new rating value using the output when user updates the rating
Open the movie-rating.component.ts
file. First, import the RatingComponent from @progress/kendo-angular-inputs
. Next, declare the rating
property as input to get the value and ratingChange
as output to emit the event.
import { Component, input, output } from '@angular/core';
import { RatingComponent } from '@progress/kendo-angular-inputs';
@Component({
selector: 'app-movie-rating',
standalone: true,
imports: [RatingComponent],
templateUrl: './movie-rating.component.html',
styleUrl: './movie-rating.component.scss',
})
export class MovieRatingComponent {
rating = input(4);
ratingChange = output<number>();
onRatingChange(newRating: number) {
this.ratingChange.emit(newRating);
}
}
In the template, we use the kendo-rating
component, bind the value
property with the rating
() signal, and listen to the valueChange
event to trigger the onRatingChange
event. The final code looks like:
<kendo-rating [value]="rating()" (valueChange)="onRatingChange($event)">
</kendo-rating>
Perfect, now it's time to connect the movie list with our movie-rating component. Let's do it!
Communicate The Movie Rating
Open the app.component.ts
, declare a type Movie
with the following properties:
interface Movie {
title: string;
rating: number;
}
We create a mock list of movies to use in the component.
movies = signal<Movie[]>([
{ title: 'Inception', rating: 4 },
{ title: 'Interstellar', rating: 5 },
{ title: 'The Dark Knight', rating: 5 },
{ title: 'Dunkirk', rating: 3 },
]);
The most important step is to import the MovieRatingComponent
into the app.component.ts
. The final code looks like:
import { Component, signal } from '@angular/core';
import { RouterOutlet this } from '@angular/router';
import { MovieRatingComponent } from './componentes/movie-rating/movie-rating.component';
interface Movie {
title: string;
rating: number;
}
@Component({
selector: 'app-root',
standalone: true,
imports: [RouterOutlet, MovieRatingComponent],
templateUrl: './app.component.html',
styleUrl: './app.component.scss',
})
export class AppComponent {
movies = signal<Movie[]>([
{ title: 'Inception', rating: 4 },
{ title: 'Interstellar', rating: 5 },
{ title: 'The Dark Knight', rating: 5 },
{ title: 'Dunkirk', rating: 3 },
]);
}
In the app.component.html we use the @for to iterate over movies list, show the movie title, rate and the movie-rating component to allow the user vote.
Using [(rating)]
two way binding we can get the value when it emits.
@for (movie of movies(); track movie) {
<h3>{{ movie.title }}</h3>
<app-movie-rating [(rating)]="movie.rating"></app-movie-rating>
<p>Current rating: {{ movie.rating }} stars</p>
}
Save changes and run ng serve
to have our app show the list of movies and their ratings. Go to http://localhost:4200
in your browser, and you'll see the list of movies with the rating feature working.
We successfully implemented bidirectional binding using the input/output signals, but this requires manually handling the input and emitting the output.
If I told you we can do it with less code using model()
? 😎
Moving to Model
Before we start, why do I want to move to model()
? Well, model()
automatically creates both an input and an output, making it easier to manage two-way bindings without manually emitting events. So, with a small change in MovieRatingComponent
to use model()
instead of the input/output, we can achieve the same result without using output
signals.
Let’s do it, first import model
from @angular/core
import { Component, model } from '@angular/core';
import { RatingComponent } from '@progress/kendo-angular-inputs';
Change the type of input
to model
. The model is a writable signal, so we can update it using the update
method.
import { Component, model } from '@angular/core';
import { RatingComponent } from '@progress/kendo-angular-inputs';
@Component({
selector: 'app-movie-rating',
standalone: true,
imports: [RatingComponent],
templateUrl: './movie-rating.component.html',
styleUrl: './movie-rating.component.scss',
})
export class MovieRatingComponent {
rating = model(4);
onRatingChange(newRating: number) {
this.rating.update(() => newRating);
}
}
It automatically emits the value when the signal changes. We no longer need the output
for bidirectional communication because the model creates an output for us with the change appended to the name. In our case, it is ratingChange
.
<app-movie-rating [(rating)]="movie.rating" (ratingChange)="onRatingChange($event)"></app-movie-rating>
Open http://localhost:4200
in your browser to see the list of movies with their ratings. It works the same way with less code! Yeah!
WAIT A SECOND! Want to dive even deeper into Angular Signals?
I highly recommend the Angular Signals Masterclass eBook by Kevin Kreuzer. It’s the best way to learn everything about Signals in one place, with clear examples to guide you.
Grab a free preview to get a taste of the awesome insights inside! And when you're ready to go all in, unlock the full version for the complete Signals deep dive.
Now you know my secret to learning about Signals. 😎 Let’s continue!
Ok but Why we have input and model are not the same ?
Well, you can use input()
and model()
to communicate with your components, but the magic of model()
is that it creates an input and an output for our property.
Before pick one keep in mind the following points:
Model()
: This automatically creates both an input and an output for a property, allowing for bidirectional data binding with far less code. It’s a writable signal, meaning it can be updated and emits changes to subscribers without needing manual event handling.Input/Output Signals
: These require manual handling of input values and emitting output events to get two-way binding.
Remember: The model()
simplifies communication but doesn’t support transformation functions, which might be necessary in more complex scenarios. For these cases, using input/output
signals may be a better option.
Recap
We learned how to implement bidirectional data binding using input/output signals in Angular and then saw how the new model()
feature can simplify this process with less code.
We started by building a movie rating component using input/output signals, where we manually handled data binding and event emissions. While this approach works well, it requires more boilerplate code to manage the communication between components, then, we switched to the model()
approach, which simplifies the code by automatically handling both input and output bindings.
Happy coding!
Subscribe to my newsletter
Read articles from Dany Paredes directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
Dany Paredes
Dany Paredes
I'm passionate about front-end development, specializing in building UI libraries and working with technologies like Angular, NgRx, Accessibility, and micro-frontends. In my free time, I enjoy writing content for the Google Dev Library, This Is Angular Community, Kendo UI, and sharing insights here.