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?

1
  • 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. Commented Aug 16, 2017 at 9:23

1 Answer 1

3

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

4 Comments

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ü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.
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ünterZöchbauer Thanks for the detailed info, +1

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.