最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - Angular material Snackbar configuration with custom panelClass configuration for error, success, warning messages -

programmeradmin1浏览0评论

I have created a global snackBarService in my angular application. I want to customise the panelClass based on the type of message (error, success, warning etc.). The approach I took is to have a global config in the constructor, which helps to define global styles/configurations for the snack bar and will add custom classes to change the background colours based on the message type.

SnackBarService.ts

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

@Injectable({
  providedIn: "root",
})
export class SnackbarService {
  private config: MatSnackBarConfig;

  constructor(private snackbar: MatSnackBar, private zone: NgZone) {
    this.config = new MatSnackBarConfig();
    this.config.panelClass = ["snackbar-container"];
    this.config.verticalPosition = "top";
    this.config.horizontalPosition = "right";
    this.config.duration = 4000;
  }

  error(message: string) {
    this.config.panelClass = ["snackbar-container", "error"];
    this.show(message);
  }

  success(message: string) {
    this.config.panelClass = ["snackbar-container", "success"];
    this.show(message);
  }

  warning(message: string) {
    this.config.panelClass = ["snackbar-container", "warning"];
    this.show(message);
  }

  private show(message: string, config?: MatSnackBarConfig) {
    config = config || this.config;
    this.zone.run(() => {
      this.snackbar.open(message, "x", config);
    });
  }
}

app.scss

.snackbar-container {
  margin-top: 70px !important;
  color: beige;
  &.error {
    background-color: #c62828 !important;
  }
  &.success {
    background-color: #2e7d32 !important;
  }

  &.warning {
    background-color: #ff8f00 !important;
  }
}

And from the ponent I will be using the service like this

this.snackbarService.success("This message is from snackbar!!!");

The above code works perfectly.

But,

Since the panelClass does not have a .push method, I can't add dynamic classes, and because of this, I need to duplicate the global class every time like this this.config.panelClass = ["snackbar-container", "error"];

 error(message: string) {
    this.config.panelClass.push("error"); // this throws error in typescript
    this.show(message);
  }

Is there any better way to solve this problem?

I have created a global snackBarService in my angular application. I want to customise the panelClass based on the type of message (error, success, warning etc.). The approach I took is to have a global config in the constructor, which helps to define global styles/configurations for the snack bar and will add custom classes to change the background colours based on the message type.

SnackBarService.ts

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

@Injectable({
  providedIn: "root",
})
export class SnackbarService {
  private config: MatSnackBarConfig;

  constructor(private snackbar: MatSnackBar, private zone: NgZone) {
    this.config = new MatSnackBarConfig();
    this.config.panelClass = ["snackbar-container"];
    this.config.verticalPosition = "top";
    this.config.horizontalPosition = "right";
    this.config.duration = 4000;
  }

  error(message: string) {
    this.config.panelClass = ["snackbar-container", "error"];
    this.show(message);
  }

  success(message: string) {
    this.config.panelClass = ["snackbar-container", "success"];
    this.show(message);
  }

  warning(message: string) {
    this.config.panelClass = ["snackbar-container", "warning"];
    this.show(message);
  }

  private show(message: string, config?: MatSnackBarConfig) {
    config = config || this.config;
    this.zone.run(() => {
      this.snackbar.open(message, "x", config);
    });
  }
}

app.scss

.snackbar-container {
  margin-top: 70px !important;
  color: beige;
  &.error {
    background-color: #c62828 !important;
  }
  &.success {
    background-color: #2e7d32 !important;
  }

  &.warning {
    background-color: #ff8f00 !important;
  }
}

And from the ponent I will be using the service like this

this.snackbarService.success("This message is from snackbar!!!");

The above code works perfectly.

But,

Since the panelClass does not have a .push method, I can't add dynamic classes, and because of this, I need to duplicate the global class every time like this this.config.panelClass = ["snackbar-container", "error"];

 error(message: string) {
    this.config.panelClass.push("error"); // this throws error in typescript
    this.show(message);
  }

Is there any better way to solve this problem?

Share Improve this question asked May 23, 2020 at 8:07 Sarath S NairSarath S Nair 6233 gold badges15 silver badges29 bronze badges
Add a ment  | 

2 Answers 2

Reset to default 7

Angular Material actually provides you a native way for setting a default config, so you don't need to instantiate the MatSnackBarConfig and then set its values. In the module you import the MatSnackBarModule (App/Shared/Material module), add the following:

import { MatSnackBarModule, MatSnackBarConfig, MAT_SNACK_BAR_DEFAULT_OPTIONS } from '@angular/material/snack-bar';

const matSnackbarDefaultConfig: MatSnackBarConfig = {
  verticalPosition: 'top',
  horizontalPosition: 'right',
  duration: 4000,
};

@NgModule({
  // ...
  providers: [
    {
      provide: MAT_SNACK_BAR_DEFAULT_OPTIONS,
      useValue: matSnackbarDefaultConfig,
    },
  ],
})
export class MaterialModule { }

Then, you can use your service like this (I added a bit more of typing to it, feel free to remove them if you dont' like it and don't use strictNullChecks):

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

// I actually remend that you put this in a utils/helpers folder so you can use reuse it whenever needed
export const coerceToArray = <T>(value: T | T[]): T[] => (
  Array.isArray(value)
    ? value
    : [value]
);

@Injectable({
  providedIn: 'root',
})
export class SnackbarService {
  constructor(private snackbar: MatSnackBar, private zone: NgZone) { }

  error(message: string): void {
    this.show(message, { panelClass: ['snackbar-container', 'error'] });
  }

  success(message: string): void {
    this.show(message, { panelClass: ['snackbar-container', 'success'] });
  }

  warning(message: string): void {
    this.show(message, { panelClass: ['snackbar-container', 'warning'] });
  }

  private show(message: string, customConfig: MatSnackBarConfig = {}): void {
    const customClasses = coerceToArray(customConfig.panelClass)
      .filter((v) => typeof v === 'string') as string[];

    this.zone.run(() => {
      this.snackbar.open(
        message,
        'x',
        { ...customConfig, panelClass: ['snackbar-container', ...customClasses] },
      );
    });
  }
}

Also, since your public methods don't accept other configs to passes (eg. duration), you can reduce your service to this:

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

// Just add the new required types here and TypeScript will require the public consumer to pass a valid type
export type SnackBarType = 'error' | 'success' | 'warning'; 

@Injectable({
  providedIn: 'root',
})
export class SnackbarService {
  constructor(private snackbar: MatSnackBar, private zone: NgZone) { }

  show(message: string, type: SnackBarType): void {
    this.zone.run(() => {
      this.snackbar.open(
        message,
        'x',
        { panelClass: ['snackbar-container', type] },
      );
    });
  }
}

You could do this:

(this.config.panelClass as string[]).push('error');

but you will be adding classes without removing the ones that are there already. You would still need to reset the array with the initial class every time:

this.config.panelClass = ['snackbar-container']
发布评论

评论列表(0)

  1. 暂无评论