Local data in Angular Grid component

25 Aug 202524 minutes to read

The Syncfusion Angular Grid component provides a straightforward approach to bind local data, such as arrays or JSON objects, directly to the grid. This feature enables data display and manipulation within the grid without requiring external server calls, making it ideal for scenarios involving static data, cached datasets, or locally processed information.

To achieve local data binding, assign a JavaScript object array to the dataSource property. Alternatively, provide the local data source using an instance of the DataManager for enhanced data operations and query capabilities.

The following example demonstrates local data binding implementation in the Angular Grid component:

import { NgModule } from '@angular/core'
import { BrowserModule } from '@angular/platform-browser'
import { GridModule, PageService, SortService, FilterService  } from '@syncfusion/ej2-angular-grids'



import { Component, OnInit } from '@angular/core';
import { data } from './datasource';

@Component({
imports: [
        
        GridModule
    ],

providers: [PageService, SortService, FilterService],
standalone: true,
    selector: 'app-root',
    template: `<ejs-grid [dataSource]='data'>
                <e-columns>
                    <e-column field='OrderID' headerText='Order ID' textAlign='Right' width=120></e-column>
                    <e-column field='CustomerID' headerText='Customer ID' width=150></e-column>
                    <e-column field='ShipCity' headerText='Ship City' width=150></e-column>
                    <e-column field='ShipName' headerText='Ship Name' width=150></e-column>
                </e-columns>
                </ejs-grid>`
})
export class AppComponent implements OnInit {

    public data?: object[];

    ngOnInit(): void {
        this.data = data.slice(0, 7);
    }
}
import { bootstrapApplication } from '@angular/platform-browser';
import { AppComponent } from './app.component';
import 'zone.js';
bootstrapApplication(AppComponent).catch((err) => console.error(err));

Data binding with SignalR

The Syncfusion Angular Grid component supports real-time data binding using SignalR, enabling automatic grid updates as data changes on the server. This capability proves essential for applications requiring live updates and multi-client synchronization.

To implement real-time data binding with SignalR in the Syncfusion Angular Grid, follow these steps:

Step 1: Install the necessary SignalR package for the client application using npm:

npm install @microsoft/signalr --save

Step 2: Create a SignalR hub on the server to manage client-server communication. Create a ChatHub.cs file under the Hubs folder with the following code to define methods for sending data updates to clients:

using Microsoft.AspNetCore.SignalR;

namespace SignalRChat.Hubs
{
    public class ChatHub : Hub 
    {
        public async Task SendMessage(string message)
        {
            await Clients.All.SendAsync("ReceiveMessage", message);
        }
    }
}

Step 3: Configure the SignalR server to route requests to the SignalR hub. In the Program.cs file, add the following configuration:

using SignalRChat.Hubs;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddSignalR(); // Add SignalR services
// Add services to the container.
builder.Services.AddControllers()
    .AddJsonOptions(options =>
    {
        options.JsonSerializerOptions.PropertyNamingPolicy = null; // Use PascalCase
    });

builder.Services.AddCors(options =>
{
    options.AddPolicy("CORSPolicy",
        builder => builder
        .AllowAnyMethod()
        .AllowAnyHeader()
        .AllowCredentials()
        .SetIsOriginAllowed((hosts) => true));
});
builder.Services.AddControllersWithViews();
var app = builder.Build();

// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
    // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
    app.UseHsts();
}
app.UseCors("CORSPolicy");
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.MapHub<ChatHub>("/chatHub"); // Map the ChatHub
app.MapControllerRoute(
    name: "default",
    pattern: "{controller=Home}/{action=Index}/{id?}");

app.MapFallbackToFile("index.html");

app.Run();

Step 4: Create a basic Angular Grid by following the Getting Started documentation.

Step 5: In the client-side code, establish a connection to the SignalR hub and configure grid data binding in the fetch.component.ts file:

import { Component, ViewChild } from '@angular/core';
import { GridComponent, EditEventArgs, ToolbarItems } from '@syncfusion/ej2-angular-grids';
import { DataManager, UrlAdaptor } from '@syncfusion/ej2-data';
import { EditSettingsModel } from '@syncfusion/ej2-angular-grids';
import { HubConnection} from '@microsoft/signalr';
import * as signalR from '@microsoft/signalr';

@Component({ 
  selector: 'app-fetch-data',
  template: ` 
    <ejs-grid #grid [dataSource]='data' (actionComplete)="actionComplete($event)" (created)="created()" [editSettings]='editSettings' [toolbar]='toolbar' allowPaging="true" height="320">
        <e-columns>
            <e-column field='OrderID' headerText='Order ID' isPrimaryKey=true width='150'></e-column>
            <e-column field='CustomerID' headerText='Customer Name' width='150'></e-column>
            <e-column field='ShipCity' headerText='Ship City' width='150' textAlign='Right'></e-column>
        </e-columns>
    </ejs-grid>
  `
})
export class FetchDataComponent {
  @ViewChild('grid')
  public grid?: GridComponent;
  public data?: DataManager;
  public editSettings?: EditSettingsModel;
  public toolbar?: ToolbarItems[];
  public orderIDRules?: object;
  public customerIDRules?: object;
  public flag = false;
  private connection!: HubConnection;

  ngOnInit(): void {
    this.data = new DataManager({
      url: 'https://localhost:****/Home/UrlDatasource',
      updateUrl: 'https://localhost:****/Home/Update',
      insertUrl: 'https://localhost:****/Home/Insert',
      removeUrl: 'https://localhost:****/Home/Delete',
      adaptor: new UrlAdaptor()
    }); //Use remote server host number instead ****
    this.connection = new signalR.HubConnectionBuilder()
      .withUrl("https://localhost:****/ChatHub")  //Use remote server host number instead ****
      .build();
    this.editSettings = { allowEditing: true, allowAdding: true, allowDeleting: true, mode: 'Normal' };
    this.toolbar = ['Add', 'Edit', 'Delete', 'Update', 'Cancel'];
    this.orderIDRules = { required: true };
    this.customerIDRules = { required: true, minLength: 3 };
  }
  created() {
    // Add connection handler to receive messages from the hub
    this.connection.on("ReceiveMessage", function (message: string) {
      var grid = (document.getElementsByClassName('e-grid')[0] as HTMLFormElement)["ej2_instances"][0];
      grid.refresh();
    });

    // Start the connection
    this.connection.start()
  .then(() => {
    console.log("SignalR connection established successfully");
    // Send initial message once connection is established
    this.connection.invoke('SendMessage', "refreshPages")
      .catch((err: Error) => {
        console.error("Error sending data:", err.toString());
      });
  })
  .catch((err: Error) => {
    console.error("Error establishing SignalR connection:", err.toString());
  });

  }
  actionComplete(args: EditEventArgs) {
    if (args.requestType == "save" || args.requestType == "delete") {
      // Send message from connected client to all clients
      this.connection.invoke('SendMessage', "refreshPages")
        .catch((err:Error ) => {
          console.error(err.toString());
        });
    }
  }
}

