All Components

Template-Driven Forms

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

Basic Concepts

To implement the editing mode of the Grid in Template-Driven Forms, define the editor template for every Grid column.

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

import { GridDataResult } from '@progress/kendo-angular-grid';
import { State, process } from '@progress/kendo-data-query';

import { Product } from './model';
import { map } from 'rxjs/operators/map';
import { EditService } from './edit.service';


@Component({
  selector: 'my-app',
  template: `
      <form novalidate #myForm="ngForm">
          <kendo-grid
              [data]="view | async"
              [height]="533"
              [pageSize]="gridState.take" [skip]="gridState.skip" [sort]="gridState.sort"
              [pageable]="true" [sortable]="true"
              (dataStateChange)="onStateChange($event)"
              (edit)="editHandler($event)" (cancel)="cancelHandler($event)"
              (save)="saveHandler($event)" (remove)="removeHandler($event)"
              (add)="addHandler($event)"
              [navigable]="true"
            >
            <ng-template kendoGridToolbarTemplate>
                <button kendoGridAddCommand type="button">Add new</button>
            </ng-template>
            <kendo-grid-column field="ProductName" title="Product Name">
                <ng-template kendoGridEditTemplate let-dataItem="dataItem">
                    <input [(ngModel)]="dataItem.ProductName" kendoGridFocusable name="ProductName" class="k-textbox" required/>
                </ng-template>
            </kendo-grid-column>
            <kendo-grid-column field="UnitPrice" editor="numeric" title="Price">
                <ng-template kendoGridEditTemplate let-dataItem="dataItem">
                    <input [(ngModel)]="dataItem.UnitPrice" kendoGridFocusable name="UnitPrice" class="k-textbox" type="number"/>
                </ng-template>
            </kendo-grid-column>
            <kendo-grid-column field="Discontinued" editor="boolean" title="Discontinued">
                <ng-template kendoGridEditTemplate let-dataItem="dataItem">
                    <input [(ngModel)]="dataItem.Discontinued" kendoGridFocusable name="Discontinued" type="checkbox"/>
                </ng-template>
            </kendo-grid-column>
            <kendo-grid-column field="UnitsInStock" editor="numeric" title="Units In Stock">
                <ng-template kendoGridEditTemplate let-dataItem="dataItem">
                    <input
                        [(ngModel)]="dataItem.UnitsInStock"
                        kendoGridFocusable
                        name="UnitsInStock"
                        required
                        min="0"
                        max="99"
                        class="k-textbox"
                        type="number"/>
                </ng-template>
            </kendo-grid-column>
            <kendo-grid-command-column title="command" width="220">
                <ng-template kendoGridCellTemplate let-isNew="isNew">
                    <button kendoGridEditCommand type="button" class="k-primary">Edit</button>
                    <button kendoGridRemoveCommand type="button">Remove</button>
                    <button kendoGridSaveCommand type="button" [disabled]="myForm.invalid">{{ isNew ? 'Add' : 'Update' }}</button>
                    <button kendoGridCancelCommand type="button">{{ isNew ? 'Discard changes' : 'Cancel' }}</button>
                </ng-template>
            </kendo-grid-command-column>
          </kendo-grid>
      </form>
  `
})
export class AppComponent implements OnInit {
    public view: Observable<GridDataResult>;
    public gridState: State = {
        sort: [],
        skip: 0,
        take: 10
    };

    private editService: EditService;
    private editedRowIndex: number;
    private editedProduct: Product;

    constructor(@Inject(EditService) editServiceFactory: any) {
        this.editService = editServiceFactory();
    }

    public ngOnInit(): void {
        this.view = this.editService.pipe(map(data => process(data, this.gridState)));

        this.editService.read();
    }

    public onStateChange(state: State) {
        this.gridState = state;

        this.editService.read();
    }

    public addHandler({sender}) {
        this.closeEditor(sender);

        sender.addRow(new Product());
    }

    public editHandler({sender, rowIndex, dataItem}) {
        this.closeEditor(sender);

        this.editedRowIndex = rowIndex;
        this.editedProduct = Object.assign({}, dataItem);

        sender.editRow(rowIndex);
    }

    public cancelHandler({sender, rowIndex}) {
        this.closeEditor(sender, rowIndex);
    }

    public saveHandler({sender, rowIndex, dataItem, isNew}) {
        this.editService.save(dataItem, isNew);

        sender.closeRow(rowIndex);

        this.editedRowIndex = undefined;
        this.editedProduct = undefined;
    }

    public removeHandler({dataItem}) {
        this.editService.remove(dataItem);
    }

    private closeEditor(grid, rowIndex = this.editedRowIndex) {
        grid.closeRow(rowIndex);
        this.editService.resetItem(this.editedProduct);
        this.editedRowIndex = undefined;
        this.editedProduct = undefined;
    }
}
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs/Observable';
import { HttpClient } from '@angular/common/http';
import { BehaviorSubject } from 'rxjs/BehaviorSubject';

import { tap } from 'rxjs/operators/tap';
import { map } from 'rxjs/operators/map';

