21

I have a working snackbar, but it is only on each component, I want to add it on my service so I will just call it. This is my sample on my component.ts

import { MdSnackBar, MdSnackBarRef } from '@angular/material';
...
export class EmployeeListComponent implements OnInit {
  public toastRef: MdSnackBarRef<any>;
  constructor(private _activatedRoute:ActivatedRoute,private router: Router, private http:PMISHttpService, private toast: MdSnackBar) {

  ngOnInit() {
    this.notify('test');
  }
  ...
  notify (text: string) {
    this.toastRef = this.toast.open(text, null);
    setTimeout(() => {
      this.toastRef.dismiss();
    }, 5000);
  }
  ...
}
Edric
  • 24,639
  • 13
  • 81
  • 91
Storm Spirit
  • 1,440
  • 4
  • 19
  • 42

5 Answers5

22

If you want a SnackBar to work across your entire app, you should put it into your app.component and communicate with it with a service.

notification.service.ts:

public notification$: Subject<string> = new Subject();

app.component.ts:

constructor(
  private notificationService: NotificationService, private snackBar: MatSnackBar
) {
  this.notificationService.notification$.subscribe(message => {
    this.snackBar.open(message);
  });
}

any.component.ts:

this.notificationService.notification$.next('this is a notification');
jabu.hlong
  • 2,194
  • 20
  • 21
Ploppy
  • 14,810
  • 6
  • 41
  • 58
  • 1
    where to put the notify function? sorry for being newbie – Storm Spirit Mar 15 '17 at 08:36
  • You create a global service, which has a 'subj_notification' rxjs subject, your app.component has the snackBar and is subscribed to 'subj_notification'. At this point any component in your app can call subj_notification.next() of the service and your app component will be notified of the change of the subject. – Ploppy Mar 15 '17 at 18:51
  • @Ploppy you're exposing the whole Subject while it's better to keep it private (and expose public setter). This way no client code to that service can e.g. complete it. – Tomek Jul 15 '22 at 08:38
15

To have it everywhere, create a service for it. Also you should use the snackbar config for setting duration and make snackbar public:

import {Injectable, NgZone} from '@angular/core';
import {MatSnackBar} from '@angular/material';

@Injectable()
export class CustomSnackbarService {

    constructor(
      public snackBar: MatSnackBar,
      private zone: NgZone
    ) {}

