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 datosscrollOrientation
: siempre debe ser'vertical'
containerSize
: el tamaño del contenedor virtualizado (enpx
). Esto también debe aplicarse en el envoltorio<div>
itemSize
: el tamaño de los elementos que se mostrarán (enpx
)
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
yheight
igual acontainerSize
enpx
<igx-drop-down-item-group>
no se puede utilizar para agrupar elementos cuando la lista está virtualizada. Utilice la propiedadisHeader
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 aconst 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