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

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

About this issue

  • Original URL
  • State: closed
  • Created 4 years ago
  • Reactions: 3
  • Comments: 19 (9 by maintainers)

Most upvoted comments

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

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!

@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.