"Si un trabajador quiere hacer bien su trabajo, primero debe afilar sus herramientas." - Confucio, "Las Analectas de Confucio. Lu Linggong"
Página delantera > Programación > Angular LAB: creemos una directiva de visibilidad

Angular LAB: creemos una directiva de visibilidad

Publicado el 2024-11-03
Navegar:199

Angular LAB: let

En este artículo voy a ilustrar cómo crear una directiva angular muy simple que realiza un seguimiento del estado de visibilidad de un elemento, o en otras palabras, cuando entra y sale de la ventana gráfica. ¡Espero que este sea un ejercicio agradable y quizás útil!

Para hacer esto, usaremos la API JavaScript IntersectionObserver que está disponible en los navegadores modernos.

Lo que queremos lograr

Queremos utilizar la Directiva así:

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

  • la visibilidad es el selector de nuestra directiva personalizada
  • visibilidadMonitor es una entrada opcional que especifica si se debe seguir observando el elemento (si es falso, deje de monitorear cuando ingrese a la ventana gráfica)
  • visibilidadChange nos notificará

La salida tendrá esta forma:

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

Tener un objetivo indefinido significará que el elemento ha sido eliminado del DOM (por ejemplo, por un @if).

Creación de la Directiva

Nuestra directiva simplemente monitoreará un elemento, no cambiará la estructura DOM: será una Directiva de atributos.

@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();
}

En el código anterior ves:

  • la entrada y salida de la que hablamos antes
  • una propiedad llamada afterViewInit$ (un Observable) que actuará como una contraparte reactiva del gancho del ciclo de vida ngAfterViewInit
  • una propiedad llamada observador que almacenará el IntersectionObserver encargado de monitorear nuestro elemento
  • una propiedad llamada isVisibile que almacenará el último estado de visibilidad, para evitar volver a emitir el mismo estado dos veces seguidas

Y, naturalmente, inyectamos ElementRef para capturar el elemento DOM al que aplicamos nuestra directiva.

Antes de escribir el método principal, ocupémonos del ciclo de vida de la directiva.

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 {}

Esto es lo que sucede:

  • Dentro de ngOnInit y ngOnChanges reiniciamos el observador. Esto es para hacer que la directiva sea reactiva: si la entrada cambia, la directiva comenzará a comportarse de manera diferente. Tenga en cuenta que, incluso si ngOnChanges también se ejecuta antes de ngOnInit, todavía necesitamos ngOnInit porque ngOnChanges no se ejecuta si no hay entradas en la plantilla.
  • Cuando se inicializa la vista activamos el Asunto, llegaremos a esto en unos segundos
  • Desconectamos nuestro observador cuando se destruye la directiva para evitar pérdidas de memoria. Por último, si el desarrollador lo solicitó, notificamos que el elemento ha sido eliminado del DOM emitiendo un elemento indefinido.

IntersecciónObservador

Este es el corazón de nuestra directiva. ¡Nuestro método reconnectObserver será el indicado para comenzar a observar! Será algo como esto:

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);
    });
  }

Créeme, ¡no es tan complicado como parece! Aquí está el mecanismo:

  • Primero desconectamos el observador si ya estaba ejecutándose
  • Creamos un IntersectionObserver y definimos su comportamiento. Las entradas contendrán los elementos monitoreados, por lo que contendrá nuestro elemento. La propiedad isIntersecting indicará si la visibilidad del elemento ha cambiado: lo comparamos con el estado anterior (nuestra propiedad) y si es debido, lo emitimos. Luego almacenamos el nuevo estado en nuestra propiedad para más adelante.
  • Si visibilidadMonitor es falso, tan pronto como el elemento se vuelve visible desconectamos el observador: ¡su trabajo está hecho!
  • Luego tenemos que iniciar el observador pasando nuestro elemento, por lo que esperamos a que nuestra vista se inicialice para poder hacerlo.

Por último, implementemos el método que desconecta al observador, muy fácil:

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

código final

Aquí está la directiva completa. Esto fue solo un ejercicio, ¡así que puedes cambiarlo como quieras!

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;
    }
  }
}
Declaración de liberación Este artículo se reproduce en: https://dev.to/this-is-angular/angular-lab-lets-create-a-visibility-directive-5dpp?1 Si hay alguna infracción, comuníquese con [email protected] para borrarlo
Último tutorial Más>

Descargo de responsabilidad: Todos los recursos proporcionados provienen en parte de Internet. Si existe alguna infracción de sus derechos de autor u otros derechos e intereses, explique los motivos detallados y proporcione pruebas de los derechos de autor o derechos e intereses y luego envíelos al correo electrónico: [email protected]. Lo manejaremos por usted lo antes posible.

Copyright© 2022 湘ICP备2022001581号-3