DI problem with standalone component
See original GitHub issueWhich @angular/* package(s) are the source of the bug?
common
Is this a regression?
Yes
Description
I have a standalone component AppComponent and a module GuideModule and use createApplication() and @angular/elements to create custom element. I got an error when angular instantiate GuideListComponent (.in GuideModule ). Why it happens, I imported MatDialogModule in GuideModule.
AppComponent*
@Component({ standalone: true, selector: 'pz-creator-app', templateUrl: './app.component.html', styleUrls: ['./app.component.scss'], encapsulation: ViewEncapsulation.ShadowDom, imports: [ CommonModule, // Features GuideModule, RouterModule, ], }) export class AppComponent implements OnDestroy {
GuideModule
@NgModule({ declarations: [ GuideListComponent, GuideEditorComponent, SettingsDialogComponent, ], imports: [ MatDialogModule, ], schemas: [CUSTOM_ELEMENTS_SCHEMA], exports: [], }) export class GuideModule {}
main.ts
createApplication({ providers: [ INTERCEPTORS, GLOBAL_CONFIG, importProvidersFrom([ HttpClientModule, MatSnackBarModule, AppRoutingModule, BrowserModule, BrowserAnimationsModule, CampaignDataAccessModule.forRoot(), ]), ], }).then((appRef) => { const ElementConstructor = createCustomElement(AppComponent, { injector: appRef.injector, }); customElements.define('pz-creator-app', ElementConstructor); });
Please provide a link to a minimal reproduction of the bug
No response
Please provide the exception or error you saw
`
ERROR Error: Uncaught (in promise): NullInjectorError: R3InjectorError(Environment Injector)[MatDialog -> MatDialog]:
NullInjectorError: No provider for MatDialog!
NullInjectorError: R3InjectorError(Environment Injector)[MatDialog -> MatDialog]:
NullInjectorError: No provider for MatDialog!
at NullInjector.get (main.js:164607:21)
at R3Injector.get (main.js:165110:27)
at R3Injector.get (main.js:165110:27)
at ChainedInjector.get (main.js:173498:32)
at lookupTokenUsingModuleInjector (main.js:161055:31)
at getOrCreateInjectable (main.js:161107:10)
at Module.ɵɵdirectiveInject (main.js:169929:10)
at NodeInjectorFactory.GuideListComponent_Factory [as factory] (main.js:872:559)
at getNodeInjectable (main.js:161318:38)
at instantiateRootComponent (main.js:172027:21)
at resolvePromise (polyfills.js:1438:19)
at resolvePromise (polyfills.js:1385:9)
at polyfills.js:1512:9
at ZoneDelegate.invokeTask (polyfills.js:459:171)
at Object.onInvokeTask (main.js:187695:25)
at ZoneDelegate.invokeTask (polyfills.js:459:54)
at Zone.runTask (polyfills.js:220:37)
at drainMicroTaskQueue (polyfills.js:664:23)
`
Please provide the environment you discovered this bug in (run ng version
)
Angular CLI: 14.2.2
Node: 18.3.0 (Unsupported)
Package Manager: npm 8.11.0
OS: darwin x64
Angular: 14.2.1
... animations, cdk, common, compiler, compiler-cli, core
... elements, forms, language-service, material
... platform-browser, platform-browser-dynamic, router
... service-worker
Package Version
---------------------------------------------------------
@angular-devkit/architect 0.1402.2
@angular-devkit/build-angular 14.2.2
@angular-devkit/core 14.2.2
@angular-devkit/schematics 14.2.2
@angular/cli 14.2.2
@angular/flex-layout 14.0.0-beta.40
@schematics/angular 14.2.2
rxjs 7.5.4
typescript 4.7.3
Anything else?
For a workaround, it works when I extract providers of GuideModule and add them to createApplication()
main.ts
createApplication({ providers: [ INTERCEPTORS, GLOBAL_CONFIG, importProvidersFrom([ ... GuideModule, ]), ], }).then((appRef) => { const ElementConstructor = createCustomElement(AppComponent, { injector: appRef.injector, }); customElements.define('pz-creator-app', ElementConstructor); });
Stackblitz
Issue Analytics
- State:
- Created a year ago
- Comments:6 (3 by maintainers)
Top GitHub Comments
@pkozlowski-opensource and myself looked into this again today, and we’ve come to the conclusion that this is working as designed (although confusing).
What happens is that the
<router-outlet>
creates the routed componentGuideListComponent
in the context of the environment injector of theRouterModule
, which is the root injector that is created usingcreateApplication
.GuideListComponent
is not itself a standalone component, so there is no guarantee that the providers from its NgModule graph are present as would be the case with standalone components (standalone components were specifically designed to avoid this pitfall).The injector that actually contains a provider for
MatDialog
is the standalone injector ofAppComponent
, but as previously mentioned the router uses its own environment injector, which is the root environment. Therefore, the standalone injector ofAppComponent
is not visible to the routed component instance.One solution could be to make
GuideListComponent
itself standalone, and importingMatDialogModule
from its `import array.We do recognize that this behavior can be confusing and is not ideal, but using standalone components across the board aims to avoid cases like these. In a hybrid setup like the reproduction, the interaction between the various DI hierarchies becomes hard to reason about.
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.