const CREATE_ACTION = 'create';
const UPDATE_ACTION = 'update';
const REMOVE_ACTION = 'destroy';

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

    private data: any[] = [];

    public read() {
        if (this.data.length) {
            return super.next(this.data);
        }

        this.fetch()
            .pipe(
                tap(data => {
                    this.data = data;
                })
            )
            .subscribe(data => {
                super.next(data);
            });
    }

    public save(data: any, isNew?: boolean) {
        const action = isNew ? CREATE_ACTION : UPDATE_ACTION;

        this.reset();

        this.fetch(action, data)
            .subscribe(() => this.read(), () => this.read());
    }

    public remove(data: any) {
        this.reset();

        this.fetch(REMOVE_ACTION, data)
            .subscribe(() => this.read(), () => this.read());
    }

    public resetItem(dataItem: any) {
        if (!dataItem) { return; }

        // find orignal data item
        const originalDataItem = this.data.find(item => item.ProductID === dataItem.ProductID);

        // revert changes
        Object.assign(originalDataItem, dataItem);

        super.next(this.data);
    }

    private reset() {
        this.data = [];
    }

    private fetch(action: string = '', data?: any): Observable<any[]> {
        return this.http
            .jsonp(`https://demos.telerik.com/kendo-ui/service/Products/${action}?${this.serializeModels(data)}`, 'callback')
            .pipe(map(res => <any[]>res));
    }

    private serializeModels(data?: any): string {
        return data ? `&models=${JSON.stringify([data])}` : '';
    }
}
export class Product {
    public ProductID: number;
    public ProductName = '';
    public Discontinued = false;
    public UnitsInStock: number;
    public UnitPrice = 0;
}
import { enableProdMode } from '@angular/core';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppModule } from './ng.module';

enableProdMode();

const platform = platformBrowserDynamic();
platform.bootstrapModule(AppModule);
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { HttpClient, HttpClientModule, HttpClientJsonpModule } from '@angular/common/http';
import { FormsModule } from '@angular/forms';

import { GridModule } from '@progress/kendo-angular-grid';

import { AppComponent } from './app.component';
import { EditService } from './edit.service';

@NgModule({
    declarations: [
        AppComponent
    ],
    imports: [
        HttpClientModule,
        HttpClientJsonpModule,
        BrowserModule,
        BrowserAnimationsModule,
        FormsModule,
        GridModule
    ],
    providers: [
        {
            deps: [HttpClient],
            provide: EditService,
            useFactory: (jsonp: HttpClient) => () => new EditService(jsonp)
        }
    ],
    bootstrap: [AppComponent]
})
export class AppModule {}

Setup

The editing of the Grid in template-driven forms is based on the row index. As a result, the Template Forms immediately update the value.

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

  1. Wrap the component.

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

    <kendo-grid-column field="ProductName" title="Product Name">
       <ng-template kendoGridEditTemplate let-dataItem="dataItem">
           <input [(ngModel)]="dataItem.ProductName" name="ProductName" class="k-textbox" required/>
       </ng-template>
    </kendo-grid-column>
  3. Configure the command column. You need to define the command buttons inside the CommandColumn template.

    <kendo-grid-command-column title="command" width="220">
       <ng-template kendoGridCellTemplate let-isNew="isNew">
           <!-- edit the command directive, will be visible when not in edit mode -->
           <button kendoGridEditCommand class="k-primary">Edit</button>
           <!-- remove the command directive, will be visible when not in edit mode -->
           <button kendoGridRemoveCommand>Remove</button>
           <!-- save the command directive, will be visible when in edit mode -->
           <button kendoGridSaveCommand>{{ isNew ? 'Add' : 'Update' }}</button>
           <!-- cancel the command directive, will be visible when in edit mode -->
           <button kendoGridCancelCommand>{{ isNew ? 'Discard changes' : 'Cancel' }}</button>
       </ng-template>
    </kendo-grid-command-column>
  4. Attach handlers for CRUD data operations.

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

    <kendo-grid
     [data]="view | async"
     (edit)="editHandler($event)" (cancel)="cancelHandler($event)"
     (save)="saveHandler($event)" (remove)="removeHandler($event)"
     (add)="addHandler($event)"
    >
    <!-- the rest of configuration -->
    </kendo-grid>

Toggling the Edit State

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

Editing Records

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

public editHandler({sender, rowIndex, dataItem}) {
    // close the previously edited item
    this.closeEditor(sender);

    // track the most recently edited row
    // it will be used in `closeEditor` for closing the previously edited row
    this.editedRowIndex = rowIndex;

    // clone the current - `[(ngModel)]` will modify the original item
    // use this copy to revert changes
    this.editedProduct = Object.assign({}, dataItem);

    // edit the row
    sender.editRow(rowIndex);
}

Adding Records

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

public addHandler({sender}) {
    // close the previously edited item
    this.closeEditor(sender);

    // open a new item editor
    sender.addRow(new Product());
}

Cancelling Editing

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

public cancelHandler({sender, rowIndex}) {
    // call the helper method
    this.closeEditor(sender, rowIndex);
}

private closeEditor(grid, rowIndex = this.editedRowIndex) {
    // close the editor
    grid.closeRow(rowIndex);

    // revert the data item to original state
    this.editService.resetItem(this.editedProduct, rowIndex);

    // reset the helpers
    this.editedRowIndex = undefined;
    this.editedProduct = undefined;
}

Saving Records

The save event fires when the kendoGridSaveCommand is clicked.

public saveHandler({sender, rowIndex, dataItem, isNew}) {
    // update the data source
    this.editService.save(dataItem, isNew);

    // close the editor, that is, revert the row back into view mode
    sender.closeRow(rowIndex);
}

Removing Records

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

public removeHandler({dataItem}) {
    // remove the current dataItem from the current data source
    // in this example, the dataItem is `editService`
    this.editService.remove(dataItem);
}
In this article