    public open(message, action = 'success', duration = 50000) {
        this.zone.run(() => {
            this.snackBar.open(message, action, { duration });
        });
    }
}

Also it needs to be run in ngZone: https://github.com/angular/material2/issues/9875

Then in the component:

customSnackbarService.open('hello')

Hans Daigle
  • 364
  • 2
  • 14
mchl18
  • 2,119
  • 12
  • 20
  • This worked great for me! I just did a small modification to allow the SnackBarRef to be accessible to the caller: return this.zone.run(() => { return this.snackBar.open(message, action, { duration }); }); – BobtheMagicMoose Jun 29 '19 at 14:16
  • 1
    That's interesting, does `zone.run` return the inner function? – mchl18 Jul 04 '19 at 15:04
  • 1
    Not sure if `NgZone` is still needed. I tried without and the code seems to work anyway (using Angular 10). Anyway, thanks for the solution! – umbe1987 Sep 08 '20 at 13:14
  • Very much possible – mchl18 Sep 09 '20 at 17:40
13

Here is my working example (Angular 11, Angular Material 11.0.1).

The most important part is to include MatSnackBarModule in the app.module.ts. Also, don't forget to import BrowserAnimationsModule as well.

import { MatSnackBarModule } from '@angular/material/snack-bar';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';

@NgModule({
  imports: [
    MatSnackBarModule,
    BrowserAnimationsModule
    ...
  ],

Then, my service looks like this

import { Injectable } from '@angular/core';
import { MatSnackBar } from '@angular/material/snack-bar';

@Injectable({
  providedIn: 'root'
})
export class SnackbarService {

  constructor(
    private _snackBar: MatSnackBar) {
  }

  error(message: string) {
    return this._snackBar.open(message, undefined, {panelClass: ['snackbar-error']});
  }

  success(message: string) {
    return this._snackBar.open(message, undefined, {panelClass: ['snackbar-success']});
  }

  info(message: string) {
    return this._snackBar.open(message, undefined, {panelClass: ['snackbar-info']});
  }
}

To define styles, I added these to styles.scss

.mat-simple-snackbar {
  font-size: 1.2em;
  color: white;
}

.snackbar-error {
  background-color: red;
}

.snackbar-success {
  background-color: green;
}

.snackbar-info {
  background-color: blue;
}

This way, I am now able to call SnackBar from anywhere in the code (including components from other modules). Usage example:

import { Component } from '@angular/core';
import { AuthService } from 'src/app/services/auth/auth.service';
import { SnackbarService } from 'src/app/services/snackbar.service';

@Component({
  selector: 'app-login',
  templateUrl: './login.component.html',
  styleUrls: ['./login.component.scss']
})
export class LoginComponent {

  loginForm: any;

  constructor(private authService: AuthService, private snackbar: SnackbarService) { }

  onSubmit() {
      this.authService.login(this.loginForm).subscribe(res => {
        this.snackbar.success('Logged in');
      }, e => {
        this.snackbar.error('Login failed');
      });
  }

}

Tomas Lukac
  • 1,923
  • 2
  • 19
  • 37
  • 1
    Works perfectly with Angular 10 with Angular Material ^10.2.7 You can upgrade snackbars with extra properties, like fading away after 5 sec, or relocate the bar from bottom to top, like this: `error(message: string) { return this._snackBar.open(message, null, { duration: 5000, verticalPosition: 'top', horizontalPosition: 'center', panelClass: ['snackbar-error'] }); }` – Shephard May 12 '21 at 08:22
  • Great answer, you should use `undefined` instead of `null` though! – andrès coronado Jul 30 '21 at 09:08
  • @andrèscoronado Why `undefined` ? – Tomas Lukac Jul 30 '21 at 09:15
  • 1
    @TomasLukac ```typescript (method) MatSnackBar.open(message: string, action?: string | undefined, config?: MatSnackBarConfig | undefined): MatSnackBarRef``` – andrès coronado Aug 03 '21 at 15:41
  • 1
    Very good and easy answer by @TomasLukac I added the extra parameter to auto dismiss the snackbar `{ panelClass: ['snackbar-info'], duration: 5000 }` – Newton Suhail Jan 17 '22 at 08:06
10

You can easily do this . Please find below the example for the sample which i used in one of my projects and it works perfectly fine

import { Injectable } from '@angular/core';
import {
  MatSnackBar,
  MatSnackBarConfig,
  MatSnackBarHorizontalPosition,
  MatSnackBarVerticalPosition,
  MatSnackBarRef
} from '@angular/material';

@Injectable()
export class SnackBarService {

  snackBarConfig: MatSnackBarConfig;
  snackBarRef: MatSnackBarRef<any>;
  horizontalPosition: MatSnackBarHorizontalPosition = 'center';
  verticalPosition: MatSnackBarVerticalPosition = 'top';
  snackBarAutoHide = '1500';

  constructor(private snackBar: MatSnackBar) { }

  openSnackBar(message) {
    this.snackBarConfig = new MatSnackBarConfig();
    this.snackBarConfig.horizontalPosition = this.horizontalPosition;
    this.snackBarConfig.verticalPosition = this.verticalPosition;
    this.snackBarConfig.duration = parseInt(this.snackBarAutoHide, 0);
    this.snackBarConfig.panelClass = 'custom-snackbar';
    this.snackBarRef = this.snackBar.open(message, '', this.snackBarConfig);
}

}

Now , you only need to inject this service in your component or where ever you want to use it and call openSnackBar() method with the message you want to show.

Hope this helps!!

Harmandeep Singh Kalsi
  • 3,315
  • 2
  • 14
  • 26
  • 1
    Simple and neat answer – Hidayt Rahman Jan 09 '21 at 07:33
  • Import need to be updated `from '@angular/material/snack-bar` Complete example `import { MatSnackBar, MatSnackBarConfig, MatSnackBarHorizontalPosition, MatSnackBarVerticalPosition, MatSnackBarRef } from '@angular/material/snack-bar''; – Saad Abbasi Feb 03 '23 at 14:27
3

I am using version version": "2.0.0-beta.10", This is what I did to get it working

In ApModule

import { NotificationService } from "./notification/notification.service";
import { MdSnackBarModule } from "@angular/material";

@NgModule({
  imports: [
    MdSnackBarModule,
    FormsModule
  ],
  providers: [WebService, NotificationService]

Create a notification service as suggested in previous post

import { Injectable } from "@angular/core";
import {
  MdSnackBar,
  MdSnackBarConfig,
  // MdSnackBarHorizontalPosition,
  // MdSnackBarVerticalPosition,
  MdSnackBarRef
} from "@angular/material";

@Injectable()
export class NotificationService {
  private snackBarConfig: MdSnackBarConfig;
  private snackBarRef: MdSnackBarRef<any>;
    private snackBarAutoHide = "5000"; //milliseconds for notification , 5 secs

  constructor(private sb: MdSnackBar) {}

  openSnackBar(message) {
    this.snackBarConfig = new MdSnackBarConfig();
    //this.snackBarConfig.horizontalPosition = this.horizontalPosition; only in current version Demo uses very old version . need to upgrade later
    //this.snackBarConfig.verticalPosition = this.verticalPosition; only in current version Demo uses very old version . need to upgrade later
    this.snackBarConfig.duration = parseInt(this.snackBarAutoHide, 0);
      this.sb.open(message, "", this.snackBarConfig);
  }
}

Consume service as shown below

   this.notify.openSnackBar(message);
Suresh R
  • 31
  • 3