2

I have started with angular 2 and I have found it interesting. But from past few days am stuck with something and I need someone to help me out.

Scenario :

Am building a select box with checkbox in a hierarchical structure.

A
|---A1
|---A2
     |---A2.1
     |---A2.2
           |.....
|.....

So the web service will return this data structure in json format. hence I won't be aware of number of hierarchical children it will have

Currently what I have done is:

@Component({
  selector: 'tree-view',
  template: `<ul>
    <li *ngFor="let items of data">
      <a href="javascript:void(0);"
      (click)="toggleMultiSelect($event,[items])">
        <input type="checkbox" name="{{items.name}}"
        [checked]="getChecked(items.id)"
        id="{{items.id}}">
        <label [attr.for]="items.id" class="custom-unchecked">{{items.name}}</label>
      </a>
      <tree-view
        *ngIf="items.children && items.children.size > 0"
        (isSelected)="resultChild($event)"
        [data]="items.children"></tree-view>
    </li>
  </ul>`,
  directives: [ MultiHierarchyTreeViewComponent ]
})

So what I'm doing is I'm checking if data has child then it will repeat the selector again. So this brings hierarchical structure of select box.

But the problem is I have to repeat only the template, currently it is repeating class along with the template, so this is making select all, deselect all, parent - child selection operation difficult.

I thought that <template> would solve my issue, but no.

Am breaking my head from past two days to solve this but am just getting negative response every time.

Mr Lister
  • 45,515
  • 15
  • 108
  • 150
Rohit
  • 35
  • 4
  • I think repeating the component would be the easiest way. You can also use https://angular.io/docs/ts/latest/api/common/index/NgTemplateOutlet-directive.html but that seems way more complicated to me. – Günter Zöchbauer Sep 26 '16 at 12:14
  • This related question might be interesting to you as well http://stackoverflow.com/questions/39698833/angular2-recursive-tree-expand-all/39699104?noredirect=1#comment66701422_39699104 – Günter Zöchbauer Sep 26 '16 at 12:14
  • See also http://stackoverflow.com/questions/37746516/use-component-in-itself/37747022#37747022 – Günter Zöchbauer Sep 26 '16 at 12:16
  • 1
    @GünterZöchbauer ok thanks i will try one of this and let you know :-) – Rohit Sep 26 '16 at 12:20

1 Answers1

1

I dealt with a similar thing recently and for me the best way was storing the tree data structure and components separately.

Tree data structure are just nested objects:

export interface TreeNodeInterface {
    parent: TreeNodeInterface;
    children: TreeNodeInterface[];
}

Basic class implementing this interface looks like this (complete code is available here):

export class TextTreeNode implements TreeNodeInterface {
    private parentNode: TreeNodeInterface;
    private childrenNodes: TreeNodeInterface[] = [];

    constructor(text: string, options: TreeNodeOptions|Object, children: TreeNodeInterface|TreeNodeInterface[] = []) {
        // ...
    }

    get parent() {
        return this.parentNode;
    }

    get children() {
        return this.childrenNodes;
    }
    // ...
}

Then rendering is a separate component.

I wanted to do it like this because I can show/hide subtrees with *ngIf and therefore easily keep the DOM relatively simple even with very large trees (assuming you usually see only a smaller portion of the whole tree). This also let's me easily reuse the same tree in different parts of my application.

I'm sure you could do the same with QueryList but as I said, keeping the tree structure separate works better for my usecase.

@Component({
    selector: 'ng2-treeview',
    directives: [ TreeViewComponent ],
    template: `
        ...
        <ng2-treeview *ngFor="let child of node.children" [node]="child"></ng2-treeview>
    `
})
export class TreeViewComponent implements TreeViewInterface, AfterViewInit
{
    @Input() node: TreeNodeInterface;        
    // ...
}

Usage is then very simple:

@Component({
    selector: 'demo',
    directives: [TreeViewComponent],
    template: `
        <ng2-treeview [node]="textTreeView"></ms-treeview>
    `
})
export class DemoComponent {
    textTreeView = new TextTreeNode('Root node', null, [
        new TextTreeNode('Child node #1'),
        new TextTreeNode('Child node #2'),
        new TextTreeNode('Child node #3'),
        new TextTreeNode('Child node #4', null, [
            new TextTreeNode('Hello'),
            new TextTreeNode('Ahoy'),
            new TextTreeNode('Hola'),
        ]),
        new TextTreeNode('Child node #5'),
    ]);
}

I have complete source code in ng2-treeview, however I didn't have time recently to finish the documentation and right now it's made for old Angular2 RC.1.

Edit: If I want to handle for example mouse clicks on each tree node you can create a service that's injected into TreeViewComponent:

import {EventEmitter, Injectable} from '@angular/core';

@Injectable()
export class TreeNodeService {
    click: EventEmitter<Object> = new EventEmitter();
}

Then each (click) on the TreeViewComponent is handed over to the service:

nodeClick(event) {
    this.treeNodeService.click.emit(this.node.id);
}

And finally, the DemoComponent can receive TreeNodeService as a dependency in the constructor and subscribe to the click event emitter. Since DemoComponent can be also provider for TreeNodeService there can be multiple independent instances of TreeNodeService at the same time.

martin
  • 93,354
  • 25
  • 191
  • 226
  • Martin awesome documentation but i have one question , like i have done something like this and am getting view properly but i want my inner child template to access only the top most component methods and variable, to perform my logic. At the moment it seems each time when template is repeated it creates separate instance of class. Do you have any idea how to do this? – Rohit Sep 26 '16 at 14:14
  • Please let me know if you need any other clarification? – Rohit Sep 26 '16 at 14:18
  • @Rohit I updated my answer, I hope this is what you meant. – martin Sep 26 '16 at 15:23