Performance – ein Aspekt, der in der Softwareentwicklung zu oft übersehen wird. Doch es ist ein Faktor, der einen großen Einfluss auf den Erfolg einer Anwendung haben kann. Warum? Weil die Performance eng mit der Benutzererfahrung verknüpft ist. Eine langsame oder träge Anwendung kann schnell zu Frustration führen, was meistens zur Folge hat, dass Benutzer die Anwendung verlassen oder sogar zur Konkurrenz wechseln.

Aber es ist nicht nur die Benutzererfahrung. Eine schlechte Performance kann auch negative Auswirkungen auf die Suchmaschinenoptimierung (SEO) einer Website haben. Suchmaschinen wie Google verwenden mehrere Faktoren, um die Rangfolge der Websites zu bestimmen, die sie in ihren Suchergebnissen anzeigen. Einer dieser Faktoren ist die Ladezeit einer Website. Websites, die langsam laden, können in den Suchergebnissen nach unten rutschen, was zu weniger Besuchern und im schlimmsten Fall zu einem Rückgang des Geschäfts führen kann.

1. Change Detection

Die Standart Change Detention Strategie von Angular ist “Default”, was bedeutet, dass bei jeder Änderung, die die Anwendung betrifft, eine Überprüfung auf der gesamten Komponentenstruktur durchgeführt wird. Das kann bei komplexen Anwendungen schnell ineffizient werden.

Hier kommt “OnPush” ins Spiel. Mit dieser Strategie werden Änderungen nur dann erkannt, wenn eine Änderung auf einer gebundenen Eingabe erkannt wird oder wenn manuell eine Änderungserkennung ausgelöst wird. Das kann den Performance-Overhead erheblich reduzieren, besonders bei großen Anwendungen.

Um “OnPush” zu verwenden, muss es in den Metadaten der Komponente deklariert werden.

import { Component, ChangeDetectionStrategy } from '@angular/core';

@Component({
  selector: 'meine-komponente',
  templateUrl: './meine-komponente.component.html',
  styleUrls: ['./meine-komponente.component.css'],
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class MeineKomponente {

}

In diesem Beispiel wurde ChangeDetectionStrategy.OnPush in den Metadaten der Komponente deklariert, wodurch Angular informiert wird, dass Änderungen nur dann überprüft werden sollen, wenn es eine Änderung bei den gebundenen Eingaben gibt.

2. Lazy Loading

Eine weitere effektive Methode zur Performance-Optimierung in Angular ist das Implementieren von Lazy Loading. Bei dieser Technik werden bestimmte Teile der Anwendung – typischerweise Module oder Komponenten – nur dann geladen, wenn sie tatsächlich benötigt werden. Dies reduziert die Menge der Daten, die beim ersten Laden der Anwendung geladen werden müssen, und kann somit zu schnelleren Ladezeiten führen.

Für das Implementieren von Lazy Loading in Angular muss in der Routing-Konfiguration festgelegt werden, welches Modul beim Aufruf welchen Pfads geladen werden soll.

import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';

const routes: Routes = [
  {
    path: 'feature',
    loadChildren: () => import('./feature/feature.module').then(m => m.FeatureModule)
  },

];

@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule]
})
export class AppRoutingModule { }

In diesem Beispiel wird das “FeatureModule” nur dann geladen, wenn die URL-Pfad “feature” aufgerufen wird. Es ist wichtig zu beachten, dass das Modul mit der import() Funktion geladen wird. Diese Funktion gibt ein Promise zurück, das das Modul enthält, und ermöglicht es so, das Modul asynchron zu laden.

Mit Lazy Loading kann die anfängliche Ladezeit der Anwendung erheblich verbessert werden, indem nur die notwendigen Teile der Anwendung geladen werden. Dies kann zu einer besseren Benutzererfahrung führen, da die Benutzer nicht auf das Laden von Teilen der Anwendung warten müssen, die sie vielleicht gar nicht benötigen.

3. Verwendung von “trackBy” in “ngFor”

Es gibt kaum eine Angular-Anwendung, die nicht die *ngFor Direktive verwendet, um Listen von Elementen zu rendern. Doch was passiert, wenn sich die Daten in der Liste ändern? Angular muss entscheiden, welche Elemente aktualisiert, welche gelöscht und welche neu erstellt werden müssen. Ohne zusätzliche Hinweise löscht und erstellt Angular einfach alle Elemente neu, was nicht besonders effizient ist.

Hier kommt trackBy ins Spiel. Durch die Bereitstellung einer trackBy Funktion kann Angular effektiv verfolgen, welche Elemente in der Liste sich geändert haben und nur diese Elemente aktualisieren. Dies kann insbesondere bei großen Listen die Performance verbessern und das Flackern der Benutzeroberfläche reduzieren.

