Angular

Prérequis

Le site de référence est https://angular.io/

On admet que vous disposez des outils nécessaires pour commencer la programmation Angular.

Afin d’aborder Angular, nous allons créer une application et étudier certains de ses particularités. L’usage courant consiste à utiliser la ligne de commande, en particulier pour bénéficier de la génération automatique de code. L’outil adapté est Angular CLI (Command Line Interface).

Angular CLI (Command Line Interface) est un outil en ligne de commande pour Angular qui facilite, entre autres, la création et la configuration de nouveaux projets Angular, ainsi que la génération de composants, de services, et d’autres éléments de l’application. Angular CLI permet également d’exécuter des tâches de développement courantes, telles que la compilation, le débogage et le déploiement de l’application.).

Bien que Angular CLI ne soit pas obligatoire, il facilite grandement le travail !

Installation de Angular CLI

En ligne de commande (ouvrir un terminal) tapper la commande qui installe Angular CLI sur votre compte utilisateur (inutile d’être en admin)

npm install -g @angular/cli

Vérification

On demande la version actuelle de Angular CI par la commande ng version. Exemple.

ng version

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


Angular CLI: 15.1.2
Node: 16.15.1
Package Manager: npm 9.2.0
OS: linux x64

Angular:
...

Package                      Version
------------------------------------------------------
@angular-devkit/architect    0.1501.2 (cli-only)
@angular-devkit/core         15.1.2 (cli-only)
@angular-devkit/schematics   15.1.2 (cli-only)
@schematics/angular          15.1.2 (cli-only)

Création d’une première application

Une application de type Frontend est souvent qualifiée de mono-page, ou SPA (Single Page Application). Elle s’exécute principalement dans le navigateur et qui communique gééralement avec le serveur qui l’a délivrée, via des services web.

Les avantages d’une SPA par rapport à une application web standard sont :

  • de fluidifier l’expérience utilisateur en évitant de charger une nouvelle page à chaque action de l’utilisateur ;

  • de permettre de s’exécuter localement sur le navigateur de l’utilisateur.

Les frameworks servant de base à une application SPA sont généralement développés avec les technologies web classiques : HTML5, CSS3, Javascript ou Typescript.

Création d’une application Angular en ligne de commande :

ng new projet1 --style=css --routing=false --strict

Les options indiquent le nom du projet (projet1), le type des fichiers de style (ici css), la non-application des routes de l’application, et active le mode strict selon les recommandations de la team TypeScript afin de limiter les bugs et de produire du code maintenable.

La commande a créé un dossier du nom du projet1, et 2 sous-dossiers :

.
└── projet1
    ├── node_modules
    └── src
  • Le dossier node_modules contient les librairies JavaScript développées par des tiers (dont Google). Dans la version actuelle (2023) ce dossier contient 540 sous-dossiers…​

Vous ne devez jamais retoucher le code des librairies de node_modules !
  • Le dossier src (source). C’est là où vous mettrez le code de votre application.

Voici ce que contient le dossier src, à l’état initial (résultat de la commande CLI)

projet1/src
src/
├── app (1)
│   ├── app.component.html (2)
│   ├── app.component.css (3)
│   ├── app.component.spec.ts (4)
│   ├── app.component.ts (5)
│   └── app.module.ts (6)
├── assets
├── favicon.ico
├── index.html
├── main.ts
└── styles.scss
1 app est le composant principal de l’application. Comme tout composant Angular, il est composé de 4 parties principales :
2 Le code du rendu visuel (.html)
3 Le code de la mise en page (.css ou .scss)
4 Le code de la classe du composant (.ts - une classe TypeScript décorée)
5 Le code des tests unitaires (.ts)

Le composant principal

projet1/src/app/app.component.ts
import { Component } from '@angular/core';

@Component({ (1)
  selector: 'app-root', (2)
  templateUrl: './app.component.html', (3)
  styleUrls: ['./app.component.css'] (4)
})
export class AppComponent { (5)
  title = 'projet1';  (6)
}
1 Cette annotation (décorateur) s’applique à la classe déclarée dans ce fichier
2 Le sélecteur HTML qui identifie ce composant dans une page HTML
3 Le code HTML de la vue de ce composant
4 Le code CSS associé
5 Déclaration de la classe du composant (observez la convention de nommage)
6 La propriété title est définie ici.

Le module du composant principal

app.module.ts est un fichier qui déclare les dépendances et les relations entre les différents éléments de l’application. Cette pratique, imposé par Angular, favorise la lisibilité et donc maintenance de l’application.

projet1/src/app/app.module.ts
import { NgModule } from '@angular/core'; (1)
import { BrowserModule } from '@angular/platform-browser';

