Services & HTTP

Services & Injection

  • sind simple TypeScript Klassen
  • sollten mit @Injectable() annotiert werden
  • sind somit für den Angular Injector als verfügbare Dependencies markiert
  • kapseln den Zugriff auf das Backend und andere APIs
  • enthalten Logik die aus Komponenten ausgelagert wird

Dependency Injection

  • Injectables müssen im AppModule unter providers registriert werden
  • theoretisch können diese auch für eine Component (und deren Kinder) registriert werden - dies ist selten nötig; nur falls man jeweils eine eigene Instanz benötigt

Providers

Stellen zur Laufzeit eine konkrete Dep. zur Verfügung

                    
                        // kurze Form
                        providers: [PizzaService]
                    
                    
                        // lange Form
                        [{ provide: PizzaService, useClass: PizzaService }]
                    
                    
                        // mit einem Objekt (gut für Tests ;-))
                        let myService = {
                            getPizze: () => { return []; }
                        };
                        [{ provide: PizzaService, useValue: myService }]
                    
                

Providers

Verwendung in Komponente

                    
                        Component({
                            selector: 'pizza'
                        })
                        export class PizzaComponent {

                            // Service im Konstruktor angeben um zu injecten
                            constructor(private pizzaService: PizzaService) {
                            }
                        }
                    
                

Zusammenfassung

in 98% der Fälle gilt...

  • Services mit @Injectable annotieren
  • Services bei den Providern im AppModule registrieren
  • die Kurzform für die Registrierung verwenden
  • Im Test einen Mock / Stub mit useValue benutzen

Aufgabe 9.1 - Services

  • Branch: 08_ComponentArchitecture_5
  • Extrahiere die Liste der movies in einen MovieService
  • Stelle eine Methode getAllMovies zur Verfügung, die das Array returned
  • Nutze den Service in der MoviesComponent

Aufgabe 9.1 - Mögliche Lösung

  • Branch: 09_ServicesHTTP_1
  • Wenn ihr eure Änderungen verwerfen möchtet und direkt zur Lösung wollt: git reset --hard && git checkout 09_ServicesHTTP_1

RxJS

Recap

  • JavaScript ist single threaded
  • Viele Operationen im Web sind asynchron: Animationen, API Calls, Zeichnen vom UI u.v.m.
  • Um diese Herausforderung zu meistern gibt es mehrere Konzepte: wir werden einen kurzen Blick auf Callbacks, Promises und dann RxJS und Observables werfen

RxJS

Klassische Callbacks

                    
                        fetchCustomerById(id, showCustomerFunc) {
                          openConnection(function(conn, err) {
                            if(err) {
                              // log
                            } else {
                              getCollec(conn, 'customers', function(col, err) {
                                if(err) {
                                   // log
                                } else {
                                   find(col, {'id': id}, function(result, err) {
                                      showCustomerFunc(result);
                                    })
                                }
                             })
                            }
                          })
                        }
                    
                

Klassische Callbacks

  • sind ein Konstrukt um mit Asynchronität umzugehen
  • eine callback Funktion wird einer Funktion als Parameter übergeben und später dann (vllt.) aufgerufen, bspw. wenn Daten verfügbar sind
  • ein grosser Nachteil ist die sogenannte Callback-Hell - wie auf der Slide vorher zu sehen
  • asynchroner Code wird sehr schnell unlesbar

Promises

                    
                        fetchCustomerById(id, callback) {
                            return openConnection()
                                .then(conn => getCollection(conn, 'customers'))
                                .then(col => find(col, id))
                                .then(callback)
                                .catch(err => {
                                    console.err(err);
                                    throw err;
                                });
                        }
                    
            

Promises

  • können gechained werden - ein Promise kann ein weiteres Promise zurückgeben
  • sind das zukünftige Ergebnis einer asynchronen Operation (*ouch*)
  • sind oft besser lesbar als Callbacks: api().then(result => api2()).then(handleResult)
  • Ein Promise resolved nur einmal und ist nicht (einfach) retryable

Promises – im Vergleich zum JDK

  • ähnlich wie ein Future
  • genauer: Seit Java8 ein CompletableFuture<T>
                    
                        supplyAsync(this::api)
                            .thenApply(this::api2)
                            .thenAccept(this::handleResult);
                    
                

RxJS & Observables

Observables sind ein "Array over Time"

  • mehrere Werte können über die Zeit hinweg ankommen
  • verhalten sich wie Streams => Observer Pattern!
  • können abgebrochen werden
  • können wiederholt werden
  • die API ist die selbe: ob für 0, 1 oder N Werte
  • bieten mächtige Operatoren: map, filter, reduce

RxJS Beispiel

                    
                        import {Observable} from "rxjs";

                        const source = Observable.of(1, 2, 3);
                        source.subscribe(x => console.log(x));
                        // 1
                        // 2
                        // 3
                    
                    
                        Observable.of(1, 2, 3)
                            .map(n => n * n)
                            .filter(n => n >= 4)
                            .subscribe(x => console.log(x));
                        // 4
                        // 9
                    
                

RxJS Cancellation

                    
                        const src: Observable<number> = Observable.of(1, 2, 3);
                        const sub: Subscription = src
                            .subscribe(x => console.log(x));
                        sub.unsubscribe();
                    
                

