Template editing in Angular Grid component

18 Jul 202424 minutes to read

The Syncfusion Angular Grid component supports template editing, providing a powerful and flexible way to customize the appearance and behavior of cells during editing. This feature allows you to use Angular templates to define the structure and content of the cells within the grid.

Inline or dialog template editing

The Syncfusion Grid provides support for inline and dialog template editing, allowing you to customize the editing using either Reactive or Template-driven Forms. These forms can be utilized to add and update grid records.

To enable this feature, you need to set the editSettings.mode property of the Grid to either Normal or Dialog and define the grid editors using the editSettingsTemplate template variable of ngTemplate.

Using Reactive Forms

Reactive forms offer a model-driven approach using TypeScript for form handling in Angular. This approach involves dynamically creating and managing form controls programmatically. Reactive forms use an explicit and immutable approach to managing the state of a form at a given point in time. This particularly useful in scenarios where you need a more dynamic and programmatic approach to form handling, providing fine-grained control and flexibility over form validation and error handling.

In some cases, you want to add new field editors in the dialog which are not present in the column model. In that situation the dialog template will help us to customize the default edit dialog.

In the following example, a FormGroup is created with FormControls for each column during the actionBegin event. During saving, the form group is validated, and the grid is then updated with the edited data from the FormGroup object.

import { CommonModule } from '@angular/common'; 
import { GridModule, EditService, ToolbarService } from '@syncfusion/ej2-angular-grids'
import { ReactiveFormsModule, FormsModule } from '@angular/forms'
import { NumericTextBoxAllModule } from '@syncfusion/ej2-angular-inputs'
import { DatePickerAllModule } from '@syncfusion/ej2-angular-calendars';
import { DropDownListAllModule } from '@syncfusion/ej2-angular-dropdowns';
import {TextAreaModule} from '@syncfusion/ej2-angular-inputs';
import { Component, OnInit } from '@angular/core';
import { orderDetails } from './datasource';
import { DialogEditEventArgs, SaveEventArgs } from '@syncfusion/ej2-angular-grids';
import { DataUtil } from '@syncfusion/ej2-data';
import { FormGroup, AbstractControl, FormControl, Validators } from '@angular/forms';

@Component({
imports: [
        
        GridModule,
        NumericTextBoxAllModule, DatePickerAllModule,CommonModule,
        DropDownListAllModule, ReactiveFormsModule, FormsModule,TextAreaModule
    ],

providers: [EditService, ToolbarService],
standalone: true,
    selector: 'app-root',
    templateUrl: `reactive-form.html`
})
export class AppComponent implements OnInit {
    public data?: Object[];
    public editSettings?: Object;
    public toolbar?: string[];
    public orderForm?: FormGroup|any;
    public pageSettings?: Object;
    public shipCityDistinctData?: Object[];
    public shipCountryDistinctData?: Object[];
    public submitClicked: boolean = false;

    public ngOnInit(): void {
        this.data = orderDetails;
        this.editSettings = { allowEditing: true, allowAdding: true, allowDeleting: true, mode: 'Dialog' };
        this.toolbar = ['Add', 'Edit', 'Delete'];
        this.pageSettings = { pageCount: 5};
        this.shipCityDistinctData = DataUtil.distinct(orderDetails, 'ShipCity', true);
        this.shipCountryDistinctData = DataUtil.distinct(orderDetails, 'ShipCountry', true );
    }

    createFormGroup(data: IOrderModel): FormGroup {
        return new FormGroup({
            OrderID: new FormControl(data.OrderID, Validators.required),
            OrderDate: new FormControl(data.OrderDate, (this.dateValidator() as Object)),
            CustomerName: new FormControl(data.CustomerName, Validators.required),
            Freight: new FormControl(data.Freight,Validators.required),
            ShipAddress: new FormControl(data.ShipAddress,Validators.required),
            ShipCity: new FormControl(data.ShipCity,Validators.required),
            ShipCountry: new FormControl(data.ShipCountry,Validators.required)
        });
    }

    dateValidator() {
        return (control: FormControl): null | Object  => {
            return control.value && control.value.getFullYear &&
            (1900 <= control.value.getFullYear() && control.value.getFullYear() <=  2099) ? null : { OrderDate: { value : control.value}};
        }
    }

