Une nouvelle version d'Angular est sortie la semaine dernière (le 2 juin 2022), le moment pour moi de faire un récap des gros changements depuis la version 13.

Strictly Typed Reactive Forms

Cette amélioration était attendue depuis très longtemps : on a maintenant des Reactive Forms parfaitement typé. Il n'y a plus de any implicite qui vont traîner et nous créer des erreurs qu'on ne devrait pas avoir en TypeScript.

En premier lieu : aucun stress vous n'aurez pas à repasser sur tout votre code au moment de la migration. Le CLI va automatiquement remplacer les usages de FormGroup, FormControl, FormArray et FormBuilder par respectivement UntypedFormGroup, UntypedFormControl, UntypedFormArray et UntypedFormBuilder, qui sont des alias du premier type mais avec any comme type générique (exemple : UntypedFormGroup est un alias de FormGroup<any>).

Une fois la migration finie, vous pourrez dans un second temps revenir sur vos formulaires pour remplacer les Untyped* pour rendre typé vos formulaires. Attention à prendre en compte que par défaut, on aura une inférence de type qui sera fait en autorisant null comme valeur pour tous les champs, ce qui est obligatoire pour conserver la possibilité d'appeler la méthode reset() sans paramètre comme on l'a un peu toujours fait.

Je vois laisse aller regarder l'article de Nicolas ou celui de la Ninja Squad sur le sujet pour voir comment gérer cette nullité et interdire les null. 😉

Standalone Components

Le début de la solution à un élément d'Angular qui embête beaucoup les débutants, fait de gros débat chez les gens qui comprennent un peu plus : les NgModule. Les Standalone Components visent justement à permettre de se débarrasser des NgModule.

L'idée générale c'est de ne plus déclarer les imports/exports au niveau des modules mais de faire les imports directement au niveau de la directive @Component, de cette manière chaque composant est implicitement déclaré dans son propre module mais sans syntaxe superflue (c'est totalement équivalent aux SCAM (Single Component Angular Module) mais sans avoir à le coder manuellement).

Pour donner une idée du gain, je vous montre un exemple de composant App et du bootstrap avec 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 faut noter qu'on peut aussi rendre standalone un Pipe ou une Directive avec la même logique que pour les Component.

À noter que les Standalone Components sont considérés comme en preview sur la version 14 d'Angular, donc c'est sans doute un peu tôt pour réécrire les modules de vos applications en Standalone Components, mais c'est parfaitement opérationnel pour des petits projets ou des projets persos.

Comme pour le point précédent je vous renvoie à l'article de Nicolas ou celui de la Ninja Squad pour plus de détail sur les Standalone Components avec entre autres les implications sur le lazy loading des Standalone Components via le router et quelques notes sur les providers. 😉

La nouvelle fonction inject()

Une nouvelle fonction a été introduite dans Angular 14 pour permettre de récupérer une référence gérée par l'injecteur de dépendance d'Angular pour peu qu'on l'appelle au moment de l'initialisation d'une classe. L'intérêt est très clair : permettre de la réutilisation de code très simplement :

// 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))
  );
}

Je sais pas vous, mais personnellement je trouve que ça a un petit air des hooks qu'on trouve dans React et Vue 3, bientôt des composants fonctions ? 😎

Je vais me répéter un peu, mais je vous conseille à nouveau l'article de Nicolas sur le sujet si vous voulez plus de détails ! 😉

Quelques petites nouveautés

Définir le titre de la page au routage

Petite nouveauté bien sympa : on peut maintenant définir le titre de la page (le contenu de la balise <title> dans le <head> et donc le nom de l'onglet) dès le routeur.

Pour un titre statique on pourra donc écrire quelque chose comme ça :

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

Comme l'équipe Angular sait très bien qu'on a parfois besoin d'un titre totalement dynamique, on pourra remplacer la chaîne de caractère par la référence vers un résolveur du type :

@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)
    );
  }
}

