Angular, Framework typescript

Angular, initialement librairie javascript dans sa première version, designe aujourd’hui un framework typescript/javascript pour le développement d’applications web. « Typescript » car c’est le langage de programmation utilisé dans ce type de projet et « Javascript » car c’est notamment dans ce langage que doit être compilé le projet Angular pour être interprété par le navigateur.

Présentation

Angular est fondé autour de plusieurs technologie/langage :

  • Le HTML, pour l’affichage des données
  • le CSS/SCSS pour la stylisation
  • Le Typescript, langage à typage strict proche du javascript

Il permet la création de projet professionnel s’appuyant sur différents concepts :

  1. Les Components : les components sont les composants de base d’une application Angular. Une application Angular peut être vue comme une arborescence de components avec AppComponent comme component racine.
  2. les Directives : Classe typescript qui ajoute du comportement à l’application Angular.
    On peut créer une directive « EnhanceInputText » qui va appliquer un comportement global à tous les InputText par exemple, tant que le component l’utilisant importe la directive.
  3. Les Services pour l’exploitation simplifiée de données au fil de l’application
  4. Le Routing pour la construction et l’acheminement rapide des pages.

Enfin il s’appuie sur des outils existant :

  • NPM, le Gestionnaire de Package pour Node.js
  • Node.js sur lequel le CLI Angular puise des briques
  • …et le CLI Angular permettant d’effectuer les opération de base sur un projet angular

Construction du projet

Après installation de npm et du CLI angular, un projet s’initialise ainsi (pour un projet utilisant SCSS sans aucune classe de tests) :

# création d'un projet via le CLI
ng new <project-name> --style=scss --skip-tests=true

On trouve en dossier racines utiles :

  • /src : qui contiendra le coeur de l’app : fichier de modèle, de service, components etc.
  • /public : qui pourra contenir les images (à partir de Angular v19 – avant existait encore un dossier assets/)
  • /dist : qui contiendra la version compilée du site

Le projet vient avec un composant initial « App » qui est importé dans le fichier src/index.html, fichier de base de l’application web angular.

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>Snapface</title>
  <base href="/">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <link rel="icon" type="image/x-icon" href="favicon.ico">
</head>
<body>
  <!-- "app-root" est le selecteur du composant "app" -->
  <app-root></app-root>
</body>
</html>

src/<component>

Chaque création de component peut se faire par ligne de commande :

# Création d'un nouveau component
ng generate component <component-name>

# C'est équivalent à ceci :
ng g c <component-name>

Un nouveau composant « component-name » sera créé dans dans le dossier src/app/component-name/ et viendra avec 3 fichiers. Son nom au sein d’angular sera ComponentNameComponent.

Il dispose aussi du décorateur @Component est d’un sélecteur (par defaut : app-component-name).

Nomenclature du composant

Le nom du composant et son selecteur seront determiné automatiquement à partir du nom qu’on lui donne. ex : « ma-navigation » apparaîtra dans un dossier /app/ma-navigation/ sera déclaré en tant que classe MaNavigationComponent et son selecteur par defaut sera app-ma-navigation.

Voici les fichiers de bases qui composent un composant :

  • nom.component.ts : la classe typescript, sorte de controller, qui contiendra les information pour identifier la route, importer les classes utiles, définir les propriété et methode etc.
  • nom.component.html : qui identifiera la Vue, c’est à dire la page affichant les informations
  • nom.component.scss : le fichier de style propre au composant

Component : Classe Typescript

La classe typescript de base peut se représenter ainsi :

//"implements" l'interface OnInit, bonne pratique
export class NomComponent implements OnInit 
{
  title!:string; 
  //Le "!" est un "bang" : il indique à Typescript que la propriété sera initialisé plus
  tard sinon Typescript indique une erreur
  description!:string;
  createdAt!:Date;
  number!:number;
  imagePath!:string;

  //Initialisateur de la classe executé après le constructeur (mais avant la génération du
  template/js)
  ngOnInit(): void {
    this.title = "titre";
    this.description="Texte de description";
    this.createdAt = new Date();
    this.number = 0;

    //Se trouve dans le dossier /public en v19
    this.imagePath = "img/image.png";
  }
  maMethode(): void {
    this.number++;
  }
}

L’interface OnInit déclare la signature de méthode ngOnInit() appelé automatiquement après la méthode constructor() de la classe définissant le composant. C’est une bonne pratique de la dédier aux étapes d’initialisation du composant.

Component : Affichage HTML/CSS

Angular utilise 3 moyens pour intégrer du contenu dans une page web depuis la classe typescript de son composant : l’interpolation de string {{ value }}, l’event binding (event)="value" et l’attribute binding [attr]="value".

<!-- "String interpolation" -->
<h2>{{title}}</h2>

<!-- "Attribute binding" -->
<img [src]="imagePath" [alt]="title" />
<p>{{description}}</p>
<p>Créé le {{createdAt}}</p>
<p>
  <!-- "Event binding" -->
  <button (click)="OnIncrement()">Increment !</button> {{number}}
</p>

De la même manière que le propose Symfony, on pourra utiliser sur Angular des control flow blocks : ils permettent en HTML d’utiliser des structures conditionnelles ou itératives. Aussi on va trouver des Pipes qui vont servir à formatter l’affichage des données sans les modifier. :

<!-- Control flow block if/else -->
@if(hasLocation){    
    <p>
      <!-- Ici on utilise le signe pipe pour formater l'affichage -->
      {{location || uppercase}}
    </p>} 
  @else {
      <p>Emplacement inconnu</p>
    }  

