Nesting Components

In component-based frameworks like Angular, React we create a root component and build a tree. With ngBackbone we can do the same. Let's create a new child component foo.ts:

import { Component, View } from "ng-backbone";

@Component({
  el: "ng-foo",
  template: `Hello, it's Foo`
})

export class FooView extends View {
  initialize(){
    this.render();
  }
}

Then we add its sibling bar.ts

import { Component, View } from "ng-backbone";

@Component({
  el: "ng-bar",
  template: `Hello, it's Bar`
})

export class BarView extends View {
  initialize(){
    this.render();
  }
}

As you can see from the code both of the components render themselves during initialization. In the root app.ts we can use views map to refer to the nesting components:

import { Component, View } from "ng-backbone";
import { FooView } from "./foo";
import { BarView } from "./bar";

@Component({
  el: "ng-hello",
  views: {
    foo: FooView,
    bar: BarView
  },
  template: `<ng-foo></ng-foo> <ng-bar></ng-bar>`
})

class AppView extends View {
}

let app = new AppView();
app.render();

Here we specified the locations for the child components in the template by placing there elements ng-foo and ng-bar. These are the elements we have bound our nested components to.

Child Component and Construction Options

Well, what if we need to pass specific constructor arguments to a nesting component? Not a problem. We can specify a subordinated component as an array where the constructor goes in the first element and view options in the second:

@Component({
  el: "ng-hello",
  views: {
    foo: [ FooView, { name: "foo" } ],
    bar: [ BarView, { name: "bar" } ]
  },
  template: `<ng-foo></ng-foo> <ng-bar></ng-bar>`
})

Child components get constructed after the parent first rendering. Thus they can bind to the elements of the parent template. Besides construction ngBackbone also takes care about destruction. Whenever remove() method is called on the parent, this method is being invoked per each child recursively.

Accessing Sub-components

Within parent component child ones are available in views map. You can access the instance of a sub-component as this.views.get( "name" ). Thus we obtain the control over child component from the parent:

@Component({
  el: "ng-hello",
  events: {
    "click [data-bind=toggleFoo]" : "toggleFoo",
    "click [data-bind=toggleBar]" : "toggleBar"
  },
  views: {
    foo: FooView,
    bar: BarView
  },
  template: `
  <button data-bind="toggleFoo">Show Foo</button>
  <button data-bind="toggleBar">Show Bar</button>
  <ng-foo></ng-foo> <ng-bar></ng-bar>`
})

class AppView extends View {
  toggleFoo(){
    this.views.get( "foo" ).toogle();
  }
  toggleBar(){
    this.views.get( "bar" ).toogle();
  }
}

Communicating between Parent and Child

As we just examined the parent component has the links to child ones. But every child component also has a link to the parent (this.parent). We can use it to implement bi-directional communication.

Let's write a child component that has a bound echo model. The template renders echo.msg. When the component initializes it changes echo.msg of it's parent, assuming the parent has similar model:

child.ts

import { Component, View, Model } from "ng-backbone";

@Component({
  el: "ng-child",
  models: {
    echo: new Model({
      msg: ""
    })
  },
  template: `<h2>It's Child</h2>
  <p>Echo: <output data-ng-text="echo.msg"></output></p>
  `
})

export class ChildView extends View {
  initialize(){
    this.render();
    this.parent.models.get( "echo" ).set( "msg", "Hello, it's Child" );
  }
}

Now we create the parent component. During initialization it also changes the model of the child.

parent.ts

import { Component, View, Model } from "ng-backbone";
import { ChildView } from "./child";

@Component({
  el: "ng-parent",
  views: {
    child: ChildView
  },
  models: {
    echo: new Model({
      msg: ""
    })
  },
  template: `<h2>It's Parent</h2>
  <p>Echo: <output data-ng-text="echo.msg"></output></p>
  <ng-child></ng-child>
  `
})

export class ParentView extends View {
  initialize(){
    this.render();
    let echo = this.views.get( "child" ).models.get( "echo" );
    echo.set( "msg", "Hello, it's Parent" );
  }
}

new ParentView();

When we compile the code and run it a browser, we see that parent received and displayed the message from child and vice versa.

Maintaining bindings on parent view change

Let's say we have an imaginary task. We have to create a list where every item has own view. So as for items the example view may look like:

  @Component({
    el: "ng-item",
    template: "it's item"
  })
  class ItemView extends View {
    initialize(){
      this.render();
    }
  }

The parent (list) view has a bound collection named items and a subview named also items. As you remember we expect subview binding to ng-item element.

  let items = new Collection([ new Model() ]);
  @Component({
    tagName: "ng-list",
    template: "<ng-item data-ng-for=\"let item of items\"></ng-item>",
    collections: {
      items: items
    },
    views: {
      items: ItemView
    }
  })
  class ListView extends View {
    initialize(){
      this.render();
    }
  }

Initially the collection has just a single element and on render we have <ng-list><ng-item>it's item</ng-item></ng-list>

  let list = new ListView();
  list.views.getAll( "foo" ).length; // 1
  list.views.get( "foo" ); // ItemView

With method list.views.getAll( "foo" ) we can access the array of bound ItemView instances. Here it consists of one element. However let's see what happens if we change the collection:

  items.add([ new Model() ]);
  view.on( "component-did-update", () => {
    list.views.getAll( "foo" ).length; // 2
    list.views.get( "foo", 0 ); // ItemView
    list.views.get( "foo", 1 ); // ItemView
    done();
  });

The method list.views.getAll( "foo" ) indicates that we have now 2 subview instances matching ng-item element. We can access a particular instance like list.views.get( "foo", index )

Last updated