Methods to Share Data Between Angular Components

Deep Gautam  Print 
09 Sep 2018
 
Intermediate
341

It is a real challenge for the angular developer to share the data between components. I have seen angular.io tutorials about data sharing and I found that the example is not so great to understand the concept of data sharing. So here in this tutorial , I have come up with demonstration of data sharing concepts in angular with real time scenarios , which you face in day to day angular development.

Goals

Data Sharing Between Angular Components

  1. Parent to Child: via Input

  2. Child to Parent: via Output() and EventEmitter

  3. Child to Parent: via ViewChild

  4. Unrelated Components: via a Service

Specifications

In this tutorial I have created new angular project and demonstrated the all four method of data sharing mentioned in goal section. So let us get started with real time implementations.

Method1: Parent to Child via @Input

So let me put a problem statement for you then explanation with source code. Suppose you have one component, where you have got a list and you want to supply that list to child component, How to do it ??? answer is @input. So what is @input: @input is a decorator which allows you to accept the input from the parent component. Input can be imported from @angular/core.

SO let us assume I have one component called the teacher and one component called student. Teacher component has the list of all the teachers with their subject details, what they teach and we wanted to supply that list to student component so that it can be displayed which teacher is going to what subject. List of teacher is something like this

export class Teacher {
 name: string;
 subject:string;
 }
 export const Teachers = [
 {name: 'Mr. Deep',subject:'Angular 6 in DotNet Techy YouTube Channel'},
 {name: 'Mr. Gautam' ,subject:'C#, WEB API in DotNet Techy YouTube Channel'},
 {name: 'Mr. DotNet Techy' ,subject:'High chart, chart js, prime ng, ag grid in DotNet Techy YouTube Channel '}
 ];

Now let us create a project

ng new DataSharing

Let us add parent and child component

 ng g component teacher
 ng g component student

So here is the source code for both the component

Teacher component html

 
 <h1>
 <span style="color: red;font-size:larger">
 I am Teacher Component behaving as parent :)
 </span>
 
 </h1>
 <h2>{{principle}} controls {{teachers.length}} teachers</h2>
 <app-student *ngFor="let teacher of teachers"
 [teacher]="teacher"
 [principle]="principle"></app-student>
 

Teacher component ts

 
 import { Component, OnInit } from '@angular/core';
import { Teachers } from '../model/teacher.model';

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

 teachers = Teachers;
 principle = 'Principle';

 constructor() { }

 ngOnInit() {
 }

}

Student component html

 
<h1>
<span style="color: green;font-size:larger">I am Student Component behaving as child :)</span>
</h1>

<h3 style="color: yellowgreen;">{{teacher.name}} says:</h3>
<p style="color:blueviolet;">I, {{teacher.name}}, I will teach you {{teacher.subject}}, {{principleName}}.</p>

Student component ts

 
import { Component,Input, OnInit } from '@angular/core';
import { Teacher } from '../model/teacher.model';

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

 @Input() teacher: Teacher;
 @Input('principle') principleName: string;

 constructor() { }

 ngOnInit() {
 }

}

So now if you notice student component ts then it has two @input properties.

 
 @Input() teacher: Teacher;
 @Input('principle') principleName: string;

Which is getting used in teacher component html

 
<app-student *ngFor="let teacher of teachers"
[teacher]="teacher"
[principle]="principle"></app-student>

This is how parent component is supplying the input to child component

 
[teacher]="teacher"
[principle]="principle"

So now if we go to our appcomponent html and render the teacher component like this

 
<app-teacher></app-teacher>

You will see parent to child communication in action. What we have in teachers is the teacher model which discussed initially. So that's how you can achieve parent to child communication via @input.

Method 2: Child to Parent via @Output and EventEmitter

So let us discuss what is @output, @output is a component decorator which becomes the output for the parent component and EventEmitter is something which has the capability to propagate the event from child component to parent component. So let us see this in action with a real-time example. So my problem statement is If I have one header component and I have login page below the header component. Now Once the user enters the username and password in login component and he wants to send the logged-in username to header component at that point of time this @Output and EventEmitter works perfectly.

