Operaciones de datos remotos de Angular Grid
El Ignite UI for Angular Grid admite operaciones de datos remotas como virtualización remota, clasificación remota, filtrado remoto y otros. Esto permite al desarrollador realizar estas tareas en un servidor, recuperar los datos que se producen y mostrarlos en la cuadrícula.
Angular Grid Remote Data Operations Overview Example
De forma predeterminada, Grid utiliza su propia lógica para realizar operaciones de datos. Puede realizar estas tareas de forma remota y alimentar los datos resultantes a Grid aprovechando ciertas entradas y eventos, que están expuestos por Grid.
Remote Virtualization
The IgxGrid supports the scenario in which the data chunks are requested from a remote service, exposing the behavior implemented in the igxForOf directive it uses internally.
To utilize this feature, you need to subscribe to the dataPreLoad output so that you make the appropriate request based on the arguments received, as well as set the public IgxGrid property totalItemCount with the respective information coming from the service.
<igx-grid #grid [data]="remoteData | async" [autoGenerate]="false"
(dataPreLoad)="processData(false)"
(sortingDone)="processData(true)">
<igx-column [field]="'ProductID'" [sortable]="true"></igx-column>
<igx-column [field]="'ProductName'" [sortable]="true"></igx-column>
<igx-column [field]="'UnitPrice'" [dataType]="'number'" [formatter]="formatCurrency" [sortable]="true"></igx-column>
</igx-grid>
public ngAfterViewInit() {
this.grid.isLoading = true;
this._remoteService.getData(this.grid.virtualizationState, this.grid.sortingExpressions[0], true, (data) => {
this.grid.totalItemCount = data['@odata.count'];
this.grid.isLoading = false;
});
}
public processData(reset) {
if (this.prevRequest) {
this.prevRequest.unsubscribe();
}
this._prevRequest = this._remoteService.getData(this.grid.virtualizationState,
this.grid.sortingExpressions[0], reset, () => {
...
this.cdr.detectChanges();
});
}
When requesting data, you need to utilize the IForOfState interface, which provides the startIndex and chunkSize properties.
Note
The first chunkSize will always be 0 and should be determined by you based on the specific application scenario.
Remote Virtualization Demo
Infinite Scroll
A popular design for scenarios requiring fetching data by chunks from an end-point is the so-called infinite scroll. For data grids, it is characterized by continuous increase of the loaded data triggered by the end-user scrolling all the way to the bottom. The next paragraphs explain how you can use the available API to easily achieve infinite scrolling in IgxGrid.
To implement infinite scroll, you have to fetch the data in chunks. The data that is already fetched should be stored locally and you have to determine the length of a chunk and how many chunks there are. You also have to keep a track of the last visible data row index in the grid. In this way, using the startIndex and chunkSize properties, you can determine if the user scrolls up and you have to show them already fetched data or scrolls down and you have to fetch more data from the end-point.
The first thing to do is use the ngAfterViewInit lifecycle hook to fetch the first chunk of the data. Setting the totalItemCount property is important, as it allows the grid to size its scrollbar correctly.
public ngAfterViewInit() {
this._remoteService.loadDataForPage(this.page, this.pageSize, (request) => {
if (request.data) {
this.grid.totalItemCount = this.page * this.pageSize;
this.grid.data = this._remoteService.getCachedData({startIndex: 0, chunkSize: 10});
this.totalItems = request.data['@odata.count'];
this.totalPageCount = Math.ceil(this.totalItems / this.pageSize);
this.grid.isLoading = false;
}
});
}
Additionally, you have to subscribe to the dataPreLoad output, so that you can provide the data needed by the grid when it tries to display a different chunk, rather than the currently loaded one. In the event handler, you have to determine whether to fetch new data or return data, that's already cached locally.
public handlePreLoad() {
const isLastChunk = this.grid.totalItemCount ===
this.grid.virtualizationState.startIndex + this.grid.virtualizationState.chunkSize;
// when last chunk reached load another page of data
if (isLastChunk) {
if (this.totalPageCount === this.page) {
this.grid.data = this._remoteService.getCachedData(this.grid.virtualizationState);
return;
}
this.page++;
this.grid.isLoading = true;
this._remoteService.loadDataForPage(this.page, this.pageSize, (request) => {
if (request.data) {
this.grid.totalItemCount = Math.min(this.page * this.pageSize, this.totalItems);
this.grid.data = this._remoteService.getCachedData(this.grid.virtualizationState);
this.grid.isLoading = false;
}
});
} else {
this.grid.data = this._remoteService.getCachedData(this.grid.virtualizationState);
}
}
Infinite Scroll Demo
Remote Sorting/Filtering
To provide remote sorting and filtering, you need to subscribe to the dataPreLoad, sortingExpressionsChange and filteringExpressionsTreeChange outputs, so that you make the appropriate request based on the arguments received, as well as set the public IgxGrid property totalItemCount with the respective information coming from the service.
We will also take advantage of the rxjs debounceTime function, which emits a value from the source Observable only after a particular time span has passed without another source emission. This way the remote operation will be triggered only when the specified amount of time has passed without the user interrupting it.
const DEBOUNCE_TIME = 300;
...
public ngAfterViewInit() {
...
this.grid.dataPreLoad.pipe(
debounceTime(DEBOUNCE_TIME),
takeUntil(this.destroy$)
).subscribe(() => {
this.processData();
});
this.grid.filteringExpressionsTreeChange.pipe(
debounceTime(DEBOUNCE_TIME),
takeUntil(this.destroy$)
).subscribe(() => {
this.processData(true);
});
this.grid.sortingExpressionsChange.pipe(
debounceTime(DEBOUNCE_TIME),
takeUntil(this.destroy$)
).subscribe(() => {
this.processData();
});
}
When remote sorting and filtering are provided, usually we do not need the built-in sorting and filtering of the grid. We can disable them by setting the sortStrategy and the filterStrategy inputs of the grid to the NoopSortingStrategy and the NoopFilteringStrategy respective instances.
<igx-grid #grid [data]="remoteData | async" [height]="'500px'" [width]="'100%'" [autoGenerate]='false'
[filterStrategy]="noopFilterStrategy"
[sortStrategy]="noopSortStrategy"
[allowFiltering]="true">
...
</igx-grid>
public noopFilterStrategy = NoopFilteringStrategy.instance();
public noopSortStrategy = NoopSortingStrategy.instance();
Note
Cuando se solicitan datos remotos, la operación de filtrado distingue entre mayúsculas y minúsculas.
Remote Sorting/Filtering Demo
Puede ver el resultado del código anterior al comienzo de este artículo en la sección Demostración.
Unique Column Values Strategy
The list items inside the Excel Style Filtering dialog represent the unique values for the respective column. The Grid generates these values based on its data source by default. In case of remote filtering, the grid data does not contain all the data from the server. In order to provide the unique values manually and load them on demand, we can take advantage of the Grid's uniqueColumnValuesStrategy input. This input is actually a method that provides three arguments:
- columna: la instancia de columna respectiva.
- filteringExpressionsTree: el árbol de expresiones de filtrado, que se reduce según la columna respectiva.
- done: devolución de llamada que debe llamarse con los valores de columna recién generados cuando se recuperan del servidor.
El desarrollador puede generar manualmente los valores de columna únicos necesarios en función de la información proporcionada por la columna y los argumentos filteringExpressionsTree y luego invocar la devolución de llamada hecha.
Note
Cuando se proporciona launiqueColumnValuesStrategy entrada, no se utilizará el proceso generador de valores únicos por defecto en el filtrado estilo Excel.
<igx-grid #grid1 [data]="data" [filterMode]="'excelStyleFilter'" [uniqueColumnValuesStrategy]="columnValuesStrategy">
...
</igx-grid>
public columnValuesStrategy = (column: ColumnType,
columnExprTree: IFilteringExpressionsTree,
done: (uniqueValues: any[]) => void) => {
// Get specific column data.
this.remoteValuesService.getColumnData(column, columnExprTree, uniqueValues => done(uniqueValues));
}
Unique Column Values Strategy Demo
Para proporcionar una plantilla de carga personalizada para el filtrado en estilo Excel, podemos usar laigxExcelStyleLoading directiva:
<igx-grid [data]="data" [filterMode]="'excelStyleFilter'" [uniqueColumnValuesStrategy]="columnValuesStrategy">
...
<ng-template igxExcelStyleLoading>
Loading ...
</ng-template>
</igx-grid>
Remote Paging
La función de localización puede funcionar con datos remotos. Para demostrar esto, primero declaremos que nuestro servicio será responsable de la obtención de datos. Necesitaremos el recuento de todos los elementos de datos para poder calcular el recuento de páginas. Esta lógica se agregará a nuestro servicio.
@Injectable()
export class RemotePagingService {
public remoteData: BehaviorSubject<any[]>;
public dataLenght: BehaviorSubject<number> = new BehaviorSubject(0);
public url = 'https://www.igniteui.com/api/products';
constructor(private http: HttpClient) {
this.remoteData = new BehaviorSubject([]) as any;
}
public getData(index?: number, perPage?: number): any {
let qS = '';
if (perPage) {
qS = `?$skip=${index}&$top=${perPage}&$count=true`;
}
this.http
.get(`${this.url + qS}`).pipe(
map((data: any) => data)
).subscribe((data) => this.remoteData.next(data));
}
public getDataLength(): any {
return this.http.get(this.url).pipe(
map((data: any) => data.length)
);
}
}
Después de declarar el servicio, necesitamos crear un componente que será responsable de la construcción de la red y la suscripción de datos.
export class RemotePagingGridSample implements OnInit, AfterViewInit, OnDestroy {
public data: Observable<any[]>;
private _dataLengthSubscriber;
constructor(private remoteService: RemoteService) {}
public ngOnInit() {
this.data = this.remoteService.remoteData.asObservable();
this._dataLengthSubscriber = this.remoteService.getDataLength().subscribe((data) => {
this.totalCount = data;
this.grid1.isLoading = false;
});
}
public ngOnDestroy() {
if (this._dataLengthSubscriber) {
this._dataLengthSubscriber.unsubscribe();
}
}
}
Ahora podemos elegir entre configurar nuestra propia plantilla de paginación personalizada o usar la predeterminada que proporcionen.igx-paginator Veamos primero qué es necesario para configurar la paginación remota usando la plantilla de paginación predeterminada.
Remote paging with default template
Si quieres usar la plantilla de paginación predeterminada necesitas establecer la propiedad deltotalRecords paginador, solo entonces la cuadrícula podrá calcular el número total de página basándose en registros remotos totales. Al realizar una paginación remota, el paginador pasará a la cuadrícula solo los datos de la página actual, por lo que la cuadrícula no intentará paginar la fuente de datos proporcionada. Por eso deberíamos poner la propiedad depagingMode Grid en GridPagingMode.remote. Además, es necesario suscribirse apagingDone eventos de ORperPageChange para obtener los datos de tu servicio remoto; depende del caso de uso qué evento se utilize.
<igx-grid #grid1 [data]="data | async" [isLoading]="isLoading" [pagingMode]="mode">
<igx-column field="ID"></igx-column>
...
<igx-paginator [(page)]="page" [(perPage)]="perPage" [totalRecords]="totalCount"
(pagingDone)="paginate($event.current)">
</igx-paginator>
</igx-grid>
public totalCount = 0;
public data: Observable<any[]>;
public mode = GridPagingMode.remote;
public isLoading = true;
@ViewChild('grid1', { static: true }) public grid1: IgxGridComponent;
private _dataLengthSubscriber;
public set perPage(val: number) {
this._perPage = val;
this.paginate(0);
}
public ngOnInit() {
this.data = this.remoteService.remoteData.asObservable();
this._dataLengthSubscriber = this.remoteService.getDataLength().subscribe((data: any) => {
this.totalCount = data;
this.grid1.isLoading = false;
});
}
public ngAfterViewInit() {
const skip = this.page * this.perPage;
this.remoteService.getData(skip, this.perPage);
}
public paginate(page: number) {
this.page = page;
const skip = this.page * this.perPage;
const top = this.perPage;
this.remoteService.getData(skip, top);
}
Remote Paging with custom igx-paginator-content
Cuando definimos el contenido de un paginador personalizado, necesitamos definir el contenido de manera que los datos solo correspondan a la página solicitada y que pasemos los parámetros correctos de salto y superior al servicio remoto según la página y los elementosperPage seleccionados. Vamos a usar el<igx-paginator> para facilitar nuestra configuración de ejemplo, junto con losIgxPageSizeSelectorComponent queIgxPageNavigationComponent se han introducido,igx-page-size añadiremos el desplegable por página y etiquetar, yigx-page-nav añadiremos los botones y etiquetas de la acción de navegación.
<igx-paginator #paginator
[totalRecords]="totalCount"
[(page)]="page"
[(perPage)]="perPage"
[selectOptions]="selectOptions"
(pageChange)="paginate($event)"
(perPageChange)="perPageChange($event)">
<igx-paginator-content>
<igx-page-size></igx-page-size>
[This is my custom content]
<igx-page-nav></igx-page-nav>
</igx-paginator-content>
</igx-paginator>
@ViewChild('grid1', { static: true }) public grid1: IgxGridComponent;
private _perPage = 15;
private _dataLengthSubscriber: { unsubscribe: () => void; } | undefined;
constructor(private remoteService: RemotePagingService) { }
public ngAfterViewInit() {
this.grid1.isLoading = true;
this.remoteService.getData(0, this.perPage);
}
public paginate(page: number) {
this.page = page;
const skip = this.page * this.perPage;
const top = this.perPage;
this.remoteService.getData(skip, top);
}
public perPageChange(perPage: number) {
const skip = this.page * perPage;
const top = perPage;
this.remoteService.getData(skip, top);
}
Note
Para que la paginación remota esté configurada correctamente,GridPagingMode.Remote debe establecerse:
<igx-grid #grid1 [data]="data | async" width="100%" height="580px" [pagingMode]="mode"></igx-grid>
...
public mode = GridPagingMode.Remote;
El último paso será declarar el contenido del paginador según sus requisitos.
<igx-paginator-content>
<igx-page-size></igx-page-size>
[This is my custom content]
<igx-page-nav></igx-page-nav>
</igx-paginator-content>
Después de todos los cambios anteriores, se logrará el siguiente resultado.
Remote Paging with custom paginator
En algunos casos, es posible que desee definir su propio comportamiento de paginación y aquí es cuando podemos aprovechar la plantilla de paginación y agregar nuestra lógica personalizada junto con ella. Vamos a ampliar el ejemplo de paginación remota para demostrar esto:
Below you will find the methods that we've defined in order to implement our own next and previous page actions.
@ViewChild('grid1', { static: true }) public grid1: IgxGridComponent;
public ngAfterViewInit() {
this.grid1.isLoading = true;
this.remoteService.getData(0, this.perPage);
}
public nextPage() {
this.firstPage = false;
this.page++;
const skip = this.page * this.perPage;
const top = this.perPage;
this.remoteService.getData(skip, top);
if (this.page + 1 >= this.totalPages) {
this.lastPage = true;
}
this.setNumberOfPagingItems(this.page, this.totalPages);
}
public previousPage() {
this.lastPage = false;
this.page--;
const skip = this.page * this.perPage;
const top = this.perPage;
this.remoteService.getData(skip, top);
if (this.page <= 0) {
this.firstPage = true;
}
this.setNumberOfPagingItems(this.page, this.totalPages);
}
public paginate(page: number, recalculate = false) {
this.page = page;
const skip = this.page * this.perPage;
const top = this.perPage;
if (recalculate) {
this.totalPages = Math.ceil(this.totalCount / this.perPage);
}
this.setNumberOfPagingItems(this.page, this.totalPages);
this.remoteService.getData(skip, top);
this.buttonDeselection(this.page, this.totalPages);
}
Remote Paging with Batch editing
Con los ejemplos hasta ahora aclaramos cómo configurar el IgxGrid con datos remotos. Ahora, centrémonos en habilitar la edición por lotes para la cuadrícula siguiendo el tema/guía Edición por lotes.
Antes de continuar con el ejemplo, es bueno aclarar el caso de uso actual. Cuando se realiza la paginación en el servidor, la cuadrícula contiene los datos solo para la página actual y si agregamos nuevas filas, las filas recién agregadas (con edición por lotes) se concatenarán con los datos actuales que contiene la cuadrícula. Por lo tanto, si el servidor no devuelve datos para una página determinada, la fuente de datos de la cuadrícula estará compuesta únicamente por las filas recién agregadas, que la cuadrícula paginará según la configuración de paginación definida (página, por página).
public ngOnInit() {
this._dataLengthSubscriber = this.remoteService.getDataLength().subscribe((data) => {
this.totalCount = data;
this._recordOnServer = data;
this._totalPagesOnServer = Math.floor(this.totalCount / this.perPage);
this.grid1.isLoading = false;
});
}
In order to handle this use case properly, we need to implement some custom logic.
First, we have to know the total number of records that are on the server. Given that, we calculate the total number of data pages on the server (see this._totalPagesOnServer) and based on its value, we will implement the custom pagination logic.
public paginate(page: number) {
this.grid1.endEdit(true);
if (page > this._totalPagesOnServer) {
if (this.page !== this._totalPagesOnServer) {
const skipEl = this._totalPagesOnServer * this.perPage;
this.remoteService.getData(skipEl, this.perPage);
}
this.page = page - this._totalPagesOnServer;
this.page = page;
return;
} else {
this.page = 0;
}
this.page = page;
const skip = this.page * this.perPage;
this.remoteService.getData(skip, this.perPage);
}
As you can see in the paginate method, custom pagination logic is performed, based on the _totalPagesOnServer value.
Paginación remota con demostración de edición por lotes
Known Issues and Limitations
- Cuando la cuadrícula no
primaryKeytiene un sistema fijo y los escenarios de datos remotos están habilitados (al paginar, ordenar, filtrar o desplazar las solicitudes de disparo a un servidor remoto para recuperar los datos que se mostrarán en la cuadrícula), una fila perderá el siguiente estado tras completar una solicitud de datos:- Selección de fila
- Fila Expandir/contraer
- Edición de filas
- Fijación de filas
- En escenarios de datos remotos, cuando la cuadrícula tiene un
primaryKeyargumento set,rowSelectionChanging.oldSelectionevento no contendrá el objeto de datos completo de fila para las filas que actualmente están fuera de la vista de datos. En este caso,rowSelectionChanging.oldSelectionobject contendrá solo una propiedad, que es elprimaryKeycampo. Para el resto de las filas, actualmente en la vista de datos,rowSelectionChanging.oldSelectioncontendrán los datos completos de la fila.
API References
Additional Resources
- Paginación
- Descripción general de la cuadrícula
- Virtualización y rendimiento
- Filtración
- Clasificación
- resúmenes
- Columna en movimiento
- Fijación de columnas
- Cambio de tamaño de columna
- Selección