detectChangesを呼んでもChange Detectionが動かない

マウスイベント(mousedown/mousemove/mouseup)を使ってD&Dのような操作を実現しようとしています。

mousedown後にのみ、mousemoveが発火してほしいため、NgZone.runOutsideAngular()の中で、mousemoveイベントをaddEventListerし、mouseup時にremoveEventListenerしています。

mousemove時にChange Detectionが動いてほしいため、detectChanges()を呼び出しているのですが、なぜか動かず困っています。(ngDoCheck()が呼ばれてない)

原因や解決方法をご存知の方がおられましたら、ご教授いただけないでしょうか。

他の都合により、draggable要素にすることはできず、dragStart/dragOver/dragEnd等のイベントを使うことはできません。

2 Likes

解決策としては、mouseMoveコールバック関数の中身を NgZone.run でラップすることで期待した挙動になるんじゃないかと思います。

  mouseMove = () => {
    this.ngZone.run(() => {
      console.log('mouseMove fired');
      this.cd.detectChanges();
    });
  };

Change Detection自体はトリガーされているようなのですが、DoCheckは呼び出されていないようで不思議ですね。これは推測ですがDoCheckを呼び出すのはAngular Zoneのmicrotaskキューが空になるのを待っている気がするので、runOutsideAngular内のイベントハンドラーはAngular Zoneには乗らずDoCheckが呼ばれないのかな、と思いました。

こういったケースでは、コンソールログでそのタイミングでのZoneがどのZoneなのかを調べると原因がわかりやすい事が多いです

console.log('mouseMove fired: ', (window as any).Zone.current.name);

もし補足があれば @JiaLiPassion さんにお願いしたいです

ご回答ありがとうございます。
教えていただいた方法で実現できそうです。

Change Detectionがトリガーされる=ngDoCheckが呼ばれる、だと思ってましたが、そうとは限らないんですね。あと、ZoneもAngular Zoneだけだと思ってたので、とても勉強になりました。

ちなみにですが、NgZone.runでラップしない場合、Change Detection自体は動くため、DoCheckを使わない場合はそれでも問題なさそうですが、やはりできる限りAngular Zoneで動かすようにした方がよいのでしょうか?

1 Likes

Change Detectionがトリガーされる と書きましたがあまり確証はないので、基本的には Angular Zoneに戻ってから detectChangesするのが無難かなと思います

1 Likes

これが確かにAPIとして開発者に親切ではないです。今のコードが開発者としては正しいと思いますが、動かない原因がDoCheckなどのHookが自分のComponenntのChangeDetectionが実行されているとき実行ではなく、親のChangeDetectionが走るとき実行されます。
実際はAppComponentはAngular ViewTreeのRootではなく、さらに隠したHost Viewという親が存在します。
なので、サンプルのコードを動かすため、一番簡単なやり方が、changeDetectorRef.detectChanges() をapplicationRef.tick()に変更すること、angular-vrf5zn - StackBlitz

今AngularのBehaviorがやはりちょっとおかしいですが、これからAngularでこの動き(HostViewが無くなること)を変わる可能性が高いと思いますので、とりあえずHostView依存のコードを書かない方がいいかもしれません。

1 Likes

ご回答ありがとうございます。

整理すると、runOutsideAngular内のイベントハンドラでChangeDetectionをトリガーしたい場合は、detectChanges()ではなく、applicationRef.tick()の方がよいということですね。

勉強になりました!