How to properly perform CRUD operations in EJ2 Grid using Angular HTTP service and Observables?

The Grid documentation (for Angular) is most of the time using local data or DataManagers that are internally handling calls to a remote API.
How can I hook it up to Angular services that are using Http Client, RxJs and returning observables?

8 Replies

DR Dhivya Rajendran Syncfusion Team April 20, 2018 11:55 AM UTC

Hi Emerson, 

Thanks for your update, 

We have an option to perform CRUD operation in EJ2 Grid using observables, to achieve your requirement you can use dataSourceChanged and dataStateChange event in Grid. dataStateChange event will trigger whenever the state of the Grid changes and dataSourceChanged event will trigger when you perform CRUD operation in Grid and you have to call the endEdit method from the dataSourceChanged event argument after observable has been resolved. Kindly refer the below code example for more information. 


@Component({ 
    selector: 'app-container', 
    template: `<ejs-grid [dataSource]='data | async' [editSettings]='editSettings' (dataStateChange)= 'dataStateChange($event)' [toolbar]='toolbar' (dataSourceChanged)= 'dataSourceChanged($event)'
          <e-columns> 
            <e-column field='OrderID' headerText='Order ID' width='120' textAlign='Right' isPrimaryKey='true' [validationRules]='orderidrules'></e-column> 
            . . . . . . 
        </e-columns> 
    </ejs-grid>` 
}) 
export class AppComponent { 
    public data: Observable<DataSourceChangedEventArgs>; 
    constructor(private service: OrdersService) { 
        this.data = service; 
    } 

    public dataStateChange(state: DataStateChangeEventArgs): void { 
        this.service.execute(state);                     // for binding the data. 
    } 

    public dataSourceChanged(state: DataSourceChangedEventArgs): void { 
        if (state.action == 'add') { 
            this.service.add(state).subscribe(() => state.endEdit());  // you can perform add operation 
        }  
        else if (state.action === 'edit') { 
         this.service.edit(state).subscribe(() => state.endEdit());    // you can perform edit operation 
        }  
        else if (state.requestType === 'delete') { 
         this.service.delete(state).subscribe(() => state.endEdit()); // you can perform delete operation 
        } 
    } 

