Advanced scenarios in Syncfusion EJ2 TypeScript DataManager
22 Jul 202524 minutes to read
This section covers advanced features such as offline mode, load on demand, deferred execution, and error handling in the Syncfusion EJ2 TypeScript DataManager.
Offline mode
Offline mode in Syncfusion EJ2 TypeScript DataManager enables full client-side data processing by fetching data from the server once and then performing all subsequent operations (such as filtering, sorting, paging, and grouping) locally, without additional network requests.
This feature is ideal for:
-
Applications with static or infrequently changing datasets.
-
Reducing repeated server calls to enhance performance.
-
Supporting offline first workflows where a persistent internet connection is not guaranteed.
You can enable this feature by setting the offline property to true when creating the DataManager instance. When the offline property is set to true, the DataManager fetches data from the server once and then performs all subsequent operations on the locally stored data, ensuring faster and responsive UI interactions without further server round-trips.
In remote data binding, each call to executeQuery sends a request to the server for processing to avoid repeated server postbacks, you can set the DataManager to load all data during initialization and handle query processing entirely on the client-side.
The following sample demonstrates how to enable offline mode:
import { DataManager, 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></tr>';
let compiledFunction: Function = compile(template);
const SERVICE_URI: string = 'https://services.odata.org/V4/Northwind/Northwind.svc/Orders/?$top=7';
let table: HTMLElement = (<HTMLElement>document.getElementById('datatable'));
let dm: DataManager = new DataManager({
url: SERVICE_URI,
adaptor: new ODataV4Adaptor,
offline: true
});
dm.ready.then((e: ReturnOption) => {
(<Object[]>e.result).forEach((data: Object) => {
table.appendChild(compiledFunction(data)[0]);
});
});<!DOCTYPE html>
<html lang="en">
<head>
<title>EJ2 DataManager</title>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="description" content="Typescript DataManager 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>
<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></tr>
</thead>
<tbody>
</tbody>
</table>
</div>
</body>
</html>Load on demand
Load on demand is an efficient technique that optimizes performance and reduces bandwidth usage by fetching only a specific subset of data from the server, rather than loading the entire dataset at once. This approach is particularly beneficial for applications dealing with large datasets, ensuring faster load times and improved responsiveness.
You can achieve load on demand using the Query.page method in Syncfusion EJ2 TypeScript DataManager. This method requests a specific page of data from the server, based on the given page number and page size.
For example, an employee directory in an HR portal displays thousands of records. Instead of loading all employees at once, the Grid uses Query.page to retrieve only the records for the current page, loading data on demand as users navigate through pages.
The following code example demonstrates the implementation of load on demand using DataManager:
import { DataManager, Query, ReturnOption, UrlAdaptor } from '@syncfusion/ej2-data';
import { compile } from '@syncfusion/ej2-base';
interface Employee {
EmployeeID: number;
Employees: string;
Designation: string;
Location: string;
Check: boolean;
}
let template: string ='<tr><td>${EmployeeID }</td><td>${Employees}</td><td>${Designation}</td><td>${Location}</td><td>${Status}</td></tr>';
let table: HTMLElement = <HTMLElement>document.getElementById('datatable');
let compiledFunction: Function = compile(template);
const dataManager = new DataManager({
url: "https://services.syncfusion.com/js/production/api/UrlDataSource",
adaptor: new UrlAdaptor(),
crossDomain: true
});
const btnLoad = document.getElementById("submit") as HTMLButtonElement;
const inputFrom = document.getElementById("pageIndex") as HTMLInputElement;
const inputTo = document.getElementById("pageSize") as HTMLInputElement;
// Initial load: page 1, size 5.
let query = new Query().page(1, 5);
function renderTable(data: Employee[]) {
data.forEach((data: Object) => {
table.appendChild(compiledFunction(data)[0]);
});
}
// Execute initial query.
dataManager.executeQuery(query).then((e: ReturnOption) => {
renderTable(e.result as Employee[]);
}).catch(console.error);
// Load data on demand button click.
btnLoad.addEventListener("click", () => {
const pageIndex = parseInt(inputFrom.value);
const pageSize = parseInt(inputTo.value);
if (!isNaN(pageIndex) && !isNaN(pageSize) && pageIndex > 0 && pageSize > 0) {
const tempQuery = new Query().page(pageIndex, pageSize);
dataManager.executeQuery(tempQuery).then((e: ReturnOption) => {
renderTable(e.result as Employee[]);
}).catch(console.error);
}
});<!DOCTYPE html>
<html lang="en">
<head>
<title>Syncfusion DataManager</title>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<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>
<style>
body {
margin-top: 40px;
font-family: Roboto, sans-serif;
}
#container {
max-width: 1000px;
margin: auto;
}
table.e-table {
width: 100%;
border-collapse: collapse;
border: 1px solid #e0e0e0;
}
table.e-table th,
table.e-table td {
border: 1px solid #e0e0e0;
padding: 10px;
text-align: left;
white-space: nowrap;
}
table.e-table th {
background-color: #343a40;
color: white;
}
.form-section {
display: flex;
align-items: center;
gap: 16px;
margin-bottom: 20px;
border-bottom: 1px solid #ccc;
padding-bottom: 12px;
}
.form-group {
display: flex;
align-items: center;
gap: 8px;
}
.form-group label {
min-width: 100px;
font-weight: 600;
white-space: nowrap;
}
.form-group input {
width: 120px;
padding: 6px;
border: 1px solid #ccc;
border-radius: 4px;
}
.btn-primary {
background-color: #007bff;
color: white;
border: none;
padding: 8px 6px;
font-size: 14px;
border-radius: 4px;
cursor: pointer;
transition: background-color 0.2s ease;
}
.btn-primary:hover {
background-color: #0056b3;
}
.btn-primary:active {
background-color: #004494;
}
</style>
<script src="https://cdn.syncfusion.com/ej2/syncfusion-helper.js" type ="text/javascript"></script>
</head>
<body>
<div id="container">
<div class="form-section">
<div class="form-group">
<label for="pageIndex">Page Index:</label>
<input type="text" id="pageIndex" class="form-control" placeholder="e.g. 1">
</div>
<div class="form-group">
<label for="pageSize">Page Size:</label>
<input type="text" id="pageSize" class="form-control" placeholder="e.g. 5">
</div>
<button id="submit" class="btn btn-primary">Load data on demand</button>
</div>
<table id="datatable" class="e-table">
<thead>
<tr>
<th>Employee ID</th>
<th>Employee Name</th>
<th>Designation</th>
<th>Location</th>
<th>Status</th>
</tr>
</thead>
<tbody></tbody>
</table>
</div>
</body>
</html>Deferred execution & error handling
Deferred execution & error handling is a technique used in asynchronous programming to manage operations that take time to complete, such as network requests, loading data from a server or file access.
This approach improves code readability, maintainability, and robustness by separating the logic for successful execution (then, resolve) from error handling (catch, reject). It enables chaining of asynchronous tasks and ensures that errors are caught and managed gracefully, preventing unexpected application crashes.
To achieve this, the following concepts are used:
-
catch- Handles errors or rejections that occur during the asynchronous operation.
-
promise- Represents the eventual completion (or failure) of an asynchronous operation and its resulting value. If it fails, it passes an error or reason to the rejection handler for appropriate processing.
-
reject- Indicates that the asynchronous operation has failed and passes an error or reason to the handler.
-
resolve- Indicates that the asynchronous operation has completed successfully and passes the result to the handler.
-
then- Executes the logic that run after the asynchronous operation completes successfully. It allows chaining of further actions.
The following sample demonstrates how to use deferred execution & error handling:
import { DataManager, Query, Deferred, ODataV4Adaptor, UrlAdaptor } from '@syncfusion/ej2-data';
import { ChangeEventArgs, Switch } from '@syncfusion/ej2-buttons';
const SERVICE_URI = 'https://services.odata.org/V4/Northwind/Northwind.svc/Orders/';
const CUSTOMER_URI = 'https://services.odata.org/V4/Northwind/Northwind.svc/Customers/';
const table = document.querySelector('#datatable');
const tbody = (table as HTMLElement).querySelector('tbody');
const messageDiv = document.getElementById('message') as HTMLElement;
let switchObj: Switch = new Switch({
cssClass: 'handle-text',
change: change
});
switchObj.appendTo('#switch1');
switchObj.toggle();
function fetchDataWithAdaptor(url: string, adaptor: any, deferred: Deferred): void {
const manager = new DataManager({
url,
adaptor,
crossDomain: true
});
manager.executeQuery(new Query())
.then((response) => {
if ('result' in response) {
deferred.resolve((response as { result: Order[] }).result);
}
})
.catch((error) => {
deferred.reject(error);
});
}
function handleResult(deferred) {
deferred.promise
.then((result) => {
(tbody as HTMLElement).innerHTML = '';
result.forEach((item: Order) => {
const row = document.createElement('tr');
row.innerHTML = `
<td>${item.OrderID}</td>
<td>${item.CustomerID}</td>
<td>${item.EmployeeID}</td>
`;
(tbody as HTMLElement).appendChild(row);
});
messageDiv.innerText = '✅ Data loaded successfully';
messageDiv.style.color = 'green';
})
.catch(() => {
(tbody as HTMLElement).innerHTML = '';
messageDiv.innerText = '❌ Error loading data';
messageDiv.style.color = 'red';
});
}
// Initial load.
const initialDeferred = new Deferred();
handleResult(initialDeferred);
fetchDataWithAdaptor(SERVICE_URI, new ODataV4Adaptor(), initialDeferred);
function change(args:ChangeEventArgs): void {
const newDeferred = new Deferred();
const useCustomer = switchObj.checked;
const newUrl = useCustomer ? SERVICE_URI : CUSTOMER_URI;
const newAdaptor = useCustomer ? new ODataV4Adaptor() : new UrlAdaptor();
handleResult(newDeferred);
fetchDataWithAdaptor(newUrl, newAdaptor, newDeferred);
}
interface Order {
OrderID: number;
CustomerID: string;
EmployeeID: number;
ShipCity: string;
ShipCountry: string;
}<!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="https://cdn.syncfusion.com/ej2/32.1.19/ej2-base/styles/fabric.css" rel="stylesheet" />
<link href="https://cdn.syncfusion.com/ej2/32.1.19/ej2-buttons/styles/bootstrap.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>
<style>
#loadData {
background-color: #007bff;
color: white;
border: none;
padding: 10px 20px;
font-size: 14px;
border-radius: 4px;
cursor: pointer;
margin: 20px 60px;
font-family: Roboto, sans-serif;
transition: background-color 0.3s ease;
}
#loadData:hover {
background-color: #0056b3;
}
#datatable {
width: 90%;
margin: 0 auto;
border-collapse: collapse;
font-family: Roboto, sans-serif;
font-size: 14px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
#datatable thead {
background-color: #007bff;
color: white;
}
#datatable th,
#datatable td {
padding: 12px 15px;
border: 1px solid #ddd;
text-align: left;
}
#datatable tbody tr:nth-child(even) {
background-color: #f9f9f9;
}
#datatable tbody tr:hover {
background-color: #f1f1f1;
}
#message {
text-align: center;
margin-left: 60px;
color: red;
padding-bottom: 20px;
}
/* Customize Handle and Bar Switch */
.e-switch-wrapper.handle-text {
width: 200px;
height: 30px;
}
.e-switch-wrapper.handle-text .e-switch-handle {
width: 90px;
height: 20px;
left: 10px;
background-color: #fff;
}
.e-switch-wrapper.handle-text .e-switch-inner,
.e-switch-wrapper.handle-text .e-switch-handle {
border-radius: 0;
}
.e-switch-wrapper.handle-text .e-switch-handle.e-switch-active {
left: 125px;
}
.e-switch-wrapper.handle-text .e-switch-inner.e-switch-active,
.e-switch-wrapper.handle-text:hover .e-switch-inner.e-switch-active .e-switch-on {
background-color: #4d841d;
border-color: #4d841d;
}
.e-switch-wrapper.handle-text .e-switch-inner,
.e-switch-wrapper.handle-text .e-switch-off {
background-color: #e3165b;
border-color: #e3165b;
}
.e-switch-wrapper.handle-text .e-switch-inner:after,
.e-switch-wrapper.handle-text .e-switch-inner:before {
font-size: 10px;
position: absolute;
line-height: 21px;
font-family: "Helvetica", sans-serif;
z-index: 1;
height: 100%;
transition: all 200ms cubic-bezier(0.445, 0.05, 0.55, 0.95);
}
.e-switch-wrapper.handle-text .e-switch-inner:before {
content: "UrlAdaptor";
padding-top: 4px;
color: #e3165b;
left: 12px;
}
.e-switch-wrapper.handle-text .e-switch-inner:after {
content: "ODataV4Adaptor";
right: 10px;
color: #fff;
padding-top: 4px;
}
.e-switch-wrapper.handle-text .e-switch-inner.e-switch-active:before {
color: #fff;
}
.e-switch-wrapper.handle-text .e-switch-inner.e-switch-active:after {
color: #4d841d;
}
.e-switch-wrapper.handle-text:not(.e-switch-disabled):hover .e-switch-handle:not(.e-switch-active) {
background-color: #fff;
}
.switch-container {
display: flex;
align-items: center;
gap: 10px;
/* space between switch and label */
margin: 20px 60px;
font-family: Roboto, sans-serif;
}
.switch-label {
font-size: 14px;
}
</style>
<script src="https://cdn.syncfusion.com/ej2/syncfusion-helper.js" type ="text/javascript"></script>
</head>
<body>
<div id='container'>
<div style="display: flex;align-items: center;margin: 20px 60px; gap: 10px;font-weight:bold">
<span class="switch-label">Toggle Data</span>
<input type="checkbox" id="switch1" />
</div>
<div id='message'></div>
<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>