Aller au contenu

Angular 14

Angular14 est là ! 🚀 Cette nouvelle version apporte de nombreux changements majeurs, notamment la typisation parfaite des Reactive Forms et l'introduction des Standalone Components. Les développeurs Angular vont adorer ces améliorations !

Photo by Matt Botsford / Unsplash

Le 2 juin 2022, une version récente d'Angular a été dévoilée, apportant avec elle plusieurs changements majeurs depuis la version 13. Dans cet article, nous allons passer en revue les principales évolutions de cette nouvelle version. Que vous soyez un développeur chevronné ou simplement curieux des dernières avancées dans le domaine du développement web, cet aperçu des nouveautés d'Angular ne manquera pas de susciter votre intérêt. Préparez-vous à plonger dans les améliorations et les fonctionnalités passionnantes qui font de cette version une étape importante pour les développeurs Angular.

Strictly Typed Reactive Forms

Une avancée tant attendue a enfin vu le jour : les Reactive Forms d'Angular bénéficient désormais d'une typisation parfaite. Fini les any traînant dans notre code et générant des erreurs inattendues en TypeScript.

Tout d'abord, pas de panique, vous n'aurez pas à revoir l'intégralité de votre code lors de la migration. Le CLI se chargera automatiquement de remplacer les utilisations de FormGroup, FormControl, FormArray et FormBuilder par leurs équivalents UntypedFormGroup, UntypedFormControl, UntypedFormArray et UntypedFormBuilder. Ces derniers sont des alias des premiers types, mais avec any comme type générique (par exemple, UntypedFormGroup est un alias de FormGroup<any>).

Une fois la migration terminée, vous pourrez prendre le temps de revenir sur vos formulaires pour remplacer les Untyped* et ainsi rendre vos formulaires typés. Gardez à l'esprit que par défaut, une inférence de type sera effectuée en autorisant la valeur null pour tous les champs. Cela est nécessaire pour conserver la possibilité d'appeler la méthode reset() sans paramètre, comme nous l'avons souvent fait par le passé.

Je vous recommande de consulter l'article de Nicolas ou celui de la Ninja Squad sur le sujet pour en savoir plus sur la gestion de la nullité et comment restreindre l'utilisation de null dans vos formulaires. Ces ressources vous fourniront des conseils précieux pour tirer pleinement parti de cette évolution passionnante.

Standalone Components

Une solution a été introduite pour résoudre l'un des problèmes couramment rencontrés par les débutants d'Angular et qui suscite de vifs débats parmi les utilisateurs plus expérimentés : les NgModule. Les Standalone Components ont pour objectif de permettre de se passer des NgModule.

L'idée principale est de ne plus déclarer les imports/exports au niveau des modules, mais de les intégrer directement dans la directive @Component. Ainsi, chaque composant est implicitement déclaré dans son propre module, sans syntaxe superflue. Cela équivaut totalement aux SCAM (Single Component Angular Module), mais sans avoir à les coder manuellement.

Pour vous donner une idée du gain obtenu, voici un exemple du composant "App" et de son bootstrap en utilisant la nouvelle syntaxe :

import { Component } from '@angular/core';
import { CommonModule } from '@angular/common';
import { bootstrapApplication } from '@angular/platform-browser';

import { MatCardModule } from '@angular/material/card';
import { ImageComponent } from './app/image.component';
import { HighlightDirective } from './app/highlight.directive';

@Component({
  selector: 'app-root',
  standalone: true, // ajout de cette propriété
  imports: [
    // import de standalone Components, Directives and Pipes
    ImageComponent, HighlightDirective, 
    // et de comme avant NgModules
    CommonModule, MatCardModule 
  ],
  template: `
    <mat-card *ngIf="url">
      <app-image-component [url]="url"></app-image-component>
      <h2 app-highlight>{{name | titlecase}}</h2>
    </mat-card>
  `
})
export class AppComponent {
  name = "emma";
  url = "www.emma.org/image";
}

