--- title: Projet Frameworks tags: livecampus, tp robots: noindex, nofollow author: Julien Noyer --- # Projet Frameworks ![](https://i.imgur.com/R32ybKp.png) <br> ## Présentation Dans le cadre de votre formation aux technologies en développement Web, vous avez abordé la création d’application Web en code natif (HTML, CSS et Javascript) pour ensuite aborder le développement avec React, Vue et Angular. L’objectif de votre formation sur les cadriciels et de vous donner une grille de lecture vous permettant d’analyser un cadriciel selon le projet à développer, en étudiant la mise en place des principes de bases inhérent au développement Web sur cadriciel : routing, Flux, MVC, … Le travail que vous avez à réaliser pour valider ce cursus est avant tout un espace dans lequel vous devez investir votre temps pour encore mieux comprendre les enjeux en développement Web. <br> ## Proposition de structure Pour mener à bien ce projet avec un cadriciel il est important de suivre une structure qui permette d'organiser correctement les différents composant de l'application. Comme vue en cours, et quelque soit le cadriciel, il est primordiale de penser la construction d'une application en suivant une logique qui la stabilise. <br> ### Mettre en place le gestion des routes Une application FRONTEND est une suite de fonctionnalités qui sont rendu accessibles via des URLs, appelées "route", qui sont organisé au niveau le plus heait de l'application. Les routes sont alors définies dans un "router" qui prend un forme différentes selon le cadriciel. <br> #### React La mise en place du router sur React se fait grâce au module `react-router-dom`, il faut donc dans un premier temps l'installer avec la commande suivante : ``` npm i react-router-dom ``` <br> Une fois le module installé, il faut créer un dossier nommé `views` à la racine du dossier `src` pour y créer les fichier nécessaire aux routes de l'application. Ces fichiers ont la structure suivante : ```jsx= /* [CMP] Main import Define main imports to create the component */ // React modules import React, { Component } from 'react'; // /* [CMP] Definition Set component class */ class SampleView extends Component { render() { return ( <div className="sample-view-component"> <p>sample-view-component</p> </div> ) } } // /* [CMP] Export Export component class */ export default SampleView; // ``` <br> Les composants des routes doivent s'afficher au niveau le plus haut de l'application, à savoir dans le fichier `App.js`. L'objectif de ce fichier est de présenter une structure HTML globale pour l'application. Il faut dans un prmier temps, importer le module `react-router-dom` qui premet de configurer le router : ```jsx= import { BrowserRouter as Router, Switch, Route, Link, useParams } from "react-router-dom"; ``` > En rêgle générale les imports se font au début du fichier. <br> Le principe de React est ensuite d'utiliser la directive `<Router>` qui agit comme un tableau. Ce "*tableau*" permet de définir les routes de l'application avec cette fois-ci la directive `<Route>`. Quelque soit le framework, une route est toujour défini par un path - c'est à dire une URL - dans lequel est injecté un composant "parent". De ce fait, la deuxième étape pour intégrer le router est d'importer les différents composant des routes de la manière suivante : ```jsx= import SampleView from './views/SampleView'; ``` <br> Les routes sont à configurer dans le template du composant car une directive est une balise HTML spécifique dans laquelle des fonctionnalités sont injectées par React ou l'un de ses modules. ```jsx= ... <Router> <Route path="/" element={<SampleView />} /> </Router> ... ``` > L'URL est définie avec la propriété `path` et le composant à afficher par la propriété `element` <br> Dernière étape de l'intégration du router, nous devons faire en sorte de donner la capacité à notre application de manipuler les URLs dans le navigateur, pour afficher les composants définis dans le fichier `App.js`. Nous devons donc importer dans le fichier principale de l'application - à savoir `index.js` - le module `react-router-dom` de la manière suivante : ```jsx= import { BrowserRouter } from "react-router-dom"; ``` <br> Puis, comme pour le fichier `App.js` il faut utiliser un dérictive particulière qui englober la balise `<App />` afin de rendre le router actif de la manière suivante : ```jsx= ... <BrowserRouter> <App /> </BrowserRouter> ... ``` <br> Une fois les routes configurées dans le fichier `App.js`, pour y accéder il ne faut pas utiliser des balise `<a...>` classique mais la directive spécifique au module `react-router-dom` de la manière suivantes : ```jsx= <Link to={"/"}>Home</Link> ``` > La propriété `to` permet d'identifier la path voulu <br> #### Vue La mise en place du router sur Vue.js se fait grâce au module `vue-router`, il faut donc dans un premier temps l'installer avec la commande suivante : ``` npm i vue-router ``` <br> Peu importe le framework, les composants des routes doivent être à l'intérieur d'un dossier nommé `views`. Il ne sagit pas ici d'une obligation mais d'une recommandation, la création d'une application prévoit une grande quantité de composants, il est essentiel de bien organiser son dossier de travail. Les composants des routes ont la structre de base suivante : ```jsx= <template> <div class="sample-view-component" > <p>sample-view-component</p> </div> </template> <script> /* eslint-disable no-console */ /* eslint-disable no-unused-vars */ /* eslint-disable no-mixed-spaces-and-tabs */ /* [VUE] Component Define properties and methods => https://bit.ly/3GdqmXg */ export default { // [VUE] Component name name: 'SampleView', /* [VUE] Components => https://bit.ly/3GdqmXg Used to inject children components */ components: {}, // /* [VUE] Computed => https://vuejs.org/guide/essentials/computed.html Used to define simple in-template expression, the expression below bind values from Store getters */ computed: {}, // /* [VUE] Data => https://bit.ly/3GdqmXg Used to inject data in the Vue.js component */ data(){ return {} }, // /* [VUE] Methods => https://bit.ly/3GdqmXg Used to add methods in Vue.js component */ methods: {}, // } // </script> <style scoped> </style> ``` > Les commantaire en haut du script permettent de masquer des message d'alerte ESlint. <br> Une fois les composants des routes créés, il faut les intégrer dans le fichier de configuration du router. En Vue.js il faut créer un dossier `router` à la racine du dossier `src` pour créer un fichier `index.js` qui contiendra la configuration du router. Il faut dans un premier temps importer le composant `vue-router` de la façon suivante : ```jsx= import { createWebHistory, createRouter } from "vue-router"; ``` <br> Les routes en Vue.js sont configurées dans un tableau qui contient un objet par route. Ce tableau est ensuite utilisé pour configurer le router et ensuite exporté afin de l'intégrer dans l'application. La structure de ce tableau est la suivante : ```jsx= const routerPath = [ { path: '/', component: () => import('../views/SampleView.vue'), } ] ``` > L'URL est définie avec la propriété `path` et le composant à afficher par la propriété `component`. <br> C Nous allons à présent utliliser ce tableau pour créer le router avec la méthode `createRouter` qui permet d'identifier les routes et les options du router : ```jsx= const AppRouter = createRouter({ history: createWebHistory(), routes: routerPath, }); ``` > L'option `history` permet d'utiliser la mémoire cache de la navigation <br> De la m^mee manière que pour React, l'affiche des composants des routes se fait en rêgle générale dans fichier d'entrée de l'application, à savoir le fichier `App.vue`. Ce fichier correspond au composant de plus haut niveaude l'application, sont rôle est de contenir la structure principale du DOM de l'application. EN Vue.js l'affichage des composant des routes se fait grâve à la directive `<router-view>` associée à la directive `<component>` afin d'analyser l'URL et d'y injecter le bon coposant : ```jsx= <router-view v-slot="{ Component }" :key="$route.name" > <component :is="Component" @toggleFlashnote="toggleFlashnote" /> </router-view> ``` > Les directives étant des balise HTML, il faut les utiliser dans le template du composant. <br> Dernière étape pour rendre effective la configuration de notre router dans l'application, nous devons importer le fichier `index.js` du dossier `router` pour l'intégrer dans le fichier `main.js` de la manière suivante : ```jsx= /* [VUE] Main imports Define main imports to create the application */ // Vue imports import { createApp } from 'vue' // App imports import ClientAppEntrypoint from './App.vue'; import AppRouter from "./router"; // Path binding => https://router.vuejs.org // /* [APP] Start Vue.js app Init new Vue.js applilcation */ createApp(ClientAppEntrypoint) .use( AppRouter ) .mount( '#app' ) // ``` <br> Une fois les routes configurées dans le fichier `main.js`, pour y accéder il ne faut pas utiliser des balise `<a...>` classique mais la directive spécifique au module `vue-router` de la manière suivantes : ```jsx= <router-link :to="'/'">Home</router-link> ``` > La propriété `to` permet d'identifier la path voulu <br> #### Angular La gestion des routes avex Angular est proche de celle mis en place avec Vue.js, le principe est de créer un tableau d'objet contenant la définition des routes et identifians les composants à charger selon l'URL demandée par le client. Pour commencer la mise en place du router il est recommander de crer les composants nécessaires aux routes pour qu'ils soient référencés dans le fichier `app.module.ts`. Pour ce faire nous utilisons Angular CLI avec la commande suivante : ``` ng g c views/homeViews ``` <br> Cette commande nous permet de créer un fichier contenant un code correspondant au code suivant : ```typescript= /* Imports */ // Angular components import { Component, OnInit } from '@angular/core'; // /* Definition and export */ @Component({ selector: 'app-home-view', template: ` <p>HomeViewComponent</p> `, }) export class HomeViewComponent implements OnInit { /* [HOOK] OnInit Called once when the component is initialized (eq: Vue mounted() / React componentDidMount()) */ ngOnInit(): void {} // } // ``` <br> Une fois les différents composants des routes créer, nous pouvons organiser notre router en créant un fichier nommé `app.router.ts` à la racine du dossier `app`. Ce fichier vos nous permettre de définir toutes nous routes et les composants qui y sont associés. Le contenu du fichier `app.router.ts` est le suivant : ```typescript= /* Imports */ // Angular components import { Routes } from '@angular/router'; // Inner components import { HomeViewComponent } from "./views/home-view/home-view.component"; // /* Export */ export const AppRouterModule: Routes = [ { path: '', component: HomeViewComponent, }, ]; // ``` <br> Nous importons dans un promier temps l'interface `Routes` qui nous permet de configurer la constante `AppRouterModule`, puis nous importons chacun des composants dont nous avons besoin pour nos routes que nous configurons de la manière suiante : - `path` : le "endpoint" (fin de l'URL) de la route - `component` : le composant à utiliser pour la route > NB: le slash initial de l'URL n'est pas à ajouter dans le "path" (cf. `<base>` dans le fichier `index.html`) <br> Nous routes ainsi configurées peuvent ensuite être injectées dans notre application au niveau du fichier principal de notre application, à savoir le fichier `app.module.ts`. Nous devons dans un premier temps importer le module `RouterModule` pour notre module `AppRouterModule` dans notre fichier de la manière suivante : ```typescript= // Add router import { RouterModule } from "@angular/router"; import { AppRouterModule } from "./app.router"; ``` <br> Pui, pour configurer notre router il faut l'imaginer comme un middleware globale à notre application, c'est pourquoi nous ajoutons dans le tableau `imports` le code suivant qui permet d'injecter le router dans notre application : ```typescript= // Inject router in the App RouterModule.forRoot( AppRouterModule, { onSameUrlNavigation: 'reload' } ), ``` <br> La méthode `forRoot` du module `RouterModule` permet d'injecter le router à la racine de notre application, cette méthode induit que tous les composants de toutes les routes sont chargés dès le lancement du fichier `app.component` dans le navigateur. Dans la grande majorité des cas il est préférable de mettre en place le principe de `Lazy Loading` que vous pouvez aborder sur le lien suivant : https://angular.io/guide/lazy-loading-ngmodules > Le principe de `Lazy Loading` est celui qui est utilisé dans le router de Vue.js. <br> Comme pour les autres frameworks, nous devons à présent définir le composant qui va accueilir notre router et y injecter les composants correspondant aux routes. Nous allons observer la même logique en considérant notre fichier `app.component.ts` comme celui quinous permet de définir les balise HTML présentes sur toutes les vues de l'application. Nous ouvrons donc ce fishier pour le faire correspondre au code suivant : ```typescript= @Component({ selector: 'app-root', template: ` <main> <!-- Use the router-outlet directive to display route components --> <router-outlet></router-outlet> </main> `, }) /* Definition and export */ export class AppComponent implements OnInit { /* [CMP] Injection Inject value inside the component */ constructor(){} // /* [HOOK] OnInit => https://angular.io/guide/lifecycle-hooks Called once, after the component in mounted/changed */ ngOnInit(): void {} // } // ``` <br> Comme nous pouvons le constater dans le code ci-dessus, l'affichage des routes avec Angular se fait grâce à la directive `<router-outlet>` dans laquelle ce charge les différents composants des routes. <br> ### Mettre en place le pattern FLUX La gestion de l'information dans une application FRONTEND est essentiel, il est primodiale de préserver un maximum la puissance de calcule d'une machine pour optimiser le code et sont exécution. Le principe de pattern "Flux", initiallement définie par les équipes de Facebook, à une place importante dans la création d'une application FRONTEND, c'est pourquoi il est important de mettre en place ce pattern avant de construire l'application. <br> ### React.js <br> L'object d'un strore est de mettre en place des fonctionnalités qui rendent accessibles les informations depuis un seul point. Le principe est de faire en sotre que les composant qui ont besoin de la même information ne soit pas obligé de les récupérer individuellement. Avec React deux solutions sont possible, à savoir l'utilisation du `context` ou l'utilisation du module Redux. Aux vues de la structure du framework, l'utilisation de `context` à privilégier mais dans le but de syuvre une srtucture commune, nous allons aborder la mise en place du store avec Redux. dans un premier temps nous devons structure notre strore, nous crééons donc un doccier nommé `store` à la racine du dossier `src` dans lequel nous ajoutons un fichuer `index.js` qui contient le code suivant : ```jsx= /* Imports */ // React modules import { createStore } from "redux"; // Store reducer import rootReducer from './reducers' // /* Definition and exports */ export default createStore( rootReducer ); // ``` <br> Le premier import permet de construir le store avec Redux et le deuxième import permet d'en organiser les différents modules dans un fichier nommé `reducer.js` que nous crééons à présent pour l'ajouter à la racine du dossier `store`. Le fichier `reducer.js` contient le code suivant : ```jsx= /* Imports */ // React modules import { combineReducers } from "redux"; // Store modules import user from './modules/user' // /* Definition and exports */ export default combineReducers({ user, }); // ``` <br> Nous commençons à par importer la méthode `combineReducers` de Redux qui nous permet de créer un fichier par modules et de les identifier comme étant des `states` de notre store. Le code présenté si-dessus intégre un module nommé `user` qui à pour objectif de connaitre les informations d'un utilisateur connecté. Nous devons à présent créer le fichier `user.js` dans un dossier nommé `modules` à ma racine du dossier `store` contenant le code suivant : ```jsx= /* LocalStorage strategy */ const localUserinfo = JSON.parse(window.localStorage.getItem('userinfos')) || null // /* Definition and export */ export default function( state = localUserinfo, action ){ // Check action type switch( action.type ){ case 'LOGIN_USER': // Save user infos in LocalStorage window.localStorage.setItem('userinfos', JSON.stringify(action.value) ); // Update state return state = action.value; case 'LOGOUT_USER': // Clear LocalStorage window.localStorage.removeItem('userinfos'); // Clear state return state = null; default: return state; } } // ``` > Les deux méthodes ci-dessus ont pour équivalant en Vue.js les `mutation`. <br> Notre store ainsi configuré nous devons pour le rendre accessible dans l'application, l'intégrer au niveau du fichier principal de l'application, à savoir le fichier `index.js` que nous allons modifier de la façons suivante : ```jsx= /* [APP] Main import Define main imports to create the component */ // React components import React from 'react'; import ReactDOM from 'react-dom'; // Add Redux modules import { Provider } from "react-redux"; import store from './store/index'; // Add BrowserRouter module to define router import { BrowserRouter } from "react-router-dom"; // App component import App from './App'; // /* [APP] Init Define main App configuration */ ReactDOM.render( <React.StrictMode> {/* Use the Provider directive to add Redux ability */} <Provider store={ store }> {/* Use the BrowserRouter directive to add router ability */} <BrowserRouter> <App /> </BrowserRouter> </Provider> </React.StrictMode>, document.getElementById('root') ); // ``` <br> D'un point de vue logique et historique dans le développement Web, les méthodes qui permettent d'exécuter des requêtes asynchrones s'intégrer dans un principe de "Provider". Ce principe défini que, pour avoir accès à une information, nous devons être capable de nous identifier dans un flux d'information, un "Provider" apporte un support de communication pour les requpete asynchrone. <br> L'utilisation du store dans un composant demande la configuration de deux principes différents, nous devons d'abord pouvoir récupérer et garder à jour les `states` du store et pourvoir appeler les fonction du `reducer`. Pour gardre les `states` à jour, il faut importer le store et la méthode `connect` de Redux afin de mapper les `states` aux propriétés du composant. Pour ce faire il faut éditer le composant de la manière suivante : ```jsx= /* [CMP] Main import Define main imports to create the component */ // React modules import React, { Component } from 'react'; // Store modules import { connect } from "react-redux"; import store from '../store/index'; // /* [CMP] Definition Set component class */ class SampleView extends Component { render() { return ( <div className="sample-view-component"> <p>sample-view-component</p> </div> ) } } // /* [STORE] State Map stotre state to component props */ const mapStateToProps = (state) => { return { user: state.user, } } // /* [CMP] Export Export component class */ export default connect( mapStateToProps )( SampleView ); // ``` <br> Le principe pour déclencher un `reducer` (ou `mutation`), est de créer une fonction dans notre composant qui injecte des valeurs pour mettre à jour un `state`. La technique à mettre en place pour exécuter une requête asyncrone est à écrire dans le composant, pour pourvoir à la fin de la requête, modifier la valeur du `state` par le résultat de la requête. La fonction ci-dessous est un exemple pour la connexion d'un utilisateur : ```jsx= /* [CMP] Method Add component functionnalities */ // Method to bind form submit onSubmit( event ){ // Send Fetch request fetch( `http://localhost:9449/users/?email=${event.email}&password=${event.password}`, { method: 'GET' }) .then( apiResponse => { if( apiResponse.ok ){ return apiResponse.json() } else{ throw apiResponse } }) .then( jsonResponse => { // Check jsonResponse if( jsonResponse[0] ){ /* [STORE] Dispatch Call a reducer function (eq. action in Vue.js) */ store.dispatch( { type: 'LOGIN_USER', value: jsonResponse[0] }) // } }) .catch( apiError => { console.log('apiError', apiError) }); } // ``` <br> La mise à jour du `state` est visible au niveau du commentaire `[STORE] Dispatch`, la méthode `dispatch` du store Redux permet de déclencher une des fonction associées aux `reducres` qui peremttent de mettre à jour des `states`. <br> ### Vue.js La mise la mise en place de `Vuex` débute par l'installation de la dépendance dans le projet avec la commande suivante : ``` npm i vuex ``` <br> Initallement proposer par les équipe de Facebook, le Flux dans Vue doit être organisé dans un dossier spécifique et découpé en modules afin définir au mieux les fonction nacessaire pour accedées aux informations. Il faut donc créer un dossier nommé `store` à la racine du dossier `src` dans lequel nous crééons un fichier `index.js` pour configurer le store. LE code de base de ce fichier est les suivant : ```jsx= /* [VUEX] Main imports Define main imports to create the store */ // Vue imports import { createStore } from "vuex"; // Data binding => https://vuex.vuejs.org /* [STORE] Export Define and export Vuex modules */ // Export Vuex Store export default createStore({ // Add Vuex modules modules: {}, }); // ``` <br> Ce fishier va nous servir de base pourr les différents modules de notre store. Le module le plus emblématique à réaliser et celui qui nous permettra d'exécuter un CRUD. Nous crééons donc un dossier `modules` à la racine du dossier `store` pour y ajouter un fichier `crud.js` contenant la structure suivante : ```jsx= /* eslint-disable no-console */ /* eslint-disable no-unused-vars */ export default { /* [STORE] State => https://bit.ly/3ua8avj Define store private states */ state: { fetchresponse: undefined, }, // /* [STORE] Getters => https://bit.ly/3ua8avj Define methods to export state value */ getters: { fetchresponse: (state) => state.fetchResponse, }, // /* [STORE] Muttation => https://bit.ly/3ua8avj Define setters to update state value */ mutations: { fetchresponse( state, payload ){ state.fetchResponse = payload.data }, }, // /* [STORE] Actions => https://bit.ly/3ua8avj Define dispatchers to call state mutations */ actions: { /* [FETCH] getDataOperation() Used to send GET HTTP request @params{dispatch} Store action dispatcher FUNCTION @params{commit} Store action emitter STRING @params{data.path} path to fecth STRING @params{data.next} next store dispatch STRING */ getDataOperation( { dispatch, commit, state }, data ){ fetch( data.path, { method: `GET`, }) .then( async apiResponse => { // Check response if(apiResponse.ok){ return apiResponse.json(apiResponse) } else{ throw apiResponse } }) .then( async jsonResponse => { // Commit fetch response await commit( 'fetchresponse', { data: { ok: true, action: 'getMethod', path: data.path, value: jsonResponse, error: null } }); }) .catch( async requestRejected => { // Commit fetch response await commit( 'fetchresponse', { data: { ok: false, action: 'getMethod', path: data.path, value: null, error: requestRejected } }); }) }, // } // } ``` <br> CRUD présente une action nommée `getDataOperation` qui permet d'exécuter une requête AJAX via l'API Fetch, pour finaliser ce module, il faut créer les différentes actions pour créer, modifier et supprimer des information en suivant la méthodologie de l'API Fetch (https://developer.mozilla.org/fr/docs/Web/API/Fetch_API). <br> Une fois le module créé, il faut l'importer dans le fichier `index.js` pour rendre accessible dans le store. Il faut donc éditer le fichier pour qu'il corresponde au code suivant : ```jsx= /* [VUEX] Main imports Define main imports to create the store */ // Vue imports import { createStore } from "vuex"; // Data binding => https://vuex.vuejs.org import crud from "./modules/crud"; // Used has Store module /* [STORE] Export Define and export Vuex modules */ // Export Vuex Store export default createStore({ // Add Vuex modules modules: { crud, }, }); // ``` <br> Pour organiser le modules du store, la méthode la plus simple est de définir en priorité les fonction les plus spécifiques, par exemple la gestion d'un utilisateur. Pour connecter un utilisateur dasn le cadre du projet que vous avez à développer, il faut faire un réquête "GET" dur la collection `user`, cette requête pourait être faite avec l'action `getDataOperation` mais le fait de créer un module `auth` contenant une action `loginOperator` permet d'associer au login un fonctionnalité spécifique - par exemple enregistrer un token. Pour rendre accessible le store dans notre application, nous devons le configurer comme un middleware - sur le même principe que le router - depuis le fichier `main.js` à la racine du dossier `src`. Avec l'intrégartion du router, le fichier `main.js` contient à présent le code suivant : ```jsx= /* [VUE] Main imports Define main imports to create the application */ // Vue imports import { createApp } from 'vue' // App imports import ClientAppEntrypoint from './App.vue'; import AppStore from './store/index'; // Data binding => https://vuex.vuejs.org import AppRouter from "./router"; // Path binding => https://router.vuejs.org // /* [APP] Start Vue.js app Init new Vue.js applilcation */ createApp(ClientAppEntrypoint) .use( AppStore ) .use( AppRouter ) .mount( '#app' ) // ``` <br> Une fois cette configuration fini, les `actions`, les `getters` et les `mutations` sont accessible dans tous les composant de l'application depuis la propriété `$store` : - `$store.dispatch(...)` : exécuter une action - `$store.subscribe( mutations => {...} )` : s'abonner à une mutation - `$store.getters` : récupérer la valeur d'un `state` > Pour utiliser `$store` dans la classe du composant il faut le préfixé avec `this`. <br> #### Angular La manipulation du pattern Flux avec Angular se fait par la mise en place du principe de "Reactiv JS" avec l'utilisation de la librairie `rxjs`. Cette librairie est indépendante d'Angular et peut s'utiliser dans un projet en code natif, son principe est de mettre en place une couche technique qui permet d'intégrer la notion de données dite "observable". Un "observable" est une donnée à laquelle il est possible de s'abonner pour agir à chaque fois que la-dite donnée change. Cette logique est la même que celle mise en place avec Redux ou Vuex, RXjs est la libraire qui a permis aux équipes des deux autres librairies, à mttre en place les principe d'un store. La mise en place du pattern Flux avec Angular est donc légèrement différents de celles vues en React ou Vue.js, nous devons créer "services" dans lequels nous allons configurer à la fois les méthodes qui permettent d'éditer des données obserbales et des méthodes pour organiser la manière d'y accéder. Nous allons commencer par notre premier service qui aura pour objectif de contenir toutes les requêtes HTTP pour consituer un CRUD. Pour ce faire nous utilisons Angular CLI avec la commande suivante pour créer notre service "crud" : ``` ng g s /services/crud/crud ``` <br> Puis nous ouvrons le fichier `crud.service.ts` pour qu'il corresponde au code suivant : ```typescript= /* Imports */ // Angular core import { Injectable } from '@angular/core'; import { HttpClient } from "@angular/common/http"; // /* Definition and export */ @Injectable({ providedIn: 'root' }) export class CrudService { // Inject 'HttpClient' and 'StateService' inside the service constructor( private HttpClient: HttpClient, private StateService: StateService, ){} /* [CRUD] Get operator Send Ajax request to get value from API */ public getOperator( type: String, path: String ): Promise<any> { // Retun Ajax request within HttpClient module return this.HttpClient.get( `http://localhost:9449/${type}/${path}` ).toPromise() .then( apiResponse => Promise.resolve( { apiResponse, type } ) .catch( apiError => Promise.reject( { apiError, type } ); } } // ``` <br> Pour exécuter des requêtes HTTP avec Angular il est recommander de rester dans les logiques proposées par le frameworks. C'est pour cette raison que nous n'utilisons pas Fetch dans notre service mais le module `HttpClient` issu d'Angular et que nous configurons avec la methode `toPromis()` comme étant une requête asynchrone. <br> Nous allons à présent créer un nouveau service pour nos données observables que nous allons appeler `state` pour correspondre aux logiques que nous avons déjà aborder avec React et Vue.js : ``` ng g s /services/crud/crud ``` <br> Puis nous ouvrons le fichier `state.service.ts` pour qu'il corresponde au code suivant : ```typescript= /* Imports */ // Angular core import { Injectable } from '@angular/core'; // Rxjs modules import { BehaviorSubject, Observable } from "rxjs"; // /* Definition and export */ @Injectable({ providedIn: 'root' }) export class StateService { constructor() { } /* [STATE] Definitions Define protected propeties */ protected userinfos: BehaviorSubject<Object> = new BehaviorSubject<Object>( // Get LocalStorage value or return null JSON.parse( window.localStorage.getItem('userinfos') ) || null ); // /* [GETTER] Definitions Define method to get state value */ public getUserinfo(): Observable<Object> { return this.userinfos }; // /* [SETTER] Definition Define global method to update state value */ public setObservableValue( type: String, data: any ): void { // checl type switch( type ){ case 'userinfo': return this.userinfos.next( data ); default: return; } } // } // ``` <br> Nous pouvons observer dans le code ci-dessus la logique mise en place avec RXjs pour créer des données observable, pour ce faire nous importons les modules "BehaviorSubject" et "Observable" qui nous permettent de définir nos états de la manière suivante : ```typescript= protected userinfos: BehaviorSubject<Object> ``` <br> Comme pour avec les autres frameworks, un state ne doit jamais être accessible directement par un composant extérieur, en TypeScript nous devons définir des propriétés `protected` pour les states et des méthode `public` pour définir nos `setters` et nos `getters`. > Le code mis en place pour le `setter` permet d'en factoriser la création. <br> Nos deux service ainsi créé nous devons à présent rendre accessible dans notre application les requête HTTP; pour ce faire nous devons auvir le fichier `app.module.ts` pour y importer le module `HttpClientModule` et le configurer dans le tableau des imports de la façon suivante : ```typescript= ... // Add router import { RouterModule } from "@angular/router"; import { AppRouterModule } from "./app.router"; ... // Definition and export @NgModule({ ... imports: [ ... // Inject module to use HTTP requests HttpClientModule, ... ] }) ... ``` <br> Puis dans chaque composant où nous aurons besoin de nos services, ne devons les importer pour les rendre accessibles et les configurer de la façon suivante pour s'abonner aux données observables : ```typescript= /* Imports */ // Angular components import { Component, OnInit } from '@angular/core'; // App modules import { CrudService } from "../../services/crud/crud.service"; import { StateService } from "../../services/state/state.service"; // /* Definition and export */ @Component({ selector: 'app-home-view', template: ` <section *ngIf="cmpUserinfos === undefined"> <h1>Not logged</h1> </section> <section *ngIf="cmpUserinfos !== undefined"> <h1>Hello User</h1> </section> `, }) export class HomeViewComponent implements OnInit { /* Declaration Define used properties */ public cmpLoginFormValue: Object; public cmpUserinfos: Object; // /* [CMP] Injection Inject value inside the component */ // Inject 'CrudService' and 'StateService' inside the component constructor( private CrudService: CrudService, private StateService: StateService, ) { // Subscribe to state observable update this.StateService.getUserinfo().subscribe( observer => { if( observer !== null ){ this.cmpUserinfos = observer } else{ this.cmpUserinfos = undefined }; }) } // /* [CMP] Methods */ public onSubmit( event: any ): void { /* [SERVICE] Dispatch Call 'getOperator' method whithin CrudService */ this.CrudService.getOperator('users', `?email=julien@dwsapp.io&password=$azertyuiop`) .then( apiResponse => { console.log( apiResponse ) }) .catch( apiError => { console.log( apiError ) }) // } // /* [HOOK] OnInit Called once when the component is initialized (eq: Vue mounted() / React componentDidMount()) */ ngOnInit(): void {} // } // ``` <br> Mise à part les logiques spécifiques à Angular pour importer des composant et les rendre accessible, la notion la plus importante du code ci-dessous et l'abonnement à une donnée obserable dans le `constructor`. Le principe et d'initier dès le chargement du composant l'abonnement pour que la propriété `cmpUserinfos` soit mise à jour une fois la méthode `getOperator` dur service CRUD ai fini son exécution. <br> ### Organiser les composants Une application FRONTEND est construite avec des composants sur le principe des "Poupées Russes" : ils s'intègrent les uns dans les autres. Le principe de base à observer est de faire en sorte que seuls les composants de premier niveau - composant "parent" - contiennent les fonctionnalités principales que les composant de sous-niveau - composant "enfant" - peuvent déclencher.