Rohit
Rohit

Reputation: 35

Repeat Template in recursive way without repeating component in angular 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.

Upvotes: 2

Views: 2546

Answers (1)

martin
martin

Reputation: 96889

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.

Upvotes: 1

Related Questions