39

我想使用材料 CDK 中的新门户在表单的多个部分中注入动态内容。

我有一个复杂的表单结构,目标是有一个表单来指定子组件可以(或不)注入模板的多个位置。

也许 CDK 门户不是最好的解决方案?

我尝试了一些东西,但我确信这不是这样做的方式: https ://stackblitz.com/edit/angular-yuz1kg

我也试过了,new ComponentPortal(MyPortalComponent)但我们如何设置输入呢?通常是这样的componentRef.component.instance.myInput

4

6 回答 6

37

您可以创建自定义注入器并将其注入您创建的组件门户。

createInjector(dataToPass): PortalInjector {
    const injectorTokens = new WeakMap();
    injectorTokens.set(CONTAINER_DATA, dataToPass);
    return new PortalInjector(this._injector, injectorTokens);
}

CONTAINER_DATA是由 - 创建的自定义注入器 ( InjectorToken)

export const CONTAINER_DATA = new InjectionToken<{}>('CONTAINER_DATA');

要使用创建的注入器,请使用 -

let containerPortal = new ComponentPortal(ComponentToPort, null, this.createInjector({
          data1,
          data2
        }));

overlay.attach(containerPortal);

overlayOverlayRef(Portal Outlet)的一个实例

在里面ComponentToPort,你需要注入创建的注入器 -

@Inject(CONTAINER_DATA) public componentData: any

更多关于这个here

于 2018-01-03T09:34:40.123 回答
36

如果您使用 Angular 10+ 并遵循Awadhoot 的回答,则 PortalInjector 现在已被弃用,而不是:

new PortalInjector(this.injector, new WeakMap([[SOME_TOKEN, data]]))

你现在有:

Injector.create({
  parent: this.injector,
  providers: [
    { provide: SOME_TOKEN, useValue: data }
  ]
})
于 2020-10-14T15:07:39.513 回答
34

可以通过这种方式设置组件输入(或作为可观察对象绑定到输出):

portal = new ComponentPortal(MyComponent);
this.portalHost = new DomPortalHost(
      this.elementRef.nativeElement,
      this.componentFactoryResolver,
      this.appRef,
      this.injector
    );

const componentRef = this.portalHost.attach(this.portal);
componentRef.instance.myInput = data;
componentRef.instance.myOutput.subscribe(...);
componentRef.changeDetectorRef.detectChanges();
于 2018-12-13T11:03:21.250 回答
12

这似乎更简单,使用 cdkPortalOutlet 和(附加的)发射器

import {Component, ComponentRef, AfterViewInit, TemplateRef, ViewChild, ViewContainerRef, Input, OnInit} from '@angular/core';
import {ComponentPortal, CdkPortalOutletAttachedRef, Portal, TemplatePortal, CdkPortalOutlet} from '@angular/cdk/portal';

/**
 * @title Portal overview
 */
@Component({
  selector: 'cdk-portal-overview-example',
  template: '<ng-template [cdkPortalOutlet]="componentPortal" (attached)=foo($event)></ng-template>',
  styleUrls: ['cdk-portal-overview-example.css'],
})
export class CdkPortalOverviewExample implements OnInit {
  componentPortal: ComponentPortal<ComponentPortalExample>;

  constructor(private _viewContainerRef: ViewContainerRef) {}

  ngOnInit() {
    this.componentPortal = new ComponentPortal(ComponentPortalExample);
  }

  foo(ref: CdkPortalOutletAttachedRef) {
    ref = ref as ComponentRef<ComponentPortalExample>;
    ref.instance.message = 'zap';
  }
}

@Component({
  selector: 'component-portal-example',
  template: 'Hello, this is a component portal {{message}}'
})
export class ComponentPortalExample {
  @Input() message: string;
}
于 2020-02-26T01:29:21.687 回答
9

您可以使用在第 3 个参数ComponentPortal传递的特定注入器注入数据ComponentPortal

修复语法问题

Can't resolve all parameters for Component: ([object Object], [object Object], ?

这是代码

export const PORTAL_DATA = new InjectionToken<{}>('PortalData');

class ContainerComponent {
  constructor(private injector: Injector, private overlay: Overlay) {}

  attachPortal() {
    const componentPortal = new ComponentPortal(
      ComponentToPort,
      null,
      this.createInjector({id: 'first-data'})
    );
    this.overlay.create().attach(componentPortal);
  }

  private createInjector(data): PortalInjector {

    const injectorTokens = new WeakMap<any, any>([
      [PORTAL_DATA, data],
    ]);

    return new PortalInjector(this.injector, injectorTokens);
  }
}

class ComponentToPort {
  constructor(@Inject(PORTAL_DATA) public data ) {
    console.log(data);
  }
}
于 2018-07-13T11:11:43.997 回答
4

在弃用版本 Angular 9 'DomPortalHost' 并将其更改为 'DomPortalOutlet' 之后。所以现在它会喜欢:

this.portalHost = new DomPortalOutlet(
   this.elementRef.nativeElement,
   this.componentFactoryResolver,
   this.appRef,
  this.injector
);

const componentRef = this.portalHost.attachComponentPortal(this.portal); componentRef.instance.myInput = data;

除此之外,我觉得最好的解决方案就是绑定(附加的)事件并在那里设置输入:

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

并在 ts 中设置您的输入:

setInputs(portalOutletRef: CdkPortalOutletAttachedRef) {
    portalOutletRef = portalOutletRef as ComponentRef<myComponent>;
    portalOutletRef.instance.inputPropertyName = data;
}
于 2020-05-28T10:12:19.203 回答