8
votes

I am trying to use the Angular material module to open a model on click of a button. I have followed the example as suggested in the https://material.angular.io/components/dialog/examples.

However, I see that as try to run the application, I see the following error -

errors.js:48 ERROR Error: Uncaught (in promise): Error: StaticInjectorError[MatDialog]: 
  StaticInjectorError[MatDialog]: 
    NullInjectorError: No provider for MatDialog!
Error: StaticInjectorError[MatDialog]: 
  StaticInjectorError[MatDialog]: 
    NullInjectorError: No provider for MatDialog!
    at _NullInjector.get (injector.js:31)
    at resolveToken (injector.js:387)
    at tryResolveToken (injector.js:330)
    at StaticInjector.get (injector.js:170)
    at resolveToken (injector.js:387)
    at tryResolveToken (injector.js:330)
    at StaticInjector.get (injector.js:170)
    at resolveNgModuleDep (ng_module.js:103)
    at NgModuleRef_.get (refs.js:1037)
    at resolveDep (provider.js:455)
    at _NullInjector.get (injector.js:31)
    at resolveToken (injector.js:387)
    at tryResolveToken (injector.js:330)
    at StaticInjector.get (injector.js:170)
    at resolveToken (injector.js:387)
    at tryResolveToken (injector.js:330)
    at StaticInjector.get (injector.js:170)
    at resolveNgModuleDep (ng_module.js:103)
    at NgModuleRef_.get (refs.js:1037)
    at resolveDep (provider.js:455)
    at resolvePromise (zone.js:824)
    at resolvePromise (zone.js:795)
    at eval (zone.js:873)
    at ZoneDelegate.invokeTask (zone.js:425)
    at Object.onInvokeTask (ng_zone.js:575)
    at ZoneDelegate.invokeTask (zone.js:424)
    at Zone.runTask (zone.js:192)
    at drainMicroTaskQueue (zone.js:602)
    at ZoneTask.invokeTask [as invoke] (zone.js:503)
    at invokeTask (zone.js:1540)
defaultErrorLogger @ errors.js:48

I have the following setup of the project files

app.module.ts

I have imported the MatDialogModule and also placed the same in the imports. Within the Entry components, I have added the component that needs to be rendered in the modal.

import { MatDialogModule } from '@angular/material/dialog';

@NgModule({
  imports:      [ BrowserModule, BrowserAnimationsModule, FormsModule, NgbModule.forRoot(), ReactiveFormsModule, 
                  HttpClientModule, routing, MatTabsModule, MatDialogModule ],
  declarations: [ AppComponent, AppHeaderComponent, SignInComponent, RecruitmentHomeComponent, JobTemplatesComponent, CreateJobTemplateComponent ],
  bootstrap:    [ AppComponent ],
  entryComponents: [CreateJobTemplateComponent],
  providers: [AuthGuard, UserService, AuthenticationService]
})

Code in job-templates.component.ts

This is the file which is responsible for invoking the model on click of a button added to its template.

import { Component, OnInit } from '@angular/core';
import { CreateJobTemplateComponent } from './create-job-template/create-job-template.component';
import { MatDialog } from '@angular/material';


@Component({
  selector: 'app-job-templates',
  templateUrl: './job-templates.component.html',
  styleUrls: ['./job-templates.component.css']
})
export class JobTemplatesComponent implements OnInit {

  constructor(public dialog: MatDialog) { }

  ngOnInit() {
  }

  createjobtemplate(){
    let dialogRef = this.dialog.open(CreateJobTemplateComponent, {
      width: '250px'
    });
    //Set the dialogRef property of opened dialog with the obtained ref
    dialogRef.componentInstance.dialogRef = dialogRef;
  }

}

create-job-template.component.ts

This component will render itself in the modal dialog.

import { Component, Inject, OnInit } from '@angular/core';
import { MatDialogRef } from '@angular/material';
import { FormGroup, FormBuilder } from '@angular/forms';

@Component({
  selector: 'app-create-job-template',
  templateUrl: './create-job-template.component.html',
  styleUrls: ['./create-job-template.component.css']
})
export class CreateJobTemplateComponent implements OnInit {

  form: FormGroup;

  constructor(private formBuilder: FormBuilder, public dialogRef: MatDialogRef<CreateJobTemplateComponent>) { }

  ngOnInit() {
    this.form = this.formBuilder.group({
      filename: ''
    })
  }

