All Components

Editing in Template-Driven Forms

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

For more information on how to utilize the Reactive Forms, refer to the article on editing the Grid in Reactive Forms.

For more information on how to build external popup editing forms in the Grid, refer to the article on external form editing.

Basic Concepts

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

import { Observable } from 'rxjs/Rx';
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 { EditService } from './edit.service';

@Component({
  selector: 'my-app',
  template: `
      <form novalidate #myForm="ngForm">
          <kendo-grid
              [data]="view | async"
              [height]="400"
              [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)"
            >
            <kendo-grid-toolbar>
                <button kendoGridAddCommand>Add new</button>
            </kendo-grid-toolbar>
            <kendo-grid-column field="ProductName" title="Product Name">
                <template kendoGridEditTemplate let-dataItem="dataItem">
                    <input [(ngModel)]="dataItem.ProductName" name="ProductName" class="k-textbox" required/>
                </template>
            </kendo-grid-column>
            <kendo-grid-column field="UnitPrice" editor="numeric" title="Price">
                <template kendoGridEditTemplate let-dataItem="dataItem">
                    <input [(ngModel)]="dataItem.UnitPrice" name="UnitPrice" class="k-textbox" type="number"/>
                </template>
            </kendo-grid-column>
            <kendo-grid-column field="Discontinued" editor="boolean" title="Discontinued">
                <template kendoGridEditTemplate let-dataItem="dataItem">
                    <input [(ngModel)]="dataItem.Discontinued" name="Discontinued" type="checkbox"/>
                </template>
            </kendo-grid-column>
            <kendo-grid-column field="UnitsInStock" editor="numeric" title="Units In Stock">
                <template kendoGridEditTemplate let-dataItem="dataItem">
                    <input [(ngModel)]="dataItem.UnitsInStock" name="UnitsInStock" required min="0" max="99" class="k-textbox" type="number"/>
                </template>
            </kendo-grid-column>
            <kendo-grid-command-column title="command" width="220">
                <template let-isNew="isNew">
                    <button kendoGridEditCommand class="k-primary">Edit</button>
                    <button kendoGridRemoveCommand>Remove</button>
                    <button kendoGridSaveCommand [disabled]="myForm.invalid">{{ isNew ? 'Add' : 'Update' }}</button>
                    <button kendoGridCancelCommand>{{ isNew ? 'Discard changes' : 'Cancel' }}</button>
                </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.map(data => process(data, this.gridState));

        this.editService.read();
    }

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

        this.editService.read();
    }

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

        sender.addRow(new Product());
    }

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

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

        sender.editRow(rowIndex);
    }

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

    private closeEditor(grid, rowIndex = this.editedRowIndex) {
        grid.closeRow(rowIndex);
        this.editService.resetItem(this.editedProduct);
        this.editedRowIndex = undefined;
        this.editedProduct = undefined;
    }

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

        sender.closeRow(rowIndex);

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

    protected removeHandler({dataItem}) {
        this.editService.remove(dataItem);
    }
}
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs/Rx';
import { Jsonp } from '@angular/http';
import { BehaviorSubject } from 'rxjs/BehaviorSubject';

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

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

    private data: any[] = [];

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

        this.fetch()
            .do(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.jsonp
            .get(`http://demos.telerik.com/kendo-ui/service/Products/${action}?callback=JSONP_CALLBACK${this.serializeModels(data)}`)
            .map(response => response.json());
    }

    private serializeModels(data?: any): string {
       return data ? `&models=${JSON.stringify([data])}` : '';
    }
}
export class Product {
    public ProductID: number;
    public ProductName: string = "";
    public Discountinued: boolean = false;
    public UnitsInStock: number;
    public UnitPrice: number = 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 { Jsonp, JsonpModule } from '@angular/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: [
        JsonpModule,
        BrowserModule,
        FormsModule,
        GridModule
    ],
    providers: [
        {
            deps: [Jsonp],
            provide: EditService,
            useFactory: (jsonp: Jsonp) => () => new EditService(jsonp)
        }
    ],
    bootstrap: [AppComponent]
})
export class AppModule {}

Setup

This section provides the steps for you to follow to enable the editing mode of the Grid in the Templte-Driven Forms.

Wrap the Component

The following example demonstrates how to wrap the Grid inside a Template-Driven Form.

<form novalidate #myForm="ngForm">
    <kendo-grid>
        <!-- the rest of Grid configuration goes  here -->
    </kendo-grid>
</form>

Configure the Columns Editor Template

To properly configure the editing mode, verify that each column of the Grid has a defined editor template.

<kendo-grid-column field="ProductName" title="Product Name">
    <template kendoGridEditTemplate let-dataItem="dataItem">
        <input [(ngModel)]="dataItem.ProductName" name="ProductName" class="k-textbox" required/>
    </template>
</kendo-grid-column>

Configure the Command Column

You need to define the command buttons inside the command column template.

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

Attach Handlers for CRUD 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 confguration -->
</kendo-grid>

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

Handling the edit Event

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.

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

    // track last edited row
    // it will be used in `closeEditor` for closing previous edited row
    this.editedRowIndex = rowIndex;

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

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

Handling the add Event

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.

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

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

Handling the cancel Event

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.

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

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

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

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

Handling the save Event

The save event fires when the kendoGridSaveCommand is clicked.

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

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

Handling the remove Event

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.

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