    actionBegin(args: SaveEventArgs): void {
        if (args.requestType === 'beginEdit' || args.requestType === 'add') {
            this.submitClicked = false;
            this.orderForm = this.createFormGroup((args.rowData as Object));
        }
        if (args.requestType === 'save') {
            this.submitClicked = true;

            if (this.orderForm.valid) {
                args.data = this.orderForm.value;
            } else {
                args.cancel = true;
            }
        }
    }

    actionComplete(args: DialogEditEventArgs): void {
        if ((args.requestType === 'beginEdit' || args.requestType === 'add')) {
            // Set initial focus
            if (args.requestType === 'beginEdit') {
                ((args.form as HTMLFormElement).elements.namedItem('CustomerName') as HTMLInputElement).focus();
            } else if (args.requestType === 'add') {
                ((args.form as HTMLFormElement).elements.namedItem('OrderID') as HTMLInputElement).focus();
            }
        }
    }

    get OrderID(): AbstractControl  { return this.orderForm.get('OrderID'); }
    get CustomerName(): AbstractControl { return this.orderForm.get('CustomerName'); }
    get OrderDate(): AbstractControl { return this.orderForm.get('OrderDate'); }
    get Freight(): AbstractControl { return this.orderForm.get('Freight'); }
    get ShipCountry(): AbstractControl { return this.orderForm.get('ShipCountry'); }
    get ShipCity(): AbstractControl { return this.orderForm.get('ShipCity'); }

}

