Как загрузить дочерний компонент Nativescript Angular с помощью кода?

Oct 01 2020

Я пытаюсь добавить пользовательский дочерний компонент Angular в GridLayout, который определен в родительском шаблоне. Родитель должен поддерживать несколько компонентов, которые он знает, как создавать и взаимодействовать с ними, но он должен иметь возможность размещать их, когда и где указано в GridLayout.

  • Я могу указать свой дочерний элемент SampleComponent в шаблоне, и он отобразится.
  • Если я попытаюсь добавить свой SampleComponent в GridLayout с использованием кода, логика grid.addChildChild не выдаст ошибок, но компонент не отобразится.
  • Если я добавлю кнопку в сетку с помощью кода позади нее, она будет отображаться должным образом.

Я понимаю, что пытаюсь загрузить компонент вместо кнопки, но мой компонент расширяет ContentView. Я видел несколько дискуссий по поводу Builders, но казалось, что они компилируются из исходного кода, где мои дочерние компоненты создаются вместе с моим родителем, и они используют шаблоны .xml. Я попытался посмотреть ComponentBuilder, но не могу найти никакой документации, которая помогла бы мне понять, как я могу его использовать.

Ниже приведены 3 наиболее важные функции из моего примера, инициированные пользователем, вводящим «образец» или «кнопку» и щелчком ДОБАВИТЬ для запуска 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;
    }

Ответы

JeffWheatley Oct 15 2020 at 19:50

Ключи к реализации этой работы с использованием парадигмы NativeScript Angular, похоже, - это директивы и инъекции.

Мое решение - использовать директиву для загрузки моей дочерней директивы. Директива использует инъекцию, чтобы получить ссылки, необходимые для создания дочернего компонента в родительском представлении.

Итак, в моем примере ниже

<StackLayout *LoadChildComponent></StackLayout>

запускает мой LoadChildDirective, но также обертывает элемент стека

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

Это означает, что контекст представления директивы находится сразу за пределами элемента стека.

  • Как и в случае с компонентами, директива может использовать инъекцию для получения интересных вещей, которые позволяют нам загружать дочерний компонент, такой как ViewContainerRef.

Родитель

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

Директива по дочерней нагрузке

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

Рекомендации

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