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
¿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 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.
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:
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
yheight
igual acontainerSize
enpx
<igx-drop-down-item-group>
No se puede utilizar para agrupar elementos cuando la lista está virtualizada. Utilice laisHeader
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 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