Lista suspensa virtual

    O componente Ignite UI for Angular Drop Down pode ser totalmente integrado à diretiva IgxForOf para exibir uma lista muito grande de itens para sua seleção.

    Angular Virtual Drop Down Example

    Usage

    First Steps

    Para configurar o drop-down para exibir uma lista de itens virtuais, você precisa cumprir alguns pré-requisitos. Primeiro, precisamos importar o IgxForOfModule no módulo do componente que declarará nosso drop-down.

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

    Em seguida, precisamos criar o modelo do componente drop-down, fazendo um loop pelos dados usando *igxFor em vez de *ngFor. A diretiva *igxFor precisa de alguma configuração adicional para exibir corretamente todos os itens:

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

    Os parâmetros adicionais passados para a diretiva *igxFor são:

    • index- captura o índice do item atual no conjunto de dados
    • scrollOrientation- deve ser sempre 'vertical'
    • containerSize- o tamanho do contêiner virtualizado (em px). Isso precisa ser imposto no <div> de encapsulamento também
    • itemSize- o tamanho dos itens que serão exibidos (em px)

    Para garantir a exclusividade dos itens, passe item dentro da entrada value e index dentro da entrada index do igx-drop-down-item. Para preservar a seleção durante a rolagem, o item drop-down precisa ter uma referência aos itens de dados aos quais está vinculado.

    Note

    Para que o menu suspenso funcione com uma lista virtualizada de itens, as entradas value e index​ ​devem ser passadas para todos os itens.

    Note

    É altamente recomendável que cada item tenha um valor único passado para a entrada [value]. Caso contrário, pode levar a resultados inesperados (seleção incorreta).

    Note

    Quando o menu suspenso usa itens virtualizados, o tipo de dropdown.selectedItem se torna { value: any, index: number }, onde value é uma referência ao item de dados passado dentro da entrada [value] e index é o índice do item no conjunto de dados

    Component Definition

    Dentro do construtor do componente, declararemos uma lista moderadamente grande de itens (contendo cabeçalhos e itens desabilitados), que serão exibidos no menu suspenso. Também precisaremos declarar itemHeight e 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

    A última parte da configuração é definir overflow: hidden para o div de encapsulamento para evitar o aparecimento de duas barras de rolagem (uma do igxFor e uma do próprio contêiner):

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

    Remote Data

    O igx-drop-down suporta o carregamento de pedaços de dados remotos usando a diretiva estrutural *igxFor. A configuração é similar à dos itens locais, a principal diferença é como os pedaços de dados são carregados.

    Template

    O modelo drop-down não precisa mudar muito em comparação ao exemplo anterior - ainda precisamos especificar um div de encapsulamento, estilizá-lo adequadamente e escrever a configuração completa para o *igxFor. Como obteremos nossos dados de uma fonte remota, precisamos especificar que nossos dados serão um observable e passá-los pelo pipe async do 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 você pode ver, o template é quase idêntico ao do exemplo anterior. Neste cenário de dados remotos, o código por trás fará a maior parte do trabalho pesado.

    Primeiro, precisamos definir um serviço remoto para buscar dados:

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

    O serviço expõe um Observable sob remoteData. Injetaremos nosso serviço e vincularemos a essa propriedade em nosso componente drop-down 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 do ngAfterViewInit gancho, chamamos para obter dados para o estado inicial e assinar o igxForOf diretivas chunkPreload emissor. Esta assinatura será responsável por buscar dados toda vez que o bloco carregado mudar. Usamos pipe(takeUntil(this.destroy$)) para que possamos facilmente cancelar a assinatura do emissor na destruição do componente.

    Remote Virtualization - Demo

    O resultado da configuração acima é um menu suspenso que carrega dinamicamente os dados que deve exibir, dependendo do estado da barra de rolagem:

    Notes and Limitations

    Usar o drop-down com uma lista virtualizada de itens impõe algumas limitações. Por favor, esteja ciente do seguinte ao tentar configurar uma lista drop-down usando *igxFor:

    • Os itens suspensos que estão sendo repetidos precisam ser passados em um elemento de encapsulamento (por exemplo, <div>) que tenha o seguinte css: overflow: hidden e height igual a containerSize em px
    • <igx-drop-down-item-group> não pode ser usado para agrupar itens quando a lista é virtualizada. Use a propriedade isHeader em vez disso
    • O acessador items retornará apenas a lista de itens suspensos sem cabeçalho que estão atualmente na exibição virtualizada.
    • dropdown.selectedItem é do tipo { value: any, index: number }
    • O objeto emitido pela selection muda para const emittedEvent: { newSelection: { value: any, index: number }, oldSelection: { value: any, index: number }, cancel: boolean, }
    • dropdown.setSelectedItem deve ser chamado com o índice do item no conjunto de dados
    • definir a entrada [selected] do item suspenso não marcará o item na seleção suspensa

    API References