Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to set context on an Angular [cdkPortalOutlet]

The Portal module in Angular Material (CDK) is a very powerful way to render dynamic content into a 'PortalOutlet'.

A Portal<T> can be a TemplateRef where T is the type of the context data.

<ng-template cdkPortal let-data>
    <p>Portal contents {{ data | json }}</p>
</ng-template>

You get a reference to it with @ViewChild

@ViewChild(CdkPortal) myPortalRef: CdkPortal;  

You can display a portal in a cdkPortalOutlet

Content gets inserted here: 
<ng-template [cdkPortalOutlet]="myPortalRef"></ng-template>

This will insert the portal contents into the outlet.

But there are two big missing features with the provided directives.

1) no way to set the actual context object on the outlet using the directive
2) no way to set the context object on the portal itself

You can manually create an portal and provide a context, or attach a Portal to a PortalOutlet programatically with a context but I want to do it more declaratively.

The similar (and more commonly used) [NgTemplateOutlet] does provide a property for [ngTemplateOutletContext].

What's the best way to set context on a CdkPortal? Should I extend the directives myself, or try to pass through my data with a service? I thought the whole point of this module was to make things super easy.

Angular source

See also: https://github.com/angular/material2/issues/6310

like image 780
Simon_Weaver Avatar asked Aug 11 '18 19:08

Simon_Weaver


2 Answers

I have hit the same questioning, and ended up thinking we are actually taking this from the wrong angle!

Injecting a context only makes sense if we have a template to project. In that case, the variables are added to the template environment and they can be bound to component properties in the template code:

<ng-template #user_details let-user>
  <qtl-manage-user-panel [user]="user"></qtl-manage-user-panel>
</ng-template>

But without a ng-template (i.e. the Portal case), that would feel weird. The target component would have out-of-the-box context, without a let directive. And what variables should it be bound to?

So, getting back to our case of projecting an actual component into the Portal: how to pass context? The best answer is Injection I think.

The context will be passed through a custom Injector to the component's constructor when it's built. All it takes is creating custom injection tokens:

export const MY_TOKEN = new InjectionToken<TTT>('xxx');

Then, when creating the ComponentPortal, we give a custom injector:

const injector =
Injector.create({
  providers: [
    {
      provide: MY_TOKEN,
      useValue: /* Here, the context variable */,
    },
  ]});

const cPortal = new ComponentPortal(ComponentClass, null, injector);

That way, the context variable will be injected in the ComponentClass constructor, and can be accessed through:

constructor(
    @Inject(MY_TOKEN) public context: CCC,
  ) { ... }

And now context can be used in the component class or template.

A little late to the party, but I hope it can help people ending up on this question in the future.

like image 57
Qortex Avatar answered Oct 29 '22 20:10

Qortex


I think you can assign the context directly to the portal but I haven't tested this. It has to be assigned before the outlet uses the portal and that might be tricky.

@ViewChild(CdkPortal) myPortalRef: CdkPortal;  

public ngAfterViewInit(): void {
    this.myPortalRef.context = {...}; // your context data
}

I think the issue here is that the context is attached to the portal instead of being independant. So you can't use the same portal to render in two different outlets with two different contexts.

Also, have you tried using cdkPortalOutlet with the micro-syntax parser in Angular to see if it works?

<ng-template *cdkPortalOutlet="myPortalRef; context: contextExp"></ng-template>

From what I can tell the ng-template methods for the portals isn't used in the library. Everything with portals is done as services, and the template variant appears to have been an add on feature for the community but it wasn't fully vetted.

like image 21
Reactgular Avatar answered Oct 29 '22 22:10

Reactgular