asyncパイプがネストされている場合、想定通り動かない

困っていること

以下のようにasyncパイプがネストされている場合に、外側のasyncパイプの値(itemListExist )がtrueになっても内側のasyncパイプのリスト(itemList )が表示されず困っています。
解決方法をご存知の方いらっしゃればご教示いただきたく存じます。

<div *ngIf="itemListExist | async">
  <p>Food</p>
  <ul>
    <li *ngFor="let item of itemList | async">{{ item }}</li>
  </ul>
</div>

以下サンプルアプリケーションになります。

「いいね!」 1

解決方法はいくつかありますが、まず原因を解説しますね。

この挙動の前提となるのは3つの点があります。
第一に、ItemComponentのテンプレートで入れ子になっている内側は、ngIfがtrueになるまでビューインスタンスが生成されていません。つまり、ngIfがtrueになったあとで内側のAsyncPipeがインスタンス化され、itemListの購読を始めるということです。

第二に、itemListSubjectであるということは購読を始めたあとでnext()された値だけが購読対象ということです。

第三に、AsyncPipeはストリームから新しい値を受け取ったあと、即時にテンプレートに反映させるのではなく、変更検知フラグを立てるだけで反映は非同期であるということです。

ボタンを押すと itemListExistitemListの両方に新しい値が流れますが、このとき購読が始まっているのはngIfの外側にある itemListExist | async だけです。その評価結果がtrueなので内側のテンプレートが生成されますが、ここで初めて itemList | async が購読を開始します(第一)。
購読を開始しますが、ストリームがSubject なのでこの時点では何の値も流れてきません。
次に値が流れてきたときに初めてitemList | asyncが機能し、リストが表示されます。(第二)

これはItemService内でのnext()の順番に関係なく発生しますが、それは第三の点のせいです。
2つのnext()は同期的に連続していますが、AsyncPipeは変更検知を要求するフラグを立てるだけで即座には反映しません。2つのnext()の結果実際に発生する変更検知は1回にまとめられます。

以上の流れで、1回のボタンクリックでは内側のアイテムリストは表示されません。

解決策

いろいろ要因が絡んでいるのでどれかひとつでも解決すれば表示されるようになります。

  1. itemListBehaviorSubjectReplaySubjectのように「すでに流れた値」を扱えるストリームにすると、内側のAsyncPipeが購読開始した時点で値があるのでリストが表示されます。
  1. AsyncPipeがNgIfの内側にある(false状態では購読されない)のを解消し、外側で購読してしまうのも手です。その際2つのストリームを1つのストリームに合流させると取り回しやすくなります
<ng-container *ngIf="{ itemList: itemList | async, itemListExist: itemListExist | async } as state">
<div *ngIf="state.itemListExist">
  <p>Food</p>
  <ul>
    <li *ngFor="let item of state.itemList">{{ item }}</li>
  </ul>
</div>

参考: 第2章 Effective RxJS - コンポーネントにおけるObservableの購読

  1. おすすめはしませんが、2つのnext()の呼び出しを非同期的にすればそれぞれのnext()が1回ずつ変更検知をトリガーするため、リストが表示されるようになります。
  setItemList(itemList: string[]) {
    if (itemList.length > 0) {
      this.itemListExist.next(true);
      // タイミングをずらす
      setTimeout(() => {
        this.itemList.next(itemList);
      });
    }
  }
「いいね!」 2

lacolacoさん

コメントありがとうございます。
教えていただいた方法で解決できることをこちらでも確認できました。
わかりやすい解説までしていただき非常に助かりました。