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

Barbara Streisand
リリース: 2024-10-09 08:21:02
オリジナル
932 人が閲覧しました

Angular LAB: let

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

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

私たちが達成したいこと

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

<p
  visibility
  [visibilityMonitor]="true"
  (visibilityChange)="onVisibilityChange($event)"
>
  I'm being observed! Can you see me yet?
</p>
ログイン後にコピー
  • 可視性はカスタム ディレクティブのセレクターです
  • 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<void>();

  /**
   * 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<VisibilityChange>();
}
ログイン後にコピー

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

  • 先ほど説明した入力と出力
  • 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は実行されないためです。
  • ビューが初期化されると、Subject がトリガーされます。これには数秒で完了します
  • メモリリークを避けるために、ディレクティブが破棄されるとオブザーバーを切断します。最後に、開発者が要求した場合は、未定義の要素を発行することで要素が 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;
    }
  }
}
ログイン後にコピー

以上がAngular LAB: 可視性ディレクティブを作成しましょうの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

ソース:dev.to
このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。
著者別の最新記事
人気のチュートリアル
詳細>
最新のダウンロード
詳細>
ウェブエフェクト
公式サイト
サイト素材
フロントエンドテンプレート