In any Angular app, components are the basic building blocks. Angular Components provide us with the views/templates for the app. They also manage the data bound to those views. Below are some of the most common data sharing scenarios in any app, where two or more components share information between them:
Let us have a look at each of these scenarios with examples.
In our example, we will display a list of courses to the user and, upon selecting any course, the user will be taken to the details for that course.
Below is how our app.module.ts would look like:
1//app.module.ts
2import { BrowserModule } from '@angular/platform-browser';
3import { NgModule } from '@angular/core';
4import { FormsModule } from '@angular/forms';
5import { HttpModule } from '@angular/http';
6import { AppComponent } from './app.component';
7import { CourseListComponent } from './courses/course-list/course-list.component';
8import { CourseItemComponent } from './courses/course-list/course-item/course-item.component';
9
10@NgModule({
11 declarations: [
12 AppComponent,
13 CourseListComponent,
14 CourseItemComponent
15 ],
16 imports: [
17 BrowserModule,
18 FormsModule,
19 HttpModule
20 ],
21 providers: [],
22 bootstrap: [AppComponent]
23})
24export class AppModule { }
Below is how our course model would look like:
1//course.model.ts
2export class Course {
3 public name: string;
4 public description: string;
5 public courseImagePath: string;
6
7 constructor(name: string, desc: string, courseImagePath: string) {
8 this.name = name;
9 this.description = desc;
10 this.courseImagePath = imagePath;
11 }
12}
Our root component, or the app component, contains the course-list component and looks like below:
1import { Component, OnInit } from '@angular/core';
2import { Course } from './course.model';
3
4@Component({
5 selector: 'app-root',
6 templateUrl: './app.component.html',
7 styleUrls: ['./app.component.css']
8})
9export class AppComponent implements OnInit {
10 selectedCourse: Course;
11 constructor() { }
12 ngOnInit() {
13 }
14}
Below is the HTML code for the app component:
1<div class="row">
2 <div class="col-md-5">
3<app-course-list
4 (courseWasSelected)="selectedCourse = $event"></app-course-list>
5 </div>
6 <div class="col-md-7">
7<app-course-detail
8 *ngIf="selectedCourse; else infoText"
9 [course]="selectedCourse"></app-course-detail>
10<ng-template #infoText>
11 <p>Please select any course to view details!</p>
12</ng-template>
13 </div>
14</div>
Also, our course-list component looks something like this:
1import { Component, OnInit, EventEmitter, Output } from '@angular/core';
2import { Course } from '../course.model';
3
4@Component({
5 selector: 'app-course-list',
6 templateUrl: './course-list.component.html',
7 styleUrls: ['./course-list.component.css']
8})
9export class CourseListComponent implements OnInit {
10 courses: Course[] = [
11 new Course('Angular Course 1', 'This is simply a practice Angular course 1', 'http://via.digital.com/350x150.jpeg'),
12 new Course('Angular Course 2', 'This is simply a practice Angular course 2', 'http://via.digital.com/350x150'),
13 new Course('Angular Course 3', 'This is simply a practice Angular course 3', 'http://via.digital.com/350x150')
14 ];
15
16 constructor() { }
17
18 ngOnInit() {
19 }
20}
Below is the template:
1<div class="row">
2 <div class="col-xs-12">
3 <button class="btn btn-success">New Course</button>
4 </div>
5</div>
6<hr>
7<div class="row">
8 <div class="col-xs-12">
9 <app-course-item></app-course-item>
10 </div>
11</div>
Below is our course item component:
1import { Component, OnInit, Input, EventEmitter, Output } from '@angular/core';
2
3import { Course } from '../../course.model';
4
5@Component({
6 selector: 'app-course-item',
7 templateUrl: './course-item.component.html',
8 styleUrls: ['./course-item.component.css']
9})
10export class CourseItemComponent implements OnInit {
11 constructor() { }
12 ngOnInit() { }
13}
And the course item template:
1<a
2 href="#"
3 class="list-group-item clearfix">
4 <div class="pull-left"><!--Display Course Name/Desc here --></div>
5 <span class="pull-right"><!--Display Course Image here--></span>
6</a>
With the above setup, let us have a look at the different ways to communicate between these components.
This is the most common way of sharing data. It uses the @Input() decorator to pass data via the template. @Input decorator allows parent component to bind it's properties to child component and thus gives the child component access to its data. These bindings are actually a reference to properties on the parent component.
Say we want to pass "course" information from our CourseListComponent to the CourseItemComponent.
Below is how we would update our course-list template:
1<div class="row">
2 <div class="col-xs-12">
3 <app-course-item
4 *ngFor="let courseEl of courses"
5 [course]="courseEl"></app-course-item>
6 </div>
7</div>
You can see that, as we are iterating over the list of courses, we pass the individual course element for that iteration to the CourseItemComponent. Thus, we are binding the "course" property of the child CourseItemComponent from the parent CourseListComponent.
Now, to be able to do this, we have to use the @Input decorator on the child CourseItemComponent. Thus, our updated CourseItemComponent would look like below:
1import { Component, OnInit, Input, EventEmitter, Output } from '@angular/core';
2
3import { Course } from '../../course.model';
4
5@Component({
6 selector: 'app-course-item',
7 templateUrl: './course-item.component.html',
8 styleUrls: ['./course-item.component.css']
9})
10export class CourseItemComponent implements OnInit {
11@Input() course: Course;
12 constructor() { }
13 ngOnInit() { }
14}
We can now render the course name/description/image in the CourseItemComponent template as below:
1<a
2 href="#"
3 class="list-group-item clearfix">
4 <div class="pull-left">
5 <h4 class="list-group-item-heading">{{ course.name }}</h4>
6 <p class="list-group-item-text">{{ course.description }}</p>
7 </div>
8 <span class="pull-right">
9 <img
10 [src]="course.courseImagePath"
11 alt="{{ course.name }}"
12 class="img-responsive"
13 style="max-height: 50px;">
14 </span>
15</a>
In certain cases, we want to be able to emit data back from the child component to the parent component. There is an EventEmitter property exposed by the Child component exposes, which would emit data whenever any action/event occurs on the child component.
In the same example we looked at above, let's say that whenever any of the individual course’s link is clicked, we want to inform the parent component about the same.
So, we'll have a "click" event on the CourseItemComponent. This is how the template for that would be updated to:
1<a
2 href="#"
3 class="list-group-item clearfix"
4 (click)="onSelected()">
5 <div class="pull-left">
6 <h4 class="list-group-item-heading">{{ course.name }}</h4>
7 <p class="list-group-item-text">{{ course.description }}</p>
8 </div>
9 <span class="pull-right">
10 <img
11 [src]="course.courseImagePath"
12 alt="{{ course.name }}"
13 class="img-responsive"
14 style="max-height: 50px;">
15 </span>
16</a>
Now, from our CourseItemComponent typescript code, we want to be able to emit this event and inform the parent component about the event. If we want "courseSelected" to be listenable from outside, we have to use the @Output decorator. This would give access to emit() method on this property. Also while creating an instance of EventEmitter, we can optionally define any event data that we might emit.
1import { Component, OnInit, Input, EventEmitter, Output } from '@angular/core';
2
3import { Course } from '../../course.model';
4
5@Component({
6 selector: 'app-course-item',
7 templateUrl: './course-item.component.html',
8 styleUrls: ['./course-item.component.css']
9})
10export class CourseItemComponent implements OnInit {
11@Input() course: Course;
12@Output() courseSelected = new EventEmitter<Course>();
13 constructor() { }
14 ngOnInit() { }
15onSelected() {
16 this.courseSelected.emit(this.course);
17 }
18}
With the above change, we would now be able to listen to the "courseSelected" event on the parent CourseListComponent as shown below.
1<div class="row">
2 <div class="col-xs-12">
3 <button class="btn btn-success">New Course</button>
4 </div>
5</div>
6<hr>
7<div class="row">
8 <div class="col-xs-12">
9 <app-course-item
10 *ngFor="let courseEl of courses"
11 [course]="courseEl"
12 (courseSelected)="onCourseSelected(courseEl)"></app-course-item>
13 </div>
14</div>
Also, we'll now get access to the "course" in our CourseListComponent typescript code.
1import { Component, OnInit, EventEmitter, Output } from '@angular/core';
2import { Course } from '../course.model';
3
4@Component({
5 selector: 'app-course-list',
6 templateUrl: './course-list.component.html',
7 styleUrls: ['./course-list.component.css']
8})
9export class CourseListComponent implements OnInit {
10 courses: Course[] = [
11 new Course('Angular Course 1', 'This is simply a practice Angular course 1', 'http://via.digital.com/350x150.jpeg'),
12 new Course('Angular Course 2', 'This is simply a practice Angular course 2', 'http://via.digital.com/350x150')
13 ];
14
15 constructor() { }
16
17 ngOnInit() {
18 }
19
20onCourseSelected(course: Course) {
21 // console.log("Selected course : " + course);
22 }
23}
Thus, we see that the parent component binds to the event which is outputted by our child component via the template. Also, the parent component reacts to this event using the handler function. The value which gets emitted by the child component is passed to the parent's handler function. Generally, we use $event to refer to any event data.
Thus, we can see that using Angular services is a much better way to pass data around between components instead of manually passing them between various components. Also, our individual components can subscribe to parts of this data store, which greatly reduces the burden on parent/child components in our app to pass data back and forth. Angular services can contain all the business logic which is entirely independent of the components and we can consume them across multiple components. In most practical scenarios, these services are used to make an external API call using REST APIs to get the data and it can act as a bridge between two components i.e. Sender component and Receiver component.