「労働者が自分の仕事をうまくやりたいなら、まず自分の道具を研ぎ澄まさなければなりません。」 - 孔子、「論語。陸霊公」
表紙 > プログラミング > Angular LAB: 可視性ディレクティブを作成しましょう

Angular LAB: 可視性ディレクティブを作成しましょう

2024 年 11 月 3 日に公開
ブラウズ:218

Angular LAB: let

この記事では、要素の可視性の状態、つまり要素がいつ出入りするかを追跡する非常に単純な Angular ディレクティブを作成する方法を説明します。ビューポート。これが素晴らしい、おそらく役立つ演習になることを願っています!

これを行うために、最新のブラウザで利用できる IntersectionObserver JavaScript API を使用します。

私たちが達成したいこと

ディレクティブを次のように使用したいと思います:

I'm being observed! Can you see me yet?

  • 可視性はカスタム ディレクティブのセレクターです
  • VisibilityMonitor は、要素を監視し続けるかどうかを指定するオプションの入力です (false の場合、要素がビューポートに入ったときに監視を停止します)
  • 可視性の変更は私たちに通知します

出力は次の形式になります:

type VisibilityChange =
  | {
      isVisible: true;
      target: HTMLElement;
    }
  | {
      isVisible: false;
      target: HTMLElement | undefined;
    };

ターゲットが未定義であるということは、要素が (@if などによって) DOM から削除されたことを意味します。

指令の作成

私たちのディレクティブは単に要素を監視するだけであり、DOM 構造は変更しません。これは 属性ディレクティブ.
になります。

@Directive({
  selector: "[visibility]",
  standalone: true
})
export class VisibilityDirective implements OnInit, OnChanges, AfterViewInit, OnDestroy {
  private element = inject(ElementRef);

  /**
   * Emits after the view is initialized.
   */
  private afterViewInit$ = new Subject();

  /**
   * The IntersectionObserver for this element.
   */
  private observer: IntersectionObserver | undefined;

  /**
   * Last known visibility for this element.
   * Initially, we don't know.
   */
  private isVisible: boolean = undefined;

  /**
   * If false, once the element becomes visible there will be one emission and then nothing.
   * If true, the directive continuously listens to the element and emits whenever it becomes visible or not visible.
   */
  visibilityMonitor = input(false);

  /**
   * Notifies the listener when the element has become visible.
   * If "visibilityMonitor" is true, it continuously notifies the listener when the element goes in/out of view.
   */
  visibilityChange = output();
}

上記のコードには次のように表示されます:

  • 先ほど説明した入力と出力
  • afterViewInit$ (Observable) というプロパティ。ngAfterViewInit ライフサイクル フックに対するリアクティブな対応物として機能します。
  • 要素の監視を担当する IntersectionObserver を格納する Observer と呼ばれるプロパティ
  • isVisibile というプロパティ。同じ状態が 2 回続けて再発行されることを避けるために、最後の可視性状態を保存します。

そして当然のことながら、ディレクティブを適用する DOM 要素を取得するために ElementRef を挿入します。

main メソッドを記述する前に、ディレクティブのライフサイクルに注意しましょう。

ngOnInit(): void {
  this.reconnectObserver();
}

ngOnChanges(): void {
  this.reconnectObserver();
}

ngAfterViewInit(): void {
  this.afterViewInit$.next();
}

ngOnDestroy(): void {
  // Disconnect and if visibilityMonitor is true, notify the listener
  this.disconnectObserver();
  if (this.visibilityMonitor) {
    this.visibilityChange.emit({
      isVisible: false,
      target: undefined
    });
  }
}

private reconnectObserver(): void {}
private disconnectObserver(): void {}

これで何が起こります:

  • ngOnInit と ngOnChanges の両方の内部でオブザーバーを再起動します。これはディレクティブをリアクティブにするためです。入力が変更されると、ディレクティブは異なる動作を開始します。 ngOnChanges も ngOnInit の前に実行される場合でも、ngOnInit が必要であることに注意してください。テンプレートに入力がない場合、ngOnChanges は実行されないためです。
  • ビューが初期化されると、件名がトリガーされます。数秒以内にこれに到達します
  • メモリリークを避けるために、ディレクティブが破棄されるとオブザーバーを切断します。最後に、開発者が要求した場合は、未定義の要素を発行することで要素が DOM から削除されたことを通知します。

交差点観察者

これが私たちの指令の核心です。 reconnectObserver メソッドが観察を開始するメソッドになります。次のようになります:

