"Se um trabalhador quiser fazer bem o seu trabalho, ele deve primeiro afiar suas ferramentas." - Confúcio, "Os Analectos de Confúcio. Lu Linggong"
Primeira página > Programação > Angular LAB: vamos criar uma diretiva de visibilidade

Angular LAB: vamos criar uma diretiva de visibilidade

Publicado em 2024-11-03
Navegar:807

Angular LAB: let

Neste artigo vou ilustrar como criar uma Diretiva Angular muito simples que monitora o estado de visibilidade de um elemento, ou em outras palavras, quando ele entra e sai do a janela de visualização. Espero que este seja um exercício agradável e talvez útil!

Para fazer isso, usaremos a API JavaScript IntersectionObserver, que está disponível em navegadores modernos.

O que queremos alcançar

Queremos usar a Diretiva assim:

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

  • visibilidade é o seletor de nossa diretiva personalizada
  • VisibilidadeMonitor é uma entrada opcional que especifica se deve ou não continuar observando o elemento (se for falso, pare de monitorar quando ele entrar na janela de visualização)
  • visibilidadeChange nos notificará

A saída terá este formato:

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

Ter um alvo indefinido significará que o elemento foi removido do DOM (por exemplo, por um @if).

Criação da Diretiva

Nossa diretiva simplesmente monitorará um elemento, não alterará a estrutura do DOM: será uma Diretiva 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();
}

No código acima você vê:

  • a entrada e a saída de que falamos anteriormente
  • uma propriedade chamada afterViewInit$ (um Observável) que atuará como uma contraparte reativa do gancho do ciclo de vida ngAfterViewInit
  • uma propriedade chamada observer que armazenará o IntersectionObserver encarregado de monitorar nosso elemento
  • uma propriedade chamada isVisibile que armazenará o último estado de visibilidade, para evitar a reemissão do mesmo estado duas vezes seguidas

E naturalmente, injetamos o ElementRef para capturar o elemento DOM no qual aplicamos nossa diretiva.

Antes de escrever o método principal, vamos cuidar do ciclo de vida da diretiva.

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

Agora, eis o que acontece:

  • Dentro de ngOnInit e ngOnChanges reiniciamos o observador. Isto é para tornar a directiva reactiva: se a entrada mudar, a directiva começará a comportar-se de forma diferente. Observe que, mesmo que ngOnChanges também seja executado antes do ngOnInit, ainda precisamos do ngOnInit porque ngOnChanges não é executado se não houver entradas no modelo!
  • Quando a visualização é inicializada, acionamos o Assunto, chegaremos a isso em alguns segundos
  • Desconectamos nosso observador quando a diretiva é destruída para evitar vazamentos de memória. Por fim, se o desenvolvedor solicitar, notificamos que o elemento foi removido do DOM emitindo um elemento indefinido.

InterseçãoObservador

Este é o cerne da nossa diretiva. Nosso método reconnectObserver será aquele para começar a observar! Será algo assim:

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

Acredite em mim, não é tão complicado quanto parece! Aqui está o mecanismo:

  • Primeiro desconectamos o observador se ele já estava em execução
  • Criamos um IntersectionObserver e definimos seu comportamento. As entradas conterão os elementos monitorados, portanto conterão nosso elemento. A propriedade isIntersecting indicará se a visibilidade do elemento mudou: comparamos com o estado anterior (nossa propriedade) e se for devido, emitimos. Em seguida, armazenamos o novo estado em nossa propriedade para mais tarde.
  • Se VisibilityMonitor for falso, assim que o elemento se tornar visível desconectamos o observador: seu trabalho está concluído!
  • Então temos que iniciar o observador passando nosso elemento, então esperamos que nossa view seja inicializada para fazer isso.

Por último, vamos implementar o método que desconecta o observador, fácil:

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

Código final

Aqui está a diretriz completa. Este foi apenas um exercício, então fique à vontade para alterá-lo para o que quiser!

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;
    }
  }
}
Declaração de lançamento Este artigo foi reproduzido em: https://dev.to/this-is-angular/angular-lab-lets-create-a-visibility-directive-5dpp?1 Se houver alguma violação, entre em contato com [email protected] para excluí-lo
Tutorial mais recente Mais>

Isenção de responsabilidade: Todos os recursos fornecidos são parcialmente provenientes da Internet. Se houver qualquer violação de seus direitos autorais ou outros direitos e interesses, explique os motivos detalhados e forneça prova de direitos autorais ou direitos e interesses e envie-a para o e-mail: [email protected]. Nós cuidaremos disso para você o mais rápido possível.

Copyright© 2022 湘ICP备2022001581号-3