0
votes

I am trying to add a custom child Angular component to a GridLayout which is defined in the parent's template. The parent needs to support a few components which it knows how to build and interact with, but it must be able to place them when and where instructed within the GridLayout.

  • I can specify my SampleComponent child within the template, and it displays.
  • If I try adding my SampleComponent to the GridLayout using code behind, the grid.addChildChild logic gives no errors, but the component fails to display.
  • If I add a Button to the grid using code behind, it displays as expected.

I understand that I am trying to load a Component vs. a Button, but my component does extend ContentView. I have seen some discussions around Builders, but they seemed to be compiling from source code, where my child components are built with my parent, and they use .xml templating. I did try looking at ComponentBuilder, but I cannot find any documentation that helps me understand how I might use it.

The most relevant 3 functions of my sample are below, initiated by the user keying in 'sample' or 'button' and clicking ADD to fire 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;
    }
1
I corrected some formatting. A thoughtful contributor on my other post warned me of format issues. I am blind, and have been known to make visual blunders... They are unintended, so I appreciate your understanding! Please never hesitate to let me know I have screwed up...Jeff Wheatley

1 Answers

0
votes

The keys to make this work using a NativeScript Angular paradigm seem to be directives and injection.

My solution is to use a directive to load my child directive. The directive uses injection to get the references necessary to build the child component within the parent's view.

So, in my sample below,

<StackLayout *LoadChildComponent></StackLayout>

fires my LoadChildDirective, but also wraps the stack element

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

This means that the view context of the directive is the position just outside of the stack element.

  • Like components, the directive can use injection to get cool stuff that lets us load a child component such as ViewContainerRef.

Parent

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

Child Load Directive

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