// Cette appel remplace le contenu du app.module.ts
bootstrapApplication(AppComponent);

Il est également possible de rendre les Pipe et les Directive autonomes en utilisant la même logique que pour les Component.

Il convient de souligner que les Standalone Components sont encore considérés comme une fonctionnalité en préversion dans la version 14 d'Angular. Il est donc peut-être encore trop tôt pour convertir tous les modules de vos applications en Standalone Components. Cependant, ils sont tout à fait opérationnels pour les petits projets ou les projets personnels.

Comme pour le point précédent, je vous recommande de consulter l'article de Nicolas ou celui de la Ninja Squad pour obtenir plus de détails sur les Standalone Components, notamment sur leurs implications concernant le chargement différé (lazy loading) des Standalone Components via le router, ainsi que quelques notes sur les providers.

La nouvelle fonction inject()

Angular 14 a introduit une nouvelle fonctionnalité permettant de récupérer une référence gérée par l'injecteur de dépendances d'Angular lors de l'initialisation d'une classe. Cette fonctionnalité offre un avantage évident : faciliter la réutilisation du code de manière simple.

// params.helper.ts
export function getParam<T>(key: string): Observable<T> {
  const route = inject(ActivatedRoute);
  return route.paramMap.pipe(
    map(params => params.get(key)),
    distinctUntilChanged()
  );
}

// todo-details.component.ts
@Component({ ... })
export class TodoComponent {
  todoId$ = getParam<Todo>('id');
  todo$ = todoId$.pipe(
    switchMap(id => inject(TodoService).getTodo(id))
  );
}

Personnellement, je trouve que cela rappelle un peu les hooks que l'on trouve dans React et Vue 3. Est-ce que cela pourrait signifier l'arrivée prochaine des composants fonctionnels dans Angular ? C'est une perspective intéressante ! 😎

Je me répète un peu, mais je vous recommande vivement de consulter l'article de Nicolas sur le sujet si vous souhaitez obtenir plus de détails. Il est accessible ici : lien vers l'article de Nicolas.

Quelques petites nouveautés

Définir le titre de la page au routage

PUne petite nouveauté bien sympathique : nous avons désormais la possibilité de définir le titre de la page, c'est-à-dire le contenu de la balise <title> dans la section <head> du document HTML, directement depuis le routeur.

Pour définir un titre statique, nous pouvons utiliser la syntaxe suivante :

export const routes: Routes = [
  {
    path: 'todos',
    title: 'Todos',
    loadChildren: () => import('./todos-page/todos.routes')
                                      .then(m => m.todosRoutes)
  }
]

Cependant, l'équipe Angular est consciente du fait que nous avons parfois besoin d'un titre entièrement dynamique. Dans ce cas, nous pouvons remplacer la chaîne de caractères par une référence à un résolveur, comme dans l'exemple ci-dessous :

@Injectable({ providedIn: 'root' })
class TodoTitle implements Resolve<string> {
  constructor(private todosRepo: TodosRepository) { }

  resolve(route: ActivatedRouteSnapshot) {
    return this.todosRepo.active$.pipe(
      map(todo => todo.title),
      take(1)
    );
  }
}

De manière similaire, nous pouvons également définir une stratégie (une classe étendant TitleStrategy) pour définir les titres des pages en utilisant une fonction unique à chaque changement de route. Cela peut être particulièrement pratique si nous avons des routes très dynamiques et que nous souhaitons éviter de nombreuses déclarations.

Je vous invite à consulter l'article de Netanel Basal sur le sujet pour obtenir plus de détails sur les titres dynamiques et les stratégies de titre.

Guide pour le change detection

Un travail de fond a été réalisé autour de la documentation d'Angular, ce qui se traduit notamment par la création d'un nouveau guide pour utiliser efficacement les stratégies de détection des changements dans Angular.

Vous pouvez consulter ce guide à l'adresse suivante : https://angular.io/guide/change-detection.