export interface IOrderModel {
    OrderID?: number;
    CustomerName?: string;
    ShipCity?: string;
    OrderDate?: Date;
    Freight?: number;
    ShipCountry?: string;
    ShipAddress?: string;
}
<div class="control-section">
    <ejs-grid [dataSource]='data' allowPaging='true' [pageSettings]='pageSettings' [editSettings]='editSettings'
        [toolbar]='toolbar' (actionBegin)='actionBegin($event)' (actionComplete)='actionComplete($event)'>
        <e-columns>
            <e-column field='OrderID' headerText='Order ID' width='120' textAlign='Right'
                [isPrimaryKey]='true'></e-column>
            <e-column field='CustomerName' headerText='Customer Name' width='120'></e-column>
            <e-column field='Freight' headerText='Freight' width='120' format='C2' textAlign='Right'></e-column>
            <e-column field='OrderDate' headerText='Order Date' width='130' format='yMd' textAlign='Right'></e-column>
            <e-column field='ShipCountry' headerText='Ship Country' width='150'></e-column>
        </e-columns>
        <ng-template #editSettingsTemplate let-data>
            <div [formGroup]="orderForm">
                <div class="form-row">
                    <div class="form-group col-md-6">
                        <div class="e-float-input e-control-wrapper"
                            [ngClass]="{'e-error': OrderID.invalid && (OrderID.dirty || OrderID.touched)}">
                            <input formControlName="OrderID" data-msg-containerid='OrderIDError' id="OrderID"
                                name="OrderID" type="text" [attr.disabled]="!data.isAdd ? '' : null">
                            <span class="e-float-line"></span>
                            <label class="e-float-text e-label-top" for="OrderID"> Order ID</label>
                        </div>
                        <div id="OrderIDError"
                            [style.visibility]='((OrderID.invalid && (OrderID.dirty || OrderID.touched)) || (OrderID.invalid && submitClicked))? "visible": "hidden"'>
                            <label class="e-error" for="OrderID" id="OrderID-info" style="display: block;">*Order ID is
                                required</label>
                        </div>
                    </div>
                    <div class="form-group col-md-6">
                        <div class="e-float-input e-control-wrapper"
                            [ngClass]="{'e-error': CustomerName.invalid && (CustomerName.dirty || CustomerName.touched)}">
                            <input formControlName="CustomerName" data-msg-containerid='CustomerNameError'
                                id="CustomerName" name="CustomerName" type="text">
                            <span class="e-float-line"></span>
                            <label class="e-float-text e-label-top" for="CustomerName">Customer Name</label>
                        </div>
                        <div id="CustomerNameError"
                            [style.visibility]='((CustomerName.invalid && (CustomerName.dirty || CustomerName.touched)) || (CustomerName.invalid && submitClicked))? "visible": "hidden"'>
                            <label class="e-error" for="CustomerName" id="CustomerName-info"
                                style="display: block;">*Customer Name is required</label>
                        </div>
                    </div>
                </div>
                <div class="form-row">
                    <div class="form-group col-md-6">
                        <ejs-numerictextbox formControlName="Freight" id="Freight" placeholder="Freight"
                            floatLabelType='Always'></ejs-numerictextbox>
                        <div id="FreightError"
                            [style.visibility]='((Freight.invalid && (Freight.dirty || Freight.touched)) || (Freight.invalid && submitClicked)) ? "visible": "hidden"'>
                            <label class="e-error" for="Freight" id="Freight-info" style="display: block;">*Order Date
                                is required</label>
                        </div>
                    </div>
                    <div class="form-group col-md-6">
                        <ejs-datepicker id="OrderDate" formControlName="OrderDate" placeholder="Order Date"
                            floatLabelType='Always'></ejs-datepicker>
                        <div id="OrderDateError"
                            [style.visibility]='((OrderDate.invalid && (OrderDate.dirty || OrderDate.touched)) || (OrderDate.invalid && submitClicked)) ? "visible": "hidden"'>
                            <label class="e-error" for="OrderDate" id="OrderDate-info" style="display: block;">*Order
                                Date is required</label>
                        </div>
                    </div>
                </div>
                <div class="form-row">
                    <div class="form-group col-md-6">
                        <ejs-dropdownlist id="ShipCountry" formControlName="ShipCountry"
                            [dataSource]='shipCountryDistinctData'
                            [fields]="{text: 'ShipCountry', value: 'ShipCountry' }" placeholder="Ship Country"
                            popupHeight='300px' floatLabelType='Always'></ejs-dropdownlist>
                        <div id="ShipCountryError"
                            [style.visibility]='((ShipCountry.invalid && (ShipCountry.dirty || ShipCountry.touched)) || (ShipCountry.invalid && submitClicked)) ? "visible": "hidden"'>
                            <label class="e-error" for="ShipCountry" id="ShipCountry-info" style="display: block;">*Ship
                                Country is required</label>
                        </div>
                    </div>
                    <div class="form-group col-md-6">
                        <ejs-dropdownlist id="ShipCity" formControlName="ShipCity" [dataSource]='shipCityDistinctData'
                            [fields]="{text: 'ShipCity', value: 'ShipCity' }" placeholder="Ship City"
                            popupHeight='300px' floatLabelType='Always'></ejs-dropdownlist>
                        <div id="ShipCityError"
                            [style.visibility]='((ShipCity.invalid && (ShipCity.dirty || ShipCity.touched)) || (ShipCity.invalid && submitClicked)) ? "visible": "hidden"'>
                            <label class="e-error" for="ShipCity" id="ShipCity-info" style="display: block;">*Ship City
                                is required</label>
                        </div>
                    </div>
                </div>
                <div class="form-row">
                    <div class="form-group col-md-12">
                        <div class="e-float-input e-control-wrapper">
                            <textarea formControlName="ShipAddress" id="ShipAddress" name="ShipAddress"
                                type="text"></textarea>
                            <span class="e-float-line"></span>
                            <label class="e-float-text e-label-top" for="ShipAddress">Ship Address</label>
                        </div>
                    </div>
                </div>
            </div>
        </ng-template>
    </ejs-grid>
</div>
import { bootstrapApplication } from '@angular/platform-browser';
import { AppComponent } from './app.component';
import 'zone.js';
bootstrapApplication(AppComponent).catch((err) => console.error(err));

Using Template-driven Forms

Template-driven Forms provide a template-driven approach using HTML for form handling in Angular. This approach utilizes two-way data binding, such as the ngModel directive, to update the data model in the component as changes occur in the template and vice versa. This is useful for simplifying syntax and reducing the amount of code needed for form setup, making it suitable for simpler forms with straightforward validation.

In the following example, a FormGroup is created using the ngForm directive. During the save operation, the form group is validated, and the grid is updated with the edited model data.