import { AppComponent } from './app.component';

@NgModule({ (2)
  declarations: [ (3)
    AppComponent
  ],
  imports: [ (4)
    BrowserModule
  ],
  providers: [], (5)
  bootstrap: [AppComponent] (6)
})
export class AppModule { } (7)
1 Les imports permettre d’importer les modules nécessaires pour ce module. Ici, on importe NgModule de @angular/core et BrowserModule de @angular/platform-browser. Ces modules sont nécessaires pour exécuter l’application dans un navigateur.
2 Le décorateur (une annotation) @NgModule de la class AppMoudle est paramétrable :
3 déclarations définit les composants (entre autres) qui appartiennent à ce module. Ici, on déclare AppComponent importé précédemment.
4 imports définit les modules qui sont utilisés par ce module. Ici, on importe BrowserModule pour permettre l’exécution de l’application dans un navigateur.
5 providers (fournisseurs): définit les services qui sont utilisés par ce module. Ici, il n’y a pas de fournisseurs définis.
6 bootstrap définit le composant qui sera utilisé comme point d’entrée pour l’application. Ici, on utilise AppComponent comme point d’entrée.
7 export permet d’exporter le module pour qu’il puisse être utilisé par d’autres parties de l’application.

Ceci est un exemple d’un module de base, vous pourriez avoir besoin d’ajouter des déclarations, des imports, des fournisseurs ou des configurations supplémentaires selon les besoins de votre application.

Construction de l’application

La commande ng build est une commande d’Angular CLI qui permet de compiler et de construire l’application pour la production. Elle utilise la configuration de l’application définie dans le fichier angular.json pour compiler les sources TypeScript en JavaScript, optimiser les images et les fichiers de style, et générer les fichiers de sortie dans un format optimisé pour la production tels que les fichiers HTML, CSS et JavaScript minifiés. Le résultat est placé généralement dans un dossier appelé "dist" ou "build" et contient tous les fichiers nécessaires pour l’exécution de l’application par un navigateur.

/dist
dist/projet1/
├── 3rdpartylicenses.txt
├── assets
│   └── images
│       ├── logo.png
│       └── tortoro.png
├── favicon.ico
├── index.html (1)
├── main.cf9d357b51189202.js (2)
├── polyfills.ae26d754bd3db637.js
├── runtime.91eea6741d616f29.js
└── styles.8392c6a69aec60ad.css (3)
1 Le point d’entrée de l’application
2 Le code js et les bibliothèques utilisées par l’application. Il n’y a plus de code typescript, et polyfills se charge de d’implémenter les fonctions absentes sur certaines machines.
3 Les feuilles de script sont regroupée en un seul fichier.

Lancement de l’application

Nous aurons besoin d’un serveur HTTP pour rendre disponible l’application à des navigateurs web. Angular CLI dispose d’une commande bien pratique, nommée serve.

La commande ng serve compile (comme le build, mais en mémoire) le projet et lance un serveur HTTP qui se charge de servir l’application aux requêtes des navigateurs web. Il permet également de surveiller les modifications des fichiers et de relancer automatiquement le serveur lorsque des modifications sont détectées.

lancement
ng serve
first
Figure 1. Exemple (extrait) d’exécution dans le navigateur

Travaux pratiques

  1. Faire en sorte que l’application ne présente que le message Hello World lorsqu’elle est lancée, à l’image de la capture écran ci-dessous, tout en conservant l’image SVG de la fusée et son texte (modifié pour l’occasion)

hello world
Figure 2. Exemple
  1. Faire en sorte, sans changer le nom du projet, que le texte de l’onglet du navigateur soit Hello World et non pas Projet1

Ajout d’un service

Nous allons faire en sorte que l’adresse IP du client soit affichée sur la page du composant principal.

Cette valeur peut être déterminée

  • par le serveur HTTP qui a servi l’application au client (en réponse à une requête HTTP du client)

  • par l’application client elle-même (JS)

Nous retiendrons la deuxième version, qui a le mérite d’être dynamique (le client peut changer d’IP, ce qui ne serait pas connu du serveur)

Par défaut, le client ne peut exploiter les ressources systèmes de la machine sur lequel le navigateur est excécuté. Il devra donc demander son IP publique à un service internet (une application web).

Exemple de service pour connaître son IP (n’hésitez pas à le tester):

http://api.ipify.org/?format=json

Création du service

On appelle service une classe qui propose des fonctionnalités utilisables par différents composants.

L’application actuelle n’a qu’un seul composant. Nous pourrions donc nous dispenser de créer un service et coder ce service dans la classe du composant. Mais faire ainsi n’est pas dans les bonnes pratiques, car on chargerait ainsi notre composant de trop de responsabilités.

