Nativescript-angular: [Question] Add custom component into StackLayout

Created on 9 Sep 2016  路  4Comments  路  Source: NativeScript/nativescript-angular

Hi, I'm trying to add a child component into StackLayout in a parent component.

Tested on: iOS Simulator 9.3

The parent's code:

import {Component, ElementRef, ViewChild} from "@angular/core";
import {Router} from "@angular/router";
import {StackLayout} from "ui/layouts/stack-layout";
import {Page} from "ui/page";

import {NestedComponentChild} from "./NestedComponentChild"
import * as builder from "ui/builder";

@Component({
  selector: "NestedComponentParent",
  template: `
    <StackLayout #contentStack>
        <Label text="Text on parent"></Label>
        <Button (tap)="addChild()" text="Add child"></Button>
        <NestedComponentChild></NestedComponentChild> <!-- this works -->
    </StackLayout>
  `,
    directives: [NestedComponentChild]
})

export class NestedComponentParent {
     @ViewChild("contentStack") contentStackRef: ElementRef;
    constructor(private _router: Router, private page: Page) {
    }
    counter = 0;
    addChild() {
        this.counter++;
        let stack = <StackLayout>this.contentStackRef.nativeElement;
        let childInstance = <NestedComponentChild> builder.load({
            path: "Pages/NestedComponent/NestedComponentChild",
            name: "NestedComponentChild",
            attributes: {
                counter : this.counter
            }
        });
        stack.addChild(childInstance); //Nothing new appeared
        console.log(childInstance.counter); // correct
        console.log("#children in stack: " + stack.getChildrenCount()); // does increase
    }
}

The children (NestedComponentChild.ts)

import {Component, Injector, Provider} from "@angular/core";
import {View} from "ui/core/view"
@Component({
  selector: "NestedComponentChild",
  template: `
        <Label [text]='"Child #" + counter'></Label>
  `
})

export class NestedComponentChild extends View {
    counter: number = 0;
}

In short, if I use the selector "NestedComponentChild" in NestedComponentParent's template, the child component (with label) appears. However, if I add it by code, the number of children of stack increases, but it (the added child component) isn't shown on screen.

Most helpful comment

Hello @fleuverouge,

There are several things to consider from your code sample.
You are trying to use NativeScript core concepts with Angular-2 which won't provide the expected results. For example, you are building a childInstance with NS builder and using stackLayout.addChild() which ae both NativeScript core concepts for which basically Angular-2 does not know how to handle.
In your NestedComponentChild you are extending View which is also unnecessary.
What you need to do is to create your project the Angular-2 way and use the Angular concepts for initializing and handling components.

For example here is how to add a child component to your stack with an angular structural directive *ngFor and ViewContainerRef (Represents a container where one or more Views can be attached).

your parent page code

import {Component, ViewContainerRef} from "@angular/core";
import {Page} from "ui/page";

import {NestedComponentChild} from "./NestedComponentChild"

@Component({
    selector: "NestedComponentParent",
    template: `
    <StackLayout #contentStack>
        <Label text="Text on parent"></Label>
        <Button (tap)="addChild()" text="Add child"></Button>
        <NestedComponentChild *ngFor="let idx of items" [counter]="idx"></NestedComponentChild>
    </StackLayout>
  `,
    directives: [NestedComponentChild]
})

export class NestedComponentParent {
    items = [];
    counter = 0;

    constructor(private page: Page, vcRef:ViewContainerRef) {
    }

    addChild() {
        this.counter++;
        this.items.push(this.counter);
    }
}

your nested component code (notice the usage of Input to export the variable _counter_)

import {Component, Input} from "@angular/core";
import {View} from "ui/core/view"

@Component({
  selector: "NestedComponentChild",
  template: `
    <Label [text]='"Child #" + counter'></Label>
  `
})

export class NestedComponentChild {
    @Input() counter: number = 0;
}

All 4 comments

Hello @fleuverouge,

There are several things to consider from your code sample.
You are trying to use NativeScript core concepts with Angular-2 which won't provide the expected results. For example, you are building a childInstance with NS builder and using stackLayout.addChild() which ae both NativeScript core concepts for which basically Angular-2 does not know how to handle.
In your NestedComponentChild you are extending View which is also unnecessary.
What you need to do is to create your project the Angular-2 way and use the Angular concepts for initializing and handling components.

For example here is how to add a child component to your stack with an angular structural directive *ngFor and ViewContainerRef (Represents a container where one or more Views can be attached).

your parent page code

import {Component, ViewContainerRef} from "@angular/core";
import {Page} from "ui/page";

import {NestedComponentChild} from "./NestedComponentChild"

@Component({
    selector: "NestedComponentParent",
    template: `
    <StackLayout #contentStack>
        <Label text="Text on parent"></Label>
        <Button (tap)="addChild()" text="Add child"></Button>
        <NestedComponentChild *ngFor="let idx of items" [counter]="idx"></NestedComponentChild>
    </StackLayout>
  `,
    directives: [NestedComponentChild]
})

export class NestedComponentParent {
    items = [];
    counter = 0;

    constructor(private page: Page, vcRef:ViewContainerRef) {
    }

    addChild() {
        this.counter++;
        this.items.push(this.counter);
    }
}

your nested component code (notice the usage of Input to export the variable _counter_)

import {Component, Input} from "@angular/core";
import {View} from "ui/core/view"

@Component({
  selector: "NestedComponentChild",
  template: `
    <Label [text]='"Child #" + counter'></Label>
  `
})

export class NestedComponentChild {
    @Input() counter: number = 0;
}

Hi @NickIliev,
Thank you so much for your guidance!
I had to make NestedComponentChild extend View to use the ui builder.
My idea is to add different components dynamically basing on the data fetched by the parent component (ie: if data contain properties of Type1 & Type2, components Child1 & Child2 will be added, if data contain Type3, only Child3 will be added). I won't know which components/classes will be added and their order beforehand. That's the reason why I wanted to use StackLayout.addChild() instead of html template.
I'm an iOS developer with Obj-C & Swift. Nativescript & angular2 is really new to me. So thank you so much for any advice.

(Pls excuse me for my English)

Hey @fleuverouge

There are several approaches for your scenario with Angular-2 directives.
You can use ngSwitch directive and load components based on the switch case.
Or you can create your own custom directive with its own extended logic.
For better understanding, I recommend that you take a look at this sample application which we created with NativeScript + Angular-2
In this section you can find a basic example of how to use angular directives and how to create your own custom one.

Thanks, @NickIliev. I wonder why I hadn't found that repo before. I'll dig in it.

Was this page helpful?
0 / 5 - 0 ratings