7

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.

1 Answer 1

8

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!

Sign up to request clarification or add additional context in comments.

2 Comments

Nice! Thanks. I'd be very interested to hear what the ideal implementation would be, as my app will rely quite heavily on this logic. If you have any time an explanation would be greatly appreciated, but otherwise thanks for the working solution anyhow! :)
would be a very interesting case to workout with changedetection :)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.