【问题标题】:Handle @Input and @Output for dynamically created Component in Angular 2处理 Angular 2 中动态创建的组件的 @Input 和 @Output
【发布时间】: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。我不确定是否仍然如此。
  • 这仍然有效。没有其他方法(除了使用共享服务进行通信)。 stackoverflow.com/questions/36325212/… 包含更多详细信息。
  • @KrisHollenbeck @Günter Zöchbauer 谢谢。所以每次父组件中的数据更改时,我必须将其设置为'ref.instance.someData = someData'并自行触发更改检测('ref.changeDetectorRef.detectChanges();')?以及如何找出子组件中的哪个属性是正确的?可以命名完全不同...:/也许直接使用Reflection会尝试:)
  • @thpnk,是的,我相信是的,听起来不错。自从后来的 RC 版本之一以来,我还没有尝试过这样做。我会参考 Gunter Zochbauer 的那篇文章以获取更多信息。 stackoverflow.com/questions/36325212/…

标签: javascript angular angular2-routing angular2-template


【解决方案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' 上。如果还没有解析动态组件的类型,如何修复知道类型?
        猜你喜欢
        • 1970-01-01
        • 2017-03-28
        • 2018-11-17
        • 2017-07-07
        • 2016-04-25
        • 1970-01-01
        • 2019-09-17
        • 2018-10-02
        • 1970-01-01
        相关资源
        最近更新 更多