Adaptors in Syncfusion EJ2 TypeScript DataManager

10 Jul 202524 minutes to read

Different data sources and remote services often follow distinct protocols for handling requests and returning responses. While the DataManager is designed to support a wide range of data services, it cannot natively interpret every possible data format or communication pattern.

To tackle this challenge, the DataManager leverages a flexible adaptor system. Adaptors act as intermediaries that translate requests and responses between the DataManager and the data service, ensuring seamless interaction regardless of the backend’s architecture.

Adaptors in Syncfusion EJ2 TypeScript DataManager act as communication bridges between the component and various data sources. They format queries and interpret responses appropriately depending on whether the data source is local or remote.

Purpose of Adaptors:

For local data sources: An adaptor facilitates the management of data that is already present within the application, such as a list or table maintained in memory. It handles operations like searching, sorting, filtering, and paging directly on this local dataset, eliminating the need for communication with an external server.

For example, imagine you have a contact list saved on your phone. If you want to quickly find all contacts with the name “John” or sort them by last called date, the adaptor handles this immediately on your phone without needing internet or asking a server.

For remote data sources: An adaptor serves as a bridge between the application and the server, translating data operations into appropriate request formats. It constructs and sends queries to the server using protocols like REST, OData, or GraphQL. Once the server responds, the adaptor processes and formats the data so the application can understand and use it.

For example, in an online shopping app where product information is stored on a remote server, when you search for products or browse pages, the adaptor formats your request properly, sends it to the server, and then converts the returned data into a list that the app can display.

Types of Adaptors:

Syncfusion provides several built-in adaptors to work with different data sources:

Adaptor Description
JsonAdaptor Works with local JavaScript arrays.
ODataAdaptor Communicates with OData v3 services.
ODataV4Adaptor For OData v4 endpoints.
WebApiAdaptor Integrates with ASP.NET Web API.
WebMethodAdaptor Integrates with web methods (e.g., ASP.NET server methods).
UrlAdaptor A generic adaptor for RESTful endpoints.
CustomAdaptor Enables fully custom data processing logic.
GraphQLAdaptor Used to communicate with GraphQL services.

Json adaptor

The JsonAdaptor is a built-in adaptor provided by Syncfusion EJ2 TypeScript DataManager module. It is specifically designed to work with local data sources, such as JavaScript arrays or in-memory collections. It allows you to perform various data operations like filtering, sorting, paging, and grouping directly on the client-side, without the need for server-side requests.

If you’re building a feature like a product listing or a customer table where data is already available on the client-side (e.g., fetched once from an API or stored locally), the JsonAdaptor allows you to perform data operations directly in the browser, eliminating unnecessary server requests and improving performance.

To achieve the manipulation of local data using JsonAdaptor, follow these steps:

Step 1: Import DataManager, Query, JsonAdaptor modules from @syncfusion/ej2-data, and the compile module from @syncfusion/ej2-base.

  import { DataManager, Query, JsonAdaptor } from '@syncfusion/ej2-data';
  import { compile } from '@syncfusion/ej2-base';

Step 2: Provide your local data array to json of DataManager.

  const data: Object[] = [
    { OrderID: 10248, CustomerID: 'VINET', EmployeeID: 5 },
    { OrderID: 10249, CustomerID: 'TOMSP', EmployeeID: 6 },
    { OrderID: 10250, CustomerID: 'HANAR', EmployeeID: 4 },
    // ... more data items.
  ];

Step 3: Configure the DataManager:

Assign your local data array to the json property and set the adaptor to an instance of JsonAdaptor.

  const dataManager = new DataManager({
    json: data,
    adaptor: new JsonAdaptor()
  });

Step 4: Apply a query using executeLocal method:

Use the executeLocal method with a Query object to retrieve and manipulate data directly on the client-side. This method allows you to perform operations like filtering, sorting, paging, and grouping on local data without any server requests. For example, to retrieve the first 8 records.

  const result: Object[] = dataManager.executeLocal(new Query().take(8));

Here is an example that demonstrates how to use the JsonAdaptor:

import { DataManager, Query, JsonAdaptor } from '@syncfusion/ej2-data';
import { compile } from '@syncfusion/ej2-base';
import {data} from './datasource.ts';

let template: string = '<tr><td>${OrderID}</td><td>${CustomerID}</td><td>${EmployeeID}</td></tr>';
let compiledFunction: Function = compile(template);

let result: Object[] = new DataManager({ json: data, adaptor: new JsonAdaptor }).executeLocal(new Query().take(8));

let table: HTMLElement = (<HTMLElement>document.getElementById('datatable'));

result.forEach((data: Object) => {
    table.appendChild(compiledFunction(data)[0]);
});
<!DOCTYPE html>
<html lang="en">

<head>
  <title>EJ2 Grid</title>
  <meta charset="utf-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <meta name="description" content="Typescript Grid Control" />
  <meta name="author" content="Syncfusion" />
  <link href="index.css" rel="stylesheet" />
  <script src="https://cdnjs.cloudflare.com/ajax/libs/systemjs/0.19.38/system.js"></script>
  <script src="systemjs.config.js"></script>
<script src="https://cdn.syncfusion.com/ej2/syncfusion-helper.js" type ="text/javascript"></script>
</head>

<body>
  <div id='container'>
    <table border="1" id='datatable' class='e-table'>
      <thead>
        <tr>
          <th>Order ID</th>
          <th>Customer ID</th>
          <th>Employee ID</th>
        </tr>
      </thead>
      <tbody>
      </tbody>
    </table>
  </div>
</body>

</html>
export let data: Object[] = [
    {
        OrderID: 10248, CustomerID: 'VINET', EmployeeID: 5, OrderDate: new Date(8364186e5),
        ShipName: 'Vins et alcools Chevalier', ShipCity: 'Reims', ShipAddress: '59 rue de l Abbaye',
        ShipRegion: 'CJ', ShipPostalCode: '51100', ShipCountry: 'France', Freight: 32.38, Verified: !0
    },
    {
        OrderID: 10249, CustomerID: 'TOMSP', EmployeeID: 6, OrderDate: new Date(836505e6),
        ShipName: 'Toms Spezialitäten', ShipCity: 'Münster', ShipAddress: 'Luisenstr. 48',
        ShipRegion: 'CJ', ShipPostalCode: '44087', ShipCountry: 'Germany', Freight: 11.61, Verified: !1
    },
    {
        OrderID: 10250, CustomerID: 'HANAR', EmployeeID: 4, OrderDate: new Date(8367642e5),
        ShipName: 'Hanari Carnes', ShipCity: 'Rio de Janeiro', ShipAddress: 'Rua do Paço, 67',
        ShipRegion: 'RJ', ShipPostalCode: '05454-876', ShipCountry: 'Brazil', Freight: 65.83, Verified: !0
    },
    {
        OrderID: 10251, CustomerID: 'VICTE', EmployeeID: 3, OrderDate: new Date(8367642e5),
        ShipName: 'Victuailles en stock', ShipCity: 'Lyon', ShipAddress: '2, rue du Commerce',
        ShipRegion: 'CJ', ShipPostalCode: '69004', ShipCountry: 'France', Freight: 41.34, Verified: !0
    },
    {
        OrderID: 10252, CustomerID: 'SUPRD', EmployeeID: 4, OrderDate: new Date(8368506e5),
        ShipName: 'Suprêmes délices', ShipCity: 'Charleroi', ShipAddress: 'Boulevard Tirou, 255',
        ShipRegion: 'CJ', ShipPostalCode: 'B-6000', ShipCountry: 'Belgium', Freight: 51.3, Verified: !0
    },
    {
        OrderID: 10253, CustomerID: 'HANAR', EmployeeID: 3, OrderDate: new Date(836937e6),
        ShipName: 'Hanari Carnes', ShipCity: 'Rio de Janeiro', ShipAddress: 'Rua do Paço, 67',
        ShipRegion: 'RJ', ShipPostalCode: '05454-876', ShipCountry: 'Brazil', Freight: 58.17, Verified: !0
    },
    {
        OrderID: 10254, CustomerID: 'CHOPS', EmployeeID: 5, OrderDate: new Date(8370234e5),
        ShipName: 'Chop-suey Chinese', ShipCity: 'Bern', ShipAddress: 'Hauptstr. 31',
        ShipRegion: 'CJ', ShipPostalCode: '3012', ShipCountry: 'Switzerland', Freight: 22.98, Verified: !1
    },
    {
        OrderID: 10255, CustomerID: 'RICSU', EmployeeID: 9, OrderDate: new Date(8371098e5),
        ShipName: 'Richter Supermarkt', ShipCity: 'Genève', ShipAddress: 'Starenweg 5',
        ShipRegion: 'CJ', ShipPostalCode: '1204', ShipCountry: 'Switzerland', Freight: 148.33, Verified: !0
    },
    {
        OrderID: 10256, CustomerID: 'WELLI', EmployeeID: 3, OrderDate: new Date(837369e6),
        ShipName: 'Wellington Importadora', ShipCity: 'Resende', ShipAddress: 'Rua do Mercado, 12',
        ShipRegion: 'SP', ShipPostalCode: '08737-363', ShipCountry: 'Brazil', Freight: 13.97, Verified: !1
    },
    {
        OrderID: 10257, CustomerID: 'HILAA', EmployeeID: 4, OrderDate: new Date(8374554e5),
        ShipName: 'HILARION-Abastos', ShipCity: 'San Cristóbal', ShipAddress: 'Carrera 22 con Ave. Carlos Soublette #8-35',
        ShipRegion: 'Táchira', ShipPostalCode: '5022', ShipCountry: 'Venezuela', Freight: 81.91, Verified: !0
    },
    {
        OrderID: 10258, CustomerID: 'ERNSH', EmployeeID: 1, OrderDate: new Date(8375418e5),
        ShipName: 'Ernst Handel', ShipCity: 'Graz', ShipAddress: 'Kirchgasse 6',
        ShipRegion: 'CJ', ShipPostalCode: '8010', ShipCountry: 'Austria', Freight: 140.51, Verified: !0
    },
    {
        OrderID: 10259, CustomerID: 'CENTC', EmployeeID: 4, OrderDate: new Date(8376282e5),
        ShipName: 'Centro comercial Moctezuma', ShipCity: 'México D.F.', ShipAddress: 'Sierras de Granada 9993',
        ShipRegion: 'CJ', ShipPostalCode: '05022', ShipCountry: 'Mexico', Freight: 3.25, Verified: !1
    },
    {
        OrderID: 10260, CustomerID: 'OTTIK', EmployeeID: 4, OrderDate: new Date(8377146e5),
        ShipName: 'Ottilies Käseladen', ShipCity: 'Köln', ShipAddress: 'Mehrheimerstr. 369',
        ShipRegion: 'CJ', ShipPostalCode: '50739', ShipCountry: 'Germany', Freight: 55.09, Verified: !0
    },
    {
        OrderID: 10261, CustomerID: 'QUEDE', EmployeeID: 4, OrderDate: new Date(8377146e5),
        ShipName: 'Que Delícia', ShipCity: 'Rio de Janeiro', ShipAddress: 'Rua da Panificadora, 12',
        ShipRegion: 'RJ', ShipPostalCode: '02389-673', ShipCountry: 'Brazil', Freight: 3.05, Verified: !1
    },
    {
        OrderID: 10262, CustomerID: 'RATTC', EmployeeID: 8, OrderDate: new Date(8379738e5),
        ShipName: 'Rattlesnake Canyon Grocery', ShipCity: 'Albuquerque', ShipAddress: '2817 Milton Dr.',
        ShipRegion: 'NM', ShipPostalCode: '87110', ShipCountry: 'USA', Freight: 48.29, Verified: !0
    }];

Client and server API integration

Step 1: Set up your development environment:

Before you start, make sure you have the following installed:

  • .NET Core SDK
  • Node.js
  • Visual Studio or any other preferred code editor.

Step 2: Create a new ASP.NET Core project:

Open Visual Studio and create an ASP.NET Core Web API project named any of Adaptor(For ex: UrlAdaptor).

Step 3: Add the Microsoft.TypeScript.MSBuild NuGet package to the project:

In Solution Explorer, right-click the project node and select Manage NuGet Packages. In the Browse tab, search for Microsoft.TypeScript.MSBuild and then select Install on the right to install the package.

Step 4: Configure the server:

In Program.cs, call UseDefaultFiles and UseStaticFiles.

var app = builder.Build();

app.UseDefaultFiles();
app.UseStaticFiles();

Comment out the below line in launchSettings.json:

  "https": {
    "commandName": "Project",
    "dotnetRunMessages": true,
    "launchBrowser": true,
    // "launchUrl": "swagger",
    "applicationUrl": "https://localhost:xxxx;http://localhost:xxxx",
    "environmentVariables": {
      "ASPNETCORE_ENVIRONMENT": "Development"
    }
  }

This configuration enables the server to locate and serve the index.html file.

Step 5: Model class creation:

Create a model class named OrdersDetails.cs in the Models folder to represent the order data.

namespace UrlAdaptor.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; }
  }
}

Step 6: API controller creation:

Create a file named OrdersController.cs under the Controllers folder. This controller will handle data communication with the table or Syncfusion controls.

using Microsoft.AspNetCore.Mvc;
using Syncfusion.EJ2.Base;
using UrlAdaptor.Models;

namespace UrlAdaptor.Controllers
{
  [ApiController]
  public class OrdersController
  {
    /// <summary>
    /// Processes the DataManager request to perform paging operations (skip and take) on the ordersdetails data.
    /// </summary>
    /// <param name="DataManagerRequest">Contains the details of the data operation requested, including paging parameters.</param>
    /// <returns>Returns a JSON object and the total record count.</returns>
    [HttpPost]
    [Route("api/[controller]")]
    public object Post([FromBody] DataManagerRequest DataManagerRequest)
    {
      // Retrieve data from the data source (e.g., database).
      IQueryable<OrdersDetails> DataSource = GetOrderData().AsQueryable();

        // Return the paginated data and the total record count.
        return new { result = DataSource, count = totalRecordsCount };
      }

      /// <summary>
      /// Retrieves all order data records from the data source.
      /// </summary>
      /// <returns>Returns a list of all order records.</returns>
      [HttpGet]
      [Route("api/[controller]")]
      public List<OrdersDetails> GetOrderData()
      {
        var data = OrdersDetails.GetAllRecords().ToList();
        return data;
      }
    }
}

The GetOrderData method retrieves sample order data. You can replace it with your custom logic to fetch data from a database or any other source.

The controller logic can be modified based on the selected adaptor configuration such as Web API or OData to ensure seamless integration and optimal performance for each integration scenario.

Step 7: To integrate the table or Syncfusion controls into your EJ2 TypeScript and ASP.NET Core project using Visual Studio, follow these steps:

1: Create a package.json file:

Run the following command in the project root to create a package.json file.

  npm init -y

2: Install webpack and other dependencies:

  npm i -D -E clean-webpack-plugin css-loader html-webpack-plugin mini-css-extract-plugin ts-loader typescript webpack webpack-cli

3: Configure package.json scripts:

Replace the scripts property of package.json file with the following code:

  "scripts": {
    "build": "webpack --mode=development --watch",
    "release": "webpack --mode=production",
    "publish": "npm run release && dotnet publish -c Release"
  },

4: Create wwwroot folder:

Create a folder named wwwroot in the project root directory. This folder will contain static files served by the web server.

5: Create webpack.config.js:

Create a file named webpack.config.js in the project root, with the following code to configure the Webpack compilation process:

  const path = require("path");
  const HtmlWebpackPlugin = require("html-webpack-plugin");
  const { CleanWebpackPlugin } = require("clean-webpack-plugin");
  const MiniCssExtractPlugin = require("mini-css-extract-plugin");

  module.exports = {
      entry: "./src/index.ts",
      output: {
          path: path.resolve(__dirname, "wwwroot"),
          filename: "[name].[chunkhash].js",
          publicPath: "/",
      },
      resolve: {
          extensions: [".js", ".ts"],
      },
      module: {
          rules: [
              {
                  test: /\.ts$/,
                  use: "ts-loader",
              },
              {
                  test: /\.css$/,
                  use: [MiniCssExtractPlugin.loader, "css-loader"],
              },
          ],
      },
      plugins: [
          new CleanWebpackPlugin(),
          new HtmlWebpackPlugin({
              template: "./src/index.html",
          }),
          new MiniCssExtractPlugin({
              filename: "css/[name].[chunkhash].css",
          }),
      ],
  };

6: Create a new directory named src in the project root for the client code.

7: Install Syncfusion packages:

Open your terminal in the project’s root folder and install the required Syncfusion packages using npm:

  npm install @syncfusion/ej2-data --save

8: Implement Adaptor:

Create src/index.html to add the required HTML structure, and create src/index.ts to implement the adapter logic, which has been elaborated based on each adaptor in below topics.

9: Create src/tsconfig.json in the project and add the following content:

  {
    "compilerOptions": {
      "noImplicitAny": true,
      "noEmitOnError": true,
      "removeComments": false,
      "sourceMap": true,
      "target": "es5"
    },
    "exclude": [
      "node_modules",
      "wwwroot"
    ]
  }

11: Install additional packages and build the project:

  npm i @types/node
  npm run build

12: Run the project:

Run the project in Visual Studio.

The wwwroot/index.html file is served at https://localhost:xxxx.

  • In an API service project, add Syncfusion.EJ2.AspNet.Core by opening the NuGet package manager in Visual Studio (Tools → NuGet Package Manager → Manage NuGet Packages for Solution), search and install it.
  • To access DataManagerRequest and QueryableOperation, import Syncfusion.EJ2.Base in OrdersController.cs file.

Url adaptor

The UrlAdaptor is a built-in adaptor in Syncfusion EJ2 TypeScript DataManager module designed to interact with remote web services such as RESTful APIs. It acts as the base class for many other adaptors (like WebApiAdaptor and ODataAdaptor), providing core functionality for HTTP communication.

