programmer561
programmer561

Reputation: 41

Display global error message in angular pages

I have child components nested in parent page components. Currently I have an event emitter that emits error events and messages to the parent component to be displayed if an error is encountered. Is there a way to bubble up error messages such that they are displayed on all pages. I am thinking about using the appcomponent file but not sure how to approach it.

Child:
@Output() errorEmitter = new EventEmitter();

errorEmission(message: string){
    this.errorEmitter.emit(message);
  }

Parent:
<app-manage (errorEmitter)='displayError($event)'></app-manage>


displayError(message: string) {
    this.errorMessageIsOn = true;
    this.errorMessageString = message;
  }

Is there a way of making this extensible so I don't have to rewrite this code for every page but just every component?

Upvotes: 3

Views: 3549

Answers (1)

Evaldas Buinauskas
Evaldas Buinauskas

Reputation: 14097

Briefly the idea is to totally decouple errors from UI logic:

  1. Create a notification (toaster) component that would subscribe to error, warning, informational, success messages
  2. Create a service that would be able to send messages and notification component to consume them.

Sample notification.service.ts:

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

@Injectable({
  providedIn: 'root'
})
export class NotificationService {
  private readonly errorsSubject$ = new Subject<string>();

  public errors$() { 
    return this.errorsSubject$.asObservable();
  }

  public showError(message: string) : void {
    this.errorsSubject$.next(message);
  }
}

Sample notification.component.ts, there should be just a single instance of it in your application.

import { Component, Input } from "@angular/core";
import { NotificationService } from "./notification.service";

@Component({
  selector: "notification",
  template: `
    <h2 *ngIf="(error$ | async) as error">Hello {{ error }}!</h2>
  `,
  styles: [
    `
      h1 {
        font-family: Lato;
      }
    `
  ]
})
export class NotificationComponent {
  error$ = this.notificationService.errors$();

  constructor(private readonly notificationService: NotificationService) {}
}

Also a sample component that can send messages, this could be any other component doing it.

import { Component, Input } from "@angular/core";
import { NotificationService } from "./notification.service";

@Component({
  selector: "hello",
  template: `
    <button (click)="onClick()">Show error</button>
  `,
  styles: [
    `
      button {
        font-family: Lato;
      }
    `
  ]
})
export class HelloComponent {
  @Input() name: string;
  constructor(private readonly notificationService: NotificationService) {}

  onClick(): void {
    this.notificationService.showError(
      `This error has been posted on ${Date.now()}`
    );
  }
}

So now as long as you inject notification service in any of your components and send message through that service, notification component will be able to subscribe to it and show them globally. This is a Stackblitz showing it working.

Obviously this is very simplified, you'd need to implement more than that, but this should put you on right track.

An improvement would be to remove error$ observable from notification service allowing only Notification component to access it. You could achieve that by implementing a internal notification service that would act as a bridge between notification service and notification component. What you win? Notification service no longer exposes error$ observable, only methods to send messages.

notification.service.ts

import { Injectable } from "@angular/core";
import { Subject } from "rxjs";
import { NotificationInternalService } from "./notification-internal.service";

@Injectable({
  providedIn: "root"
})
export class NotificationService {
  constructor(private readonly internalService: NotificationInternalService){}

  public showError(message: string): void {
    this.internalService.errorSubject$.next(message);
  }
}

notification-internal.service.ts

import { Injectable } from "@angular/core";
import { Subject } from "rxjs";

@Injectable({
  providedIn: "root"
})
export class NotificationInternalService {
  public errorSubject$ = new Subject<string>();

  public get error$() {
    return this.errorSubject$.asObservable();
  }
}

And notification.component.ts now references notification-internal.service.ts

import { Component, Input } from "@angular/core";
import { NotificationInternalService } from "./notification-internal.service";

@Component({
  selector: "notification",
  template: `
    <h2 *ngIf="(error$ | async) as error">Hello {{ error }}!</h2>
  `,
  styles: [
    `
      h1 {
        font-family: Lato;
      }
    `
  ]
})
export class NotificationComponent {
  error$ = this.service.error$;

  constructor(private readonly service: NotificationInternalService) {}
}

Upvotes: 4

Related Questions