Reputation: 129
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.
Upvotes: 1
Views: 134
Reputation: 683
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)
);
Upvotes: 1
Reputation: 4495
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]),
);
}
}
Upvotes: 3