Components are the building blocks of an Angular application. A complex Angular application may contain hundreds of angular components structured together to build a complete Angular application.

In this article, we will learn how to pass data from a parent component to a child component.

Passing Data with @Input

Create a sample angular application, and add the ng-bootstrap module to add basic styling.

ng new component-example
cd component-example
ng add @ng-bootstrap/ng-bootstrap

Create a new component called dashboard.

ng generate component dashboard

This component is the topmost parent component rendered inside the app component and is used to initialize the data that is used by the child components.

The dashboard component initializes a list of cards, and each card will display the post title, the number of minutes needed to read the post, etc.

Update the app.component.html to include the dashboard component.

<app-dashboard></app-dashboard>

The template of the dashboard component will have *ngFor directive, that iterates over the list of posts and passes required card data.

Update the dashboard.component.ts file with the below content.

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

export interface PostCard{
  title: String,
  estimatedReadTime: number
}

@Component({
  selector: 'app-dashboard',
  templateUrl: './dashboard.component.html',
  styleUrls: ['./dashboard.component.css']
})
export class DashboardComponent implements OnInit {

  _cards :Array<PostCard> = [];

  constructor() { }

  ngOnInit(): void {
    this.generatePostData();
  }

  generatePostData(){
    for(var i=0; i<3; i++){
      this._cards.push({'title': 'Post ' + i, 'estimatedReadTime' : i*2 });
    }
  }
}

The dashboard component is the topmost component. We are using this component to load the data.

The dashboard component also can be categorized as a data component, where the angular component loads the required data for current and child components.

Update the dashboard.component.html with the below content.

<div class="container">
  <div class="row">
    <div *ngFor="let card of _cards; let i = index">
      <div class="row">
        <app-card [card_name]="card.title" [read_time]="card.estimatedReadTime">
        </app-card>
      </div>
    </div>
  </div>
</div>

We are iterating through the posts list and passing it to the <app-card> component. Notice that we are passing card title and extimatedReadTime as input to the card component.

card_name and read_time are aliases used with @input() this we will add it in the card component in the next step.

Creating the child component

Create a new card component.

ng generate component card

The card component is a display component. 

display component does not have any operation regarding retrieving data. 

The display component accepts the inputs and emits events to the parent component and makes the angular component loosely coupled and reusable.

Update the card.component.ts file with below content.

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

@Component({
  selector: 'app-card',
  templateUrl: './card.component.html',
  styleUrls: ['./card.component.css']
})
export class CardComponent {

  constructor() { }

  @Input('card_name') _name: String;
  @Input('read_time') _timeInMinutes: number;
}

The card component accepts two inputs from the parent component. The _name and _timeInMinutes fields get assigned with input values.

We have used @Input() with the fields. The values passed from the parent components are assigned to these fields.

We can also specify an alias name for the component inputs. In the above example, we have defined card_name and read_time as an alias for the component inputs.

Update the card.component.html with the below content.

<div class="card" style="width: 18rem;">
  <img class="card-img-top"
  src="https://images.unsplash.com/photo-1489533119213-66a5cd877091?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=751&q=80"
  alt="Card image cap">
  <div class="card-body">
    <p class="text-left card-title">{{_name}}</p>
    <p class="card-text" class="text-right">{{_timeInMinutes}} minutes of reading</p>
  </div>
</div>

The above template displays the input values received from the parent dashboard component in the UI.

Angular passing value between components

Validating the component inputs

In the above example, we directly assigned whatever input value passed from the parent component into child components variables.

Any undesired input passed from the parent components may break the child component.

We can handle this with the help of getter and setter methods.

Update the card.component.ts file with the below content.

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

@Component({
  selector: 'app-card',
  templateUrl: './card.component.html',
  styleUrls: ['./card.component.css']
})
export class CardComponent {

  constructor() { }

  private _name: String;
  private _timeInMinutes: number;

  @Input('card_name')
  set name(name: String) {
    if (name === '')
      name = 'No Title';
    this._name = name;
  }

  get name(): String {
    return this._name;
  }

  @Input('read_time')
  set timeInMinutes(readTime: number) {
    if (isNaN(readTime))
      this._timeInMinutes = 0;
    this._timeInMinutes = readTime;
  }
  get timeInMinutes(): number {
    return this._timeInMinutes;
  }
}
  • We have made the fields private.
  • The set prefix defines setter methods, and getter methods, we can use the get prefix.
  • We are assigning default values if input passed to the component is invalid.


Update the card.component.html with the below content.

<div class="card" style="width: 18rem;">
  <img class="card-img-top"
  src="https://images.unsplash.com/photo-1489533119213-66a5cd877091?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=751&q=80"
  alt="Card image cap">
  <div class="card-body">
    <p class="text-left card-title">{{name}}</p>
    <p class="card-text" class="text-right">{{timeInMinutes}} minutes of reading</p>
  </div>
</div>

Now name and timeInMinutes variables invoke getter methods to display the value.

Run the application, and we will get our postcard. 🙂 Same as before.

