【问题标题】:Angular 4 and RxJS 5: Observable.concat() behaving unexpectedlyAngular 4 和 RxJS 5:Observable.concat() 行为异常
【发布时间】:2017-07-09 21:26:02
【问题描述】:

我的 Angular 4 / TypeScript 2.3 服务有一个函数build(),如果类属性未初始化,则会出错。我正在尝试构建一个更安全的版本——safeBuild()——它将返回一个 Observable,它会在尝试调用 build() 之前等待并监听要初始化的属性。

export class BuildService {
  
  renderer:Renderer2; // must be set for build() below to work
  
  // emits the new Renderer2 when renderer is set
  private rendererSet$:BehaviorSubject<Renderer2> = new BehaviorSubject(null);

  /** Set renderer, and notify any listener */
  setRenderer(renderer:Renderer2){
    this.renderer = renderer;
    this.rendererSet$.next(renderer);
  }

  /** Returns a new DOM element. Requires renderer to be set */
  build(elemTag:string){
    // if renderer is not set, we can't proceed
    // why is this error thrown when safeBuild() is called?
    if (!this.renderer) 
      throw new Error('Renderer must be set before build() is run');

    return this.renderer.createElement(elemTag);
  }

  /**
   * A safe version of build(). Will wait until renderer is set
   * before attempting to call build (Asynchronous)
   */
  safeBuild(elemTag:string):Observable<any> {
    // inform user that renderer should be set
    // this warning is printed to the console as expected
    if (!this.renderer) 
      console.warn('The build will be delayed until setRenderer() is called');

    // Listen to rendererSet$, filter out the null output, and call build()
    // only once the renderer is set. Why does the error still get thrown?
    return Observable.concat(
      this.rendererSet$.filter(e=>!!e).take(1),
      Observable.of(this.build(elemTag))
    )
  }
}

我尝试像这样构建(从另一个服务):

this.buildService.safeBuild(elemTag).subscribe(...)

在我看到的控制台中:

警告:构建将延迟到 setRenderer() 被调用

错误:必须在 build() 运行之前设置渲染器

我预计会收到警告,但在我的应用程序的另一部分调用 setRenderer() 之前什么都不会发生。此时,subscribe() 中的代码将运行。

为什么我会看到错误?

【问题讨论】:

  • 什么时候调用 setRenderer?
  • @BunyaminCoskuner 稍后 -- 在safeBuild() 之后,来自另一个组件。
  • Observable.of(this.build(elemTag)) 需要调用 this.build 来创建 observable,这发生在 将其传递给 .concat 之前。
  • 为什么不只是return this.rendererSet$.filter(e =&gt; !!e).map(() =&gt; this.build());
  • @jonrsharpe 接近我自己找到的解决方案。我保留了take(1),因为我不希望取消订阅的负担在调用代码上,我使用了switchMap(r =&gt; Observable.of(this.build()))。它可以工作,但map() 更干净。我仍然想知道为什么会抛出错误。

标签: angular typescript rxjs5


【解决方案1】:

问题是this.build(elemTag) 在编写concat observable 时被调用——而不是在执行连接时。

你可以使用defer解决问题:

import 'rxjs/add/observable/defer';

...
return Observable.concat(
  this.rendererSet$.filter(e => !!e).take(1),
  Observable.defer(() => Observable.of(this.build(elemTag)))
);

或者,正如 cmets 中所指出的,使用 map

return this.rendererSet$
  .filter(e => !!e)
  .take(1)
  .map(() => this.build(elemTag));

【讨论】:

  • 不知道.defer()。谢谢,我查一下。我想我会使用mapTo(),因为除了知道 Renderer 已经设置之外,我不关心第一个 observable 的输出。您的回答也是第一个让我明白为什么会抛出错误的答案。
  • 我看到你已经编辑了你的答案。 .mapTo(this.build()) 抛出错误。我想它会立即执行。 .map() 是。
  • 是的。我没想到这一点。
【解决方案2】:

这是因为,无论this.build 函数返回什么,您都创建了一个 Observable。由于您尚未设置渲染器,因此下面的行会引发错误。确保你 先调用setRenderer函数

if (!this.renderer) 
    throw new Error('Renderer must be set before build() is run');

你应该可以通过返回 Observable 来解决这个问题,如下所示

return Observable.concat(
  this.rendererSet$.filter(e=>!!e).take(1),
  this.rendererSet$.asObservable().map(() => this.build(elemTag)) // this line will execute when there is a new value set to rendererSet
)

【讨论】:

  • “确保首先调用 setRenderer 函数” 并不是非常有用的建议,而这正是 OP 试图 做的事情。
  • 这可以解释为什么直接调用build() 会出错。但是safeBuild() 试图通过在调用build() 之前等待setRenderer() 来解决这个问题。我的问题是,为什么运行safeBuild() 也会出错?
  • 他说`后来——在safeBuild()之后,从另一个组件。`。我是说他应该先打电话。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2018-02-01
  • 1970-01-01
  • 1970-01-01
  • 2018-05-23
  • 2021-10-21
  • 2018-02-08
  • 1970-01-01
相关资源
最近更新 更多