Programmation reactive et observable

Une des grandes nouveautés d’Angular 5 est la programmation reactive et les observables.

Qu’est ce que cela veut dire? en faite cela signifie qu’on va avoir des objets qui vont nous fournir des résultats mais qu’on ne va pas attendre le résultat pour poursuivre le programme.

Quand les resultats arriveront nous les traiterons mais entre temps on pourra faire autres choses.

Cela est très interessant car cela

  • evite de bloquer une page
  • permet de lancer par exemple plusieurs requêtes en même temps

Je vais essayer d’expliciter tous cela avec un exemple concret.

Vous pourrez retrouver une explication différente sur https://makina-corpus.com/blog/metier/2017/premiers-pas-avec-rxjs-dans-angular

Nous allons d’abord construire un serveur python qui pourra nous fournir des réponses rest

from flask import Flask
from flask_cors import CORS
import json
import time

app = Flask(__name__)
CORS(app)

@app.route("/item")
def item():
    return json.dumps({"value":"hello"})

@app.route("/test")
def test():
    return json.dumps([{"value":"hello"},])

@app.route("/five")
def five():
    time.sleep(5)
    return json.dumps([{"value":"hello after 5"},])

if __name__ == "__main__":
    app.run(port=8080)

Note

la route item retourne un objet . Les autres routes retourne une liste d’objet.

Nous allons maintenant construire notre application angular avec

  • classe result
  • un service resultService
npm update -g @angular/cli
ng new myproject
cd myproject
npm audit fix
npm install --save @angular/cdk
ng serve


ng generate class result
ng generate service result

nous lancerons notre server python ainsi

python myapp.py

nous lancerons notre application ainsi

ng serve

Comme nous allons via notre service faire des appels rest nous ajoutons dans app.module.ts

...
import { HttpClientModule } from '@angular/common/http';
...
@NgModule({
...
imports: [
    ...
    HttpClientModule
],
...
export class AppModule { }

Nous allons modifier result.ts ainsi

export class Result {
    value: string;

    public static fromJson(json: Object): Result {
        return new Result(
            json['value']
        );
    }

    constructor(value: string) {
    this.value = value;
    }
}

On peut donc créer un objet Result à partir d’un json

Nous créons result.service.ts

import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { map } from 'rxjs/operators';
import { Observable } from 'rxjs';

import { Result } from './result';
import { JsonPipe } from '@angular/common';

@Injectable({
providedIn: 'root'
})
export class ResultService {
    url = 'http://127.0.0.1:8080/';

    constructor(protected http: HttpClient) { }

    getFive(){
        return this.http.get(this.url+'five').pipe(
        map(
            (jsonArray: Object[]) => {
                console.log("step 1 getFive", new Date());
                return jsonArray.map(jsonItem => Result.fromJson(jsonItem));
            }
        )
        );
    }

    getTest(){
        return this.http.get(this.url+'test').pipe(
        map(
            (jsonArray: Object[]) => {
                console.log("step 1 getFive", new Date());
                return jsonArray.map(jsonItem => Result.fromJson(jsonItem));
            }
        )
        );
    }

    getItem(): Observable<Result>{
        return this.http.get<Result>(this.url+'item');
    }

}

Testons maintenant cela avec la route item

Notre fichier app.component.ts est donc le suivant

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

import { ResultService } from './result.service';

@Component({
    selector: 'app-root',
    templateUrl: './app.component.html',
    styleUrls: ['./app.component.css'],
    providers: [ResultService]
})
export class AppComponent {
    title = 'myproject';
    results = [];
    constructor(private data: ResultService){
    };

    ngOnInit() {
        console.log("step 0", new Date());
        this.data.getItem().subscribe(
        result => (this.results = result, console.log(this.results))
        );
        console.log("step 2", new Date());
    }

}

si nous lançons notre navogateur sur http://127.0.0.1:4200 avec une console de débugage nous attendons quelques choses du type

step 0
step 1
step 2

et pourtant nous avons

step 0 Date 2019-04-27T18:24:34.391Z
step 2 Date 2019-04-27T18:24:34.396Z
step 1 getFive Date 2019-04-27T18:24:34.409Z

en effet le service getItem() nous ramène un observable auquel on souscrit: on attend donc son résultat mais on poursuit le code.

On peut dans le cas de liste utiliser la fonction map pour la aussi traiter les élements

Notre code devient donc

ngOnInit() {
    console.log("step 0", new Date());
    this.data.getFive().subscribe(
    results => (this.results = results, console.log(this.results))
    );
    console.log("step 2", new Date());
}

On peut aussi synchroniser plusieurs appels en parallèle: exemple de function à ajouter dans notre service

getJoin(): Observable<Result>{
  return forkJoin(
    this.getFive(),
    this.getTest()
  ).pipe(
    map(([five, test]) => {
        return five.concat(test)
      }
    )
  )
}