简体   繁体   English

如何在文档点击时暂停和恢复 observable?

[英]How can I pause and resume an observable on document click?

I have an observable that emits certain values to the console on every second.我有一个 observable 每秒向控制台发出某些值。 I show an example below and a StackBlitz as well.我在下面展示了一个示例以及一个StackBlitz

My dilemma is I need to able to pause it on the next document click and resume it (not restart it) it on the next one again.我的困境是我需要能够在下一次单击文档时暂停它并在下一次单击时恢复它(而不是重新启动它)。 I was looking into switchMap and I couldn't figure out how to implement it.我正在研究 switchMap ,但我不知道如何实现它。

export class AppComponent implements OnInit {
  title = "my-app";
  pageClickObservable: Observable<Event> = fromEvent(document, "click");

  ngOnInit(): void {
    new Observable();

    const watchForClick = () => {
      setTimeout(() => {
        console.log("A");
      }, 1000);
      setTimeout(() => {
        console.log("BB");
        console.log("10");
      }, 2000);
      setTimeout(() => {
        console.log("CCC");
      }, 3000);
      setTimeout(() => {
        console.log("DDDD");
        console.log("20");
      }, 4000);
      setTimeout(() => {
        console.log("EEEEE");
      }, 5000);
      setTimeout(() => {
        console.log("FFFFFF");
        console.log("30");
      }, 6000);
    };

    this.pageClickObservable.pipe().subscribe(() => {
      watchForClick();
    });
  }
}

I think you want something like this:我想你想要这样的东西:

const $myContinuousObs = ...... // This is your observable that keeps emitting
const $isOn = new BehaviourSubject(true); // BehaviorSubject to store the current on/off state

combineLatest([$myContinuousObs, $isOn]).pipe( //combine the two
    filter(res => res[1]), // only continue when $isOn is true
    map(res => res[0]) // return only the myContinuousObs and not the $isOn
).subscribe(console.log) // log the value

To change $onOff :要更改$onOff

$isOn.next(true)
$isOn.next(false)

To toggle $onOff切换$onOff

$isOn.pipe(take(1)).subscribe(val => $onOff.next(!val))

you need to turn your page click into some stream that can track the state of the switch using scan like so:您需要将您的页面点击变成一些 stream ,它可以使用scan跟踪开关的 state,如下所示:

const clickStream$ = fromEvent(document, 'click').pipe(
  scan((isOn, click) => !isOn, true), // on every click, toggle the switch
  startWith(true) // initially emit true
)

then you can just switchMap :然后你可以switchMap

clickStream$.pipe(
  switchMap(isOn => isOn ? obs$ : EMPTY)
).subscribe(v => console.log(v, 'got value'))

where obs$ is whatever you want to start and stop listening to.其中obs$是您想要开始和停止收听的任何内容。 now this method with switchMap will resubscribe on each clickStream$ emission, which depending on the nature of the observable of interest, may not be what you're after, but the concept of the clickStream$ observable may still be useful to you combined with the other answer here using filter现在这个带有switchMap的方法将在每次clickStream$发射时重新订阅,这取决于感兴趣的 observable 的性质,可能不是您所追求的,但是clickStream$ observable 的概念可能仍然对您有用,结合此处使用filter的其他答案

Check this out:看一下这个:

import { Component, OnInit } from "@angular/core";
import { fromEvent, Observable } from "rxjs";

@Component({
  selector: "my-app",
  templateUrl: "./app.component.html",
  styleUrls: ["./app.component.css"]
})
export class AppComponent implements OnInit {
  title = "my-app";
  pageClickObservable: Observable<Event> = fromEvent(document, "click");

  pause = false; // creating a variable to track the pause
  ngOnInit(): void {
    new Observable();

    this.pageClickObservable.pipe().subscribe(() => {
      this.pause = !this.pause; // change the pause status - true / false at alternative clicks
    });


 // mimicking observable sending data every second
    setInterval(() => {
      if (!this.pause) {
        // if not pause then execute the complete functions
        console.log("Click");
      } else {
        console.log("Do nothing");
      }
    }, 1000);
  }
}

