Services & HTTP

Services & Injection

  • simple TypeScript classes
  • should be annotated with @Injectable({providedIn?: Type<any> | 'root' | 'platform' | 'any' | null})
  • marked with @Injectable marks a class as available to be provided and injected as a dependency and to be used by dependency injector
  • ensures that compiler will generate the necessary metadata to create the class's dependencies when the class is injected.
  • encapsulate the access to the backend or other API's

Dependency Injection

  • can contain logic that is moved from a component
  • @Injectable({providedIn?: Type<any>}) we specify to which module we want to provide this service to. This method is preferred because it enables tree-shaking of the service if nothing injects it.
  • @Injectable({providedIn: 'root'}) is recommended way of providing service to the angular application. On this way we specify that Angular should provide the service in the root injector on the whole application level and this will enable tree-shaking and ensure service is singleton.

Dependency Injection

  • {'platform'} provides service in special singleton platform injector shared by all applications on the page.
  • {providedIn: 'any'} provides a unique instance in each lazy loaded module while all eagerly loaded modules share one instance.
  • {providedIn: 'null' | undefined} injectable is not provided in any scope automatically and must be added to a providers array of an @NgModule(for example in AppModule), @Component or @Directive

Dependency Injection

  • in theory, they can be registered for a component and its children only - this is only necessary in very rare cases, only if a component needs a dedicated instance
  • You should always provide your service in the root injector unless there is a case where you want the service to be available only if the consumer imports a particular @NgModule.

Providers

provide a concrete dependency during runtime

                    
                        // short form
                        providers: [PizzaService]
                    
                    
                        // long form
                        [{ provide: PizzaService, useClass: PizzaService }]
                    
                    
                        // with an object (good for Tests ;-))
                        let myService = {
                            getPizze: () => { return []; }
                        };
                        [{ provide: PizzaService, useValue: myService }]
                    
                

Providers

usage in components

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

                            // use constructor to inject services
                            constructor(private pizzaService: PizzaService) {
                            }
                        }
                    
                

Summary

in 98% of the cases...

  • annotate services with @Injectable({providedIn: 'root'})
  • annotate services with @Injectable({providedIn: UserModule}) if you want to provide service in a specific module just import module and define in service itself his provider. This method is preferred because it enables tree-shaking of the service if nothing injects it. So do not use providers: array in ngModule but this way. For more infor check here.
  • use a mock / stub in a test with useValue

Task 9.1 - Services

  • Base branch: 08_ComponentArchitecture_5_solution
  • extract the list of pizzas to a PizzaService
  • provide a method fetchPizzas that return the list of pizzas
  • use the service in the PizzaOrderComponent

Task 9.1 - Possible Solution

  • Branch: 09_ServicesHTTP_1_solution
  • if you want to throw your local changes away and you want to see the solution: git reset --hard && git checkout 09_ServicesHTTP_1_solution

RxJS

Recap

  • JavaScript is single threaded
  • many operations in the web are asynchronous: animations, API calls, ui rendering etc.
  • there are many concepts that deal with those circumstances: we will have a short look at callbacks, promises and last but not least RxJS and Observables

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);
                                    })
                                }
                             })
                            }
                          })
                        }
                    
                

Callbacks (2)

  • are for dealing with asynchronous behaviour
  • a callback function is handed as a function parameter und might be called later on, e.g. when data is available, an error occurs
  • a huge disadvantage is the so called callback hell - see the slide before
  • asynchronous callback code tends to get unreadable

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

  • can be chained - a promise can return another promise
  • are the future result of an asynchronous operation (*ouch*)
  • readability is often better than with callbacks: api().then(result => api2()).then(handleResult)
  • a promise resolves once and is not (easily) repeatable

Promises – compared to the JDK

  • like a Future
  • more exactly: since Java 8 a CompletableFuture<T>
                    
                        supplyAsync(this::api)
                            .thenApply(this::api2)
                            .thenAccept(this::handleResult);
                    
                

RxJS & Observables

Observables are an "Array over Time"

  • multiple values can arrive over time
  • behave like Streams => Observer Pattern!
  • can be aborted
  • can be retried
  • the API is the same: no matter if there are 0, 1 or N values
  • offer powerful operators: map, filter, reduce

RxJS Example

                    
                        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 are lazy (cold)

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

                        src.subscribe(nr => console.log(nr));
                        // now the request is sent
                    
                

RxJS LiveSearch

                    
                          searchControl
                              // if a value changes
                              .valueChanges

                              // wait for 500ms after the last change
                              .debounceTime(500)

                              // only if the new value differs
                              .distinctUntilChanged()

                              // only the last known value
                              // unsubscribe from old values
                              .switchMap(search => this.dataService.find(search))

                              // give me data!
                              .subscribe(items => this.items = items);
                    
                

Observables and Angular

  • internally, Angular is built as a reactive system
  • in order to support functional reactive programming, Angular internals work with Observables
  • the valueChanges Observable property on a control in the example before is from Angular
  • HTTP Service in Angular returns Observables, not Promises (as fetch does it)

Angular HTTP Client

  • most of the time: injected in a service constructor
  • offers the known HTTP methods: get, post, put, delete, patch, head and options
  • the client expects a JSON response in the specified structure http.get<Pizza[]>
  • handling errors is done with a catch() block
  • very powerful interface: official documentation

first example

function in service that executes the GET call

                    
                        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');
                            }
                        }
                    
                

first example (2)

call the service in the component and subscribe to the Observable.

                    
                        export class PizzeComponent implements OnInit {

                            pizze: Pizza[];

                            constructor(private pizzaService: PizzaService) {
                            }

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

Task 9.2 - HTTP GET

  • Branch: 09_ServicesHTTP_1_solution
  • get the pizzas from the backend using a GET request instead of the hardcoded array
  • the REST endpoint to retrieve all the pizzas is https://pizza-data-api-test.apps.caast01.balgroupit.com/pizzas
  • test whether the api runs: show all pizzas
  • Hint: we need to import HttpClientModule in AppModule in order to use HttpClient service in our app
  • Hint: do not forget to unsubscribe from observable in onDestroy lifecycle hook

Task 9.2 - Possible Solution

  • Branch: 09_ServicesHTTP_2_solution
  • if you want to throw your local changes away and you want to see the solution: git reset --hard && git checkout 09_ServicesHTTP_2_solution

reactive system

                    
                        movies: Movie[];
                        moviesSubscription: Subscription;

                        constructor(private movieService: MovieService) {
                        }

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

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

                    
                

the async pipe - | async

  • simplifies the handling of Observables & Promises
  • helps to avoid memory leaks by automatically unsubscribing from Observables
  • code is shorter
  • a good article regarding the async pipe can be found here

the 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>
                    
                

Task 9.3 - Async pipe

  • Branch: 09_ServicesHTTP_2_solution
  • use async pipe in PizzaOrderComponent and get rid of ngOnDestroy and subscription variable
  • use template binding in order to show spinner when waiting for request to load. See here for more info.

Task 9.3 - Possible Solution

  • Branch: 09_ServicesHTTP_3_solution
  • if you want to throw your local changes away and you want to see the solution: git reset --hard && git checkout 09_ServicesHTTP_3_solution

modern reactive architecture

  • as said before, Angular encourages the usage of a reactive architecture
  • it is often implemented using a unidirectional data flow
  • a well established pattern is Redux
  • an excellent library for Angular is ngrx/store

in interaction with other concepts

  • the reactive architecture plays well with other established concepts
  • Smart Components have access to the store
  • Smart Components can dispatch actions
  • Smart Components hand data down to children using the async pipe
  • Dumb Components only get data and emit events