La commande suivante permet de créer un service (rappel, se placer à la racine de l’application) :

ng generate service service/Ip
En formualant le nom du servie avec une expression de chemin, service/Ip, on demande à Angular de placer le service Ip dans un dossier spécial (src/app/service) .

Le service src/app/service/ip.service.ts (138 bytes) est alors créé.

src/app/service/ip.service.ts
import { Injectable } from '@angular/core';

@Injectable({
  providedIn: 'root'
})
export class IpService { (1)

  constructor() { }
}
1 Notez la convention de nommage : Le suffixe Service a été ajouté automatiquement au nom du service (tout comme le suffixe Component précédemment)

L’instance de ce service est injectable dans d’autres composants par simple déclaration.

C’est une application du principe d’IOC (Inversion Of Control). Voir https://fr.wikipedia.org/wiki/Inversion_de_contr%C3%B4le.

Nous allons spécialiser ce service (actuellement il ne fait rien).

Ce service a besoin de lancer des requêtes HTTP vers api.ipify.org. Pour cela nous allons utilser un autre service dédié à cette tache : HttpClient du module intégré à angular : @angular/common/http.

Cette dépendance doit être déclarée au niveau global, dans le fichier app.module.ts vu précédemment.

src/app/app.module.ts (extrait)
import { HttpClientModule } from "@angular/common/http";
...

imports: [
    BrowserModule,
    HttpClientModule
]

...

⇒ On vient d’ajouter le service HttpClientModule aux services importés.

Intéressons-nous maintenant à notre nouveau service, car actuellement il ne fait rien ! Nous avons besoin d’il lance une requête HTTP sur un serveur extérieur afin d’obtenir l’adresse IP publique de la machine sur lequel il s’exécute.

Nous ajoutons une méthode portant un nom qui témoigne de sa fonction : getIpAddress. Son type de retour n’est pas une string (ce qui aurait dû être en toute logique), car on ne sait pas combien de temps peut prendre un service web distant pour répondre. C’est pourquoi la méthode get du composant HttpClient nous retourne un Observable, qui est un composant utilisé dans une logique de programmation réactive qui facilite la composition de code asynchrone ou basé sur des événements par callbacks (https://rxjs.dev/guide/overview).

src/app/service/ip.service.ts
import { Injectable } from '@angular/core';
import { HttpClient  } from '@angular/common/http';
import { Observable } from 'rxjs';

@Injectable({
  providedIn: 'root'
})
export class IpService  {
  constructor(private http:HttpClient) { }  (1)

  public getIPAddress() : Observable<any> (2)
  {
    return this.http.get("http://api.ipify.org/?format=json") (3)
  }
}
1 La propriété nommée http est déclarée et sera automatiquement valorisée à la création d’une instance de cette classe. C’est une syntaxe courte (un sucre syntaxique).
2 Déclaration d’une méthode qui retourne une référence vers un objet Observable, qui est le type de retour de la méthode get utilisée ici.Observable<any>, any signifie que nous n’avons pas défini de type de données pour ce retour (pas très propre, mais on l’acceptera pour cette démo).
3 this.http fait référence à la propriété de l’instance (celle déclarée en paramètre du constructeur)

Voilà, nous venons de créer une classe de type service. Nous allons maintenant en faire usage.

Utilisation du service

Nous allons intégrer ce service dans app.component.ts. Cela passe par une importation (déclaration de dépendance) et une injection d’une instance de notre service dans le constructeur.

Dans la foulée, nous implémentons la méthode getIpAddress() :

src/app/app.component.ts
import { Component } from '@angular/core';
import { IpService } from './service/ip.service';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  constructor(private ipService:IpService){}

  title: string = 'projet titre';

  ipAddress: string = 'Inconnue IP';

  getIP()
  {
    this.ipService.getIPAddress().subscribe((res:any)=>{
      this.ipAddress=res.ip;
    });
  }
}

Il ne nous reste qu’à lier la variable ipAddress à la vue :

src/app/app.component.html
...
 <h2>{{ ipAddress }}</h2>
...

Ce qui donne.

InconnueIp
Figure 3. localhost:4200

Travaux pratiques

Il semblerait que l’instruction {{ipAddress}} dans la vue nous donne la valeur par défaut de la propriété de même nom de la classe TypeScript du composant. Trouvez un moyen pour corriger cela.

Il existe plusieurs façons d’opérer. Faites marcher votre logique en premier, puis, si cela ne suffit pas, passez en mode connecté internet.

  1. Faire en sorte que l’application présente l'adresse IP du client web sur la page principale, et non "Inconnue IP".

  1. Faire en sorte que la page principale présente à l’utilisateur la date, l’heure et la minute actuelle.