So here in my below example header is the parent component and login is the child component and from login component, I would like to emit an event which will give the username to parent component i.e header component. So let us generate two component.

ng g component header
ng g component Login

Now you need to write very simple html to take the username and password in login component and one button which will validate the user and emit an event to header component with logged-in user name.So here is the source code of both the component.

header component html

 
 <h1>
 <span style="color: red;font-size:larger">
 I am Parent Component behaving as a parent for the @output and Event emitter :)
 </span>
 
 </h1>
 <div style="width: 100%;background-color: cornflowerblue">
 <h2 style="color: white">
 Welcome {{userName}}
 </h2>
 </div>
 <app-login (login)="onLogin($event)"></app-login>

header component ts

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

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

 constructor() { }

 ngOnInit() {
 }
 userName = "";
 onLogin(user: string) {
 this.userName =user;
 }

}

login component html

 
 <h1>
 <span style="color: red;font-size:larger">
 I am Login Component behaving as a child for the @output and Event emitter :)
 </span>
 
 </h1>
 <div class="container">
 <form>
 <div class="form-group">
 <label for="userName">User Name</label>
 <input type="text" name="userName" [(ngModel)]="userName" class="form-control" id="userName" required>
 <!-- <input type="text" name="userName" #ctrl="ngModel" required> -->
 </div>
 <div class="form-group">
 <label for="Password">Password</label>
 <input type="password" class="form-control" id="Password">
 </div>
 <button (click)="submit()">Login</button>
 </form>
 </div>

login component ts

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

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

 userName: string = '';
 @Output() login = new EventEmitter<string>();
 constructor() { }

 ngOnInit() {
 }

 submit()
 {
 console.log(this.userName)
 this.login.emit(this.userName);
 }

}

First thing to notice here in login component is

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

Where we have imports for EventEmitter, Output which will support us to Emit the event and use the @OutPut.Second thing is

 @Output() login = new EventEmitter<string>();
 

So login is going to be the event for the parent component which will receive the emitted event with string type value as mentioned in event emitter(EventEmitter<string>).Now on login button hit we are emitting the event like this

 this.login.emit(this.userName);

So let us discuss about header component which is the parent component here.If you notice header component is rendering the login component like below

<app-login (login)="onLogin($event)"></app-login>
 

Here login is @Output which has an event emitted from login compoent and it is calling a local method called onLogin which is setting the user name in header component.

onLogin(user: string) {
 this.userName =user;
 }

So now if you go and put this line of the code in app component html

 <app-header></app-header>

Then you will @Output and event emitter in action.SO that's all about @Output and event emitter .

Method3: Child to Parent via @ViewChild

So here comes the question why @viewchild is needed if we have @output and event emitter to share the data from child to parent component, hold on it is needed because it gives extra control to parent component to access the child events. So in this example, ecparent component is the parent component as the name suggest and ecchild component is the child component. So here is the easily understandable code of components

ecparent component html

<h1>
 Election Commission Parent component.
 </h1>

 <h3>Vote Counting (via ViewChild)</h3>
 <button (click)="start()">Start Vote Counting</button>
 <button (click)="stop()">Stop Vote Counting(Lunch Break)</button>
 <div class="seconds">{{ seconds() }}</div>
 <app-ecchild></app-ecchild>

ecparent component ts

import { Component, OnInit,ViewChild } from '@angular/core';
import { EcchildComponent } from '../ecchild/ecchild.component';

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

 @ViewChild(EcchildComponent)
 private counterComponent: EcchildComponent;
 seconds() { return 0; }
ngOnInit()
{

}
 ngAfterViewInit() {
 // Redefine `seconds()` to get from the `CountdownTimerComponent.seconds` ...
 // but wait a tick first to avoid one-time devMode
 // unidirectional-data-flow-violation error
 setTimeout(() => this.seconds = () =>this.counterComponent.seconds, 0);
 }
 start() { this.counterComponent.start(); }
 stop() { this.counterComponent.stop(); }
}

