3 Möglichkeiten, große Listen in Angular zu rendern

Im Jahr 2020 wurden Front-End-Frameworks besser, effizienter und schneller. Aber selbst in diesem Sinne kann das Rendern großer Listen ohne „Einfrieren“ des Browsers selbst für die schnellsten vorhandenen Frameworks eine entmutigende Aufgabe sein.

Dies ist einer der Fälle, in denen "das Framework schnell und der Code langsam ist". Es gibt viele Ansätze, mit denen Sie eine große Anzahl von Elementen anzeigen können, ohne die Benutzerinteraktion mit der Webseite zu blockieren. Der Autor des Artikels, dessen Übersetzung wir heute veröffentlichen, möchte die vorhandenen Methoden zur Anzeige großer Listen auf Webseiten untersuchen und über deren Anwendungsbereiche sprechen.







Obwohl sich dieses Material an Angular richtet, gilt das, was hier diskutiert wird, für andere Frameworks und Projekte, die in reinem JavaScript geschrieben sind. Insbesondere werden hier die folgenden Ansätze zum Rendern großer Listen betrachtet:

  • Virtuelles Scrollen (mit Angular CDK).
  • Manuelles Rendern.
  • Progressives Rendern.

Virtuelles Scrollen


Virtuelles Scrollen ist möglicherweise die effizienteste Methode, um mit großen Listen zu arbeiten. Er hat zwar einige Nachteile. Das virtuelle Scrollen ist dank des Angular CDK und anderer Plugins in jeder Komponente sehr einfach zu implementieren.

Die Idee hinter dem virtuellen Scrollen ist einfach, aber nicht immer leicht zu implementieren. Die Quintessenz hier ist, dass wir einen Container und eine Liste von Elementen haben. Ein Element wird nur gerendert, wenn es sich im sichtbaren Bereich des Containers befindet.

Wir werden das Modul scrollingaus dem Angular CDK verwenden, mit dem das virtuelle Scrollen organisiert werden soll. Dazu müssen Sie zuerst das CDK installieren:

npm i @angular/cdk

Dann müssen Sie das Modul importieren:

import { ScrollingModule } from '@angular/cdk/scrolling';
@NgModule({
 ...
 imports: [ ScrollingModule, ...]
})
export class AppModule {}

Danach können Sie in den Komponenten Folgendes verwenden cdk-virtual-scroll-viewport:

<cdk-virtual-scroll-viewport itemSize="50">
 <div *cdkVirtualFor="let item of items">
   {{ item }}
 </div>
</cdk-virtual-scroll-viewport>

Hier ist ein Beispielprojekt, das diesen Ansatz zum Organisieren des virtuellen Bildlaufs verwendet.

Wie Sie sehen können, ermöglichen die Standard-Winkelmechanismen ohne große Schwierigkeiten beeindruckende Ergebnisse. Die Komponente im Beispiel rendert viele tausend Elemente ohne Probleme.

Wenn das virtuelle Scrollen so gut ist und so einfach zu implementieren ist, stellt sich die Frage, warum Sie andere Möglichkeiten zum Rendern großer Listen untersuchen sollten. Diese Frage hat mich auch interessiert. Wie sich herausstellte, hat dieser Zustand mehrere Gründe:

  • , , . , . , Autocomplete ( ). , , , , . — .
  • — , .
  • Es gibt einige Probleme mit der Zugänglichkeit und Benutzerfreundlichkeit des Inhalts einer Liste mit virtuellem Bildlauf. Versteckte Elemente werden nicht gerendert. Dies bedeutet, dass sie für Bildschirmleser nicht verfügbar sind und mit Standardbrowsermechanismen nicht auf der Seite gefunden werden können.

