Clean Architecture et Serverless : retour sur le Devfest 2023
Dernier REX de la série, Gauthier, Développeur Web & Ops, nous raconte son expérience lors du DevFest de Lille 2023 !
Dans cet article, nous allons construire une application Angular de zéro que nous connecterons à un serveur GraphQL avec Apollo Angular.
L'application front-end aura pour simple fonctionnalité d'afficher les détails d'un utilisateur (prénom et nom) provenant du back-end en GraphQL.
Voici une capture d'écran du résultat dans un navigateur :
Et un aperçu du schéma d'architecture de cette application :
La partie back-end qui renvoie le nom et prénom de l'utilisateur ne sera pas couverte dans cet article. Vous pouvez donc utiliser une API graphQL de votre choix, ou tout comme nous, en construire une sur une base NestJS en suivant cet article par exemple.
Pour commencer, il vous faut installer le CLI Angular (s'il n'est pas déjà installé sur votre machine), et générer un nouveau projet Angular :
npm install -g @angular/cling new angular-graphql-demo
Lors de l'installation, pas besoin d'ajouter le routing, celui-ci n'est pas couvert en détails dans la suite de l'article.
Pour le style, nous resterons simple en gardant un format css basique.
Pour plus de détails sur ces commandes, ou sur le fonctionnement du framework, je vous invite à consulter la documentation officielle d'Angular.
Nous allons maintenant créer le UserComponent qui aura pour fonction d'afficher les informations (nom & prénom) d'un utilisateur :
ng generate component User
Voici comment construire et afficher le UserComponent au sein de l'application. Pour l'instant, nous allons lui donner des informations utilisateur statiques.
// user.component.tsimport { Component, OnInit } from "@angular/core";@Component({ selector: "app-user", templateUrl: "./user.component.html", styleUrls: ["./user.component.css"],})export class UserComponent implements OnInit { user = { firstName: "", lastName: "" }; constructor() {} ngOnInit(): void { this.user.firstName = "Jean"; this.user.lastName = "Bonbeurre"; }}<!-- user.component.html --><h1>Informations utilisateur</h1><h2>Nom :</h2><span>{{ user.firstName }}</span><h2>Prénom :</h2><span>{{ user.lastName }}</span><!-- app.component.html --><app-user></app-user>
Nos disposons d'ores et déjà d'une application simpliste permettant d'afficher les détails d'un utilisateur, il est maintenant temps de la dynamiser à l'aide d'informations récupérées via GraphQL.
Pour communiquer entre notre application front Angular à GraphQL, nous allons installer apollo-angular avec la commande suivante :
ng add apollo-angular
Lors de cette installation, nous devons déclarer le point d'entrée de notre serveur GraphQL. Si vous avez construit votre API back-end comme suggéré dans l'introduction, le endpoint est généralement "/graphql".
La résolution de ce endpoint sera faite à l'aide de la configuration de proxy que nous verrons un peu plus tard.
L'installation crée automatiquement un module dédié à GraphQL (dans le fichier graphql.module.ts), qui permet la mise en place de la connexion de notre client au serveur GraphQL externe. Ce module GraphQL fait appel à HttpLink qui requiert lui-même HttpClient, donc le HttpClientModule doit lui aussi être importé dans notre app module (cela est fait automatiquement lors de l'installation).
Afin de permettre à notre application Angular de communiquer avec notre API GraphQL, nous allons créer un fichier proxy.config.json afin d'indiquer à Angular de rediriger les appels à /graphql vers notre API démarrée localement (voir la documentation Angular pour plus de détails).
Voici la configuration pour notre application :
// proxy.config.json{ "/graphql": { "target": "http://localhost:3000" }}
Il ne nous reste plus qu'a ajouter un script dans le package.json afin de simplifier le lancement de l'application Angular avec notre configuration de proxy :
"start:proxy": "ng serve --proxy-config proxy.config.json"
NB: Les requêtes CORS doivent êtres autorisées du côté de l'API GraphQL à laquelle vous souhaitez vous connecter.
Nous allons commencer par ajouter un UserService dans notre application Angular. Ce service a pour objectif de faire appel au back-end en GraphQL et de retourner les données récupérées.
La création d'un service spécifique nous permettra d'appliquer les principes de séparation de responsabilités entre le composant, responsable d'afficher la donnée, et un service, responsable de la fournir.
Voici le contenu du fichier user.service.ts :
// user.service.tsimport { Injectable } from "@angular/core";import { Apollo } from "apollo-angular";import { Observable } from "rxjs";import { ApolloQueryResult, gql } from "@apollo/client/core";const GET_USER_QUERY = gql` query ($id: String!) { user(id: $id) { id firstName lastName } }`;export type User = { id: string; firstName: string; lastName: string;};export interface UserQueryResponse { user: User | null; errors: any;}@Injectable({ providedIn: "root",})export class UserService { constructor(private apollo: Apollo) {} getUserFromId(id: string): Observable<ApolloQueryResult<UserQueryResponse>> { const userObservable = this.apollo.watchQuery<UserQueryResponse>({ query: GET_USER_QUERY, variables: { id: id }, }).valueChanges; console.log(userObservable); return this.apollo.watchQuery<UserQueryResponse>({ query: GET_USER_QUERY, variables: { id: id }, }).valueChanges; }}
Pour faire appel à l'API externe GraphQL, le service aura besoin du client Apollo-Angular qui doit donc être présent dans son constructeur.
La fonction getUserFromId a pour rôle d'interroger le serveur back-end via une query GraphQL (et ses variables associées).
L'envoi de cette query GraphQL est fait à l'aide de la librairie cliente "Apollo-Angular", et notamment de la fonction "watchQuery".
En Angular, les Observables sont favorisés plutôt que les promesses pour la gestion des valeurs délivrées de manière asynchrone.
watchQuery nous retourne un résultat de type "QueryRef" qui fournit un Observable à l'aide de la propriété "valueChanges". Nous retournons donc cet Observable dans notre Service afin de permettre à ses consommateurs de s'y abonner.
Pour commencer, il nous faut connecter le service au composant qu'il alimente. Pour cela, nous l'ajoutons aux providers dans les metadata du composant.
Notre service construit précédemment renvoie un Observable. Un Observable fonctionne un peu comme une promesse. Nous nous y abonnons afin qu'il nous notifie d'un changement de valeur ou d'état. Un seul abonnement suffit pour obtenir plusieurs valeurs au fil du temps, contrairement aux promesses qui ne se résolvent qu'une seule fois (plus de détails sur la documentation de RxJS).
Une fois abonnés à l'Observable (méthode "subscribe"), nous ré-attribuons les données reçues aux propriétés de notre composant.
Nous pourrons donc afficher les valeurs de ces propriétés dans le HTML dans notre navigateur.
Voici les détails du code du composant user, relié à son service :
//user.component.tsimport { Component, OnInit } from "@angular/core";import { ApolloQueryResult } from "@apollo/client/core";import { User, UserQueryResponse, UserService } from "./user.service";@Component({ selector: "app-user", templateUrl: "./user.component.html", styleUrls: ["./user.component.css"],})export class UserComponent implements OnInit { user: User | null = null; loading = true; error: any; constructor(private userService: UserService) {} ngOnInit(): void { this.userService .getUserFromId("1") .subscribe((result: ApolloQueryResult<UserQueryResponse>) => { this.user = result?.data?.user; this.loading = result.loading; this.error = result?.errors; }); }}
Nous avons construit une application Angular qui est capable, grâce au client apollo-angular, de récupérer au sein de son composant des données d'une API externe en GraphQL. Voici le lien vers l'application complète avec tous les fichiers.
A ce stade, l'application est fonctionnelle, mais dans un soucis de qualité, nous nous intéresserons dans un prochain article à l'écriture de tests unitaires sur le composant et son service en rajoutant Jest à cette stack.
Nous croyons en un nouveau modèle de consulting où l’excellence commence par l’écoute, le partage et une vraie vision