import { NgModule } from '@angular/core'
import { BrowserModule } from '@angular/platform-browser'
import { CommonModule } from '@angular/common'; 
import { GridModule, EditService, ToolbarService } from '@syncfusion/ej2-angular-grids'
import { ReactiveFormsModule, FormsModule } from '@angular/forms'
import { NumericTextBoxAllModule } from '@syncfusion/ej2-angular-inputs'
import { DatePickerAllModule } from '@syncfusion/ej2-angular-calendars'
import { DropDownListAllModule } from '@syncfusion/ej2-angular-dropdowns'
import { Component, OnInit, ViewChild } from '@angular/core';
import { data } from './datasource';
import { DialogEditEventArgs, SaveEventArgs} from '@syncfusion/ej2-angular-grids';
import { DataUtil } from '@syncfusion/ej2-data';
import { FormGroup } from '@angular/forms';

@Component({
imports: [
        
        GridModule,
        NumericTextBoxAllModule, DatePickerAllModule, DropDownListAllModule, BrowserModule,ReactiveFormsModule, FormsModule,CommonModule
    ],

providers: [EditService, ToolbarService],
standalone: true,
    selector: 'app-root',
    templateUrl: `template-driven.html`
})
export class AppComponent implements OnInit {

    public data?: Object[];
    public editSettings?: Object;
    public toolbar?: string[];
    public pageSettings?: Object;
    public shipCityDistinctData?: Object[];
    public shipCountryDistinctData?: Object[];
    public orderData!: IOrderModel;
    @ViewChild('orderForm')  public orderForm?: FormGroup|any;

    public ngOnInit(): void {
        this.data = data;
        this.editSettings = { allowEditing: true, allowAdding: true, allowDeleting: true, mode: 'Normal' };
        this.toolbar = ['Add', 'Edit', 'Delete', 'Update', 'Cancel'];
        this.pageSettings = { pageCount: 5};
        this.shipCityDistinctData = DataUtil.distinct(data, 'ShipCity', true);
        this.shipCountryDistinctData = DataUtil.distinct(data, 'ShipCountry', true );
    }

    actionBegin(args: SaveEventArgs): void {
        if (args.requestType === 'beginEdit' || args.requestType === 'add') {
            this.orderData = Object.assign({}, args.rowData);
        }
        if (args.requestType === 'save') {
            if (this.orderForm.valid) {
                args.data = this.orderData;
            } else {
                args.cancel = true;
            }
        }
    } 

    actionComplete(args: DialogEditEventArgs): void {
        if (args.requestType === 'beginEdit' || args.requestType === 'add') {
            // Set initial focus
            if (args.requestType === 'beginEdit') {
                ((args.form as HTMLFormElement).elements.namedItem('CustomerName') as HTMLInputElement).focus();
            } else if (args.requestType === 'add') {
                ((args.form as HTMLFormElement).elements.namedItem('OrderID') as HTMLInputElement).focus();
            }

        }
    }
    public focusIn(event: FocusEvent): void {
        ((event.target as HTMLElement).parentElement as HTMLElement).classList.add('e-input-focus');
    }