Virtuelles Scrollen ist in einer Reihe von Situationen ideal (vorausgesetzt, es funktioniert):

  • In diesem Fall, wenn Sie Listen anzeigen möchten, deren Größe nicht im Voraus bekannt ist oder die sehr groß sein können (nach einer groben Schätzung Listen mit mehr als 5.000 Elementen, dies hängt jedoch stark von der Komplexität der einzelnen Elemente ab).
  • Für den Fall, dass Sie endloses Scrollen organisieren müssen.

Manuelles Rendern


Eine der Möglichkeiten, mit Listen zu arbeiten, mit denen ich versucht habe, die Ausgabe einer großen Anzahl von Elementen zu beschleunigen, besteht darin, stattdessen das manuelle Rendern mit der Angular-API zu verwenden *ngFor.

Wir haben eine einfache Vorlage, die eine Schleife verwendet, die mit der Direktive * organisiert istngFor

<tr 
*ngFor="let item of data; trackBy: trackById; let isEven = even; let isOdd = odd"
    class="h-12"
    [class.bg-gray-400]="isEven"
    [class.bg-gray-500]="isOdd"
>
  <td>
    <span class="py-2 px-4">{{ item.id }}</span>
  </td>

  <td>
    <span>{{ item.label }}</span>
  </td>

  <td>
    <a>
      <button class="py-2 px-4 rounded (click)="remove(item)">x</button>
    </a>
  </td>
</tr>

Um die Renderleistung von 10.000 einfachen Elementen zu messen, habe ich einen Benchmark verwendet, der auf dem js-Frameworks-Benchmark basiert .

Zuerst habe ich die Leistung einer Liste untersucht, die eine reguläre Schleife zur Ausgabe verwendet *ngFor. Als Ergebnis stellte sich heraus, dass die Skripterstellung 1099 ms, das Rendern (Rendern) 1553 ms und das Malen (Malen) 3 ms dauerte.


Eine Studie zur Leistung der Liste, bei der * ngFor zum Anzeigen der Ausgabe verwendet wird. Listenelemente

können mithilfe der Angular-API manuell gerendert werden:

<tbody>
  <ng-container #itemsContainer></ng-container>
</tbody>
<ng-template #item let-item="item" let-isEven="isEven">
  <tr class="h-12 "
      [class.bg-gray-400]="isEven"
      [class.bg-gray-500]="!isEven"
  >
    <td>
      <span class="py-2 px-4">{{ item.id }}</span>
    </td>

    <td>
      <span>{{ item.label }}</span>
    </td>

    <td>
      <a>
        <button class="py-2 px-4 rounded" (click)="remove(item)">x</button>
      </a>
    </td>
  </tr>
</ng-template>

Lassen Sie uns darüber sprechen, wie sich der Controller-Code geändert hat.

Wir haben eine Vorlage und einen Container deklariert:

@ViewChild('itemsContainer', { read: ViewContainerRef }) container: ViewContainerRef;
@ViewChild('item', { read: TemplateRef }) template: TemplateRef<any>;

Beim Generieren von Daten rendern wir sie mit der createEmbeddedViewEntitätsmethode ViewContainerRef:

private buildData(length: number) {
  const start = this.data.length;
  const end = start + length;

  for (let n = start; n <= end; n++) {
    this.container.createEmbeddedView(this.template, {
      item: {
        id: n,
        label: Math.random()
      },
      isEven: n % 2 === 0
    });
  }
}

Infolgedessen wurden die Indikatoren, die die Leistung der Liste charakterisieren, leicht verbessert. Das Ausführen des Codes dauerte nämlich 734 ms, das Rendern 1443 ms und das Zeichnen 2 ms.


Wenn Sie die Leistung einer Liste untersuchen, die die Angular-API zur Ausgabe von

True verwendet, bedeutet dies in der Praxis, dass die Liste immer noch extrem langsam ist. Wenn Sie auf die entsprechende Schaltfläche klicken, friert der Browser einige Sekunden lang ein. Wenn dies in der Produktion auftauchen würde, würde es den Benutzern definitiv nicht gefallen.

So sieht es aus (hier imitiere ich mit der Maus die Ladeanzeige).


