Services are not only great for sharing data, they can also provide notifications. we can use the service to broadcast a notification to components/services about state changes without the need to wait for the component to ask for the new state.
We will examine 3 techniques of broadcasting service notification:
- EventEmitter (not a good choice)
- Subject
- BehaviorSubject
instead of waiting to a component to ask for new state, especially if the value is not bound to the template, the service can broadcast a notification. this basically pushes the change to any components subscribing to this notifications.
How Notification work
- The component call a method in the service, notifying it of the change.
- The service then broadcast a notification
- Any component or service can listen for that notification and respond accordingly.
So how does the service broadcast this notification?
Broadcast notification with Eventemmiter
The first technique that comes to mind is EventEmmiter because basically we want to emit an event. but best practices discourage use of the EventEmmiter except with an output property (when we use to send an event outside from child component to parent component).
Let’s see an example of broadcasting notification with Eventemmiter but keep in mind that this is not the recommended way.
In the service we will provide a custom event with the EvenetEmmiter. we will trigger this event from a component that needs to broadcast something and another component will subscribe to this custom event to get notify when there is a broadcast.
import { EventEmitter, Injectable } from "@angular/core";
@Injectable({
providedIn: "root"
})
export class NotificationService {
constructor() {}
messageNotification = new EventEmitter<string>();
}
import { Component, OnInit } from "@angular/core";
import { NotificationService } from "./notification.service";
@Component({
selector: "child1",
template: `
<div>
<button (click)="onButtonClick()">Update Message</button>
</div>
`,
styles: []
})
export class Child1Component implements OnInit {
constructor(private notificationService: NotificationService) {}
ngOnInit(): void {}
onButtonClick() {
this.notificationService.messageNotification.emit(
`message: sending notification with the EventEmitter ${Math.random()}`
);
}
}
import { Component, OnInit } from "@angular/core";
import { NotificationService } from "./notification.service";
@Component({
selector: "child2",
template: `
<div>{{ message }}</div>
`,
styles: []
})
export class Child2Component implements OnInit {
message: string;
constructor(private notificationService: NotificationService) {
this.notificationService.messageNotification.subscribe((msg: string) => {
this.message = msg;
});
}
ngOnInit(): void {}
}
Broadcast notification with a Subject
A key purpose of a subject is to send out notifications. we can use subject anywhere in our applicaton but often encapsulate them in a service.
Subject is a special type of observable that can multicast a value or event to multiple subscribers. any component or a service that subscribes to the subject’s observable will receive notifications .
Subject is also an observer, an observer allows us to push new data into an observable sequence. any component or other service can feed new values into the subject using its next method. the new value then multicast to all subscribers.
In situations that we can’t use binding and we need explicit notifications from a service.
In one component we will call a method on the service and pass it a new value. in the service we will receive the data and we will call the next method on the subject to push the new value into the observable sequence. this broadcast notification and passing along the new valve.
we will make the subject private so that no other code can access it and we will use the asObservable method of the subject to expose its observable. any component or service can subscribe to this observable to receive notification.
In the component that needs to be notified, we will subscribe to the subject’s observable to receive notification.
import { Injectable } from "@angular/core";
import { Subject } from "rxjs";
@Injectable({
providedIn: "root"
})
export class NotificationService {
constructor() {}
private messageSource = new Subject<string>();
messageChanges$ = this.messageSource.asObservable();
changeMessage(message: string) {
this.messageSource.next(message);
}
}
import { Component, OnInit } from "@angular/core";
import { NotificationService } from "./notification.service";
@Component({
selector: "child1",
template: `
<div>
<button (click)="onButtonClick2()">
Send Notification with Subject
</button>
<div>
`,
styles: []
})
export class Child1Component implements OnInit {
constructor(private notificationService: NotificationService) {}
ngOnInit(): void {}
onButtonClick2() {
this.notificationService.changeMessage(
`message: sending notification with the Subject ${Math.random()}`
);
}
}
import { Component, OnInit } from "@angular/core";
import { NotificationService } from "./notification.service";
@Component({
selector: "child2",
template: `
<div>{{ message2 }}</div>
`,
styles: []
})
export class Child2Component implements OnInit {
message2: string;
constructor(private notificationService: NotificationService) {
this.notificationService.messageChanges$.subscribe((msg: string) => {
this.message2 = msg;
});
}
ngOnInit(): void {}
}
Broadcast notification with a BehaviorSubject
In the example above we used the subject to broadcast a change. the subject sent a value to the component that subscribed to it. if we navigate away from the component, means it will be destroyed and then we will navigate back, means it will be initialized again,it won’t remember the last notification from the subject.
For the component to “remember” the last notification we need to use a different kind of subject, we need a behaviorSubject. The behaviorSubject works like a subject except of 2 key features:
- require an initial value
- provide the current value on a new subscription
It means that when the component destroyed and reinitialized, a new subscription is created, since the subscription is for the behaviorSubject, the component will receive the last value from the behaviorSubject’s broadcast . means the component will “remember the value “.
import { EventEmitter, Injectable } from "@angular/core";
import { BehaviorSubject } from "rxjs";
@Injectable({
providedIn: "root"
})
export class NotificationService {
constructor() {}
private messageSource2 = new BehaviorSubject<string>(null);
messageChanges2$ = this.messageSource2.asObservable();
changeMessage2(message: string) {
this.messageSource2.next(message);
}
}
import { Component, OnInit } from "@angular/core";
import { NotificationService } from "./notification.service";
@Component({
selector: "child1",
template: `
<div>
<button (click)="onButtonClick3()">
Send Notification with BehaviorSubject
</button>
</div>
`,
styles: []
})
export class Child1Component implements OnInit {
constructor(private notificationService: NotificationService) {}
ngOnInit(): void {}
onButtonClick3() {
this.notificationService.changeMessage2(
`message: sending notification with the BehaviorSubject ${Math.random()}`
);
}
}
import { Component, OnInit } from "@angular/core";
import { NotificationService } from "./notification.service";
@Component({
selector: "child2",
template: `
<div>{{ message3 }}</div>
`,
styles: []
})
export class Child2Component implements OnInit {
message3: string;
constructor(private notificationService: NotificationService) {
this.notificationService.messageChanges2$.subscribe((msg: string) => {
this.message3 = msg;
});
}
ngOnInit(): void {}
}