Step 6: Create a controller on the server to manage data operations such as fetching, updating, inserting, and deleting records. Create a HomeController.cs file under the Controllers folder with the following code:

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using AngularwithASPCore.Models;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;

namespace AngularwithASPCore.Controllers
{
    public class HomeController : Controller
    {
        public IActionResult Index()
        {
            return View();
        }
        // Fetch data source
        public IActionResult UrlDatasource([FromBody] Data dm)
        {
            var val = OrdersDetails.GetAllRecords();
            var Data = val.ToList();

            if (dm.@where != null)
            {
                Data = OrdersDetails.GetAllRecords().Where(or => or.EmployeeID.ToString() == dm.@where[0].value).ToList();
            }

            if (dm.skip != 0)
                Data = Data.Skip(dm.skip).ToList();
            if (dm.take != 0)
                Data = Data.Take(dm.take).ToList();
            int count = Data.Count();
            return dm.requiresCounts ? Json(new { result = Data, count = count }) : Json(Data);
        }

        // Update record
        public ActionResult Update([FromBody] CRUDModel<OrdersDetails> value)
        {
            var ord = value.value;
            OrdersDetails val = OrdersDetails.GetAllRecords().Where(or => or.OrderID == ord.OrderID).FirstOrDefault();
            val.OrderID = ord.OrderID;
            val.EmployeeID = ord.EmployeeID;
            val.CustomerID = ord.CustomerID;
            val.Freight = ord.Freight;
            val.OrderDate = ord.OrderDate;
            val.ShipCity = ord.ShipCity;
            val.ShipCountry = ord.ShipCountry;

            return Json(value.value);
        }
        // Insert record
        public ActionResult Insert([FromBody] CRUDModel<OrdersDetails> value)
        {
            OrdersDetails.GetAllRecords().Insert(0, value.value);
            return Json(value.value);
        }
        // Delete record
        public ActionResult Delete([FromBody] CRUDModel<OrdersDetails> value)
        {
            // Find and remove record with matching OrderID
            OrdersDetails.GetAllRecords().Remove(OrdersDetails.GetAllRecords().Where(or => or.OrderID == int.Parse(value.key.ToString())).FirstOrDefault());
            return Json(value);
        }
    }
}

public class Data
{
    public bool requiresCounts { get; set; }
    public int skip { get; set; }
    public int take { get; set; }
    public List<Wheres> where { get; set; }
}

public class CRUDModel<T> where T : class
{
    public string action { get; set; }
    public string table { get; set; }
    public string keyColumn { get; set; }
    public object key { get; set; }
    public T value { get; set; }
    public List<T> added { get; set; }
    public List<T> changed { get; set; }
    public List<T> deleted { get; set; }
    public IDictionary<string, object> @params { get; set; }
}

public class Wheres
{
    public List<Predicates> predicates { get; set; }
    public string field { get; set; }
    public bool ignoreCase { get; set; }
    public bool isComplex { get; set; }
    public string value { get; set; }
    public string Operator { get; set; }
}

public class Predicates
{
    public string value { get; set; }
    public string field { get; set; }
    public bool isComplex { get; set; }
    public bool ignoreCase { get; set; }
    public string Operator { get; set; }
}

Step 7: Define a model class to represent the data structure. Create an OrdersDetails.cs file under the Models folder with the following code:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace AngularwithASPCore.Models
{
    public class OrdersDetails
    {
        public static List<OrdersDetails> order = new List<OrdersDetails>();

        public OrdersDetails()
        {
        }

        public OrdersDetails(
            int OrderID, string CustomerId, int EmployeeId, double Freight, bool Verified,
            DateTime OrderDate, string ShipCity, string ShipName, string ShipCountry,
            DateTime ShippedDate, string ShipAddress)
        {
            this.OrderID = OrderID;
            this.CustomerID = CustomerId;
            this.EmployeeID = EmployeeId;
            this.Freight = Freight;
            this.ShipCity = ShipCity;
            this.Verified = Verified;
            this.OrderDate = OrderDate;
            this.ShipName = ShipName;
            this.ShipCountry = ShipCountry;
            this.ShippedDate = ShippedDate;
            this.ShipAddress = ShipAddress;
        }

        public static List<OrdersDetails> GetAllRecords()
        {
            if (order.Count() == 0)
            {
                int code = 10000;
                for (int i = 1; i < 10; i++)
                {
                    order.Add(new OrdersDetails(code + 1, "ALFKI", i + 0, 2.3 * i, false, new DateTime(1991, 05, 15), "Berlin", "Simons bistro", "Denmark", new DateTime(1996, 7, 16), "Kirchgasse 6"));
                    order.Add(new OrdersDetails(code + 2, "ANATR", i + 2, 3.3 * i, true, new DateTime(1990, 04, 04), "Madrid", "Queen Cozinha", "Brazil", new DateTime(1996, 9, 11), "Avda. Azteca 123"));
                    order.Add(new OrdersDetails(code + 3, "ANTON", i + 1, 4.3 * i, true, new DateTime(1957, 11, 30), "Cholchester", "Frankenversand", "Germany", new DateTime(1996, 10, 7), "Carrera 52 con Ave. Bolívar #65-98 Llano Largo"));
                    order.Add(new OrdersDetails(code + 4, "BLONP", i + 3, 5.3 * i, false, new DateTime(1930, 10, 22), "Marseille", "Ernst Handel", "Austria", new DateTime(1996, 12, 30), "Magazinweg 7"));
                    order.Add(new OrdersDetails(code + 5, "BOLID", i + 4, 6.3 * i, true, new DateTime(1953, 02, 18), "Tsawassen", "Hanari Carnes", "Switzerland", new DateTime(1997, 12, 3), "1029 - 12th Ave. S."));
                    code += 5;
                }
            }
            return order;
        }

        public int OrderID { get; set; }
        public string CustomerID { get; set; }
        public int EmployeeID { get; set; }
        public double Freight { get; set; }
        public string ShipCity { get; set; }
        public bool Verified { get; set; }
        public DateTime OrderDate { get; set; }
        public string ShipName { get; set; }
        public string ShipCountry { get; set; }
        public DateTime ShippedDate { get; set; }
        public string ShipAddress { get; set; }
    }
}

