去抖动@HostListener事件

Vic*_*ens 19 rxjs angular

我正在Angular2中实现一个简单的无限滚动指令.我正在使用@HostListener('window:scroll')滚动事件并解析数据$target.

问题是,对于每个滚动事件,一切都将被再次检查而不需要.

我检查了离子infinite-scroll指令的灵感,但他们没有使用@HostListener,我猜他们需要更精细的控制.

我在搜索https://github.com/angular/angular/issues/13248时最终遇到了这个问题,但找不到任何方法来做我想要的事情.

我想如果我创建一个Observable,用debounce和push(next)项目订阅它,我会达到我想要的行为,但我无法做到这一点.

yur*_*zui 43

我会利用debounce方法装饰器,如:

export function debounce(delay: number = 300): MethodDecorator {
  return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
    let timeout = null

    const original = descriptor.value;

    descriptor.value = function (...args) {
      clearTimeout(timeout);
      timeout = setTimeout(() => original.apply(this, args), delay);
    };

    return descriptor;
  };
}
Run Code Online (Sandbox Code Playgroud)

并按如下方式使用:

@HostListener('window:scroll', ['$event'])  
@debounce() 
scroll(event) {
  ...
}
Run Code Online (Sandbox Code Playgroud)

Plunker示例


小智 12

我真的很喜欢@yurzui的解决方案,我更新了很多代码来使用它。但是,我认为它包含一个错误。在原始代码中,timeout每个只有一个,但实际上每个实例都需要一个。

在 Angular 术语中,这意味着如果使用的组件在@debounce()容器中多次实例化,则每次实例化都将cancelTimeout是前一个实例化,只有最后一个实例化才会触发。

我提出这个轻微的变体来消除这个麻烦:

export function debounce(delay: number = 300): MethodDecorator {
  return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {

    const original = descriptor.value;
    const key = `__timeout__${propertyKey}`;

    descriptor.value = function (...args) {
      clearTimeout(this[key]);
      this[key] = setTimeout(() => original.apply(this, args), delay);
    };

    return descriptor;
  };
}
Run Code Online (Sandbox Code Playgroud)

当然,可以更复杂地消除合成__timeout__属性的歧义。


Mar*_*n S 9

执行此操作的 RXJS 方法可以fromEventthrottleTime操作符一起使用来实现。

您无需使用 来装饰事件处理程序,而是使用(例如,在方法中)@HostListener从事件创建一个可观察对象,然后使用 来限制事件的发出。fromEventngOnInitthrottleTime

...
import {fromEvent, Subscription} from 'rxjs';
import {tap, throttleTime} from 'rxjs/operators';


export class MyComponent implements OnInit, OnDestroy { 

  private eventSub: Subscription;

  ngOnInit() {
    this.eventSub = fromEvent(window, 'scroll').pipe(
      throttleTime(300), // emits once, then ignores subsequent emissions for 300ms, repeat...
      tap(event => this.scroll(event))
    ).subscribe();
  }

  scroll(event) {
    ...
  }

  ngOnDestroy() {
    this.eventSub.unsubscribe(); // don't forget to unsubscribe
  }
}
Run Code Online (Sandbox Code Playgroud)

使用 RXJS 的优点之一是您可以将自定义调度程序传递给throttleTime操作员以实现不同的行为。例如,您可以通过动画帧速率来限制事件发射(例如,限制触摸事件的发射)。

import {animationFrameScheduler, ...} from 'rxjs';
...

this.eventSub = fromEvent(window, 'touchmove').pipe(
  throttleTime(0, animationFrameScheduler),
  tap(event => ...)
).subscribe();
Run Code Online (Sandbox Code Playgroud)