Menú desplegable virtual

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

    Angular Virtual Drop Down Example

    Usage

    First Steps

    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 {}
    

    Template Configuration

    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>
    

    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.

    Note

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

    Note

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

    Note

    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

    Component Definition

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

    Styles

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

    Remote Data

    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.

    Template

    La plantilla desplegable no necesita cambiar mucho en comparación con el ejemplo anterior; aún necesitamos especificar un div envolvente, darle el estilo correspondiente y escribir la configuración completa para *igxFor. Dado que obtendremos nuestros datos de una fuente remota, debemos especificar que nuestros datos serán observables y pasarlos a través del canal async de 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>
    

    Handling chunk load

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

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

    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.

    Remote Virtualization - Demo

    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:

    Notes and Limitations

    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 propiedad isHeader 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

    API References