1

I have a toast notification component called ToastComponent which I want to call from any other component. I implemented it like this:

ToastComponent:

export class ToastComponent implements OnInit {

  constructor() {}

  showToast() {
    // some code
  }
}

app.component.html:

<llqa-main-container>
  <llqa-header></llqa-header>
  <div class="content-container">
    <main class="content-area">
      <llqa-toast></llqa-toast> <!-- ToastComponent which I want to call -->
      <router-outlet></router-outlet>
    </main>
  </div>
</llqa-main-container>

UserManagementComponent which is inside the <router-outlet>:

export class UserManagementComponent implements OnInit {

  @ViewChild(ToastComponent) toast: ToastComponent;

  constructor() {}

  someSaveMethod() {
    this.toast.showToast() // throws error below
  }
}

On calling the someSaveMethod() method, I'll get the error that toast is undefined.

If I take <llqa-toast></llqa-toast>out of the app.component.html and put it on top of the user-management.component.html, it works fine, but then I have to put it in every component. How can I get this to work?

Maxim Kuzmin
  • 2,574
  • 19
  • 24
nintschger
  • 1,786
  • 5
  • 30
  • 45
  • 2
    Where is `someSaveMethod` being called? Try to check with `console.log()` statements whether the constructor of `ToastComponent` is called before your call to `someSaveMethod`. – Günter Zöchbauer Aug 16 '17 at 09:23

1 Answers1

4

Since in your case, the ToastComponent is used in the grand parent (AppComponent), that's why you are getting this error. One way to avoid this error is to use Subject defined in some shared service. I am using that approach in my project to show toast notifications. Here is how you can do it:


Keep your <llqa-toast></llqa-toast> in app.component.html.

Define a service to basically emit an event and subscribe to that event in your ToastComponent. For example,

UtilityService:

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

@Injectable()
export class UtilityService {

    public OnShowToast = new Subject<boolean>();

    public showToast(): void {
        this.OnShowToast.next(true);
    }
}

You need to inject this service in your AppModule providers. Now subscribe to the OnShowToast event in your ToastComponent.

ToastComponent:

import { UtilityService } from './path/to/the/utility.service';
export class ToastComponent implements OnInit {

  constructor(private readonly utilityService: UtilityService) { }

  ngOnInit() { 
     this.utilityService.OnShowToast.subscribe(value =>
        {
            this.showToast();
        });
  }

  private showToast() {
    // some code
  }
}

Now, you can call the showToast() method of the UtilityService from any component you want. For example,

UserManagementComponent

export class UserManagementComponent implements OnInit {

  // You dont need this now
  // @ViewChild(ToastComponent) toast: ToastComponent;

  constructor(private readonly utilityService: UtilityService) {}

  someSaveMethod() {
    this.utilityService.showToast();
  }
}
FAISAL
  • 33,618
  • 10
  • 97
  • 105
  • 1
    `EventEmitter` is not supposed to be used in services. An `Observable` or `Subject` does the same. `EventEmitter` is supposed to be used **only** for `@Output()`s. – Günter Zöchbauer Aug 16 '17 at 12:33
  • @GünterZöchbauer Didn't know that, since it was also working without any issues in my service. Thanks. Updating my code here as well to use `Subject` instead. – FAISAL Aug 16 '17 at 12:38
  • 1
    Currently it's working because `EventEmitter` just extends `Subject` (AFAIR), but the Angular team can change this without prior notice to a custom implementation that still works for `@Output()` but might break other uses, because `@Output()` is the only thing `EventEmitter` is supposed to be used for. – Günter Zöchbauer Aug 16 '17 at 12:41
  • 1
    @GünterZöchbauer Thanks for the detailed info, +1 – FAISAL Aug 16 '17 at 12:47