
Syncfusion Angular Data Manager
- 184 installs
- Updated August 4, 2026
- syncfusion/angular-ui-components-skills
Use syncfusion-angular-data-manager for development tasks
About
syncfusion-angular-data-manager: A skill for development. This provides functionality for development workflows.
- syncfusion-angular-data-manager
Syncfusion Angular Data Manager by the numbers
- 184 all-time installs (skills.sh)
- +5 installs in the week ending Jul 27, 2026 (Skillselion tracking)
- Ranked #2,160 of 4,347 Backend & APIs skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/syncfusion/angular-ui-components-skills --skill syncfusion-angular-data-managerAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 184 |
|---|---|
| Last updated | August 4, 2026 |
| Repository | syncfusion/angular-ui-components-skills ↗ |
What it does
Use syncfusion-angular-data-manager for development tasks
Files
Syncfusion Angular DataManager
When to Use This Skill
Use this skill when you need to:
- Bind local JSON arrays to Angular components
- Connect Angular apps to remote REST, OData, or GraphQL services
- Implement querying, filtering, sorting, and paging on data
- Perform CRUD operations (Create, Read, Update, Delete) with data persistence
- Work offline and sync data when reconnected
- Transform and filter data using Query class
- Customize data communication with middleware
- Cache data to reduce server requests
- Handle different data source protocols automatically
This is your primary reference for:
- Any data operation in Syncfusion Angular components
- Universal data binding across all components
- Data source configuration and selection
- Async operations and error handling
- Performance optimization with caching
---
Component Overview
The DataManager is Syncfusion's universal data management module that acts as a bridge between your Angular application and any data source. It provides:
- 10 Built-in Adaptors — Support for JSON, OData v3/v4, REST, Web API, Web Methods, GraphQL, and custom sources
- Query Class — Powerful filtering, sorting, grouping, and paging API
- CRUD Operations — Insert, update, delete with automatic handling
- Promise-based Async — Native JavaScript Promise support
- Middleware Customization — Pre/post request hooks for authentication and transformation
- Offline Support — Work without server connection, sync when online
- Caching — Reduce server load and improve performance
- Type Safety — Full TypeScript definitions
---
⚠️ Security & Trust Boundary
- This skill generates code only, the agent does not execute data operations or fetch remote endpoints — all DataManager interactions occur solely within the user's application at runtime.
- Generated code must treat all third-party API responses as untrusted input, never bind to unvalidated or user-provided URLs, and ensure authentication is enforced on all remote endpoints.
---
Key Concepts & Trigger Keywords
| Concept | When to Use | Key Methods |
|---|---|---|
| Local Data Binding | Working with in-memory JSON arrays | JsonAdaptor, executeLocal |
| Remote Data Binding | Calling API endpoints for data | UrlAdaptor, WebApiAdaptor, executeQuery |
| Query Construction | Filtering, sorting, grouping data | from(), where(), select(), sortBy(), take() |
| CRUD Operations | Managing records (add, edit, delete) | insert(), update(), remove(), saveChanges() |
| Adaptor Selection | Choosing the right data source type | 10 adaptors with decision tree |
| Error Handling | Managing failure scenarios | .catch(), try/catch, error status checking |
| Offline Mode | Working without server connection | offline property, localStorage |
| Caching | Improving performance | enableCache, cache clearing strategies |
| Middleware | Customizing requests/responses | applyPreRequestMiddlewares, applyPostMiddlewares |
| Async Patterns | Sequential and parallel operations | async/await, Promise.all(), then/catch |
---
Documentation and Navigation Guide
Getting Started
📄 Read: references/getting-started.md
When to read: Starting new DataManager implementation, understanding package setup, learning TypeScript imports, configuring your first DataManager instance
Topics covered:
- Package installation and dependencies
- Module imports and TypeScript types
- Creating DataManager instances
- Difference between executeLocal and executeQuery
- Basic query execution patterns
---
Data Binding - Local and Remote
📄 Read: references/data-binding.md
When to read: Deciding between client-side and server-side processing, implementing local JSON binding, connecting to remote APIs, understanding executeLocal vs executeQuery patterns
Topics covered:
- Local binding with json property
- Remote binding with url + adaptor
- Client-side vs server-side processing
- Promise-based async patterns
- Basic error handling
---
Querying and Filtering
📄 Read: references/querying-and-filtering.md
When to read: Building queries, filtering by multiple conditions, sorting results, searching data, pagination, grouping records, advanced predicates
Topics covered:
- Query class fundamentals
- from() and select() methods
- where() with operators and predicates
- sortBy(), orderBy(), orderByDescending()
- take(), skip() for pagination
- search() method
- Grouping and aggregation
---
CRUD Operations
📄 Read: references/crud-operations.md
When to read: Adding new records, updating existing data, deleting records, batch operations, CRUD method parameters, persisting changes to server
Topics covered:
- Insert (add new records)
- Update (modify existing records)
- Remove (delete records)
- keyField in CRUD method parameters
- saveChanges() for batch operations
- Error handling and validation
---
10 Adaptors Guide - Complete Reference
📄 Read: references/adaptors-guide.md
When to read: Selecting appropriate data source, understanding adaptor differences, implementing specific adaptor patterns, working with different API types
Topics covered:
- JsonAdaptor — Local JavaScript arrays, no server calls
- ODataAdaptor — OData v3 service endpoints
- ODataV4Adaptor — OData v4 protocol support
- UrlAdaptor — Generic RESTful API endpoints
- WebApiAdaptor — ASP.NET Web API integration
- WebMethodAdaptor — ASP.NET Web Methods
- RemoteSaveAdaptor — Client-side queries + server-side CRUD
- GraphQLAdaptor — GraphQL query language support
- CustomDataAdaptor — Custom request/response handling
- CustomAdaptor — Extending built-in adaptors
- Adaptor decision tree
- Comparison matrix for all 10
- Code examples for each adaptor
---
Middleware Customization
📄 Read: references/middleware-customization.md
When to read: Adding authentication headers, transforming requests/responses, logging API calls, implementing custom business logic
Topics covered:
- Pre-request middleware (applyPreRequestMiddlewares)
- Post-request middleware (applyPostMiddlewares)
- Custom headers (Authorization, etc.)
- Authentication patterns (Bearer tokens)
- Request/response transformation
- Error handling in middleware
---
Caching and Offline Mode
📄 Read: references/caching-offline-mode.md
When to read: Improving performance with caching, working offline when server unavailable, implementing sync strategies, managing data persistence
Topics covered:
- Offline property configuration
- localStorage for data persistence
- Cache auto-clearing on CRUD
- Manual cache management
- Online/offline workflow
- Sync strategies
---
Advanced Features
📄 Read: references/advanced-features.md
When to read: Load on demand for large datasets, lazy loading relationships, pagination strategies, deferred operations, async/await patterns, memory management, state persistence
Topics covered:
- Load on demand pattern
- Lazy loading with expand()
- Pagination (client & server-side)
- Deferred operations with Promises
- Async/await patterns
- Promise.all() for parallel operations
- Error handling with status codes
- Memory management strategies
- Type safety with TypeScript
- State persistence patterns
---
How-To Recipes
📄 Read: references/how-to-recipes.md
When to read: Quick solutions for common scenarios, best practices, troubleshooting, pattern examples
Topics covered:
- Work in offline mode
- Send additional parameters
- Add custom headers
- Implement authentication
- Handle errors gracefully
- Common patterns
- Performance optimization tips
- Troubleshooting guides
---
Quick Start Example
Setup and Basic Data Binding
Step 1: Install Package
npm install @syncfusion/ej2-dataStep 2: Create Component
import { Component, OnInit } from '@angular/core';
import { DataManager, Query, JsonAdaptor, ReturnOption } from '@syncfusion/ej2-data';
@Component({
selector: 'app-data-demo',
templateUrl: './data-demo.component.html',
styleUrls: ['./data-demo.component.css']
})
export class DataDemoComponent implements OnInit {
public orders: object[];
ngOnInit(): void {
// Local data
const data = [
{ OrderID: 10248, CustomerID: 'VINET', EmployeeID: 5, ShipCity: 'Reims' },
{ OrderID: 10249, CustomerID: 'TOMSP', EmployeeID: 6, ShipCity: 'Münster' },
{ OrderID: 10250, CustomerID: 'HANAR', EmployeeID: 4, ShipCity: 'Rio de Janeiro' }
];
// Create DataManager with local data
const dataManager = new DataManager({
json: data,
adaptor: new JsonAdaptor()
});
// Execute query
this.orders = dataManager.executeLocal(
new Query().where('EmployeeID', 'equal', 5)
);
}
}Step 3: Display in Template
<table>
<thead>
<tr>
<th>Order ID</th>
<th>Customer ID</th>
<th>Employee ID</th>
<th>Ship City</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let order of orders">
<td>{{ order.OrderID }}</td>
<td>{{ order.CustomerID }}</td>
<td>{{ order.EmployeeID }}</td>
<td>{{ order.ShipCity }}</td>
</tr>
</tbody>
</table>---
Common Patterns
Remote Server with Promise
const dataManager = new DataManager({
url: 'url',
adaptor: new WebApiAdaptor()
});
// Using Promise
dataManager.executeQuery(new Query().take(10))
.then((result: ReturnOption) => {
this.orders = result.result;
})
.catch((error) => {
console.error('Failed to fetch orders:', error);
});Async/Await Pattern
async fetchOrders(): Promise<void> {
try {
const result = await this.dataManager.executeQuery(
new Query().take(10)
);
this.orders = result.result;
} catch (error) {
console.error('Error:', error);
}
}Filtering with Predicates
const query = new Query()
.where('EmployeeID', 'equal', 5)
.where('ShipCity', 'startswith', 'Rio');
const result = dataManager.executeLocal(query);Server-side Operations
const query = new Query()
.select(['OrderID', 'CustomerID', 'EmployeeID'])
.where('EmployeeID', 'greaterThan', 3)
.sortBy('OrderID')
.take(10)
.skip(0);
dataManager.executeQuery(query).then((result: ReturnOption) => {
this.orders = result.result;
this.totalRecords = result.count;
});---
Key Properties Reference
| Property | Type | Purpose |
|---|---|---|
json | array | Local data array |
url | string | Remote service endpoint |
adaptor | Adaptor | Data source type handler |
headers | object[] | Custom HTTP headers |
offline | boolean | Enable offline mode |
enableCache | boolean | Enable response caching |
crossDomain | boolean | Enable cross-domain requests |
cachingPageSize | number | Paging record count |
timeTillExpiration | number | Cache TTL in milliseconds (default: infinite) |
ignoreOnPersist | string[] | Properties to exclude from persistence |
timeZoneHandling | boolean | Enable timezone offset handling |
---
Error Handling Best Practices
dataManager.executeQuery(query)
.then((result: ReturnOption) => {
if (result.result) {
this.items = result.result;
}
})
.catch((error: any) => {
if (error.status === 401) {
// Redirect to login
console.error('Unauthorized');
} else if (error.status === 403) {
// Access denied
console.error('Forbidden');
} else if (error.status === 500) {
// Server error
console.error('Server error');
} else if (error.status === 0) {
// Network error
console.error('Network error - check connectivity');
}
});---
Common Use Cases
Use Case 1: Display Data Table
Start with getting-started.md → data-binding.md → querying-and-filtering.md
Use Case 2: Implement CRUD Form
Start with adaptors-guide.md → crud-operations.md → advanced-features.md
Use Case 3: Work with Multiple Data Sources
Start with adaptors-guide.md (decision tree) → specific adaptor pattern → middleware-customization.md
Use Case 4: Optimize Large Datasets
Start with advanced-features.md → caching-offline-mode.md → how-to-recipes.md
---
TypeScript Type Safety
// Define your data model
interface Order {
OrderID: number;
CustomerID: string;
EmployeeID: number;
ShipCity: string;
}
// Use generics for type safety
const dataManager: DataManager<Order> = new DataManager({
json: orders,
adaptor: new JsonAdaptor()
});
// Typed results
const result = await dataManager.executeQuery(new Query());
const typedOrders: Order[] = result.result as Order[];---
Performance Optimization Tips
1. Use proper adaptor — Select adaptor matching your backend 2. Filter early — Push filtering to server when possible 3. Enable caching — Reduce redundant server calls 4. Lazy load — Load data on demand for large datasets 5. Pagination — Limit records per request 6. Offline mode — Load once, work offline 7. Select columns — Fetch only needed fields
---
Complete Guide to 10 Syncfusion Angular DataManager Adaptors
Table of Contents
- Overview
- Adaptor Decision Tree
- 1. JsonAdaptor
- 2. ODataAdaptor
- 3. ODataV4Adaptor
- 4. UrlAdaptor
- 5. WebApiAdaptor
- 6. WebMethodAdaptor
- 7. RemoteSaveAdaptor
- 8. GraphQLAdaptor
- 9. CustomDataAdaptor
- 10. CustomAdaptor
- Comparison Matrix
---
Overview
An adaptor handles communication between DataManager and your data source. Each adaptor format requests and interprets responses differently based on the backend's protocol.
When to Use an Adaptor
- JsonAdaptor → In-memory local data
- ODataAdaptor → OData v3 endpoints
- ODataV4Adaptor → OData v4 endpoints
- UrlAdaptor → Generic REST APIs
- WebApiAdaptor → ASP.NET Web API
- WebMethodAdaptor → ASP.NET web methods
- RemoteSaveAdaptor → Client-side queries + server CRUD
- GraphQLAdaptor → GraphQL servers
- CustomDataAdaptor → Custom request/response handling
- CustomAdaptor → Extend built-in adaptors
---
Adaptor Decision Tree
Start: Which data source?
│
├─ Local JSON array in memory?
│ └─ Use: JsonAdaptor
│
└─ Remote API endpoint?
│
├─ OData v3 standard protocol?
│ └─ Use: ODataAdaptor
│
├─ OData v4 standard protocol?
│ └─ Use: ODataV4Adaptor
│
├─ ASP.NET Web API?
│ └─ Use: WebApiAdaptor (recommended for .NET backends)
│
├─ ASP.NET Web Methods?
│ └─ Use: WebMethodAdaptor
│
├─ Generic RESTful API?
│ ├─ Need client-side queries + server-side CRUD?
│ │ └─ Use: RemoteSaveAdaptor
│ │
│ └─ Standard REST patterns?
│ └─ Use: UrlAdaptor
│
├─ GraphQL endpoint?
│ └─ Use: GraphQLAdaptor
│
└─ Need custom logic? Custom request/response format?
├─ FIRST CHOICE: CustomDataAdaptor ⭐ (MOST FLEXIBLE)
│ ├─ Use ANY fetching tool (fetch, axios, Socket.IO, etc.)
│ └─ Return standard: { result, count }
├─ Full control needed? → CustomAdaptor
└─ Standard REST format → UrlAdaptor---
1. JsonAdaptor
Purpose
Works with local JavaScript arrays. All operations happen in the browser (client-side).
When to Use
- Data is already in memory
- Small to medium datasets
- Need instant filtering/sorting without server calls
- Working offline or without network
Configuration
import { DataManager, Query, JsonAdaptor } from '@syncfusion/ej2-data';
const data = [
{ OrderID: 10248, CustomerID: 'VINET', Freight: 32.38 },
{ OrderID: 10249, CustomerID: 'TOMSP', Freight: 11.61 },
{ OrderID: 10250, CustomerID: 'HANAR', Freight: 65.83 }
];
const dataManager = new DataManager({
json: data,
adaptor: new JsonAdaptor()
});Usage Example
// Client-side filtering and sorting
const result = dataManager.executeLocal(
new Query()
.where('Freight', 'greaterThan', 30)
.sortBy('CustomerID')
.take(10)
);Advantages
✓ No network calls ✓ Works offline ✓ Instant operations ✓ Simpler complexity
Limitations
✗ Limited to loaded data ✗ Memory constraints ✗ Can't leverage server processing
---
2. ODataAdaptor
Purpose
Communicates with OData v3 service endpoints using the OData protocol.
When to Use
- Working with OData v3 compliant services
- Need standardized REST protocol
- Server supports $filter, $select, $top, $skip parameters
Configuration
import { DataManager, Query, ODataAdaptor } from '@syncfusion/ej2-data';
const dataManager = new DataManager({
url: 'url',
adaptor: new ODataAdaptor()
});Usage Example
dataManager.executeQuery(
new Query()
.select(['OrderID', 'CustomerID', 'Freight'])
.where('Freight', 'greaterThan', 30)
.sortBy('OrderID')
.take(10)
)
.then((result) => {
console.log(result.result);
console.log(result.count);
});Server Request Format
GET url
?$select=OrderID,CustomerID,Freight
&$filter=Freight gt 30
&$orderby=OrderID
&$top=10---
3. ODataV4Adaptor
Purpose
Supports OData v4 protocol endpoints (newer version of OData v3).
When to Use
- OData v4 compliant services
- Need latest OData standards
- Server implements $expand, $group features
Configuration
import { DataManager, Query, ODataV4Adaptor } from '@syncfusion/ej2-data';
const dataManager = new DataManager({
url: 'url',
adaptor: new ODataV4Adaptor()
});Usage Example
dataManager.executeQuery(
new Query()
.select(['OrderID', 'CustomerID', 'Freight'])
.where('Freight', 'greaterThan', 30)
.expand('Customer') // Navigate to related data
.take(20)
)
.then((result) => {
const orders = result.result;
orders.forEach(order => {
console.log(order.OrderID, order.Customer.CustomerID);
});
});---
4. UrlAdaptor
Purpose
Generic RESTful API adaptor for standard HTTP endpoints. Important: UrlAdaptor sends only POST requests with query parameters in the request body - never GET requests.
When to Use
- Custom REST APIs (POST-based)
- Non-OData compliant endpoints
- Any HTTP service endpoint accepting POST queries
- GraphQL or custom query formats
Configuration
import { DataManager, Query, UrlAdaptor } from '@syncfusion/ej2-data';
const dataManager = new DataManager({
url: 'url',
adaptor: new UrlAdaptor()
});Usage Example
dataManager.executeQuery(
new Query()
.select(['id', 'name', 'price'])
.where('price', 'greaterThan', 50)
.take(10)
)
.then((result) => {
this.orders = result.result;
});Expected Server Response
{
"result": [
{ "OrderID": 10248, "CustomerID": "VINET", "Freight": 32.38 },
{ "OrderID": 10249, "CustomerID": "TOMSP", "Freight": 11.61 }
],
"count": 830
}---
5. WebApiAdaptor
Purpose
Specifically designed for ASP.NET Web API endpoints (recommended for .NET backends).
When to Use
- ASP.NET Web API backends
- Standard REST resource endpoints
- Best practice for .NET integration
Configuration
import { DataManager, Query, WebApiAdaptor } from '@syncfusion/ej2-data';
const dataManager = new DataManager({
url: 'url',
adaptor: new WebApiAdaptor(),
crossDomain: true
});Usage Example
dataManager.executeQuery(
new Query()
.select(['OrderID', 'CustomerID', 'Freight'])
.where('Freight', 'greaterThan', 20)
.sortBy('CustomerID')
.take(25)
)
.then((result) => {
this.items = result.result;
this.pageCount = result.count;
})
.catch((error) => {
console.error('Error:', error);
});Backend Example (C#)
[ApiController]
[Route("api/[controller]")]
public class OrdersController : ControllerBase {
[HttpGet]
public IActionResult Get(int $skip = 0, int $take = 10, string $filter = "", string $select = "") {
// Process OData-like parameters
var data = DBContext.Orders;
if (!string.IsNullOrEmpty($filter)) {
// Parse and apply filter
}
var result = data.Skip($skip).Take($take).ToList();
return Ok(new { Items, Count = data.Count() });
}
[HttpPost]
public IActionResult Post([FromBody] Order order) {
DBContext.Orders.Add(order);
DBContext.SaveChanges();
return Ok(order);
}
[HttpPut("{id}")]
public IActionResult Put(int id, [FromBody] Order order) {
var existing = DBContext.Orders.Find(id);
if (existing == null) return NotFound();
existing.CustomerID = order.CustomerID;
existing.Freight = order.Freight;
DBContext.SaveChanges();
return Ok(existing);
}
[HttpDelete("{id}")]
public IActionResult Delete(int id) {
var order = DBContext.Orders.Find(id);
if (order == null) return NotFound();
DBContext.Orders.Remove(order);
DBContext.SaveChanges();
return Ok();
}
}Expected Response Format
{
"Items": [
{ "OrderID": 10248, "CustomerID": "VINET", "Freight": 32.38 },
{ "OrderID": 10249, "CustomerID": "TOMSP", "Freight": 11.61 }
],
"Count": 830
}---
6. WebMethodAdaptor
Purpose
Integrates with ASP.NET Web Methods (traditional server-side methods exposed via HTTP).
When to Use
- Legacy ASP.NET applications
- Web Method-based services
- Traditional ASMX web services
Configuration
import { DataManager, Query, WebMethodAdaptor } from '@syncfusion/ej2-data';
const dataManager = new DataManager({
url: 'urlervice.asmx/GetOrders',
adaptor: new WebMethodAdaptor()
});Usage Example
dataManager.executeQuery(new Query().take(10))
.then((result) => {
this.orders = result.result;
});---
7. RemoteSaveAdaptor
Purpose
Unique adaptor for hybrid client/server processing: Client-side queries (filtering, sorting, paging) + Server-side CRUD (insert, update, delete).
When to Use
- Need to load all data once, filter locally
- Server-side CRUD for data persistence only
- Reduce CRUD operation server calls
- Large datasets with local filtering/sorting
- Dashboard-like applications
Key Feature
Executes executeQuery() with client-side filtering/sorting, but sends CRUD operations (insert, update, delete) to server.
Configuration
import { DataManager, RemoteSaveAdaptor } from '@syncfusion/ej2-data';
this.http.get<Order[]>('url')
.subscribe((value: Order[]) => {
this.dataManager = new DataManager({
json: value, // Load data locally
adaptor: new RemoteSaveAdaptor(), // Handle persistence on server
url: 'url' // CRUD endpoint,
insertUrl: 'url/insert', // Executed Automaticaly
updateUrl: 'url/update', // Executed Automaticaly
removeUrl: 'url/delete', // Executed Automaticaly
});
});Usage Example
// Filtering/sorting happens CLIENT-SIDE (executeQuery)
this.dataManager.executeQuery(
new Query()
.where('Freight', 'greaterThan', 30)
.sortBy('CustomerID')
.take(10)
)
.then((result) => {
this.filteredItems = result.result; // Client-side filtered
});
// CRUD operations go to SERVER
const newOrder = { CustomerID: 'NEW01', Freight: 25 };
this.dataManager.insert(newOrder, 'Orders') // Calls server: POST /api/orders
.then(() => console.log('Inserted'));
this.dataManager.update('OrderID', { OrderID: 10248, Freight: 50 }, 'Orders')
.then(() => console.log('Updated')); // Calls server: PUT /api/orders/10248
this.dataManager.remove('OrderID', 10249, 'Orders')
.then(() => console.log('Deleted')); // Calls server: DELETE /api/orders/10249Backend Response Format
{
"result": [
{ "OrderID": 10248, "CustomerID": "VINET", "Freight": 32.38 },
{ "OrderID": 10249, "CustomerID": "TOMSP", "Freight": 11.61 }
],
"count": 830
}C# Backend Example
[ApiController]
[Route("api/[controller]")]
public class OrdersController : ControllerBase {
[HttpGet]
public IActionResult Get() {
var orders = DBContext.Orders.ToList();
return Ok(new { result = orders, count = orders.Count() });
}
[HttpPost]
public IActionResult Post([FromBody] Order order) {
DBContext.Orders.Add(order);
DBContext.SaveChanges();
return Ok(order);
}
[HttpPut("{id}")]
public IActionResult Put(int id, [FromBody] Order order) {
var existing = DBContext.Orders.Find(id);
if (existing != null) {
existing.CustomerID = order.CustomerID;
existing.Freight = order.Freight;
DBContext.SaveChanges();
}
return Ok(existing);
}
[HttpDelete("{id}")]
public IActionResult Delete(int id) {
var order = DBContext.Orders.Find(id);
if (order != null) {
DBContext.Orders.Remove(order);
DBContext.SaveChanges();
}
return Ok();
}
}Advantages
✓ Load once, work offline locally ✓ Instant filtering/sorting ✓ Server persists changes ✓ Reduce CRUD network overhead
Limitations
✗ Memory limited to loaded data ✗ Real-time data not reflected ✗ Manual reload needed for server updates
---
8. GraphQLAdaptor
Purpose
Integrates with GraphQL servers using GraphQL query language.
When to Use
- GraphQL endpoints
- Complex, nested data queries
- Need flexible, client-driven queries
Configuration
import { DataManager, Query, GraphQLAdaptor } from '@syncfusion/ej2-data';
const dataManager = new DataManager({
url: 'url',
adaptor: new GraphQLAdaptor()
});Usage Example
dataManager.executeQuery(
new Query()
.select(['OrderID', 'CustomerID', 'Freight'])
.sortBy('OrderID')
)
.then((result) => {
this.orders = result.result;
});---
9. CustomDataAdaptor
Purpose
Complete control over request/response with custom fetch logic. Extends UrlAdaptor with ability to override getData() method.
When to Use
- APIs with non-standard request/response formats
- Custom authentication mechanisms
- Non-standard query parameters
- Complex transformation logic
- Complete control over HTTP communication
Key Feature
Override getData() method to implement custom fetch logic with complete control over request construction and response handling.
Configuration
import { DataManager, Query, CustomDataAdaptor } from '@syncfusion/ej2-data';
this.dataManager = new DataManager({
adaptor: new CustomDataAdaptor({
getData: (option: any) => {
// Custom request construction
const request = {
skip: option.skip,
take: option.take,
filter: option.where,
sort: option.sorted
};
fetch("url", {
method: 'POST'
})
.then(response => response.json())
.then(data => {
// Custom response handling
option.onSuccess({
result: data.items,
count: data.total
});
})
.catch(error => {
option.onFailure({}, error);
});
}
})
});Complete Example
this.dataManager = new DataManager({
adaptor: new CustomDataAdaptor({
getData: (option: any) => {
// Build custom request payload
const queryOptions = {
page: Math.floor(option.skip / option.take) + 1,
pageSize: option.take,
search: option.search,
filters: option.where,
sortBy: option.sorted
};
fetch("url", {
method: 'POST'
})
.then(response => {
if (!response.ok) {
throw new Error('API Error');
}
return response.json();
})
.then(data => {
// Transform response to expected format
const result = {
result: data.data, // Extract items
count: data.pagination.total // Extract count
};
option.onSuccess(result, {});
})
.catch(error => {
option.onFailure({}, error);
});
}
})
});
// Use with queries
this.dataManager.executeQuery(
new Query().take(10).skip(0)
)
.then((result) => {
this.items = result.result;
});Expected Server Response
{
"result": [
{ "OrderID": 10248, "CustomerID": "VINET" }
],
"count": 830
}Advantages
✓ Complete control over requests ✓ Flexible response handling ✓ Custom authentication ✓ Transform data as needed
Limitations
✗ More complex setup ✗ Manual error handling ✗ Requires deeper API knowledge
---
10. CustomAdaptor
Purpose
Extend built-in adaptors (like UrlAdaptor) and override specific methods for fine-grained customization.
When to Use
- Need to modify behavior of existing adaptor
- Custom query translation
- Request/response interception
- Extend rather than completely replace adaptor
Configuration with Method Overrides
import { DataManager, Query, UrlAdaptor } from '@syncfusion/ej2-data';
class MyCustomAdaptor extends UrlAdaptor {
// Override processQuery to customize URL building
processQuery(dm: DataManager, query: Query, params?: any): DMRequest | null {
const req = super.processQuery(dm, query, params);
// Modify request
console.log('Query:', req);
return req;
}
// Override beforeSend to add headers
beforeSend(dm: DataManager, request: XMLHttpRequest | Request, settings?: any): void {
super.beforeSend(dm, request, settings);
if (request instanceof XMLHttpRequest) {
request.setRequestHeader('Authorization', 'send_token');
request.setRequestHeader('X-API-Key', 'apikey');
}
}
// Override processResponse to transform data
processResponse(response: any, dm?: DataManager, query?: Query, xhr?: XMLHttpRequest, request?: Request, params?: any): any {
const result = super.processResponse(response, dm, query, xhr, request, params);
// Transform result
if (result && result.result) {
result.result = result.result.map((item: any) => ({
...item,
processed: true
}));
}
return result;
}
insert(dm: DataManager, value, tableName) {
// Complete custom insert logic
return fetch(`${dm.dataSource.url}/${tableName}`, {
method: 'POST',
}).then(r => r.json());
}
update(dm: DataManager, value, tableName, key, keyField) {
// Complete custom update logic
return fetch(`${dm.dataSource.url}/${tableName}/${key}`, {
method: 'PUT',
body: JSON.stringify(value)
}).then(r => r.json());
}
remove(dm: DataManager, keyField, value, tableName) {
// Complete custom delete logic
return fetch(`${dm.dataSource.url}/${tableName}/${value}`, {
method: 'DELETE'
}).then(r => r.json());
}
}
const dataManager = new DataManager({
url: 'url',
adaptor: new MyCustomAdaptor()
});Available Methods to Override
processQuery()— Transform Query to requestbeforeSend()— Modify headers, authenticationprocessResponse()— Transform server responseinsert(),update(),remove()— Custom CRUD
---
Comparison Matrix
| Feature | Json | OData | ODataV4 | Url | WebApi | WebMethod | RemoteSave | GraphQL | CustomData | Custom |
|---|---|---|---|---|---|---|---|---|---|---|
| Local Data | ✓ | ✗ | ✗ | ✗ | ✗ | ✗ | ✓ | ✗ | ✗ | ✗ |
| Remote API | ✗ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| Server Filtering | ✗ | ✓ | ✓ | ✓ | ✓ | ✓ | ✗ | ✓ | ✓ | ✓ |
| Offline Mode | ✓ | ✗ | ✗ | ✗ | ✗ | ✗ | ✓ | ✗ | ✗ | ✗ |
| GraphQL Support | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✓ | ✓* | ✓* |
| Custom Headers | ✗ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| Ease of Use | ⭐⭐⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐ | ⭐⭐ |
| Customization | None | Low | Low | Medium | Medium | Medium | Low | Low | High | High |
---
Performance Recommendations
| Scenario | Recommended Adaptor | Reason |
|---|---|---|
| Small local dataset | JsonAdaptor | Fastest, no network |
| Large remote dataset | WebApiAdaptor | Server-side processing |
| Real-time dashboard | RemoteSaveAdaptor | Load once, filter locally |
| Non-standard API | CustomDataAdaptor | Complete flexibility |
| .NET Backend | WebApiAdaptor | Best practice integration |
| OData Service | ODataV4Adaptor | Use latest OData version |
---
Advanced Features in Syncfusion Angular DataManager
Table of Contents
- Overview
- Load on Demand
- Lazy Loading with Expand
- Pagination Strategies
- Deferred Operations
- Async/Await Patterns
- Promise.all() for Parallel Operations
- Error Handling with Status Codes
- Memory Management
- State Persistence
- Type Safety with TypeScript
---
Overview
Advanced features enable:
- Efficient handling of large datasets
- Complex async workflows
- Better error management
- Performance optimization
- Type-safe development
---
Load on Demand
Virtual Scrolling Pattern
@Component({
selector: 'app-virtual-scroll',
template: `
<cdk-virtual-scroll-viewport itemSize="50" class="list">
<div *cdkVirtualFor="let item of items">
{{ item.OrderID }}: {{ item.CustomerID }}
</div>
</cdk-virtual-scroll-viewport>
`,
styles: [`.list { height: 500px; }`]
})
export class VirtualScrollComponent implements OnInit {
public items: any[] = [];
private dataManager: DataManager;
ngOnInit(): void {
this.dataManager = new DataManager({
url: 'url',
adaptor: new WebApiAdaptor()
});
this.loadInitialData();
}
async loadInitialData(): Promise<void> {
const result = await this.dataManager.executeQuery(
new Query().take(50)
);
this.items = result.result;
}
}Intersection Observer Pattern
@Component({
selector: 'app-load-on-scroll',
template: `
<div #endOfList></div>
<div *ngFor="let item of items">{{ item.OrderID }}</div>
`
})
export class LoadOnScrollComponent implements AfterViewInit, OnDestroy {
@ViewChild('endOfList') endOfList: ElementRef;
public items: any[] = [];
private observer: IntersectionObserver;
private pageIndex = 0;
ngAfterViewInit(): void {
this.observer = new IntersectionObserver((entries) => {
if (entries[0].isIntersecting) {
this.loadMore();
}
});
this.observer.observe(this.endOfList.nativeElement);
}
async loadMore(): Promise<void> {
const dataManager = new DataManager({
url: 'url',
adaptor: new WebApiAdaptor()
});
const result = await dataManager.executeQuery(
new Query()
.skip(this.pageIndex * 50)
.take(50)
);
this.items.push(...result.result);
this.pageIndex++;
}
ngOnDestroy(): void {
if (this.observer) {
this.observer.disconnect();
}
}
}---
Lazy Loading with Expand
Navigation Properties
import { DataManager, Query, ODataV4Adaptor } from '@syncfusion/ej2-data';
const dataManager = new DataManager({
url: 'url',
adaptor: new ODataV4Adaptor()
});
// Load related Customer data
dataManager.executeQuery(
new Query()
.select(['OrderID', 'CustomerID', 'Freight'])
.expand('Customer') // Load related Customer entity
.take(10)
)
.then((result) => {
result.result.forEach((order: any) => {
console.log(order.OrderID);
console.log(order.Customer.CustomerName); // Related data available
});
});Multi-level Expand
// Load Order -> OrderDetails -> Product data
dataManager.executeQuery(
new Query()
.expand('OrderDetails')
.expand('OrderDetails.Product') // Nested expand
.take(5)
)
.then((result) => {
result.result.forEach((order: any) => {
order.OrderDetails.forEach((detail: any) => {
console.log(detail.Product.ProductName);
});
});
});---
Pagination Strategies
Client-side Pagination
@Component({
selector: 'app-pagination'
})
export class PaginationComponent {
public allData: any[] = [];
public pageSize = 10;
public currentPage = 1;
get pagedData(): any[] {
const start = (this.currentPage - 1) * this.pageSize;
return this.allData.slice(start, start + this.pageSize);
}
async ngOnInit(): Promise<void> {
const dm = new DataManager({
url: 'url',
adaptor: new WebApiAdaptor()
});
const result = await dm.executeQuery(new Query());
this.allData = result.result;
}
getTotal(): number {
return Math.ceil(this.allData.length / this.pageSize);
}
}Server-side Pagination
async goToPage(pageNumber: number): Promise<void> {
const pageSize = 10;
const skip = (pageNumber - 1) * pageSize;
const dataManager = new DataManager({
url: 'url',
adaptor: new WebApiAdaptor()
});
const result = await dataManager.executeQuery(
new Query()
.skip(skip)
.take(pageSize)
);
console.log('Total records:', result.count);
console.log('Current page:', result.result);
}---
Deferred Operations
The Deferred object provides methods for managing asynchronous operations with .resolve(), .reject(), .promise, .then(), and .catch():
Basic Deferred Usage
import { DataManager, Deferred } from '@syncfusion/ej2-data';
// Create a deferred object for async operation management
const deferred = new Deferred();
// Simulate async work (e.g., fetching from API)
setTimeout(() => {
const result = {
result: [
{ id: 1, name: 'Order 1' },
{ id: 2, name: 'Order 2' }
],
count: 2
};
deferred.resolve(result); // Mark operation as successful
}, 1000);
// Handle the deferred operation result
deferred.promise
.then((result: any) => {
console.log('Operation succeeded:', result.result);
})
.catch((error: any) => {
console.error('Operation failed:', error.message);
});Deferred with Rejection (Error Handling)
import { DataManager, Deferred } from '@syncfusion/ej2-data';
function fetchDataWithError(shouldFail: boolean = false): Deferred {
const deferred = new Deferred();
setTimeout(() => {
if (shouldFail) {
deferred.reject(new Error('API returned 500 Server Error'));
} else {
deferred.resolve({ result: [{ id: 1, value: 'Success' }] });
}
}, 1000);
return deferred;
}
// Handling rejected deferred
fetchDataWithError(true)
.promise
.then((result: any) => {
console.log('Success:', result.result);
})
.catch((error: any) => {
console.error('Error caught:', error.message); // "API returned 500 Server Error"
});Deferred with Query Execution
import { DataManager, Query, WebApiAdaptor, Deferred } from '@syncfusion/ej2-data';
const dm = new DataManager({
url: 'url',
adaptor: new WebApiAdaptor()
});
// executeQuery() returns a Deferred internally
const deferred: any = dm.executeQuery(
new Query().where('Status', 'equal', 'Completed').take(10)
);
deferred
.then((result: any) => {
console.log('Query succeeded:', result.result);
return result.result;
})
.catch((error: any) => {
console.error('Query failed:', error);
});Multiple Deferred Operations (Parallel Execution)
import { Deferred } from '@syncfusion/ej2-data';
const deferred1 = new Deferred();
const deferred2 = new Deferred();
const deferred3 = new Deferred();
// Simulate multiple async operations
setTimeout(() => deferred1.resolve({ data: 'Result 1' }), 500);
setTimeout(() => deferred2.resolve({ data: 'Result 2' }), 700);
setTimeout(() => deferred3.resolve({ data: 'Result 3' }), 600);
// Wait for all deferred operations to complete
Promise.all([deferred1.promise, deferred2.promise, deferred3.promise])
.then((results: any[]) => {
console.log('All operations complete:', results);
})
.catch((error: any) => {
console.error('One or more operations failed:', error);
});---
Async/Await Patterns
Sequential Operations
async loadDataSequentially(): Promise<void> {
try {
const dm = new DataManager({
url: 'url',
adaptor: new WebApiAdaptor()
});
// Load orders
const ordersResult = await dm.executeQuery(new Query());
console.log('Orders loaded:', ordersResult.result.length);
// Then load customers
const customersResult = await dm.executeQuery(
new Query().select(['CustomerID', 'CustomerName'])
);
console.log('Customers loaded:', customersResult.result.length);
} catch (error) {
console.error('Sequential load failed:', error);
}
}Parallel Operations
async loadDataParallel(): Promise<void> {
try {
const ordersDm = new DataManager({
url: 'url',
adaptor: new WebApiAdaptor()
});
const customersDm = new DataManager({
url: 'url',
adaptor: new WebApiAdaptor()
});
// Load both simultaneously
const [ordersResult, customersResult] = await Promise.all([
ordersDm.executeQuery(new Query()),
customersDm.executeQuery(new Query())
]);
console.log('Orders:', ordersResult.result);
console.log('Customers:', customersResult.result);
} catch (error) {
console.error('Parallel load failed:', error);
}
}---
Promise.all() for Parallel Operations
Multiple Data Sources
async loadMultipleSources(): Promise<void> {
const sources = [
new DataManager({ url: 'api/orders', adaptor: new WebApiAdaptor() }),
new DataManager({ url: 'api/customers', adaptor: new WebApiAdaptor() }),
new DataManager({ url: 'api/products', adaptor: new WebApiAdaptor() }),
new DataManager({ url: 'api/employees', adaptor: new WebApiAdaptor() })
];
try {
const results = await Promise.all(
sources.map(dm => dm.executeQuery(new Query().take(100)))
);
this.orders = results[0].result;
this.customers = results[1].result;
this.products = results[2].result;
this.employees = results[3].result;
} catch (error) {
console.error('Multi-source load failed:', error);
}
}Race Condition Handling
// First data source to respond wins
async loadFatest(): Promise<any> {
const sources = [
new DataManager({ url: 'api1.example.com/orders', adaptor: new WebApiAdaptor() }),
new DataManager({ url: 'api2.example.com/orders', adaptor: new WebApiAdaptor() })
];
try {
const result = await Promise.race(
sources.map(dm => dm.executeQuery(new Query()))
);
return result.result;
} catch (error) {
console.error('All sources failed:', error);
}
}---
Error Handling with Status Codes
HTTP Status Code Handling
async loadWithErrorHandling(): Promise<void> {
const dm = new DataManager({
url: 'url',
adaptor: new WebApiAdaptor()
});
try {
const result = await dm.executeQuery(new Query());
this.orders = result.result;
} catch (error: any) {
const status = error.status || error.statusCode || 0;
if (status === 0) {
console.error('Network error - check connectivity');
this.errorMessage = 'Network connection failed';
} else if (status === 400) {
console.error('Bad request - invalid parameters');
this.errorMessage = 'Invalid request data';
} else if (status === 401) {
console.error('Unauthorized - authentication required');
this.errorMessage = 'Please login again';
this.redirectToLogin();
} else if (status === 403) {
console.error('Forbidden - access denied');
this.errorMessage = 'You don\'t have permission to access this';
} else if (status === 404) {
console.error('Not found - resource doesn\'t exist');
this.errorMessage = 'Resource not found';
} else if (status === 409) {
console.error('Conflict - data conflict');
this.errorMessage = 'Data conflict - please refresh and try again';
} else if (status === 500) {
console.error('Server error - try again later');
this.errorMessage = 'Server error - please try again later';
} else if (status >= 500) {
console.error('Server error:', status);
this.errorMessage = 'Server error - please try again later';
} else {
console.error('Unknown error:', status);
this.errorMessage = 'An unexpected error occurred';
}
}
}Retry Logic with Exponential Backoff
async loadWithRetry(maxRetries = 3): Promise<any> {
const dm = new DataManager({
url: 'url',
adaptor: new WebApiAdaptor()
});
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
return await dm.executeQuery(new Query());
} catch (error: any) {
const isRetryable = error.status === 0 || error.status >= 500;
const isLastAttempt = attempt === maxRetries;
if (isRetryable && !isLastAttempt) {
const delay = Math.pow(2, attempt) * 1000; // 2s, 4s, 8s
console.log(`Retry ${attempt}/${maxRetries} after ${delay}ms`);
await new Promise(resolve => setTimeout(resolve, delay));
} else {
throw error;
}
}
}
}---
Memory Management
Garbage Collection for Large Datasets
@Component({
selector: 'app-large-dataset'
})
export class LargeDatasetComponent implements OnDestroy {
private dataManager: DataManager;
private subscription: any;
ngOnInit(): void {
this.dataManager = new DataManager({
url: 'url',
adaptor: new WebApiAdaptor()
});
}
loadLargeDataset(): void {
// Load only what's visible to user (pagination)
const pageSize = 50;
this.dataManager.executeQuery(
new Query().take(pageSize)
)
.then((result) => {
// Keep limited data in memory
this.data = result.result.slice(0, pageSize);
});
}
ngOnDestroy(): void {
// Clean up
if (this.subscription) {
this.subscription.unsubscribe();
}
// Help garbage collection
if (this.dataManager) {
this.dataManager.clearCache();
}
}
}Memory-efficient Mapping
// ❌ MEMORY INEFFICIENT - Creates large intermediate arrays
const results = data
.map(item => ({ id: item.OrderID, name: item.CustomerID }))
.filter(item => item.id > 10248)
.map(item => item.name)
.slice(0, 100);
// ✅ MEMORY EFFICIENT - Single iteration, minimal allocation
const results = [];
for (const item of data) {
if (item.OrderID > 10248) {
results.push(item.CustomerID);
if (results.length >= 100) break;
}
}---
State Persistence
🔒 Security Warning:localStorageandsessionStoragestore data unencrypted in the browser. Never store sensitive data (passwords, tokens, PII, payment info, user secrets, authentication credentials) in persisted TreeGrid state. State persistence is safe for UI state only (expand/collapse state, page number, sort order, column visibility, filter selections). For sensitive configuration or user data, use secure server-side session storage instead.
Syncfusion DataManager provides built-in state persistence using browser localStorage. Query states are automatically saved and restored across page reloads.
Basic State Persistence
import { Component, OnInit } from '@angular/core';
import { DataManager, WebApiAdaptor, Query } from '@syncfusion/ej2-data';
@Component({
selector: 'app-state-persistence',
template: `
<div>
<h3>Orders (sorted by CustomerID desc)</h3>
<ul>
<li *ngFor="let order of orders">
{{ order.CustomerID }} - ${{ order.Freight }}
</li>
</ul>
<p>Note: Sorting state persisted. Refresh page to see state restored.</p>
</div>
`
})
export class StatePersistenceComponent implements OnInit {
orders: any[] = [];
ngOnInit(): void {
const dataManager = new DataManager({
url: 'url',
adaptor: new WebApiAdaptor(),
enablePersistence: true, // Enable state persistence
id: 'angularOrderManager' // Unique ID for localStorage key
});
// Execute query with sorting
dataManager.executeQuery(
new Query().sortBy('CustomerID', 'descending').take(10)
).then((result) => {
this.orders = result.result;
// Query state (sorting) automatically saved to localStorage
}).catch((error) => {
console.error('Error:', error);
});
}
}Excluding Queries from Persistence
import { Component, OnInit } from '@angular/core';
import { DataManager, UrlAdaptor, Query } from '@syncfusion/ej2-data';
@Component({
selector: 'app-selective-persistence'
})
export class SelectivePersistenceComponent implements OnInit {
products: any[] = [];
ngOnInit(): void {
const dataManager = new DataManager({
url: 'url',
adaptor: new UrlAdaptor(),
enablePersistence: true,
id: 'angularProductManager',
ignoreOnPersist: ['onSortBy', 'onSearch'] // Don't persist sorting and search
});
// These will be persisted: filtering, grouping
// These will NOT be persisted: sorting, search
dataManager.executeQuery(
new Query()
.where('Status', 'equal', 'Active')
.sortBy('Price', 'descending') // Won't persist
).then((result) => {
this.products = result.result;
}).catch((error) => {
console.error('Error:', error);
});
}
}Retrieve Persisted State
🔒 Security Warning:localStorageandsessionStoragestore data unencrypted in the browser. Never store sensitive data (passwords, tokens, PII, payment info, user secrets, authentication credentials) in persisted TreeGrid state. State persistence is safe for UI state only (expand/collapse state, page number, sort order, column visibility, filter selections). For sensitive configuration or user data, use secure server-side session storage instead.
import { Component } from '@angular/core';
import { DataManager } from '@syncfusion/ej2-data';
@Component({
selector: 'app-get-persisted-data',
template: `<button (click)="getPersistedState()">Show Persisted State</button>`
})
export class GetPersistedDataComponent {
getPersistedState(): void {
// Retrieve the persisted query state from localStorage
const persisted = DataManager.getPersistedData('angularOrderManager');
console.log('Persisted state:', persisted);
// Output: { onSortBy: [{field: 'CustomerID', direction: 'descending'}], ... }
}
}Update Persisted State
🔒 Security Warning:localStorageandsessionStoragestore data unencrypted in the browser. Never store sensitive data (passwords, tokens, PII, payment info, user secrets, authentication credentials) in persisted TreeGrid state. State persistence is safe for UI state only (expand/collapse state, page number, sort order, column visibility, filter selections). For sensitive configuration or user data, use secure server-side session storage instead.
import { Component } from '@angular/core';
import { DataManager, Query } from '@syncfusion/ej2-data';
@Component({
selector: 'app-set-persisted-data',
template: `<button (click)="updatePersistedState()">Update Persisted State</button>`
})
export class SetPersistedDataComponent {
updatePersistedState(): void {
// Create a new query to be persisted
const newQuery = new Query()
.where('Freight', 'greaterThan', 100)
.sortBy('EmployeeID', 'descending');
// Update the persisted state in localStorage
DataManager.setPersistData(
null, // event: null when not in event context
'angularOrderManager', // DataManager id
newQuery // New query to persist
);
console.log('Persisted state updated');
}
}Clear Persisted State
import { Component } from '@angular/core';
import { DataManager } from '@syncfusion/ej2-data';
@Component({
selector: 'app-clear-persistence',
template: `
<button (click)="clearThis()">Clear This DataManager</button>
<button (click)="clearAll()">Clear All (Logout)</button>
`
})
export class ClearPersistenceComponent {
clearThis(): void {
// Remove all persisted data for this DataManager
DataManager.clearPersistence('angularOrderManager');
console.log('Persisted state cleared - DataManager reset to initial state');
}
clearAll(): void {
// Clear persistence for multiple DataManagers on logout
DataManager.clearPersistence('angularOrderManager');
DataManager.clearPersistence('angularProductManager');
console.log('All persisted states cleared');
}
}Persistence Properties Reference
| Property | Type | Purpose |
|---|---|---|
enablePersistence | boolean | Enable/disable state persistence |
id | string | Unique identifier for localStorage key |
ignoreOnPersist | string[] | Query types to exclude: onSortBy, onSearch, onWhere, Grouping |
---
Type Safety with TypeScript
Strongly Typed Operations
interface Order {
OrderID: number;
CustomerID: string;
EmployeeID: number;
Freight: number;
}
async loadTypedData(): Promise<Order[]> {
const dm = new DataManager({
url: 'url',
adaptor: new WebApiAdaptor()
});
const result = await dm.executeQuery(new Query());
const orders: Order[] = result.result as Order[];
// TypeScript knows Order properties
orders.forEach(order => {
console.log(order.OrderID); // ✓ Valid
console.log(order.Freight); // ✓ Valid
console.log(order.InvalidField); // ✗ Compile error
});
return orders;
}Generic Type Definitions
async genericLoad<T>(url: string): Promise<T[]> {
const dm = new DataManager({
url: url,
adaptor: new WebApiAdaptor()
});
const result = await dm.executeQuery(new Query());
return result.result as T[];
}
// Usage
const orders = await this.genericLoad<Order>('api/orders');
const customers = await this.genericLoad<Customer>('api/customers');---
Complete Advanced Example
@Component({
selector: 'app-advanced-data'
})
export class AdvancedDataComponent implements OnInit, OnDestroy {
public items: any[] = [];
public loading = false;
public error: string | null = null;
constructor(private stateService: DataManagerStateService) {}
async ngOnInit(): Promise<void> {
// Try to restore state
const saved = this.stateService.restoreState();
if (saved) {
this.items = saved.results;
return;
}
// Load fresh data
await this.loadData();
}
private async loadData(): Promise<void> {
this.loading = true;
this.error = null;
try {
const dm = new DataManager({
url: 'url',
adaptor: new WebApiAdaptor()
});
const result = await dm.executeQuery(
new Query()
.where('Status', 'equal', 'Active')
.sortBy('OrderID')
.take(100)
);
this.items = result.result;
this.stateService.saveState(new Query(), this.items);
} catch (error: any) {
this.error = this.getErrorMessage(error.status);
} finally {
this.loading = false;
}
}
private getErrorMessage(status: number): string {
const messages: { [key: number]: string } = {
0: 'Network error',
401: 'Please login',
403: 'Access denied',
404: 'Not found',
500: 'Server error'
};
return messages[status] || 'Unknown error';
}
ngOnDestroy(): void {
this.stateService.clearState();
}
}---
Caching and Offline Mode in Syncfusion Angular DataManager
Table of Contents
- Overview
- Offline Mode Configuration
- Data Persistence with localStorage
- Cache Auto-clearing Rules
- Manual Cache Management
- Online/Offline Workflow
- Sync Strategies
- Performance Optimization
---
Overview
DataManager provides two key optimization features:
- Offline Mode — Load all data at initialization, work without server connection
- Caching — Reduce redundant server requests by storing responses
Both features are essential for:
- Mobile applications with intermittent connectivity
- Reducing server load
- Improving application performance
- Better user experience during network issues
---
##Offline Mode Configuration
Enable Offline Mode
import { DataManager, Query, WebApiAdaptor, ReturnOption } from '@syncfusion/ej2-data';
const dataManager = new DataManager({
url: 'url',
adaptor: new WebApiAdaptor(),
offline: true // ENABLE OFFLINE MODE
});
// Data will be loaded during initialization with offline: true
// All queries will then execute locally without server calls
console.log('DataManager initialized with offline mode enabled');How Offline Mode Works
// STEP 1: Load all data at initialization
const dataManager = new DataManager({
url: 'url',
adaptor: new WebApiAdaptor(),
offline: true
});
// STEP 2: Data is loaded during initialization
// Execute queries locally on the cached data
const query = new Query()
.where('Freight', 'greaterThan', 50)
.sortBy('CustomerID')
.take(20);
// STEP 3: Queries run CLIENT-SIDE (no server calls)
dataManager.executeQuery(
new Query()
.where('Freight', 'greaterThan', 50)
.sortBy('CustomerID')
.take(20)
)
.then((result) => {
// Uses local data, instant result
console.log('Filtered records:', result.result);
});Manual Data Loading for Offline
// For manual control, load via HTTP then use locally
import { HttpClient } from '@angular/common/http';
constructor(private http: HttpClient) {}
loadDataForOffline(): void {
this.http.get<any[]>('url')
.subscribe((data) => {
const dataManager = new DataManager({
json: data,
adaptor: new JsonAdaptor()
});
// All operations are client-side
const result = dataManager.executeLocal(
new Query().take(10)
);
});
}---
Data Persistence with localStorage
Save Data to localStorage
🔒 Security Warning:localStorageandsessionStoragestore data unencrypted in the browser. Never store sensitive data (passwords, tokens, PII, payment info, user secrets, authentication credentials) in persisted TreeGrid state. State persistence is safe for UI state only (expand/collapse state, page number, sort order, column visibility, filter selections). For sensitive configuration or user data, use secure server-side session storage instead.
async saveDataOffline(): Promise<void> {
const dataManager = new DataManager({
url: 'url',
adaptor: new WebApiAdaptor()
});
try {
const result = await dataManager.executeQuery(new Query());
// Save to browser storage
localStorage.setItem(
'cached_orders',
JSON.stringify(result.result)
);
// Save metadata
localStorage.setItem('cached_orders_timestamp', new Date().toISOString());
console.log('Data saved for offline use');
} catch (error) {
console.error('Failed to cache data:', error);
}
}Load Data from localStorage
🔒 Security Warning:localStorageandsessionStoragestore data unencrypted in the browser. Never store sensitive data (passwords, tokens, PII, payment info, user secrets, authentication credentials) in persisted TreeGrid state. State persistence is safe for UI state only (expand/collapse state, page number, sort order, column visibility, filter selections). For sensitive configuration or user data, use secure server-side session storage instead.
loadDataFromCache(): any[] {
const cached = localStorage.getItem('cached_orders');
const timestamp = localStorage.getItem('cached_orders_timestamp');
if (cached && timestamp) {
const age = Date.now() - new Date(timestamp).getTime();
const oneHour = 60 * 60 * 1000;
if (age < oneHour) {
console.log('Using cached data (fresh)');
return JSON.parse(cached);
} else {
console.log('Cache expired, fetching fresh data');
localStorage.removeItem('cached_orders');
localStorage.removeItem('cached_orders_timestamp');
return [];
}
}
return [];
}Cache Strategy with Expiration
🔒 Security Warning:localStorageandsessionStoragestore data unencrypted in the browser. Never store sensitive data (passwords, tokens, PII, payment info, user secrets, authentication credentials) in persisted TreeGrid state. State persistence is safe for UI state only (expand/collapse state, page number, sort order, column visibility, filter selections). For sensitive configuration or user data, use secure server-side session storage instead.
@Injectable({ providedIn: 'root' })
export class CacheService {
private cacheKey = (name: string) => `cache_${name}`;
private timestampKey = (name: string) => `cache_timestamp_${name}`;
private cacheExpiry = 1 * 60 * 60 * 1000; // 1 hour
set(name: string, data: any): void {
localStorage.setItem(this.cacheKey(name), JSON.stringify(data));
localStorage.setItem(this.timestampKey(name), Date.now().toString());
}
get(name: string): any {
const data = localStorage.getItem(this.cacheKey(name));
const timestamp = localStorage.getItem(this.timestampKey(name));
if (!data || !timestamp) return null;
const age = Date.now() - parseInt(timestamp);
if (age > this.cacheExpiry) {
this.remove(name);
return null;
}
return JSON.parse(data);
}
remove(name: string): void {
localStorage.removeItem(this.cacheKey(name));
localStorage.removeItem(this.timestampKey(name));
}
clear(): void {
localStorage.clear();
}
}---
Cache Auto-clearing Rules
When DataManager Clears Cache
Cache automatically clears when CRUD operations occur:
const dataManager = new DataManager({
url: 'url',
adaptor: new WebApiAdaptor(),
offline: true
});
// Data is automatically cached when offline: true is set
// No need to wait explicitly
// INSERT → Cache clears (new record added to server)
dataManager.insert({ CustomerID: 'NEW', Freight: 25 }, 'Orders')
.then(() => {
// Cache invalidated automatically
console.log('Record inserted, cache cleared');
});
// UPDATE → Cache clears
dataManager.update('OrderID', { OrderID: 10248, Freight: 50 }, 'Orders')
.then(() => {
console.log('Record updated, cache cleared');
});
// DELETE → Cache clears
dataManager.remove('OrderID', 10248, 'Orders')
.then(() => {
console.log('Record deleted, cache cleared');
});After CRUD, Re-fetch Fresh Data
async performCrudAndRefresh(newRecord: any): Promise<void> {
const dataManager = new dataManager({
url: 'url',
adaptor: new WebApiAdaptor(),
offline: true
});
try {
// 1. Insert new record
await dataManager.insert(newRecord, 'Orders');
console.log('Inserted successfully');
// 2. Cache cleared automatically
// 3. Manually re-fetch to get fresh data including new record
const freshResult = await dataManager.executeQuery(new Query());
this.orders = freshResult.result;
} catch (error) {
console.error('CRUD failed:', error);
}
}---
Manual Cache Clearing with clearCache()
🔒 Security Warning:localStorageandsessionStoragestore data unencrypted in the browser. Never store sensitive data (passwords, tokens, PII, payment info, user secrets, authentication credentials) in persisted TreeGrid state. State persistence is safe for UI state only (expand/collapse state, page number, sort order, column visibility, filter selections). For sensitive configuration or user data, use secure server-side session storage instead.
Clear cached data explicitly using the clearCache() method:
const dataManager = new DataManager({
url: 'url',
adaptor: new WebApiAdaptor(),
enableCache: true,
offline: true
});
// Data is automatically loaded and cached with offline: true
// Option 1: Clear all cache manually
dataManager.clearCache();
console.log('Cache cleared');
// Next query hits server
dataManager.executeQuery(new Query()).then((result) => {
console.log('Fresh data from server');
});Method signature: dm.clearCache()
Purpose: Clears cached data from offline mode or browser storage
When to use:
- After modifying offline data
- During user logout or session end
- Resetting application state
- Manually invalidating entire cache
- After critical system updates
Example in Angular service:
@Injectable({ providedIn: 'root' })
export class OrderService {
private dm: DataManager;
constructor() {
this.dm = new DataManager({
url: '/api/orders',
adaptor: new WebApiAdaptor(),
enableCache: true
});
}
clearAllCache(): void {
// Clear all cached orders
this.dm.clearCache();
console.log('All order cache cleared');
}
async logout(): Promise<void> {
// Security: Clear sensitive cached data
this.dm.clearCache();
// Clear other caches
localStorage.clear();
sessionStorage.clear();
}
async performCriticalUpdate(data: any): Promise<void> {
// Make update to critical data
await this.dm.update('OrderID', data, 'Orders');
// Force all components to refresh
this.dm.clearCache();
console.log('Critical update complete - cache cleared');
}
}Usage in component:
@Component({
selector: 'app-orders',
templateUrl: './orders.component.html'
})
export class OrdersComponent implements OnInit {
orders: any[] = [];
constructor(private orderService: OrderService) {}
ngOnInit() {
this.loadOrders();
}
loadOrders() {
this.orderService.getOrders().subscribe(data => {
this.orders = data;
});
}
onLogout() {
this.orderService.logout();
// Redirect to login
}
onCriticalUpdate() {
// After important system change
this.orderService.clearAllCache();
this.loadOrders(); // Reload fresh data
}
}Difference: Automatic vs Manual Clearing:
- Automatic: CRUD operations automatically clear cache
await dm.insert(newOrder, 'Orders'); // Auto-clears cache
await dm.update('OrderID', updated, 'Orders'); // Auto-clears cache- Manual: Explicit control with clearCache()
dm.clearCache(); // Explicit clear for all cache---
Online/Offline Workflow
Detect Network Status
@Injectable({ providedIn: 'root' })
export class NetworkService {
private onlineSubject = new BehaviorSubject<boolean>(navigator.onLine);
public online$ = this.onlineSubject.asObservable();
constructor() {
window.addEventListener('online', () => this.onlineSubject.next(true));
window.addEventListener('offline', () => this.onlineSubject.next(false));
}
isOnline(): boolean {
return this.onlineSubject.value;
}
}Adaptive Data Loading
🔒 Security Warning:localStorageandsessionStoragestore data unencrypted in the browser. Never store sensitive data (passwords, tokens, PII, payment info, user secrets, authentication credentials) in persisted TreeGrid state. State persistence is safe for UI state only (expand/collapse state, page number, sort order, column visibility, filter selections). For sensitive configuration or user data, use secure server-side session storage instead.
@Component({
selector: 'app-adaptive-data',
templateUrl: './adaptive-data.component.html'
})
export class AdaptiveDataComponent implements OnInit {
public orders: any[] = [];
public isOnline = true;
constructor(
private networkService: NetworkService,
private dataService: DataService
) {}
ngOnInit(): void {
this.networkService.online$.subscribe((online) => {
this.isOnline = online;
this.loadData();
});
}
loadData(): void {
if (this.isOnline) {
// Online: Fetch fresh data and save for offline
this.dataService.getOrders()
.then((result) => {
this.orders = result;
localStorage.setItem('cached_orders', JSON.stringify(result));
})
.catch((error) => {
console.error('Load failed:', error);
this.loadFromCache();
});
} else {
// Offline: Use cached data
this.loadFromCache();
}
}
loadFromCache(): void {
const cached = localStorage.getItem('cached_orders');
this.orders = cached ? JSON.parse(cached) : [];
}
}Queue Operations While Offline
@Injectable({ providedIn: 'root' })
export class OfflineQueueService {
private queue: any[] = [];
constructor(private networkService: NetworkService) {
this.networkService.online$.subscribe((online) => {
if (online) {
this.processPendingOperations();
}
});
}
queueOperation(operation: any): void {
this.queue.push({
...operation,
timestamp: new Date()
});
console.log('Operation queued:', operation);
}
async processPendingOperations(): Promise<void> {
while (this.queue.length > 0) {
const operation = this.queue.shift();
try {
await this.executeOperation(operation);
console.log('Operation completed:', operation);
} catch (error) {
console.error('Operation failed, re-queueing:', operation);
this.queue.unshift(operation);
break;
}
}
}
private async executeOperation(operation: any): Promise<any> {
// Execute the operation (update, insert, delete)
// Implementation depends on operation type
}
}---
Sync Strategies
Strategy 1: Last-Write-Wins
class LastWriteWinsStrategy {
async sync(localData: any[], remoteData: any[]): Promise<any[]> {
const merged = [];
for (const local of localData) {
const remote = remoteData.find(r => r.id === local.id);
if (!remote) {
// Record exists only locally (new)
merged.push(local);
} else if (!local.modifiedDate || !remote.modifiedDate) {
// Fallback to local
merged.push(local);
} else {
// Use whichever was modified last
const localModified = new Date(local.modifiedDate).getTime();
const remoteModified = new Date(remote.modifiedDate).getTime();
merged.push(localModified > remoteModified ? local : remote);
}
}
// Add remote records not in local copy
for (const remote of remoteData) {
if (!merged.find(m => m.id === remote.id)) {
merged.push(remote);
}
}
return merged;
}
}Strategy 2: Conflict Resolution
class ConflictResolvingSync {
async sync(localChanges: any[], dataManager: DataManager): Promise<void> {
const conflicts: any[] = [];
for (const change of localChanges) {
try {
// Try to apply change
if (change.operation === 'insert') {
await dataManager.insert(change.data, 'Orders');
} else if (change.operation === 'update') {
await dataManager.update('OrderID', change.data, 'Orders');
} else if (change.operation === 'delete') {
await dataManager.remove('OrderID', change.id, 'Orders');
}
} catch (error: any) {
if (error.status === 409) {
// Conflict detected
conflicts.push({ change, error });
}
}
}
if (conflicts.length > 0) {
console.warn('Sync conflicts:', conflicts);
// Notify user to resolve conflicts
}
}
}---
Performance Optimization
Lazy Loading with Pagination
@Component({
selector: 'app-lazy-load',
template: `
<div>
<div *ngFor="let order of orders">{{ order.OrderID }}</div>
<button (click)="loadMore()">Load More</button>
</div>
`
})
export class LazyLoadComponent {
public orders: any[] = [];
private pageSize = 50;
private pageIndex = 0;
constructor(private dataManager: DataManager) {}
ngOnInit(): void {
this.loadMore();
}
async loadMore(): Promise<void> {
const result = await this.dataManager.executeQuery(
new Query()
.skip(this.pageIndex * this.pageSize)
.take(this.pageSize)
);
this.orders.push(...result.result);
this.pageIndex++;
}
}Memory Management
// Clean up large datasets
if (this.orders.length > 10000) {
// Implement pagination or virtual scrolling
this.orders = this.orders.slice(0, 5000);
}
// Use trackBy to help Angular with change detection
trackByOrderId(index: number, order: any): number {
return order.OrderID;
}---
Complete Example
🔒 Security Warning:localStorageandsessionStoragestore data unencrypted in the browser. Never store sensitive data (passwords, tokens, PII, payment info, user secrets, authentication credentials) in persisted TreeGrid state. State persistence is safe for UI state only (expand/collapse state, page number, sort order, column visibility, filter selections). For sensitive configuration or user data, use secure server-side session storage instead.
@Component({
selector: 'app-offline-orders',
template: `
<div>
<p>Status: {{ isOnline ? '🟢 Online' : '🔴 Offline' }}</p>
<button (click)="syncData()">Sync Data</button>
<table>
<tr *ngFor="let order of orders; trackBy: trackByOrderId">
<td>{{ order.OrderID }}</td>
<td>{{ order.CustomerID }}</td>
</tr>
</table>
</div>
`
})
export class OfflineOrdersComponent implements OnInit {
public orders: any[] = [];
public isOnline = navigator.onLine;
constructor(
private http: HttpClient,
private networkService: NetworkService
) {}
ngOnInit(): void {
window.addEventListener('online', () => this.onGoOnline());
window.addEventListener('offline', () => this.onGoOffline());
this.initializeData();
}
private async initializeData(): Promise<void> {
if (this.isOnline) {
await this.loadFromServer();
} else {
this.loadFromCache();
}
}
private async loadFromServer(): Promise<void> {
const dm = new DataManager({
url: 'url',
adaptor: new WebApiAdaptor(),
offline: true
});
try {
const result = await dm.ready;
this.orders = result.result || [];
this.cacheData(this.orders);
} catch (error) {
console.error('Load failed:', error);
this.loadFromCache();
}
}
private loadFromCache(): void {
const cached = localStorage.getItem('orders_cache');
this.orders = cached ? JSON.parse(cached) : [];
}
private cacheData(data: any[]): void {
localStorage.setItem('orders_cache', JSON.stringify(data));
localStorage.setItem('orders_cache_time', Date.now().toString());
}
async syncData(): Promise<void> {
if (this.isOnline) {
await this.loadFromServer();
}
}
private onGoOnline(): void {
this.isOnline = true;
console.log('Connected to network');
this.loadFromServer();
}
private onGoOffline(): void {
this.isOnline = false;
console.log('Disconnected from network');
this.loadFromCache();
}
trackByOrderId(index: number, order: any): number {
return order.OrderID;
}
}---
CRUD Operations in Syncfusion Angular DataManager
Table of Contents
- Overview
- Insert - Adding Records
- Update - Modifying Records
- Delete - Removing Records
- Batch Operations
- Response Handling
- Error Handling
---
Overview
CRUD (Create, Read, Update, Delete) operations manage data persistence. DataManager provides methods for each operation:
- insert() — Add new records
- update() — Modify existing records
- remove() — Delete records
- saveChanges() — Batch persist all changes
All CRUD methods return Promises for async handling.
---
Insert - Adding Records
Basic Insert
import { DataManager, WebApiAdaptor } from '@syncfusion/ej2-data';
const dataManager = new DataManager({
url: 'url',
adaptor: new WebApiAdaptor()
});
const newOrder = {
OrderID: 10251,
CustomerID: 'NEW01',
EmployeeID: 1,
Freight: 25.50
};
dataManager.insert(newOrder, 'Orders')
.then((result) => {
console.log('Record inserted successfully');
})
.catch((error) => {
console.error('Insert failed:', error);
});Insert Multiple Records
const newOrders = [
{ CustomerID: 'CUST1', EmployeeID: 1, Freight: 20 },
{ CustomerID: 'CUST2', EmployeeID: 2, Freight: 30 },
{ CustomerID: 'CUST3', EmployeeID: 3, Freight: 40 }
];
// Insert one by one
for (const order of newOrders) {
dataManager.insert(order, 'Orders');
}
// Or collect and insert via saveChanges
const batch = [];
for (const order of newOrders) {
batch.push(dataManager.insert(order, 'Orders'));
}
await Promise.all(batch);Insert with Local Data
const localData = [
{ OrderID: 10248, CustomerID: 'VINET' },
{ OrderID: 10249, CustomerID: 'TOMSP' }
];
const dataManager = new DataManager({
json: localData,
adaptor: new JsonAdaptor()
});
// For local data, add to array and reassign
const newRecord = { OrderID: 10250, CustomerID: 'HANAR' };
localData.push(newRecord);
// Or use DataSource property
dataManager.insert(newRecord, null);---
Update - Modifying Records
Basic Update
const dataManager = new DataManager({
url: 'url',
adaptor: new WebApiAdaptor()
});
const updatedRecord = {
OrderID: 10248,
CustomerID: 'VINET_UPDATED',
Freight: 50.00
};
dataManager.update('Orders', updatedRecord)
.then(() => {
console.log('Record updated successfully');
})
.catch((error) => {
console.error('Update failed:', error);
});Update Specific Fields
// Update only selected fields
const partialUpdate = {
OrderID: 10248,
Freight: 45.00 // Only update Freight
};
dataManager.update('OrderID', partialUpdate, 'Orders');Update Multiple Records
const updates = [
{ OrderID: 10248, Freight: 40.00 },
{ OrderID: 10249, Freight: 25.00 },
{ OrderID: 10250, Freight: 60.00 }
];
const promises = updates.map(update =>
dataManager.update('OrderID', update, 'Orders')
);
Promise.all(promises)
.then(() => console.log('All updates complete'))
.catch((error) => console.error('Update failed:', error));---
Delete - Removing Records
Basic Delete
const dataManager = new DataManager({
url: 'url',
adaptor: new WebApiAdaptor()
});
const recordToDelete = { OrderID: 10248 };
dataManager.remove('OrderID', recordToDelete.OrderID, 'Orders')
.then(() => {
console.log('Record deleted successfully');
})
.catch((error) => {
console.error('Delete failed:', error);
});Delete by Key
// Pass just the key value
dataManager.remove('OrderID', 10248, 'Orders'); // Deletes record with OrderID = 10248
// For string keys
dataManager.remove('CustomerID', 'VINET', 'Orders'); // Deletes record with CustomerID = 'VINET'Delete Multiple Records
const keysToDelete = [10248, 10249, 10250];
const promises = keysToDelete.map(id =>
dataManager.remove('OrderID', id, 'Orders')
);
Promise.all(promises)
.then(() => console.log('All deletions complete'))
.catch((error) => console.error('Delete failed:', error));---
Batch Operations
Using saveChanges()
saveChanges() persists multiple operations (inserts, updates, deletes) in a single batch.
const dataManager = new DataManager({
url: 'url',
adaptor: new WebApiAdaptor()
});
// Collect changes
const recordsToInsert = [
{ CustomerID: 'NEW1', Freight: 25 },
{ CustomerID: 'NEW2', Freight: 30 }
];
const recordsToUpdate = [
{ OrderID: 10248, Freight: 40 }
];
const recordsToDelete = [10249, 10250];
// Execute batch
dataManager.saveChanges({
addedRecords: recordsToInsert,
changedRecords: recordsToUpdate,
deletedRecords: recordsToDelete
})
.then((result) => {
console.log('Batch operation completed');
console.log('Inserted:', result.addedRecords);
console.log('Updated:', result.changedRecords);
console.log('Deleted:', result.deletedRecords);
})
.catch((error) => {
console.error('Batch operation failed:', error);
});Batch Example with Component
import { Component } from '@angular/core';
import { DataManager, WebApiAdaptor } from '@syncfusion/ej2-data';
@Component({
selector: 'app-batch-crud',
template: `
<div>
<h3>Manage Orders</h3>
<button (click)="addOrder()">Add Order</button>
<button (click)="updateOrder()">Update Order</button>
<button (click)="deleteOrder()">Delete Order</button>
<button (click)="saveBatch()" style="font-weight:bold;">Save All Changes</button>
</div>
`
})
export class BatchCrudComponent {
private dataManager = new DataManager({
url: 'url',
adaptor: new WebApiAdaptor()
});
private toAdd = [];
private toUpdate = [];
private toDelete = [];
addOrder(): void {
const newOrder = {
CustomerID: 'NEWCUST',
EmployeeID: 1,
Freight: 30
};
this.toAdd.push(newOrder);
console.log('Order added to batch');
}
updateOrder(): void {
const updatedOrder = {
OrderID: 10248,
Freight: 50
};
this.toUpdate.push(updatedOrder);
console.log('Order updated in batch');
}
deleteOrder(): void {
this.toDelete.push(10249);
console.log('Order deleted from batch');
}
async saveBatch(): Promise<void> {
try {
const result = await this.dataManager.saveChanges({
addedRecords: this.toAdd,
changedRecords: this.toUpdate,
deletedRecords: this.toDelete
});
console.log('Batch saved successfully:', result);
// Clear batches
this.toAdd = [];
this.toUpdate = [];
this.toDelete = [];
} catch (error) {
console.error('Batch save failed:', error);
}
}
}---
Response Handling
Success Response
dataManager.insert('Orders', newOrder)
.then((result) => {
// result = { AddedRecords: [...] } or similar
console.log('Insertion successful');
console.log('Response:', result);
});Response Structure
interface InsertResponse {
AddedRecords?: any[];
ChangedRecords?: any[];
DeletedRecords?: any[];
}
dataManager.saveChanges({...})
.then((response: InsertResponse) => {
console.log('Added:', response.AddedRecords);
console.log('Changed:', response.ChangedRecords);
console.log('Deleted:', response.DeletedRecords);
});---
Error Handling
HTTP Error Codes
dataManager.update('Orders', updatedRecord)
.catch((error: any) => {
if (error.status === 400) {
console.error('Bad request - invalid data');
} else if (error.status === 401) {
console.error('Unauthorized - check authentication');
} else if (error.status === 404) {
console.error('Record not found');
} else if (error.status === 409) {
console.error('Conflict - record modified by another user');
} else if (error.status === 500) {
console.error('Server error - retry later');
}
});Validation Before CRUD
function validateOrder(order: any): boolean {
if (!order.OrderID) {
console.error('OrderID is required');
return false;
}
if (!order.CustomerID) {
console.error('CustomerID is required');
return false;
}
if (order.Freight < 0) {
console.error('Freight cannot be negative');
return false;
}
return true;
}
if (validateOrder(newOrder)) {
dataManager.insert(newOrder, 'Orders');
}Optimistic Error Recovery
const originalData = { ...recordToUpdate };
dataManager.update('OrderID', recordToUpdate, 'Orders')
.catch((error) => {
if (error.status === 409) {
console.warn('Update conflict - reverting to original data');
// Restore from originalData
} else {
console.error('Update failed:', error);
}
});---
Complete CRUD Example
import { Component, OnInit } from '@angular/core';
import { DataManager, WebApiAdaptor } from '@syncfusion/ej2-data';
interface Order {
OrderID?: number;
CustomerID: string;
EmployeeID: number;
Freight: number;
}
@Component({
selector: 'app-order-crud',
template: `
<div>
<form (ngSubmit)="onSubmit()">
<label>Customer ID: <input [(ngModel)]="form.CustomerID" name="customer"></label>
<label>Employee ID: <input type="number" [(ngModel)]="form.EmployeeID" name="employee"></label>
<label>Freight: <input type="number" [(ngModel)]="form.Freight" name="freight"></label>
<button type="submit">{{ isEdit ? 'Update' : 'Add' }}</button>
<button type="button" (click)="cancel()" *ngIf="isEdit">Cancel</button>
</form>
<table>
<thead>
<tr><th>ID</th><th>Customer</th><th>Employee</th><th>Freight</th><th>Actions</th></tr>
</thead>
<tbody>
<tr *ngFor="let order of orders">
<td>{{ order.OrderID }}</td>
<td>{{ order.CustomerID }}</td>
<td>{{ order.EmployeeID }}</td>
<td>{{ order.Freight }}</td>
<td>
<button (click)="onEdit(order)">Edit</button>
<button (click)="onDelete(order.OrderID)">Delete</button>
</td>
</tr>
</tbody>
</table>
</div>
`
})
export class OrderCrudComponent implements OnInit {
public orders: Order[] = [];
public form: Order = { CustomerID: '', EmployeeID: 0, Freight: 0 };
public isEdit = false;
public editingId: number | null = null;
private dataManager = new DataManager({
url: 'url',
adaptor: new WebApiAdaptor()
});
ngOnInit(): void {
this.loadOrders();
}
async loadOrders(): Promise<void> {
try {
const result = await this.dataManager.executeQuery(new Query());
this.orders = result.result as Order[];
} catch (error) {
console.error('Load failed:', error);
}
}
async onSubmit(): Promise<void> {
try {
if (this.isEdit && this.editingId) {
await this.dataManager.update('OrderID', { OrderID: this.editingId, ...this.form }, 'Orders');
} else {
await this.dataManager.insert(this.form, 'Orders');
}
await this.loadOrders();
this.resetForm();
} catch (error) {
console.error('Save failed:', error);
}
}
onEdit(order: Order): void {
this.form = { ...order };
this.editingId = order.OrderID;
this.isEdit = true;
}
async onDelete(id: number): Promise<void> {
try {
await this.dataManager.remove('Orders', id);
await this.loadOrders();
} catch (error) {
console.error('Delete failed:', error);
}
}
cancel(): void {
this.resetForm();
}
private resetForm(): void {
this.form = { CustomerID: '', EmployeeID: 0, Freight: 0 };
this.isEdit = false;
this.editingId = null;
}
}---
Data Binding in Syncfusion Angular DataManager
Table of Contents
- Overview
- Local Binding (JSON)
- Remote Binding (URL)
- Client-side vs Server-side Processing
- Promise-based Async Operations
- Error Handling
- Property Reference
---
Overview
Data binding in DataManager determines where your data comes from and how it's processed:
- Local Binding: Data exists in memory (JSON array)
- Remote Binding: Data fetches from a server URL
Each binding type uses different DataManager properties and methods.
---
Local Binding (JSON)
When to Use Local Binding
- Working with in-memory data already fetched
- Small datasets that fit in browser memory
- Quick prototyping or testing
- Offline-capable application features
- Client-side filtering/sorting for better UX
Configuration
import { DataManager, JsonAdaptor, Query } from '@syncfusion/ej2-data';
const orders = [
{ OrderID: 10248, CustomerID: 'VINET', Freight: 32.38 },
{ OrderID: 10249, CustomerID: 'TOMSP', Freight: 11.61 },
{ OrderID: 10250, CustomerID: 'HANAR', Freight: 65.83 }
];
const dataManager = new DataManager({
json: orders, // Local data array
adaptor: new JsonAdaptor() // Handles local operations
});Basic Operations
// Fetch all records
const all = dataManager.executeLocal(new Query());
// Filter records
const filtered = dataManager.executeLocal(
new Query().where('Freight', 'greaterThan', 30)
);
// Sort and paginate
const sorted = dataManager.executeLocal(
new Query()
.sortBy('Freight')
.take(5)
.skip(0)
);
// Combine operations
const result = dataManager.executeLocal(
new Query()
.where('CustomerID', 'equal', 'VINET')
.select(['OrderID', 'Freight'])
.sortBy('OrderID')
);Key Properties
| Property | Type | Purpose |
|---|---|---|
json | array | Your local data array |
adaptor | JsonAdaptor | Handles json property |
Advantages
- ✓ No network latency
- ✓ Works offline
- ✓ Instant filtering/sorting
- ✓ Simpler error handling
Limitations
- ✗ Limited to data already loaded
- ✗ Memory constraints for large datasets
- ✗ Must load all data at once
---
Remote Binding (URL)
When to Use Remote Binding
- Data stored on a server (REST API, OData, GraphQL)
- Large datasets too big for memory
- Real-time data that changes frequently
- Need server-side processing (filtering, sorting)
- Security-sensitive operations
Configuration
import { DataManager, WebApiAdaptor } from '@syncfusion/ej2-data';
const dataManager = new DataManager({
url: 'url', // API endpoint
adaptor: new WebApiAdaptor(), // Handles HTTP requests
crossDomain: true
});Basic Operations
// Fetch with promise
dataManager.executeQuery(new Query())
.then((result) => {
console.log(result.result); // Data array
console.log(result.count); // Total records
})
.catch((error) => {
console.error('Error:', error);
});
// With filtering
dataManager.executeQuery(
new Query().where('Freight', 'greaterThan', 50)
)
.then((result) => {
this.items = result.result;
});
// With paging
dataManager.executeQuery(
new Query().take(10).skip(0)
)
.then((result) => {
this.items = result.result;
this.totalCount = result.count;
});Key Properties
| Property | Type | Purpose |
|---|---|---|
url | string | API endpoint URL |
adaptor | Adaptor | Type of data service (WebApiAdaptor, ODataAdaptor, etc.) |
headers | object[] | Custom HTTP headers |
crossDomain | boolean | Enable CORS requests |
offline | boolean | Enable offline mode |
Supported Adaptors for Remote Binding
- WebApiAdaptor — ASP.NET Web API
- ODataAdaptor — OData v3 services
- ODataV4Adaptor — OData v4 endpoints
- UrlAdaptor — Generic REST endpoints
- WebMethodAdaptor — ASP.NET web methods
- GraphQLAdaptor — GraphQL servers
- CustomDataAdaptor — Custom fetch logic
- RemoteSaveAdaptor — Client-side queries + server CRUD
Advantages
- ✓ Handle unlimited data volume
- ✓ Real-time data
- ✓ Server-side processing (faster, efficient)
- ✓ Secure operations on server
Limitations
- ✗ Network delays
- ✗ Requires server connection
- ✗ More complex error handling
---
Client-side vs Server-side Processing
Client-side Processing (executeLocal)
Use: Local binding with executeLocal()
const dataManager = new DataManager({
json: largeArray,
adaptor: new JsonAdaptor()
});
// Processing happens in browser
const result = dataManager.executeLocal(
new Query()
.where('Status', 'equal', 'Active')
.sortBy('Date')
.take(10)
);Characteristics:
- All data loaded first
- Filtering/sorting in browser
- Instant results
- Limited to memory size
---
Server-side Processing (executeQuery)
Use: Remote binding with executeQuery()
const dataManager = new DataManager({
url: 'url',
adaptor: new WebApiAdaptor()
});
// Request sent to server with query parameters
// Server filters, sorts, and returns only needed data
const result = await dataManager.executeQuery(
new Query()
.where('Status', 'equal', 'Active')
.sortBy('Date')
.take(10)
);Characteristics:
- Only needed data downloaded
- Server handles heavy operations
- Better for large datasets
- More efficient bandwidth
- Scales better with size
---
Promise-based Async Operations
Using .then().catch()
const dataManager = new DataManager({
url: 'url',
adaptor: new WebApiAdaptor()
});
dataManager.executeQuery(new Query())
.then((result) => {
// Success: result contains data
this.items = result.result;
this.totalCount = result.count;
})
.catch((error) => {
// Failure: handle error
console.error('Data fetch failed:', error);
});Using Async/Await
async loadData(): Promise<void> {
try {
const dataManager = new DataManager({
url: 'url',
adaptor: new WebApiAdaptor()
});
const result = await dataManager.executeQuery(new Query());
this.items = result.result;
} catch (error) {
console.error('Error:', error);
}
}Multiple Sequential Operations
// Pattern: Load data, then load related data
async loadOrderAndItems(orderId: number): Promise<void> {
try {
const orderDm = new DataManager({
url: `url/${orderId}`,
adaptor: new WebApiAdaptor()
});
const order = await orderDm.executeQuery(new Query());
this.order = order.result;
// Then load related items
const itemsDm = new DataManager({
url: `url/${orderId}/items`,
adaptor: new WebApiAdaptor()
});
const items = await itemsDm.executeQuery(new Query());
this.items = items.result;
} catch (error) {
console.error('Error:', error);
}
}Parallel Operations with Promise.all
async loadAllData(): Promise<void> {
try {
const ordersDm = new DataManager({
url: 'url',
adaptor: new WebApiAdaptor()
});
const customersDm = new DataManager({
url: 'url',
adaptor: new WebApiAdaptor()
});
// Load both simultaneously
const [ordersResult, customersResult] = await Promise.all([
ordersDm.executeQuery(new Query()),
customersDm.executeQuery(new Query())
]);
this.orders = ordersResult.result;
this.customers = customersResult.result;
} catch (error) {
console.error('Error:', error);
}
}---
Error Handling
Basic Error Handling
dataManager.executeQuery(query)
.then((result) => {
this.items = result.result;
})
.catch((error) => {
console.error('Data fetch failed:', error);
});HTTP Status Code Handling
dataManager.executeQuery(query)
.catch((error) => {
if (error.status === 401) {
// Handle unauthorized
this.redirectToLogin();
} else if (error.status === 403) {
// Handle forbidden
console.error('Access denied');
} else if (error.status === 404) {
// Handle not found
console.error('Endpoint not found');
} else if (error.status === 500) {
// Handle server error
console.error('Server error');
} else if (error.status === 0) {
// Handle network error
console.error('Network connectivity issue');
} else {
console.error('Unknown error:', error);
}
});Async/Await Error Handling
async loadData(): Promise<void> {
try {
const result = await dataManager.executeQuery(new Query());
this.items = result.result;
} catch (error: any) {
if (error.status === 401) {
// Redirect to login
} else if (error.status >= 500) {
// Show server error message
this.errorMessage = 'Server error - please try again later';
} else {
// Generic error
this.errorMessage = 'Failed to load data';
}
}
}---
Complete Example: Remote Data with Error Handling
import { Component, OnInit } from '@angular/core';
import { DataManager, Query, WebApiAdaptor, ReturnOption } from '@syncfusion/ej2-data';
interface Order {
OrderID: number;
CustomerID: string;
Freight: number;
}
@Component({
selector: 'app-remote-orders',
template: `
<div>
<h3>Orders</h3>
<div *ngIf="errorMessage" class="error">{{ errorMessage }}</div>
<div *ngIf="loading" class="loader">Loading...</div>
<table *ngIf="!loading && orders.length">
<thead>
<tr>
<th>Order ID</th>
<th>Customer</th>
<th>Freight</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let order of orders">
<td>{{ order.OrderID }}</td>
<td>{{ order.CustomerID }}</td>
<td>{{ order.Freight | currency }}</td>
</tr>
</tbody>
</table>
</div>
`,
styles: [`
.error { color: red; padding: 10px; background: #ffebee; }
.loader { text-align: center; padding: 20px; }
`]
})
export class RemoteOrdersComponent implements OnInit {
public orders: Order[] = [];
public loading = false;
public errorMessage = '';
ngOnInit(): void {
this.loadOrders();
}
loadOrders(): void {
this.loading = true;
this.errorMessage = '';
const dataManager = new DataManager({
url: 'url',
adaptor: new WebApiAdaptor()
});
dataManager.executeQuery(
new Query().select(['OrderID', 'CustomerID', 'Freight']).take(20)
)
.then((result: ReturnOption) => {
this.orders = result.result as Order[];
this.loading = false;
})
.catch((error) => {
this.loading = false;
if (error.status === 0) {
this.errorMessage = 'Network error - check your connection';
} else if (error.status === 404) {
this.errorMessage = 'Service not found';
} else {
this.errorMessage = 'Failed to load orders';
}
});
}
}---
Property Reference
| Property | Type | Local | Remote | Purpose |
|---|---|---|---|---|
json | array | ✓ | ✗ | Local data array |
url | string | ✗ | ✓ | Remote endpoint |
adaptor | class | ✓ | ✓ | Adaptor type |
headers | object[] | ✗ | ✓ | HTTP headers |
offline | boolean | ✗ | ✓ | Offline support |
enableCache | boolean | ✗ | ✓ | Response caching |
crossDomain | boolean | ✗ | ✓ | CORS requests |
---
Getting Started with Syncfusion Angular DataManager
Table of Contents
- Installation
- Package Dependencies
- TypeScript Imports
- Creating Your First DataManager
- executeLocal vs executeQuery
- Handling Results
---
Installation
Install the Syncfusion data module using npm:
npm install @syncfusion/ej2-dataVerify Installation
Check your package.json to confirm the package is listed:
{
"dependencies": {
"@syncfusion/ej2-data": "^20.0.0 or higher"
}
}---
Package Dependencies
The @syncfusion/ej2-data package includes:
- DataManager — Core data management class
- Query — Query builder for filtering, sorting, paging
- Adaptors — Multiple adaptor types (Json, OData, Rest, etc.)
- ReturnOption — TypeScript interface for results
Optional Dependencies
For HTTP requests, install Angular's HTTP client:
npm install @angular/common---
TypeScript Imports
Basic Imports for Local Data
import { DataManager, Query, JsonAdaptor } from '@syncfusion/ej2-data';
// For results type safety
import { ReturnOption } from '@syncfusion/ej2-data';Imports for Remote Data
import {
DataManager,
Query,
WebApiAdaptor, // or ODataAdaptor, UrlAdaptor, etc.
ReturnOption
} from '@syncfusion/ej2-data';Complete Component Imports
import { Component, OnInit } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import {
DataManager,
Query,
WebApiAdaptor,
ReturnOption
} from '@syncfusion/ej2-data';---
Creating Your First DataManager
Local Data (JSON Array)
import { Component, OnInit } from '@angular/core';
import { DataManager, Query, JsonAdaptor, ReturnOption } from '@syncfusion/ej2-data';
@Component({
selector: 'app-local-data',
templateUrl: './local-data.component.html'
})
export class LocalDataComponent implements OnInit {
public items: object[];
ngOnInit(): void {
// Step 1: Define local data
const data = [
{ OrderID: 10248, CustomerID: 'VINET', EmployeeID: 5 },
{ OrderID: 10249, CustomerID: 'TOMSP', EmployeeID: 6 },
{ OrderID: 10250, CustomerID: 'HANAR', EmployeeID: 4 }
];
// Step 2: Create DataManager
const dataManager = new DataManager({
json: data,
adaptor: new JsonAdaptor()
});
// Step 3: Execute query
const result = dataManager.executeLocal(new Query().take(2));
this.items = result;
}
}Remote Data (API Endpoint)
import { Component, OnInit } from '@angular/core';
import { DataManager, Query, WebApiAdaptor, ReturnOption } from '@syncfusion/ej2-data';
@Component({
selector: 'app-remote-data',
templateUrl: './remote-data.component.html'
})
export class RemoteDataComponent implements OnInit {
public items: object[];
ngOnInit(): void {
// Step 1: Create DataManager with remote URL
const dataManager = new DataManager({
url: 'url',
adaptor: new WebApiAdaptor()
});
// Step 2: Execute query
dataManager.executeQuery(new Query().take(5))
.then((e: ReturnOption) => {
this.items = e.result;
})
.catch((error: any) => {
console.error('Error:', error);
});
}
}---
executeLocal vs executeQuery
executeLocal — Client-side Processing
Use when:
- Data is already in memory (JSON array)
- You want instant operations without server calls
- Working offline
const data = [
{ id: 1, name: 'Product A' },
{ id: 2, name: 'Product B' },
{ id: 3, name: 'Product C' }
];
const dm = new DataManager({ json: data, adaptor: new JsonAdaptor() });
// Returns immediately (synchronous)
const result = dm.executeLocal(
new Query().where('id', 'greaterThan', 1)
);
// result = [{ id: 2, name: 'Product B' }, { id: 3, name: 'Product C' }]executeQuery — Server-side Processing
Use when:
- Data is on remote server
- You need server-side filtering/sorting
- Large datasets (pagination on server)
const dm = new DataManager({
url: 'url',
adaptor: new WebApiAdaptor()
});
// Returns Promise (asynchronous)
dm.executeQuery(new Query().take(10))
.then((result: ReturnOption) => {
console.log(result.result); // Array of records
console.log(result.count); // Total record count
})
.catch((error) => console.error(error));Key Differences Table
| Aspect | executeLocal | executeQuery |
|---|---|---|
| Data Source | In-memory JSON | Remote server |
| Processing | Client-side | Server-side |
| Return Type | Direct array | Promise |
| Speed | Instant | Network delay |
| Best For | Small datasets | Large datasets |
| Offline | Works offline | Requires connection |
---
Handling Results
Processing Local Results
// Direct array result
const data = [
{ id: 1, value: 'A' },
{ id: 2, value: 'B' }
];
const dm = new DataManager({ json: data, adaptor: new JsonAdaptor() });
const result = dm.executeLocal(new Query().take(1));
console.log(result); // [{ id: 1, value: 'A' }]
console.log(result[0].id); // 1Processing Remote Results
const dm = new DataManager({
url: 'url',
adaptor: new WebApiAdaptor()
});
dm.executeQuery(new Query().take(10))
.then((result: ReturnOption) => {
// result object structure:
// {
// result: [ /* array of records */ ],
// count: 100, // total records on server
// aggregates: { /* aggregate values */ }
// }
this.items = result.result as any[];
this.totalCount = result.count;
})
.catch((error) => {
console.error('Failed to fetch data:', error);
});Typed Results with TypeScript
interface Product {
id: number;
name: string;
price: number;
}
async getProducts(): Promise<void> {
try {
const dm = new DataManager({
url: 'url',
adaptor: new WebApiAdaptor()
});
const result = await dm.executeQuery(new Query());
const products = result.result as Product[];
products.forEach(p => {
console.log(`${p.name}: $${p.price}`);
});
} catch (error) {
console.error('Error:', error);
}
}---
Error Handling
Basic Error Handling
dm.executeQuery(query)
.then((result: ReturnOption) => {
this.items = result.result;
})
.catch((error) => {
console.error('Data fetch failed:', error);
});Detailed Error Handling
dm.executeQuery(query)
.catch((error: any) => {
if (error.statusCode === 404) {
console.error('Endpoint not found');
} else if (error.statusCode === 401) {
console.error('Unauthorized - check authentication');
} else if (error.statusCode === 500) {
console.error('Server error');
} else {
console.error('Unknown error:', error);
}
});---
Complete Example: Orders Component
import { Component, OnInit } from '@angular/core';
import { DataManager, Query, WebApiAdaptor, ReturnOption } from '@syncfusion/ej2-data';
interface Order {
OrderID: number;
CustomerID: string;
EmployeeID: number;
Freight: number;
}
@Component({
selector: 'app-orders',
template: `
<div>
<h3>Orders</h3>
<table>
<thead>
<tr>
<th>Order ID</th>
<th>Customer</th>
<th>Employee</th>
<th>Freight</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let order of orders">
<td>{{ order.OrderID }}</td>
<td>{{ order.CustomerID }}</td>
<td>{{ order.EmployeeID }}</td>
<td>{{ order.Freight | currency }}</td>
</tr>
</tbody>
</table>
</div>
`
})
export class OrdersComponent implements OnInit {
public orders: Order[] = [];
ngOnInit(): void {
this.loadOrders();
}
loadOrders(): void {
const dataManager = new DataManager({
url: 'url',
adaptor: new WebApiAdaptor(),
});
dataManager.executeQuery(
new Query().select(['OrderID', 'CustomerID', 'EmployeeID', 'Freight']).take(10)
)
.then((result: ReturnOption) => {
this.orders = result.result as Order[];
})
.catch((error) => {
console.error('Failed to load orders:', error);
});
}
}---
How-To Recipes: Common Patterns and Solutions
Table of Contents
- Work in Offline Mode
- Send Additional Parameters
- Add Custom Headers
- Implement Authentication
- Handle Errors Gracefully
- Implement Search
- Sort by Multiple Fields
- Group and Aggregate
- Real-time Data Updates
---
Work in Offline Mode
Recipe: Load Data Once, Work Without Server
// Complete implementation
@Component({
selector: 'app-offline-recipe',
template: `
<button (click)="toggleOnline()">{{ isOnline ? 'Go Offline' : 'Go Online' }}</button>
<input [(ngModel)]="filterText" placeholder="Search (works offline too)">
<table>
<tr *ngFor="let item of filteredItems">
<td>{{ item.OrderID }}</td>
<td>{{ item.CustomerID }}</td>
</tr>
</table>
`
})
export class OfflineRecipeComponent implements OnInit {
public isOnline = true;
public filterText = '';
public items: any[] = [];
get filteredItems(): any[] {
if (!this.filterText) return this.items;
const dm = new DataManager({ json: this.items, adaptor: new JsonAdaptor() });
return dm.executeLocal(
new Query().where('CustomerID', 'contains', this.filterText)
);
}
ngOnInit(): void {
this.loadData();
}
private async loadData(): Promise<void> {
const dm = new DataManager({
url: 'url',
adaptor: new WebApiAdaptor(),
offline: true // Load all data, enable offline
});
try {
// Data is loaded with offline: true
// Execute a query to get all cached records
const result = dm.executeLocal(new Query());
this.items = result || [];
console.log(`Loaded ${this.items.length} records for offline use`);
} catch (error) {
console.error('Failed to load data:', error);
}
}
toggleOnline(): void {
this.isOnline = !this.isOnline;
console.log(this.isOnline ? 'Online mode' : 'Offline mode');
}
}---
Send Additional Parameters
Recipe: Pass Custom Query Parameters to API
// The user.created the API and wants to send custom parameters
// API expects: /api/orders?department=sales®ion=north
@Component({
selector: 'app-params-recipe'
})
export class ParamsRecipeComponent implements OnInit {
ngOnInit(): void {
this.loadWithParameters();
}
private loadWithParameters(): void {
const dm = new DataManager({
url: 'url',
adaptor: new WebApiAdaptor()
});
const query = new Query()
.addParams('department', 'sales')
.addParams('region', 'north')
.take(10);
dm.executeQuery(query)
.then((result) => {
console.log('Results:', result.result);
})
.catch((error) => {
console.error('Error:', error);
});
}
}
// Result URL: /api/orders?department=sales®ion=north&$take=10---
Add Custom Headers
Recipe: Send Authorization and Custom Headers
import { DataManager, WebApiAdaptor } from '@syncfusion/ej2-data';
@Component({
selector: 'app-headers-recipe'
})
export class HeadersRecipeComponent implements OnInit {
constructor(private authService: AuthService) {}
ngOnInit(): void {
this.loadWithHeaders();
}
private loadWithHeaders(): void {
const token = this.authService.getToken();
const dm = new DataManager({
url: 'url',
adaptor: new WebApiAdaptor()
});
dm.executeQuery(new Query().take(10))
.then((result) => {
console.log('Data with headers:', result.result);
});
}
}
// HTTP Request headers:
// Authorization: send_token
// X-API-Version: 2.0
// X-Custom-Header: custom-value
// Accept-Language: en-US---
Implement Authentication
Recipe: JWT Token-based Authentication
@Injectable({ providedIn: 'root' })
export class AuthenticatedDataService {
private token: string;
constructor(private http: HttpClient, private auth: AuthService) {
this.token = this.auth.getToken();
this.auth.tokenRefreshed$.subscribe(newToken => {
this.token = newToken;
});
}
getDataManager(url: string): DataManager {
return new DataManager({
url: url,
adaptor: new WebApiAdaptor()
});
}
async loadSecuredData(endpoint: string): Promise<any> {
const dm = this.getDataManager(endpoint);
try {
const result = await dm.executeQuery(new Query().take(10));
return result.result;
} catch (error: any) {
if (error.status === 401) {
// Token expired, refresh and retry
await this.auth.refreshToken();
return this.loadSecuredData(endpoint);
}
throw error;
}
}
}
// Usage in Component
@Component({
selector: 'app-auth-recipe'
})
export class AuthRecipeComponent implements OnInit {
public data: any[] = [];
constructor(private dataService: AuthenticatedDataService) {}
async ngOnInit(): Promise<void> {
try {
this.data = await this.dataService.loadSecuredData('url');
} catch (error) {
console.error('Failed to load secured data:', error);
}
}
}---
Handle Errors Gracefully
Recipe: Comprehensive Error Handling
@Component({
selector: 'app-error-recipe',
template: `
<div *ngIf="error" class="error-box">
<h3>{{ error.title }}</h3>
<p>{{ error.message }}</p>
<button (click)="retry()" *ngIf="error.retryable">Retry</button>
</div>
<div *ngIf="!error && loading" class="loader">Loading...</div>
<div *ngIf="!error && !loading && items.length">
<table>
<tr *ngFor="let item of items">
<td>{{ item.OrderID }}</td>
</tr>
</table>
</div>
`
})
export class ErrorRecipeComponent {
public items: any[] = [];
public loading = false;
public error: any = null;
async loadData(): Promise<void> {
this.loading = true;
this.error = null;
try {
const dm = new DataManager({
url: 'url',
adaptor: new WebApiAdaptor()
});
const result = await dm.executeQuery(new Query().take(10));
this.items = result.result;
} catch (err: any) {
this.error = this.parseError(err);
} finally {
this.loading = false;
}
}
private parseError(error: any): any {
const status = error.status || 0;
const errorMap: { [key: number]: any } = {
0: {
title: 'Network Error',
message: 'Please check your internet connection',
retryable: true
},
400: {
title: 'Bad Request',
message: 'Invalid query parameters',
retryable: false
},
401: {
title: 'Unauthorized',
message: 'Please login again',
retryable: false
},
404: {
title: 'Not Found',
message: 'The requested resource does not exist',
retryable: false
},
500: {
title: 'Server Error',
message: 'The server encountered an error. Please try again later',
retryable: true
}
};
return errorMap[status] || {
title: 'Unknown Error',
message: `Error code: ${status}`,
retryable: true
};
}
async retry(): Promise<void> {
await this.loadData();
}
}---
Implement Search
Recipe: Real-time Search Across Multiple Fields
@Component({
selector: 'app-search-recipe',
template: `
<input
[(ngModel)]="searchText"
(ngModelChange)="onSearchChange()"
placeholder="Search orders...">
<div *ngIf="searching" class="loader">Searching...</div>
<table>
<tr *ngFor="let item of searchResults">
<td>{{ item.OrderID }}</td>
<td>{{ item.CustomerID }}</td>
<td>{{ item.ShipCity }}</td>
</tr>
</table>
<p *ngIf="!searching && searchResults.length === 0">No results found</p>
`
})
export class SearchRecipeComponent {
public searchText = '';
public searchResults: any[] = [];
public searching = false;
private allData: any[] = [];
private searchTimeout: any;
async ngOnInit(): Promise<void> {
// Load all data once
const dm = new DataManager({
url: 'url',
adaptor: new WebApiAdaptor(),
offline: true
});
const result = await dm.ready;
this.allData = result.result || [];
}
onSearchChange(): void {
// Debounce search
clearTimeout(this.searchTimeout);
if (!this.searchText.trim()) {
this.searchResults = [];
return;
}
this.searching = true;
this.searchTimeout = setTimeout(() => {
const dm = new DataManager({
json: this.allData,
adaptor: new JsonAdaptor()
});
// Search across multiple fields
this.searchResults = dm.executeLocal(
new Query().search(this.searchText, [
'CustomerID',
'ShipCity',
'ShipCountry'
])
);
this.searching = false;
}, 300); // Debounce 300ms
}
}---
Sort by Multiple Fields
Recipe: Multi-field Sorting with User Interface
@Component({
selector: 'app-sort-recipe',
template: `
<div class="sort-controls">
<button (click)="toggleSort('CustomerID')">
Customer {{ getSortIndicator('CustomerID') }}
</button>
<button (click)="toggleSort('Freight')">
Freight {{ getSortIndicator('Freight') }}
</button>
<button (click)="resetSort()">Reset</button>
</div>
<table>
<tr *ngFor="let item of sortedItems">
<td>{{ item.OrderID }}</td>
<td>{{ item.CustomerID }}</td>
<td>{{ item.Freight }}</td>
</tr>
</table>
`
})
export class SortRecipeComponent {
public items: any[] = [];
private sortFields: Map<string, 'asc' | 'desc'> = new Map();
async ngOnInit(): Promise<void> {
const dm = new DataManager({
url: 'url',
adaptor: new WebApiAdaptor()
});
const result = await dm.executeQuery(new Query());
this.items = result.result;
}
toggleSort(field: string): void {
const current = this.sortFields.get(field);
if (!current) {
this.sortFields.set(field, 'asc');
} else if (current === 'asc') {
this.sortFields.set(field, 'desc');
} else {
this.sortFields.delete(field);
}
this.applySort();
}
private applySort(): void {
const dm = new DataManager({
json: this.items,
adaptor: new JsonAdaptor()
});
let query = new Query();
this.sortFields.forEach((direction, field) => {
if (direction === 'asc') {
query = query.sortBy(field);
} else {
query = query.sortByDescending(field);
}
});
this.items = dm.executeLocal(query);
}
getSortIndicator(field: string): string {
const direction = this.sortFields.get(field);
return direction === 'asc' ? '↑' : direction === 'desc' ? '↓' : '';
}
resetSort(): void {
this.sortFields.clear();
this.loadItems();
}
private async loadItems(): Promise<void> {
const dm = new DataManager({
url: 'url',
adaptor: new WebApiAdaptor()
});
const result = await dm.executeQuery(new Query());
this.items = result.result;
}
}---
Group and Aggregate
Recipe: Grouping with Aggregate Calculations
@Component({
selector: 'app-group-recipe'
})
export class GroupRecipeComponent {
public groupedData: any[] = [];
async ngOnInit(): Promise<void> {
const dm = new DataManager({
url: 'url',
adaptor: new WebApiAdaptor()
});
const result = await dm.executeQuery(new Query().group('CustomerID'));
// Transform grouped result
this.groupedData = result.result.map((group: any) => ({
customerID: group.key,
count: group.items.length,
totalFreight: group.items.reduce((sum: number, item: any) => sum + item.Freight, 0),
avgFreight: group.items.reduce((sum: number, item: any) => sum + item.Freight, 0) / group.items.length,
items: group.items
}));
}
// Template to display grouped data
template = `
<div *ngFor="let group of groupedData">
<h4>{{ group.customerID }}</h4>
<p>Orders: {{ group.count }}, Total: {{ group.totalFreight | currency }}, Avg: {{ group.avgFreight | currency }}</p>
<ul>
<li *ngFor="let item of group.items">
Order {{ item.OrderID }}: {{ item.Freight | currency }}
</li>
</ul>
</div>
`;
}---
Real-time Data Updates
Recipe: Polling for New Data
@Component({
selector: 'app-realtime-recipe'
})
export class RealtimeRecipeComponent implements OnInit, OnDestroy {
public items: any[] = [];
private pollInterval: any;
private pollFrequency = 30000; // 30 seconds
ngOnInit(): void {
this.startPolling();
}
private startPolling(): void {
this.pollInterval = setInterval(() => {
this.refreshData();
}, this.pollFrequency);
// Initial load
this.refreshData();
}
private async refreshData(): Promise<void> {
const dm = new DataManager({
url: 'url',
adaptor: new WebApiAdaptor()
});
try {
const result = await dm.executeQuery(
new Query().orderByDescending('OrderDate').take(10)
);
this.items = result.result;
console.log('Data refreshed at', new Date().toLocaleTimeString());
} catch (error) {
console.error('Refresh failed:', error);
}
}
ngOnDestroy(): void {
if (this.pollInterval) {
clearInterval(this.pollInterval);
}
}
}Recipe: WebSocket Real-time Updates
@Component({
selector: 'app-websocket-recipe'
})
export class WebsocketRecipeComponent implements OnInit, OnDestroy {
public items: any[] = [];
private ws: WebSocket;
ngOnInit(): void {
this.connectWebSocket();
}
private connectWebSocket(): void {
this.ws = new WebSocket('url');
this.ws.onopen = () => {
console.log('WebSocket connected');
};
this.ws.onmessage = (event) => {
const update = JSON.parse(event.data);
this.handleUpdate(update);
};
this.ws.onerror = (error) => {
console.error('WebSocket error:', error);
};
}
private handleUpdate(update: any): void {
const dm = new DataManager({
json: this.items,
adaptor: new JsonAdaptor()
});
if (update.operation === 'insert') {
this.items.push(update.data);
} else if (update.operation === 'update') {
const index = this.items.findIndex(item => item.OrderID === update.data.OrderID);
if (index >= 0) {
this.items[index] = update.data;
}
} else if (update.operation === 'delete') {
this.items = this.items.filter(item => item.OrderID !== update.id);
}
console.log('Data updated via WebSocket');
}
ngOnDestroy(): void {
if (this.ws) {
this.ws.close();
}
}
}---