DI problem with standalone component

This issue has been tracked since 2022-09-15.

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

reproduce

pkozlowski-opensource wrote this answer on 2022-09-15

Could you please create a minimal reproduce scenario in stackblitz. It is impossible to determine a root cause without a runnable reproduce scenario.

JoostK wrote this answer on 2022-09-15

This seems to be an interesting case, which I agree behaves unexpectedly.

The router creates the routed component dynamically, entering this code path:

const _injector = isComponentFactory ? contextInjector : this.parentInjector;
// DO NOT REFACTOR. The code here used to have a `injector.get(NgModuleRef, null) ||
// undefined` expression which seems to cause internal google apps to fail. This is documented
// in the following internal bug issue: go/b/142967802
const result = _injector.get(EnvironmentInjector, null);

Here, this.parentInjector is a node injector that with a the ChainedInjector of the standalone component as parent; it chains the component's environment injector (containing a provier for MatDialog) with a node injector, with the node injector being requested first.

Then, requesting the EnvironmentInjector through the ChainedInjector we end up looking in the node injector first, which arrives at the EnvironmentInjector that was used during setup of the custom element: the application injector. The standalone environment injector that contains MatDialog will not have been consulted at this point, meaning that from that moment onwards the router has created the dynamic component in the environment of the app, not of the standalone component.

I believe this is the same as, or at least very similar to, #37441 (comment). It's unfortunate that this issue still manifests itself with standalone.

More Details About Repo
Owner Name angular
Repo Name angular
Full Name angular/angular
Language TypeScript
Created Date 2014-09-18
Updated Date 2022-09-30
Star Count 84091
Watcher Count 3064
Fork Count 22233
Issue Count 1203

YOU MAY BE INTERESTED

Issue Title Created Date Updated Date