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 tippy
e passando elementos host
para ela content
. Além disso, chamamos tippy
fora da zona angular, pois não precisamos dos eventos registrados tippy
para 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 getEventListeners
no 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 eventosSe 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ânciastippy
para itens que não são exibidos na janela de exibição? Não, não precisa.Usaremos a APIIntersectionObserver
para 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 oIntersectionObserver
wrapper 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 IntersectionObserver
o suporte ao navegador. Se o navegador não suportar IntersectionObserver
, basta emitir true
e sair. Os próprios usuários do IE são os culpados por sua miséria.Agora inView
podemos 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 projetoExcelente. 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.
