Uso diferido da funcionalidade de diretiva no Angular

Recentemente, tive que resolver o problema de alterar o mecanismo antigo de exibição das dicas de ferramentas, implementadas por meio da nossa biblioteca de componentes, para uma nova. Como sempre, decidi não inventar a bicicleta. Para começar a resolver esse problema, comecei a procurar por uma biblioteca de código aberto escrita em JavaScript puro que pudesse ser colocada na diretiva Angular e usada neste formulário. No meu caso, como trabalho muito com o popper.js , encontrei a biblioteca tippy.js



escrito pelo mesmo desenvolvedor. Para mim, essa biblioteca parecia a solução perfeita para o problema. A biblioteca tippy.js possui um amplo conjunto de recursos. Com sua ajuda, você pode criar dicas de ferramentas (elementos de dicas de ferramentas) e muitos outros elementos. Esses elementos podem ser personalizados usando temas: são rápidos, fortemente tipados, fornecem acessibilidade ao conteúdo e diferem em muitos outros recursos úteis.

Comecei criando uma diretiva de wrapper para tippy.js:

@Directive({ selector: '[tooltip]' })
export class TooltipDirective {
  private instance: Instance;
  private _content: string;

  get content() {
    return this._content;
  }

  @Input('tooltip') set content(content: string) {
    this._content = content;
    if (this.instance) this.instance.setContent(content);
  }

  constructor(private host: ElementRef<Element>, private zone: NgZone) {}

  ngAfterViewInit() {
    this.zone.runOutsideAngular(() => {
      this.instance = tippy(this.host.nativeElement, {
        content: this.content,
      });
    });
}

Uma dica de ferramenta é criada chamando uma função tippye passando elementos hostpara ela content. Além disso, chamamos tippyfora da zona angular, pois não precisamos dos eventos registrados tippypara acionar o ciclo de detecção de alterações.

Agora, usaremos a dica de ferramenta em uma grande lista de 700 elementos:

@Component({
  selector: 'my-app',
  template: `
    <ul>
      <li *ngFor="let item of data" [tooltip]="item.label">
         {{ item.label }}
      </li>
    </ul>
  `
})
export class AppComponent {
  data = Array.from({ length: 700 }, (_, i) => ({
    id: i,
    label: `Value ${i}`,
  }));
}

Tudo funciona como esperado. Cada item exibe uma dica de ferramenta. Mas podemos resolver esse problema melhor. No nosso caso, 700 cópias foram criadas tippy. E para cada elemento, as ferramentas tippy.js adicionaram 4 ouvintes de eventos. Isso significa que registramos 2800 ouvintes (700 * 4).

Para ver isso por si mesmo, você pode usar o método getEventListenersno console das Ferramentas para Desenvolvedor do Chrome. A construção de exibição getEventListeners(element)retorna informações sobre ouvintes de eventos registrados para um determinado elemento.


Resumo de todos os ouvintes de eventos

Se você deixar o código neste formulário, isso poderá afetar o consumo de memória do aplicativo e o tempo de sua primeira renderização. Isto é especialmente verdade para a saída de página em dispositivos móveis. Vamos refletir sobre isso. Preciso criar instânciastippypara itens que não são exibidos na janela de exibição? Não, não precisa.

Usaremos a APIIntersectionObserverpara adiar a inclusão do suporte à dica de ferramenta até que o item apareça na tela. Se você não estiver familiarizado com a APIIntersectionObserver, consulte a documentação

Crie para oIntersectionObserverwrapper representado pelo objeto observado:

const hasSupport = 'IntersectionObserver' in window;

export function inView(
  element: Element,
  options: IntersectionObserverInit = {
    root: null,
    threshold: 0.5,
  }
) {
  return new Observable((subscriber) => {
    if (!hasSupport) {
      subscriber.next(true);
      subscriber.complete();
    }

    const observer = new IntersectionObserver(([entry]) => {
      subscriber.next(entry.isIntersecting);
    }, options);

    observer.observe(element);

    return () => observer.disconnect();
  });
}

Criamos um objeto observável que informa os assinantes sobre o momento em que o elemento se cruza com uma determinada área. Além disso, aqui verificamos IntersectionObservero suporte ao navegador. Se o navegador não suportar IntersectionObserver, basta emitir truee sair. Os próprios usuários do IE são os culpados por sua miséria.

Agora inViewpodemos usar o objeto observado em uma diretiva que implementa a funcionalidade da dica de ferramenta:

@Directive({ selector: '[tooltip]' })
export class TooltipDirective {
  ...

  ngAfterViewInit() {
    //   
    inView(this.host.nativeElement).subscribe((inView) => {
      if (inView && !this.instance) {
        this.zone.runOutsideAngular(() => {
          this.instance = tippy(this.host.nativeElement, {
            content: this.content,
          });
        });
      } else if (this.instance) {
        this.instance.destroy();
        this.instance = null;
      }
    });
  }
}

Execute o código novamente para analisar o número de ouvintes de eventos.


Resumo de todos os ouvintes do evento após a finalização do projeto

Excelente. Agora, criamos dicas de ferramentas apenas para elementos visíveis.

Vamos procurar respostas para algumas perguntas relacionadas à nova solução.

Por que não usamos a rolagem virtual para resolver esse problema? A rolagem virtual não pode ser usada em nenhuma situação. Além disso, a biblioteca Angular Material armazena em cache o modelo, como resultado, os dados correspondentes continuarão ocupando memória.

E a delegação de eventos? Para fazer isso, você precisa implementar mecanismos adicionais, pois no Angular não há uma maneira universal de resolver esse problema.

Sumário


Aqui falamos sobre como adiar a aplicação da funcionalidade das diretivas. Isso permite que o aplicativo carregue mais rápido e consuma menos memória. O exemplo da dica de ferramenta é apenas um dos muitos casos em que uma técnica semelhante pode ser aplicada. Tenho certeza que você encontrará muitas maneiras de usá-lo em seus próprios projetos.

E como você resolveria o problema de exibir uma grande lista de elementos, cada um dos quais precisa estar equipado com uma dica de ferramenta?

Lembramos que continuamos o concurso de previsões no qual você pode ganhar um novo iPhone. Ainda há tempo para entrar nele e fazer a previsão mais precisa dos valores atuais.


All Articles