3

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?

1
  • maybe use a service to share messages to all components or ngrx Commented Nov 4, 2019 at 19:29

1 Answer 1

4

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) {}
}
Sign up to request clarification or add additional context in comments.

3 Comments

a weel done simple notification component 👍👍 , I like this error$ | async very much
Thanks @malbarmawi 👍
Thank you @EvaldasBuinauskas very helpful - appreciate the time that you took to post this solution

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.