Parent-Child Component Communication in Angular
Understanding how components exchange data is essential when building Angular applications. This article focuses on the parent-child relationship, which is the most common communication pattern you will encounter. We will cover both directions of data flow: from parent to child, and from child back to parent.
Prerequisites
Before proceeding, you should be comfortable with:
- What an Angular component is
- How to create a component
To set the stage, let us define the parent-child relationship clearly. Imagine component P. Its template contains the selector of another component, say C. We then say C is a child of P, or alternatively, P is the parent of C. This containment in the template is the foundation of the relationship.
Now that the theory is clear, we can set up our hands-on example. We will work with two components: one named movie-dashboard which acts as the parent, and another named movie-table which functions as the child.
The CLI commands:
ng g c movie-dashboard
ng g c movie-table
Project Folder Structure:
The parent is highlighted with a yellow arrow 🟡
The child is highlighted with a green arrow 🟢
Let us start building. In the root app.component.html, we insert the following code:
<app-movie-dashboard></app-movie-dashboard>
Next, we place the following code inside movie-dashboard.component.html:
<p>movie-dashboard works!</p>
<app-movie-table></app-movie-table>
When you run the application, your browser at localhost:4200 will display the following:
Our development environment is now ready.
The goal is straightforward: the parent component holds an array of movie names (we will call it the movie list). We need to pass this list down to the child, which renders it in a table. In that table, the user can click a button to select a movie. This selection — a user action — is then sent back up to the parent (for example, for further processing).
Let us begin with the first leg of the journey: parent to child communication.
Passing Data with the Input Decorator
Data flows from parent to child by decorating a property with the Input decorator.
A quick reminder on decorators:
You have already encountered decorators when defining components, directives, pipes, and modules — specifically @Component, @Directive, @Pipe, and @NgModule. Those are Class Decorators, placed above a class definition.
The Input decorator, however, is a property decorator. What does that mean? Any variable declared directly inside a class (outside of methods) is a property. The Input decorator can only be applied on top of such a property — hence the name. Placing it there grants the property a special capability, akin to a superpower.
Which component should use it? Simple rule: the component that receives the data decorates its property. In our scenario, the movie-table child receives the data, so let us open movie-table.component.ts and paste the following code:
@Input()
movieList: Array<string> = [];
Line number 11 declares the movieList property. The @Input() decorator sits above it on line 10. Alternatively, you can place the decorator directly in front of the property, like this:
@Input() movieList: Array<string> = [];
Once decorated, the property gains the ability to hold data coming from the parent.
We now have a placeholder in the child ready to capture data. But how do we send it from the parent? Open movie-dashboard.component.ts and paste the code below:
myFavoriteMovies = [ 'Encanto',
'Spider-Man: No Way Home',
"Harry Potter and the Sorcerer's Stone" ];
Now, we update the corresponding template movie-dashboard.component.html. Remove the old content and paste the code below:
<p>movie-dashboard works!</p>
<app-movie-table [movieList]="myFavoriteMovies"></app-movie-table>
Take note of two key things here:
1️⃣ The input property from the child is placed inside square brackets, i.e. movieList within [].
2️⃣ The data intended for the child is assigned to that property using the equal sign and the parent's variable containing the data. In this case, myFavoriteMovies holds the data to be passed.
That completes the parent-to-child data flow. To see it in action, we need to render the data in the child. Let us paste the code below into movie-table.component.html:
<table>
<tr>
<th>Movie Name</th>
<th></th>
</tr>
<tr *ngFor="let movie of movieList">
<td>{{movie}}</td>
<td><input type="button" value="Select"></td>
</tr>
</table>
The output you will see is:
Excellent! The child component receives the array. Since movieList holds an array, we iterate over it using ngFor (if this directive is new to you, do check it out).
The first part of our task is complete. Now we shift direction: the user clicks a select button, and the chosen movie goes back to the parent.
Emitting Events with the Output Decorator
For child-to-parent communication, we use another property decorator: the Output decorator.
Let us update movie-table.component.ts with the code below:
@Output()
movieSelectedEventEmitter = new EventEmitter();
We introduced a new property, movieSelectedEventEmitter, decorated with @Output(). It is initialized as an EventEmitter instance. This gives the property a special capability: it can broadcast events or send data upward to the parent component.
We also need to listen for the button click. So we create a method that gets invoked when the click occurs:
movieSelected(selectedMovie: string) { }
For now, the method's body is empty — we will fill it shortly.
In the corresponding template file, we wire up the click event:
<td><input type="button" value="Select"
(click)="movieSelected(movie)"></td>
All events are written within parentheses (). Here, click is a built-in event. When this event fires, the function specified after the equal sign gets called. Now, let us go back and implement that method body:
this.movieSelectedEventEmitter.emit(selectedMovie);
What is happening? We invoke the emit method on the @Output() property — remember, it is an EventEmitter object — and pass in the data, which in this case is the selected movie name.
But the job is only half done. To actually receive that data in the parent, we need a few more lines. Open movie-dashboard.component.html and replace the old content with:
<p>movie-dashboard works!</p>
<app-movie-table
(movieSelectedEventEmitter)="selectedMovieToWatch($event)"
[movieList]="myFavoriteMovies">
</app-movie-table>
Here, the custom event movieSelectedEventEmitter is wrapped in round brackets, just like the built-in click event we used earlier. We bind it to a method that should execute when the event fires. This is the same principle as the click handler — the only variance being that click is a standard event while this one is user-defined.
Let us define that method. In movie-dashboard.component.ts, add:
selectedMovieToWatch(data: string) {
debugger;
alert(data);
}
In this method, the data parameter carries the value emitted from the child. That is all there is to it. Now let us see the whole flow. I clicked the second item in the list (really):
That brings us to the conclusion of this walkthrough.
Key Takeaways
For @Input():
1️⃣ Decorate a property in the child component with @Input()
@Input()
movieList: Array<string> = [];
2️⃣ In the parent component, where the child selector is placed, bind the same property using Square Brackets [] and supply the value
[movieList]="myFavoriteMovies"
For @Output():
1️⃣ Decorate a property in the child component with @Output() and initialize it with an EventEmitter instance
@Output()
movieSelectedEventEmitter = new EventEmitter();
2️⃣ In the parent, listen to the event and attach it to a handler method
(movieSelectedEventEmitter)="selectedMovieToWatch($event)"
Thanks for reading through this post.
If you found it useful, feel free to like ❤️ share 💞 and leave a comment 🧡.
There are more Angular topics on the way.
Stay tuned for updates.
I also share content on Twitter about Angular JavaScript TypeScript CSS.
Hope to connect with you there too 😃
Cheers!!!
Happy Coding