Angulat @Input example

Content Projection

To insert HTML elements or another component inside the child component, we use the content projection technique.

We use <ng-content> element in the child component template, and it gets replaced by the element/component, passed from the parent component.

Angular content projection example

Update dashboard.component.ts with the below content.

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

export interface Post {
  name: String,
  totalWords: number
};

export interface PostCard{
  title: String,
  estimatedReadTime: number,
  post : Post
}

@Component({
  selector: 'app-dashboard',
  templateUrl: './dashboard.component.html',
  styleUrls: ['./dashboard.component.css']
})
export class DashboardComponent implements OnInit {

  _cards :Array<PostCard> = [];

  constructor() { }

  ngOnInit(): void {
    this.generatePostData();
  }

  generatePostData(){
    for(var i=0; i<3; i++){
      let post = {'name': 'Post '+i, 'totalWords' : i*100 };
      this._cards.push({'title': 'Post ' + i, 'estimatedReadTime' : i*2, 'post' : post });
    }
  }
}

We have created a Post object that will be part of the PostCard object.

Create a new post component. This component will be part of the card component that we have created in the previous section.

ng g c post

Update post.component.ts with the below content.

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

@Component({
  selector: '[app-post]',
  templateUrl: './post.component.html',
  styleUrls: ['./post.component.css']
})
export class PostComponent {
  @Input('post') post : any;

  constructor() { }
}

The post component expects a Post instance as component input.

Notice that we have wrapped the component selector with []. By enclosing the selector with [], we can make the component accept an attribute instead of an element.

Add below content to the post.component.html

<div class="p-3 mb-2 bg-white text-dark">
  <div class="card card-body">
    <span>Name: {{post?.name}}</span>
    <span>Total Words: {{post?.totalWords}}</span>
  </div>
</div>

We are displaying the post name and the total number of words available in the post.

Update the card.component.html file with the below content.

<div class="card" style="width: 18rem;">
  <img class="card-img-top"
  src="https://images.unsplash.com/photo-1489533119213-66a5cd877091?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=751&q=80"
  alt="Card image cap">
  <div class="card-body">
    <p class="text-left card-title">{{name}}</p>
    <p class="card-text" class="text-right">{{timeInMinutes}} minutes of reading</p>
    <ng-content></ng-content>
  </div>
</div>

The <ng-content> element indicates that another child component gets inserted in this place. The parent element passes the required sub child element to the child component.

We have made the required changes to insert the content into the card component. Let us pass the content from the dashboard component now.

Update dashboard.component.html with the below content.

<div class="container">
  <div *ngFor="let card of _cards; let i = index">
    <div class="row">
      <app-card
        [card_name]="card.title"
        [read_time]="card.estimatedReadTime">
        <span app-post [post]="card.post"></span>
      </app-card>
    </div>
  </div>
</div>

We have used the app-post as an HTML attribute to pass the value. We can do this as we have updated the component selector of the post component by enclosing the name of the component selector with [] in the earlier section.

The value gets inserted into the <ng-content> element of the card child component.

Run the application. We should be able to get bootstrap cards, as shown below.

Angular content projection

Passing the values with ng-content

We can use the custom elements to pass the values from the parent component to the child component and use the <ng-content> element with the select attribute to display the received value in the child component.

Update the dashboard.component.html with below content.

<div class="container">
  <div *ngFor="let card of _cards; let i = index">
    <div class="row">
      <app-card
        [card_name]="card.title"
        [read_time]="card.estimatedReadTime">
        <card-number>{{i+1}}</card-number>
        <span app-post [post]="card.post"></span>
      </app-card>
    </div>
  </div>
</div>

We are using <card-number> custom element as input to the <app-card> component.

Update card.component.html with the below content.

<div class="card" style="width: 18rem;">
  <img class="card-img-top"
  src="https://images.unsplash.com/photo-1489533119213-66a5cd877091?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=751&q=80"
  alt="Card image cap">
  <div class="card-body">
    #<ng-content select="card-number"></ng-content>
    <p class="text-left card-title">{{name}}</p>
    <p class="card-text" class="text-right">{{timeInMinutes}} minutes of reading</p>
    <ng-content></ng-content>
  </div>
</div>

The passed value from the parent component can be used here with the help of <ng-element> and set the custom element name in the select attribute, as shown above.

We also have to update the app.module.ts with the below content.

//Other imports
import { NgModule, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';

@NgModule({
 //Other parameters
  schemas: [CUSTOM_ELEMENTS_SCHEMA]
})
export class AppModule { }

We are adding CUSTOM_ELEMENTS_SCHEMA to the shemas, as shown above. Without adding this, Angular throws errors as it does not know about our custom element.

Run the application.

Angular ng-element example

Conclusion

In this article, we learned how to pass data from the parent component to child components.

We learned how to use @Input(), using getter and setters to interact with the controller fields, etc.

We also learned about content projection and passing data with the help of custom elements and displaying the value in child component by using <ng-content>.

Example code is available on GitHub.

You may also be interested in