Angular Parent Child Communication

Angular Parent-Child Communication

In this tutorial we’ll look at nested components in Angular and how to use them. Why nest components? It helps to organize the layout of the user interface in addition to allowing specific functionality to be packaged up and reused in other components. This means that a nested component is going to need a way to communicate with its parent or containing component. Components can nest several times if you like. You can almost think of multiple boxes with the largest box on the outside, and small boxes contained inside. Since each component is fully encapsulated, Angular makes use of Inputs and Outputs to create communication paths outside of its own boundaries.


Create A Component To Nest

To begin, let’s create a shared directory in the Angular app folder.
angular shared directory

Within this folder we can add the files we will use for the component we’ll build. It will be a thumb component that displays a number of thumbs up icons based on the rating a game has. We’ll need a thumb.component.html, thumb.component.css, and thumb.component.ts file for this.
nested component in shared directory

Generally, a nested component should make use of a template that manages a small part of a larger interface. The component should have a selector defined so that it can be nested in a containing component. In addition, it will usually have a means to communicate with its parent using @Input() and @Output decorators. For our purpose, we will create a component that displays thumbs and can be nested in the game-list.component.html template. Our structure looks a bit like this.
angular nested component diagram

Within the template for the thumb component, we can start with the following markup. It makes use of font-awesome to show thumbs-up symbols. In order for the component to display various numbers of thumbs, the parent needs to provide the rating number to the thumb component as an @Input(). Also, we’ll add the ability to raise events in the nested component using @Output() and event emitter.


thumb.component.html

<div class="crop"
     [style.width.px]="thumbWidth"
     [title]="rating">
    <div class="thumb-container">
        <span class="fa fa-thumbs-up"></span>
        <span class="fa fa-thumbs-up"></span>
        <span class="fa fa-thumbs-up"></span>
        <span class="fa fa-thumbs-up"></span>
        <span class="fa fa-thumbs-up"></span>
    </div>
</div>

The component will display 5 thumbs, but we can crop the component to display less than 5 thumbs based on the rating a game has. So we can see a couple of property bindings there, so now let’s define the thumb component and its bindings in the associated Typescript file.

The rating property is a number provided by the data in the games array. The thumbWidth is calculated based on the value of the rating. The way this happens is using the ngOnChanges() lifecycle hook.


thumb.component.ts

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

@Component({
    selector: 'game-thumb',
    templateUrl: './thumb.component.html',
    styleUrls: ['./thumb.component.css']
})
export class ThumbComponent implements OnChanges {
    rating = 3;
    thumbWidth: number;

    ngOnChanges(): void {
        this.thumbWidth = this.rating * 95 / 5;
    }
}

This CSS styling in the component style sheet will give us the cropping effect we are looking for.


thumb.component.css

.crop {
    overflow: hidden;
}

div {
    cursor: pointer;
}

span {
    margin: 2px;
}

.thumb-container {
    width: 100px;
}

Nested Component As Directive

In order to use the nested component, we first need to add it to the declarations of a module.


app.module.ts

import {NgModule} from '@angular/core';
import {BrowserModule} from '@angular/platform-browser';
import {FormsModule} from '@angular/forms';

import {AppComponent} from './app.component';
import {GameListComponent} from './games/game-list.component';
import {ThumbComponent} from './shared/thumb.component';

@NgModule({
    declarations: [
        AppComponent,
        GameListComponent,
        ThumbComponent
    ],
    imports: [
        BrowserModule,
        FormsModule
    ],
    bootstrap: [AppComponent]
})
export class AppModule {
}

game-list.component.html

Now we can dive into the parent component template and add the nested component using it’s selector. Since we have all the plumbing in place, look at how the IDE actually picks up on the new game-thumb component in an IntelliSense-like way.
angular intellisense for components

Note the highlighted line to show where the nested component is referenced.

<div class='card'>
    <div class='card-header'>
        {{pageTitle}}
    </div>
    <div class='card-body'>
        <div class="row">
            <div class="col-3">
                <input [(ngModel)]="listFilter" type="text" class="form-control" id="filterInput" placeholder="Type to filter">
            </div>
            <div class="col">
                <div *ngIf='listFilter' class="form-text text-muted">Filtered by: {{listFilter}}</div>
            </div>
        </div>
        <div class='table-responsive'>
            <table class='table' *ngIf='games && games.length'>
                <thead>
                <tr>
                    <th>
                        <button class='btn btn-primary'
                                (click)='toggleImage()'>
                            {{showImage ? 'Hide&nbsp;' : 'Show'}} Image
                        </button>
                    </th>
                    <th style="color:firebrick">Game</th>
                    <th>Part#</th>
                    <th>Release Date</th>
                    <th>Cost</th>
                    <th>5 Thumb Rating</th>
                </tr>
                </thead>
                <tbody>
                <tr *ngFor='let game of filteredGames'>
                    <td>
                        <img *ngIf='showImage'
                             [src]='game.imageUrl'
                             [title]='game.gameName'
                             [style.width.px]='imageWidth'
                             [style.margin.px]='imageMargin'>
                    </td>
                    <td>{{ game.gameName }}</td>
                    <td>{{ game.gameCode | lowercase }}</td>
                    <td>{{ game.releaseDate }}</td>
                    <td>{{ game.price | currency:'USD':'symbol':'1.2-2' }}</td>
                    <td><game-thumb></game-thumb></td>
                </tr>
                </tbody>
            </table>
        </div>
    </div>
    <div class="card-footer text-muted text-right">
        <small>Powered by Angular</small>
    </div>