    public focusOut(event: FocusEvent): void {
        ((event.target as HTMLElement).parentElement as HTMLElement).classList.remove('e-input-focus');
        
    }

}
export interface IOrderModel {
    OrderID?: number;
    CustomerName?: string;
    ShipCity?: string;
    OrderDate?: Date;
    Freight?: number;
    ShipCountry?: string;
    ShipAddress?: string;
}
<div class="control-section">
    <ejs-grid [dataSource]='data' allowPaging='true' [pageSettings]='pageSettings' [editSettings]='editSettings'
        [toolbar]='toolbar' (actionBegin)='actionBegin($event)' (actionComplete)='actionComplete($event)'>
        <e-columns>
            <e-column field='OrderID' headerText='Order ID' width='120' textAlign='Right'
                [isPrimaryKey]='true'></e-column>
            <e-column field='CustomerName' headerText='Customer Name' width='120'></e-column>
            <e-column field='Freight' headerText='Freight' width='120' format='C2' textAlign='Right'></e-column>
            <e-column field='OrderDate' headerText='Order Date' width='130' format='yMd' textAlign='Right'></e-column>
            <e-column field='ShipCountry' headerText='Ship Country' width='150'></e-column>
        </e-columns>
        <ng-template #editSettingsTemplate let-data>
            <div ngForm #orderForm="ngForm">
                <table class="e-table e-inline-edit" cellspacing="0.25">
                    <colgroup>
                        <col style="width: 120px;">
                        <col style="width: 120px;">
                        <col style="width: 120px;">
                        <col style="width: 130px;">
                        <col style="width: 150px;">
                    </colgroup>
                    <tbody>
                        <tr>
                            <td style="text-align: right" class='e-rowcell'>
                                <div class="e-input-group" [ngClass]="{'e-disabled': !data.isAdd}">
                                    <input class="e-input e-field" [(ngModel)]="orderData.OrderID" required
                                        [attr.disabled]="!data.isAdd ? '' : null" name='OrderID' type="text"
                                        (focus)="focusIn($event)" (blur)="focusOut($event)" style="text-align: right"
                                        #OrderID="ngModel" />
                                </div>
                            </td>
                            <td class='e-rowcell'>
                                <div class="e-input-group">
                                    <input class="e-input e-field" name='CustomerName'
                                        [(ngModel)]="orderData.CustomerName" required type="text"
                                        (focus)="focusIn($event)" (blur)="focusOut($event)" #CustomerName="ngModel" />
                                </div>
                            </td>
                            <td style="text-align: right" class='e-rowcell'>
                                <ejs-numerictextbox name="Freight" id="Freight" [(ngModel)]="orderData.Freight"
                                    floatLabelType='Never'></ejs-numerictextbox>
                            </td>
                            <td style="text-align: right" class='e-rowcell'>
                                <ejs-datepicker id="OrderDate" name="OrderDate" [(ngModel)]="orderData.OrderDate"
                                    floatLabelType='Never'></ejs-datepicker>
                            </td>
                            <td class='e-rowcell'>
                                <ejs-dropdownlist id="ShipCountry" name="ShipCountry"
                                    [(ngModel)]="orderData.ShipCountry" [dataSource]='shipCountryDistinctData'
                                    [fields]="{text: 'ShipCountry', value: 'ShipCountry' }" popupHeight='300px'
                                    floatLabelType='Never'></ejs-dropdownlist>
                            </td>
                        </tr>
                    </tbody>
                </table>
            </div>
        </ng-template>
    </ejs-grid>

</div>
import { bootstrapApplication } from '@angular/platform-browser';
import { AppComponent } from './app.component';
import 'zone.js';
bootstrapApplication(AppComponent).catch((err) => console.error(err));

The form editors should have name attribute.

Using template context

You can enhance the customization of your grid’s edit forms by utilizing template contexts, such as accessing row details inside ngTemplate, rendering editors as components, getting values from editors, setting focus to editors, and disabling default form validation, and adding custom validation. These features are applicable in both inline and dialog editing modes.

The following template context topics are demonstrated through a practical example in the Render tab component inside the dialog template topic.

Access row details inside ngTemplate using template context

When utilizing edit templates in the Grid , you can access crucial row information within an ngTemplate when utilizing edit templates. This enables dynamic binding of attributes, values, or elements based on the specific row being edited. This is particularly useful for conditionally rendering or modifying elements in the edit template based on the row’s state.

The following properties will be available at the time of template execution:

Property Name Usage
isAdd A Boolean property that defines whether the current row is a new record or not.

The following code example demonstrates the usage of the isAdd property in an edit template to disable the OrderID textbox when it’s not a new record:

    <input id="OrderID" [(ngModel)]="data.OrderID" name="OrderID" type="text" [attr.disabled]="!data.isAdd ? '' : null">

Render editors as components

The Syncfusion Grid provides a powerful feature that allows you to dynamically render Syncfusion EJ2 controls as form editors during the editing process. This functionality is particularly useful when you want to provide feature-rich controls for data entry within the edit form.

To achieve this by utilizing the actionComplete event of the Grid and specifying requestType as beginEdit or add.

The following code example illustrates rendering the DropDownList component in the actionComplete event.

 actionComplete(args: DialogEditEventArgs) {
        if ((args.requestType === 'beginEdit' || args.requestType === 'add')) {
            let countryData: {}[] = DataUtil.distinct(data, 'ShipCountry', true) ;
            new DropDownList({value: args.rowData.ShipCountry, popupHeight: '200px', floatLabelType: 'Always',
                dataSource: countryData, fields: {text: 'ShipCountry', value: 'ShipCountry'}, placeholder: 'Ship Country'}, args.form.elements.namedItem('ShipCountry') as HTMLInputElement);
        }
    }

Get value from editor

