web-dev-qa-db-fra.com

Gérer @Input et @Output pour le composant créé dynamiquement dans Angular 2

Comment gérer/fournir les propriétés @Input et @Output pour les composants créés de manière dynamique dans Angular 2?

L'idée est de créer dynamiquement (dans ce cas) le SubComponent lorsque la méthode createSub est appelée. La fourchette va bien, mais comment puis-je fournir des données pour les propriétés @Input dans le SubComponent. Aussi, comment gérer/souscrire aux événements @Output fournis par le sous-composant?

Exemple: (Les deux composants sont dans le même 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
  }
}

Sous-composant

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

Vous pouvez facilement le lier lorsque vous créez le composant:

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

L'envoi de données est vraiment simple, il suffit de faire ref.someData = datadata sont les données que vous souhaitez envoyer.

Obtenir des données à partir de la sortie est également très facile, car il s'agit d'une EventEmitter vous pouvez simplement vous y abonner et le clojure que vous transmettez s'exécutera chaque fois que vous emit() une valeur du composant. 

4
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)
 }    
}
0
ayyappa maddi

J'ai trouvé le code suivant pour générer des composants à la volée à partir d'une chaîne ( angular2 générer un composant à partir d'une chaîne ) et créer une directive compileBoundHtml à partir de celle-ci qui transmet les données d'entrée (ne gère pas les sorties mais je pense la même chose). stratégie s’appliquerait pour que vous puissiez le modifier):

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

La modification importante consiste à ajouter:

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

En gros, il copie les valeurs que vous voulez faire figurer sur le nouveau composant dans la classe de composants tmp afin qu'elles puissent être utilisées dans les composants générés.

Il serait utilisé comme:

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

J'espère que cela épargnera à quelqu'un la quantité énorme de recherches que je devais faire sur Google.

0
William Neely