This adaptor is especially useful when your data resides on a server and you need to perform operations like filtering, sorting, paging, or grouping on that remote data.

The UrlAdaptor expects the server’s response to be a JSON object containing two primary properties:

- result:
An array that contains the actual data records to be processed or displayed.

- count:
A number representing the total count of records available on the server. This is especially important for enabling accurate pagination.

A sample response object should look like this:

UrlAdaptor

To achieve this, follow these steps:

Step 1: Import DataManager, Query, UrlAdaptor modules from @syncfusion/ej2-data.

import { DataManager, Query, UrlAdaptor, ReturnOption } from '@syncfusion/ej2-data';

Step 2: Configure the DataManager:

Assign your API endpoint to the url property and use UrlAdaptor as the adaptor.

const datamanger = new DataManager({
  // Use remote server host and port instead of 'xxxx'.
  url: 'https://localhost:xxxx/api/Orders',
  adaptor: new UrlAdaptor(),
});

Step 3: Apply a query using executeQuery:

Use the executeQuery method with a Query object to retrieve data. This enables you to perform server-side operations such as paging, filtering, or sorting. For example, the following code retrieves the first 10 records from the remote data source in the form of result and count.

datamanger.executeQuery(new Query().take(10)).then((e: ReturnOption) => {
  (<Object[]>e.result.result).forEach((data: Object) => {
    table.appendChild(compiledFunction(data)[0]);
  });
}).catch(error => {
  console.error("Data fetch failed:", error);
});
  • Built-in support is available for handling data operations such as searching, sorting, filtering, aggregate and paging on the server-side. These operations can be handled using methods such as PerformSearching, PerformFiltering, PerformSorting, PerformTake and PerformSkip available in the Syncfusion.EJ2.AspNet.Core package.

This example demonstrates how to use the UrlAdaptor and return the data in result and count format from server end with OrdersController.cs as below:

import { DataManager, Query, UrlAdaptor, ReturnOption } from '@syncfusion/ej2-data';
import { compile } from '@syncfusion/ej2-base';

let template: string = '<tr><td>${OrderID}</td><td>${CustomerID}</td><td>${EmployeeID}</td><td>${ShipCity}</td><td>${ShipCountry}</td></tr>';

let compiledFunction: Function = compile(template);

let table: HTMLElement = (<HTMLElement>document.getElementById('datatable'));

const datamanger = new DataManager({
    // Use remote server host and port instead of 'xxxx'.
    url: 'https://localhost:xxxx/api/Orders',
    adaptor: new UrlAdaptor(),
});

datamanger.executeQuery(new Query().take(10)).then((e: ReturnOption) => {
    (<Object[]>e.result.result).forEach((data: Object) => {
        table.appendChild(compiledFunction(data)[0]);
    });
}).catch(error => {
    console.error("Data fetch failed:", error);
});
<!DOCTYPE html>
<html lang="en">

<head>
  <title>EJ2 Grid</title>
  <meta charset="utf-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <meta name="description" content="Typescript Grid Control" />
  <meta name="author" content="Syncfusion" />
  <script src="https://cdnjs.cloudflare.com/ajax/libs/systemjs/0.19.38/system.js"></script>
  <script src="https://cdn.syncfusion.com/ej2/syncfusion-helper.js" type="text/javascript"></script>
  <style>
    .e-table {
      border: solid 1px #e0e0e0;
      border-collapse: collapse;
      font-family: Roboto;
    }

    .e-table td,
    .e-table th {
      border-style: solid;
      border-width: 1px 0 0;
      border-color: #e0e0e0;
      display: table-cell;
      font-size: 14px;
      line-height: 20px;
      overflow: hidden;
      padding: 8px 21px;
      vertical-align: middle;
      white-space: nowrap;
      width: auto;
    }
  </style>
<script src="https://cdn.syncfusion.com/ej2/syncfusion-helper.js" type ="text/javascript"></script>
</head>

<body>
  <div id='container'>
    <table id="datatable" class='e-table'>
      <thead>
        <tr>
          <th>Order ID</th>
          <th>Customer ID</th>
          <th>Employee ID</th>
          <th>Ship City</th>
          <th>Ship Country</th>
        </tr>
      </thead>
      <tbody></tbody>
    </table>
  </div>
</body>

</html>
using Microsoft.AspNetCore.Mvc;
using Syncfusion.EJ2.Base;
using UrlAdaptor.Models;

namespace UrlAdaptor.Controllers
{
    [ApiController]
    public class OrdersController
    {
        /// <summary>
        /// Processes the DataManager request to perform paging operations (skip and take) on the ordersdetails data.
        /// </summary>
        /// <param name="DataManagerRequest">Contains the details of the data operation requested, including paging parameters.</param>
        /// <returns>Returns a JSON object with the paginated data and the total record count.</returns>
        [HttpPost]
        [Route("api/[controller]")]
        public object Post([FromBody] DataManagerRequest DataManagerRequest)
        {
            // Retrieve data from the data source (e.g., database).
            IQueryable<OrdersDetails> DataSource = GetOrderData().AsQueryable();
            
            // Initialize dataOperations instance.
            QueryableOperation queryableOperation = new QueryableOperation();

            // Get the total count of records.
            int totalRecordsCount = DataSource.Count();

            // Handling paging operation.
            if (DataManagerRequest.Skip != 0)
            {
                DataSource = queryableOperation.PerformSkip(DataSource, DataManagerRequest.Skip);
            }
            if (DataManagerRequest.Take != 0)
            {
                DataSource = queryableOperation.PerformTake(DataSource, DataManagerRequest.Take);
            }

            // Return the paginated data and the total record count.
            return new { result = DataSource, count = totalRecordsCount };
        }

        /// <summary>
        /// Retrieves all order data records from the data source.
        /// </summary>
        /// <returns>Returns a list of all order records.</returns>
        [HttpGet]
        [Route("api/[controller]")]
        public List<OrdersDetails> GetOrderData()
        {
            var data = OrdersDetails.GetAllRecords().ToList();
            return data;
        }
    }
}
namespace UrlAdaptor.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 < 5; 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; }
    }
}
var builder = WebApplication.CreateBuilder(args);

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

// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle.
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

var app = builder.Build();

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}

app.UseDefaultFiles();
app.UseStaticFiles();

app.UseHttpsRedirection();

app.UseAuthorization();

app.MapControllers();

app.Run();

OData adaptor

The ODataAdaptor in Syncfusion EJ2 TypeScript DataManager facilitates seamless integration with OData services, which are standardized RESTful APIs designed for querying and manipulating data over HTTP. This adaptor streamlines operations such as querying, filtering, sorting, and paging data from OData endpoints, making it especially suitable for enterprise applications that require standardized and interoperable data access.

This adaptor is especially useful when:

  • Connecting to an OData-compliant REST API.

  • Utilizing built-in support for OData query options such as $filter, $orderby, $top, and $skip.

  • Performing efficient server-side data operations, including paging, sorting, and filtering.

The ODataAdaptor automatically translates DataManager query operations into OData-compliant HTTP requests. It manages response parsing and maps the server data into the format expected by Syncfusion components, enabling smooth client-server communication.

The ODataAdaptor expects the server’s response to be a JSON object containing two primary properties:

- result:
An array that contains the actual data records to be processed or displayed.

- count:
A number representing the total count of records available on the server. This is especially important for enabling accurate pagination.

A sample response object should look like this:

OData adaptor

To retrieve data from an OData service using the DataManager, follow these steps:

Step 1: Import DataManager, Query, ODataAdaptor modules from @syncfusion/ej2-data, and the compile module from @syncfusion/ej2-base.

import { DataManager, Query, ODataAdaptor, ReturnOption } from '@syncfusion/ej2-data';
import { compile } from '@syncfusion/ej2-base';

Step 2: Configure the DataManager:

Assign your API endpoint to the url property and use ODataAdaptor as the adaptor.

const datamanger = new DataManager({
  // Use remote server host and port instead of 'xxxx'.
  url: 'https://localhost:xxxx/api/Orders',
  adaptor: new ODataAdaptor(),
});

Step 3: Apply a query using executeQuery:

Use the executeQuery method with a Query object to retrieve data. This enables you to perform server-side operations such as paging, filtering, or sorting. For example, the following code retrieves the first 8 records from the remote data source in the form of result and count.

datamanger.executeQuery(new Query().take(8)).then((e: ReturnOption) => {
  (<Object[]>e.result.result).forEach((data: Object) => {
    table.appendChild(compiledFunction(data)[0]);
  });
});

This example demonstrates how to use the ODataAdaptor and return the data in result and count format from server end with OrdersController.cs as below:

import { DataManager, Query, ODataAdaptor, ReturnOption } from '@syncfusion/ej2-data';
import { compile } from '@syncfusion/ej2-base';

let template: string = '<tr><td>${OrderID}</td><td>${CustomerID}</td><td>${EmployeeID}</td><td>${ShipCity}</td><td>${ShipCountry}</td></tr>';

let compiledFunction: Function = compile(template);

let table: HTMLElement = (<HTMLElement>document.getElementById('datatable'));

const datamanger = new DataManager({
    // Use remote server host and port instead of 'xxxx'.  
    url: "https://localhost:xxxx/api/Orders", 
    adaptor: new ODataAdaptor() 
});

datamanger.executeQuery(new Query().take(8)).then((e: ReturnOption) => {

    (<Object[]>e.result.result).forEach((data: Object) => {
        table.appendChild(compiledFunction(data)[0]);
    });
});
<!DOCTYPE html>
<html lang="en">

<head>
  <title>EJ2 Grid</title>
  <meta charset="utf-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <meta name="description" content="Typescript Grid Control" />
  <meta name="author" content="Syncfusion" />
  <script src="https://cdnjs.cloudflare.com/ajax/libs/systemjs/0.19.38/system.js"></script>
  <style>
    .e-table {
      border: solid 1px #e0e0e0;
      border-collapse: collapse;
      font-family: Roboto;
    }

    .e-table td,
    .e-table th {
      border-style: solid;
      border-width: 1px 0 0;
      border-color: #e0e0e0;
      display: table-cell;
      font-size: 14px;
      line-height: 20px;
      overflow: hidden;
      padding: 8px 21px;
      vertical-align: middle;
      white-space: nowrap;
      width: auto;
    }
  </style>
<script src="https://cdn.syncfusion.com/ej2/syncfusion-helper.js" type ="text/javascript"></script>
</head>

<body>
  <div id='container'>
    <table id='datatable' class='e-table'>
      <thead>
        <tr>
          <th>Order ID</th>
          <th>Customer ID</th>
          <th>Employee ID</th>
          <th>Ship City</th>
          <th>Ship Country</th>
        </tr>
      </thead>
      <tbody>
      </tbody>
    </table>
  </div>
</body>

</html>
using Microsoft.AspNetCore.Mvc;
using ODataAdaptor.Models;

namespace ODataAdaptor.Controllers
{
    [Route("api/[controller]")]

    [ApiController]
    public class OrdersController : ControllerBase
    {
        // GET: api/Orders.
        [HttpGet]
        // Action to retrieve orders.
        public object Get()
        {
            var queryString = Request.Query;
            var data = OrdersDetails.GetAllRecords().ToList();

            int totalRecordsCount = data.Count;

            //Perform paging operation.
            int skip = Convert.ToInt32(queryString["$skip"]);
            int take = Convert.ToInt32(queryString["$top"]);
            if (take != 0)
            {
                data = data.Skip(skip).Take(take).ToList();
            }

            // Return the paginated data and the total record count.
            return new { result = data, count = totalRecordsCount };
        }

    }
}
namespace ODataAdaptor.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; }
    }
}
var builder = WebApplication.CreateBuilder(args);

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

// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle.
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

var app = builder.Build();

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}

app.UseDefaultFiles();
app.UseStaticFiles();

app.UseHttpsRedirection();

app.UseAuthorization();

app.MapControllers();

app.Run();

By default, ODataAdaptor is used by DataManager.

ODataV4 adaptor

The ODataV4Adaptor is a specialized adaptor in Syncfusion EJ2 TypeScript DataManager module, designed for interacting with OData v4 services. OData (Open Data Protocol) is a standardized protocol for creating and consuming RESTful APIs. The ODataV4 protocol is an improved version of previous OData protocols, offering enhanced capabilities and better support for modern web standards.

Syncfusion’s ODataV4Adaptor allows the DataManager to communicate with OData V4-compliant services, performing operations like filtering, sorting, paging, and grouping directly via OData query options in the URL. These operations are translated into OData query options and appended to the request URL, allowing the server to process them efficiently. This adaptor is particularly useful when integrating enterprise-grade OData services, such as those provided by Microsoft Dynamics 365, Azure, or SAP.

If you’re building a reporting dashboard that connects to a Microsoft Dynamics 365 service (which exposes data via OData V4), you can use the ODataV4Adaptor to retrieve and manipulate data like customer orders, sales reports, or invoices directly from the OData-compliant API.

For more information on OData v4 protocol, refer to the official OData V4 documentation.

To achieve this, follow these steps:

Step 1: Import DataManager, Query, ODataV4Adaptor modules from @syncfusion/ej2-data, and the compile module from @syncfusion/ej2-base.

import { DataManager, Query, ReturnOption, ODataV4Adaptor } from '@syncfusion/ej2-data';
import { compile } from '@syncfusion/ej2-base';

Step 2: Configure the DataManager:

Assign your API endpoint to the url property and use ODataV4Adaptor as the adaptor.

const datamanger = new DataManager({
  // Use remote server host and port instead of 'xxxx'.
  url: "https://localhost:xxxx/odata/Orders/", 
  adaptor: new ODataV4Adaptor 
});

Step 3: Apply a query using executeQuery:

Use the executeQuery method with a Query object to retrieve data. This enables you to perform server-side operations such as paging, filtering, or sorting. For example, the following code retrieves the first 8 records from the remote data source.

datamanger.executeQuery(new Query().take(8)).then((e: ReturnOption) => {
  (<Object[]>e.result).forEach((data: Object) => {
    table.appendChild(compiledFunction(data)[0]);
  });
});

To construct the entity data model for your ODataV4 service, utilize the ODataConventionModelBuilder to define the model’s structure. Start by creating an instance of the ODataConventionModelBuilder, then register the entity set Orders using the EntitySet<T> method, where OrdersDetails represents the CLR type containing order details. Once the entity data model is built, you need to register the ODataV4 services in your ASP.NET Core application. Please refer to the Program.cs file below.

This example demonstrates how to use the ODataV4Adaptor and return data from server side end with OrdersController.cs as below:

import { DataManager, Query, ReturnOption, ODataV4Adaptor } from '@syncfusion/ej2-data';
import { compile } from '@syncfusion/ej2-base';

let template: string = '<tr><td>${OrderID}</td><td>${CustomerID}</td><td>${EmployeeID}</td><td>${ShipCountry}</td></tr>';

let compiledFunction: Function = compile(template);

let table: HTMLElement = (<HTMLElement>document.getElementById('datatable'));

const datamanger = new DataManager({ 
    // Use remote server host and port instead of 'xxxx'.
    url: "https://localhost:xxxx/odata/Orders/", 
    adaptor: new ODataV4Adaptor 
});

datamanger.executeQuery(new Query().take(8)).then((e: ReturnOption) => {

    (<Object[]>e.result).forEach((data: Object) => {
        table.appendChild(compiledFunction(data)[0]);
    });
});
<!DOCTYPE html>
<html lang="en">

<head>
  <title>EJ2 Grid</title>
  <meta charset="utf-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <meta name="description" content="Typescript Grid Control" />
  <meta name="author" content="Syncfusion" />
  <script src="https://cdnjs.cloudflare.com/ajax/libs/systemjs/0.19.38/system.js"></script>
  <style>
    .e-table {
      border: solid 1px #e0e0e0;
      border-collapse: collapse;
      font-family: Roboto;
    }

    .e-table td,
    .e-table th {
      border-style: solid;
      border-width: 1px 0 0;
      border-color: #e0e0e0;
      display: table-cell;
      font-size: 14px;
      line-height: 20px;
      overflow: hidden;
      padding: 8px 21px;
      vertical-align: middle;
      white-space: nowrap;
      width: auto;
    }
  </style>
<script src="https://cdn.syncfusion.com/ej2/syncfusion-helper.js" type ="text/javascript"></script>
</head>

<body>
  <div id='container'>
    <table id='datatable' class='e-table'>
      <thead>
        <tr>
          <th>Order ID</th>
          <th>Customer ID</th>
          <th>Employee ID</th>
          <th>ShipCountry</th>
        </tr>
      </thead>
      <tbody>
      </tbody>
    </table>
  </div>
</body>

</html>
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.OData.Query;
using Microsoft.AspNetCore.OData.Routing.Controllers;
using ODataV4Adaptor.Models;

namespace OdataV4Adaptor.Controllers
{
    public class OrdersController : Controller
    {
        /// <summary>
        /// Retrieves all orders.
        /// </summary>
        /// <returns>The collection of orders.</returns>
        [HttpGet]
        [EnableQuery]
        public IActionResult Get()
        {
            var data = OrdersDetails.GetAllRecords().AsQueryable();
            return Ok(data);
        }
    }
}
using System.ComponentModel.DataAnnotations;

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

        }
        public OrdersDetails(
        int OrderID, string CustomerId, int EmployeeId, string ShipCountry)
        {
            this.OrderID = OrderID;
            this.CustomerID = CustomerId;
            this.EmployeeID = EmployeeId;
            this.ShipCountry = ShipCountry;
        }

        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, "Denmark"));
                    order.Add(new OrdersDetails(code + 2, "ANATR", i + 2, "Brazil"));
                    order.Add(new OrdersDetails(code + 3, "ANTON", i + 1, "Germany"));
                    order.Add(new OrdersDetails(code + 4, "BLONP", i + 3, "Austria"));
                    order.Add(new OrdersDetails(code + 5, "BOLID", i + 4, "Switzerland"));
                    code += 5;
                }
            }
            return order;
        }
        [Key]
        public int? OrderID { get; set; }
        public string? CustomerID { get; set; }
        public int? EmployeeID { get; set; }
        public string? ShipCountry { get; set; }
    }
}
using Microsoft.AspNetCore.OData;
using Microsoft.OData.ModelBuilder;
using ODataV4Adaptor.Models;

