Template-Driven Forms

The TreeList provides options for editing its data by using the Template-Driven Angular Forms.

Basic Concepts

To implement the editing mode of the Kendo UI TreeList for Angular in Template-Driven Forms, define the EditTemplateDirective for every TreeList column.

Data-Binding Directives vs. Manual Setup

The TreeList includes a Template Editing Directive that significantly reduces the amount of boiler plate code required for editing. Try it out before using the more flexible, but verbose manual setup.

The following example demonstrates how to manually set up the inline editing mode of the Kendo UI TreeList for Angular using Template-Driven Forms.

import { Component, OnInit } from '@angular/core';
import { NgForm, FormControl, FormGroup, Validators } from '@angular/forms';
import { Observable } from 'rxjs';
import { take } from 'rxjs/operators';

import {
    AddEvent,
    CancelEvent,
    EditEvent,
    RemoveEvent,
    SaveEvent,
    TreeListComponent
} from '@progress/kendo-angular-treelist';

import { EmployeeEditService } from './employee-edit.service';
import { Employee } from './employee';


@Component({
  selector: 'my-app',
  template: `
    <form novalidate #myForm="ngForm">
        <kendo-treelist
            kendoTreeListExpandable
            [data]="rootData | async"
            idField="EmployeeId"
            [fetchChildren]="fetchChildren"
            [hasChildren]="hasChildren"
            (add)="addHandler($event, myForm)"
            (edit)="editHandler($event)"
            (remove)="removeHandler($event)"
            (save)="saveHandler($event, myForm)"
            (cancel)="cancelHandler($event)"
            [height]="533"
        >
            <ng-template kendoTreeListToolbarTemplate>
                <button kendoTreeListAddCommand type="button">Add new</button>
            </ng-template>
            <kendo-treelist-column [expandable]="true" field="FirstName" title="First Name">
                <ng-template kendoTreeListEditTemplate let-dataItem="dataItem">
                    <input [(ngModel)]="dataItem.FirstName" name="FirstName" class="k-textbox" required/>
                </ng-template>
            </kendo-treelist-column>
            <kendo-treelist-column field="LastName" title="Last Name">
                <ng-template kendoTreeListEditTemplate let-dataItem="dataItem">
                    <input [(ngModel)]="dataItem.LastName" name="LastName" class="k-textbox" required/>
                </ng-template>
            </kendo-treelist-column>
            <kendo-treelist-column field="Position" title="Position">
                <ng-template kendoTreeListEditTemplate let-dataItem="dataItem">
                    <input [(ngModel)]="dataItem.Position" name="Position" class="k-textbox"/>
                </ng-template>
            </kendo-treelist-column>
            <kendo-treelist-column field="Extension" title="Extension" editor="numeric" format="#">
                <ng-template kendoTreeListEditTemplate let-dataItem="dataItem">
                    <input
                        [(ngModel)]="dataItem.Extension"
                        name="Extension"
                        required
                        min="0"
                        max="99"
                        class="k-textbox"
                        type="number"/>
                </ng-template>
            </kendo-treelist-column>
            <kendo-treelist-command-column width="140">
                <ng-template kendoTreeListCellTemplate let-isNew="isNew" let-cellContext="cellContext">
                    <!-- "Add Child" command directive, will not be visible in edit mode -->
                    <button [kendoTreeListAddCommand]="cellContext"
                            icon="filter-add-expression" title="Add Child">
                    </button>

                    <!-- "Edit" command directive, will not be visible in edit mode -->
                    <button [kendoTreeListEditCommand]="cellContext"
                            icon="edit" title="Edit" [primary]="true">
                    </button>

                    <!-- "Remove" command directive, will not be visible in edit mode -->
                    <button [kendoTreeListRemoveCommand]="cellContext"
                            icon="delete" title="Remove">
                    </button>

                    <!-- "Save" command directive, will be visible only in edit mode -->
                    <button [kendoTreeListSaveCommand]="cellContext"
                            [disabled]="formGroup?.invalid"
                            icon="save" title="{{ isNew ? 'Add' : 'Update' }}">
                    </button>

                    <!-- "Cancel" command directive, will be visible only in edit mode -->
                    <button [kendoTreeListCancelCommand]="cellContext"
                            icon="cancel" title="{{ isNew ? 'Discard changes' : 'Cancel' }}">
                    </button>
                </ng-template>
            </kendo-treelist-command-column>
        </kendo-treelist>
      </form>
  `
})
export class AppComponent implements OnInit {
    public rootData: Observable<Employee[]>;
    public editedItem: Employee;
    private originalValues: any;

    constructor(private editService: EmployeeEditService) {}

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

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

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

    public addHandler({ sender, parent }: AddEvent, form: NgForm): void {
        // Close the current edited row, if any
        this.closeEditor(sender);

        // Expand the parent.
        if (parent) {
            sender.expand(parent);
        }

        // Associate the new records with the parent employee, if any
        const employee = {
            ReportsTo: parent ? parent.EmployeeId : null
        };

        // Add the new row and put it in edit mode
        sender.addRow(employee, parent);
    }

