Binding to Remote Data

The TreeList is agnostic as to how and when the data gets updated. Binding to remote data uses the same API as local data binding.

To bind the TreeList to remote data:

  • Fetch the root nodes and set them as data.
  • Declare a hasChildren function that checks if a node is expandable.
  • Declare a fetchChildren а function that retrieves the child nodes of a particular node from the remote service.

The example below uses the EmployeesDirectory end-point from the Kendo UI ASP.NET sample service to retrieve an organizational chart. It accepts a single parameter—the manager id. If the parameter is omitted or set to null, the service returns the root of the organizational chart.

import { Component, OnInit } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';
import { Employee } from './employee';



@Component({
    selector: 'my-app',
    template: `
        <kendo-treelist
            kendoTreeListExpandable
            [data]="rootData | async"
            [fetchChildren]="fetchChildren"
            [hasChildren]="hasChildren"
            [height]="400"
          >
          <kendo-treelist-column [expandable]="true" title="Name" [width]="250">
              <ng-template kendoTreeListCellTemplate let-dataItem>
                  {{ dataItem.FirstName }}
                  {{ dataItem.LastName }}
              </ng-template>
          </kendo-treelist-column>
          <kendo-treelist-column field="Position" title="Position" [width]="180">
          </kendo-treelist-column>
          <kendo-treelist-column field="Extension" title="Extension" [width]="180">
          </kendo-treelist-column>
        </kendo-treelist>
    `
})
export class AppComponent implements OnInit {
    public rootData: Observable<Employee[]>;
    private serviceUrl = 'https://demos.telerik.com/kendo-ui/service/EmployeeDirectory';

    constructor(private http: HttpClient) {}

    public ngOnInit(): void {
        this.rootData = this.query();
    }

    public fetchChildren = (item: Employee): Observable<Employee[]> => {
        return this.query(item.EmployeeId);
    }

    public hasChildren = (item: Employee): boolean => {
        return item.hasChildren;
    }

    public query(reportsTo: number = null): Observable<Employee[]> {
        return this.http.jsonp<Employee[]>(
            `${this.serviceUrl}?id=${reportsTo}`,
            'callback'
        );
    }
}
import { NgModule } from '@angular/core';
import { HttpClientModule, HttpClientJsonpModule } from '@angular/common/http';
import { BrowserModule } from '@angular/platform-browser';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';

import { TreeListModule } from '@progress/kendo-angular-treelist';

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

@NgModule({
    imports: [
        BrowserModule,
        BrowserAnimationsModule,
        HttpClientModule,
        HttpClientJsonpModule,
        TreeListModule
    ],
    declarations: [
        AppComponent
    ],
    bootstrap: [
        AppComponent
    ]
})

export class AppModule { }
export interface Employee {
    EmployeeId: number;
    FirstName: string;
    LastName: string;
    Position: string;
    Extension: string;
    hasChildren?: boolean;
}
import { enableProdMode } from '@angular/core';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppModule } from './app.module';

enableProdMode();

const platform = platformBrowserDynamic();
platform.bootstrapModule(AppModule);

Creating a Custom Directive

Creating a custom data-binding directive allows to encapsulate the data-binding code and to reuse it across multiple instances.

To extract the logic from the example above into a custom data-binding directive:

  1. Create an Angular service to encapsulate the details of invoking the remote service.

    @Injectable()
    export class EmployeesService {
       private url: string = defaultUrl;
    
       constructor(
           private http: HttpClient
       ) {}
    
       public query(reportsTo: number = null): Observable<Employee[]> {
           return this.http.jsonp<Employee[]>(
               `${this.url}?id=${reportsTo}`,
               'callback'
           );
       }
    }
  2. Create a EmployeesBindingDirective directive and use DI to provide the instance of the EmployeesService and the TreeListComponent.

    @Directive({
       selector: '[employeesBinding]'
    })
    export class EmployeesBindingDirective {
       constructor(
           private service: EmployeesService,
           private treelist: TreeListComponent
       ) {}
    }
  3. Declare the fetchChildren and hasChildren methods using the model structure.

    @Directive({
       selector: '[employeesBinding]'
    })
    export class EmployeesBindingDirective {
       constructor(
           private service: EmployeesService,
           private treelist: TreeListComponent
       ) {}
    
       public fetchChildren = (item: Employee): Observable<Employee[]> => {
           return this.service.query(item.EmployeeId);
       }
    
       public hasChildren = (item: Employee): boolean => {
           return item.hasChildren;
       }
    }
  4. Inside OnInit, hook up to the callbacks by using bind() to maintain the this reference. Assign the observable of root nodes to TreeListComponent.data

    @Directive({
       selector: '[employeesBinding]'
    })
    export class EmployeesBindingDirective implements OnInit {
       constructor(
           private service: EmployeesService,
           private treelist: TreeListComponent
       ) {}
    
       public ngOnInit(): void {
           this.treelist.fetchChildren = this.fetchChildren.bind(this);
           this.treelist.hasChildren = this.hasChildren.bind(this);
    
           this.treelist.data = this.service.query();
       }
    
       public fetchChildren = (item: Employee): Observable<Employee[]> => {
           return this.service.query(item.EmployeeId);
       }
    
       public hasChildren = (item: Employee): boolean => {
           return item.hasChildren;
       }
    }
  5. Now you can use the custom directive on the TreeList instance in AppComponent.

    <kendo-treelist employeesBinding>
         <kendo-treelist-column [expandable]="true" title="Name" [width]="250">
             <ng-template kendoTreeListCellTemplate let-dataItem>
                 {{ dataItem.FirstName }}
                 {{ dataItem.LastName }}
             </ng-template>
         </kendo-treelist-column>
         <kendo-treelist-column field="Position" title="Position" [width]="180">
         </kendo-treelist-column>
         <kendo-treelist-column field="Extension" title="Extension" [width]="180">
         </kendo-treelist-column>
       </kendo-treelist>

