LIDOR SYSTEMS

Advanced User Interface Controls and Components

Load Data on Demand in TreeView for Angular 2

Created: 03 April 2017

Instead of populating the TreeView with all data at once, you can load new data on demand whenever an item is expanding. In this article, you will learn how to add new data to a specified item from a local or remote data source, just before the item is expanded.

TreeView component is part of IntegralUI Web
a suite of UI Components for development of web apps

If you have any questions, don't hesitate to contact us at support@lidorsystems.com

In above demo, when you click on expanding icon, a new random data is created and then inserted into the expanding item as its children. This process is accompanied with loading animation represented by custom loading icon that replaces the expanding icon for specified item. You can expand multiple items at once and new data will be added accordingly.

Initial Tree Structure

At first, the TreeView can have only small set of items. In this case, items that are collapsed don't have any children.

// 
// app.module.ts
//

import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { BrowserModule } from '@angular/platform-browser';
import { HttpModule } from '@angular/http';
import { IntegralUIModule } from './integralui/integralui.module';

import { AppComponent }   from './app.component';

@NgModule({
    imports:      [ 
          BrowserModule, 
          FormsModule, 
          IntegralUIModule,
        HttpModule
    ],
    declarations: [ 
        AppComponent, 
    ],
    bootstrap: [ AppComponent ]
})
export class AppModule { }

// 
// app.component.ts
//

import { Component, enableProdMode, ViewContainerRef, ViewChild, ViewEncapsulation } from '@angular/core';
import { IntegralUITreeView } from '../../integralui/components/integralui.treeview';

import { Http, Response, Headers, RequestOptions } from '@angular/http';
import {Observable} from 'rxjs/Rx';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/catch';

@Component({
    selector: 'iui-app',
    templateUrl: 'app.template.html',
    styleUrls: ['sample-styles.css'],
    encapsulation: ViewEncapsulation.None
})
export class AppComponent {
    @ViewChild('application', {read: ViewContainerRef}) applicationRef: ViewContainerRef;
    @ViewChild('treeview') treeview: IntegralUITreeView;

    private items: Array<any>;

    constructor(private http: Http){
        this.items = [
            { 
              id: 1, 
              text: "Item 1", 
              items: [
                  { id: 11, text: "Item 11", expanded: false, items: [], hasChildren: true },
                  { id: 12, text: "Item 12" },
              ]
            },
            { id: 2, text: "Item 2" },
            { id: 3, text: "Item 3", expanded: false, items: [], hasChildren: true }
        ];
    }
}
                        
// 
// app.template.html
//

<div #application>
    <iui-treeview [items]="items" [appRef]="applicationRef" #treeview>
        <template let-item>
            <span>{{item.text}}</span>
        </template>
    </iui-treeview>
</div>
                            
/*
* sample-styles.css
*/ 

.iui-treeview
{
    width: 350px;
    height: 300px;
}
                            

By default, if an item doesn't have any children, its expanding icon is hidden. In order to add new data when item is expanding, the expanding icon must be visible. To solve this, each item that may have children (not loaded yet), it must have the hasChildren field set to true. This allows the expanding icon to appear.

How to Add Child Items on Demand

There are multiple ways to add new items to the TreeView. You can use some of available public methods for inserting new items (addItem, insertItemAt, etc.) or like in this example, directly placing a new item into the list using the push method.

To add child items when expanding icon is clicked for a specified item, you can handle the beforeExpand event and add a code there that will load a new data:

// Make sure each node has a random set of child items
private getChildCount(){
    return 1 + Math.floor(Math.random() * 5);
}

// Make sure that some child items can have children
private itemHasChildren(){
    let num = 2 + Math.floor(Math.random() * 3);

    return num % 2 == 0 ? true : false;
}

// Handle the beforeExpand event to populate the expanding item with new data
private onBeforeExpand(e: any){
    let self = this;

    if (e.item.items && e.item.items.length == 0){
        // Replace the expanding icon with a loading icon
        self.treeview.beginLoad(e.item);

        let loadTimer = setTimeout(function(){
            // Get random number of child items
            let count: number = self.getChildCount();
            for (let i = 1; i <= count; i++){
                // Create a child item
                let childItem: any = {
                    expanded: false, 
                    hasChildren: self.itemHasChildren(), 
                    items: [],
                    text: e.item.text + i
                }

                // Add the child item to the expanding item
                e.item.items.push(childItem);
            }

            // Restore the expanding icon
            self.treeview.endLoad(e.item);

            // Update the appareance of the TreeView
            self.treeview.refresh();

            clearTimeout(loadTimer);
        }, 1000);

    }
}
                            
<div #application>
    <iui-treeview [items]="items" [appRef]="applicationRef" (beforeExpand)="onBeforeExpand($event)" #treeview>
        <template let-item>
            <span>{{item.text}}</span>
        </template>
    </iui-treeview>
</div>
                            

In this example, the data is created with random number of child items where some of them may also have children.

To show that something is loading into the clicked item, you can use the beginLoad method. This method when called replaces the expanding icon with a loading icon.

