Passing data to and from a ComponentPortal in Angular CDK
Asked Answered
S

4

5

I have tried to use the method found at Angular CDK: How to set Inputs in a ComponentPortal but PortalInjector seems to be deprecated, with no actual instructions on what to do in its place. The deprecation warning states to "Use Injector.create instead." with but not how or where to use it or what it's actually replacing.

I have also tried to wrap my head around Material's own Dialog component to see if I could figure out how they did it, but nothing.

So I pose the question again for Angular 13:

How can I pass data into and out of a component that was created using a ComponentPortal()? If the answer is something generic such as using an Injector, can you please point me to an example or documentation on how to do so? A 'hello world' of passing Injectors?

Scree answered 12/12, 2021 at 2:16 Comment(0)
I
5

IMHO, we do not have a straightforward way to have something that we do as an Input or Output from componentportal.

What you can do is to pass the data which you want to use as @Input using the attached event of portal.

Check this demo code where we are passing value to Guest component.

<ng-template (attached)="onComponentRendering($event)" [cdkPortalOutlet]="guestPortal"></ng-template>

and then using it as

 public onComponentRendering(ref): void {
    ref = ref as ComponentRef<any>;
    ref.instance['guestData'] = [ ...something];
 }

For output data, you can create a service (with Subject) and use it to communicate between components.

Initiatory answered 12/12, 2021 at 7:18 Comment(0)
A
13

Obviously I am late to the party, but you can actually use @Input() and @Output() of the component displayed inside a Portal. It is not obvious on the first sight, but calling this.overlayRef.attach(MyComponent) will return ComponentRef<MyComponent>. So from that you can quite easily get direct access to the component instance, and then do whatever you need with it.

Like this:

const componentPortal = new ComponentPortal(MyComponent);
const componentRef = this.overlayRef.attach(componentPortal);

// pass data via Input:
componentRef.instance.someInput = 'test';

// subscribe to output:
componentRef.instance.someOutput.subsribe();

And the MyComponent would look like this:

@Component({...})
export class MyComponent {
    @Input()
    someInput: string;

    @Output()
    someOutput = new EventEmitter<string>();
}

Happy coding!

Altaf answered 8/9, 2022 at 9:15 Comment(4)
Nobody late at party :). Furthermore, your solution is for me the good solutionCone
Wow, I wish I had know about this a few projects ago. This is a great solution and now I can just use one overlay and pass the display text in as an Input. Thank you!Manganous
I don't get it what is overlayRef and where does it come from? Can you please add more information?Sinewy
Was lost as well: "The overlay package provides a way to open floating panels on the screen." - material.angular.io/cdk/overlay/overviewSilicone
P
8

I'm a little late to the party but here's another option actually using Injector.create.

First I defined an injection token:

export const DATA_INJECTION_TOKEN = new InjectionToken<string>('DATA_INJECTION_TOKEN');

In my case I have a service that handles creating and providing the ComponentPortal. Which looks something like this:

export class FooService {
  // ...

  open(component: ComponentType<any>, data?: unknown) {
    // ...

    const portal = new ComponentPortal(
      component,
      null,
      Injector.create({
        providers: [{ provide: DATA_INJECTION_TOKEN, useValue: data }],
      }),
    );

    // ...
  }
}

Then used @Inject() to retrieve the data.

export class FoobarComponent {
  constructor(@Inject(DATA_INJECTION_TOKEN) data: MyDataType) { }
}

And finally when calling the service that to generate the portal it looked like this:

const data = {foo: 'bar'};
fooService.open(FoobarComponent, data);
Pictorial answered 17/2, 2023 at 21:10 Comment(0)
I
5

IMHO, we do not have a straightforward way to have something that we do as an Input or Output from componentportal.

What you can do is to pass the data which you want to use as @Input using the attached event of portal.

Check this demo code where we are passing value to Guest component.

<ng-template (attached)="onComponentRendering($event)" [cdkPortalOutlet]="guestPortal"></ng-template>

and then using it as

 public onComponentRendering(ref): void {
    ref = ref as ComponentRef<any>;
    ref.instance['guestData'] = [ ...something];
 }

For output data, you can create a service (with Subject) and use it to communicate between components.

Initiatory answered 12/12, 2021 at 7:18 Comment(0)
H
0

I used another little bit different approach. Especially for small data this is reasonable. I couldn't use the suggested approach from @Shashank Vivek because I initiated the ComponentPortal in a directive.

You can use ReplaySubject. e.g. (as a very rough sketch). First define in an Injectable

@Injectable
myService {
  overlayNotification: ReplaySubject<boolean>;
}

in your Component

myComponent  implements OnInit
constructor(private myService:myService){}
ngOnInit() {
  this.myService.overlayNotification=new ReplaySubject();
}
method4Overlay(){
  this.myService.overlayNotification.next(.... your data ...);
  this.overlayRef = this.overlay.create(overlayConfig);
  this.overlayRef.attach(new ComponentPortal(MySampleComponent));
}

and last but not least in your MySampleComponent

MySampleComponent implements OnInit
constructor(private myService:myService){}
ngOnInit() {
    this.myService.overlayNotification.subscribe((myData:...) => .... received data);
 }

So you can simply update with the ReplaySubject. The ReplaySubject has as well the advantage that you don't need to care when the init for MySampleComponent runs. It emits the event independently from the order or init. Which is as well a disadvantage since you need to create it as a new one - i.e. the events are not fired again.

I think you need to code the details on your own. But this rough sketch gives you an idea of another way to circumvent @Input() (in general).

Habitat answered 10/8, 2022 at 7:56 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.