Heutige Webanwendungen zeichnen sich durch ihren modularen Aufbau aus. Warum dabei State Management eine wichtige Rolle spielt und wie Caching das State Management optimiert, erfährst du in diesem Artikel.
Die Bausteine einer Webanwendung haben unterschiedliche Aufgaben. Dennoch verwenden sie häufig die gleichen Daten. In einem Adressbuch gibt es beispielweise eine Komponente für die Kontaktliste und eine andere für die Detailansicht eines Kontakts. Beide Komponenten zeigen den Namen an.
State
Nun könnten beide Komponenten die Kontakte vom Backend abrufen. Aber das führt zu unnötigem Netzwerk-Traffic. Hier kommt der State ins Spiel. Der State stellt den „Single Source of Truth“ dar. Er stellt Daten komponentenübergreifend bereit. So reicht es aus, dass eine Komponente den Backendaufruf macht und die anderen Komponenten können die Daten aus dem State laden.
State Caching
In der Praxis stößt man schnell auf das Problem, dass eine Komponente nicht weiß, ob bereits eine andere Komponente die benötigten Daten geladen und in den State gespeichert hat. Hier hilft State Caching weiter.
Beim Einsatz von State Caching wird zuerst im State nach den geforderten Daten geschaut. Sind sie hier vorhanden, werden die Daten aus dem State geladen. Sind die Daten nicht im State vorhanden, werden die Daten vom Backend geladen.
Umsetzung
Im Folgenden wird ein Beispiel gezeigt, um die Theorie praktisch zu verdeutlichen. In der Angular-Anwendung wird die State Management Library NGXS verwendet.
Die Beispiel-Anwendung listet alle Länder auf und nutzt dazu die REST-Schnittstelle von restcountries.eu. Ein Klick auf das Länderkürzel ruft über einen weiteren Endpoint die Detailinformationen des Landes ab. Die Detailinformationen werden im State gespeichert.
Abb. 1 zeigt die Liste der Länderkürzel. Ein Klick auf ein Kürzel fügt das Land zu der Tabelle mit Detailinformationen hinzu.
Wenn zu einem späteren Zeitpunkt das gleiche Länderkürzel geklickt wird, wird nicht mehr die REST-Schnittstelle aufgerufen und stattdessen die Daten vom State geladen.
export class GetCountryById {
static readonly type = '[Countries] Get country by ID';
constructor(public id: string) {
}
}
@Injectable()
export class CountriesState {
constructor(private countriesService: CountriesService) { }
@Action(GetCountryById)
getCountryById(
{ getState, patchState, setState }: StateContext<CountriesStateModel>,
{ id }: GetCountryById
): Observable<any> {
const countries = getState().countries;
if (!!countries && !!countries[id]) {
setState(
patch({
countries: patch({
[id]: patch({
isFetchedFromState: true
})
})
})
);
return;
}
return this.countriesService.getCountryById(id).pipe(
tap((country: Country) => {
patchState({
countries: {
...countries,
[id]: {
...country,
isFetchedFromState: false
},
}
});
})
);
}
}
// Abb. 2
In Abb. 2 gibt es einen if-Block, der prüft, ob die Detailinformationen bereits im State gespeichert sind. Wenn dies der Fall ist, wird isFetchedFromState: true
gesetzt. Dies ist dazu da, um auf der Oberfläche in der Beispiel-Anwendung anzuzeigen, aus welcher Quelle (Backend oder State) die Daten geladen wurden.
In Abb. 3 ist zu sehen, dass ein erneuter Klick auf „MX“ die Detailinformationen aus dem State lädt („fetched from State“).
State updaten
Mit dem Button „Update population to 100“ wird ein Wert beispielhaft verändert. Der veränderte Wert wird an das Backend übermittelt und bei einer 200 Response wird der State geupdatet.
State Caching ist eine einfache Art, häufige Netzwerkaufrufe zu verhindern. Der schnelle Zugriff auf bereits geladene Daten steigert so auch die Benutzerfreundlichkeit. Die Beispiel-Anwendung ist auf GitHub veröffentlicht: state-caching-with-ngxs.