Como carrego um componente filho Angular Nativescript usando code-behind?
Estou tentando adicionar um componente Angular filho personalizado a um GridLayout que é definido no modelo do pai. O pai precisa oferecer suporte a alguns componentes que ele sabe como construir e interagir, mas deve ser capaz de colocá-los quando e onde for instruído no GridLayout.
- Posso especificar meu filho SampleComponent dentro do modelo e ele é exibido.
- Se eu tentar adicionar meu SampleComponent ao GridLayout usando code behind, a lógica grid.addChildChild não dá erros, mas o componente falha ao ser exibido.
- Se eu adicionar um botão à grade usando code behind, ele será exibido conforme o esperado.
Eu entendo que estou tentando carregar um componente em vez de um botão, mas meu componente estende ContentView. Eu vi algumas discussões sobre Builders, mas eles pareciam estar compilando a partir do código-fonte, onde meus componentes filho são construídos com meu pai, e eles usam modelos .xml. Eu tentei olhar o ComponentBuilder, mas não consigo encontrar nenhuma documentação que me ajude a entender como posso usá-lo.
As 3 funções mais relevantes do meu exemplo estão abaixo, iniciadas pelo usuário digitando 'amostra' ou 'botão' e clicando em ADICIONAR para disparar onTap ():
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;
}
Respostas
As chaves para fazer este trabalho usando um paradigma NativeScript Angular parecem ser diretivas e injeção.
Minha solução é usar uma diretiva para carregar a diretiva do meu filho. A diretiva usa injeção para obter as referências necessárias para construir o componente filho dentro da visão do pai.
Então, em meu exemplo abaixo,
<StackLayout *LoadChildComponent></StackLayout>
dispara meu LoadChildDirective, mas também envolve o elemento de pilha
<ng-template loadChild><StackLayout></StackLayout></ng-template>
Isso significa que o contexto de visão da diretiva é a posição logo fora do elemento de pilha.
- Como os componentes, a diretiva pode usar injeção para obter coisas legais que nos permitem carregar um componente filho, como ViewContainerRef.
Pai
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");
}
}
Diretiva 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;
}
}
Referências
https://angular.io/guide/dynamic-component-loader#dynamic-component-loading