Angular 4和RxJS 5:Observable.concat()意外行为

时间:2017-07-09 21:26:02

标签: angular typescript rxjs5

My 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()中的代码将会运行。

为什么我会看到错误?

2 个答案:

答案 0 :(得分:3)

问题是在编写this.build(elemTag) observable时调用concat - 而不是在执行连接时调用。{/ p>

您可以使用defer解决问题:

import 'rxjs/add/observable/defer';

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

或者,正如评论中指出的那样,使用map

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

答案 1 :(得分:-1)

这是因为,您创建了一个任何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
)