The following screenshot demonstrates addition, editing, and deletion operations with changes reflected across all connected clients:

SignalR real-time data synchronization across multiple clients

You can find a complete sample for SignalR integration on GitHub.

Binding data from Excel file

The Syncfusion Angular Grid component enables importing data from Excel files into web applications for display and manipulation within the grid. This feature streamlines Excel data transfer to web-based environments using the Uploader component change event.

To import Excel data into the grid, follow these steps:

  1. Import Excel file using the Uploader component
  2. Parse Excel file data using the XLSX library
  3. Bind the parsed JSON data to the grid component

The following example demonstrates Excel data import into the grid utilizing the Uploader component’s change event with the XLSX library:

import { NgModule } from '@angular/core'
import { BrowserModule } from '@angular/platform-browser'
import { GridModule, EditService, PageService, ToolbarService } from '@syncfusion/ej2-angular-grids'
import { UploaderModule,RemovingEventArgs, UploaderComponent } from '@syncfusion/ej2-angular-inputs'
import { DialogModule, DialogComponent } from '@syncfusion/ej2-angular-popups'
import { Component, ViewChild } from '@angular/core';
import { GridComponent } from '@syncfusion/ej2-angular-grids';
import * as XLSX from 'xlsx';

interface CustomFile {
  type: string;
  rawFile: Blob;
  /* Add other properties if needed */
}

@Component({
  imports: [ GridModule,UploaderModule,DialogModule],
  providers: [EditService, PageService, ToolbarService],
  standalone: true,
  selector: 'app-root',
  template: `
    <div class="control-section">
      <div class="col-lg-9">
        <div class="control_wrapper">
          <label style="padding:20px 0px 20px 0px">Browse excel file to load and return grid</label>
          <ejs-uploader [asyncSettings]='path' #defaultupload id='defaultfileupload' (removing)="onRemove($event)" [dropArea]='dropElement' (change)="onSuccess($event)"></ejs-uploader>
        </div>
        <ejs-grid #grid id='grid'>
        </ejs-grid>
        <ejs-dialog #dialog id='dialog' width="350" [visible]='false' header="Alert" content='Invalid JSON' showCloseIcon='true' >
        </ejs-dialog>
      </div>
  </div>
  `,
})
export class AppComponent {
  @ViewChild('defaultupload') public uploadObj?: UploaderComponent;
  @ViewChild('grid') public gridObj?: GridComponent;
  @ViewChild('dialog') public dialog?: DialogComponent;
  public newColumn: any;
  public path: Object = {
    saveUrl: 'https://services.syncfusion.com/angular/production/api/FileUploader/Save',
    removeUrl: 'https://services.syncfusion.com/angular/production/api/FileUploader/Remove'
  };

  public dropElement: HTMLElement = document.getElementsByClassName('control-fluid')[0] as HTMLElement;

  parseExcel(file: CustomFile): void {
    if (file && file.type == "xlsx") {
      var reader = new FileReader();
      reader.onload = (e) => {
        var data = (<any>e.target).result;
        var workbook = XLSX.read(data, { type: 'array' });
        workbook.SheetNames.forEach((sheetName: string) => {
          var XL_row_object = XLSX.utils.sheet_to_json(workbook.Sheets[sheetName]);
          if (Array.isArray(XL_row_object) && XL_row_object.length > 0) {
            (this.gridObj as GridComponent).dataSource = XL_row_object;
          } else {
            (this.dialog as DialogComponent).content = "Invalid JSON";
            this.dialog?.show();
          }
        });
      };

      reader.readAsArrayBuffer(file.rawFile);
    } else {
      (this.dialog as DialogComponent).content = "Please upload only .xlsx format";
      this.dialog?.show();
    }
  }

  public onSuccess(args: { file: CustomFile[] }): void {
    var files = args.file;
    if (files) {
      this.parseExcel(files[0]);
    }
  }

  public onRemove(args: RemovingEventArgs): void {
    (this.gridObj as GridComponent).dataSource = [""];
    (this.gridObj as GridComponent).columns = [];
  }
}
import { bootstrapApplication } from '@angular/platform-browser';
import { AppComponent } from './app.component';
import 'zone.js';
bootstrapApplication(AppComponent).catch((err) => console.error(err));

Binding data and performing CRUD actions via Fetch request

The Syncfusion Angular Grid component provides seamless data binding from external sources using Fetch requests, facilitating CRUD (Create, Read, Update, Delete) operations with server-retrieved data. This capability proves valuable for sending data to servers for database updates and asynchronously retrieving data without full page refreshes.

To achieve data binding and perform CRUD actions using Fetch requests in the Syncfusion Angular Grid, follow these steps:

Step 1: Include the Syncfusion Angular Grid in the HTML with necessary configurations:

<button ejs-button (click)="click()">Bind data via Fetch</button>
<div style="padding: 20px 17px 0 0">
    <ejs-grid #grid [dataSource]='data' [editSettings]='editSettings' [toolbar]='toolbar' allowPaging="true" height="320" (actionBegin)="actionBegin($event)" (actionComplete)="actionComplete($event)">
        <e-columns>
            <e-column field='OrderID' headerText='Order ID' isPrimaryKey=true width='150'></e-column>
            <e-column field='CustomerID' headerText='Customer Name' width='150'></e-column>
            <e-column field='ShipCity' headerText='Ship City' width='150' textAlign='Right'></e-column>
        </e-columns>
    </ejs-grid>
</div>

Step 2: To bind data from an external Fetch request, utilize the dataSource property of the Grid. Fetch data from the server and provide it to the dataSource property using the fetch API:

click() {
    fetch("https://localhost:****/Home/Getdata", {
        method: 'POST',
        headers: {
            'Content-Type': 'application/json'
        }
    }) //Use remote server host number instead ****
    .then(response => response.json())
    .then(data => {
        this.grid.dataSource = data;
    })
    .catch(error => {
        console.error('Error fetching data:', error);
    });
}

