I created a directive that should emit an event once its element is visible in the viewport.
@Directive({
selector: '[scrollListener]',
})
export class ScrollListenerDirective {
@Output() scrollListener: Observable<number>;
constructor(private el: ElementRef) {
this.scrollListener = fromEvent(document, 'scroll').pipe(
map(() => this.el.nativeElement.getBoundingClientRect()),
map(({ top }) => Math.abs(top)),
filter((top) => top <= 100)
);
}
}
The problem is that this event will emit whenever the top is less than 100px. That results in way too many events. How do I change this event so it only emit once the element is in view, stop emitting when it isn't, and then once the next time when it is visible.
CodePudding user response:
You could use the scan
operator to cache the preceding value of top
. By comparing the preceding value with the current value of top
you can determine when the transition from > 100
to <= 100
happens. At the time of this transition, the observable emits a value.
@Directive({
selector: '[scrollListener]',
})
export class ScrollListenerDirective {
@Output() scrollListener: Observable<number>;
constructor(private el: ElementRef) {
this.scrollListener = fromEvent(document, 'scroll').pipe(
map(() => this.el.nativeElement.getBoundingClientRect()),
map(({ top }) => Math.abs(top)),
scan((acc: number[], curr: number) => [acc[1], curr], [0, 0]),
filter(valuesOfTop => valuesOfTop[0] > 100 && valuesOfTop[1] <= 100),
map(valuesOfTop => valuesOfTop[1]),
);
}
}
CodePudding user response:
this.scrollListener = fromEvent(document, 'scroll').pipe(
map(() => this.el.nativeElement.getBoundingClientRect()),
map(({ top }) => Math.abs(top)),
distinctUntilChanged((p, c) => c <= 100 === p <= 100),
filter((top) => top <= 100)
);