var builder = WebApplication.CreateBuilder(args);

// Create an ODataConventionModelBuilder to build the OData model.
var modelBuilder = new ODataConventionModelBuilder();
// Register the "Orders" entity set with the OData model builder
modelBuilder.EntitySet<OrdersDetails>("Orders");

var recordCount = OrdersDetails.GetAllRecords().Count;

// Add controllers with OData support to the service collection.
builder.Services.AddControllers().AddOData(
    options => options
    .Count()
    .OrderBy()
    .Filter()
    .SetMaxTop(recordCount)
    .AddRouteComponents(
        "odata",
        modelBuilder.GetEdmModel()));


// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle.
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

builder.Services.AddCors(options =>
{
    options.AddDefaultPolicy(builder =>
    {
        builder.AllowAnyOrigin().AllowAnyMethod().AllowAnyHeader();
    });
});
var app = builder.Build();
app.UseCors();

app.UseDefaultFiles();
app.UseStaticFiles();

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}

app.UseHttpsRedirection();

app.UseAuthorization();

app.MapControllers();

app.MapFallbackToFile("/index.html");

app.Run();

Web API adaptor

The WebApiAdaptor is a specialized adaptor in Syncfusion EJ2 TypeScript DataManager module designed to interact with Web APIs, particularly those that support OData query options. Since the WebApiAdaptor is extended from the ODataAdaptor, it requires that the remote service endpoint understands and can process OData-formatted queries sent along with the request.

Since WebApiAdaptor inherits from ODataAdaptor, it expects the API endpoint to understand and process OData-formatted queries such as $top, $, $filter, sent along with the request.

For example, if you are fetching employee records from a Web API that accepts OData queries (like $top, $skip, $filter, etc.), the WebApiAdaptor automatically formats and sends these queries and parses the response appropriately.

To enable OData query options for your Web API, you need to ensure that the Web API is configured to understand OData requests. For more information on how to implement OData in a Web API, refer to the documentation.

The WebApiAdaptor expects the server’s response to be a JSON object containing two primary properties:

- Items:
An array that contains the actual data records to be displayed or processed.

- Count:
A number representing the total count of records available on the server. This is especially important for enabling accurate pagination.

A sample response object should look like this:

Web API adaptor

To achieve this, follow these steps:

Step 1: Import DataManager, Query, WebApiAdaptor modules from @syncfusion/ej2-data, and the compile module from @syncfusion/ej2-base.

import { DataManager, Query, WebApiAdaptor, ReturnOption } from '@syncfusion/ej2-data';
import { compile } from '@syncfusion/ej2-base';

Step 2: Configure the DataManager:

Assign your API endpoint to the url property and use WebApiAdaptor as the adaptor.

const datamanger = new DataManager({
  // Use remote server host and port instead of 'xxxx'.
  url: 'https://localhost:xxxx/api/Orders',
  adaptor: new WebApiAdaptor(),
});

Step 3: Apply a query using executeQuery:

Use the executeQuery method with a Query object to retrieve data. This enables you to perform server-side operations such as paging, filtering, or sorting. For example, the following code retrieves the first 8 records from the remote data source in the form of Items and Count.

datamanger.executeQuery(new Query().take(8)).then((e: ReturnOption) => {
  ((e as any).actual.Items.).forEach((data: Object) => {
    table.appendChild(compiledFunction(data)[0]);
  });
});

This example demonstrates how to use the WebApiAdaptor and return the data in items and count format from server end with OrdersController.cs as below:

import { DataManager, Query, WebApiAdaptor, ReturnOption } from '@syncfusion/ej2-data';
import { compile } from '@syncfusion/ej2-base';

let template: string = '<tr><td>${OrderID}</td><td>${CustomerID}</td><td>${EmployeeID}</td><td>${ShipCity}</td><td>${ShipCountry}</td></tr>';

let compiledFunction: Function = compile(template);

let table: HTMLElement = (<HTMLElement>document.getElementById('datatable'));

const datamanger = new DataManager({ 
    // Use remote server host and port instead of 'xxxx'.
    url: "https://localhost:xxxx/api/Orders", 
    adaptor: new WebApiAdaptor() 
});

datamanger.executeQuery(new Query().take(8)).then((e: ReturnOption) => {

    e.actual.Items.forEach((data: Object) => {
        table.appendChild(compiledFunction(data)[0]);
    });
});
<!DOCTYPE html>
<html lang="en">

<head>
  <title>EJ2 Grid</title>
  <meta charset="utf-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <meta name="description" content="Typescript Grid Control" />
  <meta name="author" content="Syncfusion" />
  <script src="https://cdnjs.cloudflare.com/ajax/libs/systemjs/0.19.38/system.js"></script>
  <style>
    .e-table {
      border: solid 1px #e0e0e0;
      border-collapse: collapse;
      font-family: Roboto;
    }

    .e-table td,
    .e-table th {
      border-style: solid;
      border-width: 1px 0 0;
      border-color: #e0e0e0;
      display: table-cell;
      font-size: 14px;
      line-height: 20px;
      overflow: hidden;
      padding: 8px 21px;
      vertical-align: middle;
      white-space: nowrap;
      width: auto;
    }
  </style>
<script src="https://cdn.syncfusion.com/ej2/syncfusion-helper.js" type ="text/javascript"></script>
</head>

<body>
  <div id='container'>
    <table id='datatable' class='e-table'>
      <thead>
        <tr>
          <th>Order ID</th>
          <th>Customer ID</th>
          <th>Employee ID</th>
          <th>Ship City</th>
          <th>Ship Country</th>
        </tr>
      </thead>
      <tbody>
      </tbody>
    </table>
  </div>
</body>

</html>
using Microsoft.AspNetCore.Mvc;
using WebApiAdaptor.Models;

namespace WebApiAdaptor.Controllers
{
    [Route("api/[controller]")]

    [ApiController]
    public class OrdersController : ControllerBase
    {
        // GET: api/Orders.
        [HttpGet]
        // Action to retrieve orders.
        public object Get()
        {
            var queryString = Request.Query;
            var data = OrdersDetails.GetAllRecords().ToList();
            int totalRecordsCount = data.Count;

            //Perform paging operation.
            int skip = Convert.ToInt32(queryString["$skip"]);
            int take = Convert.ToInt32(queryString["$top"]);
            if (take != 0)
            {
                data = data.Skip(skip).Take(take).ToList();
            }

            // Return the paginated data and the total record count.
            return new { Items = data, Count = totalRecordsCount };
        }

    }
}
namespace WebApiAdaptor.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; }
    }
}
var builder = WebApplication.CreateBuilder(args);

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

// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle.
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

var app = builder.Build();

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}

app.UseDefaultFiles();
app.UseStaticFiles();

app.UseHttpsRedirection();

app.UseAuthorization();

app.MapControllers();

app.Run();

WebMethod Adaptor

The WebMethodAdaptor is a powerful and flexible adaptor provided by Syncfusion EJ2 TypeScript DataManager module, specifically designed to interact with remote services or server-side methods that accept data via HTTP POST requests. Unlike adaptors that communicate with standard REST or OData services, the WebMethodAdaptor enables seamless data binding from custom server-side logic such as controller actions, web services, or business-layer functions.

This adaptor is ideal for applications where server-side methods are responsible for data retrieval and business logic processing. It ensures that data operations such as paging, sorting, filtering, and grouping are handled on the server and returned to the client in a structured format.

The WebMethodAdaptor expects the server’s response to be a JSON object containing two primary properties:

- result:
An array that contains the actual data records to be displayed or processed.

- count:
A number representing the total count of records available on the server. This is especially important for enabling accurate pagination.

A sample response object should look like this:

WebMethod Adaptor

To achieve this, follow these steps:

Step 1: Import DataManager, Query, WebMethodAdaptor modules from @syncfusion/ej2-data, and the compile module from @syncfusion/ej2-base.

import { DataManager, Query, WebMethodAdaptor, ReturnOption } from '@syncfusion/ej2-data';
import { compile } from '@syncfusion/ej2-base';

Step 2: Configure the DataManager:

Assign your API endpoint to the url property and use WebMethodAdaptor as the adaptor.

const datamanger = new DataManager({
  // Use remote server host and port instead of 'xxxx'.
  url: "https://localhost:xxxx/api/Orders",
  adaptor: new WebMethodAdaptor()
});

Step 3: Apply a query using executeQuery:

Use the executeQuery method with a Query object to retrieve data. This enables you to perform server-side operations such as paging, filtering, or sorting. For example, the following code retrieves the first 8 records from the remote data source in the form of result and count.

datamanger.executeQuery(new Query().take(8)).then((e: ReturnOption) => {
  (<Object[]>e.result.result).forEach((data: Object) => {
    table.appendChild(compiledFunction(data)[0]);
  });
})

The server-side method must accept a parameter named value to receive the request payload from the client.

This example demonstrates how to use the WebMethodAdaptor and return the data in result and count format from server end with OrdersController.cs as below:

import { DataManager, Query, WebMethodAdaptor, ReturnOption } from '@syncfusion/ej2-data';
import { compile } from '@syncfusion/ej2-base';

let template: string = '<tr><td>${OrderID}</td><td>${CustomerID}</td><td>${EmployeeID}</td><td>${ShipCity}</td><td>${ShipCountry}</td></tr>';

let compiledFunction: Function = compile(template);

let table: HTMLElement = (<HTMLElement>document.getElementById('datatable'));

const datamanger = new DataManager({
    // Use remote server host and port instead of 'xxxx'.
    url: "https://localhost:xxxx/api/Orders",
    adaptor: new WebMethodAdaptor()
});

datamanger.executeQuery(new Query().take(8)).then((e: ReturnOption) => {
    (<Object[]>e.result.result).forEach((data: Object) => {
        table.appendChild(compiledFunction(data)[0]);
    });
});
<!DOCTYPE html>
<html lang="en">

<head>
  <title>EJ2 Grid</title>
  <meta charset="utf-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <meta name="description" content="Typescript Grid Control" />
  <meta name="author" content="Syncfusion" />
  <script src="https://cdnjs.cloudflare.com/ajax/libs/systemjs/0.19.38/system.js"></script>
  <style>
    .e-table {
      border: solid 1px #e0e0e0;
      border-collapse: collapse;
      font-family: Roboto;
    }

    .e-table td,
    .e-table th {
      border-style: solid;
      border-width: 1px 0 0;
      border-color: #e0e0e0;
      display: table-cell;
      font-size: 14px;
      line-height: 20px;
      overflow: hidden;
      padding: 8px 21px;
      vertical-align: middle;
      white-space: nowrap;
      width: auto;
    }
  </style>
<script src="https://cdn.syncfusion.com/ej2/syncfusion-helper.js" type ="text/javascript"></script>
</head>

<body>
  <div id='container'>
    <table id='datatable' class='e-table'>
      <thead>
        <tr>
          <th>Order ID</th>
          <th>Customer ID</th>
          <th>Employee ID</th>
          <th>Ship City</th>
          <th>Ship Country</th>
        </tr>
      </thead>
      <tbody>
      </tbody>
    </table>
  </div>
</body>

</html>
using Microsoft.AspNetCore.Mvc;
using Syncfusion.EJ2.Base;
using WebMethodAdaptor.Models;

namespace WebMethodAdaptor.Controllers
{
    public class OrdersController : Controller
    {
        /// <summary>
        /// Retrieves all order data records from the data source.
        /// </summary>
        /// <returns>Returns a list of all order records.</returns>
        [HttpGet]
        [Route("api/[controller]")]
        public List<OrdersDetails> GetOrderData()
        {
            // Retrieve all records and convert to list.
            var data = OrdersDetails.GetAllRecords().ToList();
            return data;
        }

        /// <summary>
        /// Processes the DataManager request to perform paging operations (skip and take) on the ordersdetails data.
        /// </summary>
        /// <param name="DataManagerRequest">Contains the details of the data operation requested, including paging parameters such as skip and take.</param>
        /// <returns>Returns an object containing the paginated result set and the total record count.</returns>

        [HttpPost]
        [Route("api/[controller]")]
        public object Post([FromBody] DataManager DataManagerRequest)
        {
            // Retrieve data source and convert to queryable.
            IQueryable<OrdersDetails> DataSource = GetOrderData().AsQueryable();

            // Initialize dataOperations instance.
            QueryableOperation queryableOperation = new QueryableOperation();

            // Retrieve data manager value.
            DataManagerRequest dataManagerParams = DataManagerRequest.Value;


            // Get total record count after applying filters.
            int totalRecordsCount = DataSource.Count();

            // Perform skip operation if skip value is provided.
            if (dataManagerParams.Skip != 0)
            {
                DataSource = queryableOperation.PerformSkip(DataSource, dataManagerParams.Skip);
            }

            // Perform take operation if take value is provided.
            if (dataManagerParams.Take != 0)
            {
                DataSource = queryableOperation.PerformTake(DataSource, dataManagerParams.Take);
            }

            // Return the paginated data and the total record count.
            return new { result = DataSource, count = totalRecordsCount };
        }
    }

    public class DataManager
    {
        public required DataManagerRequest Value { get; set; }
    }
}
namespace WebMethodAdaptor.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; }
    }
}
var builder = WebApplication.CreateBuilder(args);

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

// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle.
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

var app = builder.Build();

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}

app.UseDefaultFiles();
app.UseStaticFiles();

app.UseHttpsRedirection();

app.UseAuthorization();

app.MapControllers();

app.Run();

RemoteSaveAdaptor

The RemoteSaveAdaptor is a specialized adaptor in Syncfusion EJ2 TypeScript DataManager module designed to perform actions such as sorting, filtering, searching and paging primarily on the client-side while handling CRUD operations(Create, Read, Update, and Delete), on the server-side for data persistence. This approach optimizes your experience by minimizing unnecessary server interactions.

For example, if you are building a dashboard that displays order data stored remotely and users need to add, edit, or delete records with changes saved back to the server, the RemoteSaveAdaptor helps manage these interactions effectively.

To achieve this, follow these steps:

Step 1: Import DataManager, Query, RemoteSaveAdaptor modules from @syncfusion/ej2-data, and the compile module from @syncfusion/ej2-base.

import { DataManager, Query, RemoteSaveAdaptor, ReturnOption } from '@syncfusion/ej2-data';
import { compile } from '@syncfusion/ej2-base';

Step 2: Configure the DataManager:

Assign your API endpoint to the url property and use RemoteSaveAdaptor as the adaptor.

const datamanger = new DataManager({
  // Use remote server host and port instead of 'xxxx'.
  url: "https://localhost:xxxx/api/Orders",
  adaptor: new RemoteSaveAdaptor()
});

Step 3: Apply a query using executeQuery:

Use the executeQuery method with a Query object to retrieve data. This enables you to perform server-side operations such as paging, filtering, or sorting. For example, the following code retrieves the first 8 records from the remote data source.

datamanger.executeQuery(new Query().take(8)).then((e: ReturnOption) => {
  (<Object[]>e.result).forEach((data: Object) => {
    table.appendChild(compiledFunction(data)[0]);
  });
})

This example demonstrates how to use the RemoteSaveAdaptor and return data from server side end with OrdersController.cs as below:

import { DataManager, Query, RemoteSaveAdaptor, ReturnOption } from '@syncfusion/ej2-data';
import { compile } from '@syncfusion/ej2-base';

let dataManager: DataManager;
let template: string = '<tr><td>${OrderID}</td><td>${CustomerID}</td><td>${EmployeeID}</td><td>${ShipCity}</td><td>${ShipCountry}</td></tr>';
let compiledFunction: Function = compile(template);
let table: HTMLElement = document.getElementById('datatable')!;

function load(): void {
    fetch('https://localhost:xxxx/api/Orders') // Use remote server host and port instead of 'xxxx'.
        .then(response => {
            if (!response.ok) {
                throw new Error("Unable to fetch data. Please check the URL or network connectivity.");
            }
            return response.json();
        })
        .then(jsonValue => {
            dataManager = new DataManager({
                json: jsonValue,
                adaptor: new RemoteSaveAdaptor()
            });

            dataManager.executeQuery(new Query().take(8)).then((e: ReturnOption) => {
                const data = e.result as Object[]
                data.forEach((item) => {
                    table.appendChild(compiledFunction(item)[0]);
                });
            })
        })
        .catch(error => {
            console.error("Data fetch failed:", error);
        });
}

load();
<!DOCTYPE html>
<html lang="en">

<head>
  <title>EJ2 Grid</title>
  <meta charset="utf-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <meta name="description" content="Typescript Grid Control" />
  <meta name="author" content="Syncfusion" />
  <script src="https://cdnjs.cloudflare.com/ajax/libs/systemjs/0.19.38/system.js"></script>
  <style>
    .e-table {
      border: solid 1px #e0e0e0;
      border-collapse: collapse;
      font-family: Roboto;
    }

    .e-table td,
    .e-table th {
      border-style: solid;
      border-width: 1px 0 0;
      border-color: #e0e0e0;
      display: table-cell;
      font-size: 14px;
      line-height: 20px;
      overflow: hidden;
      padding: 8px 21px;
      vertical-align: middle;
      white-space: nowrap;
      width: auto;
    }
  </style>
<script src="https://cdn.syncfusion.com/ej2/syncfusion-helper.js" type ="text/javascript"></script>
</head>