On the server side, the GetData method within the HomeController contains the grid’s data source. When the button is clicked, a Fetch request retrieves data from the server and binds it to the Grid component.

public class HomeController : Controller
{        
    public ActionResult Getdata()
    {
        IEnumerable DataSource = OrdersDetails.GetAllRecords();
        return Json(DataSource);
    } 

    // Create model class to define properties
    public class OrdersDetails
    {
        public OrdersDetails()
        {
        }

        public OrdersDetails(int OrderID, string CustomerId, int EmployeeId, double Freight, bool Verified, DateTime OrderDate, string ShipCity, string ShipName, string ShipCountry, DateTime ShippedDate, string ShipAddress)
        {
            this.OrderID = OrderID;
            this.CustomerID = CustomerId;
            this.EmployeeID = EmployeeId;
            this.Freight = Freight;
            this.ShipCity = ShipCity;
            this.Verified = Verified;
            this.OrderDate = OrderDate;
            this.ShipName = ShipName;
            this.ShipCountry = ShipCountry;
            this.ShippedDate = ShippedDate;
            this.ShipAddress = ShipAddress;
        }

        // Render data in this method
        public static List<OrdersDetails> GetAllRecords()
        {
            List<OrdersDetails> order = new List<OrdersDetails>();
            int code = 10000;
            for (int i = 1; i < 10; i++)
            {
                order.Add(new OrdersDetails(code + 1, "ALFKI", i + 0, 2.3 * i, false, new DateTime(1991, 05, 15), "Berlin", "Simons bistro", "Denmark", new DateTime(1996, 7, 16), "Kirchgasse 6"));
                order.Add(new OrdersDetails(code + 2, "ANATR", i + 2, 3.3 * i, true, new DateTime(1990, 04, 04), "Madrid", "Queen Cozinha", "Brazil", new DateTime(1996, 9, 11), "Avda. Azteca 123"));
                order.Add(new OrdersDetails(code + 3, "ANTON", i + 1, 4.3 * i, true, new DateTime(1957, 11, 30), "Cholchester", "Frankenversand", "Germany", new DateTime(1996, 10, 7), "Carrera 52 con Ave. Bolívar #65-98 Llano Largo"));
                order.Add(new OrdersDetails(code + 4, "BLONP", i + 3, 5.3 * i, false, new DateTime(1930, 10, 22), "Marseille", "Ernst Handel", "Austria", new DateTime(1996, 12, 30), "Magazinweg 7"));
                order.Add(new OrdersDetails(code + 5, "BOLID", i + 4, 6.3 * i, true, new DateTime(1953, 02, 18), "Tsawassen", "Hanari Carnes", "Switzerland", new DateTime(1997, 12, 3), "1029 - 12th Ave. S."));
                code += 5;
            }
            return order;
        }

        public int? OrderID { get; set; }
        public string CustomerID { get; set; }
        public int? EmployeeID { get; set; }
        public double? Freight { get; set; }
        public string ShipCity { get; set; }
        public bool Verified { get; set; }
        public DateTime OrderDate { get; set; }
        public string ShipName { get; set; }
        public string ShipCountry { get; set; }
        public DateTime ShippedDate { get; set; }
        public string ShipAddress { get; set; }
    }    
}

Step 3: To perform CRUD actions, leverage the actionBegin event. Cancel default CRUD operations using the cancel argument provided by this event. This enables dynamic server-side method calls using Fetch with relevant data from the actionBegin event to update server data accordingly.

A. To add a new record using Fetch requests, follow these steps:

public flag = false;

actionBegin(e: EditEventArgs) {
    // Initially this.flag needs to be false to enter this condition
    if (!this.flag) {
        // Add operations
        if (e.requestType == 'save' && ((e as any).action == 'add')) {
            var editedData = (e as any).data;
            // Cancel default edit operation
            e.cancel = true;
            // Send updated data to server using fetch call
            fetch('https://localhost:****/Home/Insert', {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json'
                },
                body: JSON.stringify({ value: editedData })
            }) //Use remote server host number instead ****
            .then(response => response.json())
            .then(data => {
                // this.flag is enabled to skip this execution when grid ends add/edit
                this.flag = true;
                // The added/edited data will be saved in the Grid
                this.grid.endEdit();
            })
            .catch(error => {
                // Add/edit failed
                // The this.flag is disabled if operation fails so it can enter the condition on next execution
                this.flag = false;
                console.error('Add operation failed:', error);
            });
        }
    }
}
// Insert record
public ActionResult Insert(OrdersDetails value)
{
    OrdersDetails.GetAllRecords().Insert(0, value);
    return Json(value);
}

B. To edit and save a record using a Fetch request, follow these steps:

public flag = false;

actionBegin(e: EditEventArgs) {
    // Initially this.flag needs to be false to enter this condition
    if (!this.flag) {
        if (e.requestType == 'save' && ((e as any).action == "edit")) {
            var editedData = (e as any).data;
            // Cancel default edit operation
            e.cancel = true;
            // Send updated data to server using fetch call
            fetch('https://localhost:****/Home/Update', {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json'
                },
                body: JSON.stringify({ value: editedData })
            }) //Use remote server host number instead ****
            .then(response => response.json())
            .then(data => {
                // this.flag is enabled to skip this execution when grid ends add/edit
                this.flag = true;
                // The added/edited data will be saved in the Grid
                this.grid.endEdit();
            })
            .catch(error => {
                // Add/edit failed
                // The this.flag is disabled if operation fails so it can enter the condition on next execution
                this.flag = false;
                console.error('Update operation failed:', error);
            });
        }        
    }
}
// Update record
public ActionResult Update(OrdersDetails value)
{
    var ord = value;
    OrdersDetails val = OrdersDetails.GetAllRecords().Where(or => or.OrderID == ord.OrderID).FirstOrDefault();
    val.OrderID = ord.OrderID;
    val.EmployeeID = ord.EmployeeID;
    val.CustomerID = ord.CustomerID;
    return Json(value);
}

C. To delete a record using a Fetch request, follow these steps:

public flag = false;

