¿Cómo cargo un componente secundario de Nativescript Angular usando código subyacente?

Oct 01 2020

Estoy tratando de agregar un componente Angular secundario personalizado a un GridLayout que se define en la plantilla de los padres. El padre debe admitir algunos componentes con los que sabe cómo construir e interactuar, pero debe poder colocarlos cuando y donde se le indique dentro de GridLayout.

  • Puedo especificar mi hijo SampleComponent dentro de la plantilla y se muestra.
  • Si intento agregar mi SampleComponent al GridLayout usando el código subyacente, la lógica grid.addChildChild no da errores, pero el componente no se muestra.
  • Si agrego un botón a la cuadrícula usando el código subyacente, se muestra como se esperaba.

Entiendo que estoy intentando cargar un componente frente a un botón, pero mi componente extiende ContentView. He visto algunas discusiones sobre los constructores, pero parecían estar compilando desde el código fuente, donde mis componentes secundarios se compilan con mi padre, y usan plantillas .xml. Intenté buscar en ComponentBuilder, pero no puedo encontrar ninguna documentación que me ayude a comprender cómo podría usarlo.

Las 3 funciones más relevantes de mi muestra se encuentran a continuación, iniciadas por el usuario que ingresa 'muestra' o 'botón' y hace clic en AGREGAR para activar el toque ():

    onTap() {
        this.textField.nativeElement.dismissSoftInput();
        let name = this.textField.nativeElement.text.toLowerCase();
        var component: any;
        console.log(`Adding component ${name}`); switch( name ) { case 'sample': component = this.buildSample(); break; case 'button': component = this.buildButton(); break; default: console.error("User keyed in invalid response"); return; } console.log("Adding component to grid"); let grid: GridLayout = this.gridField.nativeElement; grid.addRow( new ItemSpec( 1, GridUnitType.AUTO )); let label = new Label(); label.text = name; grid.addChild( label ); GridLayout.setRow( label, this.row ); grid.addChild( component ); GridLayout.setRow( component, this.row ); GridLayout.setColumn( component, 1 ); this.row++ } private buildButton(): Button { let button = new Button(); button.text = `Button for row${this.row}`;
        return button;
    }

    private buildSample(): SampleComponent {
        let sample = new SampleComponent();
        sample.setting = 259;
        return sample;
    }

Respuestas

JeffWheatley Oct 15 2020 at 19:50

Las claves para hacer que esto funcione usando un paradigma de NativeScript Angular parecen ser directivas e inyección.

Mi solución es usar una directiva para cargar mi directiva secundaria. La directiva usa la inyección para obtener las referencias necesarias para construir el componente secundario dentro de la vista principal.

Entonces, en mi muestra a continuación,

<StackLayout *LoadChildComponent></StackLayout>

dispara mi LoadChildDirective, pero también envuelve el elemento de la pila

<ng-template loadChild><StackLayout></StackLayout></ng-template>

Esto significa que el contexto de vista de la directiva es la posición justo fuera del elemento de pila.

  • Al igual que los componentes, la directiva puede usar la inyección para obtener cosas interesantes que nos permitan cargar un componente secundario como ViewContainerRef.

Padre

import { ViewChild, ElementRef, Component,OnInit, ComponentFactoryResolver } from '@angular/core';
import { LoadChildDirective } from './load.child.directive';

@Component({
  moduleId: module.id,
  selector: 'parent',
  template: `
    <StackLayout>
      <StackLayout *loadChild></StackLayout>
      <Button id="dfSaveButton" text="save"></Button>
    </StackLayout>
  `})
export class ParentComponent implements OnInit {

  constructor() {}
  ngOnInit() {
    console.log("In parent ngOnInit");
  }
}

Directiva de carga infantil

import { Directive, OnInit , ViewContainerRef, ComponentFactoryResolver } from "@angular/core";
import { ChildComponent } from './child.component';

@Directive({
  selector: "[loadChild]" // directive tag used in parent
})
export class LoadChildDirective implements OnInit {
  constructor(private vcRef: ViewContainerRef,
  private componentFactoryResolver: ComponentFactoryResolver
) {
    console.log("In loadChildDirective constructor with vcRef=" + vcRef);
  }

  ngOnInit() {
    console.log("In LoadChildDirective ngOnInit with vcRef=" + this.vcRef);
    const componentFactory = this.componentFactoryResolver.resolveComponentFactory(ChildComponent);
    const viewContainerRef = this.vcRef;
        viewContainerRef.clear();
    const componentRef = viewContainerRef.createComponent<ChildComponent>(componentFactory);
    const child = <ChildComponent>componentRef.instance;
  }
}

Referencias

https://angular.io/guide/dynamic-component-loader#dynamic-component-loading