This will simply console "Click" and "Do nothing" based on clicks.这将简单地控制基于点击的“点击”和“什么都不做”。

For example, you have an observable sending data at every second, let's say, setInterval例如,您有一个可观察的每秒发送数据,例如setInterval

If they share a common variable, such as this.pause in this case, then you can pause and resume easily.如果它们共享一个公共变量,例如本例中的this.pause ,那么您可以轻松地暂停和恢复。

What I've done is to create an operator that delays the value based on a numerical property value.我所做的是创建一个基于数值属性值延迟值的运算符。

delayByProperty.ts delayByProperty.ts

import {of, NEVER} from 'rxjs';
import {concatMap, delay} from 'rxjsoperators';

export function delayByProperty(propertyNameOfSecondsDelay:string) {
  return concatMap(obj => {
    const seconds:any = obj[propertyNameOfSecondsDelay];
    if (!seconds) {
      return of(obj);
    } else if (!Number.isFinite(seconds)) {
      throw new TypeError(`object property "${propertyNameOfSecondsDelay}" expected to be falsy or a number, was ${typeof(secondsDelay)} with value of ${secondsDelay}.`);
    } else if (seconds === -1) {
      return NEVER;
    } else if (seconds > 0) {
      return of(obj).pipe(
        delay(seconds * 1000)
      )
    }
  });
}

Now you can do现在你可以做

from([1,2,3,2,5,2,1]).pipe(
    map(n => {value:n, seconds:Number.parseFloat(n)}),
    delayByProperty('seconds')
)

When dealing with Observables, I've found it useful to think less like a programmer and more like a plumber.在处理 Observables 时,我发现少像程序员那样思考而更像管道工是很有用的。 :-) Consider this: :-) 考虑一下:

function getObservableOfMsgs(click$) {
    //Array of objects that have whatever values you want in them, plus a property of "seconds" that contains the number of seconds you want to delay.
    const objs = [{msg:'1', seconds:1},{msg:'2', seconds:2},{msg:'TWO', seconds:2}];
    // observable we want per click.
    const perClick$ = from(objs).pipe(
        // map each object into an observable that delays, then emits the msg... and combine all of those observables to happen simultaneously.
        mergeMap(({msg, seconds}) => of(msg).pipe(delay(seconds * 1000)))
    );

    return click$.pipe(
        mapTo(perClick$)
    );
}

and now you'll have an observable that emits a pattern of messages per time you click.现在您将拥有一个可观察对象,每次单击时都会发出一种消息模式。

So if you have a function called doSomethingWithMessage that takes a msg as a single argument and returned either null or a message you wanted to log as a debug message, you could do...因此,如果您有一个名为 doSomethingWithMessage 的 function 将 msg 作为单个参数并返回 null 或您想要作为调试消息记录的消息,您可以...

const unsubscribeFunctionInCaseINeedIt = getObservableOfMsgs(click$).pipe(
    tap(doSomethingWithMessage)
).subscribe(
    (d) => if (d !== null) console.debug(d),
    (e) => console.error(e),
    ()  => console.log('Complete!')
)

and then, until you call the returned function, it will all just "happen" as events stream through.然后,直到您调用返回的 function,这一切都将作为事件 stream “发生”。

from and of are part of rxjs, so you should be able to import {from, of} from 'rxjs' Hopefully this automatically resolves because rxjs is part of Angular. fromof是 rxjs 的一部分,所以你应该能够import {from, of} from 'rxjs'希望这会自动解决,因为 rxjs 是 ZC31C335EF37283C451B18BA0ZDD13 的一部分。 Likewise, you might also have to import {mergeMap, delay, mapTo, tap} from 'rxjs/operators' or something, but I'm unable to access StackBlitz to confirm for you.同样,您可能还必须import {mergeMap, delay, mapTo, tap} from 'rxjs/operators' ,但我无法访问 StackBlitz 为您确认。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM