【问题标题】:Emit first value then ignore values emitted within a timer发出第一个值,然后忽略计时器内发出的值
【发布时间】:2019-11-27 18:38:21
【问题描述】:

这是我的代码:
app.component.ts

notifier$ = new BehaviorSubject<any>({});

notify() {
  this.notifier$.next({});
}

app.component.html

<div (scroll)="notify()"></div>
<child-component [inp]="notifier$ | async" />

问题是当用户滚动时,notify() 函数会被重复调用,而我只想在每次用户开始滚动时调用notify() 一次。

我可以通过这种方式完成我想要的:

scrolling = false;
scrollingTimer: NodeJS.Timer;

notify() {
  clearTimeout(this.scrollingTimer);
  if (!this.scrolling) {
    this.notifier$.next({});
  }
  this.scrolling = true;
  this.scrollingTimer = setTimeout(() => (this.scrolling = false), 1000);
}

但我希望喜欢使用rxjs 进行此操作。然而debounceTime 与我想要的相反,throttleTimeauditTime 也不是我想要的。有没有办法做到这一点?

【问题讨论】:

  • 用户开始滚动,因此调用了notify(),然后您想忽略其他调用,直到调用停止 1 秒?
  • @Reactgular 是的

标签: javascript angular rxjs observable


【解决方案1】:

你可以使用 take(1)

this.inp.pipe(
  take(1),
).subscribe(res => console.log(res));

take(1) 只取第一个值并完成。不涉及进一步的逻辑。

当然你应该在你的子组件中使用上述内容:)

此外,由于您的 observable 正在完成...您可以创建一个新主题并将其传递给子组件,每次他滚动时


notify() {
  this.notifier$ = new BehaviorSubject<any>({});
  this.notifier$.next({});
}

【讨论】:

  • 我想在用户每次滚动时获取第一个值,而不仅仅是一次。如果用户停止滚动然后再次开始滚动,我想再次发出。
  • 你可以尝试使用 takeUntil()
  • 这不起作用,当用户滚动时,notify() 会被重复调用。
【解决方案2】:

当用户滚动时,您希望notify$ 为每个滚动事件发出。这提供了一个恒定的发射值流。因此,您希望 notifier$ 在流启动时发出一次,并在它空闲 1 秒时再次发出。

notify$ = new Subject();

notifier$ = merge(
    notify$.pipe(first()),
    notify$.pipe(switchMap(value => of(value).pipe(delay(1000))))
).pipe( 
    take(2),
    repeat()
);

<div (scroll)="notify$.next()"></div>

你合并了两个 observables。第一个立即发出,第二个在延迟 1 秒后发出。您使用 switchMap 以便延迟的 observable 总是重新启动。

我们取下两个触发流完成的值,并使用重复来重新开始。

【讨论】:

  • 这似乎和debounceTime做同样的事情
  • 只要用户在滚动它就不应该再次发出。
【解决方案3】:

你可以像这样构建一个 observable:

const scroll$ = fromEvent(document, 'scroll');

const scrollEnd$ = scroll$.pipe(
  switchMapTo(
    timer(1000) // on every scroll, restart a timer
  )
);

const scrollStart$ = scrollEnd$.pipe( // the scroll end event triggers switch to scroll$
  startWith(0), // but start it off
  switchMapTo(
    scroll$.pipe( // then just take the first scroll$ event
      first()
    )
  )
);

scrollStart$.subscribe(v => console.log('scroll start'));

您可以将其推广到运算符:

function firstTimeout(timeout: number) { // welcoming notes on a better name
  return input$ => {
    const inputTimeout$ = input$.pipe(
      switchMapTo(timer(timeout))
    );

    return inputTimeout$.pipe(
      startWith(0),
      switchMapTo(input$.pipe(first()))
    );
  };
}

并像这样使用它:

notifier$.pipe(firstTimeout(1000)).subscribe(v => console.log('took one'));

对于这种情况,一个好主意可能是将其包装在指令中以便于重用:

@Directive({
  selector: '[scrollStart]'
})
export class ScrollStartDirective {

  private scrollSource = new Subject();

  @HostListener('scroll', ['$event'])
  private onScroll(event) {
    this.scrollSource.next(event);
  }

  @Output()
  scrollStart = new EventEmitter();

  constructor() {
    this.scrollSource.pipe(firstTimeout(1000)).subscribe(this.scrollStart);
  }
}

那么你可以这样使用它:

<div (scrollStart)="notify()"></div>

【讨论】:

  • 它是正确的解决方案.....我建议使用 take(1) 而不是 first() 因为如果用户从不滚动,那么 first() 会在您的控制台中引发错误
  • 太棒了!我唯一要添加的是指令中的unsubscribe
  • @obl 您可以但不必...这是本地主题
  • @bryan60 hm 我不确定我是否理解何时需要取消订阅。这与在 component 中使用 .subscribe 有什么区别?
  • @obl 我在这里写了一个答案:stackoverflow.com/questions/57007118/…
猜你喜欢
  • 2017-12-17
  • 1970-01-01
  • 1970-01-01
  • 2018-01-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-10-01
相关资源
最近更新 更多