<body style="margin-top: 100px;">
  <div id='container'>
    <table id='datatable' class='e-table'>
      <thead>
        <tr>
          <th>Order ID</th>
          <th>Customer ID</th>
          <th>Employee ID</th>
          <th>ShipCity</th>
          <th>ShipCountry</th>
        </tr>
      </thead>
      <tbody>
      </tbody>
    </table>
  </div>
</body>

</html>
using Microsoft.AspNetCore.Mvc;
using RemoteSaveAdaptor.Models;

namespace RemoteSaveAdaptor.Controllers
{
    [ApiController]
    public class OrdersController : Controller
    {
        /// <summary>
        /// Retrieves all order data records from the data source.
        /// </summary>
        /// <returns>Returns a list of all order records.</returns>
        [HttpGet]
        [Route("api/[controller]")]
        public List<OrdersDetails> GetOrderData()
        {
            var data = OrdersDetails.GetAllRecords().ToList();
            return data;
        }
    }
}
using System.ComponentModel.DataAnnotations;

namespace RemoteSaveAdaptor.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 <= 2000; 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;
        }
        [Key]
        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; }
    }
}
var builder = WebApplication.CreateBuilder(args);

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

// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle.
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

var app = builder.Build();

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}

app.UseDefaultFiles();
app.UseStaticFiles();

app.UseHttpsRedirection();

app.UseAuthorization();

app.MapControllers();

app.Run();

GraphQL Adaptor

The GraphQLAdaptor enables seamless data retrieval and manipulation from a GraphQL server. It allows you to precisely fetch the data you need, reducing over-fetching and under-fetching of data. GraphQL provides a flexible and expressive syntax for querying, enabling clients to request only the specific data they require. It allows efficient data retrieval with support for various operations like CRUD (Create, Read, Update, and Delete), paging, sorting, and filtering.

The adaptor extends the UrlAdaptor, which means it expects the server to return responses in a specific JSON format for proper data processing.

To achieve this, follow these steps:

Step 1: Create service for GraphQL:

1: Create a new folder named GraphQLServer specifically for your GraphQL server.

2: Install the graph pack npm package. Open your terminal and navigate to the server folder, then run:

  npm i graphpack

3: To utilize Syncfusion’s ej2-data package, you need to include it as a dependency in your project’s package.json file. Here’s how you can mention it in the configuration:

    {
      "name": "graphql-server",
      "version": "1.0.0",
      "description": "",
      "scripts": {
        "dev": "graphpack --port 4205",
        "build": "graphpack build"
      },
      "devDependencies": {
        "graphpack": "^1.0.9"
      },
      "dependencies": {
        "@syncfusion/ej2-data": "24.1.41"
      }
    }

4: Create a schema file (e.g., src/schema.graphql) in your GraphQL server project and write the schema definition.

  • Define Types: Create types representing the structure of data retrieved from GraphQL queries. Since the GraphQLAdaptor in Syncfusion extends from UrlAdaptor, it expects a JSON response with specific properties:

    result: An array containing the data entities.
    count: The total number of records.
    aggregates: Contains total aggregate data(optional).

  • Define Queries: Define queries to retrieve data from the server. Whether using a normal table or Syncfusion controls, you can define a query to fetch orders, accepting parameters such as a DataManager for advanced data operations.

  • Define DataManager Input: Define input types for DataManager, specifying parameters for sorting, filtering, paging, aggregates, etc., to be used in queries. The query parameters will be send in a string format which contains the below details.

Parameters Description
requiresCounts If it is true then the total count of records will be included in response.
skip Holds the number of records to skip.
take Holds the number of records to take.
sorted Contains details about current sorted column and its direction.
where Contains details about current filter column name and its constraints.
group Contains details about current grouped column names.
search Contains details about current search data.
aggregates Contains details about aggregate data.
#Grid Sort direction.

input Sort {
    name: String!
    direction: String!
} 

#Grid aggregates type

input Aggregate {
    field: String! 
    type: String!
}

#Syncfusion DataManager query params.

input DataManager {
    skip: Int
    take: Int
    sorted: [Sort]
    group: [String]
    table: String
    select: [String]
    where: String
    search: String
    requiresCounts: Boolean,
    aggregates: [Aggregate],
    params: String
}

# Grid field names.
input OrderInput {
  OrderID: Int!
  CustomerID: String
  EmployeeID: Int
  ShipCity: String
  ShipCountry: String
}

type Order {
  OrderID: Int!
  CustomerID: String
  EmployeeID: Int
  ShipCity: String
  ShipCountry: String
}

# need to return type as 'result (i.e, current pager data)' and count (i.e., total number of records in your database).
type ReturnType {
  result: [Order]
  count: Int
  aggregates: String
}

type Query {
  getOrders(datamanager: DataManager): ReturnType 
}

5: Create a resolver file (e.g., src/resolvers.js) to handle GraphQL queries and fetch data from your database or data source. Resolver functions are responsible for processing incoming GraphQL requests and returning the appropriate data in the expected result and count format. To efficiently handle filtering, sorting, searching, and paging, you can use the utilities from the @syncfusion/ej2-data package such as DataUtil, Query, and DataManager.

DataUtil.serverTimezoneOffset = 0;

const resolvers = {
  Query: {
    getOrders: (parent, { datamanager }, context, info) => {
      console.log(datamanager);
      let orders = [...OrderData];
      let query = new Query();

      // Perform filtering.
    if (datamanager.where) {
      console.log("filtering", datamanager.where);
      const filters = JSON.parse(datamanager.where);
      filters.forEach(predicate => {
        const { field, operator, value } = predicate;
        query = query.where(field, operator, value);
      });
    }
    
    // Perform searching.
    if (datamanager.search) {
      console.log("Searching", datamanager);
      const { fields, key } = JSON.parse(datamanager.search)[0];
      query = query.search(key, fields);
    }

      // Perform sorting.
    if (datamanager.sorted) {
      console.log("sorting", datamanager);
      for (let i = 0; i < datamanager.sorted.length; i++) {
        const { name, direction } = datamanager.sorted[i];
        query = query.sortBy(name, direction);
      }
    }

      orders = new DataManager(orders).executeLocal(query);
      var count = orders.length;

      // Perform paging.
      if (datamanager.skip && datamanager.take) {
        console.log("paging", datamanager);
        const pageSkip = datamanager.skip / datamanager.take + 1;
        const pageTake = datamanager.take;
        query.page(pageSkip, pageTake);
      } else if (datamanager.skip === 0 && datamanager.take) {
        query.page(1, datamanager.take);
      }

      const currentResult = new DataManager(orders).executeLocal(query);
      return { result: currentResult, count: count };
    },
  }
};

export default resolvers;

6: Create a simple data file src/db.js that exports your data array.