ecchild component html

 
 <h1>
 Election commission Child compoent which will do Vote Counting
 </h1>
 
 <p>{{message}}</p>

ecchild component ts

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

@Component({
 selector: 'app-ecchild',
 templateUrl: './ecchild.component.html',
 styleUrls: ['./ecchild.component.css']
})
export class EcchildComponent implements OnInit, OnDestroy {
 intervalId = 0;
 message = '';
 seconds = 0;
 clearTimer() { clearInterval(this.intervalId); }
 ngOnInit() { this.start(); }
 ngOnDestroy() { this.clearTimer(); }
 start() { this.countDown(); }
 stop() {
 this.clearTimer();
 this.message = `Holding at T-${this.seconds} seconds`;
 }
 private countDown() {
 this.clearTimer();
 this.intervalId = window.setInterval(() => {
 this.seconds += 1;
 if (this.seconds === 0) {
 this.message = 'Completed counting!';
 } else {
 if (this.seconds < 0) { this.seconds = 50; } // reset
 this.message = `Vote-${this.seconds} and counting going on`;
 }
 }, 1000);
 }
}

So let us discuss parent component first,Important line to discuss is

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

So here we have imported the viewchild to provide the particular component as view child. Then next is supply the child component as view child as done in below lines

 @ViewChild(EcchildComponent)
 private counterComponent: EcchildComponent;

And in html of parent we have these two important lines of the code

 <button (click)="start()">Start Vote Counting</button>
 <button (click)="stop()">Stop Vote Counting(Lunch Break)</button>

If you notice ts then start and stop is the event of child component.

 start() { this.counterComponent.start(); }
 stop() { this.counterComponent.stop(); }0

Which is getting referenced by

private counterComponent: EcchildComponent;

Now it is the high time to discuss the child component ,If you notice it has two method as below which is getting called from parent compoent

start() { this.countDown(); }
 stop() {
 this.clearTimer();
 this.message = `Holding at T-${this.seconds} seconds`;
 }

So if you want to see this in action , go to your app component html and put this line of code

<app-ecparent></app-ecparent>

So that is all about the @viewchild. Let us jump to next method.

Method 4: Unrelated Components via a Service

So let us discuss the problem statement , suppose you have an approval flow implementation where , if one approver has approved the request it should go to next and he should take necessary actions. So here in this example, we have three components: basiccheck, advancecheck and finalcheck. Let us generate these components

ng g component basiccheck
ng g component advancecheck 
ng g component finalcheck

And generate a service which will supply the data

ng g s approval

Now let us go and create a service which will supply the current stage of the approval to all of the above-mentioned components

So here is my service code

import { Injectable } from '@angular/core';
import { BehaviorSubject } from 'rxjs';

@Injectable({
 providedIn: 'root'
})
export class approvalService {

 private approvalStageMessage = new BehaviorSubject('Basic Approval is required!');
 currentApprovalStageMessage = this.approvalStageMessage.asObservable();

 constructor() {

 }
 updateApprovalMessage(message: string) {
 this.approvalStageMessage.next(message)
 }
}

If you notice in service code we have these important things

import { BehaviorSubject } from 'rxjs';

Which is giving us the facility to use

private approvalStageMessage = new BehaviorSubject('Basic Approval is required!');

And

 
currentApprovalStageMessage = this.approvalStageMessage.asObservable();

Now let us jump on the code of basic check component.

Basiccheck component html


 <b>Current Message</b> -{{message}}<br>
 <b>Updated approval message-</b> {{approvalText}}
 <div class="container">
 <form>
 <div class="form-group">
 <label for="approvalText">approval message</label>
 <input type="text" name="approvalText" [(ngModel)]="approvalText" class="form-control" id="approvalText" >
 </div>
 <button (click)="submit()">Approve Basic</button>
 </form>
 </div>
 

Basiccheck component ts

import { Component, OnInit } from '@angular/core';
import { approvalService } from "../approval.service";

