处理 Angular 2 中动态创建的组件的 @Input 和 @Output

Posted

技术标签:

【中文标题】处理 Angular 2 中动态创建的组件的 @Input 和 @Output【英文标题】:Handle @Input and @Output for dynamically created Component in Angular 2 【发布时间】:2017-03-15 21:42:59 【问题描述】:

如何为 Angular 2 中动态创建的组件处理/提供 @Input@Output 属性?

这个想法是在调用 createSub 方法时动态创建(在这种情况下)SubComponent。分叉很好,但我如何为 SubComponent 中的@Input 属性提供数据。另外,如何处理/订阅 SubComponent 提供的@Output 事件?

示例:两个组件在同一个 NgModule 中

应用组件

@Component(
  selector: 'app-root'
)  
export class AppComponent 

  someData: 'asdfasf'

  constructor(private resolver: ComponentFactoryResolver, private location: ViewContainerRef)  

  createSub() 
    const factory = this.resolver.resolveComponentFactory(SubComponent);
    const ref = this.location.createComponent(factory, this.location.length, this.location.parentInjector, []);
    ref.changeDetectorRef.detectChanges();
    return ref;
  

  onClick() 
    // do something
  

子组件

@Component(
  selector: 'app-sub'
)
export class SubComponent 
  @Input('data') someData: string;
  @Output('onClick') onClick = new EventEmitter();

【问题讨论】:

过去你可以做这样的事情..ref.instance.someData = someData。我不确定是否仍然如此。 这仍然有效。没有其他方法(除了使用共享服务进行通信)。 ***.com/questions/36325212/… 包含更多详细信息。 @KrisHollenbeck @Günter Zöchbauer 谢谢。所以每次父组件中的数据更改时,我必须将其设置为'ref.instance.someData = someData'并自行触发更改检测('ref.changeDetectorRef.detectChanges();')?以及如何找出子组件中的哪个属性是正确的?可以命名完全不同...:/也许直接使用Reflection会尝试:) @thpnk,是的,我相信是的,听起来不错。自从后来的 RC 版本之一以来,我还没有尝试过这样做。我会参考 Gunter Zochbauer 的那篇文章以获取更多信息。 ***.com/questions/36325212/… 【参考方案1】:
createSub() 
  const factory = this.resolver.resolveComponentFactory(SubComponent);
  const ref = this.location.createComponent(factory, this.location.length, 
  ref.instance.model = Which you like to send
  ref.instance.outPut = (data) => //will get called from from SubComponent 
  this.location.parentInjector, []);
  ref.changeDetectorRef.detectChanges();
return ref;


SubComponent
 public model;
 public outPut = <any>;  
 constructor() console.log("Your input will be seen here",this.model) 
 sendDataOnClick()
    this.outPut(inputData)
     

【讨论】:

【参考方案2】:

如果您知道要添加的组件的类型,我认为您可以使用其他方法。

在您的应用根组件 html 中:

<div *ngIf="functionHasCalled">
    <app-sub [data]="dataInput" (onClick)="onSubComponentClick()"></app-sub>
</div>

在您的应用根组件打字稿中:

private functionHasCalled:boolean = false;
private dataInput:string;

onClick()
   //And you can initialize the input property also if you need
   this.dataInput = 'asfsdfasdf';
   this.functionHasCalled = true;


onSubComponentClick()


【讨论】:

【参考方案3】:

为@Input 提供数据非常简单。您已将组件命名为 app-sub,并且它有一个名为 data 的 @Input 属性。可以通过这样做来提供这些数据:

<app-sub [data]="whateverdatayouwant"></app-sub>

【讨论】:

请先尝试理解问题【参考方案4】:

我发现以下代码可以从字符串 (angular2 generate component from just a string) 动态生成组件,并从中创建了一个 compileBoundHtml 指令,该指令传递输入数据(不处理输出,但我认为同样的策略适用于你可以修改这个):

    @Directive(selector: '[compileBoundHtml]', exportAs: 'compileBoundHtmlDirective')
export class CompileBoundHtmlDirective 
    // input must be same as selector so it can be named as property on the DOM element it's on
    @Input() compileBoundHtml: string;
    @Input() inputs?: [x: string]: any;
    // keep reference to temp component (created below) so it can be garbage collected
    protected cmpRef: ComponentRef<any>;

    constructor( private vc: ViewContainerRef,
                private compiler: Compiler,
                private injector: Injector,
                private m: NgModuleRef<any>) 
        this.cmpRef = undefined;
    
    /**
     * Compile new temporary component using input string as template,
     * and then insert adjacently into directive's viewContainerRef
     */
    ngOnChanges() 
        class TmpClass 
            [x: string]: any;
        
        // create component and module temps
        const tmpCmp = Component(template: this.compileBoundHtml)(TmpClass);

        // note: switch to using annotations here so coverage sees this function
        @NgModule(imports: [/*your modules that have directives/components on them need to be passed here, potential for circular references unfortunately*/], declarations: [tmpCmp])
        class TmpModule ;

        this.compiler.compileModuleAndAllComponentsAsync(TmpModule)
          .then((factories) => 
            // create and insert component (from the only compiled component factory) into the container view
            const f = factories.componentFactories[0];
            this.cmpRef = f.create(this.injector, [], null, this.m);
            Object.assign(this.cmpRef.instance, this.inputs);
            this.vc.insert(this.cmpRef.hostView);
          );
    
    /**
     * Destroy temporary component when directive is destroyed
     */
    ngOnDestroy() 
      if (this.cmpRef) 
        this.cmpRef.destroy();
      
    

重要的修改在于增加了:

Object.assign(this.cmpRef.instance, this.inputs);

基本上,它会将您希望在新组件上的值复制到 tmp 组件类中,以便它们可以在生成的组件中使用。

它会像这样使用:

<div [compileBoundHtml]="someContentThatHasComponentHtmlInIt" [inputs]="anInput: anInputValue"></div>

希望这可以节省我不得不做的大量谷歌搜索。

【讨论】:

【参考方案5】:

创建组件时可以轻松绑定:

createSub() 
    const factory = this.resolver.resolveComponentFactory(SubComponent);
    const ref = this.location.createComponent(factory, this.location.length, this.location.parentInjector, []);
    ref.someData =  data: '123' ; // send data to input
    ref.onClick.subscribe( // subscribe to event emitter
      (event: any) => 
        console.log('click');
      
    )
    ref.changeDetectorRef.detectChanges();
    return ref;
  

发送数据非常简单,只需 ref.someData = data 其中data 是您要发送的数据。

从输出中获取数据也很容易,因为它是一个EventEmitter,您可以简单地订阅它,并且只要您emit() 来自组件的值,您传入的clojure 就会执行。

【讨论】:

这实际上是使用输入名称设置数据吗?还是只使用字段名称? @bvdb 老实说我不确定,那是很久以前的事了,但您可以轻松检查。如果我猜我会说它正在使用字段名称,但不能确定。 我想它必须是 ref.instance.someData 而不是 ref.someData 嘿@Alexander,这样做给了我 'someData' 不存在于类型 'unknown' 上。如果还没有解析动态组件的类型,如何修复知道类型?

以上是关于处理 Angular 2 中动态创建的组件的 @Input 和 @Output的主要内容,如果未能解决你的问题,请参考以下文章

在Angular中动态加载组件[重复]

Angular 2:向动态创建的组件添加指令

angular动态设置disable

在 Angular 中动态创建的嵌套组件

动态加载现有组件 Angular 2 Final Release

如何知道动态创建的 Angular 组件何时可以在 DOM 中请求?