In previous post we saw the basics of observables, and the different ways to create observables in Angular 5.  Although that it is an interesting enough topic on its own, what it is really interesting about observables are the practical applications in our everyday code. And one of those practical applications is communication/sharing of data between different components.

But before we jump into that, we need to learn a bit about Angular Subjects.

Angular Subjects made easy

Subjects are quite similar to the Observables we have already seen, but they have a couple of extra interesting traits:

  • Unlike regular observables, Subjects can both subscribe and emit data .
  • They are multicast: meaning that there can be N subscriptions for a single Subject.

Angular application with different components that need to share data

To illustrate these points we are going to create 2 different components and add them to our application. The components will be ComponentSender, a component that emit messages, and a component that receives those messages: ComponentReceiver.

Let’s start with our ComponentReceiver: a simple component that stores a receivedMesage and displays it:

Html template:

<div>{{_receivedMessage}}</div>

Component:

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

@Component({
  selector: 'component-receiver',
  templateUrl: './app.componentReceiver.html'
})
export class ComponentReceiver {
  private _receivedMessage:string = "waiting ...";

  public get receivedMessage():string{
    return this._receivedMessage;
  }
}

Let’s continue with our sender: ComponentSender contains an input element where the user can write a message, and a button to send it … hopefully to our ComponentReceiver.

Html template:

html template for sender component

Component:

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

@Component({
  selector: 'component-sender',
  templateUrl: './app.componentSender.html'
})
export class ComponentSender {
  private _messageToSend:string = null;

  public sendMessage():void{
    console.log(this._messageToSend);
  }
  public get messageToSend():string {
    return this._messageToSend;
  }

  public set messageToSend(value:string) {
    this._messageToSend = value;
  }

}

For the time being ComponentSender instead of sending the data to the receiver is simply printing it in the console.

It looks like this:

Angular sender and receiver components

So now let’s modify out application so that the sender can actually send data to the receiver.

Use Subject to share data between components

For starters we are going to create a messaging service. We will use a Subject to emit and observe messages:

import { Injectable } from '@angular/core';
import { Observable, Subject } from 'rxjs';

@Injectable()
export class MessagingService {
    private _subject = new Subject();

    sendMessage(message: string) {
        this._subject.next(message);
    }

    getMessage(): Observable {
        return this._subject.asObservable();
    }
}

As stated above, Subjects can emit data: in the sendMessage function we are calling to that purpose the next function of the Subject. Subjects can act as observables, so in the function getMessage we are exposing the Subject as observable.

We are going to inject this service in our component sender:

import { Component } from '@angular/core';
import {MessagingService} from '../../services/messagingService.service';

@Component({
  selector: 'component-sender',
  templateUrl: './app.componentSender.html'
})
export class ComponentSender {
  private _messageToSend:string = null;

  constructor(private _messagingService: MessagingService){}

  public sendMessage():void{
    this._messagingService.sendMessage(this._messageToSend);
  }

  public get messageToSend():string {
    return this._messageToSend;
  }

  public set messageToSend(value:string) {
    this._messageToSend = value;
  }
}

Notice that we have modified the sendMessage function: now it does not print the message to console; instead it uses the messaging service to instruct the Subject to emit the message string.

Next we need to modify the receiver component to inject the messaging service, and to subscribe to the messages emitted by the sender:

import { Component, OnInit, OnDestroy } from '@angular/core';
import { MessagingService} from '../../services/messagingService.service';
import { Subscription } from 'rxjs';

@Component({
  selector: 'component-receiver',
  templateUrl: './app.componentReceiver.html'
})
export class ComponentReceiver implements OnInit, OnDestroy {

  private _receivedMessage:string = "waiting ...";
  private _subscription: Subscription;

  constructor(private _messagingService: MessagingService){}

  public ngOnInit(): void {
    this._subscription = this._messagingService.getMessage().subscribe((message:string) => {
      this._receivedMessage = message;
    });
  }

  public ngOnDestroy(): void {
    this._subscription.unsubscribe();
  }

  public get receivedMessage():string{
    return this._receivedMessage;
  }
}

The receiver component has undergone a few modifications:

  • When we initialize the component – OnInit – we create a subscription to consume the messages emitted by the subject.
  • In order to avoid memory leaks we have to unsubscribe when leaving the component. So we are implementing OnDestroy.

Finally we have to add the messaging service in the providers section of the AppModule module.

Advertisements