actionBegin(e: EditEventArgs) {
    // Initially this.flag needs to be false to enter this condition
    if (!this.flag) {
        if (e.requestType == 'delete') {
            var editedData = (e as any).data;
            // Cancel default delete operation
            e.cancel = true;
            // Send deleted data to server using fetch call
            fetch('https://localhost:****/Home/Delete', {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json'
                },
                body: JSON.stringify({ key: editedData[0][this.grid.getPrimaryKeyFieldNames()[0]] })
            }) //Use remote server host number instead ****
            .then(response => response.json())
            .then(data => {
                // this.flag is enabled to skip this execution when grid deletes record
                this.flag = true;
                // The deleted data will be removed in the Grid
                this.grid.deleteRecord();
            })
            .catch(error => {
                // Delete failed
                // The this.flag is disabled if operation fails so it can enter the condition on next execution
                this.flag = false;
                console.error('Delete operation failed:', error);
            });
        }
    }
}
// Delete record
public ActionResult Delete(int key)
{
    OrdersDetails.GetAllRecords().Remove(OrdersDetails.GetAllRecords().Where(or => or.OrderID == key).FirstOrDefault());
    var data = OrdersDetails.GetAllRecords();
    return Json(data);
}

Step 4: In the Fetch success event, utilize the Grid’s endEdit and deleteRecord methods to handle addition, editing, and deletion of corresponding data in the Grid. However, invoking these methods triggers the actionBegin event again to save changes in the Grid. To prevent this behavior and maintain execution flow control, employ a flag variable managed within the actionComplete and Fetch failure events:

actionComplete(e: EditEventArgs) {
    if (e.requestType === 'save' || e.requestType === 'delete') {
        // The this.flag is disabled after operation is successfully performed so it can enter the condition on next execution
        this.flag = false;
    }
}

The following screenshot demonstrates data loading when the button is clicked and CRUD operations performance:

Fetch API data loading and CRUD operations demonstration

You can find a complete sample for Fetch request implementation on GitHub.

Display loading indicator with local data

The Syncfusion Angular Grid component enables displaying a loading indicator while loading local data. This feature proves useful when delays occur in loading data from local sources, providing users visual feedback that data is being fetched.

To display the loading indicator with local data, set the showSpinner property to true. This property controls loading indicator visibility.

The following example demonstrates loading indicator display in the Syncfusion Angular Grid using the load and created events:

import { Component, OnInit, ViewChild } from '@angular/core';
import { GridComponent } from '@syncfusion/ej2-angular-grids';
import { data } from './datasource';

@Component({
  selector: 'app-root',
  template: `
       <ejs-grid #Grid (load)='load()' (created)='created()' >
           <e-columns>
                <e-column field='OrderID' headerText='Order ID' textAlign='Right' width=120></e-column>
                <e-column field='CustomerID' headerText='Customer ID' textAlign='Right' width=120></e-column>
                <e-column field='Quantity' headerText='Quantity' textAlign='Right' width=120></e-column>
                <e-column field='CustomerAddress' headerText='Ship Address' textAlign='Right' width=120></e-column>
            </e-columns>
       </ejs-grid>`
})
export class AppComponent implements OnInit {
 
  @ViewChild('Grid') public grid?: GridComponent;
  public isDataLoading = true;

  ngOnInit(): void {
  }
  
  load() {
    if (this.isDataLoading) { 
      (this.grid as GridComponent).showSpinner();
    }
  }

  created(){
    this.isDataLoading = true;
    (this.grid as GridComponent).dataSource = data as Object[];
  }
}

Binding data and performing CRUD actions via AJAX request

The Syncfusion Angular Grid component provides seamless data binding from external sources using AJAX requests, facilitating CRUD (Create, Read, Update, Delete) operations with server-retrieved data. This capability proves valuable for sending data to servers for database updates and asynchronously retrieving data without full page refreshes.

To achieve data binding and perform CRUD actions using AJAX requests in the Syncfusion Angular Grid, follow these steps:

Step 1: Include the Syncfusion Angular Grid in the HTML with necessary configurations:

<button ejs-button (click)="click()">Bind data via AJAX</button>
<div style="padding: 20px 17px 0 0">
    <ejs-grid #grid [dataSource]='data' allowFiltering="true" [editSettings]='editSettings' [toolbar]='toolbar' allowPaging="true" height="320" (actionBegin)="actionBegin($event)" (actionComplete)="actionComplete($event)">
        <e-columns>
            <e-column field='OrderID' headerText='Order ID' isPrimaryKey=true width='150'></e-column>
            <e-column field='CustomerID' headerText='Customer Name' width='150'></e-column>
            <e-column field='ShipCity' headerText='Ship City' width='150' textAlign='Right'></e-column>
        </e-columns>
    </ejs-grid>
</div>

Step 2: To bind data from an external AJAX request, utilize the dataSource property of the Grid. Fetch data from the server and provide it to the dataSource property using the onSuccess event of the AJAX request:

import { Ajax } from '@syncfusion/ej2-base';

click() {
    const ajax = new Ajax("https://localhost:****/Home/Getdata", 'POST'); //Use remote server host number instead ****
    ajax.send();
    ajax.onSuccess = (data: string) => {
        this.grid.dataSource = JSON.parse(data);
    };
}

On the server side, the GetData method within the HomeController contains the grid’s data source. When the button is clicked, an AJAX request retrieves data from the server and binds it to the Grid component.

public class HomeController : Controller
{        
    public ActionResult Getdata()
    {
        var DataSource = OrdersDetails.GetAllRecords();
        return Json(DataSource);
    } 

    // Create model class to define properties
    public class OrdersDetails
    {
        public OrdersDetails()
        {
        }

        public OrdersDetails(int OrderID, string CustomerId, int EmployeeId, double Freight, bool Verified, DateTime OrderDate, string ShipCity, string ShipName, string ShipCountry, DateTime ShippedDate, string ShipAddress)
        {
            this.OrderID = OrderID;
            this.CustomerID = CustomerId;
            this.EmployeeID = EmployeeId;
            this.Freight = Freight;
            this.ShipCity = ShipCity;
            this.Verified = Verified;
            this.OrderDate = OrderDate;
            this.ShipName = ShipName;
            this.ShipCountry = ShipCountry;
            this.ShippedDate = ShippedDate;
            this.ShipAddress = ShipAddress;
        }