export let OrderData = [
    {
        OrderID: 10248, CustomerID: 'VINET', EmployeeID: 5, OrderDate: new Date("07 12 1996 02:00:23"),
        ShipName: 'Vins et alcools Chevalier', ShipCity: 'Reims', ShipAddress: '59 rue de l Abbaye',
        ShipRegion: 'CJ', ShipPostalCode: '51100', ShipCountry: 'France', Freight: 32.38, Verified: !0
    },
    {
        OrderID: 10249, CustomerID: 'TOMSP', EmployeeID: 6, OrderDate: new Date("07 12 1996 00:03:23"),
        ShipName: 'Toms Spezialitäten', ShipCity: 'Münster', ShipAddress: 'Luisenstr. 48',
        ShipRegion: 'CJ', ShipPostalCode: '44087', ShipCountry: 'Germany', Freight: 11.61, Verified: !1
    },
    {
        OrderID: 10250, CustomerID: 'HANAR', EmployeeID: 4, OrderDate: new Date("07 12 1996 00:00:23"),
        ShipName: 'Hanari Carnes', ShipCity: 'Rio de Janeiro', ShipAddress: 'Rua do Paço, 67',
        ShipRegion: 'RJ', ShipPostalCode: '05454-876', ShipCountry: 'Brazil', Freight: 65.83, Verified: !0
    },
    {
        OrderID: 10251, CustomerID: 'VICTE', EmployeeID: 3, OrderDate: new Date(8367642e5),
        ShipName: 'Victuailles en stock', ShipCity: 'Lyon', ShipAddress: '2, rue du Commerce',
        ShipRegion: 'CJ', ShipPostalCode: '69004', ShipCountry: 'France', Freight: 41.34, Verified: !0
    },
    {
        OrderID: 10252, CustomerID: 'SUPRD', EmployeeID: 4, OrderDate: new Date(8368506e5),
        ShipName: 'Suprêmes délices', ShipCity: 'Charleroi', ShipAddress: 'Boulevard Tirou, 255',
        ShipRegion: 'CJ', ShipPostalCode: 'B-6000', ShipCountry: 'Belgium', Freight: 51.3, Verified: !0
    },
    {
        OrderID: 10253, CustomerID: 'HANAR', EmployeeID: 3, OrderDate: new Date(836937e6),
        ShipName: 'Hanari Carnes', ShipCity: 'Rio de Janeiro', ShipAddress: 'Rua do Paço, 67',
        ShipRegion: 'RJ', ShipPostalCode: '05454-876', ShipCountry: 'Brazil', Freight: 58.17, Verified: !0
    },
    {
        OrderID: 10254, CustomerID: 'CHOPS', EmployeeID: 5, OrderDate: new Date(8370234e5),
        ShipName: 'Chop-suey Chinese', ShipCity: 'Bern', ShipAddress: 'Hauptstr. 31',
        ShipRegion: 'CJ', ShipPostalCode: '3012', ShipCountry: 'Switzerland', Freight: 22.98, Verified: !1
    },
    {
        OrderID: 10255, CustomerID: 'RICSU', EmployeeID: 9, OrderDate: new Date(8371098e5),
        ShipName: 'Richter Supermarkt', ShipCity: 'Genève', ShipAddress: 'Starenweg 5',
        ShipRegion: 'CJ', ShipPostalCode: '1204', ShipCountry: 'Switzerland', Freight: 148.33, Verified: !0
    },
    {
        OrderID: 10256, CustomerID: 'WELLI', EmployeeID: 3, OrderDate: new Date(837369e6),
        ShipName: 'Wellington Importadora', ShipCity: 'Resende', ShipAddress: 'Rua do Mercado, 12',
        ShipRegion: 'SP', ShipPostalCode: '08737-363', ShipCountry: 'Brazil', Freight: 13.97, Verified: !1
    },
    {
        OrderID: 10257, CustomerID: 'HILAA', EmployeeID: 4, OrderDate: new Date(8374554e5),
        ShipName: 'HILARION-Abastos', ShipCity: 'San Cristóbal', ShipAddress: 'Carrera 22 con Ave. Carlos Soublette #8-35',
        ShipRegion: 'Táchira', ShipPostalCode: '5022', ShipCountry: 'Venezuela', Freight: 81.91, Verified: !0
    },
    {
        OrderID: 10258, CustomerID: 'ERNSH', EmployeeID: 1, OrderDate: new Date(8375418e5),
        ShipName: 'Ernst Handel', ShipCity: 'Graz', ShipAddress: 'Kirchgasse 6',
        ShipRegion: 'CJ', ShipPostalCode: '8010', ShipCountry: 'Austria', Freight: 140.51, Verified: !0
    },
    {
        OrderID: 10259, CustomerID: 'CENTC', EmployeeID: 4, OrderDate: new Date(8376282e5),
        ShipName: 'Centro comercial Moctezuma', ShipCity: 'México D.F.', ShipAddress: 'Sierras de Granada 9993',
        ShipRegion: 'CJ', ShipPostalCode: '05022', ShipCountry: 'Mexico', Freight: 3.25, Verified: !1
    },
    {
        OrderID: 10260, CustomerID: 'OTTIK', EmployeeID: 4, OrderDate: new Date(8377146e5),
        ShipName: 'Ottilies Käseladen', ShipCity: 'Köln', ShipAddress: 'Mehrheimerstr. 369',
        ShipRegion: 'CJ', ShipPostalCode: '50739', ShipCountry: 'Germany', Freight: 55.09, Verified: !0
    },
    {
        OrderID: 10261, CustomerID: 'QUEDE', EmployeeID: 4, OrderDate: new Date(8377146e5),
        ShipName: 'Que Delícia', ShipCity: 'Rio de Janeiro', ShipAddress: 'Rua da Panificadora, 12',
        ShipRegion: 'RJ', ShipPostalCode: '02389-673', ShipCountry: 'Brazil', Freight: 3.05, Verified: !1
    },
    {
        OrderID: 10262, CustomerID: 'RATTC', EmployeeID: 8, OrderDate: new Date(8379738e5),
        ShipName: 'Rattlesnake Canyon Grocery', ShipCity: 'Albuquerque', ShipAddress: '2817 Milton Dr.',
        ShipRegion: 'NM', ShipPostalCode: '87110', ShipCountry: 'USA', Freight: 48.29, Verified: !0
    },
    {
        OrderID: 10263, CustomerID: 'ERNSH', EmployeeID: 9, OrderDate: new Date(8380602e5),
        ShipName: 'Ernst Handel', ShipCity: 'Graz', ShipAddress: 'Kirchgasse 6', ShipRegion: null, ShipPostalCode: '8010', ShipCountry: 'Austria', Freight: 146.06, Verified: !0
    },
    { 
        OrderID: 10264, CustomerID: 'FOLKO', EmployeeID: 6, OrderDate: new Date(8381466e5), ShipName: 'Folk och fä HB', 
      ShipCity: 'Bräcke',    ShipAddress: 'Åkergatan 24', ShipRegion: null, ShipPostalCode: 'S-844 67', ShipCountry: 'Sweden', Freight: 3.67, Verified: !1 
    },
    { 
        OrderID: 10265, CustomerID: 'BLONP', EmployeeID: 2, OrderDate: new Date(838233e6), ShipName: 'Blondel père et fils', 
        ShipCity: 'Strasbourg', ShipAddress: '24, place Kléber', ShipRegion: null, ShipPostalCode: '67000', ShipCountry: 'France', Freight: 55.28, Verified: !0 
    },
    { 
        OrderID: 10266, CustomerID: 'WARTH', EmployeeID: 3, OrderDate: new Date(8383194e5), ShipName: 'Wartian Herkku', 
        ShipCity: 'Oulu', ShipAddress: 'Torikatu 38', ShipRegion: null, ShipPostalCode: '90110', ShipCountry: 'Finland', Freight: 25.73, Verified: !1
    },
    { 
        OrderID: 10267, CustomerID: 'FRANK', EmployeeID: 4, OrderDate: new Date(8385786e5), ShipName: 'Frankenversand', 
        ShipCity: 'München', ShipAddress: 'Berliner Platz 43', ShipRegion: null, ShipPostalCode: '80805', ShipCountry: 'Germany', Freight: 208.58, Verified: !0 
    },
    { 
        OrderID: 10268, CustomerID: 'GROSR', EmployeeID: 8, OrderDate: new Date(838665e6), ShipName: 'GROSELLA-Restaurante', 
        ShipCity: 'Caracas', ShipAddress: '5ª Ave. Los Palos Grandes', ShipRegion: 'DF', ShipPostalCode: '1081', ShipCountry: 'Venezuela', Freight: 66.29, Verified: !0 
    }, 
    { 
        OrderID: 10269, CustomerID: 'WHITC', EmployeeID: 5, OrderDate: new Date(8387514e5), ShipName: 'White Clover Markets', 
        ShipCity: 'Austria', ShipAddress: '1029 - 12th Ave. S.', ShipRegion: 'WA', ShipPostalCode: '98124', ShipCountry: 'USA', Freight: 4.56, Verified: !1 },
    { 
        OrderID: 10270, CustomerID: 'WARTH', EmployeeID: 1, OrderDate: new Date(8388378e5), ShipName: 'Wartian Herkku', ShipCity: 'Oulu', ShipAddress: 'Torikatu 38', ShipRegion: null, ShipPostalCode: '90110', ShipCountry: 'Finland', Freight: 136.54, Verified: !0 
    }, 
    { 
        OrderID: 10271, CustomerID: 'SPLIR', EmployeeID: 6, OrderDate: new Date(8388378e5), ShipName: 'Split Rail Beer & Ale', ShipCity: 'Lander', ShipAddress: 'P.O. Box 555', ShipRegion: 'WY', ShipPostalCode: '82520', ShipCountry: 'USA', Freight: 4.54, Verified: !1 
    },
    { 
        OrderID: 10272, CustomerID: 'RATTC', EmployeeID: 6, OrderDate: new Date(8389242e5), ShipName: 'Rattlesnake Canyon Grocery', ShipCity: 'Albuquerque', ShipAddress: '2817 Milton Dr.', ShipRegion: 'NM', ShipPostalCode: '87110', ShipCountry: 'USA', Freight: 98.03, Verified: !0 
    }, 
    { 
        OrderID: 10273, CustomerID: 'QUICK', EmployeeID: 3, OrderDate: new Date(8391834e5), ShipName: 'QUICK-Stop', ShipCity: 'Cunewalde', ShipAddress: 'Taucherstraße 10', ShipRegion: null, ShipPostalCode: '01307', ShipCountry: 'Germany', Freight: 76.07, Verified: !0 
    },
    { 
        OrderID: 10274, CustomerID: 'VINET', EmployeeID: 6, OrderDate: new Date(8392698e5), ShipName: 'Vins et alcools Chevalier', ShipCity: 'Reims', ShipAddress: '59 rue de l Abbaye', ShipRegion: null, ShipPostalCode: '51100', ShipCountry: 'France', Freight: 6.01, Verified: !1
    }, 
    { 
        OrderID: 10275, CustomerID: 'MAGAA', EmployeeID: 1, OrderDate: new Date(8393562e5), ShipName: 'Magazzini Alimentari Riuniti', ShipCity: 'Bergamo', ShipAddress: 'Via Ludovico il Moro 22', ShipRegion: null, ShipPostalCode: '24100', ShipCountry: 'Italy', Freight: 26.93, Verified: !1 
    },
    { 
        OrderID: 10276, CustomerID: 'TORTU', EmployeeID: 8, OrderDate: new Date(8394426e5), ShipName: 'Tortuga Restaurante', ShipCity: 'México D.F.', ShipAddress: 'Avda. Azteca 123', ShipRegion: null, ShipPostalCode: '05033', ShipCountry: 'Mexico', Freight: 13.84, Verified: !1 
    }, 
    { 
        OrderID: 10277, CustomerID: 'MORGK', EmployeeID: 2, OrderDate: new Date(839529e6), ShipName: 'Morgenstern Gesundkost', ShipCity: 'Leipzig', ShipAddress: 'Heerstr. 22', ShipRegion: null, ShipPostalCode: '04179', ShipCountry: 'Germany', Freight: 125.77, Verified: !0 
    },
    { 
        OrderID: 10278, CustomerID: 'BERGS', EmployeeID: 8, OrderDate: new Date(8397882e5), ShipName: 'Berglunds snabbköp', ShipCity: 'Luleå', ShipAddress: 'Berguvsvägen  8', ShipRegion: null, ShipPostalCode: 'S-958 22', ShipCountry: 'Sweden', Freight: 92.69, Verified: !0 
    },
     { 
        OrderID: 10279, CustomerID: 'LEHMS', EmployeeID: 8, OrderDate: new Date(8398746e5), ShipName: 'Lehmanns Marktstand', ShipCity: 'Frankfurt a.M.', ShipAddress: 'Magazinweg 7', ShipRegion: null, ShipPostalCode: '60528', ShipCountry: 'Germany', Freight: 25.83, Verified: !1 
     }, 
     { 
        OrderID: 10280, CustomerID: 'BERGS', EmployeeID: 2, OrderDate: new Date(839961e6), ShipName: 'Berglunds snabbköp', ShipCity: 'Luleå', ShipAddress: 'Berguvsvägen  8', ShipRegion: null, ShipPostalCode: 'S-958 22', ShipCountry: 'Sweden', Freight: 8.98, Verified: !1 
     },
    { 
        OrderID: 10281, CustomerID: 'ROMEY', EmployeeID: 4, OrderDate: new Date(839961e6), ShipName: 'Romero y tomillo', ShipCity: 'Madrid', ShipAddress: 'Gran Vía, 1', ShipRegion: null, ShipPostalCode: '28001', ShipCountry: 'Spain', Freight: 2.94, Verified: !1 
    }, 
    { 
        OrderID: 10282, CustomerID: 'ROMEY', EmployeeID: 4, OrderDate: new Date(8400474e5), ShipName: 'Romero y tomillo', ShipCity: 'Madrid', ShipAddress: 'Gran Vía, 1', ShipRegion: null, ShipPostalCode: '28001', ShipCountry: 'Spain', Freight: 12.69, Verified: !1 
    }, 
    { 
        OrderID: 10283, CustomerID: 'LILAS', EmployeeID: 3, OrderDate: new Date(8401338e5), ShipName: 'LILA-Supermercado', ShipCity: 'Barquisimeto', ShipAddress: 'Carrera 52 con Ave. Bolívar #65-98 Llano Largo', ShipRegion: 'Lara', ShipPostalCode: '3508', ShipCountry: 'Venezuela', Freight: 84.81, Verified: !0 
    }, 
    { 
        OrderID: 10284, CustomerID: 'LEHMS', EmployeeID: 4, OrderDate: new Date(840393e6), ShipName: 'Lehmanns Marktstand', ShipCity: 'Frankfurt a.M.', ShipAddress: 'Magazinweg 7', ShipRegion: null, ShipPostalCode: '60528', ShipCountry: 'Germany', Freight: 76.56, Verified: !0 
    }, 
    { 
        OrderID: 10285, CustomerID: 'QUICK', EmployeeID: 1, OrderDate: new Date(8404794e5), ShipName: 'QUICK-Stop', ShipCity: 'Cunewalde', ShipAddress: 'Taucherstraße 10', ShipRegion: null, ShipPostalCode: '01307', ShipCountry: 'Germany', Freight: 76.83, Verified: !0 
    }, 
    { 
        OrderID: 10286, CustomerID: 'QUICK', EmployeeID: 8, OrderDate: new Date(8405658e5), ShipName: 'QUICK-Stop', ShipCity: 'Cunewalde', ShipAddress: 'Taucherstraße 10', ShipRegion: null, ShipPostalCode: '01307', ShipCountry: 'Germany', Freight: 229.24, Verified: !0 
    },
    { 
        OrderID: 10287, CustomerID: 'RICAR', EmployeeID: 8, OrderDate: new Date(8406522e5), ShipName: 'Ricardo Adocicados', ShipCity: 'Rio de Janeiro', ShipAddress: 'Av. Copacabana, 267', ShipRegion: 'RJ', ShipPostalCode: '02389-890', ShipCountry: 'Brazil', Freight: 12.76, Verified: !1 
    },
    { 
        OrderID: 10288, CustomerID: 'REGGC', EmployeeID: 4, OrderDate: new Date(8407386e5), ShipName: 'Reggiani Caseifici', ShipCity: 'Reggio Emilia', ShipAddress: 'Strada Provinciale 124', ShipRegion: null, ShipPostalCode: '42100', ShipCountry: 'Italy', Freight: 7.45, Verified: !1 
    }, 
    { 
        OrderID: 10289, CustomerID: 'BSBEV', EmployeeID: 7, OrderDate: new Date(8409978e5), ShipName: 'Bs Beverages', ShipCity: 'Brazil', ShipAddress: 'Fauntleroy Circus', ShipRegion: null, ShipPostalCode: 'EC2 5NT', ShipCountry: 'UK', Freight: 22.77, Verified: !1 
    },
    { 
        OrderID: 10290, CustomerID: 'COMMI', EmployeeID: 8, OrderDate: new Date(8410842e5), ShipName: 'Comércio Mineiro', ShipCity: 'Sao Paulo', ShipAddress: 'Av. dos Lusíadas, 23', ShipRegion: 'SP', ShipPostalCode: '05432-043', ShipCountry: 'Brazil', Freight: 79.7, Verified: !0 
    }, 
    { 
        OrderID: 10291, CustomerID: 'QUEDE', EmployeeID: 6, OrderDate: new Date(8410842e5), ShipName: 'Que Delícia', ShipCity: 'Rio de Janeiro', ShipAddress: 'Rua da Panificadora, 12', ShipRegion: 'RJ', ShipPostalCode: '02389-673', ShipCountry: 'Brazil', Freight: 6.4, Verified: !1 
    },
    { 
        OrderID: 10292, CustomerID: 'TRADH', EmployeeID: 1, OrderDate: new Date(8411706e5), ShipName: 'Tradiçao Hipermercados', ShipCity: 'Sao Paulo', ShipAddress: 'Av. Inês de Castro, 414', ShipRegion: 'SP', ShipPostalCode: '05634-030', ShipCountry: 'Brazil', Freight: 1.35, Verified: !1 
    }, 
    { 
        OrderID: 10293, CustomerID: 'TORTU', EmployeeID: 1, OrderDate: new Date(841257e6), ShipName: 'Tortuga Restaurante', ShipCity: 'México D.F.', ShipAddress: 'Avda. Azteca 123', ShipRegion: null, ShipPostalCode: '05033', ShipCountry: 'Mexico', Freight: 21.18, Verified: !1 
    }, 
    { 
        OrderID: 10294, CustomerID: 'RATTC', EmployeeID: 4, OrderDate: new Date(8413434e5), ShipName: 'Rattlesnake Canyon Grocery', ShipCity: 'Albuquerque', ShipAddress: '2817 Milton Dr.', ShipRegion: 'NM', ShipPostalCode: '87110', ShipCountry: 'USA', Freight: 147.26, Verified: !0 
    }, 
    { 
        OrderID: 10295, CustomerID: 'VINET', EmployeeID: 2, OrderDate: new Date(8416026e5), ShipName: 'Vins et alcools Chevalier', ShipCity: 'Reims', ShipAddress: '59 rue de l Abbaye', ShipRegion: null, ShipPostalCode: '51100', ShipCountry: 'France', Freight: 1.15, Verified: !1 
    },
    { 
        OrderID: 10296, CustomerID: 'LILAS', EmployeeID: 6, OrderDate: new Date(841689e6), ShipName: 'LILA-Supermercado', ShipCity: 'Barquisimeto', ShipAddress: 'Carrera 52 con Ave. Bolívar #65-98 Llano Largo', ShipRegion: 'Lara', ShipPostalCode: '3508', ShipCountry: 'Venezuela', Freight: .12, Verified: !1 
    }, 
    { 
        OrderID: 10297, CustomerID: 'BLONP', EmployeeID: 5, OrderDate: new Date(8417754e5), ShipName: 'Blondel père et fils', ShipCity: 'Strasbourg', ShipAddress: '24, place Kléber', ShipRegion: null, ShipPostalCode: '67000', ShipCountry: 'France', Freight: 5.74, Verified: !1 
    },
    { 
        OrderID: 10298, CustomerID: 'HUNGO', EmployeeID: 6, OrderDate: new Date(8418618e5), ShipName: 'Hungry Owl All-Night Grocers', ShipCity: 'Cork', ShipAddress: '8 Johnstown Road', ShipRegion: 'Co. Cork', ShipPostalCode: null, ShipCountry: 'Ireland', Freight: 168.22, Verified: !0 
     }, 
    { 
        OrderID: 10299, CustomerID: 'RICAR', EmployeeID: 4, OrderDate: new Date(8419482e5), ShipName: 'Ricardo Adocicados', ShipCity: 'Rio de Janeiro', ShipAddress: 'Av. Copacabana, 267', ShipRegion: 'RJ', ShipPostalCode: '02389-890', ShipCountry: 'Brazil', Freight: 29.76, Verified: !1 
    },
    { 
        OrderID: 10300, CustomerID: 'MAGAA', EmployeeID: 2, OrderDate: new Date(8422074e5), ShipName: 'Magazzini Alimentari Riuniti', ShipCity: 'Bergamo', ShipAddress: 'Via Ludovico il Moro 22', ShipRegion: null, ShipPostalCode: '24100', ShipCountry: 'Italy', Freight: 17.68, Verified: !1 
    }, 
    { 
        OrderID: 10301, CustomerID: 'WANDK', EmployeeID: 8, OrderDate: new Date(8422074e5), ShipName: 'Die Wandernde Kuh', ShipCity: 'Stuttgart', ShipAddress: 'Adenauerallee 900', ShipRegion: null, ShipPostalCode: '70563', ShipCountry: 'Germany', Freight: 45.08, Verified: !0 
    }, 
    { 
        OrderID: 10302, CustomerID: 'SUPRD', EmployeeID: 4, OrderDate: new Date(8422938e5), ShipName: 'Suprêmes délices', ShipCity: 'Charleroi', ShipAddress: 'Boulevard Tirou, 255', ShipRegion: null, ShipPostalCode: 'B-6000', ShipCountry: 'Belgium', Freight: 6.27, Verified: !1 
    }, 
    { 
        OrderID: 10303, CustomerID: 'GODOS', EmployeeID: 7, OrderDate: new Date(8423802e5), ShipName: 'Godos Cocina Típica', ShipCity: 'Sevilla', ShipAddress: 'C/ Romero, 33', ShipRegion: null, ShipPostalCode: '41101', ShipCountry: 'Spain', Freight: 107.83, Verified: !0 
    }, 
    { 
        OrderID: 10304, CustomerID: 'TORTU', EmployeeID: 1, OrderDate: new Date(8424666e5), ShipName: 'Tortuga Restaurante', ShipCity: 'México D.F.', ShipAddress: 'Avda. Azteca 123', ShipRegion: null, ShipPostalCode: '05033', ShipCountry: 'Mexico', Freight: 63.79, Verified: !0 
    },
    { 
        OrderID: 10305, CustomerID: 'OLDWO', EmployeeID: 8, OrderDate: new Date(842553e6), ShipName: 'Old World Delicatessen', ShipCity: 'Anchorage', ShipAddress: '2743 Bering St.', ShipRegion: 'AK', ShipPostalCode: '99508', ShipCountry: 'USA', Freight: 257.62, Verified: !0 
    },
    { 
        OrderID: 10306, CustomerID: 'ROMEY', EmployeeID: 1, OrderDate: new Date(8428122e5), ShipName: 'Romero y tomillo', ShipCity: 'Madrid', ShipAddress: 'Gran Vía, 1', ShipRegion: null, ShipPostalCode: '28001', ShipCountry: 'Spain', Freight: 7.56, Verified: !1 
    }, 
    { 
        OrderID: 10307, CustomerID: 'LONEP', EmployeeID: 2, OrderDate: new Date(8428986e5), ShipName: 'Lonesome Pine Restaurant', ShipCity: 'Portland', ShipAddress: '89 Chiaroscuro Rd.', ShipRegion: 'OR', ShipPostalCode: '97219', ShipCountry: 'USA', Freight: .56, Verified: !1 
    }, 
    { 
        OrderID: 10308, CustomerID: 'ANATR', EmployeeID: 7, OrderDate: new Date(842985e6), ShipName: 'Ana Trujillo Emparedados y helados', ShipCity: 'México D.F.', ShipAddress: 'Avda. de la Constitución 2222', ShipRegion: null, ShipPostalCode: '05021', ShipCountry: 'Mexico', Freight: 1.61, Verified: !1 
    }, 
    { 
        OrderID: 10309, CustomerID: 'HUNGO', EmployeeID: 3, OrderDate: new Date(8430714e5), ShipName: 'Hungry Owl All-Night Grocers', ShipCity: 'Cork', ShipAddress: '8 Johnstown Road', ShipRegion: 'Co. Cork', ShipPostalCode: null, ShipCountry: 'Ireland', Freight: 47.3, Verified: !0 
    }, 
    { 
        OrderID: 10310, CustomerID: 'THEBI', EmployeeID: 8, OrderDate: new Date(8431578e5), ShipName: 'The Big Cheese', ShipCity: 'Portland', ShipAddress: '89 Jefferson Way Suite 2', ShipRegion: 'OR', ShipPostalCode: '97201', ShipCountry: 'USA', Freight: 17.52, Verified: !1 
    },
    { 
        OrderID: 10311, CustomerID: 'DUMON', EmployeeID: 1, OrderDate: new Date(8431578e5), ShipName: 'Du monde entier', ShipCity: 'Nantes', ShipAddress: '67, rue des Cinquante Otages', ShipRegion: null, ShipPostalCode: '44000', ShipCountry: 'France', Freight: 24.69, Verified: !1 }, 
    { 
        OrderID: 10312, CustomerID: 'WANDK', EmployeeID: 2, OrderDate: new Date(843417e6), ShipName: 'Die Wandernde Kuh', ShipCity: 'Stuttgart', ShipAddress: 'Adenauerallee 900', ShipRegion: null, ShipPostalCode: '70563', ShipCountry: 'Germany', Freight: 40.26, Verified: !0 
    },
    { 
        OrderID: 10313, CustomerID: 'QUICK', EmployeeID: 2, OrderDate: new Date(8435034e5), ShipName: 'QUICK-Stop', ShipCity: 'Cunewalde', ShipAddress: 'Taucherstraße 10', ShipRegion: null, ShipPostalCode: '01307', ShipCountry: 'Germany', Freight: 1.96, Verified: !1 }, 
    { 
        OrderID: 10314, CustomerID: 'RATTC', EmployeeID: 1, OrderDate: new Date(8435898e5), ShipName: 'Rattlesnake Canyon Grocery', ShipCity: 'Albuquerque', ShipAddress: '2817 Milton Dr.', ShipRegion: 'NM', ShipPostalCode: '87110', ShipCountry: 'USA', Freight: 74.16, Verified: !0 
    }, 
    { 
        OrderID: 10315, CustomerID: 'ISLAT', EmployeeID: 4, OrderDate: new Date(8436762e5), ShipName: 'Island Trading', ShipCity: 'Cowes', ShipAddress: 'Garden House Crowther Way', ShipRegion: 'Isle of Wight', ShipPostalCode: 'PO31 7PJ', ShipCountry: 'UK', Freight: 41.76, Verified: !0 
    }, 
    { 
        OrderID: 10316, CustomerID: 'RATTC', EmployeeID: 1, OrderDate: new Date(8437626e5), ShipName: 'Rattlesnake Canyon Grocery', ShipCity: 'Albuquerque', ShipAddress: '2817 Milton Dr.', ShipRegion: 'NM', ShipPostalCode: '87110', ShipCountry: 'USA', Freight: 150.15, Verified: !0 
    }, 
    { 
        OrderID: 10317, CustomerID: 'LONEP', EmployeeID: 6, OrderDate: new Date(8440218e5), ShipName: 'Lonesome Pine Restaurant', ShipCity: 'Portland', ShipAddress: '89 Chiaroscuro Rd.', ShipRegion: 'OR', ShipPostalCode: '97219', ShipCountry: 'USA', Freight: 12.69, Verified: !1 
    }, 
    { 
        OrderID: 10318, CustomerID: 'ISLAT', EmployeeID: 8, OrderDate: new Date(8441082e5), ShipName: 'Island Trading', ShipCity: 'Cowes', ShipAddress: 'Garden House Crowther Way', ShipRegion: 'Isle of Wight', ShipPostalCode: 'PO31 7PJ', ShipCountry: 'UK', Freight: 4.73, Verified: !1 
    }
];

7: Install required packages and start the GraphQL server by running the following commands in your terminal:

    npm install
    npm run dev

The server will be hosted at http://localhost:xxxx/. (where xxxx represents the port number).

