Streamlining real-time updates with RxJS-powered notifications
Notifications are one of the main ways we can prompt users about relevant events or changes within the system. By utilizing Observables and operators, RxJS provides a powerful framework for managing these asynchronous notifications efficiently and effectively.
How to do it…
In this recipe, we’ll have an array of notifications to represent incoming notifications based on a user action, store them by ID, and remove them after a certain period. We’ll also provide support to manually remove notifications from a stack.
Step 1 – Stacking incoming notifications
To streamline the stack of notifications efficiently, inside NotificationService
, we’ll use BehaviorSubject
to represent all the notifications that may arrive over time asynchronously. We’ll also have a Subject that triggers an event when we want to add a new notification to the stack and another for dismissal:
private notifications$ = new BehaviorSubject<Notification[]>([]);
private addNotification$ = new Subject<Notification>();
private removeNotification$ = new Subject<string>();
addNotification(notification: Notification) {
this.addNotification$.next(notification);
}
removeNotification(id: string) {
this.removeNotification$.next(id);
}
So, whenever there’s an ongoing request for posting new data, we’ll combine these two actions with the latest state of the notification stack with the help of the withLatestFrom
operator and update its state:
get notifications(): Observable<Notification[]> {
return merge(
this.addNotification$,
this.removeNotification$
).pipe(
withLatestFrom(this.notifications$),
map(([changedNotification, notifications]) => {
if (changedNotification instanceof Object) {
this.notifications$.next([
...notifications,
changedNotification
]);
} else {
this.notifications$.next(notifications.filter
(notification =>
notification.id !== changedNotification)
);
}
return this.notifications$.value;
})
)
}
Based on the latest emitted value’s type
, we can decide whether a new notification needs to be added or filtered from the stack.
Step 2 – Reacting to a user action and displaying notifications
In our app.component.html
file, we have a simple button that will trigger a POST
request to add a new random cooking recipe:
<button (click)="sendRequest()">Add recipe</button>
Clicking that button will invoke a function:
sendRequest() {
this.recipeService.postRecipes();
}
In RecipeService
, we must implement the service method for sending the request to the BE API. If we get a successful response, we’ll perform a side effect to add a notification to the stack. If we get an error, we’ll display a notification that’s of the error
type:
getRecipes(): void {
this.httpClient.get<Recipe[]>('/api/recipes').pipe(
tap(() => {
this.notificationService.addNotification({
id: crypto.randomUUID(),
message: 'Recipe added successfully.',
type: 'success'
});
}),
catchError((error) => {
this.notificationService.addNotification({
id: crypto.randomUUID(),
message: 'Recipe could not be added.',
type: 'error'
});
return throwError(() =>
new Error('Recipe could not be added.'));
}),
).subscribe();
}
Finally, in NotificationComponent
, we can subscribe to the changes on notifications$
and display notifications:
<div class="container">
<div
*ngFor="let notification of notifications | async"
class="notification {{ notification.type }}"
>
{{ notification.message }}
<mat-icon
(click)="removeNotification(notification.id)"
class="close">
Close
</mat-icon>
</div>
</div>
Now, when we open our browser, we’ll see incoming notifications stacked on each other:

Figure 2.11: A reactive stack of notifications
Step 3 – Automatic notification dismissal
Previously, we could manually remove notifications from the stack by clicking the close button. Now, after a certain period, we want a notification to be automatically removed from the stack. Back in NotificationService
, when adding a notification to the stack initially, we’ll simply define a timer, after which we’ll call the removeNotification
method:
addNotification(
notification: Notification,
timeout = 5000
) {
this.addNotification$.next(notification);
timer(timeout).subscribe(() =>
this.removeNotification(notification.id));
}
See also
- The
BehaviorSubject
class: https://rxjs.dev/api/index/class/BehaviorSubject - The
Subject
class: https://rxjs.dev/api/index/class/Subject - The
withLatesFrom
operator: https://rxjs.dev/api/operators/withLatestFrom - Web API Crypto’s
randomUUID
method: https://developer.mozilla.org/en-US/docs/ - Web/API/Crypto/randomUUID