【问题标题】:Angular CDK: How to set Inputs in a ComponentPortalAngular CDK:如何在 ComponentPortal 中设置输入
【发布时间】:2018-05-08 06:23:32
【问题描述】:

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

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

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

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

我也尝试了new ComponentPortal(MyPortalComponent),但我们如何设置输入呢?通常类似于componentRef.component.instance.myInput

【问题讨论】:

    标签: angular angular-material2 angular-cdk


    【解决方案1】:

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

    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

    【讨论】:

    • 当我尝试这样做时,我得到以下信息:无法解析 ComponentToPort 的所有参数:([object Object],[object Object],?)。在哪里?是 CONTAINER_DATA
    • 可能在“ComponentToPort”内,您需要从创建它的位置导入 CONTAINER_DATA。这可能会解决问题。
    • 这实际上是实现此目的的唯一方法吗?使用注入令牌的问题是您无法进行任何类型的更改检测——没有ngOnChanges,没有async | pipe,nada。如果没有传入 Observable 作为令牌,您似乎只剩下一个纯静态值。真的是这样吗,没有办法利用@Inputs?
    • @output 呢?
    • PortalInjector 现已弃用。似乎建议使用 Injector.create。
    【解决方案2】:

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

    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();
    

    【讨论】:

    • 一个比其他答案更容易的解决方案,至少对我来说!
    • 确实比创建自定义注入器容易得多!
    • 最佳答案,injector 方式使您以非“标准”方式编写组件并且有局限性
    • 那么这种绕过正确的@Input()机制是否重要,和/或因为您明确调用detectChanges是否重要 - 或者这就是重点:-)
    • 这除了简单之外还有一个巨大的优势,它允许您使组件与覆盖无关。一个潜在的缺点是它可能会在设置输入之前执行ngOnInit(尚未检查)。
    【解决方案3】:

    如果您使用 Angular 10+ 并遵循 Awadhoot's answer,PortalInjector 现在已被弃用,因此:

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

    你现在有:

    Injector.create({
      parent: this.injector,
      providers: [
        { provide: SOME_TOKEN, useValue: data }
      ]
    })
    

    【讨论】:

      【解决方案4】:

      这似乎更简单,使用 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;
      }
      

      【讨论】:

      • 还有一个 @output() 可以发出 ref,参见:github.com/angular/components/blob/master/src/cdk/portal/…
      • 对于给定的问题,这确实是一个非常好的解决方案。我已经尝试了上面提到的所有方法,但这是将数据传递到组件门户的最小且找到的最佳解决方案。
      【解决方案5】:

      您可以通过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);
        }
      }
      

      【讨论】:

      • 你需要解释代码以及为什么它会起作用
      【解决方案6】:

      在弃用 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;

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

      &lt;ng-template [cdkPortalOutlet]="yourPortal" (attached)="setInputs($event)"&gt; &lt;/ng-template&gt;

      并在 ts 中设置您的输入:

      setInputs(portalOutletRef: CdkPortalOutletAttachedRef) {
          portalOutletRef = portalOutletRef as ComponentRef<myComponent>;
          portalOutletRef.instance.inputPropertyName = data;
      }
      

      【讨论】:

      • 注意:DomPortalOutlet 的描述是"A PortalOutlet for attaching portals to an arbitrary DOM element outside of the Angular application context." - 所以它实际上是为了将一些 Angular 组件放在页面上完全随机的元素上。如果您单独使用 Angular,您可能需要 ng-template 方法(如上所示)。
      • 如果有人试图将某些东西放在 Angular 上下文之外,那么还请查看 Angular Elements (angular.io/guide/elements)。
      猜你喜欢
      • 1970-01-01
      • 2020-04-22
      • 2020-12-17
      • 2016-12-08
      • 2019-11-21
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多