The following example demonstrates the custom data binding directive in action.

import { Component } from '@angular/core';

@Component({
    selector: 'my-app',
    template: `
        <kendo-treelist
            employeesBinding
            [height]="400"
            kendoTreeListExpandable
          >
          <kendo-treelist-column [expandable]="true" title="Name" [width]="250">
              <ng-template kendoTreeListCellTemplate let-dataItem>
                  {{ dataItem.FirstName }}
                  {{ dataItem.LastName }}
              </ng-template>
          </kendo-treelist-column>
          <kendo-treelist-column field="Position" title="Position" [width]="180">
          </kendo-treelist-column>
          <kendo-treelist-column field="Extension" title="Extension" [width]="180">
          </kendo-treelist-column>
        </kendo-treelist>
    `
})
export class AppComponent {}
import { NgModule } from '@angular/core';
import { HttpClientModule, HttpClientJsonpModule } from '@angular/common/http';
import { BrowserModule } from '@angular/platform-browser';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';

import { TreeListModule } from '@progress/kendo-angular-treelist';

import { AppComponent } from './app.component';
import { EmployeesBindingDirective } from './employees-binding.directive';
import { EmployeesService } from './employees.service';

@NgModule({
    imports: [
        BrowserModule,
        BrowserAnimationsModule,
        HttpClientModule,
        HttpClientJsonpModule,
        TreeListModule
    ],
    declarations: [
        AppComponent,
        EmployeesBindingDirective
    ],
    bootstrap: [
        AppComponent
    ],
    providers: [
        EmployeesService
    ]
})

export class AppModule { }
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';

import { Employee } from './employee';

const defaultUrl = 'https://demos.telerik.com/kendo-ui/service/EmployeeDirectory';

@Injectable()
export class EmployeesService {
    private url: string = defaultUrl;

    constructor(
        private http: HttpClient
    ) {}

    public query(reportsTo: number = null): Observable<Employee[]> {
        return this.http.jsonp<Employee[]>(
            `${this.url}?id=${reportsTo}`,
            'callback'
        );
    }
}
import { Directive, OnInit } from '@angular/core';
import { Observable } from 'rxjs';
import { TreeListComponent } from '@progress/kendo-angular-treelist';
import { Employee } from './employee';
import { EmployeesService } from './employees.service';

@Directive({
    selector: '[employeesBinding]'
})
export class EmployeesBindingDirective implements OnInit {
    constructor(
        private service: EmployeesService,
        private treelist: TreeListComponent
    ) {}

    public ngOnInit(): void {
        this.treelist.fetchChildren = this.fetchChildren.bind(this);
        this.treelist.hasChildren = this.hasChildren.bind(this);

        this.treelist.data = this.service.query();
    }

    public fetchChildren = (item: Employee): Observable<Employee[]> => {
        return this.service.query(item.EmployeeId);
    }

    public hasChildren = (item: Employee): boolean => {
        return item.hasChildren;
    }
}
export interface Employee {
    EmployeeId: number;
    FirstName: string;
    LastName: string;
    Position: string;
    Extension: string;
    hasChildren?: boolean;
}
import { enableProdMode } from '@angular/core';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppModule } from './app.module';

enableProdMode();

const platform = platformBrowserDynamic();
platform.bootstrapModule(AppModule);

In this article