  submit(form) {
    this.dialogRef.close(`${form.value.filename}`);
  }

}

Can anyone let me know where I am going wrong here? Most of the errors that I have looked for talk about failure to Inject MatDialogRef, but I find this error for MatDialog.

Also I was looking through this blog as well to make sure I followed the same steps. https://blog.thoughtram.io/angular/2017/11/13/easy-dialogs-with-angular-material.html

For Reference, I have the code at

https://stackblitz.com/edit/angular-fhhmff

4
What if you change the import import { MatDialog } from '@angular/material'; in ´JobTemplatesComponent´ to import { MatDialog } from '@angular/material/dialog';?SplitterAlex
I checked the example project, and it looks good, check the version of packages and the imports maybeMezo Istvan

4 Answers

3
votes

Had the same error. Turns out I was importing

import { MatDialog } from '@angular/material';

Should have been

import { MatDialog } from '@angular/material/dialog';

2
votes

it seems that you forgot to add the injector of the MAT_DIALOG_DATA in the constructor:

@Inject(MAT_DIALOG_DATA) public data: any

In the CreateJobTemplateComponent you need to import the MAT_DIALOG_DATA:

import { MAT_DIALOG_DATA } from '@angular/material';

and the constructor should be:

    constructor(@Inject(MAT_DIALOG_DATA) public data: any,
                private formBuilder: FormBuilder,
                public dialogRef: MatDialogRef<CreateJobTemplateComponent>) { }
1
votes

This is for Angular 9 / Angular Material 9

As teddybear mentioned:

import { MatDialog } from '@angular/material';

should be

import { MatDialog } from '@angular/material/dialog';

I had to restart my Angular Live Development Server to resolve the error.

Another common issue I see is missing MatDialogModule in AppModule imports (which you have) as the error is the same as this one

Also you no longer need entryComponents in AppModule

0
votes

My suggestion is to simplify the way your dialog gets instantiated using the pattern below. With this pattern, the "parent" component is the only thing that needs to be aware of managing the dialog. The template file looks something like this:

<ng-template #dialog_template>
    <app-the-dialog
        [input_data]="data"
        (ok)="onSelect($event)"
        (cancel)="onCancel()">
    </app-the-dialog>
</ng-template>

<div>
    <button (click)="openDialog()">Open Dialog</button>
    <!-- Everything else in the template goes here -->
</div>

The parent component manages the dialog something like this:

import { Component, ViewChild, TemplateRef } from '@angular/core';
import { MatDialog, MatDialogRef } from '@angular/material/dialog';
import { TheDialogComponent } from './the-dialog.component';

@Component({ ... })
export class YourComponent {
    @ViewChild('dialog_template', {static: true }) private dialog_template: TemplateRef<any>;
    private dialogRef: MatDialogRef<TheDialogComponent>;

    constructor(
        public dialog: MatDialog,
    ) { }

    public openDialog(): void {
        this.dialogRef = this.dialog.open(this.dialog_template, {
            width: '85%',
            height: '85%'
        });
    }

    public onSelect(data_emitted_from_dialog: any): void {
        // ...Respond to OK event...
        this.dialogRef.close();
    }

    public onCancel(): void {
        // ...Respond to Cancel event...
        this.dialogRef.close();
    }
}

You can develop TheDialogComponent independently of its caller, and the component structure looks exactly like a typical component, with no special syntax/awareness that it's a dialog.

I battled this very same NullInjectorError: No provider for MatDialogRef! error for hours, until I uncovered the pattern above. The official MatDialog docs make me angry for how complex they make it seem: The suggested syntax is really dense, and the wiring spans three disparate places: The parent component that triggers the dialog, the component that is the dialog, and the containing module's entryComponents property. It shouldn't be that hard to open a simple dialog box. This solution above is more elegant because:

  • The dialog component is just a component that declares @Inputs and @Outputs. No crazy injection syntax (constructor(@Inject(MAT_DIALOG_DATA) public data: any)? What?) just to get data in.
  • The dialog is declared within the "parent" component's template, right where you would expect to find it. (And referencing the dialog component in a template eliminates the need to declare it on the module entryComponents)
  • With the dialog component instantiated in the parent template, standard Angular [input] and (output) syntax binds directly to the parent model, which I bet is what you wanted in the first place.

Enjoy!