Remote Data in Angular Grid Component
17 May 202424 minutes to read
In Angular Grid component, binding remote data is a fundamental aspect that enhances the efficiency of data interaction. This process involves assigning the service data, represented as an instance of DataManager
, to the dataSource
property of the Angular Grid component. By doing so, you enable seamless interaction with a remote data source, and this is achieved by specifying the endpoint URL where the data is hosted.
Additionally, leverage the power of Observables for data retrieval and operations, enhancing event handling, asynchronous programming, and concurrent value management in Angular applications.
Binding observable data using async pipe
Observables are a core concept in reactive programming and are widely used in the Angular framework. An Observable represents a stream of data or events that can be observed over time. It provides a way to handle asynchronous operations, such as handling developer’s input, dealing with HTTP requests, and managing events.
In the Syncfusion Angular Grid, seamlessly utilize the power of Observables through the async pipe for effortless binding of grid data. The AsyncPipe efficiently subscribes to the observable, extracting the latest emitted value structured with result
and count
properties, aligning perfectly with the grid’s data expectations.
The Syncfusion Grid component offers a range of powerful features for handling grid actions such as searching, filtering, sorting, grouping, and paging. These actions trigger the dataStateChange event, which provides you with the opportunity to manage and manipulate data according to the individual’s interactions.
Using the dataStateChange event
The dataStateChange
event is triggered whenever you perform actions that modify the state of the grid’s data, such as changing pages, applying sorting, or grouping. This event provides detailed information about the action performed and the current state of the grid, including parameters like page number, sorting details, and filtering criteria.
To implement the dataStateChange
event, follow these steps:
-
Subscribe to the event: In your component code, subscribe to the
dataStateChange
event using the appropriate event handler function. This function will be executed whenever you interact with the grid. -
Handle data state: Inside the event handler function, you can access the event arguments to determine the individual actions and intentions. The action property of the event arguments indicates the type of action performed (e.g., paging, sorting, grouping).
The
dataStateChange
event will not be triggered during the initial rendering. If you want the grid to display records, you can perform the operation in thengOnInit
.
Handling searching operation
When performing a search operation in the grid, the dataStateChange
event is triggered, allowing access to the following referenced arguments within the event
You can change the Observable based on the new grid data state of search action as follows:
private applySearching(query: Query, search: Array<any>): void {
// Check if a search operation is requested
if (search && search.length > 0) {
// Extract the search key and fields from the search array
const { fields, key } = search[0];
// perform search operation using the field and key on the query
query.search(key, fields);
}
}
/** GET all data from the server */
getAllData(state: any, action: any): Observable<any> {
const query = new Query();
// search
if (state.search) {
this.applySearching(query, state.search);
};
// To get the count of the data
query.isCountRequired = true
return this.http.get<Customer[]>(this.customersUrl).pipe(
map((response: any[]) => {
// Execute local data operations using the provided query
const currentResult: any = new DataManager(response).executeLocal(query);
// Return the result along with the count of total records
return {
result: currentResult.result, // Result of the data
count: currentResult.count // Total record count
};
})
);
}
Handling filtering operation
When filtering operation is performed in the grid, the dataStateChange
event is triggered, providing access to the following referenced arguments within the event.
You can change the Observable based on the new grid data state of filter action as follows:
private applyFiltering(query: Query, filter: any): void {
// Check if filter columns are specified
if (filter.columns && filter.columns.length) {
// Apply filtering for each specified column
for (let i = 0; i < filter.columns.length; i++) {
const field = filter.columns[i].field;
const operator = filter.columns[i].operator;
const value = filter.columns[i].value;
query.where(field, operator, value);
}
}
else {
// Apply filtering based on direct filter conditions
for (let i = 0; i < filter.length; i++) {
const { fn, e } = filter[i];
if (fn === 'onWhere') {
query.where(e as string);
}
}
}
}
/** GET all data from the server */
getAllData(state: any, action: any): Observable<any> {
const query = new Query();
// filtering
if (state.where) {
this.applyFiltering(query, action.queries);
}
// initial filtering
if (state.filter && state.filter.columns && state.filter.columns.length) {
this.applyFiltering(query, state.filter);
}
// To get the count of the data
query.isCountRequired = true
return this.http.get<Customer[]>(this.customersUrl).pipe(
map((response: any[]) => {
// Execute local data operations using the provided query
const currentResult: any = new DataManager(response).executeLocal(query);
// Return the result along with the count of total records
return {
result: currentResult.result, // Result of the data
count: currentResult.count // Total record count
};
})
);
}
When filtering multiple values, you can get the predicates as arguments in the dataStateChange
event. You can create your predicate execution based on the predicates values.
Handling sorting operation
When sorting operation is performed in the grid, the dataStateChange event is triggered, and within this event, you can access the following referenced arguments.
When performing multi-column sorting, you can get the below referred arguments in the dataStateChange
event.
You can change the Observable based on the new grid data state of sort action as follows:
private applySorting(query: Query, sorted: sortInfo[]): void {
// Check if sorting data is available
if (sorted && sorted.length > 0) {
// Iterate through each sorting info
sorted.forEach(sort => {
// Get the sort field name either by name or field
const sortField = sort.name || sort.field;
// Perform sort operation using the query based on the field name and direction
query.sortBy(sortField as string, sort.direction);
});
}
}
/** GET all data from the server */
getAllData(state: any, action: any): Observable<any> {
const query = new Query();
// sorting
if (state.sorted) {
state.sorted.length ? this.applySorting(query, state.sorted) :
// initial sorting
state.sorted.columns.length ? this.applySorting(query, state.sorted.columns) : null
}
// To get the count of the data
query.isCountRequired = true
return this.http.get<Customer[]>(this.customersUrl).pipe(
map((response: any[]) => {
// Execute local data operations using the provided query
const currentResult: any = new DataManager(response).executeLocal(query);
// Return the result along with the count of total records
return {
result: currentResult.result, // Result of the data
count: currentResult.count // Total record count
};
})
);
}
Handling paging operation
When paging operation is performed in the grid, the dataStateChange
event is triggered, and within this event, you can access the following referenced arguments.
You can change the Observable based on the new grid data state of page action as follows:
private applyPaging(query: Query, state: any) {
// Check if both 'take' and 'skip' values are available
if (state.take && state.skip) {
// Calculate pageSkip and pageTake values to get pageIndex and pageSize
const pageSkip = state.skip / state.take + 1;
const pageTake = state.take;
query.page(pageSkip, pageTake);
}
// If if only 'take' is available and 'skip' is 0, apply paging for the first page.
else if (state.skip === 0 && state.take) {
query.page(1, state.take);
}
}
/** GET all data from the server */
getAllData(state: any, action: any): Observable<any> {
const query = new Query();
// paging
this.applyPaging(query, state)
// To get the count of the data
query.isCountRequired = true
return this.http.get<Customer[]>(this.customersUrl).pipe(
map((response: any[]) => {
// Execute local data operations using the provided query
const currentResult: any = new DataManager(response).executeLocal(query);
// Return the result along with the count of total records
return {
result: currentResult.result, // Result of the data
count: currentResult.count // Total record count
};
})
);
}
Handling grouping operation
When grouping operation is performed in the grid, the dataStateChange
event is triggered, providing access to the following referenced arguments within the event.
You can change the Observable based on the new grid data state of group action as follows:
private applyGrouping(query: Query, group: any): void {
// Check if sorting data is available
if (group.length > 0) {
// Iterate through each group info
group.forEach((column: string) => {
// perform group operation using the column on the query
query.group(column);
});
}
}
/** GET all data from the server */
getAllData(state: any, action: any): Observable<any> {
const query = new Query();
// grouping
if (state.group) {
state.group.length ? this.applyGrouping(query, state.group) :
// initial grouping
state.group.columns.length ? this.applyGrouping(query, state.group.columns) : null
}
// To get the count of the data
query.isCountRequired = true
return this.http.get<Customer[]>(this.customersUrl).pipe(
map((response: any[]) => {
// Execute local data operations using the provided query
const currentResult: any = new DataManager(response).executeLocal(query);
// Return the result along with the count of total records
return {
result: currentResult.result, // Result of the data
count: currentResult.count // Total record count
};
})
);
}
- In order to utilize group action, it is necessary to manage the sorting query.
Lazy load grouping
In Angular, lazy loading refers to the technique of loading data dynamically when they are needed, instead of loading everything upfront. Lazy load grouping allows you to load and display grouped data efficiently by fetching only the required data on demand.
To enable this feature, you need to set the groupSettings.enableLazyLoading property to true. Also, you need to manage the state based on the initial grid action as follows.
public ngOnInit(): void {
this.groupOptions = { columns: ['ProductName'], enableLazyLoading: true, };
const state = { skip: 0, take: 12, group:this.groupOptions };
this.crudService.execute(state, query);
}
Based on the initial state, you can get the arguments as shown below
You can change the Observable based on the grid state as follows:
private applyGrouping(query: Query, group: any): void {
// Check if sorting data is available
if (group.length > 0) {
// Iterate through each group info
group.forEach((column: string) => {
// perform group operation using the column on the query
query.group(column);
});
}
}
private applyLazyLoad = (query: Query, state: any) => {
if (state.isLazyLoad) {
// Configure lazy loading for the main data
query.lazyLoad.push({ key: 'isLazyLoad', value: true });
// If on-demand group loading is enabled, configure lazy loading for grouped data
if (state.onDemandGroupInfo) {
query.lazyLoad.push({
key: 'onDemandGroupInfo',
value: state.action.lazyLoadQuery,
});
}
}
}
/** GET all data from the server */
getAllData(state: any, action: any): Observable<any> {
const query = new Query();
// grouping
if (state.group) {
state.group.length ? this.applyGrouping(query, state.group) :
// initial grouping
state.group.columns.length ? this.applyGrouping(query, state.group.columns) : null
}
// lazy load grouping
this.applyLazyLoad(query, state)
// initial grouping with lazy load
if (state.group && state.group.enableLazyLoading) {
query.lazyLoad.push({ key: 'isLazyLoad', value: true })
}
// To get the count of the data
query.isCountRequired = true
return this.http.get<Customer[]>(this.customersUrl).pipe(
map((response: any[]) => {
// Execute local data operations using the provided query
const currentResult: any = new DataManager(response).executeLocal(query);
// Return the result along with the count of total records
return {
result: currentResult.result, // Result of the data
count: currentResult.count // Total record count
};
})
);
}
Further information can be accessed in the respective documentation for lazy load grouping.
The complete example is available in the handling CRUD operations topic.
Handling CRUD operations
The Grid component provides powerful options for dynamically inserting, deleting, and updating records, enabling you to modify data directly within the grid. This feature is useful when you want to perform CRUD (Create, Read, Update, Delete) operations seamlessly.
Integrating CRUD Operations
To implement CRUD operations using Syncfusion Grid, follow these steps:
-
Configure grid settings: Set up the necessary grid settings, such as editing, adding, and deleting records. Define the toolbar options to facilitate your interactions.
-
Handle data state changes: Utilize the dataStateChange event to respond to changes in the grid’s data state. This event is triggered whenever you interact with the grid, such as paging or sorting.
-
Execute CRUD operations: Within the event handler for dataSourceChanged, implement logic to handle various CRUD actions based on the action or
requestType
property of the event arguments. -
Call endEdit method: After performing CRUD operations (adding, editing, or deleting), call the endEdit method to signal the completion of the operation and update the grid accordingly.
Insert operation
When an insert operation is performed in the grid, the dataSourceChanged
event will be triggered, allowing access to the following referenced arguments within the event.
/** POST: add a new record to the server */
addRecord(state: DataSourceChangedEventArgs): Observable<Customer> {
return this.http.post<Customer>(this.customersUrl, state.data, httpOptions);
}
Edit operation
When an edit operation is performed in the grid, the dataSourceChanged
event will be triggered, providing access to the following referenced arguments within the event.
/** PUT: update the record on the server */
updateRecord(state: DataSourceChangedEventArgs): Observable<Customer> {
return this.http.put(this.customersUrl, state.data, httpOptions);
}
Delete operation
When a delete operation is performed in the grid, the dataSourceChanged
event will be triggered, allowing access to the following referenced arguments within the event.
/** DELETE: delete the record from the server */
deleteRecord(state: any): Observable<Customer> {
const id = state.data[0].id;
const url = `${this.customersUrl}/${id}`;
return this.http.delete<Customer>(url, httpOptions);
}
The following example demonstrates how to bind observable data with using async pipe to handle grid actions and CRUD operations.
import { NgModule } from '@angular/core'
import { BrowserModule } from '@angular/platform-browser'
import { GridModule } from '@syncfusion/ej2-angular-grids'
import { HttpClient, HttpClientModule } from '@angular/common/http'
import { PageService, SortService, FilterService, GroupService, InfiniteScrollService, LazyLoadGroupService,
EditService, ToolbarService, AggregateService, SearchService, PdfExportService, ExcelExportService, } from '@syncfusion/ej2-angular-grids'
import { Component, OnInit, ViewChild } from '@angular/core';
import { GridComponent } from '@syncfusion/ej2-angular-grids';
import { DataStateChangeEventArgs, DataSourceChangedEventArgs } from '@syncfusion/ej2-angular-grids';
import { CrudService } from './crud.service';
import { Observable } from 'rxjs';
import { Customer } from './customers';
import { query } from '@angular/animations';
@Component({
imports: [
GridModule,HttpClientModule,
InMemoryWebApiModule.forRoot(InMemoryDataService)
],
providers: [PageService,
SortService,
FilterService,
EditService,
ToolbarService,
GroupService,
PdfExportService,
ExcelExportService,
AggregateService,
InfiniteScrollService,
LazyLoadGroupService,
SearchService,
HttpClient, CrudService],
standalone: true,
selector: 'app-root',
template: `<ejs-grid #grid [dataSource]='data | async' [editSettings]='editSettings' [toolbar]='toolbar' allowPaging="true" allowGrouping="true"
[groupSettings]="groupOptions" allowSorting="true" [sortSettings]='sortOptions' allowFiltering="true" [filterSettings]='filterOptions'
(dataSourceChanged)='dataSourceChanged($event)' (dataStateChange)= 'dataStateChange($event)'>
<e-columns>
<e-column field='id' headerText='Order ID' width='90' textAlign='Right' isPrimaryKey='true' [validationRules]='orderIDRules'></e-column>
<e-column field="CustomerName" headerText="Customer Name" width="100"></e-column>
<e-column field='ProductID' headerText='Product ID' width=100></e-column>
<e-column field='ProductName' headerText='Product Name' format='C2' width=100></e-column>
</e-columns>
</ejs-grid>`
})
export class AppComponent implements OnInit {
public data?: Observable<DataStateChangeEventArgs>;
public state?: DataStateChangeEventArgs;
public customers?: Customer[];
public toolbar?: string[];
public editSettings?: Object;
public orderIDRules?: object;
@ViewChild('grid')
public grid?: GridComponent;
public groupOptions?: object;
public filterOptions?: object;
public sortOptions?: object;
constructor(public crudService: CrudService) {
this.data = crudService;
}
public dataStateChange(state: DataStateChangeEventArgs): void {
const query = (this.grid as GridComponent).getDataModule().generateQuery();
this.crudService.execute(state, query);
}
public dataSourceChanged(state: DataSourceChangedEventArgs): void {
switch (state.action || state.requestType) {
case 'add': {
this.crudService.addRecord(state).subscribe(() => {
(state as GridComponent).endEdit();
});
}
break;
case 'edit': {
this.crudService.updateRecord(state).subscribe(() => (state as GridComponent).endEdit());
}
break;
case 'delete': {
this.crudService.deleteRecord(state).subscribe(() => {
(state as GridComponent).endEdit();
});
}
break;
}
}
public ngOnInit(): void {
this.groupOptions = { columns: ['ProductName'], enableLazyLoading: true, showGroupedColumn: true, };
this.filterOptions = { columns: [{ field: 'CustomerName', matchCase: false, operator: 'startswith', predicate: 'and', value: 'Maria' }] }
this.sortOptions = { columns: [{ field: 'ProductID', direction: 'Descending' }] }
const state = { skip: 0, take: 12, group: this.groupOptions, filter:this.filterOptions, sorted:this.sortOptions };
this.toolbar = ['Add', 'Edit', 'Delete', 'Update', 'Cancel', 'Search'];
this.orderIDRules = { required: true };
this.editSettings = {
allowEditing: true,
allowAdding: true,
allowDeleting: true,
};
this.crudService.execute(state, query);
}
}
import { Injectable } from '@angular/core';
import { HttpClient, HttpHeaders } from '@angular/common/http';
import { Customer } from './customers';
import { Observable } from 'rxjs';
import { Subject } from 'rxjs';
import { map } from 'rxjs';
import { DataStateChangeEventArgs, DataSourceChangedEventArgs } from '@syncfusion/ej2-grids';
import { DataManager, Query } from '@syncfusion/ej2-data';
interface sortInfo {
name: string
field:string
direction: string
}
const httpOptions = {
headers: new HttpHeaders({ 'Content-Type': 'application/json' })
};
@Injectable()
export class CrudService extends Subject<DataStateChangeEventArgs> {
private customersUrl = 'api/customers'; // URL to web api
constructor(
private http: HttpClient) {
super();
}
public execute(state: any, query: any): void {
this.getAllData(state, query).subscribe(x => super.next(x as DataStateChangeEventArgs));
}
private applyFiltering(query: Query, filter: any): void {
// Check if filter columns are specified
if (filter.columns && filter.columns.length) {
// Apply filtering for each specified column
for (let i = 0; i < filter.columns.length; i++) {
const field = filter.columns[i].field;
const operator = filter.columns[i].operator;
const value = filter.columns[i].value;
query.where(field, operator, value);
}
}
else {
// Apply filtering based on direct filter conditions
for (let i = 0; i < filter.length; i++) {
const { fn, e } = filter[i];
if (fn === 'onWhere') {
query.where(e as string);
}
}
}
}
private applySearching(query: Query, search: any): void {
// Check if a search operation is requested
if (search && search.length > 0) {
// Extract the search key and fields from the search array
const { fields, key } = search[0];
// perform search operation using the field and key on the query
query.search(key, fields);
}
}
private applySorting(query: Query, sorted: sortInfo[]): void {
// Check if sorting data is available
if (sorted && sorted.length > 0) {
// Iterate through each sorting info
sorted.forEach(sort => {
// get the sort field name either by name or field
const sortField = sort.name || sort.field;
// Perform sort operation using the query based on the field name and direction
query.sortBy(sortField as string, sort.direction);
});
}
}
private applyGrouping(query: Query, group: any): void {
// Check if sorting data is available
if (group.length > 0) {
// Iterate through each group info
group.forEach((column: string) => {
// perform group operation using the column on the query
query.group(column);
});
}
}
private applyLazyLoad = (query: Query, state: any) => {
if (state.isLazyLoad) {
// Configure lazy loading for the main data
query.lazyLoad.push({ key: 'isLazyLoad', value: true });
// If on-demand group loading is enabled, configure lazy loading for grouped data
if (state.onDemandGroupInfo) {
query.lazyLoad.push({
key: 'onDemandGroupInfo',
value: state.action.lazyLoadQuery,
});
}
}
}
private applyPaging(query: Query, state: any) {
// Check if both 'take' and 'skip' values are available
if (state.take && state.skip) {
// Calculate pageSkip and pageTake values to get pageIndex and pageSize
const pageSkip = state.skip / state.take + 1;
const pageTake = state.take;
query.page(pageSkip, pageTake);
}
// If if only 'take' is available and 'skip' is 0, apply paging for the first page.
else if (state.skip === 0 && state.take) {
query.page(1, state.take);
}
}
/** GET all data from the server */
getAllData(state: any, action: any): Observable<any> {
const query = new Query();
// filtering
if (state.where) {
this.applyFiltering(query, action.queries);
}
// initial filtering
if (state.filter && state.filter.columns && state.filter.columns.length) {
this.applyFiltering(query, state.filter);
}
// search
if (state.search) {
this.applySearching(query, state.search);
};
// sorting
if (state.sorted) {
state.sorted.length ? this.applySorting(query, state.sorted) :
// initial sorting
state.sorted.columns.length ? this.applySorting(query, state.sorted.columns) : null
}
// grouping
if (state.group) {
state.group.length ? this.applyGrouping(query, state.group) :
// initial grouping
state.group.columns.length ? this.applyGrouping(query, state.group.columns) : null
}
// lazy load grouping
this.applyLazyLoad(query, state)
// intial grouping with lazy load
if (state.group && state.group.enableLazyLoading) {
query.lazyLoad.push({ key: 'isLazyLoad', value: true })
}
// paging
this.applyPaging(query, state)
// To get the count of the data
query.isCountRequired = true
return this.http.get<Customer[]>(this.customersUrl).pipe(
map((response: any[]) => {
// Execute local data operations using the provided query
const currentResult: any = new DataManager(response).executeLocal(query);
// Return the result along with the count of total records
return {
result: currentResult.result, // Result of the data
count: currentResult.count // Total record count
};
})
);
}
/** POST: add a new record to the server */
addRecord(state: DataSourceChangedEventArgs): Observable<Customer> {
return this.http.post<Customer>(this.customersUrl, state.data, httpOptions);
}
/** DELETE: delete the record from the server */
deleteRecord(state: any): Observable<Customer> {
const id = state.data[0].id;
const url = `${this.customersUrl}/${id}`;
return this.http.delete<Customer>(url, httpOptions);
}
/** PUT: update the record on the server */
updateRecord(state: DataSourceChangedEventArgs): Observable<Customer> {
return this.http.put(this.customersUrl, state.data, httpOptions);
}
}
import { InMemoryDbService } from 'angular-in-memory-web-api';
export class InMemoryDataService implements InMemoryDbService {
createDb() {
const customers = createLazyLoadData();
return { customers };
}
}
function createLazyLoadData(): Object[] {
let lazyLoadData: Object[] = [];
let customerid: string[] = ['VINET', 'TOMSP', 'HANAR', 'VICTE', 'SUPRD', 'HANAR', 'CHOPS', 'RICSU', 'WELLI', 'HILAA', 'ERNSH', 'CENTC',
'OTTIK', 'QUEDE', 'RATTC', 'ERNSH', 'FOLKO', 'BLONP', 'WARTH', 'FRANK', 'GROSR', 'WHITC', 'WARTH', 'SPLIR', 'RATTC', 'QUICK', 'VINET',
'MAGAA', 'TORTU', 'MORGK', 'BERGS', 'LEHMS', 'BERGS', 'ROMEY', 'ROMEY', 'LILAS', 'LEHMS', 'QUICK', 'QUICK', 'RICAR', 'REGGC', 'BSBEV',
'COMMI', 'QUEDE', 'TRADH', 'TORTU', 'RATTC', 'VINET', 'LILAS', 'BLONP', 'HUNGO', 'RICAR', 'MAGAA', 'WANDK', 'SUPRD', 'GODOS', 'TORTU',
'OLDWO', 'ROMEY', 'LONEP', 'ANATR', 'HUNGO', 'THEBI', 'DUMON', 'WANDK', 'QUICK', 'RATTC', 'ISLAT', 'RATTC', 'LONEP', 'ISLAT', 'TORTU',
'WARTH', 'ISLAT', 'PERIC', 'KOENE', 'SAVEA', 'KOENE', 'BOLID', 'FOLKO', 'FURIB', 'SPLIR', 'LILAS', 'BONAP', 'MEREP', 'WARTH', 'VICTE',
'HUNGO', 'PRINI', 'FRANK', 'OLDWO', 'MEREP', 'BONAP', 'SIMOB', 'FRANK', 'LEHMS', 'WHITC', 'QUICK', 'RATTC', 'FAMIA'];
let product: string[] = ['Chai', 'Chang', 'Aniseed Syrup', 'Chef Anton\'s Cajun Seasoning', 'Chef Anton\'s Gumbo Mix', 'Grandma\'s Boysenberry Spread',
'Uncle Bob\'s Organic Dried Pears', 'Northwoods Cranberry Sauce', 'Mishi Kobe Niku', 'Ikura', 'Queso Cabrales', 'Queso Manchego La Pastora', 'Konbu',
'Tofu', 'Genen Shouyu', 'Pavlova', 'Alice Mutton', 'Carnarvon Tigers', 'Teatime Chocolate Biscuits', 'Sir Rodney\'s Marmalade', 'Sir Rodney\'s Scones',
'Gustaf\'s Knäckebröd', 'Tunnbröd', 'Guaraná Fantástica', 'NuNuCa Nuß-Nougat-Creme', 'Gumbär Gummibärchen', 'Schoggi Schokolade', 'Rössle Sauerkraut',
'Thüringer Rostbratwurst', 'Nord-Ost Matjeshering', 'Gorgonzola Telino', 'Mascarpone Fabioli', 'Geitost', 'Sasquatch Ale', 'Steeleye Stout', 'Inlagd Sill',
'Gravad lax', 'Côte de Blaye', 'Chartreuse verte', 'Boston Crab Meat', 'Jack\'s New England Clam Chowder', 'Singaporean Hokkien Fried Mee', 'Ipoh Coffee',
'Gula Malacca', 'Rogede sild', 'Spegesild', 'Zaanse koeken', 'Chocolade', 'Maxilaku', 'Valkoinen suklaa', 'Manjimup Dried Apples', 'Filo Mix', 'Perth Pasties',
'Tourtière', 'Pâté chinois', 'Gnocchi di nonna Alice', 'Ravioli Angelo', 'Escargots de Bourgogne', 'Raclette Courdavault', 'Camembert Pierrot', 'Sirop d\'érable',
'Tarte au sucre', 'Vegie-spread', 'Wimmers gute Semmelknödel', 'Louisiana Fiery Hot Pepper Sauce', 'Louisiana Hot Spiced Okra', 'Laughing Lumberjack Lager', 'Scottish Longbreads',
'Gudbrandsdalsost', 'Outback Lager', 'Flotemysost', 'Mozzarella di Giovanni', 'Röd Kaviar', 'Longlife Tofu', 'Rhönbräu Klosterbier', 'Lakkalikööri', 'Original Frankfurter grüne Soße'];
let customername: string[] = ['Maria', 'Ana Trujillo', 'Antonio Moreno', 'Thomas Hardy', 'Christina Berglund', 'Hanna Moos', 'Frédérique Citeaux', 'Martín Sommer', 'Laurence Lebihan', 'Elizabeth Lincoln',
'Victoria Ashworth', 'Patricio Simpson', 'Francisco Chang', 'Yang Wang', 'Pedro Afonso', 'Elizabeth Brown', 'Sven Ottlieb', 'Janine Labrune', 'Ann Devon', 'Roland Mendel', 'Aria Cruz', 'Diego Roel',
'Martine Rancé', 'Maria Larsson', 'Peter Franken', 'Carine Schmitt', 'Paolo Accorti', 'Lino Rodriguez', 'Eduardo Saavedra', 'José Pedro Freyre', 'André Fonseca', 'Howard Snyder', 'Manuel Pereira',
'Mario Pontes', 'Carlos Hernández', 'Yoshi Latimer', 'Patricia McKenna', 'Helen Bennett', 'Philip Cramer', 'Daniel Tonini', 'Annette Roulet', 'Yoshi Tannamuri', 'John Steel', 'Renate Messner', 'Jaime Yorres',
'Carlos González', 'Felipe Izquierdo', 'Fran Wilson', 'Giovanni Rovelli', 'Catherine Dewey', 'Jean Fresnière', 'Alexander Feuer', 'Simon Crowther', 'Yvonne Moncada', 'Rene Phillips', 'Henriette Pfalzheim',
'Marie Bertrand', 'Guillermo Fernández', 'Georg Pipps', 'Isabel de Castro', 'Bernardo Batista', 'Lúcia Carvalho', 'Horst Kloss', 'Sergio Gutiérrez', 'Paula Wilson', 'Maurizio Moroni', 'Janete Limeira', 'Michael Holz',
'Alejandra Camino', 'Jonas Bergulfsen', 'Jose Pavarotti', 'Hari Kumar', 'Jytte Petersen', 'Dominique Perrier', 'Art Braunschweiger', 'Pascale Cartrain', 'Liz Nixon', 'Liu Wong', 'Karin Josephs', 'Miguel Angel Paolino',
'Anabela Domingues', 'Helvetius Nagy', 'Palle Ibsen', 'Mary Saveley', 'Paul Henriot', 'Rita Müller', 'Pirkko Koskitalo', 'Paula Parente', 'Karl Jablonski', 'Matti Karttunen', 'Zbyszek Piestrzeniewicz'];
let customeraddress: string[] = ['507 - 20th Ave. E.\r\nApt. 2A', '908 W. Capital Way', '722 Moss Bay Blvd.', '4110 Old Redmond Rd.', '14 Garrett Hill', 'Coventry House\r\nMiner Rd.', 'Edgeham Hollow\r\nWinchester Way',
'4726 - 11th Ave. N.E.', '7 Houndstooth Rd.', '59 rue de l\'Abbaye', 'Luisenstr. 48', '908 W. Capital Way', '722 Moss Bay Blvd.', '4110 Old Redmond Rd.', '14 Garrett Hill', 'Coventry House\r\nMiner Rd.', 'Edgeham Hollow\r\nWinchester Way',
'7 Houndstooth Rd.', '2817 Milton Dr.', 'Kirchgasse 6', 'Sierras de Granada 9993', 'Mehrheimerstr. 369', 'Rua da Panificadora, 12', '2817 Milton Dr.', 'Mehrheimerstr. 369'];
let quantityperunit: string[] = ['10 boxes x 20 bags', '24 - 12 oz bottles', '12 - 550 ml bottles', '48 - 6 oz jars', '36 boxes', '12 - 8 oz jars', '12 - 1 lb pkgs.', '12 - 12 oz jars', '18 - 500 g pkgs.', '12 - 200 ml jars',
'1 kg pkg.', '10 - 500 g pkgs.', '2 kg box', '40 - 100 g pkgs.', '24 - 250 ml bottles', '32 - 500 g boxes', '20 - 1 kg tins', '16 kg pkg.', '10 boxes x 12 pieces', '30 gift boxes', '24 pkgs. x 4 pieces', '24 - 500 g pkgs.', '12 - 250 g pkgs.',
'12 - 355 ml cans', '20 - 450 g glasses', '100 - 250 g bags'];
let id: number = 10248;
for (let i: number = 0; i < 20000; i++) {
lazyLoadData.push({
'id': id + i,
'CustomerID': customerid[Math.floor(Math.random() * customerid.length)],
'CustomerName': customername[Math.floor(Math.random() * customername.length)],
'CustomerAddress': customeraddress[Math.floor(Math.random() * customeraddress.length)],
'ProductName': product[Math.floor(Math.random() * product.length)],
'ProductID': i,
'Quantity': quantityperunit[Math.floor(Math.random() * quantityperunit.length)]
})
}
return lazyLoadData;
}
export class Customer {
id?: number;
CustomerName?: string;
ProductID?:number
ProductName?:string
}
- While working with grid edit operation, defining the
isPrimaryKey
property of column is a mandatory step. In case the primary key column is not defined, the edit or delete action will take place on the first row of the grid.- Need to maintain the same observable instance for all grid actions.
- You can refer the guidelines for CRUD using observables here
Export all records in client side
Export all records with async pipe is especially beneficial when dealing with large datasets that need to be exported for offline analysis or sharing.
By default, when utilizing observables for Grid data binding, the export operation exports only the records on the current page. However, the Syncfusion Angular Grid component allows you to export all records, including those from multiple pages, by configuring the pdfExportProperties
and excelExportProperties
in conjunction with the Async Pipe for data binding.
To export all records, including those from multiple pages, configure the pdfExportProperties.dataSource for PDF exporting and excelExportProperties.dataSource for Excel exporting within the toolbarClick event handler. Inside this event, set the dataSource
property of pdfExportProperties
and excelExportProperties
for PDF and Excel exporting to include all records.
Excel Exporting
To export the complete grid data to Excel, utilize the excelExportProperties.dataSource
when initiating the Excel export. Use the following code snippet to export all records within the grid:
this.service.getData(state).subscribe((e: any) => {
let excelExportProperties: ExcelExportProperties = {
dataSource: e.result ? e.result : result
};
(this.grid as GridComponent).excelExport(excelExportProperties);// need to call excelExport method of grid when get the entire data
});
Pdf Exporting
To export the complete grid data to PDF document, utilize the pdfExportProperties.dataSource
when initiating the PDF export. Use the following code snippet to export all records within the grid:
this.service.getData(state).subscribe((e: any) => {
let pdfExportProperties: PdfExportProperties = {
dataSource: e.result ? e.result : result
};
(this.grid as GridComponent).pdfExport(pdfExportProperties); // need to call pdfExport method of grid when get the entire data
});
Further customization on grid export can be accessed in the respective documentation for PDF exporting and Excel exporting
The following code example shows how to export all records in client side for observable using the async pipe.
import { Component, OnInit, ViewChild } from '@angular/core';
import { GridComponent } from '@syncfusion/ej2-angular-grids';
import {DataStateChangeEventArgs, PdfExportProperties, ExcelExportProperties} from '@syncfusion/ej2-angular-grids';
import { ClickEventArgs } from "@syncfusion/ej2-navigations";
import { DataService } from './order.service';
import { Observable } from 'rxjs';
@Component({
selector: 'app-root',
template: `<ejs-grid #grid [dataSource]='data | async' (excelExportComplete)="exportComplete()" (pdfExportComplete)="exportComplete()" [allowExcelExport]='true' [allowPdfExport]='true' allowPaging='true' [pageSettings]='pageOptions' [toolbar]="toolbar" (toolbarClick)='toolbarClick($event)' (dataStateChange)= 'dataStateChange($event)'>
<e-columns>
<e-column field='OrderID' headerText='Order ID' width='90' textAlign='Right' isPrimaryKey='true'></e-column>
<e-column field="CustomerID" headerText="Customer Name" width="100"></e-column>
<e-column field='ShipName' headerText="Ship Name" width=110></e-column>
<e-column field='ShipCountry' headerText='Ship Country' width=100></e-column>
<e-column field='Freight' headerText='Freight' format='C2' textAlign='Right' width=100></e-column>
</e-columns>
</ejs-grid>`,
providers: [DataService],
})
export class AppComponent implements OnInit {
public data?: Observable<DataStateChangeEventArgs>;
public state?: DataStateChangeEventArgs;
public pageOptions?: object;
public toolbar?: string[];
@ViewChild('grid')
public grid?: GridComponent;
constructor(public service: DataService) {
this.data = service;
}
public dataStateChange(state: DataStateChangeEventArgs): void {
this.service.execute(state);
}
exportComplete() {
(this.grid as GridComponent).hideSpinner(); // hide the spinner when export completed
}
toolbarClick(args: ClickEventArgs): void {
let state: any = { action: {}, skip: 0, take: (this.grid as GridComponent).pageSettings.totalRecordsCount };
let result = {};
switch (args.item.text) {
case "PDF Export":
(this.grid as GridComponent).showSpinner(); // show the spinner when send the post to service
state.action.isPdfExport = true;
// fetch the entire data while PDF exporting
this.service.getData(state).subscribe((e: any) => {
let pdfExportProperties: PdfExportProperties = {
dataSource: e.result ? e.result : result
};
(this.grid as GridComponent).pdfExport(pdfExportProperties); // need to call pdfExport method of grid when get the entire data
});
break;
case "Excel Export":
// fetch the entire data while Excel exporting
(this.grid as GridComponent).showSpinner();// show the spinner when send the post to service
state.action.isExcelExport = true;
this.service.getData(state).subscribe((e: any) => {
let excelExportProperties: ExcelExportProperties = {
dataSource: e.result ? e.result : result
};
(this.grid as GridComponent).excelExport(excelExportProperties);// need to call excelExport method of grid when get the entire data
});
break;
}
}
public ngOnInit(): void {
this.pageOptions = { pageSize: 10, pageCount: 4 };
const state = { skip: 0, take: 10 };
this.toolbar = ["ExcelExport", "PdfExport",];
this.service.execute(state);
}
}
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import {DataStateChangeEventArgs,DataResult} from '@syncfusion/ej2-angular-grids';
import { Observable } from 'rxjs';
import { Subject } from 'rxjs';
import { map } from 'rxjs';
@Injectable()
export class DataService extends Subject<DataStateChangeEventArgs> {
private BASE_URL = 'https://services.odata.org/V4/Northwind/Northwind.svc/Orders';
constructor(private http: HttpClient) {
super();
}
public execute(state: any): void {
this.getData(state).subscribe(x => super.next(x));
}
public getData(state: DataStateChangeEventArgs): Observable<DataStateChangeEventArgs> {
const pageQuery = `$skip=${state.skip}&$top=${state.take}`;
return this.http
.get(`${this.BASE_URL}?${pageQuery}&$count=true`)
.pipe(map((response: any) => response))
.pipe(map((response: any) => (<DataResult>{
result: response['value'],
count: parseInt(response['@odata.count'], 10),
})))
.pipe((data: any) => data);
}
}
Binding observable data without using async pipe
In Angular, Observables data can be bound to UI elements using the AsyncPipe, which simplifies the process of subscribing to observables and managing the subscription life cycle. However, there are scenarios where you need to bind observable data to components without utilizing the async pipe. This approach offers more control over the subscription and data manipulation processes.
To bind observable data without using the async pipe in the Grid, follow these steps:
-
Subscribe to the observable data in the component.
-
Manually update the data source of the grid when the observable emits new values.
Handling searching operation
When searching operation is performed in the grid, the dataStateChange event is triggered, providing access to the following referenced arguments within the event.
You can change the Observable based on the new grid data state of search action as follows:
private applySearching(query: Query, search: Array<any>): void {
// Check if a search operation is requested
if (search && search.length > 0) {
// Extract the search key and fields from the search array
const { fields, key } = search[0];
// perform search operation using the field and key on the query
query.search(key, fields);
}
}
/** GET all data from the server */
getAllData(state: any, action: any): Observable<any> {
const query = new Query();
// search
if (state.search) {
this.applySearching(query, state.search);
};
// To get the count of the data
query.isCountRequired = true
}
return this.http.get<Customer[]>(this.customersUrl).pipe(
map((response: any[]) => {
// Execute local data operations using the provided query
const currentResult: any = new DataManager(response).executeLocal(query);
// Return the result along with the count of total records
return {
result: currentResult.result, // Result of the data
count: currentResult.count // Total record count
};
})
);
Handling filtering operation
When filtering operation is performed in the grid, the dataStateChange
event is triggered, providing access to the following referenced arguments within the event
You can change the Observable based on the new grid data state of filter action as follows:
private applyFiltering(query: Query, filter: any): void {
// Check if filter columns are specified
if (filter.columns && filter.columns.length) {
// Apply filtering for each specified column
for (let i = 0; i < filter.columns.length; i++) {
const field = filter.columns[i].field;
const operator = filter.columns[i].operator;
const value = filter.columns[i].value;
query.where(field, operator, value);
}
}
else {
// Apply filtering based on direct filter conditions
for (let i = 0; i < filter.length; i++) {
const { fn, e } = filter[i];
if (fn === 'onWhere') {
query.where(e as string);
}
}
}
}
/** GET all data from the server */
getAllData(state: any, action: any): Observable<any> {
const query = new Query();
// filtering
if (state.where) {
this.applyFiltering(query, action.queries);
}
// initial filtering
if (state.filter && state.filter.columns && state.filter.columns.length) {
this.applyFiltering(query, state.filter);
}
// To get the count of the data
query.isCountRequired = true
return this.http.get<Customer[]>(this.customersUrl).pipe(
map((response: any[]) => {
// Execute local data operations using the provided query
const currentResult: any = new DataManager(response).executeLocal(query);
// Return the result along with the count of total records
return {
result: currentResult.result, // Result of the data
count: currentResult.count // Total record count
};
})
);
}
When filtering multiple values, you can get the predicates as arguments in the dataStateChange
event. You can create your predicate execution based on the predicates values.
Handling sorting operation
When sorting operation is performed in the grid, the dataStateChange
event is triggered, and within this event, you can access the following referenced arguments
When performing multi-column sorting, you can get the below referred arguments in the dataStateChange
event.
You can change the Observable based on the new grid data state of sort action as follows:
private applySorting(query: Query, sorted: sortInfo[]): void {
// Check if sorting data is available
if (sorted && sorted.length > 0) {
// Iterate through each sorting info
sorted.forEach(sort => {
// Get the sort field name either by name or field
const sortField = sort.name || sort.field;
// Perform sort operation using the query based on the field name and direction
query.sortBy(sortField as string, sort.direction);
});
}
}
/** GET all data from the server */
getAllData(state: any, action: any): Observable<any> {
const query = new Query();
// sorting
if (state.sorted) {
state.sorted.length ? this.applySorting(query, state.sorted) :
// initial sorting
state.sorted.columns.length ? this.applySorting(query, state.sorted.columns) : null
}
// To get the count of the data
query.isCountRequired = true
return this.http.get<Customer[]>(this.customersUrl).pipe(
map((response: any[]) => {
// Execute local data operations using the provided query
const currentResult: any = new DataManager(response).executeLocal(query);
// Return the result along with the count of total records
return {
result: currentResult.result, // Result of the data
count: currentResult.count // Total record count
};
})
);
}
Handling paging operation
When paging operation is performed in the grid, the dataStateChange event is triggered, and within this event, you can access the following referenced arguments.
You can change the Observable based on the new grid data state of page action as follows:
private applyPaging(query: Query, state: any) {
// Check if both 'take' and 'skip' values are available
if (state.take && state.skip) {
// Calculate pageSkip and pageTake values to get pageIndex and pageSize
const pageSkip = state.skip / state.take + 1;
const pageTake = state.take;
query.page(pageSkip, pageTake);
}
// If if only 'take' is available and 'skip' is 0, apply paging for the first page.
else if (state.skip === 0 && state.take) {
query.page(1, state.take);
}
}
/** GET all data from the server */
getAllData(state: any, action: any): Observable<any> {
const query = new Query();
// paging
this.applyPaging(query, state)
// To get the count of the data
query.isCountRequired = true
return this.http.get<Customer[]>(this.customersUrl).pipe(
map((response: any[]) => {
// Execute local data operations using the provided query
const currentResult: any = new DataManager(response).executeLocal(query);
// Return the result along with the count of total records
return {
result: currentResult.result, // Result of the data
count: currentResult.count // Total record count
};
})
);
}
Handling grouping operation
When grouping operation is performed in the grid, the dataStateChange event is triggered, providing access to the following referenced arguments within the event.
You can change the Observable based on the new grid data state of group action as follows:
private applyGrouping(query: Query, group: any): void {
// Check if sorting data is available
if (group.length > 0) {
// Iterate through each group info
group.forEach((column: string) => {
// perform group operation using the column on the query
query.group(column);
});
}
}
/** GET all data from the server */
getAllData(state: any, action: any): Observable<any> {
const query = new Query();
// grouping
if (state.group) {
state.group.length ? this.applyGrouping(query, state.group) :
// initial grouping
state.group.columns.length ? this.applyGrouping(query, state.group.columns) : null
}
// To get the count of the data
query.isCountRequired = true
return this.http.get<Customer[]>(this.customersUrl).pipe(
map((response: any[]) => {
// Execute local data operations using the provided query
const currentResult: any = new DataManager(response).executeLocal(query);
// Return the result along with the count of total records
return {
result: currentResult.result, // Result of the data
count: currentResult.count // Total record count
};
})
);
}
- In order to utilize group actions, it is necessary to manage the sorting query.
Lazy load grouping
In Angular, lazy loading refers to the technique of loading data dynamically when they are needed, instead of loading everything upfront. Lazy load grouping allows you to load and display grouped data efficiently by fetching only the required data on demand.
To enable this feature, you need to set the groupSettings.enableLazyLoading property to true. Also, you need to manage the state based on the initial grid action as follows.
public ngOnInit(): void {
this.groupOptions = { columns: ['ProductName'], enableLazyLoading: true, };
const state = { skip: 0, take: 12, group:this.groupOptions };
this.crudService.execute(state, query);
}
Based on the initial state, you can get the arguments as shown below
You can change the Observable based on the grid state as follows:
private applyGrouping(query: Query, group: any): void {
// Check if sorting data is available
if (group.length > 0) {
// Iterate through each group info
group.forEach((column: string) => {
// perform group operation using the column on the query
query.group(column);
});
}
}
private applyLazyLoad = (query: Query, state: any) => {
if (state.isLazyLoad) {
// Configure lazy loading for the main data
query.lazyLoad.push({ key: 'isLazyLoad', value: true });
// If on-demand group loading is enabled, configure lazy loading for grouped data
if (state.onDemandGroupInfo) {
query.lazyLoad.push({
key: 'onDemandGroupInfo',
value: state.action.lazyLoadQuery,
});
}
}
}
/** GET all data from the server */
getAllData(state: any, action: any): Observable<any> {
const query = new Query();
// grouping
if (state.group) {
state.group.length ? this.applyGrouping(query, state.group) :
// initial grouping
state.group.columns.length ? this.applyGrouping(query, state.group.columns) : null
}
// lazy load grouping
this.applyLazyLoad(query, state)
// initial grouping with lazy load
if (state.group && state.group.enableLazyLoading) {
query.lazyLoad.push({ key: 'isLazyLoad', value: true })
}
// To get the count of the data
query.isCountRequired = true
return this.http.get<Customer[]>(this.customersUrl).pipe(
map((response: any[]) => {
// Execute local data operations using the provided query
const currentResult: any = new DataManager(response).executeLocal(query);
// Return the result along with the count of total records
return {
result: currentResult.result, // Result of the data
count: currentResult.count // Total record count
};
})
);
}
Further information can be accessed in the respective documentation for lazy load grouping.
The complete example is available in the Handling CRUD operations topic.
Handling CRUD operations
The Grid component provides powerful options for dynamically inserting, deleting, and updating records, enabling you to modify data directly within the grid. This feature is useful when you want to perform CRUD (Create, Read, Update, Delete) operations seamlessly.
Integrating CRUD Operations
To implement CRUD operations using Syncfusion Grid, follow these steps:
-
Configure grid settings: Set up the necessary grid settings, such as editing, adding, and deleting records. Define the toolbar options to facilitate your interactions.
-
Handle data state changes: Utilize the dataStateChange event to respond to changes in the grid’s data state. This event is triggered whenever you interact with the grid, such as paging or sorting.
-
Execute CRUD operations: Within the event handler for dataSourceChanged, implement logic to handle various CRUD actions based on the action or requestType property of the event arguments.
-
Call endEdit method: After performing CRUD operations (adding, editing, or deleting), call the endEdit method to signal the completion of the operation and update the grid accordingly.
Insert operation
When an insert operation is performed in the grid, the dataSourceChanged
event will be triggered, allowing access to the following referenced arguments within the event.
/** POST: add a new record to the server */
addRecord(state: DataSourceChangedEventArgs): Observable<Customer> {
return this.http.post<Customer>(this.customersUrl, state.data, httpOptions);
}
Edit operation
When an edit operation is performed in the grid, the dataSourceChanged
event will be triggered, providing access to the following referenced arguments within the event.
/** PUT: update the record on the server */
updateRecord(state: DataSourceChangedEventArgs): Observable<Customer> {
return this.http.put(this.customersUrl, state.data, httpOptions);
}
Delete operation
When a delete operation is performed in the grid, the dataSourceChanged
event will be triggered, allowing access to the following referenced arguments within the event.
/** DELETE: delete the record from the server */
deleteRecord(state: any): Observable<Customer> {
const id = state.data[0].id;
const url = `${this.customersUrl}/${id}`;
return this.http.delete<Customer>(url, httpOptions);
}
The following example demonstrates how to bind observable data without using async pipe to handle grid actions and CRUD operations.
import { NgModule } from '@angular/core'
import { BrowserModule } from '@angular/platform-browser'
import { GridModule } from '@syncfusion/ej2-angular-grids'
import { HttpClient, HttpClientModule } from '@angular/common/http'
import { PageService, SortService, FilterService, GroupService, InfiniteScrollService,
LazyLoadGroupService, EditService, ToolbarService, AggregateService, SearchService } from '@syncfusion/ej2-angular-grids'
import { Component, OnInit, ViewChild } from '@angular/core';
import { DataSourceChangedEventArgs, DataStateChangeEventArgs, GridComponent } from '@syncfusion/ej2-angular-grids';
import { CrudService } from './crud.service';
import { query } from '@angular/animations';
@Component({
imports: [
GridModule, HttpClientModule,
InMemoryWebApiModule.forRoot(InMemoryDataService)
],
providers: [PageService,
SortService,
FilterService,
GroupService,
EditService,
ToolbarService,
AggregateService,
SearchService,
InfiniteScrollService,
LazyLoadGroupService,
HttpClient, CrudService],
standalone: true,
selector: 'app-root',
template: `<ejs-grid #grid [dataSource]='data' allowPaging="true" allowGrouping="true" [groupSettings]="groupOptions" allowSorting="true"
[sortSettings]='sortOptions' allowFiltering="true" [filterSettings]='filterOptions' [editSettings]='editSettings' [toolbar]="toolbar"
(dataSourceChanged)='dataSourceChanged($event)' (dataStateChange)= 'dataStateChange($event)'>
<e-columns>
<e-column field='id' headerText='Order ID' width='90' textAlign='Right' [validationRules]='orderIDRules' isPrimaryKey='true'></e-column>
<e-column field="CustomerName" headerText="Customer Name" width="100"></e-column>
<e-column field='ProductID' headerText='Product ID' width=100></e-column>
<e-column field='ProductName' headerText='Product Name' width='160'></e-column>
</e-columns>
</ejs-grid>`
})
export class AppComponent implements OnInit {
public data?: object;
public state?: DataStateChangeEventArgs;
public pageOptions?: object;
public toolbar?: string[];
public editSettings?: Object;
public orderIDRules?: object;
@ViewChild('grid')
public grid?: GridComponent;
public groupOptions?: object;
public filterOptions?: object;
public sortOptions?: object;
constructor(public crudService: CrudService) {
this.data = crudService;
}
public dataStateChange(state: DataStateChangeEventArgs): void {
const query = (this.grid as GridComponent).getDataModule().generateQuery();
this.crudService.getAllData(state, query).subscribe((response: any) => (this.data = response));
}
public dataSourceChanged(state: DataSourceChangedEventArgs): void {
switch (state.action || state.requestType) {
case 'add': {
this.crudService.addRecord(state).subscribe(() => {
(state as GridComponent).endEdit();
});
}
break;
case 'edit': {
this.crudService.updateRecord(state).subscribe(() => (state as GridComponent).endEdit());
}
break;
case 'delete': {
this.crudService.deleteRecord(state).subscribe(() => {
(state as GridComponent).endEdit();
});
}
break;
}
}
ngOnInit() {
this.groupOptions = { columns: ['ProductName'], enableLazyLoading: true, showGroupedColumn: true, };
this.filterOptions = { columns: [{ field: 'CustomerName', matchCase: false, operator: 'startswith', predicate: 'and', value: 'Maria' }] }
this.sortOptions = { columns: [{ field: 'ProductID', direction: 'Descending' }] }
let state = { skip: 0, take: 12, group: this.groupOptions, filter:this.filterOptions, sorted:this.sortOptions };
this.toolbar = ['Add', 'Edit', 'Delete', 'Update', 'Cancel', 'Search'];
this.editSettings = {
allowEditing: true,
allowAdding: true,
allowDeleting: true,
};
this.orderIDRules = { required: true };
this.crudService.getAllData(state, query).subscribe((response: any) => (this.data = response));
}
}
import { Injectable } from '@angular/core';
import { HttpClient, HttpHeaders, HttpParams } from '@angular/common/http';
import { DataManager, Query } from '@syncfusion/ej2-data';
import { Customer } from './customers';
import { Observable, of } from 'rxjs';
import { Subject } from 'rxjs';
import { map } from 'rxjs';
import { DataStateChangeEventArgs, DataSourceChangedEventArgs } from '@syncfusion/ej2-grids';
interface sortInfo {
field: string;
name: string
direction: string
}
const httpOptions = {
headers: new HttpHeaders({ 'Content-Type': 'application/json' })
};
@Injectable()
export class CrudService extends Subject<DataStateChangeEventArgs> {
private customersUrl = 'api/customers'; // URL to web api
constructor(
private http: HttpClient) {
super();
}
public execute(state: any, query: any): void {
this.getAllData(state, query).subscribe(x => super.next(x as DataStateChangeEventArgs));
}
private applyFiltering(query: Query, filter: any): void {
// Check if filter columns are specified
if (filter.columns && filter.columns.length) {
// Apply filtering for each specified column
for (let i = 0; i < filter.columns.length; i++) {
const field = filter.columns[i].field;
const operator = filter.columns[i].operator;
const value = filter.columns[i].value;
query.where(field, operator, value);
}
}
else {
// Apply filtering based on direct filter conditions
for (let i = 0; i < filter.length; i++) {
const { fn, e } = filter[i];
if (fn === 'onWhere') {
query.where(e as string);
}
}
}
}
private applySearching(query: Query, search: any): void {
// Check if a search operation is requested
if (search && search.length > 0) {
// Extract the search key and fields from the search array
const { fields, key } = search[0];
// perform search operation using the field and key on the query
query.search(key, fields);
}
}
private applySorting(query: Query, sorted: sortInfo[]): void {
// Check if sorting data is available
if (sorted && sorted.length > 0) {
// Iterate through each sorting info
sorted.forEach(sort => {
// Get the sort field name either by name or field
const sortField = sort.name || sort.field;
// Perform sort operation using the query based on the field name and direction
query.sortBy(sortField as string, sort.direction);
});
}
}
private applyGrouping(query: Query, group: any): void {
// Check if sorting data is available
if (group.length > 0) {
// Iterate through each group info
group.forEach((column: string) => {
// perform group operation using the column on the query
query.group(column);
});
}
}
private applyLazyLoad = (query: Query, state: any) => {
if (state.isLazyLoad) {
// Configure lazy loading for the main data
query.lazyLoad.push({ key: 'isLazyLoad', value: true });
// If on-demand group loading is enabled, configure lazy loading for grouped data
if (state.onDemandGroupInfo) {
query.lazyLoad.push({
key: 'onDemandGroupInfo',
value: state.action.lazyLoadQuery,
});
}
}
}
private applyPaging(query: Query, state: any) {
// Check if both 'take' and 'skip' values are available
if (state.take && state.skip) {
// Calculate pageSkip and pageTake values to get pageIndex and pageSize
const pageSkip = state.skip / state.take + 1;
const pageTake = state.take;
query.page(pageSkip, pageTake);
}
// If if only 'take' is available and 'skip' is 0, apply paging for the first page.
else if (state.skip === 0 && state.take) {
query.page(1, state.take);
}
}
/** GET all data from the server */
getAllData(state: any, action: any): Observable<any> {
const query = new Query();
// filtering
if (state.where) {
this.applyFiltering(query, action.queries);
}
// initial filtering
if (state.filter && state.filter.columns && state.filter.columns.length) {
this.applyFiltering(query, state.filter);
}
// search
if (state.search) {
this.applySearching(query, state.search);
};
// sorting
if (state.sorted) {
state.sorted.length ? this.applySorting(query, state.sorted) :
// initial sorting
state.sorted.columns.length ? this.applySorting(query, state.sorted.columns) : null
}
// grouping
if (state.group) {
state.group.length ? this.applyGrouping(query, state.group) :
// initial grouping
state.group.columns.length ? this.applyGrouping(query, state.group.columns) : null
}
// lazy load grouping
this.applyLazyLoad(query, state)
// intial grouping with lazy load
if (state.group && state.group.enableLazyLoading) {
query.lazyLoad.push({ key: 'isLazyLoad', value: true })
}
// paging
this.applyPaging(query, state)
// To get the count of the data
query.isCountRequired = true
return this.http.get<Customer[]>(this.customersUrl).pipe(
map((response: any[]) => {
// Execute local data operations using the provided query
const currentResult: any = new DataManager(response).executeLocal(query);
// Return the result along with the count of total records
return {
result: currentResult.result, // Result of the data
count: currentResult.count // Total record count
};
})
);
}
/** POST: add a new record to the server */
addRecord(state: DataSourceChangedEventArgs): Observable<Customer> {
return this.http.post<Customer>(this.customersUrl, state.data, httpOptions);
}
/** DELETE: delete the record from the server */
deleteRecord(state: any): Observable<Customer> {
const id = state.data[0].id;
const url = `${this.customersUrl}/${id}`;
return this.http.delete<Customer>(url, httpOptions);
}
/** PUT: update the record on the server */
updateRecord(state: DataSourceChangedEventArgs): Observable<Customer> {
return this.http.put(this.customersUrl, state.data, httpOptions);
}
}
import { InMemoryDbService } from 'angular-in-memory-web-api';
export class InMemoryDataService implements InMemoryDbService {
createDb() {
const customers = createLazyLoadData();
return { customers };
}
}
function createLazyLoadData(): Object[] {
let lazyLoadData: Object[] = [];
let customerid: string[] = ['VINET', 'TOMSP', 'HANAR', 'VICTE', 'SUPRD', 'HANAR', 'CHOPS', 'RICSU', 'WELLI', 'HILAA', 'ERNSH', 'CENTC',
'OTTIK', 'QUEDE', 'RATTC', 'ERNSH', 'FOLKO', 'BLONP', 'WARTH', 'FRANK', 'GROSR', 'WHITC', 'WARTH', 'SPLIR', 'RATTC', 'QUICK', 'VINET',
'MAGAA', 'TORTU', 'MORGK', 'BERGS', 'LEHMS', 'BERGS', 'ROMEY', 'ROMEY', 'LILAS', 'LEHMS', 'QUICK', 'QUICK', 'RICAR', 'REGGC', 'BSBEV',
'COMMI', 'QUEDE', 'TRADH', 'TORTU', 'RATTC', 'VINET', 'LILAS', 'BLONP', 'HUNGO', 'RICAR', 'MAGAA', 'WANDK', 'SUPRD', 'GODOS', 'TORTU',
'OLDWO', 'ROMEY', 'LONEP', 'ANATR', 'HUNGO', 'THEBI', 'DUMON', 'WANDK', 'QUICK', 'RATTC', 'ISLAT', 'RATTC', 'LONEP', 'ISLAT', 'TORTU',
'WARTH', 'ISLAT', 'PERIC', 'KOENE', 'SAVEA', 'KOENE', 'BOLID', 'FOLKO', 'FURIB', 'SPLIR', 'LILAS', 'BONAP', 'MEREP', 'WARTH', 'VICTE',
'HUNGO', 'PRINI', 'FRANK', 'OLDWO', 'MEREP', 'BONAP', 'SIMOB', 'FRANK', 'LEHMS', 'WHITC', 'QUICK', 'RATTC', 'FAMIA'];
let product: string[] = ['Chai', 'Chang', 'Aniseed Syrup', 'Chef Anton\'s Cajun Seasoning', 'Chef Anton\'s Gumbo Mix', 'Grandma\'s Boysenberry Spread',
'Uncle Bob\'s Organic Dried Pears', 'Northwoods Cranberry Sauce', 'Mishi Kobe Niku', 'Ikura', 'Queso Cabrales', 'Queso Manchego La Pastora', 'Konbu',
'Tofu', 'Genen Shouyu', 'Pavlova', 'Alice Mutton', 'Carnarvon Tigers', 'Teatime Chocolate Biscuits', 'Sir Rodney\'s Marmalade', 'Sir Rodney\'s Scones',
'Gustaf\'s Knäckebröd', 'Tunnbröd', 'Guaraná Fantástica', 'NuNuCa Nuß-Nougat-Creme', 'Gumbär Gummibärchen', 'Schoggi Schokolade', 'Rössle Sauerkraut',
'Thüringer Rostbratwurst', 'Nord-Ost Matjeshering', 'Gorgonzola Telino', 'Mascarpone Fabioli', 'Geitost', 'Sasquatch Ale', 'Steeleye Stout', 'Inlagd Sill',
'Gravad lax', 'Côte de Blaye', 'Chartreuse verte', 'Boston Crab Meat', 'Jack\'s New England Clam Chowder', 'Singaporean Hokkien Fried Mee', 'Ipoh Coffee',
'Gula Malacca', 'Rogede sild', 'Spegesild', 'Zaanse koeken', 'Chocolade', 'Maxilaku', 'Valkoinen suklaa', 'Manjimup Dried Apples', 'Filo Mix', 'Perth Pasties',
'Tourtière', 'Pâté chinois', 'Gnocchi di nonna Alice', 'Ravioli Angelo', 'Escargots de Bourgogne', 'Raclette Courdavault', 'Camembert Pierrot', 'Sirop d\'érable',
'Tarte au sucre', 'Vegie-spread', 'Wimmers gute Semmelknödel', 'Louisiana Fiery Hot Pepper Sauce', 'Louisiana Hot Spiced Okra', 'Laughing Lumberjack Lager', 'Scottish Longbreads',
'Gudbrandsdalsost', 'Outback Lager', 'Flotemysost', 'Mozzarella di Giovanni', 'Röd Kaviar', 'Longlife Tofu', 'Rhönbräu Klosterbier', 'Lakkalikööri', 'Original Frankfurter grüne Soße'];
let customername: string[] = ['Maria', 'Ana Trujillo', 'Antonio Moreno', 'Thomas Hardy', 'Christina Berglund', 'Hanna Moos', 'Frédérique Citeaux', 'Martín Sommer', 'Laurence Lebihan', 'Elizabeth Lincoln',
'Victoria Ashworth', 'Patricio Simpson', 'Francisco Chang', 'Yang Wang', 'Pedro Afonso', 'Elizabeth Brown', 'Sven Ottlieb', 'Janine Labrune', 'Ann Devon', 'Roland Mendel', 'Aria Cruz', 'Diego Roel',
'Martine Rancé', 'Maria Larsson', 'Peter Franken', 'Carine Schmitt', 'Paolo Accorti', 'Lino Rodriguez', 'Eduardo Saavedra', 'José Pedro Freyre', 'André Fonseca', 'Howard Snyder', 'Manuel Pereira',
'Mario Pontes', 'Carlos Hernández', 'Yoshi Latimer', 'Patricia McKenna', 'Helen Bennett', 'Philip Cramer', 'Daniel Tonini', 'Annette Roulet', 'Yoshi Tannamuri', 'John Steel', 'Renate Messner', 'Jaime Yorres',
'Carlos González', 'Felipe Izquierdo', 'Fran Wilson', 'Giovanni Rovelli', 'Catherine Dewey', 'Jean Fresnière', 'Alexander Feuer', 'Simon Crowther', 'Yvonne Moncada', 'Rene Phillips', 'Henriette Pfalzheim',
'Marie Bertrand', 'Guillermo Fernández', 'Georg Pipps', 'Isabel de Castro', 'Bernardo Batista', 'Lúcia Carvalho', 'Horst Kloss', 'Sergio Gutiérrez', 'Paula Wilson', 'Maurizio Moroni', 'Janete Limeira', 'Michael Holz',
'Alejandra Camino', 'Jonas Bergulfsen', 'Jose Pavarotti', 'Hari Kumar', 'Jytte Petersen', 'Dominique Perrier', 'Art Braunschweiger', 'Pascale Cartrain', 'Liz Nixon', 'Liu Wong', 'Karin Josephs', 'Miguel Angel Paolino',
'Anabela Domingues', 'Helvetius Nagy', 'Palle Ibsen', 'Mary Saveley', 'Paul Henriot', 'Rita Müller', 'Pirkko Koskitalo', 'Paula Parente', 'Karl Jablonski', 'Matti Karttunen', 'Zbyszek Piestrzeniewicz'];
let customeraddress: string[] = ['507 - 20th Ave. E.\r\nApt. 2A', '908 W. Capital Way', '722 Moss Bay Blvd.', '4110 Old Redmond Rd.', '14 Garrett Hill', 'Coventry House\r\nMiner Rd.', 'Edgeham Hollow\r\nWinchester Way',
'4726 - 11th Ave. N.E.', '7 Houndstooth Rd.', '59 rue de l\'Abbaye', 'Luisenstr. 48', '908 W. Capital Way', '722 Moss Bay Blvd.', '4110 Old Redmond Rd.', '14 Garrett Hill', 'Coventry House\r\nMiner Rd.', 'Edgeham Hollow\r\nWinchester Way',
'7 Houndstooth Rd.', '2817 Milton Dr.', 'Kirchgasse 6', 'Sierras de Granada 9993', 'Mehrheimerstr. 369', 'Rua da Panificadora, 12', '2817 Milton Dr.', 'Mehrheimerstr. 369'];
let quantityperunit: string[] = ['10 boxes x 20 bags', '24 - 12 oz bottles', '12 - 550 ml bottles', '48 - 6 oz jars', '36 boxes', '12 - 8 oz jars', '12 - 1 lb pkgs.', '12 - 12 oz jars', '18 - 500 g pkgs.', '12 - 200 ml jars',
'1 kg pkg.', '10 - 500 g pkgs.', '2 kg box', '40 - 100 g pkgs.', '24 - 250 ml bottles', '32 - 500 g boxes', '20 - 1 kg tins', '16 kg pkg.', '10 boxes x 12 pieces', '30 gift boxes', '24 pkgs. x 4 pieces', '24 - 500 g pkgs.', '12 - 250 g pkgs.',
'12 - 355 ml cans', '20 - 450 g glasses', '100 - 250 g bags'];
let id: number = 10248;
for (let i: number = 0; i < 20000; i++) {
lazyLoadData.push({
'id': id + i,
'CustomerID': customerid[Math.floor(Math.random() * customerid.length)],
'CustomerName': customername[Math.floor(Math.random() * customername.length)],
'CustomerAddress': customeraddress[Math.floor(Math.random() * customeraddress.length)],
'ProductName': product[Math.floor(Math.random() * product.length)],
'ProductID': i,
'Quantity': quantityperunit[Math.floor(Math.random() * quantityperunit.length)]
})
}
return lazyLoadData;
}
export class Customer {
id?: number;
CustomerName?: string;
ProductID?:number
ProductName?:string
}
Improper handling of observables and subscriptions may lead to memory leaks and unexpected behavior. Ensure to handle subscriptions properly, especially when dealing with long-lived observables.
Export all records in client side
Export all records is especially beneficial when dealing with large datasets that need to be exported for offline analysis or sharing.
By default, when utilizing observables for Grid data binding, the export operation exports only the records on the current page. However, the Syncfusion Angular Grid component allows you to export all records, including those from multiple pages, by configuring the pdfExportProperties and excelExportProperties.
To export all records, including those from multiple pages, configure the pdfExportProperties.dataSource for PDF exporting and excelExportProperties.dataSource for Excel exporting within the toolbarClick event handler. Inside this event, set the dataSource
property of pdfExportProperties
and excelExportProperties
for PDF and Excel exporting to include all records.
Excel Exporting
To export the complete grid data to Excel, utilize the excelExportProperties.dataSource
when initiating the Excel export. Use the following code snippet to export all records within the grid:
this.service.getData(state).subscribe((e: any) => {
let excelExportProperties: ExcelExportProperties = {
dataSource: e.result ? e.result : result
};
(this.grid as GridComponent).excelExport(excelExportProperties);// need to call excelExport method of grid when get the entire data
});
Pdf Exporting
To export the complete grid data to PDF document, utilize the pdfExportProperties.dataSource
when initiating the PDF export. Use the following code snippet to export all records within the grid:
this.service.getData(state).subscribe((e: any) => {
let pdfExportProperties: PdfExportProperties = {
dataSource: e.result ? e.result : result
};
(this.grid as GridComponent).pdfExport(pdfExportProperties); // need to call pdfExport method of grid when get the entire data
});
Further customization on grid export can be accessed in the respective documentation for PDF exporting and Excel exporting
The following code example shows how to export all records in client side for observable without using the async pipe.
import { Component, OnInit, ViewChild } from '@angular/core';
import { GridComponent } from '@syncfusion/ej2-angular-grids';
import {DataStateChangeEventArgs, PdfExportProperties, ExcelExportProperties} from '@syncfusion/ej2-angular-grids';
import { ClickEventArgs } from "@syncfusion/ej2-navigations";
import { DataService } from './order.service';
import { Observable } from 'rxjs';
@Component({
selector: 'app-root',
template: `<ejs-grid #grid [dataSource]='data' (excelExportComplete)="exportComplete()" (pdfExportComplete)="exportComplete()" [allowExcelExport]='true' [allowPdfExport]='true' allowPaging='true' [pageSettings]='pageOptions' [toolbar]="toolbar" (toolbarClick)='toolbarClick($event)' (dataStateChange)= 'dataStateChange($event)'>
<e-columns>
<e-column field='OrderID' headerText='Order ID' width='90' textAlign='Right' isPrimaryKey='true'></e-column>
<e-column field="CustomerID" headerText="Customer Name" width="100"></e-column>
<e-column field='ShipName' headerText="Ship Name" width=110></e-column>
<e-column field='ShipCountry' headerText='Ship Country' width=100></e-column>
<e-column field='Freight' headerText='Freight' format='C2' textAlign='Right' width=100></e-column>
</e-columns>
</ejs-grid>`,
providers: [DataService],
})
export class AppComponent implements OnInit {
public data?: object;
public state?: DataStateChangeEventArgs;
public pageOptions?: object;
public toolbar?: string[];
@ViewChild('grid')
public grid?: GridComponent;
constructor(public service: DataService) {
this.data = service;
}
public dataStateChange(state: DataStateChangeEventArgs): void {
this.service.getData(state).subscribe(response => (this.data = response));
}
exportComplete() {
(this.grid as GridComponent).hideSpinner(); // hide the spinner when export completed
}
toolbarClick(args: ClickEventArgs): void {
let state: any = { action: {}, skip: 0, take: (this.grid as GridComponent).pageSettings.totalRecordsCount };
let result = {};
switch (args.item.text) {
case "PDF Export":
(this.grid as GridComponent).showSpinner(); // show the spinner when send the post to service
state.action.isPdfExport = true;
// fetch the entire data while PDF exporting
this.service.getData(state).subscribe((e: any) => {
let pdfExportProperties: PdfExportProperties = {
dataSource: e.result ? e.result : result
};
(this.grid as GridComponent).pdfExport(pdfExportProperties); // need to call pdfExport method of grid when get the entire data
});
break;
case "Excel Export":
// fetch the entire data while Excel exporting
(this.grid as GridComponent).showSpinner();// show the spinner when send the post to service
state.action.isExcelExport = true;
this.service.getData(state).subscribe((e: any) => {
let excelExportProperties: ExcelExportProperties = {
dataSource: e.result ? e.result : result
};
(this.grid as GridComponent).excelExport(excelExportProperties);// need to call excelExport method of grid when get the entire data
});
break;
}
}
public ngOnInit(): void {
this.pageOptions = { pageSize: 10, pageCount: 4 };
const state = { skip: 0, take: 10 };
this.toolbar = ["ExcelExport", "PdfExport",];
this.service.getData(state).subscribe(response => (this.data = response));
}
}
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import {DataStateChangeEventArgs,DataResult} from '@syncfusion/ej2-angular-grids';
import { Observable } from 'rxjs';
import { Subject } from 'rxjs';
import { map } from 'rxjs';
@Injectable()
export class DataService extends Subject<DataStateChangeEventArgs> {
private BASE_URL = 'https://services.odata.org/V4/Northwind/Northwind.svc/Orders';
constructor(private http: HttpClient) {
super();
}
public execute(state: any): void {
this.getData(state).subscribe(x => super.next(x));
}
public getData(state: DataStateChangeEventArgs): Observable<DataStateChangeEventArgs> {
const pageQuery = `$skip=${state.skip}&$top=${state.take}`;
return this.http
.get(`${this.BASE_URL}?${pageQuery}&$count=true`)
.pipe(map((response: any) => response))
.pipe(map((response: any) => {
return state.dataSource === undefined ? (<DataResult>{
result: response['value'],
count: parseInt(response['@odata.count'], 10),
}) : response['value'];
}))
.pipe(map((data: any) => data));
}
}
Sending additional parameters to the server
The Syncfusion Grid component allows you to include custom parameters in data requests. This feature is particularly useful when you need to provide additional information to the server enhanced processing.
By utilizing the query property of the grid along with the addParams
method of the Query class, you can easily incorporate custom parameters into data requests for every grid action.
To enable custom parameters in data requests for the grid component, follow these steps:
1. Bind the Query Object to the Grid: Assign the initialized query object to the query
property of the Syncfusion Grid component.
2. Initialize the Query Object: Create a new instance of the Query
class and use the addParams
method to add the custom parameters.
3. Handle Data State Changes: If you need to dynamically update the data based on interactions, implement the dataStateChange
event handler to execute the query with the updated state.
4. Execute Data Request: In the service, execute the data request by combining the custom parameters with other query parameters such as paging and sorting.
The following example demonstrates how to send additional parameters to the server using observables.
import { NgModule } from '@angular/core'
import { BrowserModule } from '@angular/platform-browser'
import { GridModule } from '@syncfusion/ej2-angular-grids'
import { HttpClient, HttpClientModule } from '@angular/common/http'
import { PageService, SortService, GroupService, PdfExportService, ExcelExportService, FilterService, EditService, ToolbarService, AggregateService } from '@syncfusion/ej2-angular-grids'
import { Component, OnInit, ViewChild } from '@angular/core';
import { GridComponent } from '@syncfusion/ej2-angular-grids';
import { DataStateChangeEventArgs } from '@syncfusion/ej2-angular-grids';
import { Query } from '@syncfusion/ej2-data';
import { DataService } from './order.service';
import { Observable } from 'rxjs';
@Component({
imports: [
GridModule,HttpClientModule,
],
providers: [PageService,
SortService,
FilterService,
EditService,
ToolbarService,
GroupService,
PdfExportService, ExcelExportService,
AggregateService,HttpClient,DataService ],
standalone: true,
selector: 'app-root',
template: `<ejs-grid #grid [dataSource]='data | async' [query]="query" allowPaging='true' [pageSettings]='pageOptions'
allowSorting= 'true' allowGrouping= 'true' (dataStateChange)= 'dataStateChange($event)'>
<e-columns>
<e-column field='OrderID' headerText='Order ID' width='90' textAlign='Right'></e-column>
<e-column field="CustomerID" headerText="Customer Name" width="100"></e-column>
<e-column field='ShipCountry' headerText='Ship Country' width=100></e-column>
<e-column field='Freight' headerText='Freight' format='C2' textAlign='Right' width=100></e-column>
</e-columns>
</ejs-grid>`
})
export class AppComponent implements OnInit {
public data?: Observable<DataStateChangeEventArgs>;
public state?: DataStateChangeEventArgs;
public pageOptions?: object;
public query?: Query;
@ViewChild('grid')
public grid?: GridComponent;
constructor(public service: DataService) {
this.data = service;
}
public dataStateChange(state: DataStateChangeEventArgs): void {
this.service.execute(state, this.query);
}
public ngOnInit(): void {
this.pageOptions = { pageSize: 10, pageCount: 4 };
const state = { skip: 0, take: 10 };
this.query = new Query().addParams('Syncfusion_Angular_Grid', 'true');
this.service.execute(state, this.query);
}
}
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import {
DataStateChangeEventArgs,
Sorts,
DataResult,
} from '@syncfusion/ej2-angular-grids';
import { Observable } from 'rxjs';
import { Subject } from 'rxjs';
import { map } from 'rxjs';
@Injectable()
export class DataService extends Subject<DataStateChangeEventArgs> {
private BASE_URL = 'https://services.odata.org/V4/Northwind/Northwind.svc/Orders';
constructor(private http: HttpClient) {
super();
}
public execute(state: any, query: any): void {
this.getData(state, query).subscribe(x => super.next(x));
}
public getData(state: DataStateChangeEventArgs, addtionalParam: any): Observable<DataStateChangeEventArgs> {
const pageQuery = `$skip=${state.skip}&$top=${state.take}`;
let sortQuery: string = '';
if ((state.sorted || []).length) {
sortQuery = `&$orderby=` + state.sorted?.map((obj: Sorts) => {
return obj.direction === 'descending' ? `${obj.name} desc` : obj.name;
}).reverse().join(',');
}
const customQuery = `&${addtionalParam.params[0].key}=${addtionalParam.params[0].value}`;
return this.http
.get(`${this.BASE_URL}?${pageQuery}${sortQuery}${customQuery}&$count=true`)
.pipe(map((response: any) => response))
.pipe(map((response: any) => (<DataResult>{
result: response['value'],
count: parseInt(response['@odata.count'], 10),
})))
.pipe((data: any) => data);
}
}
Offline mode
On remote data binding, all grid actions such as paging, sorting, editing, grouping, filtering, etc, will be processed on server-side. To avoid post back for every action, set the grid to load all data on initialization and make the actions process in client-side. To enable this behavior, use the offline
property of DataManager
.
import { Component, OnInit } from '@angular/core';
import { DataManager, ODataAdaptor } from '@syncfusion/ej2-data';
@Component({
selector: 'app-root',
template: `<ejs-grid [dataSource]='data' [allowPaging]='true' [allowGrouping]='true' [allowSorting]='true' [pageSettings]='pageOptions'>
<e-columns>
<e-column field='OrderID' headerText='Order ID' textAlign='Right' width=120></e-column>
<e-column field='CustomerID' headerText='Customer ID' width=150></e-column>
<e-column field='ShipCity' headerText='Ship City' width=150></e-column>
<e-column field='ShipName' headerText='Ship Name' width=150></e-column>
</e-columns>
</ejs-grid>`
})
export class AppComponent implements OnInit {
public data: DataManager;
public pageOptions = { pageSize: 7 };
ngOnInit(): void {
this.data = new DataManager({
url: 'https://js.syncfusion.com/demos/ejServices/Wcf/Northwind.svc/Orders?$top=7',
adaptor: new ODataAdaptor(),
offline: true
});
}
}