Verlangsamen der Liste Um

die Leistung beim manuellen Rendern von Listen zu verbessern, werden wir versuchen, die progressive Rendering-Technologie anzuwenden.

Progressives Rendern


Die Idee des progressiven Renderns ist einfach. Es besteht darin, eine Teilmenge von Elementen schrittweise zu rendern und das Rendern anderer Elemente in der Ereignisschleife zu verschieben. Dadurch kann der Browser alle Elemente anzeigen, ohne "langsamer zu werden".

Der folgende Code, der das progressive Rendern von Listen implementiert, ist sehr einfach:

  • Auf diese Weise setIntervalerstellen wir zunächst alle 10 ms einen regulären Aufruf einer Funktion, die beim Aufruf 500 Elemente rendert.
  • Nachdem alle Elemente abgeleitet wurden, die wir anhand der Analyse des Index des aktuellen Elements ermitteln, stoppen wir den regulären Funktionsaufruf und unterbrechen den Zyklus.

private buildData(length: number) {
  const ITEMS_RENDERED_AT_ONCE = 500;
  const INTERVAL_IN_MS = 10;

  let currentIndex = 0;

  const interval = setInterval(() => {
    const nextIndex = currentIndex + ITEMS_RENDERED_AT_ONCE;

    for (let n = currentIndex; n <= nextIndex ; n++) {
      if (n >= length) {
        clearInterval(interval);
        break;
      }
      const context = {
        item: {
          id: n,
          label: Math.random()
        },
        isEven: n % 2 === 0
      };
      this.container.createEmbeddedView(this.template, context);
    }

    currentIndex += ITEMS_RENDERED_AT_ONCE;
  }, INTERVAL_IN_MS);

Bitte beachten Sie, dass die Anzahl der Elemente, die pro Aufruf der übertragenen Funktion angezeigt werden setInterval, sowie die Häufigkeit des Aufrufs dieser Funktion vollständig von den Umständen abhängen. Wenn beispielsweise die Elemente der angezeigten Liste sehr komplex sind, ist die Ausgabe von 500 solcher Elemente auf einmal unannehmbar langsam.

Durch die Messung der Leistung dieser Lösung habe ich Ergebnisse erhalten, die schlechter aussehen als die, die ich zuvor erhalten habe. Codeausführung - 907 ms., Rendern - 2555 ms., Zeichnen - 16 ms.


Die Untersuchung der Leistung einer Liste, deren Ausgabe progressives Rendern verwendet.

Der Benutzer, der mit einer solchen Liste arbeitet, wird jedoch eine viel angenehmere Erfahrung als zuvor erleben. Obwohl sich die zum Rendern der Liste erforderliche Zeit erhöht hat, wird der Benutzer dies nicht bemerken. Wir rendern auf einmal 500 Elemente. In diesem Fall wird das Rendern außerhalb der Grenzen des Containers durchgeführt.

Hier können einige Probleme auftreten, weil sich die Größe des Containers während des Renderns ändert oder sich die Position des Bildlaufs ändert. In diesem Fall müssen Sie sich mit ähnlichen Problemen befassen.

So sieht die Arbeit mit einer solchen Liste aus.


Liste ist schnell

Zusammenfassung


Manuelle und progressive Rendering-Techniken für große Listen sind in einigen Situationen sicherlich nützlich. Ich habe sie in Fällen verwendet, in denen das virtuelle Scrollen aus irgendeinem Grund nicht zu mir passte.

In Anbetracht des oben Gesagten können wir sagen, dass virtuelles Scrollen, das auf der Grundlage einer guten Bibliothek wie dem Angular CDK erstellt wurde, die beste Möglichkeit ist, große Listen anzuzeigen. Wenn das virtuelle Scrollen aus irgendeinem Grund nicht verwendet werden kann, hat der Entwickler andere Möglichkeiten.

Liebe Leser! Wie geben Sie große Listen in Ihren Webprojekten aus?


All Articles