</div>

At this point, the nested component seems to be working. It is not fully functional, as it simply displays 5 thumbs no matter what rating the game has. The reason for this is because ngOnChanges() is not firing since it only watches for changes on Input properties. We have not configured that yet, but will do so next.
angular component as directive example


Parent To Child Communication With @Input

In order for a nested component to receive input from its parent, it can expose a property to do so. This is done using the @Input() Decorator. This decorator can be used to decorate any property in the nested component’s class. In the markup below, we are specifying that we want the rating value passed into the nested component.


thumb.component.ts

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

@Component({
    selector: 'game-thumb',
    templateUrl: './thumb.component.html',
    styleUrls: ['./thumb.component.css']
})
export class ThumbComponent implements OnChanges {
    @Input() rating: number;
    thumbWidth: number;

    ngOnChanges(): void {
        this.thumbWidth = this.rating * 95 / 5;
    }
}

Now in the template of the parent component, Property Binding is used to pass data to the nested component. Note the use of square brackets in the highlighted code. The binding source is set to the data that the parent wants to send to the child. So this template is passing the game.thumbRating value to the thumb component.


game-list.component.html

<div class='card'>
    <div class='card-header'>
        {{pageTitle}}
    </div>
    <div class='card-body'>
        <div class="row">
            <div class="col-3">
                <input [(ngModel)]="listFilter" type="text" class="form-control" id="filterInput"
                       placeholder="Type to filter">
            </div>
            <div class="col">
                <div *ngIf='listFilter' class="form-text text-muted">Filtered by: {{listFilter}}</div>
            </div>
        </div>
        <div class='table-responsive'>
            <table class='table' *ngIf='games && games.length'>
                <thead>
                <tr>
                    <th>
                        <button class='btn btn-primary'
                                (click)='toggleImage()'>
                            {{showImage ? 'Hide&nbsp;' : 'Show'}} Image
                        </button>
                    </th>
                    <th style="color:firebrick">Game</th>
                    <th>Part#</th>
                    <th>Release Date</th>
                    <th>Cost</th>
                    <th>5 Thumb Rating</th>
                </tr>
                </thead>
                <tbody>
                <tr *ngFor='let game of filteredGames'>
                    <td>
                        <img *ngIf='showImage'
                             [src]='game.imageUrl'
                             [title]='game.gameName'
                             [style.width.px]='imageWidth'
                             [style.margin.px]='imageMargin'>
                    </td>
                    <td>{{ game.gameName }}</td>
                    <td>{{ game.gameCode | lowercase }}</td>
                    <td>{{ game.releaseDate }}</td>
                    <td>{{ game.price | currency:'USD':'symbol':'1.2-2' }}</td>
                    <td>
                        <game-thumb [rating]="game.thumbRating"></game-thumb>
                    </td>
                </tr>
                </tbody>
            </table>
        </div>
    </div>
    <div class="card-footer text-muted text-right">
        <small>Powered by Angular</small>
    </div>
</div>

Now the thumbs are dynamic! This is because the thumb component is receiving the correct value for the number of thumbs to display based on the rating.
angular input decorator binding


Communicating From Child To Parent With @Output

In the clip above, we now have communication happening from the parent to the child component. Now we want the ability to go in reverse. For example, if something is clicked in the child, we can pass that event up to the parent. This is done by raising an event in combination with the @Output() Decorator and the EventEmitter. In fact, the only way a nested component can pass data to its parent is with an Event. The data to pass is the Event Payload.

First off, we can specify that when the thumb component is clicked, the onClick() method will run.


thumb.component.html

<div class="crop"
     [style.width.px]="thumbWidth"
     [title]="rating"
     (click)="onClick()">
    <div class="thumb-container">
        <span class="fa fa-thumbs-up"></span>
        <span class="fa fa-thumbs-up"></span>
        <span class="fa fa-thumbs-up"></span>
        <span class="fa fa-thumbs-up"></span>
        <span class="fa fa-thumbs-up"></span>
    </div>
</div>

Then in the component, we have to define the onClick() method. In addition we set up the ratingClicked as an Event Property by using the @Output() decorator. This property emits an event using the EventEmitter.


thumb.component.ts

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

@Component({
    selector: 'game-thumb',
    templateUrl: './thumb.component.html',
    styleUrls: ['./thumb.component.css']
})
export class ThumbComponent implements OnChanges {
    @Input() rating: number;
    thumbWidth: number;
    @Output() ratingClicked: EventEmitter<string> =
        new EventEmitter<string>();

