【问题标题】:RXJS way to unsubscribe after button click but with opportunity to subscribe againRXJS 方式在按钮单击后取消订阅但有机会再次订阅
【发布时间】:2026-02-06 14:10:01
【问题描述】:
import { reduce, filter, map, mapTo } from 'rxjs/operators'
import { from, fromEvent, scan, Subscription } from 'rxjs'

const button1 = document.getElementById("but1")
const button2 = document.getElementById("but2")
const button3 = document.getElementById("but3")



let click1 = fromEvent(button1, 'click')
.subscribe(result => {
    count3.subscribe(x => p3.innerHTML = `Click count: ${x}`)
})


let click2 = fromEvent(button2, 'click')

在这里我想取消订阅(当我点击第二个按钮时,但当我点击第一个按钮时有机会再次订阅)

我试过这样的东西

const sub = click1.subscribe(result => {
        count3.subscribe(x => p3.innerHTML = `Click count: ${x}`)
    })


let click2 = fromEvent(button2, 'click')
    sub.unsubscribe()

但我不知道如何将 sub.unsubscribe() 放入按钮

let click3 = fromEvent(button3, 'click')

let three = click3.pipe(mapTo(1))

let c: number = 0

let count3 = three.pipe(scan((acc, val) => acc + val, c))

const d = document.getElementById("d")
const p3 = document.createElement("p")


//count3.subscribe(x => p3.innerHTML = `Click count: ${x}`)

d?.appendChild(p3)

【问题讨论】:

  • 您是真的要销毁订阅,还是只想让订阅停止产生点击?那里有一个区别,如果您只是想控制流程,那么将 observables 组合在一起可能更有意义。
  • 停止发出点击。

标签: javascript html rxjs


【解决方案1】:

我们可以在第二次点击按钮时取消订阅,然后当再次点击第一个按钮时,我们需要清除所有已经存在的订阅,然后我们重新初始化订阅,最后我们再次订阅。

如果您发现这种困惑,请告诉我,我会澄清您的疑问。

不确定您要实现什么,但请使用以下示例并将其修改为您的用例。

import { mapTo } from 'rxjs/operators';
import { fromEvent, scan, Subscription } from 'rxjs';

const button1 = document.getElementById('but1');
const button2 = document.getElementById('but2');
const button3 = document.getElementById('but3');
let clickCountSubscription = new Subscription();
let interimValue = 0;
const getCount3 = () => {
  let click3 = fromEvent(button3, 'click');
  let three = click3.pipe(mapTo(1));
  return three.pipe(
    scan((acc, val) => {
      interimValue = acc + val;
      return acc + val;
    }, interimValue)
  );
};
let click1 = fromEvent(button1, 'click').subscribe((result) => {
  clickCountSubscription.unsubscribe();
  clickCountSubscription = new Subscription();
  clickCountSubscription.add(
    getCount3().subscribe((x) => (p3.innerHTML = `Click count: ${x}`))
  );
});

let click2 = fromEvent(button2, 'click').subscribe(() => {
  clickCountSubscription.unsubscribe();
});
const d = document.getElementById('d');
const p3 = document.createElement('p');

d?.appendChild(p3);

stackblitz

【讨论】:

  • 是的,这就是我要找的。非常感谢你。我想在第一个按钮单击时开始计数,并在单击第二个按钮时停止此计数。但是计数被指定到第三个按钮。
  • @MatthewKna 不客气 :)
【解决方案2】:

注意:此答案基于主要问题的 cmets 中的对话框。从字面上看,这是一个比 Narem Murali 更糟糕的解决方案

要以更被动的方式执行此操作,您可以仅使用一个订阅将多个流组合在一起。这将使订阅管理更简单并有助于嵌套,但会涉及更多流逻辑和 rxjs 运算符。

import { map, fromEvent } from 'rxjs';
import { filter, mergeWith, withLatestFrom } from 'rxjs/operators';

// Get the buttons
const startBtn = document.getElementById('start');
const stopBtn = document.getElementById('stop');
const countBtn = document.getElementById('count');

// Setup base input streams (could be done later separated here for clarity)
const start$ = fromEvent(startBtn, 'click').pipe(map(() => true));
const stop$ = fromEvent(stopBtn, 'click').pipe(map(() => false));

// Merge base input streams so that you only get one output
const shouldTake$ = start$.pipe(mergeWith(stop$));

const count$ = fromEvent(countBtn, 'click').pipe(
  // listen for the count click
  withLatestFrom(shouldTake$), // determine what the latest value from the merged stream
  map(([_, shouldTake]) => shouldTake), // ignore the actual click event and just use shouldTake
  filter((shouldTake) => shouldTake) // Stop the emission if you shouldn't take it
);

// Get the counter span
const counterSpan = document.getElementById('current-count');

let count = 0;

// Actually subscribe to count and do what you care about when you care
count$.subscribe(() => {
  count++;
  counterSpan.innerText = count.toString(10);
});

要在工作中看到这个,请查看这个 stackblitz https://stackblitz.com/edit/rxjs-zcbaxz?file=index.ts

【讨论】:

  • 谢谢我刚开始学习 rxjs/angular,我已经尝试了一些学习任务,但不知道解决这个问题的大多数方法。