    public ngOnInit(): void { 
        this.editSettings = { allowEditing: true, allowAdding: true, allowDeleting: true }; 
        this.service.execute(state); 
    } 


In the below demo sample we have performed paging, sorting and grouping with observables. Kindly refer the below demo link for more information. 



Please get back to us if you need further assistance. 

Regards, 
R.Dhivya   



EB EBrito April 23, 2018 05:55 PM UTC

A couple of things:

Are these events meant to work with Async Pipe only or a particular data adaptor?
I can't manage to trigger them.

I got a demo from your documentation and basically hooked into the 2 events so I could analyze the behavior and get a better understanding of it. The demo:
 
The modified app.component can be found here, the events are never triggered:

Other than that, you mentioned:

[...] dataSourceChanged event will trigger when you perform CRUD operation in Grid and you have to call the endEdit method from the dataSourceChanged event argument [...]

You continued with a link to the documentation.
When I open the link, the method you suggested is not documented. In fact, there are no methods declared (as per documentation) in the DataSourceChangedEventArgs interface, only properties.


EB EBrito April 23, 2018 07:01 PM UTC

The attached file has an attempt to replicate the async pipe sample code.
As far as I can tell (unless I'm missing something), the only different between this code and the one in the example is that this one is using an array of local data instead of the http client, however both are returning an observable.

In my code the 'dataStateChange' event is never triggered.



Attachment: asyncpipetest_1a9c67c5.zip


DR Dhivya Rajendran Syncfusion Team April 25, 2018 06:14 PM UTC

Hi Emerson, 
  
Thanks for your patience. 
  
As per your requirement, we have created a sample and perform CRUD operation using Observable in Grid.  
  
In the above sample we have used the InMemoryDbService under “in-mem.ts” file to create a data source and in the “crud.service.ts” file we will be having the WebAPI methods to perform CRUD operations by using HTTPClient. Please refer the below code  example and sample for more information. 
  
[component.ts] 
@Component({ 
    selector: 'app-container', 
    template: ` 
    <ejs-grid [dataSource]='data | async' [editSettings]='editSettings' [toolbar]='toolbar' (dataSourceChanged)='dataSourceChanged($event)' (dataStateChange)= 'dataStateChange($event)'> 
        <e-columns> 
        <e-column field='id' headerText='Customer ID' isPrimaryKey='true'></e-column> 
            <e-column field= "name" headerText="Customer Name" width="150"></e-column>             
        </e-columns> 
    </ejs-grid>` 
}) 
export class AppComponent { 
  
    public dataStateChange(state: DataStateChangeEventArgs): void { 
        this.crudService.execute(state); 
    } 
  
    public dataSourceChanged(state: DataSourceChangedEventArgs): void { 
        if (state.action === 'add') { 
            this.crudService.addRecord(state).subscribe(() => state.endEdit()); 
        } else if (state.action === 'edit') { 
            this.crudService.updateRecord(state).subscribe(() => state.endEdit()); 
        } else if (state.requestType === 'delete') { 
            this.crudService.deleteRecord(state).subscribe(() => state.endEdit()); 
        } 
    } 
  
    public ngOnInit(): void { 
        . . . . . . .  
        let state = { skip: 0, take: 12 }; 
        this.crudService.execute(state); 
    } 
} 
  
[crud.service.ts] 
  
  constructor( 
    private http: HttpClient) { 
    super(); 
  } 
   
  public execute(state: any): void { 
    this.getAllData().subscribe(x => super.next(x as DataStateChangeEventArgs)); 
  } 
  
  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> { 
    … 
    return this.http.delete<Customer>(url, httpOptions); 
  } 
  
  /** PUT: update the record on the server */ 
  updateRecord(state: DataSourceChangedEventArgs): Observable<any> { 
    return this.http.put(this.customersUrl, state.data, httpOptions); 
  } 
  
  
  
Help documentation :  
  
  
  
We have checked your provided sample, you can use BehaviourSubject instead of Subject in your service might resolve your problem. 
  
[DataService] 
export class DataService extends BehaviorSubject<DataStateChangeEventArgs> { 
  
  constructor() { 
    super(null); 
  } 
  
  public execute(state: any): void { 
    this.getData(state).subscribe(x => { 
      super.next(x) 
    }); 
  } 
  protected getData(state: DataStateChangeEventArgs): Observable<DataStateChangeEventArgs> { 
    return Observable.of(<any>{ result: data, count: 2 }); 
  } 
} 
  
Please get back to us if you need further assistance. 
  
Regards, 
R.Dhivya 



RV Rafael Valle replied to Dhivya Rajendran June 18, 2021 09:05 AM UTC

Hi Emerson, 
  
Thanks for your patience. 
  
As per your requirement, we have created a sample and perform CRUD operation using Observable in Grid.  
  
In the above sample we have used the InMemoryDbService under “in-mem.ts” file to create a data source and in the “crud.service.ts” file we will be having the WebAPI methods to perform CRUD operations by using HTTPClient. Please refer the below code  example and sample for more information. 
  
[component.ts] 
@Component({ 
    selector: 'app-container', 
    template: ` 
    (dataSourceChanged)='dataSourceChanged($event)' (dataStateChange)= 'dataStateChange($event)'
         
         
                         
         
    ` 
}) 
export class AppComponent { 
  
    public dataStateChange(state: DataStateChangeEventArgs): void { 
        this.crudService.execute(state); 
    } 
  
    public dataSourceChanged(state: DataSourceChangedEventArgs): void { 
        if (state.action === 'add') { 
            this.crudService.addRecord(state).subscribe(() => state.endEdit()); 
        } else if (state.action === 'edit') { 
            this.crudService.updateRecord(state).subscribe(() => state.endEdit()); 
        } else if (state.requestType === 'delete') { 
            this.crudService.deleteRecord(state).subscribe(() => state.endEdit()); 
        } 
    } 
  
    public ngOnInit(): void { 
        . . . . . . .  
        let state = { skip: 0, take: 12 }; 
        this.crudService.execute(state); 
    } 
} 
  
[crud.service.ts] 
  
  constructor( 
    private http: HttpClient) { 
    super(); 
  } 
   
  public execute(state: any): void { 
    this.getAllData().subscribe(x => super.next(x as DataStateChangeEventArgs)); 
  } 
  
  addRecord(state: DataSourceChangedEventArgs): Observable { 
    return this.http.post(this.customersUrl, state.data, httpOptions); 
  } 
  
  /** DELETE: delete the record from the server */ 
  deleteRecord(state: any): Observable { 
    … 
    return this.http.delete(url, httpOptions); 
  } 
  
  /** PUT: update the record on the server */ 
  updateRecord(state: DataSourceChangedEventArgs): Observable { 
    return this.http.put(this.customersUrl, state.data, httpOptions); 
  } 
  
  
  
Help documentation :  
  
  
  
We have checked your provided sample, you can use BehaviourSubject instead of Subject in your service might resolve your problem. 
  
[DataService] 
export class DataService extends BehaviorSubject { 
  
  constructor() { 
    super(null); 
  } 
  
  public execute(state: any): void { 
    this.getData(state).subscribe(x => { 
      super.next(x) 
    }); 
  } 
  protected getData(state: DataStateChangeEventArgs): Observable { 
    return Observable.of({ result: data, count: 2 }); 
  } 
} 
  
Please get back to us if you need further assistance. 
  
Regards, 
R.Dhivya 


Hi Dhivya,

I am trying to make this example works in Angular 11.2.13 (I have copied the full example), but the error "Cannot invoke an object which is possibly 'undefined'.ts(2722)" avoids that the project compile correctly. This error occurs when I try to call endEdit() with the state variable.

I have added the error that occurs in a screenshoot.

I haved tried the following options:
1- Set strictPropertyInitialization to false in tsconfig.json
2- Use the ! operator when declaring the state property to avoid Typescript checking if the property is not undefined
3- Check if state is not undefined in the dataSourceChanged() method before subscribing to the observable.
4- Check if state is not undefined in the dataSourceChanged() method when subscribing to the observable inside the arrow function.

I would thank if anyone in the Syncfusion support department can give me any guidance.

Thanks in advance.

Angular version: 11.2.13
Typescript version: 4.2.3

Attachment: sample_d1fb04da.zip


BS Balaji Sekar Syncfusion Team June 21, 2021 02:47 PM UTC

Hi Rafael, 
  
We checked the attached sample and found that you have defined strict mode as true so only you can replicate the “Cannot invoke an object which is possibly undefined” issue and we suggest you to set the strict mode as false in the tsconfig file it will overcome mentioned problem. 
  
Please refer the below configuration for more information. 
[tsconfig.json] 
{ 
  "compileOnSave"false, 
  "compilerOptions": { 
    "baseUrl""./", 
    "outDir""./dist/out-tsc", 
    "forceConsistentCasingInFileNames"true, 
    "strict"false, 
.    .     .    . 
} 
} 
  
  
In provided sample, you have defined only “<ejs-grid></ejs-grid>” and we suspect that you need the custom binding with CRUD operation since we have modified a sample based on your requirement.  
  
Please refer the below code example and sample for more information. 
  
[app.component.html] 
<ejs-grid [dataSource]='data | async' [editSettings]='editSettings' [toolbar]='toolbar' (dataSourceChanged)='dataSourceChanged($event)' (dataStateChange)'dataStateChange($event)'> 
    <e-columns> 
    <e-column field='id' headerText='Customer ID' width='120' textAlign='Right' isPrimaryKey='true'></e-column> 
        <e-column field"name" headerText="Customer Name" width="150"></e-column>             
    </e-columns> 
</ejs-grid> 
  
[app.component.ts] 
  constructor(private crudService: CrudService) { 
    this.data = crudService; 
  } 
  
  getData(): void { 
    this.crudService.getAllData() 
      .subscribe((customers) => { 
        alert(JSON.stringify(customers)) 
      }); 
  } 
  
  public dataStateChange(state: DataStateChangeEventArgs): void { 
    this.crudService.execute(state); 
  } 
  
  public dataSourceChanged(state: DataSourceChangedEventArgs): void { 
    if (state.action === 'add') { 
        this.crudService.addRecord(state).subscribe(() => state.endEdit()); 
    } else if (state.action === 'edit') { 
        this.crudService.updateRecord(state).subscribe(() => state.endEdit()); 
    } else if (state.requestType === 'delete') { 
        this.crudService.deleteRecord(state).subscribe(() => state.endEdit()); 
    } 
  } 
  
  public ngOnInit(): void { 
    this.editSettings = { allowEditing: true, allowAdding: true, allowDeleting: true }; 
    this.toolbar = ['Add''Edit''Delete''Update''Cancel'];         
    let state = { skip: 0, take: 12 }; 
    this.crudService.execute(state); 
  } 
  
  
Please get back to us, if you need further assistance. 
  
Regards, 
Balaji Sekar. 
  



MJ Mike Johnson replied to Balaji Sekar July 30, 2021 12:13 PM UTC

Would it possible for someone to provide an example code on how to handle paging, sorting and grouping on the server side. net.Core I'm using batchmode and have the http services wired up client side to my controllers, just not sure how to handle CRUD operations, what do I return. I've tired returning json object with result and count. but grid does not show any data. Thanks



JC Joseph Christ Nithin Issack Syncfusion Team August 6, 2021 11:08 AM UTC

Hi Mike, 

Sorry for the inconvenience caused. 

Based on your query you need to handle paging, sorting and grouping on the server side. For this requirement we suggest you to use custom binding concept of the Grid achieve this requirement. By default in custom binding, you can bind data from an API call by providing your own custom queries(as required by your API) and handle all the Grid’s actions like paging, sorting, grouping, etc. with it. The Grid’s custom binding approach is explained below, 

For using custom binding, you need to bind the response data(Grid data) returned from your API as an object of result(JSON data source) and count(Total data count) properties and set it to the Grid’s dataSource property. On setting the data source in this format, the ‘dataStateChange’ event will be triggered with corresponding query for every Grid action performed like Paging, Sorting and Filtering..,. So using this you can send the queries in your required format to your API, process and return the result and then assign the returned result to the Grid’s dataSource property as an object of result and count properties 
 

Please refer the below code example that explains about performing grid actions like paging, sorting, grouping etc. 


app.component.ts 
  
export class AppComponent { 
    public data: Observable<DataStateChangeEventArgs>; 
    public pageOptions: Object; 
    public count = 0; 
    public printData: Object[]; 
    @ViewChild('grid', {static: true}) 
    public grid: GridComponent; 
    public state: DataStateChangeEventArgs; 
    public toolbarOptions: ToolbarItems[]; 
    constructor( public service: OrdersService) { 
        this.data = service; 
    } 
 
    public dataStateChange(state: DataStateChangeEventArgs): void { 
       this.service.execute(state); 
    } 
 
    public ngOnInit(): void { 
        this.pageOptions = { pageSize: 20 }; 
        let state = { skip: 0, take: 20 }; 
        this.service.execute(state); 
    } 
} 



Initial rendering and paging 
 
 For performing paging the grid will require the skip and take values which is used for defining the number of records to be returned from the server. 

For initial rendering: 
   
   Here for initial rendering the skip and take values should be set manually and sent to the server. 

   

For Paging: 
 
  Here when the paging is done the `dataStateChange` event in triggered. In this event’s arguments, the details required for getting the desired records from the server is passed in the argument to the service. 

 
 
Sorting: 
 
  For performing the sorting the grid requires the details of columns to be sorted and the direction of the sort along with the paging details. 

 

Gouping: 

For performing grouping the grid requires the details of the grouping column along with the details required for sorting and paging details. 

 

Please refer the sample below where we have performed the grid actions like paging, sorting and grouping in the server side. 


In your update you have also mentioned that you want to perform the CRUD operation of the grid in the server side using Batch edit mode. For each CRUD operations performed the grid the ‘dataSourceChanged’ event will be triggered. Using this you can get the all changes in dataSourceChanged event’s argument when args.requestType as batchsave. Using this changes you can call your service and perform your action as per your requirement. 

Please refer the below code example. 


app.component.ts 

export class NormalEditComponent implements OnInit { 
     public data: Observable<DataStateChangeEventArgs>; 
    public pageOptions: Object; 
    public editSettings: Object; 
    public toolbar: string[]; 
    public state: DataStateChangeEventArgs; 
    customers: Customer[]; 
    @ViewChild('grid') 
    public grid: GridComponent; 
    constructor(private crudService: CrudService) { 
        this.data = crudService; 
    } 
 
    getData(): void { 
        this.crudService.getAllData() 
            .subscribe((customers) => { 
                alert(JSON.stringify(customers)); 
            }); 
    } 
 
    public dataStateChange(state: DataStateChangeEventArgs): void { 
        this.crudService.execute(state); 
    } 
 
    public dataSourceChanged(state: DataSourceChangedEventArgs): void { 
        if (state.action === 'add') { 
            this.crudService.addRecord(state).subscribe(() => { this.grid.notify('recordAdded', state); state.endEdit() }); 
        } else if (state.action === 'edit') { 
            this.crudService.updateRecord(state).subscribe(() => state.endEdit()); 
        } else if (state.requestType === 'delete') { 
            this.crudService.deleteRecord(state).subscribe(() => { 
                state.endEdit(); 
            }); 
        } else if (state.requestType === 'batchsave') { 
            if ((state.changes as any).changedRecords.length > 0) { 
                this.crudService.batchChangedRecord(state).subscribe(() => { 
                    state.endEdit(); 
                }); 
            } 
            if ((state.changes as any).addedRecords.length > 0) { 
                this.crudService.batchAddedRecord(state).subscribe(() => state.endEdit()); 
            } 
            if ((state.changes as any).deletedRecords.length > 0) { 
                this.crudService.batchDeletedRecord(state).subscribe(() => state.endEdit()); 
            } 
        } 
    } 
 
    public ngOnInit(): void { 
        this.editSettings = { allowEditing: true, allowAdding: true, allowDeleting: true, mode: 'Batch' }; 
        this.toolbar = ['Add', 'Edit', 'Delete', 'Update', 'Cancel']; 
        const state: any = { skip: 0, take: 12 }; 
        this.crudService.execute(state); 
    } 
} 


The below screenshot shows the arguments that are passed to the server when CRUD operation is performed by using the batch editing mode. Here we have added a records and deleted one record from the dataSource and then clicked update button in the grid’s toolbar. Now the dataSourceChanged event is triggered and we can see the changes done in the dataSourceChanged event’s arguments that are highlighted in the below screenshot. 

   


We have also attached a sample to perform CRUD operations using batch editing. 


 
Please find the attached sample and refer the help documentations and revert if you need any further assistance. 

Regards, 
Joseph I. 


Loader.
Up arrow icon