Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Best approach to create recursive treeview dynamically from data API

I'm learning Angular 2, trying to build an expandable tree-view from a (potentially very large) third-party API. The API has an underlying structure like this:

- Home (id: 1053)
- - Rugby League (id: 1054)
- - - Super League (id: 1103)
- - - - Castleford Tigers (id: 1111)
- - - - Catalans Dragons (id: 1110)
- - - - Huddersfield Giants (id: 1116)
- - - - Hull FC (id: 1108)
- - - Championship (id: 1104)
- - - - Batley Bulldogs (id: 1120)
- - - - Bradford Bulls (id: 1118)
- - - - Dewsbury Rams (id: 1124)
- - - - Featherstone Rovers (id: 1121)
- - Football (id: 1056)
- - - Premier League (id: 1057)
- - - - AFC Bournemouth (id: 1059)
- - - - etc
- - - - etc

The API is set up such that I pass an id and it returns a simple JSON array of the children (only) of that node. So, for example I call: http://www.example.com/api/contentNodes/?parentId=1053 and it returns:

[
  {"Id":1054,"Name":"Rugby League","HasChildren":true},
  {"Id":1056,"Name":"Football","HasChildren":true}
]

(HasChildren represents whether or not the node has child nodes.)

Note, because the data-set will eventually be large I want to 'pull in' more data from the API progressively as the tree branches are opened, rather than dumping the entire data-set in there and rendering that out in my app.

I've set up an Angular 2 app which can be seen here: http://plnkr.co/edit/QQ1OKCbd4pDptpSVbWch?p=preview

The key component is the `app/content-list.component.ts' file:

import {Component, OnInit}  from 'angular2/core';
import {ContentNode}        from './content-node';
import {ContentService}     from './content.service';

@Component({
    selector: 'content-list',
    template: `
        <ol class="tree">
            <li *ngFor="#contentNode of contentNodes" class="tree__branch" [ngClass]="{'tree__branch--has-children': contentNode.HasChildren}">
                <a *ngIf="contentNode.HasChildren" (click)="toggleBranch(contentNode.Id)" class="toggle">+</a> {{ contentNode.Name }}
            </li>
        </ol>
        <div class="error" *ngIf="errorMessage">{{errorMessage}}</div>
    `
})
export class ContentListComponent implements OnInit {

    constructor (private _contentService: ContentService) {}

    errorMessage: string;
    private _startNodeId: number = 1053;

    contentNodes: ContentNode[];

    ngOnInit() { 
        this.getContentNodes();
    }

    getContentNodes() {
        this._contentService.getContentNodes(this._startNodeId)
            .subscribe(
                contentNodes => this.contentNodes = contentNodes,
                error =>  this.errorMessage = <any>error
            );
    }

    toggleBranch(branchId:number){
        console.log('branchId: ' + branchId);
    }
}

You'll see here that I'm calling my service which returns the JSON as above, by being passed a parentId of 1053.

I've now hit a wall in being able to progressively load the child nodes of the treeview when the + button is clicked, into the nested HTML list (<ol>).

What would be the best approach here, to achieve this in a really neat way?

My next step will be to ensure that the app doesn't make excessive API calls, but my immediate concern is just to get a running treeview hooked up and working.

I've seen this example of a recursive treeview but it seems (a) a little buggy (in that there are empty <ol></ol> elements rendered in the HTML when child nodes are empty etc); and (b) it seems to be set up in a very 'hard-coded' kind of a way and I'm not experienced enough to confidently refactor it.

Many thanks.

Note, for security reasons I can't open up the API to public requests unfortunately, which makes testing this on Plunkr a little difficult, I realise. For the moment my example uses just a static, single level JSON data-set.

like image 607
Dan Avatar asked Apr 09 '16 20:04

Dan


1 Answers

in Angular2 you can render directives recursively. This makes rendering the tree very easy. I've modified your Plunker a little bit just to show the point. It's not the ideal implementation but it works as expected :).

Example:

@Component({
    selector: 'tree-view',
    template: `
        <div *ngFor="#dir of dirs">
            <tree-view [dirs]="dir.dirs"></tree-view>
        <div>
    `,
    directives: [TreeView]
})
export class TreeView {
    @Input() 
    private dirs: Array<Directory>;
}

I hope you will like it.

Cheers!

like image 127
S.Klechkovski Avatar answered Oct 10 '22 02:10

S.Klechkovski