Step 2: Connect Syncfusion DataManager to GraphQL:

1: Open the command prompt from the required directory, and run the following command to clone the Syncfusion JavaScript (Essential JS 2) quickstart project from GitHub.

git clone https://github.com/SyncfusionExamples/ej2-quickstart-webpack- ClientApp
cd ClientApp

2: Add Syncfusion packages:

npm install

3: Configure DataManager with GraphQLAdaptor:

A. Import DataManager, Query, GraphQLAdaptor modules from @syncfusion/ej2-data, and the compile module from @syncfusion/ej2-base.

import { DataManager, Query, ReturnOption, GraphQLAdaptor } from '@syncfusion/ej2-data';
import { compile } from '@syncfusion/ej2-base';

B. Configure the DataManager:

Assign your API endpoint to the url property and use GraphQLAdaptor as the adaptor.

let data: DataManager = new DataManager({
  // Use remote server host and port instead of 'xxxx'.
  url: "http://localhost:xxxx",
  adaptor: new GraphQLAdaptor({
    // Additional adaptor options can be specified here.
  })
});

C.Define the GraphQL query and map the response:

  • The GraphQLAdaptor includes a query property where you define your GraphQL query string. The response from the server should follow this JSON structure:

      query: `query getOrders($datamanager: DataManager) {
        getOrders(datamanager: $datamanager) {
          count,
          result {
            OrderID,
            CustomerID,
            ShipCity,
            ShipCountry
          }
        }
      }
      {
       "data": {
          "getOrders": {
            "result": [...],
            "count": 100
          }
       }
      }
  • To map this structure, set the result and count fields in the response property of the adaptor:

      response: {
        result: 'getOrders.result',
        count: 'getOrders.count'
      }

D. Apply a query using executeQuery:

Use the executeQuery method with a Queryobject to retrieve data. This enables you to perform server-side operations such as paging, filtering, or sorting. The example below retrieves the first 8 records where the ShipCity contains “Cunewalde” and ShipCountry equals “Germany,” sorted by OrderID descending.

const query = new Query()
  .skip(0).take(8).search('Cunewalde', ['ShipCity']).where('ShipCountry', 'equal', 'Germany').sortBy('OrderID', 'descending');

data.executeQuery(query).then((e: ReturnOption) => {
  const response = e.result as { result: Order[] };
  response.result.forEach((data: Order) => {
    table.appendChild(compiledFunction(data)[0]);
  });
});
import { DataManager, Query, ReturnOption, GraphQLAdaptor } from '@syncfusion/ej2-data';
import { compile } from '@syncfusion/ej2-base';

let data: DataManager = new DataManager({
  // Use remote server host and port instead of 'xxxx'.
  url: "http://localhost:xxxx", 
  adaptor: new GraphQLAdaptor({
    response: {
      result: 'getOrders.result',
      count: 'getOrders.count'
    },
    query: `query getOrders($datamanager: DataManager) {
      getOrders(datamanager: $datamanager) {
        count,
        result{
          OrderID, CustomerID, ShipCity, ShipCountry
        }
      }
    }`
  }),
});

let template: string = '<tr><td>${OrderID}</td><td>${CustomerID}</td><td>${ShipCity}</td><td>${ShipCountry}</td></tr>';

let compiledFunction: Function = compile(template);

let table: HTMLElement = (<HTMLElement>document.getElementById('datatable'));

const query = new Query()
  .skip(0).take(8).search('Cunewalde', ['ShipCity']).where('ShipCountry', 'equal', 'Germany').sortBy('OrderID', 'descending');

data.executeQuery(query).then((e: ReturnOption) => {
  const response = e.result as { result: Order[] };
  response.result.forEach((data: Order) => {
    table.appendChild(compiledFunction(data)[0]);
  });
});

interface Order {
  OrderID: number;
  CustomerID: string;
  ShipCity: string;
  ShipCountry: string;
}

5: Add HTML Table:

Create a basic HTML structure with a <table> element where data using the GraphQL adaptor will be rendered.

<!DOCTYPE html>
<html lang="en">
<head>
    <title>EJ2 Grid</title>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta name="description" content="Typescript Grid Control" />
    <meta name="author" content="Syncfusion" />
    <script src="https://cdnjs.cloudflare.com/ajax/libs/systemjs/0.19.38/system.js"></script>
<script src="https://cdn.syncfusion.com/ej2/syncfusion-helper.js" type ="text/javascript"></script>
<script src="https://cdn.syncfusion.com/ej2/syncfusion-helper.js" type ="text/javascript"></script>
</head>
<body>
    <div id='container'>
        <table border="1" id='datatable' class='e-table'>
            <thead>
                <tr><th>Order ID</th><th>Customer ID</th><th>Ship City</th><th>Ship Country</th></tr>
            </thead>
            <tbody>
            </tbody>    
        </table>        
    </div>
</body>
</html>

6: Run the Application:

Once the GraphQL server is running, assign its URL (e.g., http://localhost:xxxx/) to the dataManager.url property of the DataManager in your application.

npm start

Performing CRUD action with GraphQLAdaptor

The GraphQLAdaptor in Syncfusion EJ2 TypeScript DataManager provides a smooth way to integrate GraphQL endpoints for performing CRUD operations (Create, Read, Update, and Delete). This adaptor requires you to supply the appropriate GraphQL queries and mutations corresponding to each CRUD action.

You perform these actions by defining mutation queries dynamically inside the getMutation method, which returns the correct GraphQL mutation based on the CRUD operation being performed.

To implement CRUD operations using the GraphQLAdaptor, follow these steps:

Step 1: Configure the DataManager:

  • Define the GraphQL query to fetch data via the query property.

  • Map the response to extract the actual data and total count using the response object.

  • Implement mutation queries for insert, update, and delete actions inside the getMutation method.

Step 2: Use DataManager CRUD methods:

Use the insert, update, and remove methods of the DataManager configured with the GraphQLAdaptor to perform respective CRUD operations.

import { DataManager, Query, ReturnOption, GraphQLAdaptor } from '@syncfusion/ej2-data';
import { compile } from '@syncfusion/ej2-base';

let data: DataManager = new DataManager({
  // Use remote server host and port instead of 'xxxx'.
  url: "http://localhost:xxxx", 
  adaptor: new GraphQLAdaptor({
    response: {
      result: 'getOrders.result',
      count: 'getOrders.count'
    },
    query: `query getOrders($datamanager: DataManager) {
      getOrders(datamanager: $datamanager) {
        count,
        result{
          OrderID, CustomerID, ShipCity, ShipCountry}
        }
      }`,

    // Mutation for performing CRUD.
    getMutation: function (action: any): string {
      if (action === 'insert') {
        return `mutation CreateOrderMutation($value: OrderInput!){
          createOrder(value: $value){
            OrderID, CustomerID, ShipCity, ShipCountry
          }
        }`;
      }
      else if (action === 'update') {
        return `mutation UpdateOrderMutation($key: Int!, $keyColumn: String,$value: OrderInput){
          updateOrder(key: $key, keyColumn: $keyColumn, value: $value) {
            OrderID, CustomerID, ShipCity, ShipCountry
          }
        }`;
      } 
      else {
        return `mutation RemoveOrderMutation($key: Int!, $keyColumn: String, $value: OrderInput){
          deleteOrder(key: $key, keyColumn: $keyColumn, value: $value) {
            OrderID, CustomerID, ShipCity, ShipCountry
          }
        }`;
      }
    }
  }),
});

let template: string = '<tr><td>${OrderID}</td><td>${CustomerID}</td><td>${ShipCity}</td><td>${ShipCountry}</td></tr>';

let compiledFunction: Function = compile(template);

let table: HTMLElement = (<HTMLElement>document.getElementById('datatable'));

async function renderTable() {
  const query = new Query();
  const e = await data.executeQuery(query) as ReturnOption;
  if (e && e.result && e.result.result) {
    (table as HTMLFormElement).tBodies[0].innerHTML = '';
    (e.result.result as Object[]).forEach((data: any) => {
      if (data && data.OrderID) {
        (table as HTMLFormElement).tBodies[0].appendChild(compiledFunction(data)[0].firstChild);
      }
    });
  } else {
    console.warn('No data received from API');
  }
}
renderTable(); // Load first 8 records.

// Insert new record.
const insertBtn = document.getElementById('insertBtn') as HTMLInputElement;
insertBtn.onclick = async () => {
    const orderid = document.getElementById('insertOrderID') as HTMLInputElement;
    const cusid = document.getElementById('insertCustomerID') as HTMLInputElement;
    const shipCity = (document.getElementById('insertShipCity') as HTMLInputElement).value;
    const shipCountry = (document.getElementById('insertShipCountry') as HTMLInputElement).value; 

    if (!orderid.value || !cusid.value) {
      (document.getElementById('message') as HTMLElement).innerText='OrderID and CustomerID are required';
      return;
    }
          
    const res = await data.executeQuery(new Query()) as any;
    const exists = res.result?.result?.some((item: any) => item.OrderID === Number(orderid.value));

    if (exists) {
      (document.getElementById('message') as HTMLElement).innerText = `OrderID ${orderid.value} already exists`;
      return;
    }

    const newData = {
      OrderID: parseInt(orderid.value, 10),
      CustomerID: cusid.value,
      ShipCity: shipCity,
      ShipCountry: shipCountry
    };

    try {
      await data.insert(newData, new Query());
      await renderTable();
    } catch (err) {
      console.error('Insert failed:', err);
    }
};

// Update a record.
const updateBtn = document.getElementById('updateBtn') as HTMLInputElement;
updateBtn.onclick = async () => {
    const orderid = document.getElementById('updateOrderID') as HTMLInputElement;
    const cusid = document.getElementById('updateCustomerID') as HTMLInputElement;
    const shipCity = (document.getElementById('updateShipCity') as HTMLInputElement).value;
    const shipCountry = (document.getElementById('updateShipCountry') as HTMLInputElement).value;
    const updatedData = {
        OrderID: parseInt(orderid.value, 10),
        CustomerID: cusid.value,
        ShipCity: shipCity,
        ShipCountry: shipCountry
    };

    if (!updatedData.OrderID) { return; }
    try {
      await data.update('OrderID', updatedData, new Query());
      await renderTable();
    } catch (err) {
      console.error('Update failed:', err);
    }
};

// Delete a record.
const deleteBtn = document.getElementById('deleteBtn') as HTMLInputElement;
deleteBtn.onclick = async () => {
    const orderid = document.getElementById('deleteOrderID') as HTMLInputElement;
    if (!orderid.value) { return; }
    try {
      await data.remove('OrderID', { OrderID: parseInt(orderid.value, 10) }, new Query());
      await renderTable();
    } catch (err) {
      console.error('Delete failed:', err);
    }
};

interface Order {
  OrderID: number;
  CustomerID: string;
  ShipCity: string;
  ShipCountry: string;
}

Step 3: Implement server-side GraphQL resolvers:

Create resolver functions on the server side to handle the GraphQL queries and mutations. These functions perform data processing using the @syncfusion/ej2-data library.

import { OrderData } from "./db";
import { DataUtil, Query, DataManager } from "@syncfusion/ej2-data";

DataUtil.serverTimezoneOffset = 0;

const resolvers = {
  Query: {
    getOrders: (parent, { datamanager }, context, info) => {
      console.log(datamanager);
      let orders = [...OrderData];
      let query = new Query();

      orders = new DataManager(orders).executeLocal(query);
      var count = orders.length;

      // Perform paging.
      if (datamanager.skip && datamanager.take) {
        const pageSkip = datamanager.skip / datamanager.take + 1;
        const pageTake = datamanager.take;
        query.page(pageSkip, pageTake);
      } else if (datamanager.skip === 0 && datamanager.take) {
        query.page(1, datamanager.take);
      }

      const currentResult = new DataManager(orders).executeLocal(query);
      return { result: currentResult, count: count };
    },
  },
  Mutation: {
    createOrder: (parent, { value }, context, info) => {
      const newOrder = value;
      OrderData.push(newOrder);
      return newOrder;
    },
    updateOrder: (parent, { key, keyColumn, value }, context, info) => {
      let updatedOrder = OrderData.find(order => order.OrderID === parseInt(key));
      updatedOrder.CustomerID = value.CustomerID;
      updatedOrder.EmployeeID = value.EmployeeID;
      updatedOrder.Freight = value.Freight;
      updatedOrder.ShipCity = value.ShipCity;
      updatedOrder.ShipCountry = value.ShipCountry;
      return updatedOrder; // Make sure to return the updated order.
    },
    deleteOrder: (parent, { key, keyColumn, value }, context, info) => {
      const orderIndex = OrderData.findIndex(order => order.OrderID === parseInt(key));
      if (orderIndex === -1) throw new Error("Order not found." + value);
      const deletedOrders = OrderData.splice(orderIndex, 1);
      return deletedOrders[0];
    }
  }
};

export default resolvers;

Step 4: Define GraphQL schema:

Create a schema that supports Syncfusion DataManager queries and mutations with the required input types.

#Grid Sort direction.

input Sort {
    name: String!
    direction: String!
} 

#Grid aggregates type.

input Aggregate {
    field: String! 
    type: String!
}

#Syncfusion DataManager query params.

input DataManager {
    skip: Int
    take: Int
    sorted: [Sort]
    group: [String]
    table: String
    select: [String]
    where: String
    search: String
    requiresCounts: Boolean,
    aggregates: [Aggregate],
    params: String
}

# Grid field names.
input OrderInput {
  OrderID: Int!
  CustomerID: String
  EmployeeID: Int
  ShipCity: String
  ShipCountry: String
}

type Order {
  OrderID: Int!
  CustomerID: String
  EmployeeID: Int
  ShipCity: String
  ShipCountry: String
}

# need to return type as 'result (i.e, current pager data)' and count (i.e., total number of records in your database).
type ReturnType {
  result: [Order]
  count: Int
  aggregates: String
}

type Query {
  getOrders(datamanager: DataManager): ReturnType 
}

type Mutation {

  createOrder(value: OrderInput): Order!
  updateOrder(key: Int!, keyColumn: String, value: OrderInput): Order
  deleteOrder(key: Int!, keyColumn: String, value: OrderInput): Order!
}

Step 5: Create an HTML file with forms for insert, update, delete and a table to display the data:

<!DOCTYPE html>
<html lang="en">

<head>
    <title>EJ2 Grid</title>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta name="description" content="Typescript Grid Control" />
    <meta name="author" content="Syncfusion" />
    <script src="https://cdnjs.cloudflare.com/ajax/libs/systemjs/0.19.38/system.js"></script>
    <script src="https://cdn.syncfusion.com/ej2/syncfusion-helper.js" type="text/javascript"></script>
    <style>
        input {
            margin-right: 10px;
            width: 120px;
        }

        #message {
            color: red;
            margin-top: 10px;
        }

        table {
            border-collapse: collapse;
            width: 100%;
            margin-top: 20px;
            font-family: Roboto;
        }

        th,
        td {
            border: 1px solid #e0e0e0;
            padding: 8px 12px;
            text-align: left;
            white-space: nowrap;
        }

        .form-section {
            margin: 10px 0;
        }

        input {
            margin-right: 10px;
            width: 120px;
        }

        .e-form input[type="button"] {
            padding: 5px 16px;
            background-color: #3f51b5;
            color: white;
            border: none;
            border-radius: 4px;
            font-weight: 500;
            cursor: pointer;
            transition: background-color 0.3s ease;
        }

        .e-form input[type="button"]:hover {
            background-color: #303f9f;
        }
    </style>
<script src="https://cdn.syncfusion.com/ej2/syncfusion-helper.js" type ="text/javascript"></script>
</head>

<body>
    <div id='container'>
        <!-- Insert Form -->
        <div class="e-form">
            <h3>Insert Record</h3>
            <input type="number" id="insertOrderID" placeholder="Order ID" />
            <input type="text" id="insertCustomerID" placeholder="Customer ID" />
            <input type="text" id="insertShipCity" placeholder="Ship City" />
            <input type="text" id="insertShipCountry" placeholder="Ship Country" />
            <input type="button" value="Insert" id="insertBtn" />
        </div>
        <!-- Update Form -->
        <div class="e-form">
            <h3>Update Record</h3>
            <input type="number" id="updateOrderID" placeholder="Order ID" />
            <input type="text" id="updateCustomerID" placeholder="Customer ID" />
            <input type="text" id="updateShipCity" placeholder="Ship City" />
            <input type="text" id="updateShipCountry" placeholder="Ship Country" />
            <input type="button" value="Update" id="updateBtn" />
        </div>
        <!-- Delete Form -->
        <div class="e-form">
            <h3>Delete Record</h3>
            <input type="number" id="deleteOrderID" placeholder="Order ID" />
            <input type="button" value="Delete" id="deleteBtn" />
        </div>
        <div id="message"></div>
        <table id='datatable' class='e-table'>
            <thead>
                <tr>
                    <th>Order ID</th>
                    <th>Customer ID</th>
                    <th>Ship City</th>
                    <th>Ship Country</th>
                </tr>
            </thead>
            <tbody>
            </tbody>
        </table>
    </div>
</body>

</html>

Step 6: Create a simple data file (e.g., src/db.js) that exports an array of order objects used by the server-side resolvers.

export let OrderData = [
    {
        OrderID: 10248, CustomerID: 'VINET', EmployeeID: 5, OrderDate: new Date("07 12 1996 02:00:23"),
        ShipName: 'Vins et alcools Chevalier', ShipCity: 'Reims', ShipAddress: '59 rue de l Abbaye',
        ShipRegion: 'CJ', ShipPostalCode: '51100', ShipCountry: 'France', Freight: 32.38, Verified: !0
    },
    {
        OrderID: 10249, CustomerID: 'TOMSP', EmployeeID: 6, OrderDate: new Date("07 12 1996 00:03:23"),
        ShipName: 'Toms Spezialitäten', ShipCity: 'Münster', ShipAddress: 'Luisenstr. 48',
        ShipRegion: 'CJ', ShipPostalCode: '44087', ShipCountry: 'Germany', Freight: 11.61, Verified: !1
    },
    {
        OrderID: 10250, CustomerID: 'HANAR', EmployeeID: 4, OrderDate: new Date("07 12 1996 00:00:23"),
        ShipName: 'Hanari Carnes', ShipCity: 'Rio de Janeiro', ShipAddress: 'Rua do Paço, 67',
        ShipRegion: 'RJ', ShipPostalCode: '05454-876', ShipCountry: 'Brazil', Freight: 65.83, Verified: !0
    },
    {
        OrderID: 10251, CustomerID: 'VICTE', EmployeeID: 3, OrderDate: new Date(8367642e5),
        ShipName: 'Victuailles en stock', ShipCity: 'Lyon', ShipAddress: '2, rue du Commerce',
        ShipRegion: 'CJ', ShipPostalCode: '69004', ShipCountry: 'France', Freight: 41.34, Verified: !0
    },
    {
        OrderID: 10252, CustomerID: 'SUPRD', EmployeeID: 4, OrderDate: new Date(8368506e5),
        ShipName: 'Suprêmes délices', ShipCity: 'Charleroi', ShipAddress: 'Boulevard Tirou, 255',
        ShipRegion: 'CJ', ShipPostalCode: 'B-6000', ShipCountry: 'Belgium', Freight: 51.3, Verified: !0
    },
    {
        OrderID: 10253, CustomerID: 'HANAR', EmployeeID: 3, OrderDate: new Date(836937e6),
        ShipName: 'Hanari Carnes', ShipCity: 'Rio de Janeiro', ShipAddress: 'Rua do Paço, 67',
        ShipRegion: 'RJ', ShipPostalCode: '05454-876', ShipCountry: 'Brazil', Freight: 58.17, Verified: !0
    },
    {
        OrderID: 10254, CustomerID: 'CHOPS', EmployeeID: 5, OrderDate: new Date(8370234e5),
        ShipName: 'Chop-suey Chinese', ShipCity: 'Bern', ShipAddress: 'Hauptstr. 31',
        ShipRegion: 'CJ', ShipPostalCode: '3012', ShipCountry: 'Switzerland', Freight: 22.98, Verified: !1
    },
    {
        OrderID: 10255, CustomerID: 'RICSU', EmployeeID: 9, OrderDate: new Date(8371098e5),
        ShipName: 'Richter Supermarkt', ShipCity: 'Genève', ShipAddress: 'Starenweg 5',
        ShipRegion: 'CJ', ShipPostalCode: '1204', ShipCountry: 'Switzerland', Freight: 148.33, Verified: !0
    },
    {
        OrderID: 10256, CustomerID: 'WELLI', EmployeeID: 3, OrderDate: new Date(837369e6),
        ShipName: 'Wellington Importadora', ShipCity: 'Resende', ShipAddress: 'Rua do Mercado, 12',
        ShipRegion: 'SP', ShipPostalCode: '08737-363', ShipCountry: 'Brazil', Freight: 13.97, Verified: !1
    },
    {
        OrderID: 10257, CustomerID: 'HILAA', EmployeeID: 4, OrderDate: new Date(8374554e5),
        ShipName: 'HILARION-Abastos', ShipCity: 'San Cristóbal', ShipAddress: 'Carrera 22 con Ave. Carlos Soublette #8-35',
        ShipRegion: 'Táchira', ShipPostalCode: '5022', ShipCountry: 'Venezuela', Freight: 81.91, Verified: !0
    },
    {
        OrderID: 10258, CustomerID: 'ERNSH', EmployeeID: 1, OrderDate: new Date(8375418e5),
        ShipName: 'Ernst Handel', ShipCity: 'Graz', ShipAddress: 'Kirchgasse 6',
        ShipRegion: 'CJ', ShipPostalCode: '8010', ShipCountry: 'Austria', Freight: 140.51, Verified: !0
    }
];

CustomDataAdaptor

The CustomDataAdaptor in Syncfusion EJ2 TypeScript DataManager provides an option to send your own request and manually manage all data operations. It offers complete control over the way data is retrieved, processed, and transmitted between client and server. This adaptor is especially valuable when working with APIs that have non-standard request structures, authentication requirements, or custom business rules.

By extending the UrlAdaptor, CustomDataAdaptor inherits support for RESTful endpoints while allowing deep customization through method overrides.

Custom request construction:

Within the getData method of CustomDataAdaptor, you can inspect the option object to access details about the current action, such as:

  • Filtering: option.queries.where
  • Sorting: option.queries.sort
  • Paging: option.queries.skip, option.queries.take
  • Searching: option.queries.search

Use this information to build a custom request payload that matches your backend API requirements.

Handling the server response:

After fetching data from your service, you must notify the DataManager of the result:

  • On success, call: option.onSuccess(responseData, additionalArgs);
  • On failure, call: option.onFailure(additionalArgs, error);

Alternatively, you can use this.processResponse(data, dataSource, option) if you need to process the response before passing it to the DataManager.

Expected server response format:

Since CustomDataAdaptor extends UrlAdaptor, it expects the server to return a JSON object with the following structure:

  • result: An array of data objects representing the records.
  • count: The total number of records (for paging support).

Example:

{
  "result": [
    { /* record 1 */ },
    { /* record 2 */ }
    // ...
  ],
  "count": 67
}

This structure ensures that DataManager can correctly handle data binding, paging, and other operations.

Implement the CustomDataAdaptor:

Use the following steps in your src/index.ts to fetch and display data with a CustomDataAdaptor:

1. Import required modules:

Import the necessary classes from the Syncfusion packages:

    import { CustomDataAdaptor, DataManager, Query, ReturnOption } from "@syncfusion/ej2-data";
    import { compile } from "@syncfusion/ej2-base";

2. Set up the CustomDataAdaptor with a getData function:

Define how data is fetched from the server using the fetch API inside the CustomDataAdaptor.

    const SERVICE_URI = '/Home/UrlDatasource';

    new DataManager({
      adaptor: new CustomDataAdaptor({
        getData: function (option: {
          onSuccess: (data: any, args: object) => void;
          onFailure: (args: object, error: any) => void;
        }): void {
          fetch(SERVICE_URI, {
            method: 'POST',
            headers: {
              'Content-Type': 'application/json; charset=utf-8',
            },
            body: JSON.stringify(option)
          }).then((response: Response) => {
            if (response.ok) {
              return response.json();
            }
            throw new Error('Network response was not ok.');
          }).then((data: any) => {
            option.onSuccess(data, {});
          }).catch((error: any) => {
            option.onFailure({}, error);
          });
        },
      }),
    }).executeQuery(new Query())
      .then((e: ReturnOption) => {
        console.log("Fetched Data:", e.result);
        (e.result as Object[]).forEach((data: Object) => {
          table!.innerHTML += compiledFunction(data)[0].outerHTML;
        });
      })
      .catch((error) => {
        console.error('Data fetch error:', error);
      });
import { CustomDataAdaptor, DataManager, Query, ReturnOption } from "@syncfusion/ej2-data";
import { compile } from "@syncfusion/ej2-base";

let template: string = '<tr><td>${orderID}</td><td>${customerID}</td><td>${employeeID}</td><td>${shipCountry}</td></tr>';

const compiledFunction: Function = compile(template);

let table: HTMLElement = document.getElementById('datatable') as HTMLElement;

const SERVICE_URI: string = '/Home/UrlDatasource';

new DataManager({
  adaptor: new CustomDataAdaptor({
    getData: function (option: {
      onSuccess: (data: any, args: object) => void;
      onFailure: (args: object, error: any) => void;
    }): void {
      fetch(SERVICE_URI, {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json; charset=utf-8',
        },
        body: JSON.stringify(option)
      }).then((response: Response) => {
        if (response.status >= 200 && response.status <= 299) {
          return response.json();
        }
        throw new Error('Network response was not ok.');
      }).then((data: any) => {
        option.onSuccess(data, {});
      }).catch((error: any) => {
        option.onFailure({}, error);
      });
    },
  }),
}).executeQuery(new Query())
  .then((e: ReturnOption) => {
    console.log("Fetched Data:", e.result);
    (e.result as Object[]).forEach((data: Object) => {
      table!.innerHTML += compiledFunction(data)[0].outerHTML;
    });
  })
  .catch((error) => {
    console.error('Data fetch error:', error);
  });
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>CustomDataAdaptor in TS DataManager</title>
</head>
<body>
  <div id='container'>
    <table id="datatable" class="e-table" border="1">
      <thead>
        <tr><th>Order ID</th><th>Customer ID</th><th>Employee ID</th><th>Ship Country</th></tr>
      </thead>
      <tbody>
      </tbody>
    </table>
  </div>
  <script src="js/index.js"></script>
</body>
</html>
using System.Diagnostics;
using CustomDataAdaptor.Models;
using Microsoft.AspNetCore.Mvc;

namespace CustomDataAdaptor.Controllers
{
  public class HomeController : Controller
  {
    public IActionResult Index()
    {
      return View();
    }

    [HttpPost]
    public IActionResult UrlDatasource()
    {
      var data = OrdersDetails.GetAllRecords();
      var totalRecordsCount = data.Count();
      return Json(new { result = data, count = totalRecordsCount });
    }
  }
}
using System.ComponentModel.DataAnnotations;

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

    }
    public OrdersDetails(
    int OrderID, string CustomerId, int EmployeeId, string ShipCountry)
    {
      this.OrderID = OrderID;
      this.CustomerID = CustomerId;
      this.EmployeeID = EmployeeId;
      this.ShipCountry = ShipCountry;
    }

    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, "Denmark"));
          order.Add(new OrdersDetails(code + 2, "ANATR", i + 2, "Brazil"));
          order.Add(new OrdersDetails(code + 3, "ANTON", i + 1, "Germany"));
          order.Add(new OrdersDetails(code + 4, "BLONP", i + 3, "Austria"));
          order.Add(new OrdersDetails(code + 5, "BOLID", i + 4, "Switzerland"));
          code += 5;
        }
      }
      return order;
    }
    [Key]
    public int? OrderID { get; set; }
    public string? CustomerID { get; set; }
    public int? EmployeeID { get; set; }
    public string? ShipCountry { get; set; }
  }
}

CustomAdaptor

The CustomAdaptor in Syncfusion EJ2 TypeScript DataManager allows you to extend built-in adaptors (like UrlAdaptor) and override specific methods to customize the data communication between the client and server. This adaptor is useful when integrating with non-standard APIs or when the server’s request and response formats do not match DataManager’s default expectations.

To create and use custom adaptor, please refer to the below steps:

  • Select an built-in adaptor which will act as base class for your custom adaptor.
  • Override the desired method to achieve your requirement.
  • Assign the custom adaptor to the adaptor property of DataManager.

With a CustomAdaptor, you have control over:

  • Query translation: Customize how queries (filtering, sorting, paging) are serialized into API-compatible formats.
  • Request building: Modify request URLs, headers, or payloads.
  • Response processing: Transform server responses into formats consumable by UI components.

Types of CustomAdaptor methods

There are three types of methods in custom adaptors.

processQuery:

The processQuery method is responsible for transforming the incoming Query object into a request format understood by the server. It is typically used to construct URLs, append parameters, and serialize complex queries such as filtering, sorting, paging, and grouping. The processQuery method accepts two arguments:

  • DataManager: Used to modify the URL dynamically.

  • Query: Allows setting additional parameter values or modifying queries such as sorting, filtering, and grouping, etc.

import { DataManager, Query, ReturnOption, ODataV4Adaptor } from '@syncfusion/ej2-data';
import { compile } from '@syncfusion/ej2-base';

var template = '<tr><td>${OrderID}</td><td>${CustomerID}</td><td>${EmployeeID}</td><td>${ShipCountry}</td></tr>';
const compiledFunction: Function = compile(template);

const SERVICE_URI: string = 'https://services.odata.org/V4/Northwind/Northwind.svc/Orders';
const table: HTMLElement | null = document.getElementById('datatable');

class CustomAdaptor extends ODataV4Adaptor {
    public processQuery(dm: DataManager, query: Query): Object {
        dm.dataSource.url = 'https://services.odata.org/V4/Northwind/Northwind.svc/Orders';
        query.addParams('CustomAdaptor in Syncfusion TS DataManager', 'true'); // Add the additional parameter.
        return super.processQuery.apply(this, arguments as any);
    }
}

new DataManager({ url: SERVICE_URI, adaptor: new CustomAdaptor() })
    .executeQuery(new Query().take(8))
    .then((e: ReturnOption) => {
        console.log("Fetched Data:", e.result);
        (e.result as Object[]).forEach((data: Object) => {
            table!.innerHTML += compiledFunction(data)[0].outerHTML;
        });
    });
<!DOCTYPE html>
<html lang="en">

<head>
    <title>EJ2 Grid</title>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta name="description" content="Typescript Grid Control" />
    <meta name="author" content="Syncfusion" />
    <link href="index.css" rel="stylesheet" />
    <link href="https://cdn.syncfusion.com/ej2/30.1.37/ej2-base/styles/material.css" rel="stylesheet" />
    <link href="https://cdn.syncfusion.com/ej2/30.1.37/ej2-grids/styles/material.css" rel="stylesheet" />
    <link href="https://cdn.syncfusion.com/ej2/30.1.37/ej2-buttons/styles/material.css" rel="stylesheet" />
    <link href="https://cdn.syncfusion.com/ej2/30.1.37/ej2-popups/styles/material.css" rel="stylesheet" />
    <link href="https://cdn.syncfusion.com/ej2/30.1.37/ej2-navigations/styles/material.css" rel="stylesheet" />
    <link href="https://cdn.syncfusion.com/ej2/30.1.37/ej2-dropdowns/styles/material.css" rel="stylesheet" />
    <link href="https://cdn.syncfusion.com/ej2/30.1.37/ej2-lists/styles/material.css" rel="stylesheet" />
    <link href="https://cdn.syncfusion.com/ej2/30.1.37/ej2-inputs/styles/material.css" rel="stylesheet" />
    <link href="https://cdn.syncfusion.com/ej2/30.1.37/ej2-calendars/styles/material.css" rel="stylesheet" />
    <link href="https://cdn.syncfusion.com/ej2/30.1.37/ej2-splitbuttons/styles/material.css" rel="stylesheet" />

    <script src="https://cdnjs.cloudflare.com/ajax/libs/systemjs/0.19.38/system.js"></script>
    <script src="systemjs.config.js"></script>
<script src="https://cdn.syncfusion.com/ej2/syncfusion-helper.js" type ="text/javascript"></script>
</head>
<body>
    <div id='container'>
        <div id='Grid'></div>
        <table id='datatable' class='e-table'>
            <thead>
                <tr><th>Order ID</th><th>Customer ID</th><th>Employee ID</th><th>Ship Country</th></tr>
            </thead>
            <tbody>
            </tbody>    
        </table>        
    </div>
</body>
</html>

Process Query

beforeSend:

The beforeSend method is called immediately before the request is sent to the server, allowing you to modify the request or perform final checks. This method is particularly useful for dynamically adding authentication headers, customizing the request payload, or injecting additional metadata. It accepts the following arguments:

  • DataManager: Provides access to the data and the adaptor instance.
  • Request: Represents the request object, which can be used to set custom headers (for example, the Authorization header).
  • Settings (optional): Allows for further configuration of the request.

Common use cases include:

  • Injecting a bearer token or other authentication credentials.
  • Adding custom headers such as client ID, language, or tenant information.
  • Performing preflight validation or logging request details before sending.
import { DataManager, Query, ReturnOption, ODataV4Adaptor } from '@syncfusion/ej2-data';
import { compile } from '@syncfusion/ej2-base';

var template = '<tr><td>${OrderID}</td><td>${CustomerID}</td><td>${EmployeeID}</td><td>${ShipCountry}</td></tr>';
const compiledFunction: Function = compile(template);

const SERVICE_URI: string = 'https://services.odata.org/V4/Northwind/Northwind.svc/Orders';
const table: HTMLElement | null = document.getElementById('datatable');

class CustomAdaptor extends ODataV4Adaptor {
    public beforeSend(dm: DataManager, request: Request, settings?: any) {
        request.headers.set('Accept-Language', 'Syncfusion');
        super.beforeSend(dm, request, settings);
    }
}

new DataManager({ url: SERVICE_URI, adaptor: new CustomAdaptor() })
    .executeQuery(new Query().take(8))
    .then((e: ReturnOption) => {
        console.log("Fetched Data:", e.result);
        (e.result as Object[]).forEach((data: Object) => {
            table!.innerHTML += compiledFunction(data)[0].outerHTML;
        });
    });
<!DOCTYPE html>
<html lang="en">

<head>
    <title>EJ2 Grid</title>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta name="description" content="Typescript Grid Control" />
    <meta name="author" content="Syncfusion" />
    <link href="index.css" rel="stylesheet" />
    <link href="https://cdn.syncfusion.com/ej2/30.1.37/ej2-base/styles/material.css" rel="stylesheet" />
    <link href="https://cdn.syncfusion.com/ej2/30.1.37/ej2-grids/styles/material.css" rel="stylesheet" />
    <link href="https://cdn.syncfusion.com/ej2/30.1.37/ej2-buttons/styles/material.css" rel="stylesheet" />
    <link href="https://cdn.syncfusion.com/ej2/30.1.37/ej2-popups/styles/material.css" rel="stylesheet" />
    <link href="https://cdn.syncfusion.com/ej2/30.1.37/ej2-navigations/styles/material.css" rel="stylesheet" />
    <link href="https://cdn.syncfusion.com/ej2/30.1.37/ej2-dropdowns/styles/material.css" rel="stylesheet" />
    <link href="https://cdn.syncfusion.com/ej2/30.1.37/ej2-lists/styles/material.css" rel="stylesheet" />
    <link href="https://cdn.syncfusion.com/ej2/30.1.37/ej2-inputs/styles/material.css" rel="stylesheet" />
    <link href="https://cdn.syncfusion.com/ej2/30.1.37/ej2-calendars/styles/material.css" rel="stylesheet" />
    <link href="https://cdn.syncfusion.com/ej2/30.1.37/ej2-splitbuttons/styles/material.css" rel="stylesheet" />

    <script src="https://cdnjs.cloudflare.com/ajax/libs/systemjs/0.19.38/system.js"></script>
    <script src="systemjs.config.js"></script>
<script src="https://cdn.syncfusion.com/ej2/syncfusion-helper.js" type ="text/javascript"></script>
</head>
<body>
    <div id='container'>
        <div id='Grid'></div>
        <table id='datatable' class='e-table'>
            <thead>
                <tr><th>Order ID</th><th>Customer ID</th><th>Employee ID</th><th>Ship Country</th></tr>
            </thead>
            <tbody>
            </tbody>    
        </table>        
    </div>
</body>
</html>

Before Send

processResponse:

The processResponse method is executed after data is received from the server and before it is passed to the UI component. This method is responsible for parsing, transforming, or validating the server response to match the structure expected by Syncfusion controls. It can accept multiple optional arguments, allowing for customization based on specific requirements.

Use cases include:

  • Formatting nested or wrapped data (for example, extracting data.items and data.count).
  • Normalizing inconsistent field names.
  • Handling API-specific pagination or error formats.
  • Removing metadata or enriching records before binding.
import { DataManager, Query, ReturnOption, ODataV4Adaptor } from '@syncfusion/ej2-data';
import { compile } from '@syncfusion/ej2-base';

var template = '<tr><td>${Sno}</td><td>${OrderID}</td><td>${CustomerID}</td><td>${EmployeeID}</td><td>${ShipCountry}</td></tr>';
const compiledFunction: Function = compile(template);

const SERVICE_URI: string = 'https://services.odata.org/V4/Northwind/Northwind.svc/Orders';
const table: HTMLElement | null = document.getElementById('datatable');

class CustomAdaptor extends ODataV4Adaptor {
    public processResponse(data: any) {
        if (data && data.value && Array.isArray(data.value)) {
            data.value.forEach((item: { Sno: any; }, index: number) => {
                item.Sno = index + 1;
            });
        }
        return super.processResponse(data);
    }
}

new DataManager({ url: SERVICE_URI, adaptor: new CustomAdaptor() })
    .executeQuery(new Query().take(8))
    .then((e: ReturnOption) => {
        console.log("Fetched Data:", e.result);
        (e.result as Object[]).forEach((data: Object) => {
            table!.innerHTML += compiledFunction(data)[0].outerHTML;
        });
    });
<!DOCTYPE html>
<html lang="en">

<head>
    <title>EJ2 Grid</title>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta name="description" content="Typescript Grid Control" />
    <meta name="author" content="Syncfusion" />
    <link href="index.css" rel="stylesheet" />
    <link href="https://cdn.syncfusion.com/ej2/30.1.37/ej2-base/styles/material.css" rel="stylesheet" />
    <link href="https://cdn.syncfusion.com/ej2/30.1.37/ej2-grids/styles/material.css" rel="stylesheet" />
    <link href="https://cdn.syncfusion.com/ej2/30.1.37/ej2-buttons/styles/material.css" rel="stylesheet" />
    <link href="https://cdn.syncfusion.com/ej2/30.1.37/ej2-popups/styles/material.css" rel="stylesheet" />
    <link href="https://cdn.syncfusion.com/ej2/30.1.37/ej2-navigations/styles/material.css" rel="stylesheet" />
    <link href="https://cdn.syncfusion.com/ej2/30.1.37/ej2-dropdowns/styles/material.css" rel="stylesheet" />
    <link href="https://cdn.syncfusion.com/ej2/30.1.37/ej2-lists/styles/material.css" rel="stylesheet" />
    <link href="https://cdn.syncfusion.com/ej2/30.1.37/ej2-inputs/styles/material.css" rel="stylesheet" />
    <link href="https://cdn.syncfusion.com/ej2/30.1.37/ej2-calendars/styles/material.css" rel="stylesheet" />
    <link href="https://cdn.syncfusion.com/ej2/30.1.37/ej2-splitbuttons/styles/material.css" rel="stylesheet" />

    <script src="https://cdnjs.cloudflare.com/ajax/libs/systemjs/0.19.38/system.js"></script>
    <script src="systemjs.config.js"></script>
<script src="https://cdn.syncfusion.com/ej2/syncfusion-helper.js" type ="text/javascript"></script>
</head>
<body>
    <div id='container'>
        <div id='Grid'></div>
        <table id='datatable' class='e-table'>
            <thead>
                <tr><th>S No</th><th>Order ID</th><th>Customer ID</th><th>Employee ID</th><th>Ship Country</th></tr>
            </thead>
            <tbody>
            </tbody>    
        </table>        
    </div>
</body>
</html>

CustomAdaptor Process Response

Implement the custom adaptor:

The following example demonstrates how to extend the ODataV4Adaptor to create a custom adaptor and bind data to a table using the Syncfusion EJ2 TypeScript DataManager.

1. Import required modules:

import { DataManager, Query, ReturnOption, ODataV4Adaptor } from '@syncfusion/ej2-data';
import { compile } from '@syncfusion/ej2-base';

2. Create a custom adaptor by extending ODataV4Adaptor:

class CustomAdaptor extends ODataV4Adaptor {
  // Override adaptor methods here as needed
}

3. Configure the DataManager:

const template: string = '<tr><td>${OrderID}</td><td>${CustomerID}</td><td>${EmployeeID}</td><td>${ShipCountry}</td></tr>';
const compiledFunction: Function = compile(template);

const SERVICE_URI: string = 'https://localhost:xxxx/odata/orders';
const table: HTMLElement | null = document.getElementById('datatable');

new DataManager({ url: SERVICE_URI, adaptor: new CustomAdaptor() })
    .executeQuery(new Query())
    .then((e: ReturnOption) => {
      (e.result as Object[]).forEach((data: Object) => {
        if (table) {
          table.innerHTML += compiledFunction(data)[0].outerHTML;
        }
      });
    })
    .catch((error) => {
      console.error('Data fetch error:', error);
    });

This setup allows you to customize the adaptor’s behavior by overriding methods in the CustomAdaptor class, while efficiently fetching and displaying data from your OData service.

const template: string = '<tr><td>${OrderID}</td><td>${CustomerID}</td><td>${EmployeeID}</td><td>${ShipCountry}</td></tr>';
const compiledFunction: Function = compile(template);

const SERVICE_URI: string = 'https://localhost:xxxx/odata/orders';
const table: HTMLElement | null = document.getElementById('datatable');

new DataManager({ url: SERVICE_URI, adaptor: new CustomAdaptor() })
  .executeQuery(new Query())
  .then((e: ReturnOption) => {
    (e.result as Object[]).forEach((data: Object) => {
      if (table) {
        table.innerHTML += compiledFunction(data)[0].outerHTML;
      }
    });
  })
  .catch((error) => {
    console.error('Data fetch error:', error);
  });
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>CustomAdaptor in Syncfusion TS DataManager</title>
</head>
<body>
  <div id='container'>
    <p id="error-message" style="text-align:center;color:red">
      <table id="datatable" class="e-table" border="1">
        <thead>
          <tr><th>Order ID</th><th>Customer ID</th><th>Employee ID</th><th>Ship Country</th></tr>
        </thead>
        <tbody>
        </tbody>
      </table>
  </div>
  <script src="js/index.js"></script>
</body>
</html>
using CustomAdaptor.Models;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.OData.Query;

namespace CustomAdaptor.Controllers
{
  [Route("[controller]")]
  [ApiController]
  public class OrdersController : ControllerBase
  {
    /// <summary>
    /// Retrieves all orders.
    /// </summary>
    /// <returns>The collection of orders.</returns>
    [HttpGet]
    [EnableQuery]
    public IActionResult Get()
    {
      var data = OrdersDetails.GetAllRecords().AsQueryable();
      return Ok(data);
    }
  }
}
using System.ComponentModel.DataAnnotations;

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

    }
    public OrdersDetails(
    int OrderID, string CustomerId, int EmployeeId, string ShipCountry)
    {
      this.OrderID = OrderID;
      this.CustomerID = CustomerId;
      this.EmployeeID = EmployeeId;
      this.ShipCountry = ShipCountry;
    }

    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, "Denmark"));
          order.Add(new OrdersDetails(code + 2, "ANATR", i + 2, "Brazil"));
          order.Add(new OrdersDetails(code + 3, "ANTON", i + 1, "Germany"));
          order.Add(new OrdersDetails(code + 4, "BLONP", i + 3, "Austria"));
          order.Add(new OrdersDetails(code + 5, "BOLID", i + 4, "Switzerland"));
          code += 5;
        }
      }
      return order;
    }
    [Key]
    public int? OrderID { get; set; }
    public string? CustomerID { get; set; }
    public int? EmployeeID { get; set; }
    public string? ShipCountry { get; set; }
  }
}

Error handling

Proper error handling is essential when working with remote data sources to ensure a robust user experience and facilitate easier debugging. Syncfusion EJ2 TypeScript DataManager provides built-in mechanisms to capture and respond to errors that occur during data operations.

When using a CustomAdaptor, you must explicitly invoke error callbacks in case of failures. This ensures that the DataManager triggers the appropriate error events on the bound UI components, such as Grid, allowing you to handle errors gracefully in the UI.

Use the following example to understand how to implement error handling in a CustomAdaptor:

var template = '<tr><td>${OrderID}</td><td>${CustomerID}</td><td>${EmployeeID}</td><td>${ShipCountry}</td></tr>';
var compiledFunction = ej.base.compile(template);

var table = document.getElementById('datatable');
var errorMessage = document.getElementById('error-message');

class CustomAdaptor extends ej.data.ODataV4Adaptor {
    // Override adaptor methods here if needed.
}

const SERVICE_URI = 'https://localhost:xxxx/odata/Orders'; // Replace xxxx with your actual port number.

new ej.data.DataManager({ url: SERVICE_URI, adaptor: new CustomAdaptor() })
    .executeQuery(new ej.data.Query())
    .then(function (e) {
        e.result.forEach(function (record) {
            if (table) {
                table.innerHTML += compiledFunction(record)[0].outerHTML;
            }
        });
    })
    .catch(function (error) {
        console.error("Data fetch error:", error);
        if (errorMessage) {
            errorMessage.textContent = "Failed to load data. Please check your connection or service and try again.";
        }
    });
<!DOCTYPE html>
<html lang="en">

<head>
    <title>EJ2 Grid</title>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta name="description" content="Typescript Grid Control" />
    <meta name="author" content="Syncfusion" />
    <link href="index.css" rel="stylesheet" />
    <link href="https://cdn.syncfusion.com/ej2/30.1.37/ej2-base/styles/material.css" rel="stylesheet" />
    <link href="https://cdn.syncfusion.com/ej2/30.1.37/ej2-grids/styles/material.css" rel="stylesheet" />
    <link href="https://cdn.syncfusion.com/ej2/30.1.37/ej2-buttons/styles/material.css" rel="stylesheet" />
    <link href="https://cdn.syncfusion.com/ej2/30.1.37/ej2-popups/styles/material.css" rel="stylesheet" />
    <link href="https://cdn.syncfusion.com/ej2/30.1.37/ej2-navigations/styles/material.css" rel="stylesheet" />
    <link href="https://cdn.syncfusion.com/ej2/30.1.37/ej2-dropdowns/styles/material.css" rel="stylesheet" />
    <link href="https://cdn.syncfusion.com/ej2/30.1.37/ej2-lists/styles/material.css" rel="stylesheet" />
    <link href="https://cdn.syncfusion.com/ej2/30.1.37/ej2-inputs/styles/material.css" rel="stylesheet" />
    <link href="https://cdn.syncfusion.com/ej2/30.1.37/ej2-calendars/styles/material.css" rel="stylesheet" />
    <link href="https://cdn.syncfusion.com/ej2/30.1.37/ej2-splitbuttons/styles/material.css" rel="stylesheet" />

    <script src="https://cdnjs.cloudflare.com/ajax/libs/systemjs/0.19.38/system.js"></script>
    <script src="systemjs.config.js"></script>
<script src="https://cdn.syncfusion.com/ej2/syncfusion-helper.js" type ="text/javascript"></script>
</head>
<body>
    <div id='container'>
        <p id="error-message" style="text-align:center;color:red"></p>
        <table id="datatable" class="e-table" border="1">
            <thead>
                <tr>
                    <th>Order ID</th>
                    <th>Customer ID</th>
                    <th>Employee ID</th>
                    <th>Ship Country</th>
                </tr>
            </thead>
            <tbody>
            </tbody>
        </table>      
    </div>
</body>
</html>

Error Handling

CacheAdaptor

The CacheAdaptor is a powerful feature in Syncfusion’s EJ2 TypeScript DataManager that enhances application performance by reducing redundant server calls. It caches previously fetched data on the client side, allowing your app to quickly retrieve cached data instead of making repeated HTTP requests when navigating through pages or revisiting previously loaded data. This significantly improves the responsiveness of data-bound components and reduces server load, especially in applications with large datasets or frequent data navigation.

You can enable this functionality by setting the enableCache property to true in the DataManager configuration.

How it works:

  • When enableCache is set to true, the DataManager generates a unique ID at initialization and uses it to store previously loaded page data in cache memory. This enables efficient data retrieval without redundant server requests.

  • The cache is automatically cleared when data actions such as sorting, filtering, grouping, searching, or CRUD operations (Create, Read, Update, Delete) are performed.

  • This feature is supported by all adaptors in DataManager, ensuring consistent caching behavior across different data sources.

Consider a customer order management dashboard that displays thousands of orders in a paginated grid with filtering and sorting options. By enabling enableCache in the DataManager:

  • When the user first navigates to page 2, the data is fetched from the server and stored in the cache.

  • If the user later returns to page 2, the DataManager serves the data directly from the cache, with no additional request sent.

  • If the user applies a filter or sort, the cache is cleared to ensure new data is retrieved fresh from the server.

  • This results in a smoother user experience, reduced load times, and improved overall performance.

This caching behavior is especially beneficial for:

  • Applications with large datasets and frequent navigation.

  • Scenarios with network latency or bandwidth constraints.

  • Reducing costs in cloud-hosted backends by minimizing API calls.

The following example demonstrates how to enable caching using the enableCache property in the DataManager:

import { DataManager, Query, ReturnOption } from '@syncfusion/ej2-data';
import { compile } from '@syncfusion/ej2-base';

let template: string = '<tr><td>${OrderID}</td><td>${CustomerID}</td><td>${EmployeeID}</td></tr>';

let compiledFunction: Function = compile(template);

const SERVICE_URI: string = 'https://services.syncfusion.com/js/production/api/orders';

let table: HTMLElement = (<HTMLElement>document.getElementById('datatable'));

new DataManager({ url: SERVICE_URI }).executeQuery(new Query().take(8)).then((e: ReturnOption) => {
    const results = (e.result as { items: Order[] }).items;
    results.forEach((data: Object) => {
        table.appendChild(compiledFunction(data)[0]);
    });
});

interface Order {
    OrderID: number;
    CustomerID: string;
    EmployeeID: number;
  }




import { Grid } from '@syncfusion/ej2-grids';
import { DataManager, WebApiAdaptor } from '@syncfusion/ej2-data';

let data: Object = new DataManager({
  url: "https://services.syncfusion.com/js/production/api/orders",
  adaptor: new WebApiAdaptor,
  crossDomain: true,
  enableCache: true // Enables caching to prevent repeated HTTP requests.
});

let grid: Grid = new Grid({
  dataSource: data,
  columns: [
    { field: 'OrderID', headerText: 'Order ID', textAlign: 'Right', width: 100, type: 'number' },
    { field: 'CustomerID', width: 100, headerText: 'Customer ID', type: 'string' },
    { field: 'EmployeeID', headerText: 'Employee ID', textAlign: 'Right', width: 100 },
    { field: 'OrderDate', headerText: 'Order Date', format: 'yMd', width: 120, textAlign: 'Right', type:'Date' },
  ],
});

grid.appendTo('#Grid');
<!DOCTYPE html>
<html lang="en">

<head>
    <title>EJ2 DataManger</title>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta name="description" content="Typescript DataManger" />
    <meta name="author" content="Syncfusion" />
    <link href="index.css" rel="stylesheet" />
    <script src="https://cdnjs.cloudflare.com/ajax/libs/systemjs/0.19.38/system.js"></script>
    <script src="systemjs.config.js"></script>
    <style>
    .e-table {
      border: solid 1px #e0e0e0;
      border-collapse: collapse;
      font-family: Roboto;
    }

    .e-table td,
    .e-table th {
      border-style: solid;
      border-width: 1px 0 0;
      border-color: #e0e0e0;
      display: table-cell;
      font-size: 14px;
      line-height: 20px;
      overflow: hidden;
      padding: 8px 21px;
      vertical-align: middle;
      white-space: nowrap;
      width: auto;
    }
  </style>
<script src="https://cdn.syncfusion.com/ej2/syncfusion-helper.js" type ="text/javascript"></script>
</head>

<body>
    <div id='loader'>Loading....</div>
    <div id='container'>
        <table id='datatable' class='e-table'>
            <thead>
                <tr>
                    <th>Order ID</th>
                    <th>Customer ID</th>
                    <th>Employee ID</th>
                </tr>
            </thead>
            <tbody>
            </tbody>
        </table>
    </div>
</body>

</html>