This is the second part of a two-part introduction to inter-component communication in Angular. So check part 1 first. This article continues where the first part left off.
Keeping in touch with your family: long distance calls
Sometimes you have no idea where a component is; telling it something in that situation using the above method would be problematic. You could wire up a whole range of emitters to wander up and down the component trees between the two, but that would be incredibly tedious to maintain and messy. It would also be a very fragile communication system.
A better solution would be to use an intermediate system to connect the two components directly. You want to tell something to one or more components at once and have no idea where they are. It isn’t even important where they are, just that they get the data.
I would recommend implementing a communication service for that, an injectable Service object that implements a Subject object. This breaks with the direct hierarchical communication method shown before, but is, by far, preferable in this situation. Think of it as a WhatsApp group that the components can subscribe to to chat to each other. They can reply if they feel the need, and they’ll get the message regardless of their location.
Subject has a number of derived types that each have their own use. For simplicity’s sake we’ll use the base Subject in the following examples. The other possibilities are BehaviorSubject, ReplaySubject, PublishSubject and AsyncSubject. Their differences are subtle but profound.
Let’s add a simple communication service to give you an idea of the possibilities.
First, we’ll generate a service class using the Angular CLI with this command:
ng generate service communication
This will create a file named ‘communication.service.ts’ in your directory.
Replace the contents of that file with this:
import { Injectable } from '@angular/core'; import { Observable } from 'rxjs'; import { Subject } from 'rxjs'; @Injectable({ providedIn: 'root' }) export class CommunicationService { private subject: Subject<string> = new Subject<string>(); constructor() { } public say(message: string) { this.subject.next(message); } public hear(): Observable<string> { return this.subject.asObservable(); } }
If we left the instancing of the communication service to the components, they’d both create their own version of the communication service and would not be able to talk to each other. We need to make sure there’s only ever one instance of the communication service and that all components use only that one. To accomplish this, we’ll let the app.module deal with it (delegation of work is a great thing!).
Replace the content of your app.module.ts with this:
import { BrowserModule } from '@angular/platform-browser'; import { NgModule } from '@angular/core'; import { CommunicationService } from './communication.service'; import { AppComponent } from './app.component'; import { ChildComponent } from './child/child.component'; @NgModule({ declarations: [ AppComponent, ChildComponent ], imports: [ BrowserModule ], providers: [CommunicationService], bootstrap: [AppComponent] }) export class AppModule { }
The changes compared to the file you already had are minimal: all we have done is introduced the communication service class and told the module to give the component a reference to it when it asks for one. This is what happens through the provider collection. That’s it. Now the communication service is available to any component living in the app.module.
Next, we’ll give the components their communication service by including it in their constructor. This way the app.module will plug its version of the service into the component and we don’t have to look after it.
For brevity’s sake and to keep things easy to understand, I’ll show just the chat service connection between the two components. Everything else is stripped out – but feel free to experiment and combine it with the previous examples.
Replace the content of app.component.html, app.component.ts, child.component.html, and child.component.ts:
The changes compared to the file you already had are minimal: all we have done is introduced the communication service class and told the module to give the component a reference to it when it asks for one. This is what happens through the provider collection. That’s it. Now the communication service is available to any component living in the app.module.
Next, we’ll give the components their communication service by including it in their constructor. This way the app.module will plug its version of the service into the component and we don’t have to look after it.
For brevity’s sake and to keep things easy to understand, I’ll show just the chat service connection between the two components. Everything else is stripped out – but feel free to experiment and combine it with the previous examples.
Replace the content of app.component.html, app.component.ts, child.component.html, and child.component.ts:
The changes compared to the file you already had are minimal: all we have done is introduced the communication service class and told the module to give the component a reference to it when it asks for one. This is what happens through the provider collection. That’s it. Now the communication service is available to any component living in the app.module.
Next, we’ll give the components their communication service by including it in their constructor. This way the app.module will plug its version of the service into the component and we don’t have to look after it.
For brevity’s sake and to keep things easy to understand, I’ll show just the chat service connection between the two components. Everything else is stripped out – but feel free to experiment and combine it with the previous examples.
Replace the content of app.component.html, app.component.ts, child.component.html, and child.component.ts:
The changes compared to the file you already had are minimal: all we have done is introduced the communication service class and told the module to give the component a reference to it when it asks for one. This is what happens through the provider collection. That’s it. Now the communication service is available to any component living in the app.module.
Next, we’ll give the components their communication service by including it in their constructor. This way the app.module will plug its version of the service into the component and we don’t have to look after it.
For brevity’s sake and to keep things easy to understand, I’ll show just the chat service connection between the two components. Everything else is stripped out – but feel free to experiment and combine it with the previous examples.
Replace the content of app.component.html, app.component.ts, child.component.html, and child.component.ts:
The changes compared to the file you already had are minimal: all we have done is introduced the communication service class and told the module to give the component a reference to it when it asks for one. This is what happens through the provider collection. That’s it. Now the communication service is available to any component living in the app.module.
Next, we’ll give the components their communication service by including it in their constructor. This way the app.module will plug its version of the service into the component and we don’t have to look after it.
For brevity’s sake and to keep things easy to understand, I’ll show just the chat service connection between the two components. Everything else is stripped out – but feel free to experiment and combine it with the previous examples.
Replace the content of app.component.html, app.component.ts, child.component.html, and child.component.ts:
<h1> This is the parent component! </h1> <div *ngFor="let c of chatItems"> {{ c }} </div> <input type="text" #input /> <button type="button" (click)="send(input.value)">Say</button> <app-child></app-child>
App.component.html
import { Component } from '@angular/core'; import { CommunicationService } from './communication.service'; @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.css'] }) export class AppComponent { chatItems: Array<string> = []; constructor(private communicate: CommunicationService) { this.communicate.hear().subscribe((result) => { this.chatItems.push(result); }); } send(message: string) { this.communicate.say(message); } }
App.component.ts
<div style="border: dashed 1px black;"> <p> This is the child component </p> <div *ngFor="let c of chatItems"> {{ c }} </div> <input type="text" #input /> <button type="button" (click)="send(input.value)">Say</button>
Child.component.html
import { Component } from ’@angular/core’; import { CommunicationService } from ’../communication.service’; @Component({ selector: 'app-child', templateUrl: './child.component.html', styleUrls: ['./child.component.css'] }) export class ChildComponent { chatItems: Array<string> = []; constructor(private communicate: CommunicationService) { communicate.hear().subscribe((result) => { this.chatItems.push(result); }) } send(message: string) { this.communicate.say(message); } }
Child.component.ts
What is important to take away from this is that, while this looks and acts like any regular chat service, the messages sent between the components never leave the client. It behaves like a local chat channel.
Now both components can send and receive simple messages, regardless of where they are in your module. Just use the communication service to deal with accepting and delivering messages.
Here’s a schematic view of what you just built:
TLDR;
Together with the examples in part 1, you now have a number of basic examples covering the range of possibilities of inter-component communication. Now it is up to you to implement and extend them as you see fit. You could consider rebuilding the above service example to use a ReplaySubject, or prevent the sending component from receiving the message it just send itself. Another conceivable improvement would be a base-class to prevent code duplication.
In any case, I hope these examples are of help as you start learning Angular. Also read the first part of this blog!