Note You can use any custom image or animated gif as a loading icon. By modifying the CSS setting for the expanding icon, you can set a new url that points to a custom loading image.

When data is fully loaded, the endLoad method is called so that the loading icon is removed. At the end, the TreeView appearance is updated by calling the refresh method.

Note Because in this case the data is created and loaded locally, the load time is instant. To show that something is loading, a timeout of 1 second is used to allow animation to take place. In real case scenarios, this is not required.

How to Populate the TreeView with Data from a JSON file

Instead of using local data, you can populate the TreeView from a remote data source. In this example, a small JSON file is used.

[
    { "id": "1", "text": "Dairy", "expanded": false },
    { "id": "11", "pid": "1", "text": "Milk" },
    { "id": "12", "pid": "1", "text": "Butter" },
    { "id": "13", "pid": "1", "text": "Cheese" },
    { "id": "14", "pid": "1", "text": "Yogurt" },
    { "id": "2", "text": "Fruits", "expanded": false },
    { "id": "21", "pid": "2", "text": "Berries", "expanded": false },
    { "id": "211", "pid": "21", "text": "BlackBerries" },
    { "id": "212", "pid": "21", "text": "CranBerries" },
    { "id": "213", "pid": "21", "text": "StrawBerries" },
    { "id": "22", "pid": "2", "text": "Pits" },
    { "id": "23", "pid": "2", "text": "Core" },
    { "id": "24", "pid": "2", "text": "Citrus Fruits", "expanded": false },
    { "id": "241", "pid": "24", "text": "Oranges" },
    { "id": "242", "pid": "24", "text": "Lemons" },
    { "id": "25", "pid": "2", "text": "Melons" },
    { "id": "26", "pid": "2", "text": "Tropical Fruits", "expanded": false },
    { "id": "261", "pid": "26", "text": "Avocados" },
    { "id": "262", "pid": "26", "text": "Bananas" },
    { "id": "263", "pid": "26", "text": "Dates" },
    { "id": "3", "text": "Grains" },
    { "id": "4", "text": "Meat", "expanded": false },
    { "id": "41", "pid": "4", "text": "Beef" },
    { "id": "42", "pid": "4", "text": "Lamb", "expanded": false },
    { "id": "421", "pid": "42", "text": "Lamb Breast" },
    { "id": "422", "pid": "42", "text": "Lamb Leg" },
    { "id": "423", "pid": "42", "text": "Lamb Ribs" },
    { "id": "43", "pid": "4", "text": "Pork" },
    { "id": "5", "text": "Sweets" },
    { "id": "6", "text": "Vegetables" },
    { "id": "7", "text": "Water" }
]                            
                            

To load data from a JSON file, you can use the Angular http service. The get method of this service returns an observable to which you can subscribe and when data is available add it to the TreeView.

private onBeforeExpand(e: any){
    let self = this;

    if (e.item.items && e.item.items.length == 0){
        // Replace the expanding icon with a loading icon
        self.treeview.beginLoad(e.item);

        let loadTimer = setTimeout(function(){
            // Populate the TreeView with data from a JSON file
            self.loadFromJSON(e.item);

            clearTimeout(loadTimer);
        }, 1000);

    }
}

private loadFromJSON(item: any){
    // Use HTTP service to get data from the specified JSON file
    let obj: Observable = this.http.get("./file.json")
                                    .map(this.extractData)
                                    .catch(this.handleError); 

    // Subscribe to the observable, so when data is ready add it to the TreeView
    obj.subscribe(data => this.addData(data, item), error => console.log(error));
}

private addData(data: any, item?: any){
    // Load data from JSON into the TreeView as children for specified item
    this.treeview.loadData(data, item);

    // Restore the expanding icon
    this.treeview.endLoad(item);

    // Update the appareance of the TreeView
    this.treeview.refresh();
}

private extractData(res: Response){
    let obj = res.json();

    return obj || { };
}

private handleError(error: Response | any) {
    let errMsg: string;
    if (error instanceof Response) {
        const body = error.json() || '';
        const err = body.error || JSON.stringify(body);
        errMsg = `${error.status} - ${error.statusText || ''} ${err}`;
    }
    else 
        errMsg = error.message ? error.message : error.toString();

    console.error(errMsg);

    return Observable.throw(errMsg);
}
                            

The best way to load new data into the TreeView is by using the loadData method. This method accepts any data in flat format or as a tree hierarchy. If a parent item is specified, the data is added as its children, otherwise the TreeView is populated in whole.

Note When you have data with custom object filed names, you need to specify an object that will map your custom data fields with the ones used by the TreeView.

Conclusion

IntegralUI TreeView component for Angular 2 comes with built-in functionality that allows you to load data on demand into the TreeView as a whole, or as children to specified parent item. This process is accompanied with a loading animation that is customizable via CSS styles.

You can load new data either from a local or remote data source like JSON file or directly from a database.

The TreeView component is part of IntegralUI Web.

Did you Like this Article?


Enter your e-mail address below and you will receive latest articles as well as news on upcoming events and special offers.