        // Render data in this method
        public static List<OrdersDetails> GetAllRecords()
        {
            List<OrdersDetails> order = new List<OrdersDetails>();
            int code = 10000;
            for (int i = 1; i < 10; i++)
            {
                order.Add(new OrdersDetails(code + 1, "ALFKI", i + 0, 2.3 * i, false, new DateTime(1991, 05, 15), "Berlin", "Simons bistro", "Denmark", new DateTime(1996, 7, 16), "Kirchgasse 6"));
                order.Add(new OrdersDetails(code + 2, "ANATR", i + 2, 3.3 * i, true, new DateTime(1990, 04, 04), "Madrid", "Queen Cozinha", "Brazil", new DateTime(1996, 9, 11), "Avda. Azteca 123"));
                order.Add(new OrdersDetails(code + 3, "ANTON", i + 1, 4.3 * i, true, new DateTime(1957, 11, 30), "Cholchester", "Frankenversand", "Germany", new DateTime(1996, 10, 7), "Carrera 52 con Ave. Bolívar #65-98 Llano Largo"));
                order.Add(new OrdersDetails(code + 4, "BLONP", i + 3, 5.3 * i, false, new DateTime(1930, 10, 22), "Marseille", "Ernst Handel", "Austria", new DateTime(1996, 12, 30), "Magazinweg 7"));
                order.Add(new OrdersDetails(code + 5, "BOLID", i + 4, 6.3 * i, true, new DateTime(1953, 02, 18), "Tsawassen", "Hanari Carnes", "Switzerland", new DateTime(1997, 12, 3), "1029 - 12th Ave. S."));
                code += 5;
            }
            return order;
        }

        public int? OrderID { get; set; }
        public string CustomerID { get; set; }
        public int? EmployeeID { get; set; }
        public double? Freight { get; set; }
        public string ShipCity { get; set; }
        public bool Verified { get; set; }
        public DateTime OrderDate { get; set; }
        public string ShipName { get; set; }
        public string ShipCountry { get; set; }
        public DateTime ShippedDate { get; set; }
        public string ShipAddress { get; set; }
    }    
}

Step 3: To perform CRUD actions, leverage the actionBegin event. Cancel default CRUD operations using the cancel argument provided by this event. This enables dynamic server-side method calls using AJAX with relevant data from the actionBegin event to update server data accordingly.

A. To add a new record using AJAX requests, follow these steps:

import { Ajax } from '@syncfusion/ej2-base';

public flag = false;

actionBegin(e: EditEventArgs) {
    // Initially this.flag needs to be false to enter this condition
    if (!this.flag) {
        // Add operations
        if (e.requestType == 'save' && ((e as any).action == 'add')) {
            var editedData = (e as any).data;
            // Cancel default edit operation
            e.cancel = true;
            // Send updated data to server using AJAX call
            var ajax = new Ajax({
                url: 'https://localhost:****/Home/Insert',
                type: 'POST',
                contentType: 'application/json; charset=utf-8',
                data: JSON.stringify({ value: editedData })
            });  //Use remote server host number instead ****
            ajax.onSuccess = () => {
                // this.flag is enabled to skip this execution when grid ends add/edit
                this.flag = true;
                // The added/edited data will be saved in the Grid
                this.grid.endEdit();
            }
            ajax.onFailure = () => {
                // Add/edit failed
                // The this.flag is disabled if operation fails so it can enter the condition on next execution
                this.flag = false;
            }
            ajax.send();
        }
    }
}
// Insert record
public ActionResult Insert([FromBody] CRUDModel<OrdersDetails> value)
{
    OrdersDetails.GetAllRecords().Insert(0, value.value);
    return Json(value.value);
}

B. To edit and save a record using an AJAX request, follow these steps:

public flag = false;

actionBegin(e: EditEventArgs) {
    // Initially this.flag needs to be false to enter this condition
    if (!this.flag) {
        if (e.requestType == 'save' && ((e as any).action == "edit")) {
            var editedData = (e as any).data;
            // Cancel default edit operation
            e.cancel = true;
            // Send updated data to server using AJAX call
            var ajax = new Ajax({
                url: 'https://localhost:****/Home/Update',
                type: 'POST',
                contentType: 'application/json; charset=utf-8',
                data: JSON.stringify({ value: editedData })
            }); //Use remote server host number instead ****
            ajax.onSuccess = () => {
                // this.flag is enabled to skip this execution when grid ends add/edit
                this.flag = true;
                // The added/edited data will be saved in the Grid
                this.grid.endEdit();
            }
            ajax.onFailure = () => {
                // Add/edit failed
                // The this.flag is disabled if operation fails so it can enter the condition on next execution
                this.flag = false;
            }
            ajax.send();
        }
    }
}
// Update record
public ActionResult Update([FromBody] CRUDModel<OrdersDetails> value)
{
    var ord = value.value;
    OrdersDetails val = OrdersDetails.GetAllRecords().Where(or => or.OrderID == ord.OrderID).FirstOrDefault();
    val.OrderID = ord.OrderID;
    val.EmployeeID = ord.EmployeeID;
    val.CustomerID = ord.CustomerID;
    val.Freight = ord.Freight;
    val.OrderDate = ord.OrderDate;
    val.ShipCity = ord.ShipCity;
    val.ShipCountry = ord.ShipCountry;
    return Json(value.value);
}

C. To delete a record using an AJAX request, follow these steps:

public flag = false;

actionBegin(e: EditEventArgs) {
    // Initially this.flag needs to be false to enter this condition
    if (!this.flag) {
        if (e.requestType == 'delete') {
            var editedData = (e as any).data;
            // Cancel default delete operation
            e.cancel = true;
            // Send deleted data to server using AJAX call
            var ajax = new Ajax({
                url: 'https://localhost:****/Home/Delete',
                type: 'POST',
                contentType: 'application/json; charset=utf-8',
                data: JSON.stringify({ key: editedData[0][this.grid.getPrimaryKeyFieldNames()[0]] })
            }) //Use remote server host number instead ****
            ajax.onSuccess = () => {
                // this.flag is enabled to skip this execution when grid deletes record
                this.flag = true;
                // The deleted data will be removed in the Grid
                this.grid.deleteRecord();
            }
            ajax.onFailure = () => {
                // Delete failed
                // The this.flag is disabled if operation fails so it can enter the condition on next execution
                this.flag = false;
            }
            ajax.send();
        }
    }
}
// Delete record
public ActionResult Delete(int key)
{
    OrdersDetails.GetAllRecords().Remove(OrdersDetails.GetAllRecords().Where(or => or.OrderID == key).FirstOrDefault());
    var data = OrdersDetails.GetAllRecords();
    return Json(data);
}

