Toutes les nouveautés d'Angular 18

Toutes les nouveautés d'Angular 18

Loïc Boutter (lazybobcat)
Loïc Boutter (lazybobcat)

Angular 18 vient de sortir. Quelles sont les nouveautés apportées par cette version majeure ? Découvrons les !

Au sommaire :

  1. Breaking change : le compilateur d’Angular nécessite Typescript 5.4
  2. Les opérateurs de flux et defer sont désormais “stable”
  3. Valeurs par défaut lors de la projection de contenu
  4. Route.redirectTo peut désormais être une fonction
  5. Un router guard peut retourner un RedirectCommand
  6. Changement de détection sans zone.js (expérimental)
  7. Changements d’états des contrôles de formulaires
  8. Dépréciation de HttpClientModule
  9. Angular.dev devient le site de documentation officiel
  10. Sources et liens

Breaking change : le compilateur d’Angular nécessite Typescript 5.4

Le “compiler” d’Angular 18 (@angular/compiler-cli) nécessite désormais la version 5.4 de Typescript. Cela pourrait poser quelques soucis lors de la mise à jour si vous avez des packages qui nécessitent une version inférieure.

Typescript étant connu pour éviter à tout prix les breaking changes, cela ne devrait en réalité pas poser de problèmes lors de l’éxécution de votre projet, mais il faudra probablement ignorer et/ou fixer quelques erreurs lors de votre npm update !

Les opérateurs de flux et defer sont désormais “stables”

Si vous non plus vous ne pouvez plus écrire un template sans @if, @for, @defer et consorts : bonne nouvelle. Ces opérateurs étaient en “développeur preview” avec Angular 17, leur API est désormais “stable” avec Angular 18 !

Valeurs par défaut lors de la projection de contenu

La projection de contenu est une mécanique permettant de récupérer ce qui est placé entre les balises HTML d’un composant afin de l’afficher où on le souhaite dans le-dit composant.

Prenons l’exemple d’une carte Pokémon. Je souhaite avoir un composant <pokemon-card>...</pokemon-card> auquel je veux fournir du contenu HTML pour afficher le nom de la carte, son coût, une image, une description et une éventuelle citation ou crédit en bas de carte. Si l’une de ces informations n’est pas fournie, il serait intéressant d’avoir un contenu affiché par défaut.

Avant Angular 18

Auparavant si vous ajoutiez du contenu dans un <ng-content> vous obteniez une erreur, par exemple :

pokemon-card.component.ts
@Component({
    selector: 'pokemon-card',
    template: `<ng-content>Contenu par défaut<ng-content>`
})
export class PokemonCard {}

Vous obtenez l’erreur suivante :

✘ [ERROR] NG5002: <ng-content> element cannot have content. [plugin angular-compiler]
    src/main.ts:16:6:
      23 │       <ng-content>Contenu par défaut ?</ng-content>
         ╵       ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

En réalité, il y a des façons d’y arriver, par exemple à l’aide d’un @ContentChild().

À partir d’Angular 18

À partir d’Angular 18, le code ci-dessus est fonctionnel et la valeur passée dans la balise <ng-content> sera affichée si aucun contenu n’est fourni dans les balises du composant.


app.component.ts
<pokemon-card></pokemon-card>

Aucun contenu n’est fourni au composant PokemonCard, donc “Contenu par défaut” sera affiché.


<pokemon-card>
    <strong>Eclair :</strong> lance une attaque électrique sur son adversaire.
</pokemon-card>

Affichera : Eclair : lance une attaque électrique sur son adversaire.


Et enfin…

<pokemon-card>
    @if (false) {
        <em>Jamais affiché</em>
    }
</pokemon-card>

… n’affichera rien (n’affichera pas le contenu par défaut) ! Et oui car même s’il est vide, du contenu est fourni au composant.

Route.redirectTo peut désormais être une fonction

La fonction de redirection prend en paramètre un ActivatedRouteSnapshot et doit retourner l’URL sous forme de chaîne de caractères ou un UrlTree.

