Menú desplegable virtual

    El componente desplegable Ignite UI for Angular puede integrarse completamente con la IgxForOf directiva para mostrar una lista muy grande de elementos para su selección.

    Angular Ejemplo de menú desplegable virtual

    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.

    Uso

    Primeros pasos

    Para configurar el menú desplegable para que muestre una lista de elementos virtuales, debe cumplir algunos requisitos previos. Primero, necesitamos importar IgxForOfModule en el módulo del componente que declarará nuestro menú desplegable.

    // app.module.ts
    import { IgxForOfModule } from 'igniteui-angular';
    // import { IgxForOfModule } from '@infragistics/igniteui-angular'; for licensed package
    
    @NgModule({
        imports: [
            ...
            IgxForOfModule
        ]
    })
    export class AppModule {}
    typescript

    Configuración de plantilla

    A continuación, necesitamos crear la plantilla del componente desplegable, recorriendo los datos usando *igxFor en lugar de *ngFor. La directiva *igxFor necesita alguna configuración adicional para poder mostrar correctamente todos los elementos:

    <!-- drop-down-virtual.component.html -->
    <button igxButton [igxToggleAction]="dropdown"
            [igxDropDownItemNavigation]="dropdown">
            Item Series
    </button>
    <igx-drop-down #dropdown>
        <div class="drop-down-virtual-wrapper" style="height: {{ itemsMaxHeight }}px;">
            <igx-drop-down-item
                *igxFor="let item of items; index as index;
                         scrollOrientation: 'vertical';
                         containerSize: itemsMaxHeight;
                         itemSize: itemHeight;"
                [value]="item" [isHeader]="item.header"
                role="option" [disabled]="item.disabled"
                [index]="index">
                {{ item.name }}
            </igx-drop-down-item>
        </div>
    </igx-drop-down>
    <div>Selected Model: <span>{{ dropdown.selectedItem?.value.name }}</span></div>
    html

    Los parámetros adicionales pasados a la directiva *igxFor son:

    • index: captura el índice del elemento actual en el conjunto de datos
    • scrollOrientation: siempre debe ser 'vertical'
    • containerSize: el tamaño del contenedor virtualizado (en px). Esto también debe aplicarse en el envoltorio <div>
    • itemSize: el tamaño de los elementos que se mostrarán (en px)

    Para garantizar la unicidad de los elementos, pase item dentro de la entrada value y index dentro de la entrada index del igx-drop-down-item. Para conservar la selección mientras se desplaza, el elemento desplegable debe tener una referencia a los elementos de datos a los que está vinculado.

    Para que el menú desplegable funcione con una lista virtualizada de elementos, value y index entradas debe pasarse a todos los elementos.

    Se recomienda encarecidamente que cada elemento tenga un valor único pasado a la entrada [value]. De lo contrario, podrían producirse resultados inesperados (selección incorrecta).

    Cuando el menú desplegable usa elementos virtualizados, el tipo de dropdown.selectedItem se convierte en { value: any, index: number }, donde value es una referencia al elemento de datos pasado dentro de la entrada [value] e index es el índice del elemento en el conjunto de datos

    Definición de componente

    Dentro del constructor del componente, declararemos una lista moderadamente grande de elementos (que contienen encabezados y elementos deshabilitados), que se mostrarán en el menú desplegable. También necesitaremos declarar itemHeight y itemsMaxHeight:

    // drop-drop-virtual.component.ts
    export class DropDownVirtualComponent {
      public items: DataItem[];
      public itemHeight = 48;
      public itemsMaxHeight = 320;
    
      constructor() {
        const itemsCollection: DataItem[] = [];
        for (let i = 0; i < 50; i++) {
            const series = (i * 10).toString();
            itemsCollection.push({
                id: series,
                name: `${series} Series`,
                header: true,
                disabled: false
            });
            for (let j = 0; j < 10; j++) {
                itemsCollection.push({
                    id: `${series}_${j}`,
                    name: `Series ${series}, ${i * 10 + j} Model`,
                    header: false,
                    disabled: j % 9 === 0
                });
            }
        }
        this.items = itemsCollection;
      }
    }
    typescript

    Estilos

    La última parte de la configuración es establecer overflow: hidden en el div envolvente para evitar la aparición de dos barras de desplazamiento (una del igxFor y otra del propio contenedor):

    // drop-drop-virtual.component.scss
    .drop-down-virtual-wrapper {
      overflow: hidden;
    }
    scss

    Datos remotos

    El igx-drop-down admite la carga de fragmentos de datos remotos utilizando la directiva estructural *igxFor. La configuración es similar a la de los elementos locales, la principal diferencia es cómo se cargan los fragmentos de datos.

    Plantilla

    La plantilla desplegable no necesita cambiar mucho en comparación con el ejemplo anterior: todavía necesitamos especificar un div de envoltura, diseñarlo en consecuencia y escribir la configuración completa para el *igxFor. Dado que obtendremos nuestros datos de una fuente remota, debemos especificar que nuestros datos serán observables y pasarlos a través de la tubería de async Angular:

    <igx-drop-down #remoteDropDown>
        <div class="drop-down-virtual-wrapper">
            <igx-drop-down-item
                *igxFor="let item of rData | async; index as index;
                         scrollOrientation: 'vertical';
                         containerSize: itemsMaxHeight;
                         itemSize: itemHeight;"
                [value]="item.ProductName" role="option"
                [disabled]="item.disabled" [index]="index">
                {{ item.ProductName }}
            </igx-drop-down-item>
        </div>
    </igx-drop-down>
    html

    Manejo de carga fragmentada

    Como puede ver, la plantilla es casi idéntica a la del ejemplo anterior. En este escenario de datos remotos, el código subyacente hará la mayor parte del trabajo pesado.

    Primero, necesitamos definir un servicio remoto para recuperar datos:

    // remote.service.ts
    import { HttpClient } from '@angular/common/http';
    import { Injectable } from '@angular/core';
    import { IForOfState } from 'igniteui-angular';
    // import { IForOfState } from '@infragistics/igniteui-angular'; for licensed package
    import { BehaviorSubject, Observable } from 'rxjs';
    
    @Injectable()
    export class RemoteService {
        public remoteData: Observable<any[]>;
        private _remoteData: BehaviorSubject<any[]>;
    
        constructor(private http: HttpClient) {
            this._remoteData = new BehaviorSubject([]);
            this.remoteData = this._remoteData.asObservable();
        }
    
        public getData(data?: IForOfState, cb?: (any) => void): any {
            // Assuming that the API service is RESTful and can take the following:
            // skip: start index of the data that we fecth
            // count: number of records we fetch
        this.http.get(`https://dummy.db/dummyEndpoint?skip=${data.startIndex}&count=${data.chunkSize}`).subscribe((data) => {
            // emit the values through the _remoteData subject
            this._remoteData.next(data);
        })
    }
    typescript

    El servicio expone un Observable en remoteData. Inyectaremos nuestro servicio y nos vincularemos a esa propiedad en nuestro componente desplegable remoto:

    // remote-drop-down.component.ts
    @Component({
        providers: [RemoteService],
        selector: 'app-drop-down-remote',
        templateUrl: './drop-down-remote.component.html',
        styleUrls: ['./drop-down-remote.component.scss']
    })
    export class DropDownRemoteComponent implements OnInit, OnDestroy {
        @ViewChild(IgxForOfDirective, { read: IgxForOfDirective })
        public remoteForDir: IgxForOfDirective<any>;
        @ViewChild('remoteDropDown', { read: IgxDropDownComponent })
        public remoteDropDown: IgxDropDownComponent;
        public itemHeight = 48;
        public itemsMaxHeight = 480;
        public prevRequest: Subscription;
        public rData: any;
    
        private destroy$ = new Subject();
        constructor(private remoteService: RemoteService) { }
    
        public ngAfterViewInit() {
            const initialState = { startIndex: 0, chunkSize: Math.ceil(this.itemsMaxHeight / this.itemHeight) }
            this.remoteService.getData(initialState, (data) => {
                this.remoteForDir.totalItemCount = data['@odata.count'];
            });
            // Subscribe to igxForOf.chunkPreload and load new data from service
            this.remoteForDir.chunkPreload.pipe(takeUntil(this.destroy$)).subscribe((data) => {
                this.dataLoading(data);
            });
        }
    
        public dataLoading(evt) {
            if (this.prevRequest) {
                this.prevRequest.unsubscribe();
            }
            this.prevRequest = this.remoteService.getData(
                evt,
                (data) => {
                    this.remoteForDir.totalItemCount = data['@odata.count'];
                });
        }
    
        public ngOnInit() {
            this.rData = this.remoteService.remoteData;
        }
    
        public ngOnDestroy() {
            this.destroy$.next();
            this.destroy$.complete();
        }
    }
    typescript

    Dentro de la ngAfterViewInit gancho, llamamos para obtener datos para el estado inicial y suscribirnos al igxForOf directivas chunkPreload emisor. Esta suscripción será responsable de recuperar datos cada vez que cambie el fragmento cargado. Usamos pipe(takeUntil(this.destroy$)) para que podamos cancelar fácilmente la suscripción al emisor al destruir el componente.

    Virtualización remota: demostración

    El resultado de la configuración anterior es un menú desplegable que carga dinámicamente los datos que debe mostrar, según el estado de la barra de desplazamiento:

    EXAMPLE
    TS
    HTML
    SCSS

    Notas y limitaciones

    El uso del menú desplegable con una lista virtualizada de elementos impone algunas limitaciones. Tenga en cuenta lo siguiente cuando intente configurar una lista desplegable utilizando *igxFor:

    • Los elementos desplegables que se están repitiendo deben pasarse en un elemento envolvente (por ejemplo, <div>) que tenga el siguiente CSS: overflow: hidden y height igual a containerSize en px
    • <igx-drop-down-item-group> No se puede utilizar para agrupar elementos cuando la lista está virtualizada. Utilice la isHeader propiedad en su lugar
    • El descriptor de acceso items devolverá solo la lista de elementos desplegables sin encabezado que se encuentran actualmente en la vista virtualizada.
    • dropdown.selectedItem es de tipo { value: any, index: number }
    • El objeto emitido por selection cambia a const emittedEvent: { newSelection: { value: any, index: number }, oldSelection: { value: any, index: number }, cancel: boolean, }
    • Se debe llamar dropdown.setSelectedItem con el índice del elemento en el conjunto de datos.
    • configurar la entrada [selected] del elemento desplegable no marcará el elemento en la selección desplegable
    App Builder | CTA Banner

    Referencias de API