![Cute Tiger Cub](https://private-user-images.githubusercontent.com/2646053/259884743-6c824179-1623-4a84-a88c-7d94648f8eae.png?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3MjMyNDEzMTksIm5iZiI6MTcyMzI0MTAxOSwicGF0aCI6Ii8yNjQ2MDUzLzI1OTg4NDc0My02YzgyNDE3OS0xNjIzLTRhODQtYTg4Yy03ZDk0NjQ4ZjhlYWUucG5nP1gtQW16LUFsZ29yaXRobT1BV1M0LUhNQUMtU0hBMjU2JlgtQW16LUNyZWRlbnRpYWw9QUtJQVZDT0RZTFNBNTNQUUs0WkElMkYyMDI0MDgwOSUyRnVzLWVhc3QtMSUyRnMzJTJGYXdzNF9yZXF1ZXN0JlgtQW16LURhdGU9MjAyNDA4MDlUMjIwMzM5WiZYLUFtei1FeHBpcmVzPTMwMCZYLUFtei1TaWduYXR1cmU9YTI0MGUwZjdmYjVlN2JkYmI1N2IzODZlZGJhNWI3NzM0ZmNmYzZlZTc2MjI0MjVmYmM1ZWE4ODc2OTdhODg0NiZYLUFtei1TaWduZWRIZWFkZXJzPWhvc3QmYWN0b3JfaWQ9MCZrZXlfaWQ9MCZyZXBvX2lkPTAifQ.lKC30YDU3gutpSI34q1XgS6p5rpH1fpKZECbGsIxWzk)
This is a companion repo for RxJS in the Wild: How to Create Pop-up Alerts that demonstrates how to build a beautifully simple Alert system using Angular, ng-bootstrap, and RxJS.
Clone this repo to your workspace:
git clone https://github.com/bobbyg603/rxjs-in-the-wild-alerts
Install the dependencies and start the application:
cd rxjs-in-the-wild-alerts && npm i && npm start
Point your browser to https://localhost:4200 and click on the buttons to reveal errors of various colors. You can also toggle off the Auto Dismiss button to keep the alerts around until you interact with them.
In alert.service.ts we created a stateful service that contains an observable array of alerts$
to be displayed by the Alert Component. We need ways to push alerts, dismiss alerts, and dismiss alerts after a timeout if the alert should be dismissed automatically.
import { HttpErrorResponse } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { firstValueFrom, Observable, Subject, timer } from 'rxjs';
import { Alert, AlertColor } from './alert';
@Injectable({
providedIn: 'root',
})
export class AlertService {
private _state: Alert[] = [];
private _alertSubject = new Subject<Alert[]>();
private _alerts$?: Observable<Alert[]>;
get alerts$(): Observable<Alert[]> {
if (!this._alerts$) {
this._alerts$ = this._alertSubject.asObservable();
}
return this._alerts$;
}
dismissAlert(id: string): void {
this.removeAlert(id);
}
pushAlert(message: string, color: AlertColor = AlertColor.Red, autoDismiss: boolean = true): void {
const alert = new Alert(message, color, autoDismiss);
if (autoDismiss) {
firstValueFrom(
timer(5000)
).then(() => this.removeAlert(alert.id));
}
this.addAlert(alert);
}
private addAlert(alert: Alert): void {
this._state = this._state.concat(alert);
this._alertSubject.next(this._state);
}
private removeAlert(id: string): void {
this._state = this._state.filter(alert => alert.id !== id);
this._alertSubject.next(this._state)
}
}
In alert.component.ts we expose the alerts$
observable and expose a mechanism to dismiss alerts when they are clicked.
import { Component, Input, Output, EventEmitter } from '@angular/core';
import { Observable } from 'rxjs';
import { Alert } from "./alert";
import { AlertService } from './alert.service';
@Component({
selector: 'app-alert',
templateUrl: './alert.component.html',
styleUrls: ['./alert.component.scss']
})
export class AlertComponent {
alerts$: Observable<Alert[]>;
constructor(private _alertService: AlertService) {
this.alerts$ = this._alertService.alerts$;
}
onRemoveAlertClick(id: string): void {
this._alertService.dismissAlert(id);
}
}
The alert.component.html template uses *ngFor
and the async
pipe to display the observable alerts$
collection. A class is added to the alert div by transforming the alert via the alertColorClassName
pipe and using the resulting value as an input to the div's ngClass
property.
<ng-container *ngIf="alerts$ | async as alerts">
<ul *ngIf="alerts?.length">
<li *ngFor="let alert of alerts">
<div class="d-flex justify-content-between rounded cursor-pointer p-3 mt-2 w-100"
[ngClass]="alert | alertColorClassName" (click)="onRemoveAlertClick(alert.id)">
<div></div>
<div>
{{alert.message}}
</div>
<div>
<button type="button" class="btn-close float-right" aria-label="Close"></button>
</div>
</div>
</li>
</ul>
</ng-container>
In alert-color-class-name.pipe.ts we add a transform that converts the AlertColor
enum to a Bootstrap color class.
import { Pipe, PipeTransform } from '@angular/core';
import { Alert, AlertColor } from './alert';
@Pipe({
name: 'alertColorClassName'
})
export class AlertColorClassNamePipe implements PipeTransform {
transform(alert: Alert): string {
if (alert?.color === AlertColor.Green) {
return 'alert-success';
}
if (alert?.color === AlertColor.Yellow) {
return 'alert-warning';
}
return 'alert-danger';
}
}
Finally, in app.component.ts we add some methods that we can use to display alerts.
import { Component } from '@angular/core';
import { Title } from '@angular/platform-browser';
import { AlertColor } from './alert/alert';
import { AlertService } from './alert/alert.service';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss']
})
export class AppComponent {
autoDismiss = true;
readonly AlertColor = AlertColor;
constructor(
private _alertService: AlertService,
title: Title
) {
title.setTitle('RxJS in the Wild | Alerts');
}
onShowAlertClick(message: string, color: AlertColor): void {
this._alertService.pushAlert(message, color, this.autoDismiss);
}
}
If you liked this example, please follow me on Medium and X, where I post programming tutorials and discuss tips and tricks that have helped make me a better programmer.
Thank you for your support ❤️