Step 4: In the AJAX success event, utilize the Grid’s endEdit and deleteRecord methods to handle addition, editing, and deletion of corresponding data in the Grid. However, invoking these methods triggers the actionBegin event again to save changes in the Grid. To prevent this behavior and maintain execution flow control, employ a flag variable managed within the actionComplete and AJAX failure events:

actionComplete(e: EditEventArgs) {
    if (e.requestType === 'save' || e.requestType === 'delete') {
        // The this.flag is disabled after operation is successfully performed so it can enter the condition on next execution
        this.flag = false;
    }
}

The following screenshot demonstrates data loading when the button is clicked and CRUD operations performance:

AJAX request data loading and CRUD operations demonstration

You can find a complete sample for AJAX request implementation on GitHub.

Display loading indicator using AJAX

The Syncfusion Angular Grid component enables displaying a loading indicator while loading data using AJAX requests. This feature proves useful when delays occur in data loading, providing users visual feedback that data is being fetched. This capability particularly benefits applications working with large datasets or slower internet connections.

To display the loading indicator with AJAX data, set the showSpinner property to true. This property controls loading indicator visibility.

The following example demonstrates loading indicator display in the Syncfusion Angular Grid using the load and created events:

import { Component, OnInit, ViewChild } from '@angular/core';
import { GridComponent } from '@syncfusion/ej2-angular-grids';
import { Ajax } from '@syncfusion/ej2-base';

@Component({
    selector: 'app-root',
    template: `
                <ejs-grid #Grid allowPaging="true" [pageSettings]="pageSettings" (created)='created()' (load)='load()'>
                <e-columns>
                    <e-column field='OrderID' headerText='Order ID' textAlign='Right' width=120></e-column>
                    <e-column field='CustomerID' headerText='Customer ID' textAlign='Right' width=120></e-column>
                    <e-column field='EmployeeID' headerText='Employee ID' textAlign='Right' width=120></e-column>
                    <e-column field='ShipCountry' headerText='Ship Country' textAlign='Right' width=120></e-column>
                </e-columns>
                </ejs-grid>`
})
export class AppComponent implements OnInit {
    public data?: object[];
    public isDataLoading = true;
    public pageSettings?: object = { pageSize: 5, pageCount: 3 }
    @ViewChild('Grid') public grid?: GridComponent;
    
    ngOnInit(): void {
    }
    
    load() {
        if (this.isDataLoading) {
            (this.grid as GridComponent).showSpinner();
            this.isDataLoading = false;
        }
    }
    
    created() {
        this.isDataLoading = true;
        const grid = this.grid;  // Grid instance
        const ajax = new Ajax(
                'https://services.syncfusion.com/angular/production/api/orders',
                'GET'
              );
        ajax.send();
        ajax.onSuccess = (data: string) => {
            (grid as GridComponent).dataSource = JSON.parse(data);
        };
    }
}

Managing spinner visibility during data loading

Displaying a spinner during data loading in the Syncfusion Angular Grid component enhances user experience by providing visual indication of loading progress. This feature helps users understand that data is being fetched or processed.

To show or hide a spinner during data loading in the grid, utilize the showSpinner and hideSpinner methods provided by the Grid component.

The following example demonstrates showing and hiding the spinner during data loading using external buttons in a grid:

import { NgModule, ViewChild } from '@angular/core'
import { BrowserModule } from '@angular/platform-browser'
import { GridModule, PageService, GridComponent } from '@syncfusion/ej2-angular-grids';
import { ButtonModule } from '@syncfusion/ej2-angular-buttons';
import { enableRipple } from '@syncfusion/ej2-base'
import { Component, OnInit } from '@angular/core';
import { data } from './datasource';

@Component({
    imports: [
        ButtonModule,
        GridModule
    ],

    providers: [PageService,],
    standalone: true,
    selector: 'app-root',
    template: `
    <div>
        <div>
            <button ejs-button id="loadButton" cssClass="e-outline" (click)="onloadData()">Load Data</button>
            <button ejs-button id="showButton" style='margin-left:5px' cssClass="e-outline" (click)="showhideSpinner($event)">Show Spinner</button>
            <button ejs-button id="hideButton" style='margin-left:5px' cssClass="e-outline" (click)="showhideSpinner($event)">Hide Spinner</button>
        </div>
        <ejs-grid #grid id='grid' allowPaging='true' height='272px' style='margin-top:10px'>
            <e-columns>
                <e-column field='OrderID' headerText='Order ID' textAlign='Right' width=90></e-column>
                <e-column field='CustomerID' headerText='Customer ID' width=100></e-column>
                <e-column field='ProductName' headerText='Product Name' width=110></e-column>
                <e-column field='Quantity' headerText='Quantity' width=100></e-column>
            </e-columns>
        </ejs-grid>
    </div>`
})
export class AppComponent implements OnInit {

    @ViewChild('grid')
    public grid?: GridComponent;

    ngOnInit(): void {
    }

    onloadData(){
        (this.grid as GridComponent).showSpinner();
        setTimeout(() => {
            (this.grid as GridComponent).dataSource = data; 
            (this.grid as GridComponent).hideSpinner(); 
        }, 1000);
    }

    showhideSpinner(args: MouseEvent){
        if((args.currentTarget  as HTMLElement).id === 'showButton'){
            (this.grid as GridComponent).showSpinner();
        }
        else{
            (this.grid as GridComponent).hideSpinner();
        }
    }
}
import { bootstrapApplication } from '@angular/platform-browser';
import { AppComponent } from './app.component';
import 'zone.js';
bootstrapApplication(AppComponent).catch((err) => console.error(err));

Immutable mode

Immutable mode in the Syncfusion Angular Grid component optimizes re-rendering performance by utilizing object reference and deep compare concepts. This mode ensures that when performing Grid actions, only modified or newly added rows are re-rendered, preventing unnecessary re-rendering of unchanged rows.

To enable this feature, set the enableImmutableMode property to true.

When immutable mode is enabled and the datasource changes, only newly added rows are regenerated or reused. Consequently, the grid’s queryCellInfo and rowDataBound events trigger only for newly generated rows, not existing rows.

When immutable mode is disabled, both newly added rows and existing rows are regenerated or reused when the datasource changes. As a result, the rowDataBound and queryCellInfo events trigger for both newly added and existing rows.

This feature uses the primary key value for data comparison. Therefore, provide the isPrimaryKey column.

