Wednesday, September 23, 2020

Angular Cross Component Communication Using Subject Observable

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.

  1. Subject- Subject doesn't store the value so subscribe does not invoke a new execution that delivers values.
  2. 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
    	
  3. 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
    
  4. 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 implements OnInit {
  message = ''
  constructor(private msgService: MessageService) { }
  ngOnInit() {

  }
  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="container">
  <div class="row">
    <div class="col-xs-12 col-sm-10 col-md-8 col-sm-offset-1 col-md-offset-2">
      <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>
</div>

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">
  <div class="row">
    <div class="col-xs-12 col-sm-10 col-md-8">
      <app-home></app-home>
    </div>
  </div>
  <div class="row">
    <div class="col-xs-12 col-sm-10 col-md-8">
      <div *ngIf="messagefromSubject">
        <hr>
        {{messagefromSubject}}
      </div>        
    </div>
  </div>
</div>
Subject in Angular

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 implements OnInit {
  message = ''
  constructor(private msgService: MessageService, 
    private router: Router) { }
  ngOnInit() {

  }
  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="container">
  <div class="row">
    <div class="col-xs-12 col-sm-10 col-md-8 col-sm-offset-1 col-md-offset-2">
      <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>
</div>

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.

Even though you navigate to this component later thus the subscription to the subject happens after it has already pushed a value using next notification in the previous component 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-md-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

BehaviorSubject in Angular

Navigated to 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

  1. Angular - Call One Service From Another
  2. CanDeactivate Guard in Angular With Example
  3. What is Client Side Routing in Angular
  4. Setting and Fetching Route Parameters in Angular
  5. Location Strategies in Angular Routing

You may also like-

  1. Angular Template-Driven Form Validation Example
  2. Angular @Input and @Output Example
  3. Angular Property Binding With Examples
  4. How to Add Bootstrap to Angular Application
  5. Java JDBC Steps to Connect to DB
  6. Java BufferedWriter Class With Examples
  7. How to Reverse a Doubly Linked List in Java
  8. Spring Batch Processing With List of Objects in batchUpdate() Method

No comments:

Post a Comment