Observables sind lazy

                    
                        const src: Observable<number> = fetchNumbers();
                        // ... nothing happens ;-)

                        src.subscribe(nr => console.log(nr));
                        // jetzt wird der Request abgeschickt
                    
                

RxJS LiveSearch

                    
                          searchControl
                              // wenn sich ein Wert ändert
                              .valueChanges

                              // 500ms Warten nach letzter Änderung
                              .debounceTime(500)

                              // nur wenn der neue Wert sich verändert hat
                              .distinctUntilChanged()

                              // nur der letzte bekannte Wert
                              // von alten wird unsubscribed
                              .switchMap(search => this.dataService.find(search))

                              // gib mir Daten!
                              .subscribe(items => this.items = items);
                    
                

Observables und Angular

  • Angular ist intern als reaktives System aufgebaut
  • Um die funktionale, reaktive Programmierung zu unterstützen, arbeitet Angular intern mit Observables
  • das valueChanges Observable property auf einem Control ist von Angular
  • der HTTP Service von Angular gibt Observables, keine Promises (wie fetch) zurück

Angular HTTP Client

  • wird meist im Constructor eines Service injected
  • bietet alle gängigen HTTP Methoden: get, post, put, delete, patch, head und options
  • Standardmässig wird eine JSON-Response vom angegebenen Typ erwartet → http.get<Pizza[]>
  • Error-Handling wird beim subscriben gemacht.
  • Sehr mächtiges Interface: Offizielle Dokumentation

Erstes Beispiel

HttpClient im Module hinzufügen

                    
                        //...
                        import {HttpClientModule} from "@angular/common/http";
                        //...


                        @NgModule({
                            //...
                            imports: [..., HttpClientModule],
                            //...
                        })
                        export class AppModule {
                        }
                    
                

Erstes Beispiel (2)

Funktion im Service erstellen, der GET-Request ausführt.

                    
                        import {Injectable} from "@angular/core";
                        import {HttpClient} from "@angular/common/http";
                        import {Observable} from "rxjs";
                        import {Pizza} from "./pizza.model";

                        @Injectable()
                        export class PizzaService {

                            constructor(private http: HttpClient) { }

                            gimmePizza(): Observable<Pizza[]> {
                                return this.http.get<Pizza[]>('api/pizze');
                            }
                        }
                    
                

Erstes Beispiel (3)

Service-Funktion in Komponente aufrufen und auf Observable subscriben.

                    
                        export class PizzeComponent implements OnInit {

                            pizze: Pizza[];

                            constructor(private pizzaService: PizzaService) {
                            }

                            ngOnInit() {
                                this.pizzaService.gimmePizza()
                                    .subscribe(pizze => this.pizze = pizze);
                            }
                        }
                    
                

Aufgabe 9.2 - HTTP GET

  • Branch: 09_ServicesHTTP_1
  • Hole die Movies mit einem GET Request vom Backend, anstelle des hardcodierten Arrays
  • Starte das Backend in einer neuen Konsole: npm run api
  • Der REST-Endpoint um alle Movies zu laden, lautet /api/movies
  • Test ob die API läuft: Zeige Movies JSON

Aufgabe 9.2 - Mögliche Lösung

  • Branch: 09_ServicesHTTP_2
  • Wenn ihr eure Änderungen verwerfen möchtet und direkt zur Lösung wollt: git reset --hard && git checkout 09_ServicesHTTP_2

Reaktives System

                    
                        movies: Movie[];
                        moviesSubscription: Subscription;

                        constructor(private movieService: MovieService) {
                        }

                        ngOnInit() {
                            this.moviesSubscription = this.movieService
                                .getAllMovies()
                                .subscribe(movies => this.movies = movies);
                        }

                        ngOnDestroy() {
                            this.moviesSubscription.unsubscribe();
                        }

                    
                

Die Async Pipe - | async

  • Erleichtert den Umgang mit Observables & Promises
  • Hilft mögliche Memory-Leaks zu verhindern, in dem sie Observables automatisch unsubscribed
  • Verkürzt den eigenen Code (kein Reagieren auf Änderungen)
  • Einen guten Artikel zum Thema findest Du hier

Die Async Pipe - | async (2)

                    
                        movies: Observable<Movie[]>;

                        constructor(private movieService: MovieService) {
                        }

                        ngOnInit(): void {
                            this.movies = this.movieService.getAllMovies();
                        }
                    
                    
                        <movie *ngFor="let movie of movies | async"
                               [movie]="movie"
                               (removeMovie)="onRemoveMovie($event)">
                        </movie>
                    
                

Moderne reaktive Architektur

  • wie eingangs erwähnt unterstützt Angular die reaktive Programmierung
  • diese wird häufig mit einem Uni-Directional Data Flow umgesetzt
  • hier bietet sich das Redux-Pattern an
  • eine sehr gute Library ist ngrx/store

Zusammenspiel mit anderen Konzepten

  • die reaktive Architektur integriert sich sehr gut mit anderen etablierten Konzepten
  • Smart Components haben Zugriff auf den Store
  • Smart Components können Actions dispatchen
  • Smart Components geben die Daten per async Pipe an ihre Kinder
  • Dumb Components erhalten lediglich Daten und liefern Events