【问题标题】:Is it possible to dynamically set a components @Input at run-time?是否可以在运行时动态设置组件@Input?
【发布时间】:2021-04-30 01:34:07
【问题描述】:

假设我有一个 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

Angular docs 在此 instance 对象上并不清楚 - 只是说明该实例是 C 类型 - 绝对没有提及 C 的实际含义。

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

ComponentRef.instance 具有以下属性:

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

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

问题

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

【问题讨论】:

  • C 是一个泛型类型参数,因为我猜这个类的其他地方都使用了 T。
  • 我可以设置@Inputs - 你想让它们被Angular自动更新吗?
  • @Maximus 这将是理想的 - 我知道这听起来有点牵强。

标签: angular


【解决方案1】:

自动更新

这似乎是不可能的。原因如下。

对于每个组件,Angular 编译器都会创建一个工厂。您可以在ng:// 文件夹下的sources 选项卡中观察所有工厂,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 。无论如何,这就是我的假设。如果没有其他人可以插话,我会接受这个作为答案。
  • @DanielCooke,当然,我已经添加了一些关于手动更新的信息。此外,您可能会发现有趣的所有 Angular 文章 on my blog,它们内容丰富且深入
  • 精彩 - 关注您的博客。这正是我一直在寻找的资源。
  • 我遇到了类似的问题。但是,我的动态组件有两个输入。其中之一接收传入的参数,如上所述。另一个没有,即使我在下一个语句中以相同的方式传递它。工作输入是一个“数字”,而失败的输入是一个“字符串”,我怀疑这是问题所在。在设置值时使用console.log,表示正确设置了失败的输入。但是目标组件内的console.log 返回undefined
  • @MaxWizardK,它现在可以工作了,我在其他地方有一个错误。谢谢;)
【解决方案2】:

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

用法

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;
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2016-09-07
    • 1970-01-01
    • 1970-01-01
    • 2012-06-16
    • 1970-01-01
    • 2023-01-20
    • 1970-01-01
    相关资源
    最近更新 更多