    ngOnChanges(): void {
        this.thumbWidth = this.rating * 95 / 5;
    }

    onClick(): void {
        this.ratingClicked.emit(`This game has a ${this.rating} thumb rating!`);
    }
}

Below is the containing component, where we can set up event binding. We bind to the event raised from the thumb component using event binding denoted by (ratingClicked)=’onRatingClicked($event)’. This means we are listening for the ratingClicked event raised from the thumb component. When that event triggers, we want to run the onRatingClicked() method with $event as the payload. This method will be defined in game-list.component.ts next.


game-list.component.html

<div class='card'>
    <div class='card-header'>
        {{pageTitle}}
    </div>
    <div class='card-body'>
        <div class="row">
            <div class="col-3">
                <input type="text" [(ngModel)]='listFilter' class="form-control" id="filterInput"
                       placeholder="Type to filter">
            </div>
            <div class="col">
                <div *ngIf='listFilter' class="form-text text-muted">Filtered by: {{listFilter}}</div>
            </div>
        </div>
        <div class='table-responsive'>
            <table class='table'
                   *ngIf='games && games.length'>
                <thead>
                <tr>
                    <th>
                        <button class='btn btn-primary'
                                (click)='toggleImage()'>
                            {{showImage ? 'Hide&nbsp;' : 'Show'}} Image
                        </button>
                    </th>
                    <th>Game</th>
                    <th>Part#</th>
                    <th>Release Date</th>
                    <th>Cost</th>
                    <th>5 Thumb Rating</th>
                </tr>
                </thead>
                <tbody>
                <tr *ngFor='let game of filteredGames'>
                    <td>
                        <img *ngIf='showImage'
                             [src]='game.imageUrl'
                             [title]='game.gameName'
                             [style.width.px]='imageWidth'
                             [style.margin.px]='imageMargin'>
                    </td>
                    <td>
                        <a [routerLink]="['/games', game.gameId]">
                            {{ game.gameName }}
                        </a>
                    </td>
                    <td>{{ game.gameCode | lowercase | convertToColon: '-' }}</td>
                    <td>{{ game.releaseDate }}</td>
                    <td>{{ game.price | currency:'USD':'symbol':'1.2-2' }}</td>
                    <td>
                        <game-thumb [rating]='game.thumbRating'
                                    (ratingClicked)='onRatingClicked($event)'>
                        </game-thumb>
                    </td>
                </tr>
                </tbody>
            </table>
        </div>
    </div>
    <div class="card-footer text-muted text-right">
        <small>Powered by Angular</small>
    </div>
</div>
<div *ngIf='errorMessage'
     class='alert alert-danger'>
    Error: {{ errorMessage }}
</div>

Here is the onRatingClicked() method which simply sets the page title to something new based on the payload of the event.


game-list.component.ts

import { Component, OnInit } from '@angular/core';
import { IGame } from './game';

@Component({
    selector: 'game-list',
    templateUrl: './game-list.component.html',
    styleUrls: ['./game-list.component.css']
})
export class GameListComponent {
    pageTitle = 'Dynamic! Game List';
    imageWidth = 45;
    imageMargin = 1;
    showImage = true;
    _listFilter = '';
    get listFilter(): string {
        return this._listFilter;
    }

    set listFilter(value: string) {
        this._listFilter = value;
        this.filteredGames = this.listFilter ? this.doFilter(this.listFilter) : this.games;
    }

    filteredGames: IGame[] = [];
    games: IGame[] = [...];

    constructor() {
        this.filteredGames = this.games;
        this.listFilter = '';
    }

    onRatingClicked(message: string): void {
        this.pageTitle = 'Game List: ' + message;
    }

    doFilter(filterBy: string): IGame[] {
        filterBy = filterBy.toLocaleLowerCase();
        return this.games.filter((game: IGame) =>
            game.gameName.toLocaleLowerCase().indexOf(filterBy) !== -1);
    }

    toggleImage(): void {
        this.showImage = !this.showImage;
    }
}

Looking Good!


Summary

Nested Component Key Points

  • A nested component is built following the same steps as any other component you might build in Angular.
  • The @Input() Decorator can be used on a nested component property any time it needs input data from it’s parent or containing component.
  • Any type of property can be decorated with the @Input() Decorator.
  • The @Output Decorator is used to decorate a nested component property for when it needs to raise events to pass back to the parent.
  • Only properties with the type of EventEmitter can be used with the @Output Decorator.
  • Use the generic argument to specify the type of the event payload.
  • Use the new keyword to create an instance of the EventEmitter.

Containing Component Key Points

  • In the containing component template, use the nested component as a directive.
  • The name of the directive is based on the selector property.
  • Use Property Binding to pass data to the nested component.
  • A Property decorated with the @Input() decorator on the nested component can be used as a binding target.
  • Use Event Binding to respond to events raised from the nested component.
  • A Property decorated with the @Output() decorator on the nested component can be used as a binding target.
  • To access the event payload, you can use the $event variable passed from the nested component.