The following example demonstrates immutable mode implementation in an Angular component. When add, delete, or update actions are performed, existing rows are not regenerated or reused, ensuring efficient rendering of only modified or newly added rows:

import { NgModule } from '@angular/core'
import { BrowserModule } from '@angular/platform-browser'
import { GridModule, PageService } from '@syncfusion/ej2-angular-grids'
import { Component, ViewChild, OnInit } from "@angular/core";
import { GridComponent, RowDataBoundEventArgs, SelectionSettingsModel } from "@syncfusion/ej2-angular-grids";
import { data } from "./datasource";

interface CustomRowDataBoundEventArgs extends RowDataBoundEventArgs {
  column: {
    field: string;
  };
  cell: HTMLElement;
  data: DataType;
}

interface DataType {
  OrderID: number;
  CustomerID: string;
  Freight: number;
  ShipName: string;
  ShipCity: string;
  isNewlyAdded: boolean;
}

@Component({
imports: [
        
        GridModule
    ],

providers: [PageService],
standalone: true,
  selector: 'app-root',
  template: `
      <button  #addtop ejs-button class="e-control e-btn e-lib e-info" (click)="addTopEvent()">Add rows Data</button>
      <button style="margin-left: 20px" #delete ejs-button class="e-control e-btn e-lib e-info" (click)="deleteEvent()">Delete rows</button>
      <button style="margin-left: 20px" #addbottom ejs-button class="e-control e-btn e-lib e-info" (click)="updateEvent()">Update Freight Data</button>
      <div id="message"> {{ message }}</div>
      <div  style="padding-top: 20px">
        <ejs-grid #immutable [dataSource]='rowData' [enableHover]="false" height='350' [enableImmutableMode]="true" allowPaging="true" [selectionSettings]="selectionOptions"
          [pageSettings]="pageSettings"  (rowDataBound)="rowDataBound($event)" (queryCellInfo)="queryCellInfo($event)">
          <e-columns>
            <e-column field='OrderID' headerText='Order ID' isPrimaryKey="true" width='120'
            textAlign='Right'></e-column>
            <e-column field='CustomerID' headerText='Customer ID' width='120'></e-column>
            <e-column field='Freight' headerText='Freight' width='120'></e-column>
            <e-column field='ShipName' headerText='Ship Name' width='120'></e-column>
          </e-columns>
        </ejs-grid>
      </div>`
})
export class AppComponent implements OnInit {

  public pageSettings?: Object = { pageSize: 50 };
  public rowData?: DataType[];
  @ViewChild("immutable")
  public immutablegrid?: GridComponent;
  public intervalId: NodeJS.Timeout;
  public selectionOptions?: SelectionSettingsModel;
  public message: string = '';

  ngOnInit(): void {
    this.rowData = data as DataType[];
    this.selectionOptions = { type: 'Multiple' };
    this.message = `Initial rows rendered: ${this.rowData.length}`;
  }

  queryCellInfo(args: CustomRowDataBoundEventArgs): void {
    if (args.column.field === 'ShipName' && args.data.ShipName === "Gems Chevalier") {
      (args.cell as HTMLElement).style.backgroundColor = 'rgb(210, 226, 129)';
    }
  }
  
  rowDataBound(args: RowDataBoundEventArgs): void {
    (args.row as HTMLElement).style.backgroundColor = (args.data as DataType).isNewlyAdded ? '' : ' rgb(208, 255, 255)';
  }
  
  addTopEvent(): void {
    // Set the background color of all rows to indicate existing data
    (this.immutablegrid as GridComponent).getAllDataRows().forEach(row => {
      (row as HTMLElement).style.backgroundColor = "rgb(208, 255, 255)";
    });
    let count = 0;
    if (count < 1) {
      let newRowData: object[] = [];
      let addedRecords: object = {
        'OrderID': this.generateOrderId(),
        'CustomerID': this.generateCustomerId(),
        'ShipCity': this.generateShipCity(),
        'Freight': this.generateFreight(),
        'ShipName': this.generateShipName(),
        'isNewlyAdded': true
      };
      newRowData.push(addedRecords);
      this.rowData = ([...newRowData, ...this.rowData as DataType[]] as DataType[]);
      count++;
      this.message = `${count} rows rendered after performing the add action`;
    }
  }
  
  deleteEvent(): void {
    let count = 0;
    if (count < 1 && (this.rowData as DataType[]).length > 0) {
      this.rowData = (this.rowData as DataType[]).slice(1);
      count++;
      this.message = `${count} rows deleted after performing delete action`;
    }
  }
  
  updateEvent(): void {
    let count = 0;
    let newRowData = (this.rowData as any).map((row: any) => {
      if (row.ShipName === 'Bueno Foods') {
        count++;
        return { ...row, 'ShipName': "Gems Chevalier" };
      } else {
        return row;
      }
    });
    this.rowData = newRowData;
    this.message = ` ${count} rows updated after performing update action`;
  }

  generateOrderId(): number {
    return Math.floor(10000 + Math.random() * 90000);
  }

  generateCustomerId(): string {
    const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
    let result = '';
    for (let i = 0; i < 5; i++) {
      result += characters.charAt(Math.floor(Math.random() * characters.length));
    }
    return result;
  }

  generateShipCity(): string {
    const cities = ['London', 'Paris', 'New York', 'Tokyo', 'Berlin'];
    return cities[Math.floor(Math.random() * cities.length)];
  }

  generateFreight(): number {
    const randomValue = Math.random() * 100;
    return parseFloat(randomValue.toFixed(2));
  }

  generateShipName(): string {
    const names = ['Que Delícia', 'Bueno Foods', 'Island Trading', 'Laughing Bacchus Winecellars'];
    return names[Math.floor(Math.random() * names.length)];
  }
}
import { bootstrapApplication } from '@angular/platform-browser';
import { AppComponent } from './app.component';
import 'zone.js';
bootstrapApplication(AppComponent).catch((err) => console.error(err));

Limitations

The following features are not supported in immutable mode:

  • Frozen rows and columns
  • Grouping
  • Row Template
  • Detail Template
  • Hierarchy Grid
  • Scrolling
  • Virtual scrolling
  • Infinite scrolling
  • Column reordering
  • Row and column spanning
  • PDF export, Excel export, and Print
  • Column resizing
  • Drag and drop
  • Column template
  • Column chooser
  • Clipboard operations
  • AutoFit
  • Filtering