Angular 2 : Working with Nested Components

What are Nested/Child components?

A Component in Angular 2 can have child components. Also, those child components can have their own further child components. Angular 2 seamlessly supports nested components.

As an example, consider a calculator as a top level component. It is built with several other child components like screen component, button component, body component, battery component etc. These screen, button, body and battery components are nested components of Calculator component.

The nested component have all the functionalities just as any other Angular 2 component.

Now several question arises in our mind:


  1. How we are going to use our nested components in its parent component?
  2. How we can pass data from parent component into its child component?
  3. How can we get the data back from child component to parent component?
  4. How does the child component can respond to the parent events?
  5. How does the parent component can respond to the child events?

To answer all these questions, lets us built a step-by-step example where we have a profile page of a person. It will also have a friend's list.

1) Building Parent and Child components.

Let's first built our Child component in our example app which is Friend list component.

import { Component } from '@angular/core';
 
@Component({
    selector: 'my-friends',
    template: `<div>
                <table>
                  <tr *ngFor='let friend of friends'>
                    <td>{{friend}}</td>
                    <td>
                       <img 
                          src="http://witspry.in/ContentServer/Images/User/user.png" 
                          width="20" />
                    </td>
                  </tr>
                </table>
              </div>`
})
export class MyFriendsComponent{
    friends: Array<string> = [
      'Friend 1',
      'Friend 2',
      'Friend 3',
      'Friend 4',
      'Friend 5',
    ];
}

Now, let's built our Parent component which is User's profile page.

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

import {MyFriendsComponent} from './myFriends';

@Component({
    selector: 'my-profile',
    template:`<img [src]="imgURL" width="50"/>
              <h3>My Friends</h3>
              <my-friends></my-friends>`
})
export class MyProfileComponent{
  constructor(){
    this.imgURL = 'http://witspry.in/ContentServer/Images/User/user_circle.png';  
  }
}

* See the above example as how the <my-friends> is used as a directive to the Profile component.

Now, in main app import both component and add them in the "declarations" array.

@NgModule({
  imports: [ BrowserModule ],
  declarations: [ App, MyProfileComponent, MyFriendsComponent ],
  bootstrap: [ App ]
})
export class AppModule {}


Next, we are going to see how parent component provides input to child component and how child component emit events to send the payloads to parent component. Here is a diagram to get a gist of it.



2) Passing data using @Input() decorator.

@Input() decorator to the rescue

Add a variable name as a following line with @Input() decorator.

export class MyFriendComponent{
  @Input() name : string;   
}

Change our User's profile page as:

@Component({
    selector: 'my-profile',
    template:`
              <img [src]="imgURL" width="50"/>
              <h3>My Friends</h3>
              <div *ngFor="let friend of friends">
                <my-friend [name]='friend'></my-friend>
              </div>
    `
})
export class MyProfileComponent{
  constructor(){
    this.imgURL = 'http://witspry.in/ContentServer/Images/User/user_circle.png';  
    this.friends = [
      'Friend 1',
      'Friend 2',
      'Friend 3',
      'Friend 4',
      'Friend 5'
    ];
  }
}

See the line - <my-friend [name]='friend'></my-friend>
Here we are passing a friend name to the "name" property of the child component.

3) Raising an event using @Output() decorator

The @Output directive enables a child component to use its properties in the parent component.

Lets modify our MyFriend component as:


import { Component, Input, Output, EventEmitter } from '@angular/core';
 
@Component({
    selector: 'my-friend',
    template: `<div>
                <table>
                  <tr>
                    <td>{{name}}</td>
                    <td>
                        <img src="http://putiw.xyz/clashroyaleunlimitedgems/img/user.png"
                            width="20" />
                    </td>
                    <td><button (click)='OnClick()'>Ping</button></td>
                    <td> {{timesPinged}} </td>
                  </tr>
                </table>
              </div>`
})
export class MyFriendComponent{
  @Input() name : string;
  timesPinged: number = 0;
  @Output() pingClicked: EventEmitter<string> = new EventEmitter<string>();
  
  OnClick(){
    this.timesPinged++;
    this.pingClicked.emit('You pinged ' + this.name + ' ' + 
          this.timesPinged + (this.timesPinged == 1? ' time' : ' times'));
  }
}

Here, we have imported Output and EventEmitter from Angular core library. Also, we have defined an event using @Output() decorator. The OnClick is a local function which is invoked from button's click event. Since the "pingClicked" is decorated with @Output(), it's emit function is able to notify the parent component.

Now, change the parent component to capture the raised event from it's child component as below:

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

@Component({
    selector: 'my-profile',
    template: `
              <img [src]="imgURL" width="50"/>
              <br />
              {{pingMessage}}
              <br />
              Total Pings today : {{totalPings}}
              <h3>My Friends</h3>
              <div *ngFor="let friend of friends">
                <my-friend [name]='friend'
                          (pingClicked)='onFriendPingClicked($event)'>
                </my-friend>
              </div>
    `
})
export class MyProfileComponent{
  constructor(){
    this.imgURL = 
 'http://witspry.in/ContentServer/Images/User/user_circle.png';  
    this.friends = [
      'Friend A',
      'Friend B',
      'Friend C',
      'Friend D',
      'Friend E'
    ];
    this.totalPings = 0;
    this.pingMessage = '';
  }
  
  onFriendPingClicked(pingMessage: string): void{
    this.totalPings++;
    this.pingMessage = pingMessage;
  }
}

Finally, we have added an Event binding (please check our previous article in Angular 2 : Event bindings) which is "pingClicked". Have you remember, this was decorated with @Output in our child component. Yes, this is how was are able to use it here. Also, it is assigned to a function "onFriendPingClicked($event)". The $event contains the data that the child component emits on the occurrence of it's event.

To Summarize:

* Use @Input() decorator to pass data to child component.
* Use @Output() decorator to raise an event to parent component. Note : The @Output() decorator property can be only of EventEmitter type.

Live Code