Uso diferido de la funcionalidad directiva en Angular

Recientemente, tuve que resolver el problema de cambiar el viejo mecanismo para mostrar información sobre herramientas, implementado por medio de nuestra biblioteca de componentes, a uno nuevo. Como siempre, decidí no inventar la bicicleta. Para comenzar a resolver este problema, comencé a buscar una biblioteca de código abierto escrita en JavaScript puro que pudiera colocarse en la directiva Angular y usarse de esta forma. En mi caso, como trabajo mucho con popper.js , encontré la biblioteca tippy.js



escrito por el mismo desarrollador. Para mí, esa biblioteca parecía la solución perfecta para el problema. La biblioteca tippy.js tiene un amplio conjunto de características. Con su ayuda, puede crear información sobre herramientas (elementos de información sobre herramientas) y muchos otros elementos. Estos elementos se pueden personalizar usando temas; son rápidos, fuertemente tipados, brindan accesibilidad al contenido y difieren en muchas otras características útiles.

Comencé creando una directiva de contenedor 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,
      });
    });
}

Se crea una información sobre herramientas llamando a una función tippyy pasando elementos hosta ella content. Además, llamamos tippyfuera de la zona angular, ya que no necesitamos los eventos que se registran tippypara activar el ciclo de detección de cambios.

Ahora usaremos la información sobre herramientas en una gran 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}`,
  }));
}

Todo funciona como se esperaba. Cada elemento muestra una información sobre herramientas. Pero podemos resolver este problema mejor. En nuestro caso, se crearon 700 copias tippy. Y para cada elemento, las herramientas tippy.js agregaron 4 oyentes de eventos. Esto significa que registramos 2800 oyentes (700 * 4).

Para verlo usted mismo, puede usar el método getEventListenersen la consola de Chrome Developer Tools. La construcción de vista getEventListeners(element)devuelve información sobre oyentes de eventos registrados para un elemento dado.


Resumen de todos los oyentes de eventos

Si deja el código de esta forma, esto puede afectar el consumo de memoria de la aplicación y el momento de su primera representación. Esto es especialmente cierto para la salida de página en dispositivos móviles. Consideremos esto. ¿Necesito crear instanciastippypara elementos que no se muestran en la ventana gráfica? No, no necesita.

Utilizaremos la APIIntersectionObserverpara retrasar la inclusión del soporte de información sobre herramientas hasta que el elemento aparezca en la pantalla. Si no está familiarizado con la APIIntersectionObserver, eche un vistazo a la documentación

Cree para elIntersectionObservercontenedor representado por el 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();
  });
}

Creamos un objeto observable que informa a los suscriptores sobre el momento en que el elemento se cruza con un área determinada. Además, aquí verificamos la compatibilidad del IntersectionObservernavegador. Si el navegador no es compatible IntersectionObserver, simplemente emitimos truey salimos . Los propios usuarios de IE tienen la culpa de su miseria.

Ahora inViewpodemos usar el objeto observado en una directiva que implementa la funcionalidad de información sobre herramientas:

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

Ejecute el código nuevamente para analizar la cantidad de oyentes de eventos.


Resumen de todos los oyentes del evento después de la finalización del proyecto

Excelente. Ahora creamos información sobre herramientas solo para elementos visibles.

Busquemos respuestas a un par de preguntas relacionadas con la nueva solución.

¿Por qué no usamos el desplazamiento virtual para resolver este problema? El desplazamiento virtual no se puede usar en ninguna situación. Y, además, la biblioteca de material angular almacena en caché la plantilla, como resultado, los datos correspondientes continuarán ocupando memoria.

¿Qué pasa con la delegación de eventos? Para hacer esto, debe implementar mecanismos adicionales usted mismo, en Angular no hay una forma universal de resolver este problema.

Resumen


Aquí hablamos sobre cómo diferir la aplicación de la funcionalidad de las directivas. Esto permite que la aplicación se cargue más rápido y consuma menos memoria. El ejemplo de información sobre herramientas es solo uno de los muchos casos en los que se puede aplicar una técnica similar. Estoy seguro de que encontrará muchas formas de usarlo en sus propios proyectos.

¿Y cómo resolvería el problema de mostrar una gran lista de elementos, cada uno de los cuales debe estar equipado con información sobre herramientas?

Le recordamos que continuamos con el concurso de predicción en el que puede ganar un nuevo iPhone. Todavía hay tiempo para entrar y hacer el pronóstico más preciso sobre los valores tópicos.


All Articles