Um trackBy zu verwenden, muss eine Funktion bereitgestellt werden, die einen eindeutigen Identifikator für jedes Element in der Liste zurückgibt. Hier ist ein kurzes Codebeispiel:

import { Component } from '@angular/core';

@Component({
  selector: 'meine-komponente',
  template: `
    <div *ngFor="let item of items; trackBy: trackByFn">{{ item.name }}</div>
  `,
})
export class MeineKomponente {
  items = [{id: 1, name: 'Item 1'}, {id: 2, name: 'Item 2'}, {id: 3, name: 'Item 3'}];

  trackByFn(index, item) {
    return item.id; 
  }
}

In diesem Beispiel wird trackBy verwendet, um Angular anzuweisen, die Elemente in der Liste anhand ihrer id zu verfolgen. Dadurch muss Angular nur die Elemente neu erstellen, die tatsächlich neu zur Liste hinzugefügt wurden, und kann diejenigen, die bereits existieren, wiederverwenden.

4. AOT Kompilierung

Einer der Schlüssel zur Verbesserung der Performance in Angular ist die Nutzung der Ahead-of-Time (AOT) Kompilierung. Die AOT-Kompilierung ist eine Technik, bei der die Angular-Templates während des Build-Prozesses in effizienten JavaScript-Code umgewandelt werden, anstatt sie zur Laufzeit zu interpretieren.

Die AOT-Kompilierung hat mehrere Vorteile. Sie reduziert die Größe des Angular-Bundles, da der Angular-Compiler nicht mehr benötigt wird und somit aus dem Bundle entfernt werden kann. Dies kann zu schnelleren Ladezeiten führen. Darüber hinaus wird die Laufzeitperformance verbessert, da der kompilierte Code effizienter ist als der interpretierte Code. Schließlich führt die AOT-Kompilierung auch zu einer besseren Fehlererkennung, da Fehler bereits während des Build-Prozesses erkannt werden.

Um die AOT-Kompilierung in Angular zu aktivieren, kann die Option --aot beim Aufruf des ng build oder ng serve Befehls hinzugefügt werden:

ng build --prod --aot

In diesem Beispiel wird der ng build Befehl mit den Optionen --prod und --aot aufgerufen, um ein Produktions-Build mit AOT-Kompilierung zu erstellen. Ab Angular Version 9 wird die AOT-Kompilierung standardmäßig in Produktion verwendet, daher ist die explizite Angabe von --aot oft nicht notwendig.

5. Pipe-Operatoren

Ein weiterer Weg, um die Performance von Angular-Anwendungen zu verbessern, besteht darin, Pipe-Operatoren anstelle von Funktionen in Templates zu verwenden. Warum ist das so? Weil Funktionen in Angular-Templates bei jedem Change Detection Zyklus ausgeführt werden, was zu einer erheblichen CPU-Belastung führen kann, wenn die Funktionen komplex sind oder häufig aufgerufen werden.

Im Gegensatz dazu werden Pipe-Operatoren nur dann ausgeführt, wenn ihre Eingabewerte sich ändern. Das macht sie viel effizienter als Funktionen in Bezug auf die CPU-Nutzung. Darüber hinaus bietet Angular auch eingebaute Pipes für gängige Operationen wie das Formatieren von Daten oder das Umwandeln von Strings in Groß- und Kleinbuchstaben.

Hier ist ein kurzes Codebeispiel, das den Unterschied zwischen der Verwendung einer Funktion und einer Pipe in einem Template verdeutlicht:

import { Component, Pipe, PipeTransform } from '@angular/core';

@Component({
  selector: 'meine-komponente',
  template: `
    <!-- Vermeiden: -->
    <div>{{ transformiereDatum(datum) }}</div>

    <!-- Besser: -->
    <div>{{ datum | date:'short' }}</div>
  `,
})
export class MeineKomponente {
  datum = new Date();

  transformiereDatum(datum: Date) {
    return `${datum.getDate()}.${datum.getMonth() + 1}.${datum.getFullYear()}`;
  }
}

In diesem Beispiel wird die transformiereDatum Funktion in der ersten Zeile bei jedem Change Detection Zyklus ausgeführt. In der zweiten Zeile wird dagegen die eingebaute date Pipe verwendet, die nur dann ausgeführt wird, wenn sich der Wert von datum ändert. Das macht die zweite Zeile effizienter und sollte daher bevorzugt verwendet werden.

Die Verwendung von Pipe-Operatoren ist eine einfache und effektive Methode, um die Performance von Angular-Anwendungen zu verbessern. Es ist also empfehlenswert, diesen Ansatz zu verfolgen, wann immer es möglich ist.

Quellen

https://angular.io/docs