RxJS:让 Observable 仅在前一个 Observable 完成后才发出值

RxJS: Have Observables only emit values after the previous Observable has completed

我目前正在尝试实现 Angular 结构指令,它将读取和存储 HTML 元素(及其所有子元素)的文本内容,在 AfterViewInit 上删除其内容, 并延迟重新添加内容,一次一个字符,使其看起来像是实时输入的。

我通过分析目标节点的 NodeType 来做到这一点。如果是文本,它会将数据内容备份到一个数组中,然后继续处理下一个元素。如果它是一个不同的节点,它也会递归地分析该节点。这样,我还可以修改目标元素的另一个 HTML 元素中包含的文本。

为了将文本写回节点,我使用了 Observables。每个节点的原始值一次发出一个字符,每个字符之间有 60 毫秒的延迟,将其重新添加到原始节点的内容以模拟键盘输入延迟。在我的 anaylze 方法中,我还收集了前一个节点内容的长度,将 Observable 延迟了该数字 * 60 毫秒。我的意图是让下一个 Observable 仅在前一个 Observable 完成后才发出它的值。

然而(可能是由于 Observable 的异步性质)通常一个 Observable 会在前一个 Observable 完全完成之前开始发射值。这有时会让页面看起来好像一次添加了不止一行,这是我试图避免的一种行为。

这是我的 Observable 的样子:

from(this.mementos)
  .pipe(
    concatMap(
      memento => of(memento).pipe(delay(memento.previousNodeLength * 60)) // delay Observable by the time it takes to complete the previous node
    )
  )
  .subscribe(memento =>
    from(Array.from(memento.data))
      .pipe(concatMap(text => of(text).pipe(delay(60)))) // delay each character by 60ms
      .subscribe(c => {
        memento.node.data += c;
      })
  );

展示问题的完整工作 Stackblitz: Stackblitz Example

如何修改我的代码以便一次只添加一个节点的内容?

n * 60 计算延迟会给你准确的数字,但是如果用 n 模拟相同的延迟 setTimeout()s 它不会匹配,因为 setTimeout() 不能保证确切超时。

因此,您可以移除嵌套的 Observable 并创建一个链,使用 concatMap 将首先获取每个纪念品,然后是每个字符。

from(this.mementos)
  .pipe(
    concatMap(memento => from(memento.data).pipe( // each memento
      concatMap(char => of({ node: memento.node, char }).pipe(delay(60))), // each char
    ))
  )
  .subscribe(({ node, char }) => {
    node.data += char;
  });

只有当前一个备忘录完成时,才会开始处理下一个备忘录。每个字符都在嵌套的 Observable 中延迟。

现场演示:https://stackblitz.com/edit/angular-ivy-njfysl?file=src/app/typewriter.directive.ts