Überblick
Service Module in Angular/Typescript sind sehr gut, um Funktionen sauber vom übrigen Code zu trennen.
Aber Achtung! Beim Verwenden der Services ist ein synchroner Abruf nicht immer ratsam. Aufwendige Funktionen benötigen mehr Zeit, wodurch der Anwender auf die Rückmeldung des Service warten muss. Dies lässt sich umgehen, indem ein Service zu einem Observable Service umgewandelt wird. Er bietet dann die Möglichkeit asynchron eine Aufgabe zu starten und über Callback-Funktionen das Ergebnis oder Fehler zurückzumelden. So bleibt die Applikation flüssig und der Anwender muss nicht warten.
Das Observable Pattern ist verständlich, dennoch es lässt sich noch einfacher formulieren. Die Funktionalität ist dabei die gleiche.
Dieses Beispiel simuliert eine Webseite mit verschiedenen Arten eine Reihe von Aktionen zu laden.
Dabei ist der 1. Parameter von subscribe der Callback, um das Ergebnis am Ende des Aufrufs zu verwenden. Der zweite Parameter von subscribe ist der Callback für einen Fehlertext, den der Serviceaufruf provoziert hat. Der dritte Parameter (Handler) wird ganz zuletzt aufgerufen. Er ist optional.
Hier der Aufruf der Funktionaliät getAllReaders():

Die Service Implementierung in Typescript ist in dem nächsten Service zu sehen:

Dies ist das Beispiel in der Async/Await Variante. Der Catch-Block kann auch als Promise catch()-Aufruf genutzt werden:

Ein Miniprojekt zeigt die Unterschiede auf
Diese Single-Page Applikation soll als Reaktion auf einen Klick eine Reihe von Aktionen von einem Service abrufen und zwar auf Art und Weise, wie es der Anwender möchte
- Synchron
- Asynchron ohne await
- Asynchron mit await
Jede Aktion wird per ng-for
geladen, sobald sich die Liste ändert. Jede Aktion nach einander.
Struktur

app.component.html

Der Aufruf der Funktion onRunRequest() beinhaltet 3 Abschnitte, die zu 3 verschiedenen Aktionen führen, abhängig von den aktivierten CheckBoxen.
Diese Aktionen sind
- Synced
- Async + No Await
- Async + Await
app.component.ts


action.service.ts
synchroner Einzelabruf:

Asynchroner Einzelabruf
action.service.ts

Initiale Webseite
Um die Webseite in betrieb zu nehmen muss ng serve in der Konsole aufgerufen werden. Es kompiliert die Application und startet einen Dienst, der Standardgemäß Port 4200 verwendet. (http://localhost:4200)

Zur Service-Klasse
Ein Service ist per @Injectable() annotiert. Die Service-Component-Klasse wird damit durch Dependency Injection gefunden. Zudem muss der Service in der app.module.ts eingetragen werden.

Zu den Unterschieden
1. Ergebnis des synchronisierten / sequentiellen Aufruf aller Aktionen

Auffälligkeiten
Nach dem Klick auf „Request All Actions“ musste der Nutzer 8 x 300 ms warten. 2,4 Sekunden die der Nutzer vergeuden musste.
2. Ergebnis des asynchronen / parallelen Abruf aller Aktionen

Auffälligkeiten
Nach dem Klick auf „Request All Actions“, war es zu jedem Moment möglich erneut „Request All Actions“ zu klicken und so die Abfrage erneut zu starten. Die Schaltflächen (Aktionen) sind nacheinander aufgelistet worden. Auf hier dauerte der Aufbau 8 x 300 ms (2,4 sek.), aber der Nutzer konnte alle Komponenten der Seite weiterverwenden. Das bedeutet auch, dass er die Seite scrollen konnte. Das Zeichnen der Webseite wurde niemals unterbrochen.
Im Konsolen-Log auf der rechten Seite sieht man, dass die Abfrage bereits beendet ist, noch vor dem Erhalt der ersten Aktion vom Service, siehe die rote Markierung.
3. Ergebnis des asynchronen / parallelen Abrufs aller Aktionen, jedoch mit der Verwendung von await am Service-Aufruf

Auffälligkeiten
Wie erwartet, wurde auch hier nicht auf das volle Ergebnis gewartet. Jedoch sorgte der Serviceabruf mit vorangestellten await dafür, dass der erste Abruf der ersten Aktion abgewartet wird, bevor die Klick-Methode durchlaufen war, siehe rote Markierung.
Fehler
Jeder asynchrone Klick hat zur Folge, dass die gelisteten Elemente erneut befüllt werden, wodurch ein bereits abgeschickter asynchroner Aufruf ggf. andere Folgeelemente lädt.
Als Schreibtischtest
- Klick, dann Liste Leeren, dann Senden von index 0, dann Abwarten, dann Element 0 wird gezeigt
- (Rekursion), dann Senden von index 1, dann Abwarten, dann Element 1 wird gezeigt, dann (später)
- Klick, dann Liste Leeren, dann Senden von index 0, dann Abwarten, dann Element 2 wird gezeigt (von Rekursion erhalten), dann Element 0 wird gezeigt
- (Rekursion), dann Senden von index 1, dann Abwarten, dann Element 1 wird gezeigt, usw.
Ergebnis
Klick, dann Element 0, Element 1
Klick, dann Element 2, Element 0, Element 1 …
Die Lösung
Das Ergebnis nicht verwenden, wenn ein neuer Klick erfolgt ist.
Meine Lösung ist es, beim Klick die aktuelle ID durch eine zufällige neue UUID zu ersetzen. Jedem Abruf wird die aktuelle ID mitgegeben und im Ergebnis von NextAction wird die verwendet UUID, die aktiv war beim Aufruf im Ergebnis mitgegeben, so dass bei der Auswertung die gelieferte UUID mit der aktiven UUID verglichen werden kann. Nur, wenn die aktive UUID mit der gelieferten UUID übereinstimmt, wird die Aktionsliste mit dem Ergebnis erweitert und Folge-Aktionen werden abgerufen.
Ergebnis
Klick ID=#1, dann Element 0 (ID=#1), Element 1 (ID=#1)
Klick ID=#2, dann (Element 2 (ID=#1) ignoriert, denn #2 ungleich #1), Element 0 (ID=#2), Element 1 (ID=#2) …