@Component({
 selector: 'app-basiccheck',
 templateUrl: './basiccheck.component.html',
 styleUrls: ['./basiccheck.component.css']
})
export class BasiccheckComponent implements OnInit {
 message:string="";
 approvalText:string="";
 constructor(private appService:approvalService) { }

 ngOnInit() {
 this.appService.currentApprovalStageMessage.subscribe(msg => this.message = msg);
 
 }

 submit()
 {
 console.log(this.approvalText)
 this.appService.updateApprovalMessage(this.approvalText)
 }

}

If you notice we have these important things in this

import { approvalService } from "../approval.service";

To import approval service.In ngOninit we are subscring the approval service and getting the current stage of the approval.

ngOnInit() {
 this.appService.currentApprovalStageMessage.subscribe(msg => this.message = msg);
 
 }

On click of approve basic button we are calling below method

submit()
 {
 console.log(this.approvalText)
 this.appService.updateApprovalMessage(this.approvalText)
 }

Which is updating the approval stage.Similarly we have code in other components as well.

advancecheck component html

 <b>Current Message</b> -{{message}}<br>
 <b>Updated approval message-<cc
 <div class="container">
 <form>
 <div class="form-group">
 <label for="approvalText">approval message</label>
 <input type="text" name="approvalText" [(ngModel)]="approvalText" class="form-control" id="approvalText" >
 </div>
 <button (click)="submit()">Approve Advance</button>
 </form>
 </div>

advancecheck component ts

import { Component, OnInit } from '@angular/core';
import { approvalService } from '../approval.service';

@Component({
 selector: 'app-advancecheck',
 templateUrl: './advancecheck.component.html',
 styleUrls: ['./advancecheck.component.css']
})
export class AdvancecheckComponent implements OnInit {
 message:string="";
 approvalText:string="";
 constructor(private appService:approvalService) { }

 ngOnInit() {
 this.appService.currentApprovalStageMessage.subscribe(msg => this.message = msg)
 }
 submit()
 {
 console.log(this.approvalText)
 this.appService.updateApprovalMessage( this.approvalText);
 }
}

finalcheck component html

 <b>Current Message</b> -{{message}}<br>
 <b>Updated approval message-</b> {{approvalText}}
 <div class="container">
 <form>
 <div class="form-group">
 <label for="approvalText">User Name</label>
 <input type="text" name="approvalText" [(ngModel)]="approvalText" class="form-control" id="approvalText" >
 </div>
 <button (click)="submit()">Approve Final</button>
 </form>
 </div>

finalcheck component ts

import { Component, OnInit } from '@angular/core';
import { approvalService } from '../approval.service';

@Component({
 selector: 'app-finalcheck',
 templateUrl: './finalcheck.component.html',
 styleUrls: ['./finalcheck.component.css']
})
export class FinalcheckComponent implements OnInit {
 message:string="";
 approvalText:string="";
 constructor(private appService:approvalService) { }

 ngOnInit() {
 this.appService.currentApprovalStageMessage.subscribe(msg => this.message = msg);
 }
 submit()
 {

 this.appService.updateApprovalMessage(this.approvalText);
 }
}

So if you want to see this in action, how it works, just go app component HTML and put these line of the code.


 <!-- Sharing data via services -->
 <app-basiccheck></app-basiccheck>
 <app-advancecheck></app-advancecheck>
 <app-finalcheck></app-finalcheck>

Complete app component html is is here to test all the data sharing mechanism.

 <!-- For @input, Parent to Child: via Input -->
 <!-- <app-teacher></app-teacher> -->
 
 <!-- For @output and event emiitter, Child to Parent: via Output() and EventEmitter -->
 <!-- <app-header></app-header> -->
 
 <!-- child to parent communication using @ViewChild-->
 <!-- <app-ecparent></app-ecparent> -->
 
 <!-- Sharing data via services -->
 <app-basiccheck></app-basiccheck>
 <app-advancecheck></app-advancecheck>
 <app-finalcheck></app-finalcheck>
Summary

That is all about this tutorial of data sharing between components. I hope, you will use one of the methods for sharing data bewteen components of your Angular application.

Hands-on Learning
Free Interview Books
 
+