app.routes.ts
export const routes: Routes = [
    // ...,
    {
        path: 'poke-monsters', // ancienne route
        redirectTo: ({ queryParams }) => {
            const pokemonId = queryParams['id'];      
            if (pokemonId) {
                return `/pokemon/${pokemonId}`;
            } else {
                return `/pokemons`;
            }
        }
    },
    // ...,
];

Un router guard peut retourner un RedirectCommand

Afin d’effectuer une redirection dans un guard, il est maintenant possible de retourner un object de type RedirectCommand, prenant à la construction un UrlTree :

app.routes.ts
{
    path: '/old-url',
    component: SuperComponent,
    canActivate: [
        () => new RedirectCommand(router.parseUrl('/new-url'), {skipLocationChange: true}),
    ],
}

Les options, passées en second paramètre du constructeur, sont un objet de type NavigationBehaviorOptions.

Documentation de RedirectCommand.

Expérimental : changement de détection sans zone.js

Si vous avez même peu d’expérience avec Angular, il y a de fortes chances que vous soyez déjà tombé sur une erreur incompréhensible provenant de zone.js. Pour simplifier un peu, ce vendor est chargé de rapporter tous les changements et événements d’une “zone” donnée (en général, la zone est votre page web). Angular s’en servait donc pour effectuer la détection de changement.

Avec des travaux réalisés depuis quelques années et l’arrivée des signaux, il est désormais possible de d’utiliser Angular sans zone.js. Pour cela, il faut appeler provideExperimentalZonelessChangeDetection() lors du démarrage de l’application (bootstrap) :

main.ts
bootstrapApplication(App, {
    providers: [
        provideExperimentalZonelessChangeDetection()
    ]
});

Vous pouvez désormais retirer zone.js du fichier angular.json. Le retrait de zone.js permet notamment :

  • d’accélérer le rendu initial de l’application
  • réduire le poids global de l’application
  • avoir plus de contrôle sur la détection de changement avec la stratégie OnPush et ChangeDetectorRef.markForCheck
  • combiner des micro-frontends plus facilement (ils ne sont plus dans des zones différentes)
  • éviter d’avoir des erreurs incompréhensibles :)

La migration sera plus facile si vos composants ont déjà la stratégie de détection de changement OnPush. Alors si ce n’est pas le cas, je vous suggère de les convertir au plus vite !

Changements d’états des contrôles de formulaires

Une nouvelle propriété est disponible sur FormControl, FormGroup et FormArray : events. Il s’agit d’un observable permettant d’être informé sur les différents changements d’états : valeur, touch, pristine et son état de validation. Son utilisation est très simple :

const nameControl = new FormControl<string|null>('name', Validators.required);
nameControl.events.subscribe(event => {
    // process the individual events
});

Dépréciation de HttpClientModule

Importer HttpClientModule dans un module ou composant est désormais déprécié. Il faut utiliser la fonction provideHttpClient() dans vos providers. Par exemple :

main.ts
bootstrapApplication(App, {
    providers: [
        // ... others like provideExperimentalZonelessChangeDetection() ;)
        provideHttpClient(),
    ]
});

Si vous utilisez la commande ng update @angular/core (ou Nx) pour mettre votre projet à jour, ce changement devrait être effectué automatiquement !

Consultez la documentation pour plus d’informations.

Angular.dev devient le site de documentation officiel

Le site, déjà disponible depuis quelques mois, vient officiellement remplacer Angular.io. Ce dernier reste disponible pour l’instant (d’autant qu’il est souvent mieux référencé sur Google) mais devrait prochainement rediriger sur la nouvelle plateforme.

Angular.dev propose de la documentation et des tutoriels interactifs (avec du live coding) ainsi qu’un playground bien pratique.

Sources et liens

Nous avons vu les changements les plus importants de cette nouvelle version, mais il y en a d’autres comme des améliorations du SSR et de l’hydratation de la page. Pour plus d’informations sur ces éléments ou un angle différent sur ceux évoqués dans cet article, n’hésitez pas à consulter les liens suivants :

Retour aux articles