The get value from editor feature in the Syncfusion Grid allows you to read, format, and update the current editor value before it is saved. This feature is particularly valuable when you need to perform specific actions on the data, such as formatting or validation, before it is committed to the underlying data source.

To achieve this feature, you can utilize the actionBegin event with the requestType set to save.

In the following code example, the freight value has been formatted and updated.

    actionBegin(args: SaveEventArgs) {
        if (args.requestType === 'save') {
            // cast string to integer value.
            (args.data as ColumnDataType).Freight = parseFloat(((args as any).form.querySelector('#Freight').ej2_instances[0] as HTMLInputElement).value);
        }
    }

Set focus to particular column editor

The Syncfusion Grid allows you to control the focus behavior of input elements in edit forms. By default, the first input element in the dialog receives focus when the dialog is opened. However, in scenarios where the first input element is disabled or hidden, you can specify which valid input element should receive focus. This can be achieved using the actionComplete event of the Grid, where the requestType is set to beginEdit.

In the following code example, the CustomerID column focused.

    actionComplete(args: DialogEditEventArgs) {
        // Set initail Focus
        if (args.requestType === 'beginEdit') {
            ((args.form as HTMLFormElement).elements.namedItem('CustomerID') as HTMLInputElement).focus();
        }
    }

Disable default form validation

The Syncfusion Grid provides built-in support for angular form validation to ensure data integrity and accuracy during editing. However, there might be scenarios where you want to disable the default form validation rules. This can be achieved using the removeRules method within the actionComplete event of the Grid.

To disable default form validation rules in the Grid, follow these steps:

    actionComplete(args: DialogEditEventArgs) {
        if ((args.requestType === 'beginEdit' || args.requestType === 'add')) {
            // Disable the Validation Rules
        (args.form as HTMLFormElement)['ej2_instances'][0].removeRules();
        }
    }

You can use this method to disable validation rules: args.form.ej2_instances[0].rules = {}.

Adding validation rules for custom editors

The Syncfusion Grid provides the ability to add validation rules for fields that are not present in the column model. This feature is particularly useful to prevent erroneous or inconsistent data from being submitted, ultimately enhancing the reliability of your application’s data.

To accomplish this, you can utilize the actionComplete event along with the addRules method.

Here’s how you can use the addRules method to add validation rules for custom editors in the actionComplete event:

    actionComplete(args: DialogEditEventArgs) {
        if ((args.requestType === 'beginEdit' || args.requestType === 'add')) {
            // Add Validation Rules
            (args.form as HTMLFormElement)['ej2_instances'][0].addRules('Freight', { max: 500 });
        }
    }

Render tab component inside the dialog template

You can enhance the editing experience in the Grid by rendering a Tab component inside the dialog template. This feature is especially useful when you want to present multiple editing sections or categories in a tabbed layout, ensuring a more intuitive and easily navigable interface for data editing.

To enable this functionality, you need to set the editSettings.mode property of the Grid to Dialog. This configures the Grid to use the dialog editing mode. Additionally, you can use the editSettingsTemplate property to define a template variable that contains the Tab component and its corresponding content.

The following example renders a tab component inside the edit dialog. The tab component has two tabs, and once you fill in the first tab and navigate to the second one, the validation for the first tab is performed before navigating to the second.

import { NgModule } from '@angular/core'
import { BrowserModule } from '@angular/platform-browser';
import { CommonModule } from '@angular/common'; 
import { GridModule, EditService, ToolbarService } from '@syncfusion/ej2-angular-grids'
import { ReactiveFormsModule, FormsModule } from '@angular/forms'
import { DropDownListAllModule } from '@syncfusion/ej2-angular-dropdowns'
import { TabAllModule,SelectingEventArgs,TabComponent } from '@syncfusion/ej2-angular-navigations'
import { DropDownList } from '@syncfusion/ej2-dropdowns';
import { Component, OnInit, ViewChild } from '@angular/core';
import { FormGroup } from '@angular/forms';
import { DataUtil } from '@syncfusion/ej2-data';
import { data, ColumnDataType } from './datasource';
import { EditSettingsModel, ToolbarItems, GridComponent, DialogEditEventArgs } from '@syncfusion/ej2-angular-grids';
import { CheckBoxAllModule,ButtonModule} from '@syncfusion/ej2-angular-buttons';
import { NumericTextBoxAllModule } from '@syncfusion/ej2-angular-inputs';