    public editHandler({ sender, dataItem }: EditEvent): void {
        // Close the current edited row, if any.
        this.closeEditor(sender, dataItem);

        // Store a copy of the original item values in case editing is cancelled
        this.editedItem = dataItem;
        this.originalValues = { ...dataItem };

        // Put the row in edit mode
        sender.editRow(dataItem);
    }

    public cancelHandler({ sender, dataItem, isNew }: CancelEvent): void {
        // Close the editor for the given row
        this.closeEditor(sender, dataItem, isNew);
    }

    public saveHandler({ sender, dataItem, parent, isNew }: SaveEvent, form: NgForm): void {
        if (!form.valid) {
            return;
        }

        if (isNew && parent) {
            // Update the hasChildren field on the parent node
            parent.hasChildren = true;
        }

        this.editService
            // Publish the update to the remote service.
            .save(dataItem, parent, isNew)
            .pipe(take(1))
            .subscribe(() => {
                if (parent) {
                    // Reload the parent node to reflect the changes.
                    sender.reload(parent);
                }
            });

        sender.closeRow(dataItem, isNew);
    }

    public removeHandler({ sender, dataItem, parent }: RemoveEvent): void {
        this.editService
            // Publish the update to the remote service.
            .remove(dataItem, parent)
            .pipe(take(1))
            .subscribe(() => {
                if (parent) {
                    // Reload the parent node to reflect the changes.
                    sender.reload(parent);
                }
            });
    }

    private closeEditor(treelist: TreeListComponent, dataItem: any = this.editedItem, isNew: boolean = false): void {
        treelist.closeRow(dataItem, isNew);

        if (this.editedItem) {
            // Revert to the original values
            Object.assign(this.editedItem, this.originalValues);
        }

        this.editedItem = undefined;
        this.originalValues = undefined;
    }
}
import { Injectable } from '@angular/core';
import { HttpClient, HttpParams } from '@angular/common/http';
import { Observable, BehaviorSubject } from 'rxjs';
import { tap, take } from 'rxjs/operators';

import { Employee } from './employee';

const CREATE_ACTION = 'Create';
const UPDATE_ACTION = 'Update';
const REMOVE_ACTION = 'Destroy';

function noop() { /* */ }

@Injectable()
export class EmployeeEditService extends BehaviorSubject<Employee[]> {
    constructor(private http: HttpClient) {
        super(null);
    }

    public read(): void {
        this.fetch('')
            .pipe(take(1))
            .subscribe(data => this.next(data));
    }

    public fetchChildren(reportsTo: number = null): Observable<Employee[]> {
        return this.fetch('', null, reportsTo);
    }

    public update(item: Employee): void {
        this.save(item, null, false)
            .pipe(take(1))
            .subscribe(noop);
    }

    public save(item: Employee, parent: Employee, isNew: boolean): Observable<Employee[]> {
        const action = isNew ? CREATE_ACTION : UPDATE_ACTION;

        return this.fetch(action, item).pipe(tap(() => {
            if (!parent && isNew) {
                this.read();
            }
        }));
    }

    public remove(item: any, parent?: Employee): Observable<Employee[]> {
        return this.fetch(REMOVE_ACTION, item).pipe(tap(() => {
            if (!parent) {
                this.read();
            }
        }));
    }

    private fetch(action: string = '', data?: any, id?: any): Observable<Employee[]> {
        let params = new HttpParams();

        if (typeof id !== 'undefined') {
            params = params.set('id', id);
        }

        if (data) {
            params = params.set('models', JSON.stringify([data]));
        }

        return this.http.jsonp<Employee[]>(
            `https://demos.telerik.com/kendo-ui/service/EmployeeDirectory/${action}?${params.toString()}`,
            'callback'
        );
    }
}
export interface Employee {
    EmployeeId: number;
    ReportsTo: number;
    FirstName: string;
    LastName: string;
    Position: string;
    Extension: string;
    hasChildren?: boolean;
}
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { HttpClientModule, HttpClientJsonpModule } from '@angular/common/http';
import { FormsModule } from '@angular/forms';

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

import { AppComponent } from './app.component';
import { EmployeeEditService } from './employee-edit.service';

@NgModule({
    declarations: [
        AppComponent
    ],
    imports: [
        HttpClientModule,
        HttpClientJsonpModule,
        BrowserModule,
        BrowserAnimationsModule,
        FormsModule,
        TreeListModule
    ],
    providers: [
        EmployeeEditService
    ],
    bootstrap: [AppComponent]
})
export class AppModule {}
import { enableProdMode } from '@angular/core';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppModule } from './app.module';

enableProdMode();

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

Setup

