Home > Net >  Angular emit one value when element is in view
Angular emit one value when element is in view

Time:12-05

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:

use distinctUntilChanged

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)
);
  • Related