In this post we’ll see how to use Subject observable for cross component communication in Angular.
Subject in RxJS Library
Subject is a type of Observable using which you can multicast values to many Observers, which makes it different from a plain Observables which are unicast (each subscribed Observer owns an independent execution of the Observable).
If you have created a custom observable (or seen the code of any existing Observable) you would have noticed that next notification is inside the observable where as with Subject it is outside.
Here is a sample custom Observable code-
const customObservable = Observable.create((observer: Observer<Number>) => { setInterval( () => { observer.next(Math.floor(Math.random() * 10)); }, 1000); });
In a way Subjects are like EventEmitters with the added advantage of providing three types of notifications and other benefits like using operators. Thus in Angular apps it is one of the main usage of the Subject; use them instead of EventEmitter to emit values to different component.
Types of Subject
There are 4 variants of Subject in RxJS library.
- Subject- Subject doesn't store the value so subscribe does not invoke a new execution that delivers values.
- BehaviorSubject- It stores the latest value emitted to its consumers, and whenever a new Observer subscribes, it will immediately
receive the "current value" from the BehaviorSubject. You can also initialize BehaviorSubject with a value.
const subject = new BehaviorSubject(0); // 0 is the initial value
- ReplaySubject- This subject is similar to BehaviorSubject but rather than storing only a single value like BehaviorSubject it records
multiple values from the Observable execution and replays them to new subscribers. For example-
const subject = new ReplaySubject(5); // buffer 5 values for new subscribers
- AsyncSubject- In this Subject only the last value of the Observable execution is sent to its observers, and only when the execution completes.
Communication between Angular component using Subject example
Here is a simple example of passing a message from one component to another using Subject observable in Angular.
MessageService
A service class is used to encapsulate the usage of Subject. With in this Service class an instance of Subject is created.
In the nextMessage() method next notification is sent, in the getMessage() method subject is returned as an Observable.
import { Injectable } from '@angular/core'; import { Observable, Subject } from 'rxjs'; @Injectable({ providedIn: 'root', }) export class MessageService { private subject = new Subject<string>(); getMessage(): Observable<string>{ return this.subject.asObservable(); } //publish value to all the subscribers nextMessage(message: string){ this.subject.next(message); } }
HomeComponent
In HomeComponent there is a onSubmit() method that calls the nextMessage() method of the MessageService with the entered message.
import { OnInit, Component } from '@angular/core'; import { MessageService } from './message.service'; @Component({ selector: 'app-home', templateUrl: './home.component.html' }) export class HomeComponent { message = '' constructor(private msgService: MessageService) { } onSubmit(){ this.msgService.nextMessage(this.message); } }
home.component.html
In this template we have UI for entering a message and submitting it. Two way binding is used to bind message field.
<div class="row mb-2"> <div class="col-12"> <label for="msg">Message:</label> <input type="text" placeholder="Enter message" id="msg" [(ngModel)]="message"> </div> </div> <button (click)="onSubmit()" class="btn btn-primary">Submit</button>
AppComponent
In AppComponent we subscribe to the subject to get the message. In the ngOnDestroy() we also unsubscribe from the subject in order to prevent any memory leaks.
import { OnInit, Component } from '@angular/core'; import { Subscription } from 'rxjs'; import { MessageService } from './message.service'; @Component({ selector: 'app-root', templateUrl: './app.component.html' }) export class AppComponent implements OnInit { private msgSubscription!: Subscription; messagefromSubject: string = ''; constructor(private messageService: MessageService){ } ngOnInit(){ this.msgSubscription = this.messageService.getMessage().subscribe((msg: string)=> { this.messagefromSubject = msg; }); } ngOnDestroy(): void { this.msgSubscription.unsubscribe(); } }
app.component.html
In this template there is <app-home> selector for loading the HomeComponent and there is also an if condition to show the message if one is there.
<div class="container"> <app-home></app-home> <div class="row"> <div class="col-12"> <div *ngIf="messagefromSubject"> <hr> {{messagefromSubject}} </div> </div> </div> </div>
Communication between Angular component using BehaviorSubject example
Above example works fine because you have already subscribed to the Subject before the next notification comes. That may not be the case always, consider a scenario where you navigate from one component to another using Angular routing and the component you navigate to is subscribing to get the value. If you are using Subject in this Angular routing scenario you won’t get any value in the component because Subject doesn’t store the value. As soon as it sees next the value is multicast to the existing subscribers.
If you are navigating later to a component that is subscribing to the value you should use BehaviorSubject because that will store the latest value. Whenever a new Observer subscribes, it will immediately receive that value.
Let’s see it with an example where we have two components HomeComponent and MessageComponent and the route definitions are as given below.
AppRouting
const routes: Routes = [ {path: '', component: HomeComponent}, {path: 'message', component: MessageComponent} ];
MessageService
A service class is used to encapsulate the usage of BehaviorSubject with in this Service class.
import { Injectable } from '@angular/core'; import { BehaviorSubject, Observable } from 'rxjs'; @Injectable({ providedIn: 'root', }) export class MessageService { private subject = new BehaviorSubject<string>(''); getMessage(): Observable<string>{ return this.subject.asObservable(); } //publish value to all the subscribers nextMessage(message: string){ this.subject.next(message); } }
HomeComponent
In HomeComponent there is a onSubmit() method that calls the nextMessage() method of the MessageService with the entered message and navigates to the another route for MessageComponent.
import { OnInit, Component } from '@angular/core'; import { Router } from '@angular/router'; import { MessageService } from './message.service'; @Component({ selector: 'app-home', templateUrl: './home.component.html' }) export class HomeComponent { message = '' constructor(private msgService: MessageService, private router: Router) { } onSubmit(){ this.msgService.nextMessage(this.message); // navigate to another route this.router.navigate(['/message']); } }
home.component.html
In this template we have UI for entering a message and submitting it. Two way binding is used to bind message field.
<div class="row mb-2"> <div class="col-12"> <label for="msg">Message:</label> <input type="text" placeholder="Enter message" id="msg" [(ngModel)]="message"> </div> </div> <button (click)="onSubmit()" class="btn btn-primary">Submit</button>
MessageComponent
In MessageComponent we subscribe to the subject to get the message. In the ngOnDestroy() we also unsubscribe from the subject in order to prevent any memory leaks. Inline template is used in the Component to show the message.
You navigate to this component later, when you press submit button in home page. Therefore the subscription to the subject happens after it has already pushed a value using next notification in the previous component but you will still be able to get the value because of the use of BehaviorSubject.
import { Component, OnDestroy, OnInit } from '@angular/core'; import { Subscription } from 'rxjs'; import { MessageService } from './message.service'; @Component({ selector: 'app-message', template: `<span>Message- </span> {{ messagefromSubject }}`, }) export class MessageComponent implements OnInit, OnDestroy{ private msgSubscription!: Subscription; messagefromSubject = ''; constructor(private messageService: MessageService){ } ngOnInit(){ this.msgSubscription = this.messageService.getMessage().subscribe((msg: string)=> { this.messagefromSubject = msg; }); } ngOnDestroy(): void { this.msgSubscription.unsubscribe(); } }
app.component.html
<div class="container"> <div class="row"><p></p></div> <div class="row"> <div class="col-12"> <router-outlet></router-outlet> </div> </div> </div>
AppComponent
import { FormControl, FormGroup, Validators } from '@angular/forms'; import { Router } from '@angular/router'; import { Observable, Observer } from 'rxjs'; import { MessageService } from './message.service'; @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.css'], }) export class AppComponent{ }
Home Page
Navigated to MessageComponent
To confirm that it is because of BehaviorSubject you are getting a value in MeesageComponent, in MessageService you can change this line-
private subject = new BehaviorSubject<string>('');
to this line
private subject = new Subject<string>();
Then latest value won't be stored and you won't get the value of message in MessageComponent.
That's all for this topic Angular Cross Component Communication Using Subject Observable. If you have any doubt or any suggestions to make please drop a comment. Thanks!
>>>Return to Angular Tutorial Page
Related Topics
You may also like-
No comments:
Post a Comment