components icon indicating copy to clipboard operation
components copied to clipboard

bug(mat-dialog): MatDialog doesn't have access to providers defined in a route when given a ViewContainerRef

Open WoMayr opened this issue 1 year ago • 5 comments
trafficstars

Is this a regression?

  • [ ] Yes, this behavior used to work in the previous version

The previous version in which this bug was not present was

No response

Description

When providing services via a route, MatDialog.open is not able to create a component instance that injects this service

MatDialog.open is called like this

this.matDialog.open(MyServiceConsumerComponent, {
  viewContainerRef: this.viewContainerRef,
});

Structure of the routes and components looks like this: image

I did try to debug into MatDialog.open but the created Injectors were all able to get the service. So I don't really know what prevents the dialog component to be created.

It did correctly work, when declaring the Service in the providers-part of the component itself (see comment in my stack-blitz in my-feature.component.ts)

Also I'm not sure if this is an issue of Angular Material, CDK or Angular itself.

Reproduction

StackBlitz link: https://stackblitz.com/edit/angular-standalone-ts-strict-ijt5jr Steps to reproduce:

  1. Use standalone components
  2. Create a service (MyFeatureService)
  3. Create a route with a component (MyFeatureComponent) and define the service in the providers
  4. Create a component (MyServiceConsumerComponent) that consumes the service
  5. Open a dialog in MyFeatureComponent using matDialog.open(MyServiceConsumerComponent, { viewContainerRef: this.viewContainerRef })

Expected Behavior

The Dialog is able to resolve the requested service

Actual Behavior

The Dialog failes to resolve the service:

ERROR NullInjectorError: R3InjectorError(Environment Injector)[MyFeatureService -> MyFeatureService]: 
  NullInjectorError: No provider for MyFeatureService!
    at NullInjector.get (injector_token.ts:27:5)
    at R3Injector.get (r3_injector.ts:294:90)
    at R3Injector.get (r3_injector.ts:294:90)
    at ChainedInjector.get (component_ref.ts:238:23)
    at lookupTokenUsingModuleInjector (di.ts:372:8)
    at getOrCreateInjectable (di.ts:424:2)
    at Object.ɵɵdirectiveInject (shared.ts:86:5)
    at NodeInjectorFactory.MyServiceConsumerComponent_Factory [as factory] (my-service-consumer.component.ts:8:40)
    at getNodeInjectable (di.ts:661:20)
    at createRootComponent (inherit_definition_feature.ts:43:27)

Environment

  • Angular: 17.0.5
  • CDK/Material: 17.0.1
  • Browser(s): Chrome 119.0.6045.200
  • Operating System (e.g. Windows, macOS, Ubuntu): Windows 10

WoMayr avatar Dec 01 '23 12:12 WoMayr

This is quite a cumbersome issue. I want to have a Dialog that relies on information from a feature-store service. Said store service is provided under a route that is dedicated to this feature.

That dialog plain does not function. Because the store performs side effects when it is being initialized, providing it on the root would trigger an initialization that would occur too early and in the wrong environment.

Additionally the dialog does not have access to the store and therefore no access to updated information - making it impossible for the dialog to know whether it should display various UI states.

The only workaround for this so far is to not update the dialogs UI and just continuously open new dialogs with new information and I refuse to make the end user sit through that hell.

Another workaround I used is to create a Service/Store (provided by the component where the dialog open function is called with viewContainerRef passed as parameter) as a relay for the route store. Thus you can inject this service into dialog and access the store through it.

kalitine avatar May 15 '24 10:05 kalitine

@kalitine An example would be helpful.

Meanwhile, I'm passing an instance of the needed service to the Dialog component using the data property:

In the component opening the dialog:

interface MyDialogComponentData {
  myService: MyService;
}

class SomeComponent {
  private myService = inject(MyService);

  openDialog() {
    const data: MyDialogComponentData = { myService: this.myService };
    this.dialog.open(MyDialogComponent, { data });
  }
}

MyDialogComponent:

class MyDialogComponent {
  private data = inject<MyDialogComponentData>(MAT_DIALOG_DATA);

  someMethod() {
    // use data.myService
  }
}

DmitryEfimenko avatar May 17 '24 22:05 DmitryEfimenko

possibly related: #25262

DmitryEfimenko avatar May 18 '24 16:05 DmitryEfimenko

We faced the same issue, However importing MatDialogModule in MyFeatureComponent solves the issue.

@Component({
  standalone: true,
  templateUrl: './my-feature.component.html',
  //Import MatDialigModule here
  imports: [MatDialogModule],
})

msibhuiyan avatar Aug 10 '24 09:08 msibhuiyan

I confirm that material v17 has this problem. The injector created for the dialog by material is not a child of the injector provided to matDialog.open() config (either directly via the injector property or indirectly via the viewContainerRef property).

jeandat avatar Oct 10 '24 09:10 jeandat

This bug seems to have been fixed. I just tried to reproduce it using current versions and now it is working. See this StackBlitz: https://stackblitz.com/edit/angular-standalone-ts-strict-bfhr67ut?file=src%2Fapp%2Froutes.ts

WoMayr avatar Jan 27 '25 13:01 WoMayr

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.