Verzögerte Verwendung der Direktivenfunktionalität in Angular

Vor kurzem musste ich das Problem lösen, den alten Mechanismus zum Anzeigen von Tooltips, der mithilfe unserer Komponentenbibliothek implementiert wurde, durch einen neuen zu ersetzen. Wie immer habe ich beschlossen, das Fahrrad nicht zu erfinden. Um dieses Problem zu lösen, suchte ich nach einer Open-Source-Bibliothek, die in reinem JavaScript geschrieben war und in die Angular-Direktive eingefügt und in dieser Form verwendet werden konnte. In meinem Fall, da ich viel mit der Arbeit popper.js , fand ich die tippy.js Bibliothek



geschrieben vom selben Entwickler. Für mich schien eine solche Bibliothek die perfekte Lösung für das Problem zu sein. Die tippy.js-Bibliothek verfügt über umfangreiche Funktionen. Mit seiner Hilfe können Sie QuickInfos (QuickInfo-Elemente) und viele andere Elemente erstellen. Diese Elemente können mithilfe von Themen angepasst werden. Sie sind schnell, stark typisiert, bieten Zugriff auf Inhalte und verfügen über viele andere nützliche Funktionen.

Ich habe zunächst eine Wrapper-Direktive für tippy.js erstellt:

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

Ein Tooltip wird erstellt , indem Sie eine Funktion aufrufen tippyund vorbei Elemente hostund ihm content. Außerdem rufen wir tippyaußerhalb der Winkelzone an, da wir die protokollierten Ereignisse nicht benötigen tippy, um den Änderungserkennungszyklus auszulösen.

Jetzt verwenden wir den Tooltip in einer großen Liste von 700 Elementen:

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

Alles funktioniert wie erwartet. Jedes Element zeigt einen Tooltip an. Aber wir können dieses Problem besser lösen. In unserem Fall wurden 700 Exemplare erstellt tippy. Und für jedes Element haben die Tools von tippy.js 4 Ereignis-Listener hinzugefügt. Dies bedeutet, dass wir 2800 Hörer (700 * 4) registriert haben.

Um dies selbst zu sehen, können Sie die Methode getEventListenersin der Chrome Developer Tools-Konsole verwenden. Das Ansichtskonstrukt getEventListeners(element)gibt Informationen zu Ereignis-Listenern zurück, die für ein bestimmtes Element registriert sind.


Zusammenfassung aller Ereignis-Listener

Wenn Sie den Code in diesem Formular belassen, kann dies den Speicherverbrauch der Anwendung und die Zeit ihres ersten Renderns beeinflussen. Dies gilt insbesondere für die Seitenausgabe auf Mobilgeräten. Lassen Sie uns darüber nachdenken. Muss ich Instanzentippyfür Elementeerstellen, die nicht im Ansichtsfenster angezeigt werden? Nein, das braucht es nicht.

Wir werden die API verwendenIntersectionObserver, um die Aufnahme von Tooltip-Unterstützung zu verzögern, bis das Element auf dem Bildschirm angezeigt wird. Wenn Sie mit der API nicht vertraut sind, lesen SieIntersectionObserverdie Dokumentation

Erstellen Sie für denIntersectionObserverWrapper, der durch das beobachtete Objekt dargestellt wird:

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

Wir haben ein beobachtbares Objekt erstellt, das die Abonnenten über den Moment informiert, in dem sich das Element mit einem bestimmten Bereich schneidet. Außerdem überprüfen wir hier die IntersectionObserverBrowserunterstützung. Wenn der Browser dies nicht unterstützt IntersectionObserver, geben wir ihn einfach aus trueund beenden ihn. IE-Benutzer selbst sind für ihr Elend verantwortlich.

Jetzt können inViewwir das beobachtete Objekt in einer Direktive verwenden, die die Tooltip-Funktionalität implementiert:

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

Führen Sie den Code erneut aus, um die Anzahl der Ereignis-Listener zu analysieren.


Zusammenfassung aller Event-Listener nach Abschluss des Projekts

Excellent. Jetzt erstellen wir Tooltips nur für sichtbare Elemente.

Lassen Sie uns nach Antworten auf einige Fragen suchen, die sich auf die neue Lösung beziehen.

Warum verwenden wir kein virtuelles Scrollen, um dieses Problem zu lösen? Das virtuelle Scrollen kann in keiner Situation verwendet werden. Darüber hinaus speichert die Angular Material-Bibliothek die Vorlage zwischen, sodass die entsprechenden Daten weiterhin Speicher belegen.

Was ist mit der Delegation von Veranstaltungen? Dazu müssen Sie zusätzliche Mechanismen selbst implementieren. In Angular gibt es keine universelle Möglichkeit, dieses Problem zu lösen.

Zusammenfassung


Hier haben wir darüber gesprochen, wie die Anwendung der Funktionalität von Richtlinien verschoben werden kann. Dadurch kann die Anwendung schneller geladen werden und weniger Speicher verbrauchen. Das Tooltip-Beispiel ist nur einer von vielen Fällen, in denen eine ähnliche Technik angewendet werden kann. Ich bin sicher, Sie werden viele Möglichkeiten finden, es in Ihren eigenen Projekten zu verwenden.

Und wie würden Sie das Problem lösen, eine große Liste von Elementen anzuzeigen, von denen jedes mit einem Tooltip ausgestattet sein muss?

Wir erinnern Sie daran, dass wir den Vorhersagewettbewerb fortsetzen, bei dem Sie ein brandneues iPhone gewinnen können. Es bleibt noch Zeit, sich darauf einzulassen und die genauesten Prognosen zu aktuellen Werten zu erstellen.


All Articles