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)
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>
The app component contains all of the properties and methods for interacting with our grid.
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.
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]
})
}
}
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 { }
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 Kendo UI for Angular Data Grid documentation.
Thanks for reading!
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.