【问题标题】:The Async pipe doesn't receive a value until I subscribe to the observable在我订阅 observable 之前,异步管道不会收到值
【发布时间】:2019-06-11 14:03:16
【问题描述】:

我遇到了一个奇怪的情况,当我订阅 observable 之前异步管道无法正常工作。资料来源:

组件视图:

<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))
  );
}

我看到data 在 2 秒延迟后按预期显示,但在此延迟期间我没有看到微调器。但是,如果我取消注释 this.data$.subscribe(e =&gt; {}); 行,它就会开始正常工作。而且我找不到原因。有什么想法吗?

注意: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))
  );
}

【问题讨论】:

  • 为什么用 Null 进行初始化?这不是它应该被使用的方式
  • 您可以尝试仅打印{{ loading$ | async }} 吗?因为即使使用onPush,您所拥有的也应该可以工作。
  • @theMayer 因为页面加载后我没有数据。它应该从 API 请求。为了简单起见,我用计时器代替了它
  • @martin 相同-我用&lt;h1&gt;{{loading$ | async}}&lt;/h1&gt; 替换了一个微调器,它在没有订阅的情况下一直显示为假,并且在订阅时按预期工作-首先它显示true,然后-延迟后@ 987654335@.
  • 布尔值在逻辑上只能是真或假。我建议用一个值来初始化它。

标签: angular rxjs


【解决方案1】:

好的,在查看了您的代码后,我想我理解了这个问题。您没有看到通过在 DOM 中拾取的 loading$ observable 发出的 true 的更新值。

这是您的代码所说的将发生的情况:

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

  2. Angular 模板将呈现并通过更改检测。它将为loading 值拾取false,为data 拾取null。加载微调器不会旋转。

  3. 在更改检测周期的后期,async 管道将触发由loading 发出的true 值。这是同步发生的;由于更改检测设置为OnPush,我怀疑此更改将被错过,因为在设置值时已经有一个更改检测周期正在进行中(通常,这可能会被常规更改检测策略捕获并提升为一个错误)。

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

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

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

【讨论】:

  • 我非常沮丧。我在 stackblitz 上使用完全相同的代码创建了项目,并且运行良好。但在本地却没有。 stackblitz.com/edit/angular-wutfat
  • 顺便说一句:将初始值更改为true 有效。使用默认更改策略并不能解决问题
  • 默认更改策略仅在您没有使用推送策略的父组件时才有效。它适用于 Stackblitz,因为您没有覆盖那里的变更检测策略。
  • 看看 Stackblitz 上的控制台。你会看到ERROR Error: ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked. Previous value: 'ngIf: [object Object]'. Current value: 'ngIf: true'.
  • 看起来问题描述在下面的文章中:blog.angular-university.io/angular-debugging
【解决方案2】:

你需要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>

【讨论】:

  • 这如何解决最初的问题?
  • 我只是相信答案至少应该尝试解决原始问题。这个没有。不仅没有提供解释,甚至没有尝试使用问题中的原始代码。
猜你喜欢
  • 1970-01-01
  • 2020-11-13
  • 2021-11-04
  • 2017-11-30
  • 2021-03-10
  • 1970-01-01
  • 2018-03-13
  • 2019-05-06
  • 2017-02-14
相关资源
最近更新 更多