|
| 1 | +### Intro |
| 2 | +The lib currently supports OData and GraphQL with built-in Services, if you want to use and create a different and Custom Backend Service, then follow the steps below. |
| 3 | + |
| 4 | +### Instructions |
| 5 | +To create your own Custom Backend Service, I suggest you take the code of the [GraphqlService](https://github.com/ghiscoding/slickgrid-universal/blob/master/packages/graphql/src/services/graphql.service.ts) and then rewrite the internal of each methods. The thing to remember is that you have to implement the `BackendService` as defined in the GraphqlService (`export class GraphqlService implements BackendService`). |
| 6 | + |
| 7 | +You typically want to implement your service following these TypeScript interfaces |
| 8 | +- [backendService.interface.ts](https://github.com/ghiscoding/slickgrid-universal/blob/master/packages/common/src/interfaces/backendService.interface.ts) |
| 9 | +- [backendServiceApi.interface.ts](https://github.com/ghiscoding/slickgrid-universal/blob/master/packages/common/src/interfaces/backendServiceApi.interface.ts) |
| 10 | +- [backendServiceOption.interface.ts](https://github.com/ghiscoding/slickgrid-universal/blob/master/packages/common/src/interfaces/backendServiceOption.interface.ts) |
| 11 | + |
| 12 | +At the end of it, you'll have a Custom Backend Service that will be instantiated just like the GraphQL or OData that I've created, it should look similar to this (also note, try to avoid passing anything in the `constructor` of your Service to keep it usable by everyone) |
| 13 | +```ts |
| 14 | +class MyComponent { |
| 15 | + gridInit() { |
| 16 | + this.gridOptions = { |
| 17 | + backendServiceApi: { |
| 18 | + service: new YourCustomBackendService(), |
| 19 | + options: { |
| 20 | + // custom service options that extends "backendServiceOption" interface |
| 21 | + }, |
| 22 | + preProcess: () => !this.isDataLoaded ? this.displaySpinner(true) : '', |
| 23 | + process: (query, options) => this.getCountries(query, options), |
| 24 | + postProcess: (result) => { |
| 25 | + this.displaySpinner(false); |
| 26 | + this.isDataLoaded = true; |
| 27 | + } |
| 28 | + } as YourCustomBackendServiceApi |
| 29 | + }; |
| 30 | + } |
| 31 | + |
| 32 | + // Note: The second parameter contains the AbortSignal for an optional request cancellation |
| 33 | + getCountries(query: string, options?: { signal?: AbortSignal }) { |
| 34 | + // You can pass the signal to fetch or other HTTP libraries |
| 35 | + return fetch(`/api/countries?${query}`, { |
| 36 | + signal: options?.signal // Pass the signal to enable automatic request cancellation |
| 37 | + }); |
| 38 | + } |
| 39 | +} |
| 40 | +``` |
| 41 | + |
| 42 | +If you need to reference your Service for other purposes then you better instantiated in a separate variable and then just pass it to the `service` property of the `backendServiceApi`. |
| 43 | +```ts |
| 44 | +class MyComponent { |
| 45 | + myCustomService = new YourCustomBackendService(); |
| 46 | + |
| 47 | + gridInit { |
| 48 | + this.gridOptions = { |
| 49 | + backendServiceApi: { |
| 50 | + service: this.myCustomService, |
| 51 | + // ... |
| 52 | + } as YourCustomBackendServiceApi |
| 53 | + }; |
| 54 | + } |
| 55 | +} |
| 56 | +``` |
| 57 | + |
| 58 | +If your Service is for a well known DB or API framework, then it might be possible to add your Service to the lib itself, however it should be added to the new monorepo lib [Slickgrid-Universal](https://github.com/ghiscoding/slickgrid-universal) in the list of [slickgrid-universal/packages](https://github.com/ghiscoding/slickgrid-universal/tree/master/packages). Since that is a monorepo lib, users will have the ability to use and download only the package they need. |
| 59 | + |
| 60 | +## Request Cancellation with AbortSignal |
| 61 | + |
| 62 | +SlickGrid automatically supports request cancellation for Promise-based backend services using the standard `AbortSignal` API. This feature prevents stale results from being processed when users rapidly trigger new filter or sort operations. |
| 63 | + |
| 64 | +### Why Cancellation is Important |
| 65 | + |
| 66 | +Without cancellation, if a user is typing a filter (e.g., "Jo", then "Joe", then "John"), each letter triggers a backend request. If the requests complete out of order, the result from the "Jo" query might arrive AFTER the "John" query result, overwriting the correct data with outdated results. |
| 67 | + |
| 68 | +### How It Works |
| 69 | + |
| 70 | +1. **Automatic**: SlickGrid manages the AbortController internally - you don't need to create one |
| 71 | +2. **Transparent**: The `AbortSignal` is automatically passed to your `process` method |
| 72 | +3. **Smart**: Only the most recent request result is processed; cancelled requests are silently ignored |
| 73 | + |
| 74 | +### Implementation |
| 75 | + |
| 76 | +All you need to do is accept the optional second parameter in your `process` method and, if you want to support automatic request cancellation, pass the `signal` to your fetch/HTTP call: |
| 77 | + |
| 78 | +```ts |
| 79 | +process: (query: string, options?: { signal?: AbortSignal }) => Promise<any> |
| 80 | +``` |
| 81 | + |
| 82 | +#### Example with Fetch API |
| 83 | +```ts |
| 84 | +process: (query: string, options?: { signal?: AbortSignal }) => { |
| 85 | + return fetch('/api/data?q=' + query, { |
| 86 | + signal: options?.signal // This automatically aborts when a new request comes in |
| 87 | + }).then(r => r.json()); |
| 88 | +} |
| 89 | +``` |
| 90 | + |
| 91 | +#### Example with Axios |
| 92 | +```ts |
| 93 | +process: (query: string, options?: { signal?: AbortSignal }) => { |
| 94 | + return axios.get('/api/data?q=' + query, { |
| 95 | + signal: options?.signal |
| 96 | + }); |
| 97 | +} |
| 98 | +``` |
| 99 | + |
| 100 | +#### Example with Error Handling |
| 101 | + |
| 102 | +If you want to handle cancellation errors explicitly: |
| 103 | + |
| 104 | +```ts |
| 105 | +process: (query: string, options?: { signal?: AbortSignal }) => { |
| 106 | + return fetch('/api/data?q=' + query, { |
| 107 | + signal: options?.signal |
| 108 | + }).then(r => { |
| 109 | + if (!r.ok) throw new Error(`HTTP ${r.status}`); |
| 110 | + return r.json(); |
| 111 | + }).catch(error => { |
| 112 | + // Warning: Aborted requests throw DOMException with name 'AbortError' |
| 113 | + // SlickGrid handles these automatically, so you can ignore them |
| 114 | + if (error.name === 'AbortError') { |
| 115 | + console.log('Request cancelled due to newer filter/sort'); |
| 116 | + } else { |
| 117 | + throw error; // Re-throw real errors |
| 118 | + } |
| 119 | + }); |
| 120 | +} |
| 121 | +``` |
| 122 | + |
| 123 | +### Important Notes |
| 124 | + |
| 125 | +- The `AbortSignal` parameter is **optional** - existing implementations without it will continue to work |
| 126 | +- **AbortError handling**: When a request is cancelled, it throws an error with `name: 'AbortError'`. SlickGrid handles these automatically |
| 127 | +- **Real errors**: Non-AbortError exceptions are still passed to your error callbacks |
| 128 | +- **Backwards compatible**: Older implementations that don't use the signal parameter will work as before |
0 commit comments