Подтвердить что ты не робот

Ручка @Input и @Output для динамически созданного компонента в Angular 2

Как обрабатывать/предоставлять свойства @Input и @Output для динамически созданных Компонентов в Angular 2?

Идея состоит в том, чтобы динамически создавать (в данном случае) SubComponent при вызове метода createSub. Форкс отлично, но как я могу предоставить данные для свойств @Input в SubComponent. Также, как обрабатывать/подписываться на события @Output, которые предоставляет SubComponent?

Пример: (Оба компонента находятся в одном NgModule)

AppComponent

@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();
}
4b9b3361

Ответ 1

Я нашел следующий код для генерации компонентов "на лету" из строки (angular2 сгенерировать компонент только из строки) и создал из него директивку 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 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 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>

Надеюсь, это спасет кого-то огромное количество Googling, которое я должен был сделать.

Ответ 2

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

Ответ 3

Если вы знаете тип компонента, который хотите добавить, я думаю, вы можете использовать другой подход.

В корневом компоненте приложения html:

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

В корневом компоненте приложения typescript:

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

}

Ответ 4

Предоставление данных для @Input очень просто. Вы назвали свой компонентный под-компонент и у него есть свойство @Input с именем data. Предоставление этих данных можно сделать, выполнив следующие действия:

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

Ответ 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 вы можете просто подписаться на него, а clojure, который вы передадите, будет выполняться всякий раз, когда вы emit() получаете значение от компонента.