🙆

Ionic Angularで、ViewDidEnterをSignalに変換する方法

2025/04/12に公開

Angularの、Signal-Based Reactivity最高!!と言い続けてるんですが、Signal-Based Reactivityってライフサイクルハックと相性よくないんですよね。特に、Ionicは、画面レンダリングとライフサイクルハックが紐ついていて(※ ionViewWillEnter 等)、画面レンダリングのタイミングを無視すると以下みたいなことが起きます。

  • プッシュ遷移前に多くのデータをレンダリングする
  • 遷移アニメーションがはじまる
  • メモリリークしてアプリが落ちる

ドキュメントでも書かれてる内容ですね。

ただし、アニメーション中にデータを取得すると、大量の DOM 操作が開始される可能性があります。これにより、ぎこちないアニメーションが発生する可能性があります。
https://ionicframework.jp/docs/angular/lifecycle/

この単純な解決方法は、ngOnInitionViewWillEnter でレンダリングを行う処理をするのではなく、 ionViewDidEnter でレンダリングを開始することです。なので、パフォーマンスを気にしたClassではライフサイクル毎に何の処理を行うかが重要になってきます。これは今も変わりません。

一方で、Signal-Basedでの開発はどうなるか。そう、computed も effecthttpResource も、内部で使っているSignalの変更を検知して処理を開始します。もちろん以下のようにページコンポーネント内のライフサイクルでSignalを書き換えることで、ライフサイクルをSignal上に乗せることも可能です。

readonly didViewEnter = signal<boolean>(false);

ionViewWillEnter() {
  this.didViewEnter.set(false);
}

ionViewDidEnter() {
  this.didViewEnter.set(true);
}

なんですが、「すべてのページコンポーネントにこれを書いてメンテするの?正気?!」ってなりますよね。私はなりました。ですので、もうちょっとシンプルにIonicのライフサイクルをSignalに置き換える方法を考えてみましょう。

まず、こういう関数を用意します。

const createDidEnter = <T = any>(el: ElementRef) => {
  return new Observable(observer => {
    const willEnter = () => observer.next(false);
    const didEnter = () => observer.next(true);
    
    el.nativeElement.addEventListener('ionViewWillEnter',willEnter);
    el.nativeElement.addEventListener('ionViewDidEnter', didEnter);

    return () => {
      el.nativeElement.removeEventListener('ionViewWillEnter',willEnter);
      el.nativeElement.removeEventListener('ionViewDidEnter', didEnter);
    }
  }).pipe(startWith(false));
}

Ionicのライフサイクルハックって、ページコンポーネントのEventListenerでも発火します(Angularのライフサイクルはしません)。ですので、これをベースにObservableで ionViewWillEnter 時は falseionViewDidEnter 時は true が返ってくるObservableを用意します。そして、これをページコンポーネントでSignalに変換。

readonly el = inject(ElementRef);
readonly didEnter = toSignal(createDidEnter(this.el));

これで、Ionicのライフサイクルベースで didEnter プロパティを得ることができるようになりました。これで、以下のように書くことができます。

readonly data = httpResource<data[]>(() => {
    const params = new HttpParams().append('search', searchWord());
    return !this.didEnter() ? undefined : environment.api + 'manager/movies?' + params.toString();
});

当然、isReady がfalseの時は空の値が返ってきて、trueの時はリクエストが行われます。これでSignalベースのIonicアプリ開発が更にはかどりますね!!

Discussion