AngularT Dark_870x220

In this tutorial we’ll go through an example of how you can batch edit all rows of a Kendo UI Grid at the same time, effectively binding the whole grid to an Angular Reactive Forms FormGroup and FormArray to enable the validation and saving of all form data together instead of line by line, so it behaves a little more like a “normal” Reactive Form. This example is built with Angular 8.2.6.

The below example contains a Kendo UI Grid with a list of products that displays in “view” mode by default. When the edit button is clicked, the grid is switched to “edit” mode, which makes all product fields editable and allows products to be added or removed from the grid. After editing, you can either save or cancel the changes.

Styling of the example is done with Bootstrap 4.3, the Kendo UI for Angular Default Theme, and a couple of custom CSS styles in the main index.html file. For more info on styling Kendo UI components for Angular, see this styling overview.

Here it is in action:
(Edit on StackBlitz at https://stackblitz.com/edit/batch-editing-with-kendo-ui-grid-for-angular)

Angular App Component Template with Kendo UI Grid

The app component template contains the HTML and Angular template syntax for displaying the example Kendo UI Grid; it contains a single <kendo-grid> component wrapped in a bootstrap card for layout.

The grid defines the template reference variable #grid so it can be accessed from the app component below with the ViewChild('grid') decorator, and the data property of the grid is bound to an array of products defined in the app component using the Angular property binding [data]="products".

A different toolbar is displayed when the grid is in “view” or “edit” mode with the help of the isEditMode property, the “view” mode toolbar only contains an Edit button, and the “edit” mode toolbar contains buttons for Add, Save and Cancel. Each toolbar is defined using an <ng-template> tag with the kendoGridToolbarTemplate directive, and each button is bound to an event handler method in the app component using an Angular event binding attribute e.g. (click)="onAdd()".

There are four columns defined with the <kendo-grid-column> tag — one for each product field and one with a Remove button that is only displayed when the grid is in “edit” mode.

<div class="card m-3">
    <h5 class="card-header">Batch Editing with Kendo UI Grid for Angular</h5>
    <div class="card-body">
        <kendo-grid #grid [data]="products">
            <ng-template *ngIf="!isEditMode" kendoGridToolbarTemplate>
                <button (click)="onEdit()" class="k-button k-primary">Edit</button>
            </ng-template>
            <ng-template *ngIf="isEditMode" kendoGridToolbarTemplate>
                <button (click)="onAdd()" class="k-button">Add</button>
                <button (click)="onSave()" class="k-button">Save</button>
                <button (click)="onCancel()" class="k-button">Cancel</button>
            </ng-template>
            <kendo-grid-column field="Name"></kendo-grid-column>
            <kendo-grid-column field="Price" editor="numeric" format="{0:c}"></kendo-grid-column>
            <kendo-grid-column field="InStock" title="In Stock" editor="boolean"></kendo-grid-column>
            <kendo-grid-column *ngIf="isEditMode">
                <ng-template kendoGridCellTemplate let-rowIndex="rowIndex">
                    <button (click)="onRemove(rowIndex)" class="k-button">Remove</button>
                </ng-template>
            </kendo-grid-column>
        </kendo-grid>
    </div>
</div>

Angular App Component with Kendo UI Grid

The app component contains all of the properties and methods for interacting with our grid.

Component Properties

products contains the array of product objects bound to the grid in the template with the [data]="products" property binding attribute.

originalProducts is used to hold a copy of the original products array just before switching to “edit” mode, so the changes to the products array can be reset if the Cancel button is clicked.

productsForm is an Angular Reactive FormGroup that holds the FormArray and all FormControl components for the whole form, so all fields can be validated and saved together.

isEditMode is a boolean flag used to toggle the app component template between “view” and “edit” modes.

@ViewChild('grid') grid holds a reference to the Kendo UI Grid component defined in the app component template. The ViewChild decorator enables access to the grid component using the 'grid' parameter because it matches the #grid template reference variable defined on the kendo-grid tag in the template.

Component Methods

ngOnInit() initializes the the products array with a sample set of products, and sets the productsForm to a new FormGroup containing a FormArray for holding all of the product form groups and controls. The form group is created with the FormBuilder instance that is injected in the component constructor.

onEdit() handles when the Edit button is clicked and converts the grid into an editable form. It makes a copy of the products array in case the edit action is cancelled, then calls a couple of helper functions to initialize the form controls and switch all grid rows into “edit” mode, and lastly sets isEditMode to true to display the correct toolbars in the template.

onAdd() handles when the Add button is clicked to add a new product row to the bottom of the grid. It pushes a new object to the products array and a new form group to the FormArray of the productsForm, then sets the new row of the grid to “edit” mode.

onRemove(index) handles when the Remove button is clicked to remove the selected row from the grid. First it closes all rows of the grid (sets them to “view” mode), then removes the product object from the products array and the product form group from the FormArray before setting all rows back to “edit” mode. I found it necessary to close all rows before removing to avoid unexpected side effects from the grid.

onSave() handles when the Save button is clicked to validate and save the form data. If the form is invalid, an alert is displayed and the data is not saved. If the form is valid, the data is “saved” by copying the updated form data into the products array and setting the grid back to “view” mode. In a real world application, this is where you would typically put an API or service call to persist the data.

onCancel() handles when the Cancel button is clicked to discard any changes and switch the grid back to “view” mode. It closes all the grid rows to set them back to “view” mode, then reverts any changes by copying the original product data back into the products array, and sets isEditMode to false to display the correct toolbars in the template.

import { Component, OnInit, ViewChild } from '@angular/core';
import { FormBuilder, FormGroup, FormArray, Validators } from '@angular/forms';
import { GridComponent } from '@progress/kendo-angular-grid';

@Component({ selector: 'app', templateUrl: 'app.component.html' })
export class AppComponent implements OnInit {
    products = [];
    originalProducts = [];
    productsForm: FormGroup;
    isEditMode = false;
    @ViewChild('grid') grid: GridComponent;

    constructor(private formBuilder: FormBuilder) { }

    ngOnInit() {
        this.products = [
            { Name: 'Vegemite', Price: 2.50, InStock: true },
            { Name: 'Tim Tams', Price: 3.99, InStock: true },
            { Name: 'Meat Pies', Price: 6.00, InStock: false },
            { Name: 'Pavlova', Price: 4.39, InStock: true }
        ];

        // initialise products form with empty form array
        this.productsForm = this.formBuilder.group({
            formArray: new FormArray([])
        });
    }

    // convenience getters for easy access to form fields
    get f() { return this.productsForm.controls; }
    get fa() { return this.f.formArray as FormArray; }

    onEdit() {
        // store copy of original products in case cancelled
        this.originalProducts = [...this.products];

        // reset / initialise form fields
        this.resetForm();

        // set all rows to edit mode to display form fields
        this.editAllRows();
        this.isEditMode = true;
    }

    onAdd() {
        // add item to products array
        this.products.push({});

        // add new form group to form array
        const formGroup = this.createFormGroup();
        this.fa.push(formGroup);

        // set new row to edit mode in kendo grid
        this.grid.editRow(this.products.length - 1, formGroup);
    }

    onRemove(index) {
        // rows must all be closed while removing products
        this.closeAllRows();

        // remove product and product form group
        this.products.splice(index, 1);
        this.fa.removeAt(index);

        // reset all rows back to edit mode
        this.editAllRows();
    }

    onSave() {
        // mark all fields as touched to highlight any invalid fields
        this.productsForm.markAllAsTouched();

        // stop here if form is invalid
        if (this.productsForm.invalid) {
            alert('FORM INVALID :(');
            return;
        }

        // copy form data to products array on success
        this.products = this.fa.value;

        this.closeAllRows();
        this.isEditMode = false;
    }

    onCancel() {
        this.closeAllRows();

        // reset products back to original data (before edit was clicked)
        this.products = this.originalProducts;

        this.isEditMode = false;
    }

    // helper methods

    private editAllRows() {
        // set all rows to edit mode to display form fields
        this.products.forEach((x, i) => {
            this.grid.editRow(i, this.fa.controls[i]);
        });
    }

    private closeAllRows() {
        // close all rows to display readonly view of data
        this.products.forEach((x, i) => {
            this.grid.closeRow(i);
        });
    }

    private resetForm() {
        // clear form array and create a new form group for each product
        this.fa.clear();
        this.products.forEach((x, i) => {
            this.fa.push(this.createFormGroup(x));
        });
    }

    private createFormGroup(product: any = {}) {
        // create a new form group containing controls and validators for a product
        return this.formBuilder.group({
            Name: [product.Name, Validators.required],
            Price: [product.Price, Validators.required],
            InStock: [product.InStock || false, Validators.required]
        })
    }
}

Angular App Module

This is a fairly simple Angular app module with just what’s required for the example. To use the Kendo UI Grid for Angular, it imports the { GridModule } from '@progress/kendo-angular-grid' and includes it in the imports array of the @NgModule decorator; and to use Angular reactive forms, it imports the { ReactiveFormsModule } from '@angular/forms' and includes it in the imports array of the @NgModule decorator.

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { ReactiveFormsModule } from '@angular/forms';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';

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

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

@NgModule({
    imports: [
        BrowserModule,
        ReactiveFormsModule,
        BrowserAnimationsModule,
        GridModule
    ],
    declarations: [
        AppComponent
    ],
    bootstrap: [AppComponent]
})
export class AppModule { }

Conclusion

So that’s everything you need to do to effectively bind a Kendo UI Grid component to an Angular Reactive Forms FormArray in order to batch edit, validate and save all form fields together as a whole.

For further information about the Kendo UI Grid for Angular check out the official documentation at https://www.telerik.com/kendo-angular-ui/components/grid/.

Thanks for reading!


jason-watmore
About the Author

Jason Watmore

Jason is a web developer and blogger based in Sydney, Australia. He has been building web applications since 1998 and is the co-founder of Point Blank Development. He works a lot with Angular, React, Vue, Node and .NET Technologies. You can find his blog at jasonwatmore.com.

Related Posts

Comments

Comments are disabled in preview mode.