Components: MatDialog: When using in dynamically loaded component throws error No provider for MatDialog

Created on 12 May 2020  路  19Comments  路  Source: angular/components

Reproduction

When using MatDialog in a dynamically loaded component it doesn't work and throws error No provider for MatDialog.

Steps to reproduce:

  1. Follow the instructions in this post (https://medium.com/angular-in-depth/lazy-load-components-in-angular-596357ab05d8 ) to create a dynamically loaded component
  2. Add MatDialogModule to the imports array of NGModule for the dynamically loaded component.
  3. ng serve and check the error in console.

Expected Behavior

It should work without throwing any error.

Actual Behavior

Throws below error:

zone-evergreen.js:659 Unhandled Promise rejection: R3InjectorError(AppBrowserModule)[MatDialog -> MatDialog -> MatDialog]: 
  NullInjectorError: No provider for MatDialog! ; Zone: <root> ; Task: Promise.then ; Value: NullInjectorError: R3InjectorError(AppBrowserModule)[MatDialog -> MatDialog -> MatDialog]: 
  NullInjectorError: No provider for MatDialog!
    at NullInjector.get (http://localhost:4200/vendor.js:34269:27)
    at R3Injector.get (http://localhost:4200/vendor.js:48199:33)
    at R3Injector.get (http://localhost:4200/vendor.js:48199:33)
    at R3Injector.get (http://localhost:4200/vendor.js:48199:33)
    at NgModuleRef$1.get (http://localhost:4200/vendor.js:65751:33)
    at Object.get (http://localhost:4200/vendor.js:63486:35)
    at getOrCreateInjectable (http://localhost:4200/vendor.js:38115:39)
    at Module.傻傻directiveInject (http://localhost:4200/vendor.js:52134:12)
    at NodeInjectorFactory.MyDynamicallyLoadedComponent_Factory [as factory] 

Environment

  • Angular: 10.0.0-next.6 (same behavior in other versions e.g. 9.x)
  • CDK/Material: 9.2.3
  • Browser(s): Chrome
  • Operating System (e.g. Windows, macOS, Ubuntu): Windows
materiadialog

Most helpful comment

Hello, any news on this?
I am having a similar issue.
Thanks.

All 19 comments

To reproduce the error, lazy load below component in AppModule

import { CommonModule } from '@angular/common';
import { Component, NgModule } from '@angular/core';
import { MatButtonModule } from '@angular/material/button';
import { MatDialog, MatDialogModule, MatDialogRef } from '@angular/material/dialog';

@Component({
  templateUrl: './my-lazy.component.html'
})
export class MylazyComponent {

  public dialogRef: MatDialogRef<any>;
  public dialogSub: any;

  constructor(public dialog: MatDialog) {

  }
}

@NgModule({
  imports: [
    CommonModule,
    MatButtonModule,
    MatDialogModule
  ],
  declarations: [MylazyComponent]
})
class MyLazyComponentModule {
}

Lazy import the above component in AppModule:

        const { MylazyComponent } = await import('./account/enable-notifications.component');
        const myLazyComponentFactory = this.cfr.resolveComponentFactory(MylazyComponent);
        this.myLazyComponentInstance = this.mylazyContainer.createComponent(myLazyComponentFactory, null, this.injector);

@naveedahmed1 any chance you could make a reproduction GitHub repo?

Thanks @jelbourn , can I add you to a private repo?

Sure

Thank you, I have added you to a private repo, just run ng serve and you will notice the error in the console. In AppComponent, I'm trying to dynamically load the EnableNotificationsComponent. Although the component file contains

@NgModule({
  imports: [
    CommonModule,
    MatButtonModule,
    MatDialogModule
  ],
  declarations: [
    EnableNotificationsComponent
  ]
})
class EnableNotificationsModule { }

its still throwing error Unhandled Promise rejection: R3InjectorError(AppBrowserModule)[MatDialog -> MatDialog -> MatDialog]: NullInjectorError: No provider for MatDialog! ; Zone: <root> ; Task: Promise.then ; Value: NullInjectorError: R3InjectorError(AppBrowserModule)[MatDialog -> MatDialog -> MatDialog]: NullInjectorError: No provider for MatDialog!

Same problem

I went to look today, but the invitation expired. The code you linked does look like it _should_ work, though.

I added you again, can you please try it now?

@naveedahmed1 I still get a 404 from the invite

That's strange, I just removed you from collaborators and added again

Seems to have worked that time; I'll try to take a peek tomorrow during my weekly scheduled triage time

Thank you so much @jelbourn :)

Hello, any news on this?
I am having a similar issue.
Thanks.

I believe this is the same issue as https://github.com/angular/components/issues/19616. You can check it out for a better explanation.

@naveedahmed1 can you try what @devversion mentioned in #19616? The main thing is to ensure that you're using the MatDialog instance (to call dialog.open) that's provided/imported from the same NgModule in which the lazy-loaded component is declared.

@jelbourn thank you for your response.

If you take a look at my comment here https://github.com/angular/components/issues/19335#issuecomment-629696419 I am doing exactly the same, but the only difference is I am loading this component dynamically and not through router.

@naveedahmed1 I think the issue here is that you use the ComponentFactoryResolver from the root module and directly create the lazy component. It never goes through the lazy app module though, so the MatDialogModule with its's root providers (i.e. MatDialog service) are not having any effect.

The right solution would be to either:

  1. Add the MatDialogModule to the root module so that the service is available in your whole application
  2. Retrieve the ComponentFactoryResolver for the lazy module and instantiate the component through that.

If you go with (2), you might want to use Compiler#compileModuleSync, or directly work with NgModuleFactory. If the module is compiled with ngtsc, then the actual class is considered a factory in Ivy and you could wrap it in a NgModuleFactory, create an instance of it and then bootstrap a component with it. We have an internal example here (using Ivy factory shims though):

https://github.com/angular/components/blob/f0c7a25e0d58440029b4129188baa77d88ce5fc6/src/components-examples/private/load-example.ts#L14-L17

More general best-practices are probably in the Angular docs or in various great blog posts.

I've created a StackBlitz that shows the issue: https://stackblitz.com/edit/19335-before?file=src/main.ts. Here is an example with fix as per (2): https://stackblitz.com/edit/19335-with-fix?file=src/main.ts

The code responsible for loading the actual module with it's providers is the following. Disclaimer: Can still be optimized with parallel loading but here it's sufficient for the example:

async loadComponent() {
  const lazyModule = await import('./app/lazy.module').then(d => d.LazyModule);
  const moduleFactory = this.compiler.compileModuleSync(lazyModule); 
  const moduleInstance = moduleFactory.create(this.injector);
  const lazyComponent = await import('./app/lazy.component').then(d => d.LazyComponent);
  const factory = moduleInstance.componentFactoryResolver
    .resolveComponentFactory(lazyComponent);
  this.vcr.createComponent(factory, 0, this.injector);
}

Closing as this seems to work as expected. Please let us know if this does not work. An issue with an updated StackBlitz would be great. Thanks!

This issue has been automatically locked due to inactivity.
Please file a new issue if you are encountering a similar or related problem.

Read more about our automatic conversation locking policy.

_This action has been performed automatically by a bot._

Was this page helpful?
0 / 5 - 0 ratings

Related issues

alanpurple picture alanpurple  路  3Comments

julianobrasil picture julianobrasil  路  3Comments

Miiekeee picture Miiekeee  路  3Comments

RoxKilly picture RoxKilly  路  3Comments

savaryt picture savaryt  路  3Comments