private reconnectObserver(): void {
    // Disconnect an existing observer
    this.disconnectObserver();
    // Sets up a new observer
    this.observer = new IntersectionObserver((entries, observer) => {
      entries.forEach(entry => {
        const { isIntersecting: isVisible, target } = entry;
        const hasChangedVisibility = isVisible !== this.isVisible;
        const shouldEmit = isVisible || (!isVisible && this.visibilityMonitor);
        if (hasChangedVisibility && shouldEmit) {
          this.visibilityChange.emit({
            isVisible,
            target: target as HTMLElement
          });
          this.isVisible = isVisible;
        }
        // If visilibilyMonitor is false, once the element is visible we stop.
        if (isVisible && !this.visibilityMonitor) {
          observer.disconnect();
        }
      });
    });
    // Start observing once the view is initialized
    this.afterViewInit$.subscribe(() => {
        this.observer?.observe(this.element.nativeElement);
    });
  }

信じてください、思っているほど複雑ではありません!メカニズムは次のとおりです:

  • まず、オブザーバーがすでに実行されている場合は切断します
  • IntersectionObserver を作成し、その動作を定義します。エントリには監視対象の要素が含まれるため、要素が含まれます。 isIntersecting プロパティは、要素の可視性が変更されたかどうかを示します。以前の状態 (プロパティ) と比較し、変更が必要な場合は出力します。次に、後で使用できるように新しい状態をプロパティに保存します。
  • visibilityMonitor が false の場合、要素が表示されるとすぐにオブザーバーを切断します。その仕事は完了しました!
  • 次に、要素を渡してオブザーバーを開始する必要があるため、それを行うためにビューが初期化されるのを待ちます。

最後に、オブザーバーを切断するメソッドを実装しましょう。簡単です:

 private disconnectObserver(): void {
    if (this.observer) {
      this.observer.disconnect();
      this.observer = undefined;
    }
  }

最終コード

ディレクティブ全体は次のとおりです。これは単なる練習問題なので、好きなものに自由に変更してください。

type VisibilityChange =
  | {
      isVisible: true;
      target: HTMLElement;
    }
  | {
      isVisible: false;
      target: HTMLElement | undefined;
    };

@Directive({
  selector: "[visibility]",
  standalone: true
})
export class VisibilityDirective
  implements OnChanges, OnInit, AfterViewInit, OnDestroy {
  private element = inject(ElementRef);

  /**
   * Emits after the view is initialized.
   */
  private afterViewInit$ = new Subject();

  /**
   * The IntersectionObserver for this element.
   */
  private observer: IntersectionObserver | undefined;

  /**
   * Last known visibility for this element.
   * Initially, we don't know.
   */
  private isVisible: boolean = undefined;

  /**
   * If false, once the element becomes visible there will be one emission and then nothing.
   * If true, the directive continuously listens to the element and emits whenever it becomes visible or not visible.
   */
  visibilityMonitor = input(false);

  /**
   * Notifies the listener when the element has become visible.
   * If "visibilityMonitor" is true, it continuously notifies the listener when the element goes in/out of view.
   */
  visibilityChange = output();

  ngOnInit(): void {
    this.reconnectObserver();
  }

  ngOnChanges(): void {
    this.reconnectObserver();
  }

  ngAfterViewInit(): void {
    this.afterViewInit$.next(true);
  }

  ngOnDestroy(): void {
    // Disconnect and if visibilityMonitor is true, notify the listener
    this.disconnectObserver();
    if (this.visibilityMonitor) {
      this.visibilityChange.emit({
        isVisible: false,
        target: undefined
      });
    }
  }

  private reconnectObserver(): void {
    // Disconnect an existing observer
    this.disconnectObserver();
    // Sets up a new observer
    this.observer = new IntersectionObserver((entries, observer) => {
      entries.forEach(entry => {
        const { isIntersecting: isVisible, target } = entry;
        const hasChangedVisibility = isVisible !== this.isVisible;
        const shouldEmit = isVisible || (!isVisible && this.visibilityMonitor);
        if (hasChangedVisibility && shouldEmit) {
          this.visibilityChange.emit({
            isVisible,
            target: target as HTMLElement
          });
          this.isVisible = isVisible;
        }
        // If visilibilyMonitor is false, once the element is visible we stop.
        if (isVisible && !this.visibilityMonitor) {
          observer.disconnect();
        }
      });
    });
    // Start observing once the view is initialized
    this.afterViewInit$.subscribe(() => {
        this.observer?.observe(this.element.nativeElement);
    });
  }

  private disconnectObserver(): void {
    if (this.observer) {
      this.observer.disconnect();
      this.observer = undefined;
    }
  }
}
リリースステートメント この記事は次の場所に転載されています: https://dev.to/this-is-angular/angular-lab-lets-create-a-visibility-directive-5dpp?1 侵害がある場合は、[email protected] までご連絡ください。それを削除するには
最新のチュートリアル もっと>

免責事項: 提供されるすべてのリソースの一部はインターネットからのものです。お客様の著作権またはその他の権利および利益の侵害がある場合は、詳細な理由を説明し、著作権または権利および利益の証拠を提出して、電子メール [email protected] に送信してください。 できるだけ早く対応させていただきます。

Copyright© 2022 湘ICP备2022001581号-3