@Component({
imports: [
        
        ButtonModule,
        CheckBoxAllModule,
        TabAllModule,
        GridModule,
        DropDownListAllModule, ReactiveFormsModule, FormsModule,CommonModule,NumericTextBoxAllModule
    ],

providers: [EditService, ToolbarService],
standalone: true,
    selector: 'app-root',
    templateUrl: `tablikeedit.html`
})
export class AppComponent implements OnInit {

    public data?: Object[];
    public editSettings?: EditSettingsModel;
    public toolbar?: ToolbarItems[];
    public shipCountryDistinctData?: { [key: string]: Object }[];
    @ViewChild('grid')
    grid?: GridComponent;
    @ViewChild('orderForm')
    orderForm: FormGroup;
    @ViewChild('tab')
    tabObj: TabComponent;
   

    ngOnInit(): void {
        // Initialize component properties
        this.data = data;
        this.editSettings = { allowEditing: true, allowAdding: true, allowDeleting: true, mode: 'Dialog' };
        this.toolbar = ['Add', 'Edit', 'Delete'];
        this.shipCountryDistinctData = DataUtil.distinct(data, 'ShipCountry', true) as { [key: string]: Object }[];
    }
    // Handle completion of editing dialog
    actionComplete(args: DialogEditEventArgs) {
        if ((args.requestType === 'beginEdit' || args.requestType === 'add')) {
            // Disable default validation.
            (args.form as HTMLFormElement)['ej2_instances'][0].removeRules();           
            setTimeout(() => {
                new DropDownList({
                    value: (args.rowData as ColumnDataType).ShipCountry!==undefined ?(args.rowData as ColumnDataType).ShipCountry : "Germany" ,
                    popupHeight: '200px',
                    floatLabelType: 'Always',
                    dataSource: this.shipCountryDistinctData,
                    placeholder:'ShipCountry',
                    fields: { text: 'ShipCountry', value: 'ShipCountry' }
                
                    }, (args.form as HTMLFormElement).elements.namedItem('ShipCountry') as HTMLInputElement);
                
            }, 1);
                
            // hide dialog button
            (args.dialog as HTMLFormElement)['element'].querySelector('.e-footer-content').classList.add('e-hide');
            // Add validation rules
            (args.form as HTMLFormElement)['ej2_instances'][0].addRules('Freight', { max: 500 });
            // Set initial focus
            if (args.requestType === 'beginEdit') {
                setTimeout(() => {
                    ((args.form as HTMLFormElement).elements.namedItem('CustomerID') as HTMLInputElement).focus();
                }, 1);
            }
            if(args.requestType === 'add') {
                setTimeout(() => {
                    ((args.form as HTMLFormElement).elements.namedItem('OrderID') as HTMLInputElement).focus();
                }, 1);
            }
        }

    }
    // Move to the next tab
    nextBtn() {
        if (this.orderForm.valid) 
            this.moveNext();  
    }
    // Move to the previous tab
    previousBtn() {
        this.movePrevious();
    }

    // Handle tab selection
    selecting(e: SelectingEventArgs) {
        if (e.isSwiped) e.cancel = true;
        e.cancel = !this.orderForm.valid;

    }

    // Move to the next tab if the form is valid
    moveNext() {
        if (this.orderForm?.valid ) 
            this.tabObj.select(1);
        
        else
            this.tabObj.select(1);    
    }

    // Move to the previous tab if the form is valid
    movePrevious() {
        if (this.orderForm.valid) 
            this.tabObj.select(0);   
    }