Avec une syntaxe très proche on pourra aussi définir une stratégie (une classe qui étend TitleStrategy) pour définir le titre des pages via une fonction unique à chaque changement de route (potentiellement plus pratique si on a des routes très dynamiques pour s'éviter beaucoup de déclarations).

Je vous laisse regarder l'article de Netanel Basal sur le sujet pour avoir plus de détail sur les titres dynamiques et stratégies de titre.

Guide pour le change detection

Ce n'est pas à proprement parler d'un ajout au framework mais juste un travail de fond autour de la documentation d'Angular qui se matérialise par exemple par ce nouveau guide pour utiliser correctement les stratégies de change detection en Angular.

Le guide se trouve ici : https://angular.io/guide/change-detection.

Meilleure aide pour les templates

Outre de nouveaux outils interne au CLI pour avoir plus d'aide pour code mieux le code de nos templates, cette version ajoute aussi une erreur précise si on inverse les parenthèses et crochet des banana-box (si on écrit ([]) au lieu de [()]). C'est un petit détail mais ça peut permettre de gagner beaucoup de temps si on ne voit pas l'erreur de suite !

On aura aussi une nouvelle erreur pour indiquer qu'on utilise un nullish coalescing operator sur une valeur non nullable.

Comme souvent avec ce genre d'ajout, on pourra configurer dans le tsconfig si on veut ignorer ces erreurs (je vous le déconseille) ou en faire de simple warning (potentiellement raisonnable si on a un gros projet).

Tree-shakeable errors

Pour optimiser encore mieux le bundle de production, mais garder des messages d'erreurs utilisables pour corriger les erreurs une fois compilé pour la production, une nouvelle idée a été mis en place.

Tous les messages d'erreurs sont maintenant tree-shakeable et seront donc supprimé à la compilation pour la production. Mais en parallèle tous les messages d'erreurs auront maintenant un code d'erreur avec une nomenclature précise. C'est uniquement ce code qui sera conservé en production et on pourra retrouver le sens du code via la page dédiée de la documentation : https://angular.io/errors.

Quelques ajouts au CLI

Un build beaucoup plus rapide en option !

C'est encore en expérimental côté Angular CLI mais on peut maintenant configuré le le build pour utilisé esbuild à la place de webpack comme actuellement par défaut. esbuild est un projet populaire et qui gagne de jour en jour en popularité grâce à ses très bonnes performances. Je pense que si les retours sont positifs, esbuild finira par remplacer webpack donc si vous avez des petits projets, n'hésitez pas à tester !

ng e2e, lint et deploy

Alors qu'en version 12 l'équipe Angular faisait disparaitre l'implémentation par défaut de ng lint et ng e2e, cette version nous ramène ces commandes avec la possibilité de choisir une implémentation.

Pour le lint, on aura que eslint comme choix. Pour les e2e on aura le choix entre Cypress, Nightwatch et WebdriverIO. Pour le deploy on aura la possibilité de choisir Amazon S3, Azure, Firebase, Netlify, NPM ou Github Pages. Bien sûr on pourra toujours choisir une implémentation personnalisé pour lancer la commande qu'on souhaite.

Pour moi c'est une plutôt bonne nouvelle !

Devtools !

Pour moi la nouvelle la plus importante du côté des Devtools est qu'ils sont enfin totalement compatibles avec Firefox et que les Angular Devtools sont disponibles sur le store d'addons de Mozilla : https://addons.mozilla.org/en-US/firefox/addon/angular-devtools/ ! 🎉🎉🎉

En parallèle un petit travail de fond a été fait pour rendre le devtool 100% fonctionnel même sans connexion Internet.

Conclusion

Même si on dit souvent qu'Angular bouge très peu, en réalité beaucoup de chose se passe au niveau du framework. Récemment les versions étaient petites, mais on voit la conclusion de travaux qui durent depuis plusieurs mois/années ici. Avec des changements bienvenus et potentiellement révolutionnaires !

Je n'ai résumé ici que les changements les plus importants à mon avis, je vous renvoie donc aux sources que j'ai utilisé qui évoque bien d'autres changements !

Sources :

Crédit photo : https://pixabay.com/photos/number-fourteen-white-brown-2213576/