cmxl
cmxl

Reputation: 775

Propagating service instance to dialog component not working as expected

I have a simple reproduction of what I want to achieve here on stackblitz: https://stackblitz.com/edit/angular-zb8kvg

I have a component (here it's the app.component) which a service is declared on (MyService). I need a new instance of MyService each time the component gets opened, so it seems right to me, that the service is declared on component level and not in the module.

Now I want to open a dialog (MatDialog -> TestComponent) from this component which needs the same service instance.

I get a StaticInjectorError as seen in the console output.

How can I use the same service instance in my dialog as my calling component has?

Upvotes: 11

Views: 8292

Answers (4)

Lukas Körfer
Lukas Körfer

Reputation: 14493

I encountered the same problem and found a solution that allows the injection of services into dialog components in the usual way. The MatDialogConfig passed to the MatDialog.open method has a property called viewContainerRef with the following documentation:

Where the attached component should live in Angular's logical component tree. This affects what is available for injection and the change detection order for the component instantiated inside of the dialog. This does not affect where the dialog content will be rendered.

Simply pass the current ViewContainerRef (injected into your host component) and it will work as intended:

@Component({
  selector: "my-app",
  templateUrl: "./app.component.html",
  styleUrls: ["./app.component.css"],
  providers: [MyService]
})
export class AppComponent {
  constructor(public dialog: MatDialog, private viewContainerRef: ViewContainerRef) {}

  open() {
    this.dialog.open(DialogComponent, { viewContainerRef: this.viewContainerRef });
  }
}

@Component({
  template: "<h1>DialogComponent</h1>"
})
export class DialogComponent {
  constructor(private myService: MyService) { }
}

Upvotes: 28

Roee Rokah
Roee Rokah

Reputation: 357

You can pass the parent Injector(@angular/core/injector) into (MatDialog -> TestComponent) with the help of MatDialogConfig param of MatDialog.open

Example:

app.component.ts

import { Component, Injector } from "@angular/core";
import { MyService } from "./my.service";
import { MatDialog } from "@angular/material";
import { TestComponent } from "./test.component";

@Component({
  selector: "my-app",
  templateUrl: "./app.component.html",
  styleUrls: ["./app.component.css"],
  providers: [MyService]
})
export class AppComponent {
  constructor(public dialog: MatDialog, private injector: Injector) {}

  open() {
    this.dialog.open(TestComponent, { data: { injector: this.injector } });
  }
}

test.component.ts

import { Component, Inject, Injector, OnInit } from "@angular/core";
import { MAT_DIALOG_DATA } from "@angular/material/dialog";
import { MyService } from "./my.service";

@Component({
  selector: "app-test",
  template: "<h1>TestComponent</h1>"
})
export class TestComponent {
  myService: MyService;

  constructor(@Inject(MAT_DIALOG_DATA) public data: { injector: Injector }) {
    this.myService = this.data.injector.get(MyService);
  }
}

Upvotes: 4

Alex Biro
Alex Biro

Reputation: 1227

That is because the dialog, even though it was requested from your app.component, is not a child of it. You can see this if you inspect the DOM elements of the dialog: the dialog is appended somewhere to the root of the body, as a sibling to the root element of the angular app.

So the dependency injection is working as intended, it's just that the service's scope does not cover the dialog.

Suggested solution: you can pass data in the MatDialogConfig param of MatDialog.open, which has a data field.

That data can be used in multiple ways:

  • you can pass a callback in it from the component which invokes the dialog, which the dialog can call. The callback will have access to the services which are injected into the component which invoked the dialog.
  • you can just pass in a reference to the service instance from the component which invokes the dialog, so the component in the dialog has basically the full service instance

Example: https://material.angular.io/components/dialog/examples (see the TS part of the first example)

Upvotes: 4

Zouhair Ettouhami
Zouhair Ettouhami

Reputation: 226

You didn't provide the service in your component the "provide" property is missing

@Component({
 selector: 'app-test',
 template: '<h1>TestComponent</h1>',
 providers: [MyService] //Missing
 })

Upvotes: -2

Related Questions