All Components

Editing in Reactive Forms

This article demonstrates how to handle the editing mode of the Grid by utilizing the Reactive Forms.

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

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

Overview

Editing is one of the basic functionalities the Kendo UI Grid for Angular 2 supports. Currently, the Grid delivers an inline built-in editing form.

The editing functionality of the component fully depends on the Angular Forms directives which track the occurrence of changes in a field and whether the field becomes valid or invalid.

The available Angular Forms are:

  • Template-Driven Forms.
  • Reactive Forms (Model-Driven Forms).

Basic Concepts

By default, the built-in column editors in the Grid utilize the Model-driven Angular Forms directives. To manipulate a Grid row, you need to call the addRow or the editRow method respectively and then pass the FormGroup property as one of its parameters. The FormGroup configuration is part of the Angular Forms package.

Generally, to configure the editing mode of the Grid, you have to:

  1. Click the command button so that the corresponding event is triggered.
  2. In the corresponding event handler, define the way you want the Grid to handle the field. For example, build the FormGroup, call the editRow or the closeRow method, and so on.

After you set the event handler, the row acquires the respective behavior that is configured in it. For a detailed description on the configuration of the editing mode, refer to the following section on the setup.

The following example demonstrates how to set the inline editing mode of the Kendo UI Grid for Angular.

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: `
      <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"></kendo-grid-column>
        <kendo-grid-column field="UnitPrice" editor="numeric" title="Price"></kendo-grid-column>
        <kendo-grid-column field="Discontinued" editor="boolean" title="Discontinued"></kendo-grid-column>
        <kendo-grid-column field="UnitsInStock" editor="numeric" title="Units In Stock"></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]="formGroup?.invalid">{{ isNew ? 'Add' : 'Update' }}</button>
                <button kendoGridCancelCommand>{{ isNew ? 'Discard changes' : 'Cancel' }}</button>
            </template>
        </kendo-grid-command-column>
      </kendo-grid>
  `
})
export class AppComponent implements OnInit {
    public view: Observable<GridDataResult>;
    public gridState: State = {
        sort: [],
        skip: 0,
        take: 10
    };
    public formGroup: FormGroup;

    private editService: EditService;
    private editedRowIndex: number;

    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);

        this.formGroup = new FormGroup({
            'ProductID': new FormControl(),
            'ProductName': new FormControl("", Validators.required),
            'UnitPrice': new FormControl(0),
            'UnitsInStock': new FormControl("", Validators.compose([Validators.required, Validators.pattern('^[0-9]{1,2}')])),
            'Discontinued': new FormControl(false)
        });

        sender.addRow(this.formGroup);
    }

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

        this.formGroup = new FormGroup({
            'ProductID': new FormControl(dataItem.ProductID),
            'ProductName': new FormControl(dataItem.ProductName, Validators.required),
            'UnitPrice': new FormControl(dataItem.UnitPrice),
            'UnitsInStock': new FormControl(dataItem.UnitsInStock, Validators.compose([Validators.required, Validators.pattern('^[0-9]{1,2}')])),
            'Discontinued': new FormControl(dataItem.Discontinued)
        });

        this.editedRowIndex = rowIndex;

        sender.editRow(rowIndex, this.formGroup);
    }

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

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

    protected saveHandler({sender, rowIndex, formGroup, isNew}) {
        const product: Product = formGroup.value;

        this.editService.save(product, isNew);

        sender.closeRow(rowIndex);
    }

    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(data: any, index: number) {
        if (!data) { return; }

        this.data.splice(index, 1, data);
        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 { AppModule } from './ng.module';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';

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

import { NgModule }      from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { Jsonp, JsonpModule } from '@angular/http';
import { ReactiveFormsModule } 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,
        ReactiveFormsModule,
        GridModule
    ],
    providers: [
        {
            deps: [Jsonp],
            provide: EditService,
            useFactory: (jsonp: Jsonp) => () => new EditService(jsonp)
        }
    ],
    bootstrap: [AppComponent]
})
export class AppModule {}

Configuration

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

Configure the Type of the Columns Editor

The default editor created by the Grid is the text editor. You can change this behavior, set the editor property.

<!-- setting numeric editor -->
<kendo-grid-column field="UnitPrice" editor="numeric" title="Price">
</kendo-grid-column>

<!-- setting boolean editor -->
<kendo-grid-column field="Discontinued" editor="boolean" title="Discontinued">
</kendo-grid-column>

Configure the Command Column

To configure the command column, 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 and by providing a FormGroup configuration that describes the view model for that editor.

protected editHandler({sender, rowIndex, dataItem}) {
    // define all editable fields validators and default values
    const group = new FormGroup({
        'ProductID': new FormControl(dataItem.ProductID),
        'ProductName': new FormControl(dataItem.ProductName, Validators.required),
        'UnitPrice': new FormControl(dataItem.UnitPrice),
        'UnitsInStock': new FormControl(dataItem.UnitsInStock, Validators.compose([Validators.required, Validators.pattern('^[0-9]{1,2}')])),
        'Discontinued': new FormControl(dataItem.Discontinued)
    });

    // put the row in edit mode, with the `FormGroup` build above
    sender.editRow(rowIndex, group);
}

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 and by providing a FormGroup configuration that describes the view model for that editor (similar to the editRow method).

protected addHandler({sender}) {
    // define all editable fields validators and default values
    const group = new FormGroup({
        'ProductID': new FormControl(),
        'ProductName': new FormControl("", Validators.required),
        'UnitPrice': new FormControl(0),
        'UnitsInStock': new FormControl("", Validators.compose([Validators.required, Validators.pattern('^[0-9]{1,2}')])),
        'Discontinued': new FormControl(false)
    });

    // show the new row editor, with the `FormGroup` build above
    sender.addRow(group);
}

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}) {
    // closed the editor for the given row
    sender.closeRow(rowIndex)
}

Handling the save Event

The save event fires when the kendoGridSaveCommand is clicked.

Inside the event handler, you can perform the following actions:

  • Update the value of the form in the data source.
  • Call the closeRow method to switch the current row back to the view mode.
protected saveHandler({sender, rowIndex, formGroup, isNew}) {
    // collect current state of the from
    // `formGroup` arguments is the same that was provided when calling `editRow`
    const product: Product = formGroup.value;

    // update the data source
    this.editService.save(product, 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