To enable the editing mode of the TreeList in Template-Driven Forms:

  1. Wrap the component.

    <form novalidate #myForm="ngForm">
       <kendo-treelist>
           <!-- the rest of TreeList configuration goes here -->
       </kendo-treelist>
    </form>
  2. Configure the columns editor template. Verify that each column of the TreeList has a defined editor template.

    <kendo-treelist-column [expandable]="true" field="FirstName" title="First Name">
       <ng-template kendoTreeListEditTemplate let-dataItem="dataItem">
           <input [(ngModel)]="dataItem.FirstName" name="FirstName" class="k-textbox" required/>
       </ng-template>
    </kendo-treelist-column>
  3. Configure the command column. You need to define the command buttons inside the CommandColumn template.

    <kendo-treelist-command-column width="140">
       <ng-template kendoTreeListCellTemplate let-isNew="isNew" let-cellContext="cellContext">
           <!-- "Add Child" command directive, will not be visible in edit mode -->
           <button [kendoTreeListAddCommand]="cellContext"
                   icon="filter-add-expression" title="Add Child">
           </button>
    
           <!-- "Edit" command directive, will not be visible in edit mode -->
           <button [kendoTreeListEditCommand]="cellContext"
                   icon="edit" title="Edit" [primary]="true">
           </button>
    
           <!-- "Remove" command directive, will not be visible in edit mode -->
           <button [kendoTreeListRemoveCommand]="cellContext"
                   icon="delete" title="Remove">
           </button>
    
           <!-- "Save" command directive, will be visible only in edit mode -->
           <button [kendoTreeListSaveCommand]="cellContext"
                   [disabled]="formGroup?.invalid"
                   icon="save" title="{{ isNew ? 'Add' : 'Update' }}">
           </button>
    
           <!-- "Cancel" command directive, will be visible only in edit mode -->
           <button [kendoTreeListCancelCommand]="cellContext"
                   icon="cancel" title="{{ isNew ? 'Discard changes' : 'Cancel' }}">
           </button>
       </ng-template>
    </kendo-treelist-command-column>
  4. Attach handlers for CRUD data operations.

    When a command button is clicked, the TreeList emits the corresponding event. To instruct the component what action to perform, handle the event that is emitted.

    <kendo-treelist
       [data]="rootData | async"
       idField="EmployeeId"
       [fetchChildren]="fetchChildren"
       [hasChildren]="hasChildren"
       (add)="addHandler($event, myForm)"
       (edit)="editHandler($event)"
       (remove)="removeHandler($event)"
       (save)="saveHandler($event)"
       (cancel)="cancelHandler($event)"
    >
    <!-- the rest of configuration -->
    </kendo-treelist>

Toggling the Edit State

Inside the corresponding event handlers, you can toggle the edit state of the TreeList by using the:

Adding Records

The add event fires when the kendoTreeListAddCommand is clicked. Inside the event handler, you can show the new row editor by calling the addRow method.

public addHandler({ sender, parent }: AddEvent, form: NgForm): void {
    // Close the current edited row, if any
    this.closeEditor(sender);

    // Expand the parent.
    if (parent) {
        sender.expand(parent);
    }

    // Associate the new records with the parent employee, if any
    const employee = {
        ReportsTo: parent ? parent.EmployeeId : null
    };

    // Add the new row and put it in edit mode
    sender.addRow(employee, parent);
}

Editing Records

The edit event fires when the kendoTreeListEditCommand is clicked. Inside the event handler, you can set the row to the editing mode by calling the editRow method.

public editHandler({ sender, dataItem }: EditEvent): void {
    // Close the current edited row, if any.
    this.closeEditor(sender, dataItem);

    // Store a copy of the original item values in case editing is cancelled
    this.editedItem = dataItem;
    this.originalValues = { ...dataItem };

    // Put the row in edit mode
    sender.editRow(dataItem);
}

Removing Records

The remove event fires when the kendoTreeListRemoveCommand is clicked. Inside the event handler, you can issue a request to remove the current data item from the data source.

public removeHandler({ sender, dataItem, parent }: RemoveEvent): void {
    this.editService
        // Publish the update to the remote service.
        .remove(dataItem, parent)
        .pipe(take(1))
        .subscribe(() => {
            if (parent) {
                // Reload the parent node to reflect the changes.
                sender.reload(parent);
            }
        });
}

Saving Records

The save event fires when the kendoTreeListSaveCommand is clicked.

public saveHandler({ sender, dataItem, parent, isNew }: SaveEvent, form: NgForm): void {
    if (!form.valid) {
        return;
    }

    this.editService
        // Publish the update to the remote service.
        .save(dataItem, parent, isNew)
        .pipe(take(1))
        .subscribe(() => {
            if (parent) {
                // Reload the parent node to reflect the changes.
                sender.reload(parent);
            }
        });

    sender.closeRow(dataItem, isNew);
}

Cancelling Editing

The cancel event fires when the kendoTreeListCancelCommand is clicked. Inside the event handler, you can switch the row back to the view mode by calling the closeRow method.

public cancelHandler({ sender, dataItem, isNew }: CancelEvent): void {
    // Close the editor for the given row
    this.closeEditor(sender, dataItem, isNew);
}

private closeEditor(treelist: TreeListComponent, dataItem: any = this.editedItem, isNew: boolean = false): void {
    treelist.closeRow(dataItem, isNew);

    if (this.editedItem) {
        // Revert to the original values
        Object.assign(this.editedItem, this.originalValues);
    }

    this.editedItem = undefined;
    this.originalValues = undefined;
}

In this article