直到我订阅了可观察对象,异步管道才收到值

时间:2019-06-11 14:03:16

标签: angular rxjs

当我订阅可观察对象之前,异步管道无法正常工作时,我遇到了一个奇怪的情况。来源:

该组件的视图:

<div *ngIf="loading$ | async" fxLayout="row" fxLayoutAlign="center">
    <span class="spinner">Loading...</span>
</div>

<h1>{{data$ | async}}</h1>

组件:

@Component({
  selector: './tm-fake',
  changeDetection: ChangeDetectionStrategy.OnPush,
  templateUrl: './fake.component.html'
})
export class FakeComponent implements OnInit {
  constructor(
    private _fs: FakeService
  ) {}

  loading$ = new BehaviorSubject<boolean>(false);
  data$ = this._fs.data$.pipe(indicateUntilNULL(this.loading$));

  ngOnInit(): void {
    this._fs.requestData();

    //this.data$.subscribe(e => {});
  }
}

服务:

@Injectable()
export class FakeService {
  requestData(): void {
    timer(2000).pipe(map(e => "data")).subscribe(e => this._data.next(e));
  }

  private readonly _data = new BehaviorSubject<string>(null);
  readonly data$ = this._data.asObservable();
}

操作员:

export function indicateUntilNULL<T>(indicator: BehaviorSubject<boolean>): (source: Observable<T>) => Observable<T> {
  return source => source.pipe(
    tap(e => indicator.next(!e))
  );
}

我期望延迟2秒后看到data,但是在此延迟期间我看不到旋转器。但是,如果我取消注释行this.data$.subscribe(e => {});,它将开始正常工作。而且我找不到原因。有什么想法吗?

NB:loading$发出正确的值,即使此行被注释。

解决方案

非常感谢@theMayer及其有用的建议。我只想添加article,其中详细说明了问题。根据本文,最优雅的解决方案是delay(0)运算符:

export function indicateUntilNULL<T>(indicator: BehaviorSubject<boolean>): (source: Observable<T>) => Observable<T> {
  return source => source.pipe(
    delay(0),
    tap(e => indicator.next(!e))
  );
}

2 个答案:

答案 0 :(得分:0)

好的,在检查完您的代码后,我认为我理解了这个问题。您不会在DOM中看到通过true观察到的loading$发出的更新值。

这是您的代码将要发生的事情:

  1. 将构造对象,并将行为对象_data初始化为空值。

  2. Angular模板将在其中渲染并进行更改检测。它将为false的值拾取loading,为null的值data。载入中的微调器将不会旋转。

  3. 在更改检测周期之后,async管道将触发true发出的loading值。这是同步发生的。由于更改检测设置为OnPush,因此我怀疑此更改会被遗漏,因为设置该值时已经存在一个更改检测周期(通常,常规更改检测策略可能会捕获此更改,并且引发错误)。

  4. 然后您的data在两秒钟的延迟后发出。

请注意,使用常规更改检测时,这种情况将导致ExpressionChangedAfterItHasBeenCheckedError。正确的方法是在更改检测周期之前确保组件的内部一致性,而不是在更改检测期间执行触发进一步更改的操作。

快速的答案是将“ loading”值初始设置为true,因为代码在初始化时将始终尝试加载数据。

答案 1 :(得分:-1)

您需要share(),这是我的工作示例:

public $route!: Observable<{}>;

constructor(
    private route: ActivatedRoute
) { }

public ngOnInit(): void {
    this.$route = this.route.data.pipe(share());
}

然后在视图中

<ng-container *ngIf="$route | async as route; else loading">
    ...
</ng-container>