Django & DRF & Angular 101, partie 3.1
Tags: angular, material, rxjs, django
Published: 2020-04-04 12:08:52

3ème partie consacrée à une application de type SPA, après les 2 / 3 parties axées sur le serveur d'applications pour fournir les APIs : django & drf (django rest framework). 

## Sommaire
* [SPA, introduction](#spa)
* [Préparation de l'environnement](#environnement)
 * [CORS & Django](#env-django)
 * [Angular](#env-angular)
* [Angular](#angular)
 * [Introduction](#intro)
 * [Typescript](#typescript)
 * [Services](#services)
 * [Components](#components)
     * [Communication entre composants / components](#components-communication)
         * [Input](#input)
         * [Output](#output)
         * [Service & Subject](#service-subject)
     * [Composant app-books-list](#component-books-list)
     * [Composant app-author-display](#component-author-display)
     * [Composant principal app-root](#component-root)
* [Conclusion et sources](#end)    

# SPA, introduction <a name="spa"></a>

Les **SPA** se basent sur une approche composants et une architecture dite REST à base d'APIs, vous pouvez relire une présentation sur la [partie REST](https://dev.to/zorky/django-drf-101-partie-2-1gp5#rest) du précédent article.

Dans un contexte d'**applications métiers** (en interne d'une organisation), le gros avantage d'une SPA est son côté fluide et son apport en termes d'UX, on se croirait sur une réelle application et non plus sur un "site Web", cela a remplacé ou remplace les applications développées MPA ou en Flash ou [Silverlight](https://fr.wikipedia.org/wiki/Silverlight) ou [Java Web Start](https://fr.wikipedia.org/wiki/Java_Web_Start) et autres [applets Java](https://fr.wikipedia.org/wiki/Applet_Java) si on remonte encore plus loin, etc.

Dans un passé plus proche, les **MPA** (Multi Pages Applications) avaient / ont le défaut de leur qualité :

* des **allers-retours** avec le serveur pour charger les pages et les sources javascript / CSS, souvent dans un modèle MVC, quid des modèles chargés de façon assez peu cohérent
* on veut une **meilleure interaction** pour l'utilisateur, une meilleure **UX**, pour se faire, on utilise Javascript et ...jQuery pour les appels Ajax et des effets sur le DOM (apparitions et disparitions d'éléments etc) : on fait du SPA sans vraiment en faire, du code spaghetti augmente au fur et à mesure des vues et des versions de l'application : scripts chargés tant bien que mal, lesquels chargés pour telle vue, du JS fonctionnel et non objet, des scripts de fonctions appelées les unes par rapport aux autres : l'application devient très **difficile à maintenir** (entropie qui s'installe avec le temps) pour les suivants (développeurs) et c'est l'expérience de beaucoup de projets qui parle

Partant de ce postulat (application), une SPA est une application qui sera **embarquée dans le navigateur Web**, avec un jeu de "routes" (urls) et de chargement de **composants** à l'intérieur de l'application, ce qui peut se schématiser par 

![Alt Architecture SPA](https://miro.medium.com/max/1400/1*QP6txaW1wyYgwUBWJVkPXA.png)

(schéma pris sur cet [article](https://medium.com/formcept/angular-101-a-technical-guide-to-basic-ui-design-with-angular-605fb0090ae3))

Les sources d'une application **SPA** ressemble à cela, autant dire il n'y a pas grand chose et c'est assez perturbant ;) 

```html
<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>Ma librairie</title>
  <base href="/library/">  
  <link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
  <link href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700" rel="stylesheet">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <link rel="icon" type="image/x-icon" href="favicon.ico">
  <link rel="stylesheet" href="/library/styles.a7613a87447eae269697.css"></head>
<body>

<app-libray-root></app-library-root>

<script type="text/javascript" src="/library/runtime.44f969275dacc8c108f8.js"></script>
<script type="text/javascript" src="/library/es2015-polyfills.ed18db863a6a6727d6b6.js" nomodule></script>
<script type="text/javascript" src="/library/polyfills.6daf05ca7cb3720c8e32.js"></script>
<script type="text/javascript" src="/library/scripts.2ccf57d185e6d7cc00a6.js"></script>
<script type="text/javascript" src="/library/main.080a43d15e12f96ae533.js"></script>
</body>
</html>
```
Une Single Page Application a une architecture à base de ces éléments :

* un **frontend** : application (Javascript / HTML / CSS) embarquée dans le navigateur, des composants / des APIs : un cadre est posé
* un **backend** : un serveur d'applications qui sert des APIs sur une architecture REST

A contrario d'applications classiques **MPA** (Multi Page Applications), qui chargent les pages au fur et à mesure de la navigation, avec des allers-retours entre le navigateur et le serveur (ces serveurs d'applications utilisent souvent le modèle [MVC](https://fr.wikipedia.org/wiki/Mod%C3%A8le-vue-contr%C3%B4leur)), les SPA, vont les charger en une fois ou de façon transparente pour les utilisateurs (javascript, les vues HTML, CSS), les données chargées au fur et à mesure grâce aux APIs et à l'[Ajax](https://fr.wikipedia.org/wiki/Ajax_(informatique)), cela rend la navigation beaucoup plus fluide et améliore l'expérience utilisateur.

Parmi les frameworks et librairies existantes, je me baserai sur [**Angular**](https://angular.io) à sa version 9. Angular que j'utilise depuis sa version 2 depuis bientôt 3 ans (la version dite "2" totalement réécrite, auparavant, j'avais également participé à 2 projets en 2+ ans avec la version "1" [AngularJS](https://angularjs.org/) qui a comme seul point commun avec sa version "2" que le modèle SPA, pour le reste, pas grand chose à voir techniquement)

# Préparation de l'environnement <a name="environnement"></a>

## CORS & Django <a name="env-django"></a>

Dans un contexte de purs appels "**Ajax**" (XMLHttpRequest), un mécanisme de protection est mis en place via [CORS](https://developer.mozilla.org/fr/docs/Web/HTTP/CORS) afin de contrôler et de savoir si le client qui fait l'appel (get, post, ...) a le droit ou non, surtout sur du cross-domains : un client sur domain-A.ntld effectue une requête "Ajax" sur le domain-B.ntld, il faut l'autoriser ou ...le refuser, CORS permet cela, **contrôler l'origine de la requête** et accepter ou refuser ce type d'appels.

En Django & DRF, nous utilisons le module **django-cors-headers** et l'activons dans le **settings.py** sur la "section" INSTALLED_APPS avec **corsheaders** ainsi que le **MIDDLEWARE** corsheaders.middleware.CorsMiddleware.

Enfin, des clés de configuration sont utilisées pour paramétrer CORS (regexp sur le(s) domaine(s) autorisé(s), les entêtes) :

* **CORS_ORIGIN_REGEX_WHITELIST** : liste de regexp pour les domaines acceptés
* **CORS_ORIGIN_ALLOW_ALL** = True en mode DEBUG en mode développement
* **CORS_ALLOW_HEADERS** : liste des entêtes acceptés

Historiquement, avant CORS, lors de l'utilisation de **jQuery**, nous utilisions une astuce pour le cross-domain: le [JSONP](https://en.wikipedia.org/wiki/JSONP) qui permettait de générer une balise script (avec l'URL de l'appel) à la volée et effectuer ainsi des appels _cross-domains_, jQuery permettant facilement la génération de scripts lors d'un [appel de type JSONP](https://learn.jquery.com/ajax/working-with-jsonp/).

## Environnement Angular <a name="env-angular"></a>

Pour un environnement de développement Angular, nous aurons besoin :

* d'un IDE : j'utilise [Webstorm](https://www.jetbrains.com/webstorm/) mais [VSCode](https://code.visualstudio.com/) reste très bien
* d'installer [NodeJS](https://nodejs.org/) (v10 ou v12) (pour [npm](https://www.npmjs.com/), la compilation à la volée [Typescript](https://www.typescriptlang.org/) et du serveur Web local d'exécution). Remarques: en production, une application Angular n'a pas besoin de node, comme les sources "compilées" ne sont basées que sur JS, HTML et CSS
* Créer un répertoire "frontend", créer un fichier package.json (cf. contenu ci-après) et installer Angular CLI : le client angular qui va nous aider à compiler, créer des composantes, etc. Ici, cela sert uniquement à ne pas installer Angular CLI de façon global sur votre PC (l'option -g).

```bash
$ mkdir frontend && cd frontend
```

Contenu du fichier **package.json**

```javascript
{
  "name": "library",
  "version": "1.0.0",
}
```

Installer Angular CLI localement :

```bash
$ npm install @angular/cli --save-dev
```

La version d'angular cli est installé :

```bash
$ ng --version

     _                      _                 ____ _     ___
    / \   _ __   __ _ _   _| | __ _ _ __     / ___| |   |_ _|
   / △ \ | '_ \ / _` | | | | |/ _` | '__|   | |   | |    | |
  / ___ \| | | | (_| | |_| | | (_| | |      | |___| |___ | |
 /_/   \_\_| |_|\__, |\__,_|_|\__,_|_|       \____|_____|___|
                |___/


Angular CLI: 9.1.0
Node: 12.13.1
OS: win32 x64

Angular: undefined
...
Ivy Workspace: <error>

Package                      Version
------------------------------------------------------
@angular-devkit/architect    0.901.0
@angular-devkit/core         9.1.0
@angular-devkit/schematics   9.1.0
@angular/cli                 9.1.0
@schematics/angular          9.1.0
@schematics/update           0.901.0
rxjs                         6.5.4
```
[RxJs](https://rxjs-dev.firebaseapp.com/guide/overview) est aussi installé, nous en parlerons un peu plus tard, cela remplace les [Promises](https://developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Objets_globaux/Promise) qui avaient certaines limitations, RxJs ou la programmation réactive apporte aussi quelques avantages.

* Créer l'application "library", cela va créer dans le répertoire library toute la tuyauterie (sources, package.json, etc)

```bash
$ ng new library
```

* Exécuter une 1ère fois notre nouvelle application, notre squelette s'ouvre dans le navigateur sur l'adresse http://localhost:4200/

```bash
$ cd library
$ ng serve -o
```
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/mlxmslzy2wcpeai5avuk.png)

* Ajoutons quelques modules qui seront utiles, toujours dans "frontend/library" [material](https://material.angular.io/), PWA et [Flex layout](https://github.com/angular/flex-layout) qui nous servira pour ajuster les éléments sur une page (un [site](https://tburleson-layouts-demos.firebaseapp.com/#/docs) dont je me sers souvent avec des exemples)

```bash
$ ng add @angular/material
$ ng add @angular/pwa
$ ng add @angular/flex-layout
```
* On vérifie que tout se passe bien en lançant l'application

```bash
$ ng serve -o
```

On a alors l'ensemble des fichiers suivants dans frontend/library

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/um1r7koxs621claseza8.png)

Parmi les principaux :

  * [angular.json](https://angular.io/guide/workspace-config) : le paramétrage de l'application Angular selon l'environnement, pour les tests unitaires, mode de compilation, etc
  * [browserslist](https://github.com/browserslist/browserslist) : pour la gestion compatibilité des navigateurs, Angular peut détecter et introduire des __polyfills__ le cas échéant
  * package.json : la gestion des modules NPM
  * tsconfig.app.json : la configuration de l'application pour la compilation Typescript
  * tsconfig.json : la configuration générale Typescript, utilisé par tsconfig.app.json (cela peut être utile si nous avions plusieurs applications Angular à gérer, cela donne une configuration de base pour l'ensemble)
  * **src** : les sources de notre application se trouvera dans ce répertoire, ng nous a créé un semble de fichiers, notamment **app-component** qui représente le composant principal à partir duquel, les autres seront chargés

![Alt Sources](https://dev-to-uploads.s3.amazonaws.com/i/pwxyagsjtsa59l7f7d5h.png)

Source de notre application sur http://localhost:4200

```html
<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>Library</title>
  <base href="/">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <link rel="icon" type="image/x-icon" href="favicon.ico">
  <link href="https://fonts.googleapis.com/css?family=Roboto:300,400,500&amp;display=swap" rel="stylesheet">
  <link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
  <link rel="manifest" href="manifest.webmanifest">
  <meta name="theme-color" content="#1976d2">
</head>
<body>
  <app-root></app-root>
  <noscript>Please enable JavaScript to continue using this application.</noscript>
<script src="runtime.js" type="module"></script><script src="polyfills.js" type="module"></script><script src="styles.js" type="module"></script><script src="vendor.js" type="module"></script><script src="main.js" type="module"></script></body>
</html>
```
# Angular <a name="angular"></a>

## Introduction <a name="intro"></a>

Angular est un framework / un cadre de développement de la famille [SPA qui a été présentée](#spa) (on peut trouver d'autres plateformes, des librairies : [React](https://fr.reactjs.org/), [VueJS](https://vuejs.org/) parmi les plus connues). C'est un [framework Opensource](https://github.com/angular/angular) (sous licence MIT) qui a été porté par Google, une forte communauté s'investit et tourne autour d'Angular.

Angular présente un **cadre** qui contraint le développeur, personnellement, c'est un bien, moins permissif et moins de code à la "jQuery", ce cadre couvre :

* le langage, avec **[Typescript](#typescript)**
* une approche **[composants](#components)**
* avec un **[moteur de vue](https://angular.io/guide/architecture-components#templates-and-views)** et de _[binding](https://angular.io/guide/architecture-components#data-binding)_
* à l'aide de **[services](#services)** que l'on injectera dans les composants ou qui seront utilisés directement
* un système de **[routing](https://angular.io/start/start-routing)** ou "routes" qui représentent les URLs et les composants à charger
* un framework qui vous permet de créer des **[modules](https://angular.io/guide/architecture-modules)** pour optimiser le chargement ou cloisonner selon les utilisateurs
* tout un outillage de développement : de "compilation" / création / de mise à jour de versions ou de lancement d'une application grâce à [Angular CLI](https://cli.angular.io/), et de bundle et d'optimisation grâce à [webpack](https://v5.angular.io/guide/webpack) ou [Ivy](https://angular.io/guide/ivy) (depuis la v8) 
* et bien plus : [PWA](https://angular.io/guide/service-worker-getting-started), [schematics](https://angular.io/guide/schematics) (du _scaffolding_ qui va jusqu'à de la mise à jour du code), [Web elements](https://angular.io/guide/elements)

Autour du développement Angular, beaucoup d'éléments basés sur les [Observables](https://angular.io/guide/observables-in-angular) qui amène à la programmation réactive à l'aide de [RxJS](https://rxjs.dev/).

Du côté composants "graphiques" / UX, on trouvera [Material](https://material.angular.io/), projet également [Opensource](https://github.com/angular/components) qui constitue une excellent librairie qui implémente [Material design](https://material.io/design), bien plus moderne et réactive que [Bootstrap](https://getbootstrap.com/) que l'on peut retrouver ailleurs.

## Préambule et saisie des auteurs et livres

En attendant des interfaces d'édition des livres et auteurs sous Angular, vous pouvez utiliser l'**admin de django** qui n'est pas très _user friendly_ mais ça aide. En ouvrant sur le navigateur, suivant si vous avez suivi la méthode virtualenv ou docker pour exécuter le serveur d'applications (voir la mise en place sur cet [article](https://dev.to/zorky/django-drf-101-initialisation-1e39))

* http://plateform/admin
* http://127.0.0.1:8000/admin

Notre squelette d'application maintenant créé, on va pouvoir le modifier. 

L'objectif est de **lister les livres avec leur auteur**.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/xe97uew214fcpbga8b7f.png)

## Typescript <a name="typescript"></a>

Le développement sous Angular s'effectue avec [Typescript](https://www.typescriptlang.org/) qui est un transpileur : le compilateur prend un source et le transpile en ... Javascript. 

Les puristes vous diront que c'est une perte de temps certainement, que rien ne vaut JS, etc J'ai de longues années de pratique de Javascript, en **POO**, et il est à constater que développer en programmation objet lorsque l'on vient de langages de type C#, Java & consorts, n'est pas chose aisée car Javascript est un langage à **prototype** avec une syntaxe qui peut perturber. 

C'est pourquoi nombre de développeurs n'utilise pas toute la puissance de Javascript pour la [POO](https://developer.mozilla.org/fr/docs/Web/JavaScript/Introduction_%C3%A0_JavaScript_orient%C3%A9_objet) pour l'abstraction, on parlera alors encapsulation, héritage ou composition pour les grands principes mais souvent comme un **langage impératif** pour composer un fichier source d'une liste de fonctions à la suite des unes des autres (et génère ainsi du code spaghetti).

Typescript répond à plusieurs points que JS n'apporte pas immédiatement : 

* comment puis-je continuer à **programmer objet** comme mes autres langages objets que je connais, avec les mêmes principes ? Typescript intègre les classes, les interfaces et bien d'autres choses, nous sommes en terrain connu
* TS permet aussi de **typer** et de vérifier à la compilation notre source, JS étant un langage à typage dynamique, et comme tous les langages de ce type, on peut avoir des surprises, TS réduit ce risque à l'exécution, la phase de transpilation permettant de réparer au plus tôt le code et d'avoir un code plus compréhensible pour les autres. Grâce au typage et selon votre IDE, celui-ci pourra vous proposer l'autocompletion des types, gain de temps assuré !
* enfin, TS, permet d'avoir des **modes de compilation**, c'est à dire, la possibilité de préciser pour quelle version de Javascript je souhaite _transpiler_ : ES 5, ES 6, ES next etc sans modifier le source d'origine de mon TS ! et ça, c'est précieux, beaucoup moins de réécrire de code. TS met même en place des mécanismes, sucres syntaxiques afin de pouvoir utiliser des techniques JS futures tout en gardant une version de JS qui n'implémente pas encore ces fonctionnalité (par exemple **async** / **await** en ES 5 n'existent pas ces 2 mots clés sont liés aux [Promises](https://developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Objets_globaux/Promise), ils sont apparus à partir de ES2017, mais TS permet de les utiliser tout en produisant un code pour ES 5, magique ;) )

Pour toutes ces raisons, et il doit en manquer, Typescript est de plus en plus utilisé, dans VueJS, React, etc

En TS, par exemple, le code suivant du fichier test.ts, où on utilise le [_optional chaining_ et le _nullish coalescing_](https://www.ngdevelop.tech/optional-chaining-and-nullish-coalescing/), ainsi que async / await (sucre syntaxique), tout ceci n'existe pas en ES5.

```typescript
let obj = { attribut: 'toto' };
const variable = 0;
obj = null;
const maVarOptional = obj?.attribut;
const maVarNullish = variable ?? 1;

async function delay(n: any) {
  return n;
}

const results = (async () => await delay(400));
results().then((value: string) => console.log(value));
```

**transpilation :** tsc test.ts donnera le fichier test.js

le résultat de la transpilation avec [cet outil en ligne](https://www.typescriptlang.org/play/?noImplicitAny=false&target=1&ssl=12&ssc=55&pln=1&pc=1#code/DYUwLgBA9gRgVhAvBA3hAhmMAnAljAVzAC4IByMKSsiAXwG4AoAYygDsBnSAN3T3RigkEAAxNYCZGwLBgTVp0gBbdADU+AeQAOYXO3TBhEgPwA6TDnxF57LhBXrsAORnBcHABbDe-QSAjGxhAAjEyM6BwAnmzMEABmBDG67BAAJiDA6JEAFGyk6GyRAJSojBAQ2OAE2GwQbEy0jCy2kJUcMmAcwtkR0bHZJYgAfBgA7ui4kOmZOQAsIiJFRUxtHRwDpmAeIGzZ2bzABCCkXHhsAOaDIwocUKCmwFDn+wZHS-RAA), vous pouvez faire varier le _target_ selon la version de Javascript que vous souhaitez (ES2017 par exemple intègre le async / await), intéressant de voir ce que Typescript génère suivant les versions (en ES2020, le _optional chaining_ et le _nullish coalescing_ sont maintenant intégrés, merci TS de pouvoir les utiliser dans les versions moins récentes ;-))

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/1gl8daxjn8i1ugo3h7qi.png)

Le code TS sera traduit en Javascript de la façon suivante (attention c'est verbeux pour l'async / await en ES5) :

```javascript
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
    return new (P || (P = Promise))(function (resolve, reject) {
        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
        function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};
var __generator = (this && this.__generator) || function (thisArg, body) {
    var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
    return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
    function verb(n) { return function (v) { return step([n, v]); }; }
    function step(op) {
        if (f) throw new TypeError("Generator is already executing.");
        while (_) try {
            if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
            if (y = 0, t) op = [op[0] & 2, t.value];
            switch (op[0]) {
                case 0: case 1: t = op; break;
                case 4: _.label++; return { value: op[1], done: false };
                case 5: _.label++; y = op[1]; op = [0]; continue;
                case 7: op = _.ops.pop(); _.trys.pop(); continue;
                default:
                    if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
                    if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
                    if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
                    if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
                    if (t[2]) _.ops.pop();
                    _.trys.pop(); continue;
            }
            op = body.call(thisArg, _);
        } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
        if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
    }
};
var obj = { attribut: 'toto' };
var variable = 0;
obj = null;
var maVarOptional = obj === null || obj === void 0 ? void 0 : obj.attribut;
var maVarNullish = variable !== null && variable !== void 0 ? variable : 1;
function delay(n) {
    return __awaiter(this, void 0, void 0, function () {
        return __generator(this, function (_a) {
            return [2 /*return*/, n];
        });
    });
}
var results = (function () { return __awaiter(void 0, void 0, void 0, function () { return __generator(this, function (_a) {
    switch (_a.label) {
        case 0: return [4 /*yield*/, delay(400)];
        case 1: return [2 /*return*/, _a.sent()];
    }
}); }); });
results().then(function (value) { return console.log(value); });
```

le fichier de configuration de compilation typescript
```json
{
        "compilerOptions": {
                "target": "ES5"
        }
}
```

le **target** peut avoir les valeurs suivantes : "ES3" (par défaut), "ES5", "ES6"/"ES2015", "ES2016", "ES2017", "ESNext".

_Nota Bene_ : **async** / **await** sont 2 mots clés liés aux [Promises](https://developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Objets_globaux/Promise) et qui permettent de résoudre quand vous utilisez (les promises) ce type de code imbriqué, afin de le rendre linéaire à la lecture et donc plus lisible

```javascript
getResponse(url, (response) => {
   getResponse(response.url, (secondResponse) => {
     const responseData = secondResponse.data
     getResponse(responseData.url, (thirdResponse) => {
//       ...
     })
   })
 })
```

en

```javascript
const response = await getResponse(url)
const secondResponse = await getResponse(response.url)
const responseData = secondResponse.data
const thirdResponse = await getResponse(responseData.url)
// ...
```

## Service <a name="services"></a>

Angular propose la notion de **services**. Un service contient la logique que l'on doit développer, la majeure partie du temps, un service contiendra le code pour consommer une API : obtenir un livre, lister les listes, etc pour résumer le CRUD d'une entité mais on peut aussi les utiliser pour effectuer des traitements.

Les services sont injectés dans les components, on retrouve le principe de l'[**IoC**](https://angular.io/guide/dependency-injection) (utilisé souvent en C#, Java, etc) : une **instance est initialisée** (si elle n'existe pas déjà) puis **injectée** dans le constructeur d'un component, un service est donc un [**singleton**](https://angular.io/guide/singleton-services). 

Créer sous src/app le répertoire **services**, on va créer 2 fichiers : un modèle et un service

Le modèle : créer avec votre IDE le fichier book.model.ts et author.model.ts pour notre modèle Book et Author (une interface avec les champs typés de ce que va nous renvoyer l'api books ou authors) avec le contenu suivant

```typescript
export interface Book {
  id: number;
  name: string;
  nb_pages: number;
  enabled: boolean;

  dt_created?: string;
  dt_updated?: string;
}

export interface Author {
  id: number;
  first_name: string;
  last_name: string;

  dt_created?: string;
  dt_updated?: string;
}
```
Les champs ayant **?** sont considérés optionnels lorsque de la création d'un objet. Ce typage nous permettra aussi d'avoir **l'autocompletion** dans notre IDE sur les champs lors de l'utilisation d'un objet dans la vue ou le ts.

Le service : en ligne de commande, dans src/app/services, on crée notre service qui pointera vers l'API books et un autre vers l'API authors, on utilise le *[ng cli](https://cli.angular.io/)* 

```bash
$ ng g service bookService
$ ng g service authorService
```
dans book.service.ts / author.service.ts, éditons le code pour qu'il ressemble à ceci :

* utilisation du HttpClient qui sert à effectuer les appels (get, post, ...,), les appels sont si possible **typés**, pour notre liste de Book ou Author, le get utilisé renverra Book[] (un tableau de Book) ou Author[] selon l'API utilisée, à l'aide des [_generics_](https://www.typescriptlang.org/docs/handbook/generics.html) de Typescript 
* du modèle créé pour le modèle Book ou Author
* selon le type de l'environnement, l'URL **base** de l'API utilisée, cela représente le serveur (en local : http://localhost:4200, en test, https://domain-test.ntld, en production : https://domain-prod.ntld, etc)

```typescript
import { Injectable } from '@angular/core';
import { HttpClient} from '@angular/common/http';
import {environment} from '../../../environments/environment';
import {Book} from './book.model';

@Injectable({
  providedIn: 'root'
})
export class BookService {
  private url =  `${environment.baseUrl}/library/books/`;
  constructor(private httpClient: HttpClient) { }

  /**
   * Liste de tous les livres
   */
  public fetchAll() {
    return this.httpClient.get<Book[]>(this.url);
  }

  /**
   * Obtient un livre particulier par son id
   * @param {number} id : l'identifiant du livre
   */
  public fetch(id: number) {
    const urlId = `${this.url}${id}/`;
    return this.httpClient.get<Book>(urlId);
  }
}
```

```typescript
import {Injectable} from "@angular/core";
import {environment} from "../../../environments/environment";
import {HttpClient} from "@angular/common/http";
import {Author} from "./author.model";

@Injectable({
  providedIn: 'root'
})
export class AuthorService {
  private url =  `${environment.baseUrl}/library/authors/`;
  constructor(private httpClient: HttpClient) { }

  /**
   * Liste de tous les auteurs
   */
  public fetchAll() {
    return this.httpClient.get<Author[]>(this.url);
  }

  /**
   * Obtient un auteur particulier par son id
   * @param {number} id : l'identifiant de l'auteur
   */
  public fetch(id: number) {
    const urlId = `${this.url}${id}/`;
    return this.httpClient.get<Author>(urlId);
  }
}

```

Nos 2 services sont créés, on va pouvoir les utiliser dans nos components.

## Composants / components <a name="components"></a>

Un component est la base d'Angular. On va créer un arbre de composants (comme les balises HTML) afin de construire notre application.

Un component va être composé de 3 fichiers :

* la **vue** : component.html, dans la vue Angular intègre un [moteur](https://angular.io/guide/template-syntax) qui permet du traitement sur de la donnée ou composants : l'afficher en interpolant une "variable", itérer de dessus (*ngFor), tester (*ngIf), "two way data binding" etc
* le **code Typescript** : component.ts
* le **CSS** le cas échéant : component.css

Une commande Angular CLI permet de créer à la voléé ces fichiers ainsi qu'un squelette HTML et TS, il l'ajoutera également au module courant où se situe le component

```bash
$ ng g c monComponent
```
**g** : generate, **c** : component
 
Pour schématiser, on aura la structure suivante dans index.html puis l'arborescence à partir de app-root :

* app-root (app.component.html)
  * app-header (header.component.html)
  * app-books-list (books-list.component.html)
  * app-footer (footer.component.html)

### Communication entre components <a name="components-communication"></a>

La communication est possible entre components / composants. Les composants sont organisés en arbre, un composant peut donc avoir des enfants, qui, eux-même des enfants, etc, et un enfant un père.

```
componant-pere
  |--- composant-fils1
  |--- composant-fils2-pere2
       |--- composant-fils3
```

Pour communiquer entre composants, nous avons plusieurs options :

* <a name="input"></a> du **père vers un fils**  : par les attributs [attribut] que propose un composant avec des **[@Input()](https://angular.io/guide/template-syntax#how-to-use-input)** : le composant fils intercepte alors la valeur via son @Input sur une propriété

Par exemple, le père utilise dans sa vue le composant fils <composant-fils1>, et lui passe la valeur "ma valeur"

```html
<composant-fils1 [proprieteValeur]="'ma valeur'"></composant-fils1>
```

Côté fils, il pourra utiliser la valeur passer à proprieteValeur, ici, un **string**

```typescript
@Component({
  templateUrl: './composant-fils1.component.html',
  styleUrls: ['./composant-fils1.component.scss']
})
export class ComposantFils1Component implements OnInit {
   @Input() proprieteValeur: string= null;
}
```

* <a name="output"></a> du **fils vers son père** : par les événements de type **[EventEmitter](https://angular.io/api/core/EventEmitter)** que propose le composant fils par les **[@Output()](https://angular.io/guide/template-syntax#how-to-use-output)** : le composant père intercepte alors l'événement par une fonction associés au (output_name), et potentiellement la valeur envoyée par le EventEmitter

Par exemple, le père utilise dans sa vue le composant fils <composant-fils1>, et attend de la part du fils, un événement qui lui passera une valeur (ici, un string)

```html
<composant-fils1 (onSelected)="funInterception($event)"></composant-fils1>
```

```typescript
FunInterception(event) {
   console.log("reçu !");
   console.log(event);
}
```

Côté fils, le **onSelected** est un EventEmitter qu'il va utiliser pour **envoyer** grâce à **emit** de la donnée (par exemple, à la suite d'un click bouton, d'un traitement fini, etc)

```typescript
@Component({
  templateUrl: './composant-fils1.component.html',
  styleUrls: ['./composant-fils1.component.scss']
})
export class ComposantFils1Component implements OnInit {
   @Output() onSelected: : EventEmitter<string> = new EventEmitter<string>();

   TraitementFini() {
       this.onSelected.emit('Traitement fini, prends le relais !');
   }
}
``` 

Les Input() et Output() peuvent s'utiliser seuls ou en les [combinant](https://angular.io/guide/template-syntax#two-way) (on parlera de _two way binding_).

* <a name="service-subject"></a> par un **service** en le combinant avec un objet RxJS de type **[Subject](https://makina-corpus.com/blog/metier/2017/premiers-pas-avec-rxjs-dans-angular#subject)**. Dans ce cas, peu importe si père ou fils, la communication s’effectuera dès lors que le service est utilisé, soit en émission, soit en écoute, les composants sont alors totalement découplés, en revanche, attention à bien s'y retrouver s'il en existe trop :)

Exemple concret

Le service de communication

```typescript
@Injectable({ providedIn: 'root' })
export class CommunicationService {
  /** le Subject déclenché via filterUsed$ **/
   private filterUsedSource = new Subject<{name: string, value: any}>();

  /** l'observable à utiliser pour la communication pour l'interception **/
  filterReceive$ = this.filterUsedSource.asObservable();
  
  /** fonction d'envoi d'une valeur à intercepter par d'autres via filterUsed$ **/
  filterSend(name: string, value: any) {
    this.filterUsedSource.next({name: name, value: value, used: used});
  }
}
```

composant 1 qui attendra de recevoir

```typescript
@Component({
  templateUrl: './composant-1.component.html',
  styleUrls: ['./composant-1.component.scss']
})
export class Composant1Component implements OnInit, OnDestroy {
   sub: Subscription;
   constructor(private commSvc: CommunicationService) {
   }

   ngOnInit() {
     this.sub = this.commSvc.filterReceive$.subscribe((valeur) => {
        console.log(valeur);
        console.log('Bien reçu !');
     });
 
   }
  
   ngOnDestroy() {
      this.sub.unsubscribe();
   }
}
``` 

composant 2 qui enverra des données à qui voudra bien l'écouter !

```typescript
@Component({
  templateUrl: './composant-2.component.html',
  styleUrls: ['./composant-2.component.scss']
})
export class Composant2Component implements OnInit {
   constructor(private commSvc: CommunicationService) {
   }

   ngOnInit() {
   }

   EnvoiTraitement() {
     this.commSvc.filterSend({name: 'composant 2 envoie', value: 'cette valeur'});
   }
}
``` 

### Composant app-books-list <a name="component-books-list"></a>

Créons notre composant pour lister les livres, le but est de charger le service **bookService**, par le constructeur du component, d'appeler la fonction **fetchAll()** au clic du bouton, fonction qui fera l'appel à l'API et nous ramènera tous les livres de la base.

Squelette du TS d'un component généré grâce à "ng g c booksList", on a alors le **constructeur** du component et au moins l'événement / _hook_ (qui sera appelé au chargement du component par Angular) **ngOnInit** qui est l'implémentation de l'**interface OnInit**, d'autres [**événements sont disponibles sur le cycle de vie d'un component**](https://angular.io/guide/lifecycle-hooks). Angular utilise les [décorateurs de Typescript](https://www.typescriptlang.org/docs/handbook/decorators.html), ici, **@Component** qui a au moins 3 attributs : le **selector** qui sera utilisé dans les vues "mères", la vue **templateUrl** du component et son CSS **styleUrls**

```typescript
import { Component, OnInit } from '@angular/core';

@Component({
  selector: 'app-books-list',
  templateUrl: './books-list.component.html',
  styleUrls: ['./books-list.component.css']
})
export class BooksListComponent implements OnInit {

  constructor() { }

  ngOnInit(): void {
  }

}
```

et la vue HTML générée (attention, elle est complexe)

```html
<p>booksList works!</p>
```

Modifions les 2 fichiers pour y inclure ce qu'il faut pour la liste de nos livres : un bouton où au clic, les livres sont chargés

La vue **books-list.component.html** dans laquelle on utilise les composants [Material](https://material.angular.io/) : une **[mat-card](https://material.angular.io/components/card/overview)** (carte), un [bouton](https://material.angular.io/components/button/overview) en **mat-raised-button**, un **[matBadge](https://material.angular.io/components/badge/overview)** pour afficher sur le bouton le nombre de livres ramenés par l'API, un **[mat-progress-bar](https://material.angular.io/components/progress-bar/overview)** ("spinner") qui s'affichera lors de l'appel à l'API, une **[mat-list](https://material.angular.io/components/list/overview)** pour afficher la liste des livres, 

```html
<mat-card>
  <mat-card-title>Livres</mat-card-title>
  <mat-card-content>
    <mat-card-actions align="end" style="padding-right: 10px;">
      <button mat-raised-button
              matBadge="{{books ? books.length : 0}}" matBadgePosition="after"
              matBadgeColor="accent" matBadgeOverlap="true" matBadgeSize="large"
              (click)="fetchBooks($event)">
        Charger les livres
      </button>
    </mat-card-actions>
    <mat-progress-bar *ngIf="loading"
                      [mode]="'indeterminate'" color="warn"></mat-progress-bar>
    <mat-divider></mat-divider>
    <mat-list>
      <mat-list-item *ngFor="let book of books">
        <p mat-line>
          <span mat-list-icon><mat-icon>book</mat-icon></span>
          <span>{{book.name}}</span>
        </p>
        <p mat-line><app-author-display [author]="book?.author_obj"></app-author-display></p>
        <p mat-line><mat-divider class="mat-divider-dashed"></mat-divider></p>
      </mat-list-item>
    </mat-list>
  </mat-card-content>
</mat-card>
```

Le moteur de _[template](https://angular.io/guide/template-syntax)_ Angular permet de faire des traitements :

* chaque component peut avoir des événements qui permettent de **communiquer** du composant fils vers le père qui l'utilise, ces événements sont matérialisés notamment dans le composant fils par le décorateur **@Output()** sur une propriété. Par exemple, le **button** a un [événement au clic](https://angular.io/guide/template-syntax#event-binding) : (click), il suffit d'y adjoindre une fonction qui sera appelée au clic du bouton, ici, la fonction **fetchBooks()** qui est dans le TS de app-books-list
* *ngIf : test si true alors
* *ngFor : itération sur une liste d'objets, ici la liste de livres
* les 
```html
{{var}}
``` 
permettent d'**interpoler la valeur** d'un objet _var_ contenu dans le TS
* le **?** : l' _optional chaining_, permet si l'objet en question est null de ne pas continuer la chaîne, par exemple : book?.author_obj : si book est nul, il s'arrête là sinon il ira chercher l'attribut author_obj de book, valable également en [typescript](https://www.ngdevelop.tech/optional-chaining-and-nullish-coalescing/) depuis Angular 8 / 9, comme le _nullish coalescing_. C'est très utile de l'utiliser pour éviter des désagréments, et facilite une syntaxe du type "book ? book.author_obj : null", et en plus, l'_optional chaining_ et le _nulling coalescing_ viennent d'être adoptés pour ES 2020, what else ? Même si ES2020 ne sera pas implémenté de si tôt sur tous les navigateurs, Typescript fera en sorte d'avoir une rétro compatibilité avec des versions inférieures (ES 5, ES 6, etc)


Le source TS **books-list.component.ts** modifié :

```typescript
import {Component, OnDestroy, OnInit} from '@angular/core';
import {SubSink} from '../../services/subsink';

import {finalize} from "rxjs/operators";
import {Book, BookService} from "../../services";

@Component({
  selector: 'app-books-list',
  templateUrl: './books-list.component.html',
  styleUrls: ['./books-list.component.css']
})
export class BooksListComponent implements OnInit, OnDestroy {
  subSink = new SubSink();
  books: Book[];
  loading = false;

  constructor(private bookSvc: BookService) { }

  ngOnInit(): void {
  }

  fetchBooks(event) {
    this.loading = true;
    this.books = [];
    this.subSink.sink = this.bookSvc
      .fetchAll().pipe(finalize(() => this.loading = false))
      .subscribe((books) => {
        this.books = books;
      });
  }

  ngOnDestroy(): void {
    this.subSink.unsubscribe();
  }
}

```
* **constructor()** : le constructeur de l'objet BooksListComponent, initialisé par "typescript", c'est ici que l'on injecte notre service BookService, il sera automatiquement instancié
* au click, la méthode **fetchBooks()** est appelée, dans celle-ci on opère les actions suivantes 
  * **loading** à True : automatiquement le composant <mat-progress-bar> s'active grâce au *ngIf, pour une indication à l'utilisateur que c'est en train de charger
  * consommer l'API books : on s'inscrit via un **subscribe()** à la méthode **fetchAll()** de l'instance bookSvc. Cela suit le [pattern Observer](https://fr.wikipedia.org/wiki/Observateur_(patron_de_conception)). En effet, dans fetchAll(), nous utilisons httpClient.get() qui renvoie un [Observable](https://angular.io/guide/observables-in-angular) sur lequel l'on peut s'abonner / souscrire via un subscribe(). Dès que les données arrivent, le get() enverra un "signal" et les transmettra au subscribe, il ne reste plus qu'à les intercepter, les stocker dans **books** et automatiquement, via le **[data binding](https://www.pluralsight.com/guides/angular-data-binding-overview)**, la boucle *ngFor pourra itérer dessus. Les erreurs ne sont pas traitées dans cet exemple, subscribe permet de le faire via une seconde fonction anonyme subscribe((data), (error))
   * le **[pipe](https://www.learnrxjs.io/learn-rxjs/concepts/rxjs-primer#pipe)** fait parti de RxJS et permet de combiner des opérateurs de RxJs. Dans notre cas, on utilise l'opérateur **[finalize()](https://www.learnrxjs.io/learn-rxjs/operators/utility/finalize)** qui a pour effet qu'à la fin de l'observation, le **loading** soit mis à false, ce qui aura pour effet de cacher le mat-progress-bar, que l'appel ait réussi ou non, comme un _finally_ lors d'un _try_ en C#, il y passera forcément. Le pipe peut intégrer bon nombre d'autres [opérateurs](https://angular.io/guide/rx-library#common-operators) que je vous laisse découvrir
* On a une interface supplémentaire : **[OnDestroy](https://angular.io/api/core/OnDestroy)**, la méthode implémentée **ngOnDestroy()** sera alors automatiquement appelée lors de la destruction du composant par Angular, comme en C# ou autre, cela permet d'effectuer le ménage pour éviter d'avoir des références d'objets, ici on se désabonne des souscriptions aux observables (très **important** ! pour ne pas avoir d'effets de bords indésirables). J'utilise une classe utilitaire **SubSink** qui facilite l'ajout et le désabonnement aux _Observable()_ créés.

### Composant app-author-display <a name="component-author-display"></a>

Le component app-books-list utilise dans sa vue un autre component que l'on va créer : <app-author-display> qui est chargé d'afficher l'auteur du livre. app-books-list lui injecte directement l'auteur (author_obj) à l'aide de **[author]="book?.author_obj"** qui est un **@Input** côté app-author-display, charge ensuite à app-author-display de l'afficher.

Ce composant affichera l'auteur à l'aide d'un **mat-chip**. Côté TS, on voit un décorateur **[@Input()](https://angular.io/api/core/Input)** sur la propriété **author**. Cela permet une communication du composant père vers son fils, le père pourra injecter une valeur à l'aide d'un attribut entre [] sur le composant fils, cette valeur sera alors interceptée grâce à l'Input contenu dans le fils.

```html
<app-author-display [author]="valeur"></app-author-display>
```

```typescript
import {Component, Input, OnInit} from '@angular/core';
import {Author} from '../../../services';

@Component({
  selector: 'app-author-display',
  templateUrl: './author-display.component.html',
  styleUrls: ['./author-display.component.css']
})
export class AuthorDisplayComponent implements OnInit {
  @Input() author: Author;

  constructor() { }

  ngOnInit(): void {
  }

}

```

La vue, dès réception de la propriété **author**, l'objet sera affiché côté vue grâce à 
```html
{{author?.first_name}} {{author?.last_name}}
```
on appelle cela le **[data binding](https://www.pluralsight.com/guides/angular-data-binding-overview)** : toute modification de **author** dans le TS sera mis à jour côté vue et vis-versa, et cela de façon automatique, un gain de temps non négligeable.

```html
<mat-chip-list>
  <mat-chip color="warn">{{author?.first_name}} {{author?.last_name}}</mat-chip>
</mat-chip-list>
```

### Composant app-root (app.component) <a name="component-root"></a>

J'ai aussi créé 2 autres composants que vous retrouverez dans les sources : **app-header** qui utilise le **[mat-toolbar](https://material.angular.io/components/toolbar/overview)** et **app-footer**, on peut ainsi composer un _layout_ propre de notre application, le composant principal **app-root** (**app.component**) ressemble maintenant à cela, avec notre composant **app-books-list** qui liste les livres 

```html
<div fxLayout="column" fxLayoutGap="10px" fxFlexFill>
  <div>
    <app-header></app-header>
  </div>
  <div fxLayoutAlign="center center">
    <div fxFlex="70">
      <app-books-list></app-books-list>
    </div>
  </div>
  <div fxFlexOffset="auto">
    <app-footer></app-footer>
  </div>
</div>
```

Le composant app-root est mis dans la (**seule**, SPA !) page index.html que je mets ici

```html
<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>Library</title>
  <base href="/">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <link rel="icon" type="image/x-icon" href="favicon.ico">
  <link href="https://fonts.googleapis.com/css?family=Roboto:300,400,500&amp;display=swap" rel="stylesheet">
  <link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
  <link rel="manifest" href="manifest.webmanifest">
  <meta name="theme-color" content="#1976d2">
</head>
<body>
  <app-root></app-root>
  <noscript>Please enable JavaScript to continue using this application.</noscript>
</body>
</html>
```

# Conclusion et sources <a name="end"></a>

Nous avons vu les **services** (singletons !), les **composants / components**, et quelques composants Material et opérateurs RxJS. 

Dans le prochain article, nous continuerons sur notre lancée et nous ferons en sorte de pouvoir éditer les livres et auteurs avec des interfaces un peu plus _user friendly_ que l'admin Django, un peu de *refactoring* en utilisant la POO qu'offre Typescript et étudier une data-table de type **mat-tab** pour lister les auteurs et aussi agir dessus (tri, pagination, recherche). Nous aborderons aussi le système de _routing_, et nous irons plus loin sur opérateurs RxJS.

Les sources complets de cet article sont disponibles sur le dépôt "library", 
à la [branche django-drf-angular-3.1](https://github.com/zorky/library/tree/django-drf-angular-3.1)