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.

    Ejemplo de descripción general de operaciones de datos remotos de Angular Grid

    EXAMPLE
    TS
    HTML
    SCSS

    ¿Te gusta esta muestra? Obtenga acceso a nuestro kit de herramientas de Ignite UI for Angular completo y comience a crear sus propias aplicaciones en minutos. Descárgalo gratis.

    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.

    Virtualización remota

    IgxGrid admite el escenario en el que los fragmentos de datos se solicitan desde un servicio remoto, exponiendo el comportamiento implementado en la directiva igxForOf que utiliza internamente.

    Para utilizar esta función, debe suscribirse a la salida dataPreLoad para realizar la solicitud adecuada en función de los argumentos recibidos, así como configurar la propiedad pública IgxGrid​ ​totalItemCount con la información respectiva proveniente del servicio.

    <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>
    html
    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();
        });
    }
    typescript

    Al solicitar datos, debe utilizar la interfaz IForOfState, que proporciona las propiedades startIndex y chunkSize.

    El primer chunkSize siempre será 0 y usted debe determinarlo en función del escenario de aplicación específico.

    Demostración de virtualización remota

    EXAMPLE
    TS
    HTML
    SCSS

    Scroll infinito

    Un diseño popular para escenarios que requieren recuperar datos por fragmentos desde un punto final es el llamado desplazamiento infinito. Para las cuadrículas de datos, se caracteriza por un aumento continuo de los datos cargados provocado por el desplazamiento del usuario final hasta el final. Los siguientes párrafos explican cómo puede utilizar la API disponible para lograr fácilmente un desplazamiento infinito en IgxGrid.

    Para implementar el desplazamiento infinito, debes recuperar los datos en fragmentos. Los datos que ya se han obtenido deben almacenarse localmente y hay que determinar la longitud de un fragmento y cuántos fragmentos hay. También debe realizar un seguimiento del último índice de fila de datos visible en la cuadrícula. De esta manera, utilizando las propiedades startIndex y chunkSize, puede determinar si el usuario se desplaza hacia arriba y debe mostrarle los datos ya obtenidos o si se desplaza hacia abajo y debe obtener más datos desde el punto final.

    Lo primero que debe hacer es utilizar el enlace del ciclo de vida ngAfterViewInit para recuperar el primer fragmento de datos. Configurar la propiedad totalItemCount es importante, ya que permite que la cuadrícula ajuste el tamaño de su barra de desplazamiento correctamente.

    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;
            }
        });
    }
    typescript

    Además, debe suscribirse a la salida dataPreLoad, para poder proporcionar los datos que necesita la cuadrícula cuando intenta mostrar un fragmento diferente, en lugar del cargado actualmente. En el controlador de eventos, debe determinar si desea recuperar datos nuevos o devolver datos que ya están almacenados en caché localmente.

    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);
        }
    }
    typescript

    Demostración de desplazamiento infinito

    EXAMPLE
    TS
    HTML
    SCSS

    Clasificación/filtrado remoto

    Para proporcionar clasificación y filtrado remotos, debe suscribirse a las salidas dataPreLoad, sortingExpressionsChange y filteringExpressionsTreeChange, de modo que pueda realizar la solicitud adecuada en función de los argumentos recibidos, así como configurar la propiedad pública IgxGrid​ ​totalItemCount con la información respectiva proveniente del servicio. .

    También aprovecharemos la función rxjs ​ ​debounceTime, que emite un valor de la fuente Observable solo después de que haya pasado un lapso de tiempo particular sin otra emisión de fuente. De esta forma la operación remota se activará solo cuando haya pasado el tiempo especificado sin que el usuario la interrumpa.

    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();
        });
    }
    typescript

    Cuando se proporciona clasificación y filtrado remotos, normalmente no necesitamos la clasificación y filtrado integrados de la cuadrícula. Podemos deshabilitarlos configurando las entradas sortStrategy y filterStrategy de la cuadrícula en las instancias respectivas de NoopSortingStrategy y NoopFilteringStrategy.

    <igx-grid #grid [data]="remoteData | async" [height]="'500px'" [width]="'100%'" [autoGenerate]='false'
            [filterStrategy]="noopFilterStrategy"
            [sortStrategy]="noopSortStrategy"
            [allowFiltering]="true">
        ...
    </igx-grid>
    html
    public noopFilterStrategy = NoopFilteringStrategy.instance();
    public noopSortStrategy = NoopSortingStrategy.instance();
    typescript

    Cuando se solicitan datos remotos, la operación de filtrado distingue entre mayúsculas y minúsculas.

    Demostración de clasificación/filtrado remoto

    Puede ver el resultado del código anterior al comienzo de este artículo en la sección Demostración.

    Estrategia de valores de columna única

    Los elementos de la lista dentro del cuadro de diálogo Filtrado de estilos de Excel representan los valores únicos para la columna respectiva. Grid genera estos valores en función de su fuente de datos de forma predeterminada. En caso de filtrado remoto, los datos de la cuadrícula no contienen todos los datos del servidor. Para proporcionar los valores únicos manualmente y cargarlos según demanda, podemos aprovechar la entrada uniqueColumnValuesStrategy de Grid. Esta entrada es en realidad un método que proporciona tres argumentos:

    • 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.

    Cuando se proporciona la entrada uniqueColumnValuesStrategy, no se utilizará el proceso de generación de valores únicos predeterminado en el filtrado de estilo Excel.

    <igx-grid #grid1 [data]="data" [filterMode]="'excelStyleFilter'" [uniqueColumnValuesStrategy]="columnValuesStrategy">
        ...
    </igx-grid>
    html
    public columnValuesStrategy = (column: ColumnType,
                                   columnExprTree: IFilteringExpressionsTree,
                                   done: (uniqueValues: any[]) => void) => {
        // Get specific column data.
        this.remoteValuesService.getColumnData(column, columnExprTree, uniqueValues => done(uniqueValues));
    }
    typescript

    Demostración de estrategia de valores de columna únicos

    EXAMPLE
    TS
    HTML
    SCSS

    Para proporcionar una plantilla de carga personalizada para el filtrado de estilos de Excel, podemos usar la directiva igxExcelStyleLoading:

    <igx-grid [data]="data" [filterMode]="'excelStyleFilter'" [uniqueColumnValuesStrategy]="columnValuesStrategy">
        ...
        <ng-template igxExcelStyleLoading>
            Loading ...
        </ng-template>
    </igx-grid>
    html
    App Builder | CTA Banner

    Localización remota

    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)
            );
        }
    }
    typescript

    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();
            }
        }
    }
    typescript

    Ahora podemos elegir entre configurar nuestra propia plantilla de paginación personalizada o usar la predeterminada que proporciona igx-paginator. Primero echemos un vistazo a lo que es necesario para configurar la paginación remota utilizando la plantilla de paginación predeterminada.

    Localización remota con plantilla predeterminada

    Si desea utilizar la plantilla de paginación predeterminada, debe configurar la propiedad totalRecords del Paginador, solo entonces la cuadrícula podrá calcular el número total de páginas en función del total de registros remotos. 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. Es por eso que deberíamos establecer la propiedad pagingMode de Grid en GridPagingMode.remote. También es necesario suscribirse a los eventos pagingDone o perPageChange para poder recuperar los datos de su servicio remoto; depende del caso de uso qué evento se utilizará.

    <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>
    html
    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);
    }
    typescript

    EXAMPLE
    TS
    HTML
    SCSS

    Localización remota con contenido igx-paginator personalizado

    Cuando definimos el contenido de un paginador personalizado, necesitamos definir el contenido de manera que obtenga los datos solo para la página solicitada y pase los parámetros de salto y superior correctos al servicio remoto de acuerdo con la página seleccionada y los elementos perPage. Usaremos <igx-paginator> para facilitar nuestra configuración de ejemplo, junto con IgxPageSizeSelectorComponent e IgxPageNavigationComponent que se introdujeron: igx-page-size agregará el menú desplegable y la etiqueta por página y igx-page-nav agregará los botones y etiquetas de 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>
    html
    @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);
    }
    typescript

    Para que la paginación remota se configure correctamente, se debe configurar GridPagingMode.Remote:

    <igx-grid #grid1 [data]="data | async" width="100%" height="580px" [pagingMode]="mode"></igx-grid>
    ...
    public mode = GridPagingMode.Remote;
    html

    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>
    html

    Después de todos los cambios anteriores, se logrará el siguiente resultado.

    EXAMPLE
    TS
    HTML
    SCSS

    Paginación remota con paginador personalizado

    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:

    EXAMPLE
    TS
    HTML
    SCSS

    A continuación encontrará los métodos que hemos definido para implementar nuestras propias acciones de página next y previous.

    @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);
    }
    typescript

    Paginación remota con edición por lotes

    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;
        });
    }
    typescript

    Para manejar este caso de uso correctamente, necesitamos implementar alguna lógica personalizada. Primero, tenemos que saber el número total de registros que hay en el servidor. Dado eso, calculamos el número total de páginas de datos en el servidor (consulte this._totalPagesOnServer) y, en función de su valor, implementaremos la lógica de paginación personalizada.

    
    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);
    }
    
    typescript

    Como puede ver en el método paginar, se realiza una lógica de paginación personalizada, basada en el valor_totalPagesOnServer.

    Paginación remota con demostración de edición por lotes

    EXAMPLE
    TS
    HTML
    SCSS

    Problemas conocidos y limitaciones

    • Cuando la cuadrícula no tiene un conjunto primaryKey y los escenarios de datos remotos están habilitados (al paginar, ordenar, filtrar y desplazar solicitudes de activación a un servidor remoto para recuperar los datos que se mostrarán en la cuadrícula), una fila perderá el siguiente estado después de un dato. solicitud completa:
      • 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 una primaryKey establecida, el argumento del evento rowSelectionChanging.oldSelection no contendrá el objeto de datos de fila completo para las filas que actualmente están fuera de la vista de datos. En este caso, el objeto rowSelectionChanging.oldSelection contendrá solo una propiedad, que es el campo primaryKey. Para el resto de las filas, actualmente en la vista de datos, rowSelectionChanging.oldSelection contendrá todos los datos de la fila.

    Referencias de API

    Recursos adicionales

    Nuestra comunidad es activa y siempre da la bienvenida a nuevas ideas.