ローディングスピナーが想定されるタイミングで表示されない

困っていること

アプリ処理中にローディングスピナー表示を行いたくサンプルを作成したのですが、期待するタイミングでスピナーが表示されず困っています。
原因についてお心当たりのある方がいらっしゃればご意見いただけますと幸いです。

サンプルとしては
①スピナー表示処理呼び出し→②APIの呼び出し→③レスポンスに対する処理→④スピナー解除処理呼び出し
を行っています。
①の直後にスピナーが表示されることを期待しましたが、実際は②のsubscribeブロックに入ってから表示が行われました。
(Chromeの開発者ツールでデバッグにより処理を止めながら確認
Sourcesタブを開いて「Ctr + p」→sample.component.tsを検索して開き、ブレークポイントを打つ)

onClick() {
  // スピナーの表示
  this.service.show();
  this.response = ''; // <--- この処理からスピナーを表示したい

  // APIのモック
  const observable = of('hoge').pipe(
    // 2000ミリ秒処理を待機
    delay(2000)
  );

  // API呼び出し処理
  observable.subscribe((res) => {
    console.log(res); // <--- 実際はこの処理からスピナーが表示されている
    this.response = res;
    // スピナーの非表示
    this.service.hide();
  });
}

試したこと

this.service.show();の後にChangeDetectorRef.detectChanges();を実行すれば画面に反映されるかと思いましたが、状況変わらずでした。
また、this.service.show();以降の処理をsetTimeoutで囲めば期待する挙動となりますが、できるだけsetTimeoutは使いたくなく、他の方法で解決したいです。

サンプルアプリ

再現リポを確認しました。

クリックした直後にスピナーが表示され、2秒後に非表示になるので、
期待通りの挙動をされているように見えます。

もう一度確認していただけますか?


ChangeDetectionStrategy.OnPush にしてたのかな?」と思って、コードをOnPushにして確認してみましたが、
なぜか「Stackblitzの画面でsampleコンポーネントの中身が描写されない」という事象に当たりました。

Ctrl+s後、ブラウザのリロードでivyの再コンパイルが走り表示されるようになったため、
Stackblitz側のなにかしらのバグがもしれません。

masashiyoshiwaraさんももしかしたら、
こういったStackblitzの不安定な挙動で再現がうまくいっていなかったのかな?と少し思いました。

akaiさん

コメントありがとうございます。
クリックした直後にスピナーが表示され、2秒後に非表示になるという挙動はこちらでも確認できております。
ただし、スピナーの表示タイミングとして以下の行から表示されることを期待しておりますが、実際はsubscribe内の処理に入ったタイミングで表示されており、その原因、解決法について知りたいという状況です。

this.response = ''; // <--- この処理からスピナーを表示したい

再度確認したのですが、やはり状況は変わらずでした。
(subscribe内の処理に入ったタイミングで表示されている)

うーむ……????

ただし、スピナーの表示タイミングとして以下の行から表示されることを期待しておりますが、実際はsubscribe内の処理に入ったタイミングで表示されており、その原因、解決法について知りたいという状況です。

subscribeのタイミングでスピナーが表示されている、とおっしゃっていますが、
この再現リポではそのようなことはないように見えます。

subscribeのない、次のコードでもちゃんとスピナーは表示されております。

  onClick() {
    // スピナーの表示
    this.service.show();
    this.response = ''; // <--- この処理からスピナーを表示したい
  }

となると、どこに問題があるとおっしゃっているのかよくわからないですね。


もしかして「(Stackblitzではちゃんと動くけど、)実際(の自分のプロジェクトで)はsubscribe内で表示されてしまう」という話ですか……?(主語が違う?)

(Chromeの開発者ツールでデバッグにより処理を止めながら確認
Sourcesタブを開いて「Ctr + p」→sample.component.tsを検索して開き、ブレークポイントを打つ)

show直後にブレークポイントを置いて動作を止めながら再確認したところ、
確かにその時点ではasyncパイプに値が反映されないことを確認できました。

①の直後にスピナーが表示されることを期待しましたが、実際は②のsubscribeブロックに入ってから表示が行われました。

おそらくコレは勘違いで、スピナーの表示はsubscribeブロックに入ってから行われているのではなく、
onClick関数の終了時にasyncパイプの計算が行われて、はじめて表示されるようです。
これは、ChangeDetectorRefのmarkForCheckやdetectChgangesを用いても変わらないようです。

  • a) asyncパイプの表示処理コンテキストとonClick関数の処理コンテキストが別であるため、シングルスレッドのjsではそれぞれが逐次実行されていく
  • b) 変数が変わるたびにビューに反映すると負荷すごいことになるしパフォーマンス上、変更検知の設計からしてそうなってる

みたいなイメージかな〜と想像しました。(多分bかな?)

ちなみに、非同期の場合は関数の終了を待たずにビューに反映されるようです。
(何がトリガーになってるんでしょうかね?)


前述a,bが正しいのかはわかりませんが、この仕様で実際どういった問題が出ているのか、
もうちょっと掘り下げたら他の解決策が見つかるかもしれません。

「いいね!」 1

akaiさん
コメントありがとうございます。
また反応遅れまして申し訳ありません。

現状の動きについて理解できました。
ご丁寧な解説ありがとうございました。

一旦解決法としてはAngular CDK のOverlayRefを使ったローディングスピナー表示をすることで画面に即時表示ができるようになりましたので情報共有致します。

修正後アプリ

「いいね!」 1