Using MatSnackBar in a library

1.7k views Asked by At

I have an issue with using MatSnackBar from within a library. I have created a simple component which looks like this:

<div class="situ-error-snackbar">
    <span>{{message}}</span>
    <button type="button" mat-icon-button (click)="close()">
        <mat-icon>closer</mat-icon>
    </button>
</div>

The code just looks like this:

import {
  Component,
  Inject,
  Input,
  OnInit,
  ViewEncapsulation,
} from '@angular/core';

import {
  MatSnackBarRef,
  MAT_SNACK_BAR_DATA,
} from '@angular/material/snack-bar';

@Component({
  selector: 'situ-error-snackbar',
  templateUrl: './error-snackbar.component.html',
  styleUrls: ['./error-snackbar.component.scss'],
  encapsulation: ViewEncapsulation.None,
})
export class ErrorSnackbarComponent implements OnInit {
  @Input() duration: number = 5000;

  public constructor(
    @Inject(MAT_SNACK_BAR_DATA) public message: string,
    public snackBarRef: MatSnackBarRef<ErrorSnackbarComponent>
  ) {}

  public ngOnInit(): void {}

  public close(): void {
    this.snackBarRef.dismiss();
  }
}

And I have a service which invokes it:

public show(notifcation: Notification): void {
  let config: MatSnackBarConfig = {
    panelClass: notifcation.type,
    verticalPosition: 'top',
    duration: 5000,
    data: notifcation.message,
  };

  this.snackBar.openFromComponent(ErrorSnackbarComponent, config);
}

All of this is in a custom library I have been developing. The error-snackbar has a module which looks like this:

import { NgModule } from '@angular/core';

import { MatButtonModule } from '@angular/material/button';
import { MatIconModule } from '@angular/material/icon';
import { MatSnackBarModule } from '@angular/material/snack-bar';

import { ErrorSnackbarComponent } from './error-snackbar.component';

@NgModule({
  imports: [MatButtonModule, MatIconModule, MatSnackBarModule],
  declarations: [ErrorSnackbarComponent],
  exports: [ErrorSnackbarComponent],
})
export class ErrorSnackbarModule {}

In my application, I want to display the ErrorSnackbar whenever there is an error, so I added it to the AppComponent as follows:

<router-outlet></router-outlet>

<situ-error-snackbar></situ-error-snackbar>

And I import the library module in app.module like this:

@NgModule({
    declarations: [AppComponent],
    imports: [
        BrowserModule,
        BrowserAnimationsModule,
        HttpClientModule,
        CoreModule,
        AppRoutingModule,

        MatAutocompleteModule,
        MatDatepickerModule,
        MatNativeDateModule,
        MatSelectModule,

        ErrorSnackbarModule,
    ],
    providers: [
        { provide: HTTP_INTERCEPTORS, useClass: BearerAuthInterceptor, multi: true },
        { provide: HTTP_INTERCEPTORS, useClass: ErrorInterceptor, multi: true },
        { provide: MAT_DATE_LOCALE, useValue: 'en-GB' },
    ],
    bootstrap: [AppComponent],
})
export class AppModule {}

In my opinion, this should just work. I have many other components that are in that library and they all work fine. But when I try to run my application in the state above, I get this error:

ERROR NullInjectorError: R3InjectorError(AppModule)[InjectionToken MatSnackBarData -> InjectionToken MatSnackBarData -> InjectionToken MatSnackBarData]: NullInjectorError: No provider for InjectionToken MatSnackBarData!

I googled the error and found something similar here: https://github.com/angular/universal-starter/issues/493

So I did what they said and added this to the app.module:

{
    provide: MAT_SNACK_BAR_DATA,
    useValue: {}, // Add any data you wish to test if it is passed/used correctly
},

When I do that, I get a new error:

ERROR NullInjectorError: R3InjectorError(AppModule)[MatSnackBarRef -> MatSnackBarRef -> MatSnackBarRef]: NullInjectorError: No provider for MatSnackBarRef!

So I tried to add another provider like this:

    {
        provide: MatSnackBarRef,
        useClass: MatSnackBarRef,
    },

And now I get a new message:

main.ts:12 Error: Can't resolve all parameters for MatSnackBarRef: (?, ?).

I don't understand why I am having to mess around with providers. Does anyone know what I am doing wrong?

1

There are 1 answers

0
Alfred On

When you see this type of message

main.ts:12 Error: Can't resolve all parameters for MatSnackBarRef: (?, ?).

It's a pretty clear error message that tells you that the dependency you're trying to inject (MatSnackBarRef) has two other dependencies and the dependency injection mechanism doesn't find them. Each question mark ? stand for one dependency in the constructor of the service.

The issue stems from the fact that you wrote

{
        provide: MatSnackBarRef,
        useClass: MatSnackBarRef,
},

Here you're trying to inject the real service MatSnackBarRef which has two dependencies (cf the constructor of MatSnackBarRef)

When unit testing you should try to mock dependencies as much as possible to avoid getting into a dependency hell (adding dependencies of dependencies in your testbed)

Here's a simple mock of the dependency (just use an empty object), but you can replace whatever property is needed in your test with your own implementation

{
        provide: MatSnackBarRef,
        useValue: {},
},