Meilleure aide pour les templates

En plus des nouveaux outils internes au CLI qui nous aident à améliorer notre code de templates, cette version inclut également une erreur précise lorsque les parenthèses et les crochets des "banana-box" (par exemple, ([])) sont inversés. C'est un petit détail, mais cela peut nous faire gagner beaucoup de temps si nous ne détectons pas immédiatement l'erreur !

Une autre nouveauté est l'apparition d'une nouvelle erreur qui signale l'utilisation d'un opérateur de coalescence nulle sur une valeur non nullable.

Comme c'est souvent le cas avec ce type d'ajout, nous pourrons configurer dans le fichier tsconfig si nous souhaitons ignorer ces erreurs (ce que je déconseille) ou les traiter simplement comme des avertissements (ce qui peut être raisonnable dans le cas d'un projet volumineux).

Tree-shakeable errors

Dans le but d'optimiser davantage le bundle de production tout en conservant des messages d'erreur exploitables pour corriger les erreurs une fois compilées, une nouvelle approche a été mise en place.

Désormais, tous les messages d'erreur sont "tree-shakeable" et seront donc supprimés lors de la compilation destinée à la production. Toutefois, chaque message d'erreur sera associé à un code d'erreur spécifique, selon une nomenclature précise que vous pouvez consulter ici. Seul ce code sera conservé en production, et vous pourrez retrouver sa signification sur la page dédiée de la documentation : https://angular.io/errors.

Quelques ajouts au CLI

Un build beaucoup plus rapide en option !

Une nouvelle fonctionnalité expérimentale dans Angular CLI permet désormais de configurer la construction du projet pour utiliser esbuild à la place de webpack, qui est actuellement le choix par défaut. esbuild est un projet très populaire en raison de ses excellentes performances, et sa popularité ne cesse de croître. Si les retours sont positifs, il est fort probable que esbuild finira par remplacer webpack. Donc, si vous travaillez sur des projets de petite envergure, je vous encourage à essayer esbuild et voir ses avantages par vous-même !

ng e2e, lint et deploy

Une nouvelle version d'Angular ramène les commandes ng lint et ng e2e après les avoir supprimées par défaut dans la version 12. Mais cette fois, nous avons la possibilité de choisir les implémentations qui les accompagnent.

En ce qui concerne le lint, nous avons désormais le choix exclusif d'utiliser eslint. Pour les tests end-to-end (e2e), nous pouvons choisir entre Cypress, Nightwatch et WebdriverIO. Quant au déploiement, nous avons la possibilité de choisir parmi Amazon S3, Azure, Firebase, Netlify, NPM ou Github Pages. Bien sûr, nous pouvons toujours opter pour une implémentation personnalisée pour exécuter la commande de notre choix.

Pour ma part, c'est une excellente nouvelle !

Devtools !

La nouvelle la plus marquante pour moi en ce qui concerne les Devtools est qu'ils sont enfin pleinement compatibles avec Firefox, et les Angular Devtools sont désormais disponibles sur le store d'add-ons de Mozilla : https://addons.mozilla.org/en-US/firefox/addon/angular-devtools/ ! 🎉🎉🎉

De plus, un travail a été réalisé en coulisses pour rendre le devtool entièrement fonctionnel, même en l'absence de connexion Internet.

Conclusion

Bien que l'on associe souvent à Angular une évolution lente, la réalité est tout autre : de nombreuses avancées sont en cours dans le framework. Bien que les dernières versions aient pu sembler modestes, elles marquent en réalité la culmination de travaux qui ont duré plusieurs mois voire plusieurs années. Ces changements, qui sont à la fois bienvenus et potentiellement révolutionnaires, se concrétisent enfin.

Dans ce résumé, j'ai abordé uniquement les changements que je considère comme les plus importants. Toutefois, je vous invite à consulter les sources que j'ai utilisées, qui détaillent de nombreux autres changements passionnants.

Dernier