    // Handle submit button click
    submitBtn() {
        if (this.orderForm.valid) 
            (this.grid as GridComponent).endEdit();     
    }
}
<ejs-grid #grid [dataSource]='data' allowPaging='true' [editSettings]='editSettings' [toolbar]='toolbar'
  (actionComplete)='actionComplete($event)'>
  <e-columns>
    <e-column field='OrderID' headerText='Order ID' width='120' textAlign='Right' [isPrimaryKey]='true'></e-column>
    <e-column field='CustomerID' headerText='Customer Name' width='120'> </e-column>
    <e-column field='ShipCountry' headerText='Ship Country' width='150'></e-column>
    <e-column field='Freight' headerText='Freight' width='150'></e-column>
    <e-column field='ShipAddress' headerText='Ship Address' width='150'></e-column>
    <e-column field='Verified' headerText='Verified' width='100' type='boolean' [displayAsCheckBox]='true'></e-column>
  </e-columns>
  <ng-template #editSettingsTemplate let-data>
    <div ngForm #orderForm="ngForm">
      <ejs-tab #tab id="tab_wizard" showCloseButton=false (selecting)='selecting($event)'>
        <e-tabitems>
          <e-tabitem>
            <ng-template #headerText>
              <div> Details </div>
            </ng-template>
            <ng-template #content>
              <div id="tab1">
                <div class="form-row">
                  <div class="form-group col-md-6">
                    <div class="e-float-input e-control-wrapper"
                    [ngClass]="{'e-error': OrderID.invalid && (OrderID.touched || OrderID.dirty)}">
                      <input [(ngModel)]="data.OrderID" required id="OrderID" name="OrderID" type="text"
                        [disabled]="!data.isAdd" #OrderID="ngModel">
                      <span class="e-float-line"></span>
                      <label class="e-float-text e-label-top" for="OrderID"> Order ID</label>
                    </div>
                    <div id="OrderIDError" *ngIf='OrderID.invalid && (OrderID.dirty || OrderID.touched)'>
                      <label class="e-error" for="OrderID" id="OrderID-info" style="display: block;">*Order ID is
                        required</label>
                    </div>
                  </div>
                </div>
                <div class="form-row">
                  <div class="form-group col-md-6">
                    <div class="e-float-input e-control-wrapper" [ngClass]="{'e-error': CustomerID.invalid && (CustomerID.dirty || CustomerID.touched)}">
                      <input [(ngModel)]="data.CustomerID" required id="CustomerID" name="CustomerID" type="text"
                        #CustomerID="ngModel">
                      <span class="e-float-line"></span>
                      <label class="e-float-text e-label-top" for="CustomerID">Customer Name</label>
                    </div>
                    <div id="CustomerIDError" *ngIf="CustomerID.invalid && (CustomerID.dirty || CustomerID.touched)">
                      <label class="e-error" for="CustomerID" id="CustomerID-info" style="display: block;">*Customer
                        Name is required</label>
                    </div>
                  </div>
                </div>

                <div class="form-row">
                  <div class="form-group col-md-6">
                    <input required id="ShipCountry" name="ShipCountry" type="text">
                  </div>
                </div>
                <button ejs-button type="button" cssClass="e-info e-btn" style="float: right"
                  (click)="nextBtn()">next</button>
              </div>
            </ng-template></e-tabitem>
          <e-tabitem>
            <ng-template #headerText>
              <div> Verify </div>
            </ng-template>
            <ng-template #content>
              <div id="tab2">

                <div class="form-row">
                  <div class="form-group col-md-6">
                    <div class="e-float-input e-control-wrapper"
                      [ngClass]="{'e-error': Freight.invalid && (Freight.dirty || Freight.touched)}">
                      <label class="e-float-text e-label-top" for="Freight">Freight</label>
                      <ejs-numerictextbox style="margin-top:10px" min='0' [(ngModel)]="data.Freight" required
                        id="Freight" name="Freight" #Freight="ngModel" floatLabelType='Always'></ejs-numerictextbox>
                    </div>
                    <div id="FreightError" *ngIf="Freight.invalid && (Freight.dirty || Freight.touched)">
                      <label class="e-error" for="Freight" id="Freight-info" style="display: block;">*Freight is
                        required</label>
                    </div>
                  </div>
                </div>
                <div class="form-row">
                  <div class="form-group col-md-6">
                    <ejs-checkbox #Verified name="Verified" id="Verified" label="Verified"
                      [checked]="data.Verified"></ejs-checkbox>
                  </div>
                </div>

                <button ejs-button type="button" cssClass="e-info e-btn" style="float: right"
                  (click)="submitBtn()">Submit</button>
              </div>
            </ng-template>
          </e-tabitem>
        </e-tabitems>
      </ejs-tab>
    </div>
  </ng-template>
</ejs-grid>
import { bootstrapApplication } from '@angular/platform-browser';
import { AppComponent } from './app.component';
import 'zone.js';
bootstrapApplication(AppComponent).catch((err) => console.error(err));

See also