javascript - Disparar un evento al desplazarse hasta el final de Mat-0ptions en Mat-Select: Angular 6
Recientemente empecé a modificar un proyecto basado en Angular 6 y me encontré con un pequeño problema. Entonces, tengo (supongamos) 4 campos Mat-Select, para Idioma, Moneda, País y Organización.
Inicialmente todos los desplegables tienen 10 valores predeterminados, los obtengo haciendo una llamada a la API. Mi requisito es hacer otra llamada a la API una vez que el usuario llegue al final del cuadro de Mat-Options que se abre al seleccionar el desplegable.
Me había referido a esta pregunta y funciona bien, pero he notado algunos problemas. Esa respuesta cubre la solución para un campo. ¿Tenemos que repetir el código si estamos usando múltiples campos?
Aquí está el Html
<mat-select [disabled]="isDisabled" [(ngModel)]="model.currency" name="currency" (selectionChange)="OnChange('currency',$event.value)" #currency>
<mat-option [value]="getCurrency.currencyCode" *ngFor="let getCurrency of fetchCurrency |
filternew:searchCurrencyvaluedrop">{{ getCurrency.currencyDescription }}
</mat-option>
</mat-select>
<mat-select [disabled]="isDisabled" [(ngModel)]="model.language" name="language"
(selectionChange)="OnChange('language', $event.value)" #language>
<mat-option [value]="getLanguage.languageDescription" *ngFor="let getLanguage of fetchLanguage |
filternew:searchLanguagevaluedrop">{{ getLanguage.languageDescription }}</mat-option>
</mat-select>
Estoy agregando solo dos campos por el bien de la simplicidad. A continuación está el código de .ts:
viewIndex = 0;
windowSize = 10;
private readonly PIXEL_TOLERANCE = 3.0;
ngAfterViewInit() {
this.selectElemCu.openedChange.subscribe((event) =>
this.registerPanelScrollEventCurrency(event)
);
this.selectElem.openedChange.subscribe((event) =>
this.registerPanelScrollEventLang(event)
);
}
//for language field
registerPanelScrollEventLang(e) {
if (e) {
const panel = this.selectElem.panel.nativeElement;
panel.addEventListener('scroll', event => this.loadNextOnScrollLang(event, this.selectElem.ngControl.name));
}
}
loadNextOnScrollLang(event, select_name) {
if (this.hasScrolledToBottomLang(event.target)) {
this.viewIndex += this.windowSize;
this.modifyPageDetails(select_name)
this.appService.getAllLanguages(this.standardPageSizeObj, resp => {
console.log('list of languages', resp)
this.fetchLanguage.push(...resp['data'])
}, (error) => {
});
}
}
private hasScrolledToBottomLang(target): boolean {
return Math.abs(target.scrollHeight - target.scrollTop - target.clientHeight) < this.PIXEL_TOLERANCE;
}
El código para el menú desplegable de moneda permanece igual. ASÍ QUE, la duplicación es mi primer problema. El segundo es que al desplazar hay dos llamadas a la API en lugar de una. Puedo vivir con eso, pero dado que hay 6 campos, la repetición es demasiado.
Debido a ciertas restricciones de seguridad, ni siquiera puedo usar una biblioteca externa. ¿Hay una mejor manera de implementar esto? Por favor, háganme saber si se requiere más aclaración. Gracias
Respuesta #1
Aquí está cómo puedes hacerlo:
PASO 1
Para cada mat-select debes crear una variable local con ViewChild.
@ViewChild('select_1', { static: false }) select_1: MatSelect;
@ViewChild('select_2', { static: false }) select_2: MatSelect;
@ViewChild('select_3', { static: false }) select_3: MatSelect;
@ViewChild('select_4', { static: false }) select_4: MatSelect;
PASO 2
Crea una función que se activará cuando se abra cualquiera de los menús desplegables. Puedes usar el emisor de eventos (openedChange) y verificar si el valor del evento es true. El segundo parámetro de la función puede ser el menú desplegable que se abrió, para que no tengamos código repetitivo para cada menú desplegable.
<mat-form-field>
<mat-select placeholder="Select 1" #select_1 (openedChange)="onOpenedChange($event, 'select_1')">
<mat-option *ngFor="let item of select_1_options;">
{{item}}
</mat-option>
</mat-select>
</mat-form-field>
PASO 3
Define la función onOpenedChange() que se activará en el evento openedChange. Esa función debe verificar si el scrollTop del panel del desplegable es igual a (scrollHeight-offsetHeight) porque eso significa que el usuario desplazó hasta el final del scroll. Cuando eso ocurra, puedes llamar a la API.
onOpenedChange(event: any, select: string) {
if (event) {
this[select].panel.nativeElement.addEventListener('scroll', (event: any) => {
if (this[select].panel.nativeElement.scrollTop === this[select].panel.nativeElement.scrollHeight - this[select].panel.nativeElement.offsetHeight) {
console.log(`Call API for ${select}`);
}
});
}
}
Aquí está el código funcional: https://stackblitz.com/edit/angular-ivy-xritb1?file=src%2Fapp%2Fapp.component.ts
Respuesta #2
Podemos crear una directiva que maneje esta lógica de desplazamiento por nosotros y podemos prevenir la duplicación de código.
aquí hay una demostración: https://stackblitz.com/edit/angular-ivy-m2gees?file=src/app/mat-select-bottom-scroll.directive.ts
Y aquí hay una breve explicación:
Crea una directiva personalizada y obtén la referencia de MatSelect dentro de ella.
import {Directive,ElementRef,EventEmitter,Input,OnDestroy,Output} from '@angular/core';
import { MatSelect } from '@angular/material/select/';
import { fromEvent, Subject } from 'rxjs';
import { filter, switchMap, takeUntil, throttleTime } from 'rxjs/operators';
@Directive({
selector: '[appMatSelectScrollBottom]'
})
export class MatSelectScrollBottomDirective implements OnDestroy {
private readonly BOTTOM_SCROLL_OFFSET = 25;
@Output('appMatSelectScrollBottom') reachedBottom = new EventEmitter<void>();
onPanelScrollEvent = event => {};
unsubscribeAll = new Subject<boolean>();
constructor(private matSelect: MatSelect) {
this.matSelect.openedChange
.pipe(filter(isOpened => !!isOpened),
switchMap(isOpened =>fromEvent(this.matSelect.panel.nativeElement, 'scroll').pipe(throttleTime(50))), //controles the thrasold of scroll event
takeUntil(this.unsubscribeAll)
)
.subscribe((event: any) => {
console.log('scroll');
// console.log(event, event.target.scrollTop, event.target.scrollHeight);
if (
event.target.scrollTop >= (event.target.scrollHeight - event.target.offsetHeight - this.BOTTOM_SCROLL_OFFSET)) {
this.reachedBottom.emit();
}
});
}
ngOnDestroy(): void {
this.unsubscribeAll.next(true);
this.unsubscribeAll.complete();
}
}
Esta directiva emitirá un evento tan pronto como el desplazamiento llegue al fondo.
Comenzamos con el evento openedChange y luego lo switchMapeamos al evento de desplazamiento del panel de selección. SwitchMap se dará de baja automáticamente del evento de desplazamiento anterior tan pronto como se dispare el nuevo evento de apertura.
En su componente, use esta directiva para escuchar el evento de la siguiente manera.
<mat-form-field>
<mat-select placeholder="Choose a Doctor" (openedChange)="!$event && reset()"
(appMatSelectScrollBottom)="loadAllOnScroll()"><!-- liste to the event and call your load data function -->
<mat-option *ngFor="let dr of viewDoctors;let i = index">{{dr}}</mat-option>
</mat-select>
</mat-form-field>
