是否可以在运行时动态设置组件@Input?

时间:2017-05-18 13:32:32

标签: angular

假设我有dynamic-component-wrapper可以实例化传递给它的任何Component类。

// DRE013 DCOOKE 16/05/2017 - The component to instantiate.
@Input() componentType: Type<{}>;

// DRE013 DCOOKE 16/05/2017 - the component that will be created
private _cmpRef: ComponentRef<Component>;


// DRE013 DCOOKE 16/05/2017 - Creates a component ref in the view at #target
createComponent(){

    let factory = this.componentFactoryResolver.resolveComponentFactory(this.componentType);
    this._cmpRef = this.target.createComponent(factory);
    //this._cmpRef.instance.inputs >>>>>>>>> this is a string[] and I do not see how I can use this 
}

使用示例

<shared-dynamic-component [componentType]="TestComponent"></shared-dynamic-component>

其中 TestComponent = TestComponent //class

这可以按预期工作,我可以在dynamic-component-wrapper内收到此组件的实例,如下所示:

this._cmpRef.instance

instance对象上的Angular docs不明确 - 只是声明该实例的类型为C - 绝对没有引用C的实际内容。

谢天谢地,我的IDE能够告诉我:

ComponentRef.instance具有以下属性:

  • inputs : string[]
  • outputs : string[]

但是我不明白我是如何使用这些信息的。我想这只是@Input字段的名称 - 但我想不出如何将复杂对象作为输入传递。

问题

在使用@Inputs动态创建组件后,我是否可以设置componentFactoryResolver和其他元数据

2 个答案:

答案 0 :(得分:5)

自动更新

这似乎不可能。这是解释原因。

对于每个组件,Angular编译器都会创建一个工厂。您可以在sources文件夹下的ng://标签中观察所有工厂,here is the example看看它们的外观。在创建工厂时,它定义将在此组件视图中呈现的nodes。一些节点是子组件。

在生成此工厂时,它定义了框架应跟踪哪些输入属性。因此,除非您在编译之前定义输入属性,否则Angular将不会跟踪和更新输入。

以下是此工厂的示例:

function View_AppComponent_0(l) {
  return jit_viewDef2(0,[
    jit_queryDef3(201326592,1,{someComp: 0}),
    (l()(),jit_elementDef4(0,null,null,1,'h1',[],null,null,null,null,null)),
    (l()(),jit_textDef5(null,[
      'Hello ',
      ''
    ]
    )),
    (l()(),jit_textDef5(null,['\n\n'])),
    (l()(),jit_elementDef4(0,null,null,1,'b-comp',[],null,null,null,jit_View_BComponent_06,jit__object_Object_7)),

      *************************************
      // this is a child component's node definition which has `some` property binding
      jit_directiveDef8(24576,null,0,jit_BComponent9,[],{some: [
        0,
        'some'  <------------------------ property name to track
      ]
    },null)
     **************************************************

  ]

当您使用this.componentFactoryResolver.resolveComponentFactory时,它实际上会搜索该工厂。它没有编译任何东西。

手动更新

这当然有可能。您可以从父组件查询子组件并更新属性 - 它将在子组件的模板中正确呈现。你甚至可以触发onChanges生命周期钩子。

export class AppComponent {
  @ViewChild(BComponent) bc;

  ngAfterViewInit() {
    setTimeout(() => {
      this.bc.some = 'foo';
      const changes = {some: new SimpleChange('', 'foo', true)};
      this.bc.ngOnChanges(changes);
    })
  }

答案 1 :(得分:0)

我最终创建了一个类来这样做......以防它对其他人有所帮助。

用法

const componentChangeBuilder = new DynamicComponentChangeBuilder(componentRef);
componentChangeBuilder.addInput('inputName', 'some string value');
componentChangeBuilder.addInput('someOtherInputName', 1234);
componentChangeBuilder.commit();

import { ComponentRef, OnChanges, SimpleChange, SimpleChanges } from '@angular/core';

/**
 * Use this to track and trigger ngOnChanges for dynamically loaded components.
 */
export class DynamicComponentChangeBuilder<TComponent = any> {
    private readonly componentRef: ComponentRef<TComponent>; 
    private readonly inputs = new Map<keyof TComponent, TComponent[keyof TComponent]>();
    private readonly changesHistory = {} as SimpleChanges;

    constructor(componentRef: ComponentRef<TComponent>) {
        this.componentRef = componentRef;        
    }

    public addInput(
        input: keyof TComponent,
        value: TComponent[typeof input],
    ): void {
        this.inputs.set(input, value);
    }

    public commit() {
        const currentChanges = {} as SimpleChanges;

        this.inputs.forEach((newValue, input) => {
            const previousChange = this.changesHistory[input as string];
            let currentChange: SimpleChange;
            if (!!previousChange) {
                currentChange = {
                    previousValue: previousChange.currentValue,
                    currentValue: newValue,
                    firstChange: false,
                } as SimpleChange;
            } else {
                currentChange = {
                    currentValue: newValue,
                    firstChange: true,
                } as SimpleChange;
            }

            this.changesHistory[input as string] = currentChange;
            currentChanges[input as string] = currentChange;
            this.componentRef.instance[input] = currentChange.currentValue;
        });

        if (implementsOnChanges(this.componentRef.instance)) {
            this.componentRef.instance.ngOnChanges(currentChanges);
        }
    }
}

function implementsOnChanges(component: any): component is OnChanges {
    return (component as OnChanges)?.ngOnChanges !== undefined;
}