<!-- Le paramètre  track  permet à Angular d'identifier les différents éléments du tableau grâce à une propriété unique -->
@for(anElement of Elements; track anElement.title)
{
  <!-- Ici on fournit au composant elementDisplayer l'element "anElement" à son attribut
  "elementAttr" qui a été rendu exposable via le decorateur @Input  -->
  <elementDisplayer [elementAttr]="anElement " />
}

Le CSS d’Angular a ceci de particulier que la fiche CSS d’un composant lui est propre, et uniquement au composant. Ainsi une classe css définissant une classe .monStyle n’aura aucune influence sur un composant enfant ! Pour definir de manière global, il faut passer par /src/app/styles.scss.

Directives

D’un autre côté les directives vont permettre d’ajouter du comportement. On peut en créer soit même pour étendre le fonctionnement d’une balise html (<input /> par exemple) ou en utiliser des existantes.

On en distingue deux connus : ngStyle et ngClass

<!-- La proprieté personnalisé "ngClass", proposée par la directive Angular "NgClass", permet l'ajout de classe selon condition : {nomclasse:condition booleene}-->
<div class="face-snap-card" [ngClass]="{snapped:isSnapped}">  
<!-- La proprieté personnalisé "ngStyle", proposé par la directive angular "NgStyle" permet l'édition de style avec un formalisme clair key/valeur. -->
    <span [ngStyle]="{color:'rgb(0,100,0)'}">
      {{number}}</span>
  <!-- (...) -->
</div>

Attention toutefois, comme d’habitude, ces directives doivent être importées dans le composant qui les exploite :

import {NgClass, NgStyle} from '@angular/common';

@Component({
  standalone:true,
  selector: 'elementDisplayer',
  imports: [NgStyle,NgClass],
  templateUrl: './element-displayer.component.html',
  styleUrl: './element-displayer.component.scss'
})

Route

Le fichier /src/app/app.routes.ts contiendra aussi le mapping des routes de l’application.

// Le type Routes est un tableau de "Route", classe définissant une route
export const routes: Routes = [
  {path:"", component:LandingPageComponent},
  {path:"elements", component:ElementsComponent},
  {path:"element/:id", component:ElementDisplayerComponent},
];

Pour que l’application affiche dans la page courante le component mappé à l’url affiché on utilisera un sélecteur spécifique : router-outler.

<!-- Selecteur affichant le component lié à la page active ; la page par defaut est "", ici donc LandingPageComponent-->
<router-outlet />

Ce dernier, pour être utilisé, doit être importé côté typescript :

import {RooterOutlet} from '@angular/router';

@Component({
  standalone: true,
  selector: 'app-root',
  imports: [RouterOutlet],
  templateUrl: './app.component.html',
  styleUrl: './app.component.scss'
})

Par la suite, dans les pages html, on pourra naviguer entre les routes ; aussi bien via le HTML via l’attribut « routerLink », tout d’abord en important en typescript les directive nécessaires.

import {RouterLink, RouterLinkActive} from '@angular/router';

@Component({
  standalone: true,
  selector: 'app-root',
  imports: [RouterLink, RouterLinkActive],
  templateUrl: './landingpage.component.html',
  styleUrl: './landingpage.component.scss'
})

Puis en exploitant les attributs exposés par ces directives : routerLink pour définir un lien et routerLinkActive (ainsi que routerLinkActiveOptions) pour le paramétrer.

<header>
<h1> Les elements !</h1>
  <nav>
    <!-- routerLinkActiveOptions="{exact:true}" permet de préciser qu'elle ne sera active 
    uniquement que si c'est exactement elle qui est active (et non une de ses enfants ex :
    /parent/enfant.-->
    <a routerLink="/" routerLinkActive="activeLink" [routerLinkActiveOptions]="{exact:true}">Home</a> |
    <!-- RouterLinkActive permet d'attribuer une classe CSS au lien quand sa route 
    est la route active -->
    <a routerLink="/elements" routerLinkActive="activeLink" >Les elements</a>
  </nav>
</header>

…que par le back-end (via le typescript) :

//On injecte le service Router via le constructeur
  constructor(private router:Router) {}

onOpenUrl()
{
  //this.router.navigate(['/element/', this.element.id]);
  this.router.navigateByUrl(`/element/${this.element.id}`);
}

Service

Par conventions, on retrouveras aussi dans src/ les dossiers suivant :

  • /models : toutes les classes représentants les données
  • /services : les classes représentant les service utilisable dans l’application

Un service devra être décoré du décorateur @Injectable afin d’utiliser l’injection de dépendance dans les classes typescripts qui voudront le solliciter :

//L'objet de config qui spécifie "providedIn: 'root'" dit à Angular d'enregistrer ce service à la racine de l'app ; ça permet de n'avoir qu'une seule instance du service, partagée par tous.
@Injectable({
  providedIn: 'root'
})
export class MonSuperService
{
   //...
}

Comme vu pour le Rooter, on pourra utiliser MonSuperService en l’injectant dans un constructeur (constructor(private monSuperService:MonSuperService) {}).

C’est ainsi qu’Angular est devenu un framework solide pour la création de projet web professionnel, avec un typage strict grâce au typescript. Il permet de prendre en main tous les aspect d’une application web moderne, à la manière de Symfony, en s’appuyant sur une architecture de fichier organisée selon les objectifs.

Sources

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *