---
title: Les logiques de développement d une application Web
tags: frontend, support, projet, wip
robots: noindex, nofollow
author: Julien Noyer
---
# Les logiques de développement d'une application Web
*Single page application et DOM dynamique*

## Définition d'une application web
Dans le cadre du développement d'une application web peu importe le fait de la développer avec un framework ou non, il est recommandé de suivre une logique qui intègre la notion de [DOM dynamique](https://fr.wikipedia.org/wiki/HTML_dynamique). Une application Web, ou Single page application, s'affiche sur une seule est unique page HTML et c'est pourquoi ces applications sont la plupart du temps connectées à des API qui distribuent l'information qu'elles doivent afficher. Nous utilisons dans notre application l'[API](https://fr.wikipedia.org/wiki/Interface_de_programmation) [JSONplaceHolder](https://jsonplaceholder.typicode.com) mais peu importe l'[API](https://fr.wikipedia.org/wiki/Interface_de_programmation) utilisée, les méthodes de notre [service](https://angular.io/guide/architecture-services) `CrudService` sont les mêmes.
Nous allons donc développer les différentes logiques inhérentes aux applications web dans notre application [Angular](https://angular.io) mais elles peuvent s'adapter à n'importe quel framework ou au développement natif d'une application web.
## Créer un formulaire de connexion
Nous commençons par créer un élément essentiel à notre application, à savoir le formulaire de connexion. Nous allons intégrer dans le [composant](https://angular.io/guide/architecture-components) `HomePageComponent` un [composant](https://angular.io/guide/architecture-components) enfant qui contiendra notre formulaire. Nous créons donc ce [composant](https://angular.io/guide/architecture-components) avec la commande
```
ng g c shared/formLogin -is
```
Nous allons configurer ce [composant](https://angular.io/guide/architecture-components) pour qu'il puisse gérer un formulaire, nous ouvons le fichier `form-login.component.ts` pour importer les class `FormBuilder`, `FormGroup` et `Validators` :
```typescript=
import { FormBuilder, FormGroup, Validators } from "@angular/forms";
```
Puis nous ajoutons dans la class `FormLoginComponent` le code qui nous permet d'initialiser le formulaire de connexion :
```typescript=
export class FormLoginComponent implements OnInit {
// Declarations
public formData: FormGroup;
// Inject FormBuilder
constructor(
private FormBuilder: FormBuilder
) {}
// Method to reset form
private resetForm = () => {
this.formData = this.FormBuilder.group({
email: [ null, Validators.required ]
});
};
// Start
ngOnInit() {
this.resetForm();
}
};
```
La propriété `formData` est typée avec la class `FormGroup` ce qui permet de l'utiliser pour définir des données requises pour valider un fomualaire. Nous allons à présent éditer la vue HTML du composant `FormLoginComponent` en ouvrant le fichier `form-login.component.html` et en y ajoutant le code suivant :
```htmlmixed=
<form
[formGroup]="formData"
>
<input
formControlName="email"
type="email" name="email" required minlength="5" placeholder="Your email"
>
<button
[disabled]="!formData.valid"
type="submit"
>OK</button>
</form>
```
Dans cee formulaire nous utilisons différrentes directive issues de la class `FormGroup` qui permettent de dynamiquement récupérer les informations du formulaire et d'activer ou non le bouton `submit` selon que le formulaire soit rempli ou non.
Nous allons à présent aborder la soumission du formulaire car nous devons faire en sorte que les données du formulaire puisse être envoyées à la méthode `getUserInfo` du fichier `app.component.ts`. Nous important donc les class `EventEmitter` dans le fichier `form-login.component.ts` :
```typescript=
import { Component, OnInit, EventEmitter } from '@angular/core';
```
Puis nous crééons une nouvelle propriété `formSubmit` qui est un événement généré avec la class `EventEmitter` :
```typescript=
@Output() formSubmit = new EventEmitter();
```
Cette événement `formSubmit` sera utile pour transférer les informations du formulaire du composant `LoginFormComponent` vers le composant `AppComponnent`. Nous allons donc émettre l'événement `formSubmit` lors du submit du formulaire :
```htmlmixed=
<form
[formGroup]="formData"
(submit)="formSubmit.emit(formData.value.email)"
>
...
```
Notre formulaire de connexion eest à présent terminé mais il nous reste une dernière modification dans le fichier `app.module.ts` car pour que toute les fonctionnalités des formulaire soient disponibles dans l'application nous devons importer les class `FormsModule` et `ReactiveFormsModule` :
```typescript=
import { FormsModule, ReactiveFormsModule } from "@angular/forms";
```
Sans oublier de les ajouter dans le tableau des imports :
```typescript=
imports: [
...
FormsModule,
ReactiveFormsModule
],
```
Nous pouvons à présent terminer l'implémentation de notre formulaire de connexion en modifiant le fichier `home-page.component.html` pour qu'il intégre le formulaire :
```htmlmixed=
<app-form-login
(formSubmit)="getUserInfo($event)"
></app-form-login>
```
A cette étape nous n'avons plus besoin d'appeler la méthode `getUserInfo()` dans le composant `HomePageComponent` et formulaire permet de mettre à jour la valeur de la propriété `userInfo` du fichier `observable.service.ts`.
## Connecter un utilisateur
La connexion sécurisée d'un utilisateur intègre nécessairement des logiques serveur pour être efficace, le principe générélament utilisé est de générer un token utilisateur stocké dans un cookie. Nous allons utiliser dans notrre applicatioon une notion plus simple qui consite à enregistrer l'email d'un utilisateur en localstorage mais cette solution n'est absolument pas acceptable en production.
Nous allons donc éditer le fichier `header.component.ts` pour stocker l'email de l'utilisateur au moment ou il se connect :
```typescript=
this.ObservablesService.getUserInfo().subscribe( userDataObserver => {
if(userDataObserver === null) { this.userData = null }
else{
if(userDataObserver.length > 0){
// Set local storage
localStorage.setItem('userEmail', userDataObserver[0].email );
// Update userData value
this.userData = userDataObserver[0];
}
else{
this.userData = null
}
}
})
```
> Nous ajoutons une condition qui vérifie si `userDataObserver` contient au minimum une donnée.
Nous allons à présent éditer la méthode `ngOnInit()` du fichier `app.component.ts` pour tester l'email stocké en localstorage dès le chargement de l'application :
```typescript=
/*
Import
*/
// Angular
import { Component, OnInit } from '@angular/core';
// Inner
import { CrudService } from "./services/crud/crud.service";
//
/*
Componant configuration
*/
@Component({
selector: 'app-root',
template: `
<app-header></app-header>
<router-outlet></router-outlet>
`
})
//
/*
Componant class definition
*/
export class AppComponent implements OnInit {
constructor(
private CrudService: CrudService
){}
async ngOnInit(){
await this.CrudService.readOneItem('users', `email=${localStorage.getItem('userEmail')}`);
};
};
//
```
## Déconnecter un utilisateur
Pour déconnecter un utilisateur de notre application il faut supprimer l'email du localstorage et mettre à jour la valeur de la propriété `userInfo` de la class `ObservableService`. Nous ajoutons tout d'abord un nouveau lien dans le fichier `header.component.html` :
```htmlmixed=
<a href="#" (click)="logout()">Logout</a>
```
Ce nouveau lien déclenche au click une fonction `logout()` que nous devons à pésent créer dans le fichier `header.component.html`. Nous nous après le constructeur eet nous ajoutons le code suivant :
```typescript=
public logout = () => {
// Delete localstorage
localStorage.removeItem('userEmail');
// Set user info obserrbale value
this.ObservablesService.setObservableData('users', null)
}
```
## Réserver des routes aux utilisateurs connectés
Au delà d'afficher ou non des élements dans le [DOM](https://fr.wikipedia.org/wiki/Document_Object_Model) selon les valeurs d'une propriété, il est important de configurer notre module de [routing](https://angular.io/guide/router) pour qu'il puisse empécher l'accès à certaines routes selon des critères spécifiques. Nous allons dans un premier temps mettre à jour notre fichier `header.component.html` pour gérer l'affichage dynamique des élements de la navigation :
```htmlmixed=
<nav>
<ul>
<li *ngIf="!userData">
<a [routerLink]="'/'">Home</a>
</li>
<li *ngIf="userData">
<a [routerLink]="'/connected'">Connected</a>
</li>
<li *ngIf="userData">
<a href="#" (click)="logout()">Logout</a>
</li>
</ul>
</nav>
```
Notre navigationn affiche à présent le lien `home` pour les utilisateurs non-connectés et les liens `Connected` et `Logout` pour les utilisateur connectés. Nous devons ajouter à présent un fichier à la racine de notre dossier `app` pour gérer les régles qui seront utilisées pour rendre accessible ou non une route :
```
touch src/app/auth.guard.ts
```
Nous ouvrons à présent ce fichier et collons le code suivant :
```typescript=
// Imports
import { Injectable } from '@angular/core';
import { CanActivate, Router } from '@angular/router';
// Inner
import { CrudService } from "./services/crud/crud.service";
// Definition
@Injectable({ providedIn: 'root' })
// Export
export class AuthGuard implements CanActivate {
constructor(
private CrudService: CrudService,
private Router: Router,
){}
canActivate(): Promise<any> {
return new Promise( (resolve, reject) => {
this.CrudService.readOneItem('users', `email=${localStorage.getItem('userEmail')}`)
.then( ( apiResponse ) => {
if(apiResponse.length > 0){ return resolve(true) }
else{ this.Router.navigateByUrl('/') };
})
.catch( ( apiResponse ) => this.Router.navigateByUrl('/'))
})
}
}
```
La méthode `canActivate()` renvoi une [promesse](https://developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Objets_globaux/Promise) qui fait à la méhode `readOneItem()` de la class `CrudService`, selon les informations obtenues la [promesse](https://developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Objets_globaux/Promise) est résolue pour rendre une route accesssible ou rediriger l'utilisateur vers la page d'accueil.
Pour mettre à jour le module de [routing](https://angular.io/guide/router) devons importer dans le fichier `app.routing.ts` la class `AuthGuard` afin d'utiliser la méthode `canActivate()` dans les routes que nous souhaitons protéger :
```typescript=
import { AuthGuard } from "./auth.guard";
```
Ensuite nous éditons la constante `AppRouterModule` pour ajouter dans les routes `connected` et `crud` la propriété suivante :
```typescript=
canActivate: [ AuthGuard ]
```
Toutes les routes de notre application sont à présent configurés et seuls les utilisateurs connectés auront accès à des routes spécifiques.
## Rediriger un utilisateur
Dernière étapes de la gestion des routes protégées, nous allons à présent éditer le fichier `app.component.ts` pour pouvour rediriger automatiquement les utilisateurs connectés vers la route `connected`. Pour ce faire nous devons dans un premier temps importer la class `Router` dans le fichier :
```typescript=
import { Router } from '@angular/router';
```
Pour ensuite modifier la méthode `ngOnInt()` de cette façon :
```typescript=
async ngOnInit(){
const userInfo = await this.CrudService.readOneItem('users', `email=${localStorage.getItem('userEmail')}`);
// Check user info
if(userInfo.length > 0){
// Change route endpoint
this.Router.navigateByUrl('/connected');
};
};
```
Nous allons importer également la class `Router` dans le fichier `home-page.component.ts` pour y modifier la la méthode `getUserInfo()` de cette façon :
```typescript=
public getUserInfo = async (email: String ) => {
// Get user infos
const userInfo = await this.CrudService.readOneItem('users', `email=${email}`);
// Check user info
if(userInfo.length > 0){
// Change route endpoint
this.Router.navigateByUrl('/connected');
}
};
```
## Injecter des informations dans un composant enfant
Nous allons à présent créer un [composant](https://angular.io/guide/architecture-components) enfant que nous utiliserons dans le [composant](https://angular.io/guide/architecture-components) `ConnectedPageComponent` pour afficher une liste d'articles dans la route. Mais dans un premier temps, nous allons ajouter dans le [composant](https://angular.io/guide/architecture-components) `ConnectedPageComponent` une méthode pour charger la liste des articles. Nous ouvrons donc le fichier `connected-page.component.ts` pour importer le service `CrudService`, déclarer une propriété `postCollection` et ajouter la méthode `getPostList()` :
```typescript=
/*
Import
*/
// Angular
import { Component, OnInit } from '@angular/core';
// Inner
import { CrudService } from "../../services/crud/crud.service";
//
/*
Componant configuration
*/
@Component({
selector: 'app-crud-page',
templateUrl: './crud-page.component.html',
})
//
/*
Componant class definition
*/
export class CrudPageComponent implements OnInit {
/*
Declarations
*/
public postCollection: any;
constructor(
private CrudService: CrudService
){}
//
/*
Methods
*/
// Method to get the post list
public getPostList = async () => {
this.postCollection = await this.CrudService.readAllItems('posts');
};
//
/*
Hooks
*/
ngOnInit(){
// Get the poost list
this.getPostList();
};
//
};
//
```
Nous appelons la méthode `getPostList()` dans la méthode `ngOnInit()` afin de charger les informations dès le chargement du [composant](https://angular.io/guide/architecture-components). Nous pouvons à présent créer le [composant](https://angular.io/guide/architecture-components) pour afficher les articles avec la commande suivante :
```
ng g c shared/itemPost -is
```
Pour importer des informations depuis un [composant](https://angular.io/guide/architecture-components) parent il faut utiliser le [décorateur](https://medium.com/@madhavmahesh/list-of-all-decorators-available-in-angular-71bdf4ad6976) `@Input()`, nous ouvrons donc le fichier `item-post.component.ts` pour y coller le code suivant :
```typescript=
/*
Import
*/
// Angular
import { Component, OnInit, Input } from '@angular/core';
//
/*
Componant configuration
*/
@Component({
selector: 'app-item-post',
templateUrl: './item-post.component.html'
})
//
/*
Componant class definition
*/
export class ItemPostComponent implements OnInit {
// Input data from parent component
@Input() post: any;
constructor(){}
ngOnInit(){};
};
//
```
Puis nous ouvrons le fichier `item-post.component.html` pour ajouter les balises HTML suivantes :
```htmlmixed=
<h2 [innerText]="post.title"></h2>
<p [innerText]="post.body"></p>
```
Pour finir, nous ouvrons le fichier `connected-page.component.html` pour utiliser le [composant](https://angular.io/guide/architecture-components) `ItemPostComponent` dans une boucle en lui injectant les informations relatives à un item de la propriété `postCollection` :
```htmlmixed=
<article *ngFor="let item of postCollection">
<app-item-post
[post]="item"
></app-item-post>
</article>
```
A la fin de cette étape la liste des articles s'affiche dans la route `/connected` grâce à un [composant](https://angular.io/guide/architecture-components) qui est alimenté par les donnnées issues d'une requête [HTTP](https://fr.wikipedia.org/wiki/Hypertext_Transfer_Protocol) déclenchée dans le [composant](https://angular.io/guide/architecture-components) dont il est l'enfant.
## Ressources

> Index des liens vers les sujets traités dans ce document
- [**Angular** https://angular.io](https://angular.io)
- [**TypeScript** https://www.typescriptlang.org/](https://www.typescriptlang.org/)
- [**CLI** https://fr.wikipedia.org/wiki/Interface_en_ligne_de_commande](https://fr.wikipedia.org/wiki/Interface_en_ligne_de_commande)
- [**Angular CLI** https://cli.angular.io/](https://cli.angular.io/)
- [**Component** https://angular.io/guide/architecture-components](https://angular.io/guide/architecture-components)
- [**Routeur** https://angular.io/guide/router](https://angular.io/guide/router)
- [**HTTP** https://fr.wikipedia.org/wiki/Hypertext_Transfer_Protocol](https://fr.wikipedia.org/wiki/Hypertext_Transfer_Protocol)
- [**Endpoints** https://en.wikipedia.org/wiki/Web_API](https://en.wikipedia.org/wiki/Web_API)
- [**DOM** https://fr.wikipedia.org/wiki/Document_Object_Model](https://fr.wikipedia.org/wiki/Document_Object_Model)
- [**Directive routerLink** https://angular.io/api/router/RouterLink](https://angular.io/api/router/RouterLink)
- [**Angular décorator** https://medium.com/@madhavmahesh/list-of-all-decorators-available-in-angular-71bdf4ad6976](https://medium.com/@madhavmahesh/list-of-all-decorators-available-in-angular-71bdf4ad6976)
- [**Angular services** https://angular.io/guide/architecture-services](https://angular.io/guide/architecture-services)
- [**Web asynchrones** https://fr.wikipedia.org/wiki/Ajax_(informatique)](https://fr.wikipedia.org/wiki/Ajax_(informatique))
- [**Single page application** https://en.wikipedia.org/wiki/Single-page_application](https://en.wikipedia.org/wiki/Single-page_application)
- [**Promise** https://developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Objets_globaux/Promise](https://developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Objets_globaux/Promise)
- [**Injectables** https://angular.io/api/core/Injectable](https://angular.io/api/core/Injectable)
- [**Observables** https://rxjs-dev.firebaseapp.com/guide/observable](https://rxjs-dev.firebaseapp.com/guide/observable)
- [**HttpClientModule** https://angular.io/guide/http](https://angular.io/guide/http)
- [**CRUD** https://fr.wikipedia.org/wiki/CRUD](https://fr.wikipedia.org/wiki/CRUD)
- [**JSON** https://fr.wikipedia.org/wiki/JavaScript_Object_Notation](https://fr.wikipedia.org/wiki/JavaScript_Object_Notation)
- [**DOM dynamique** https://fr.wikipedia.org/wiki/HTML_dynamique](https://fr.wikipedia.org/wiki/HTML_dynamique)
- [**API** https://fr.wikipedia.org/wiki/Interface_de_programmation](https://fr.wikipedia.org/wiki/Interface_de_programmation)