Aller au contenu

Angular 20

Angular 20 introduit des nouveautés majeures : redirections asynchrones, resolvers parents, tagged templates, exponentiation, style guide mis à jour, et bien plus. Un tour d’horizon clair pour exploiter pleinement cette version.

Angular 20

Introduction

Publié le 26 mai 2025, Angular 20 marque une avancée importante pour le framework.

Cette version introduit plusieurs évolutions notables, comme les redirections asynchrones, le partage de données entre resolvers parents et enfants, ainsi que le support natif de l’opérateur d’exponentiation et des tagged template literals dans les templates.

Autre ajout marquant : le style guide officiel a été mis à jour afin d’encourager des pratiques plus cohérentes et maintenables à l’échelle des projets.

Dans cet article, nous explorons les nouveautés les plus significatives à travers des cas d’usage concrets.

Opérateur d'exponentiation

L'opérateur d'exponentiation ** est désormais pris en charge dans les templates. Cet opérateur, introduit en JavaScript avec ECMAScript 2016 (ES7), permet de simplifier les calculs de puissances directement dans vos expressions. Il fallait recourir à des méthodes comme Math.pow() pour effectuer des calculs de puissances dans les templates, ce qui alourdissait le code et réduisait leur lisibilité. Avec l'opérateur **, les calculs mathématiques deviennent plus intuitifs et lisibles. Voici quelques exemples concrets pour illustrer son utilisation.

Calcul simple

<p>
  Avant : Math.pow(base, exponent) = {{ Math.pow(base, exponent) }}
</p>
<p>
  Aujourd'hui : {{ base }} ** {{ exponent }} = {{ base ** exponent }}
</p>

export class ExponentialOperator {
  base = 2;
  exponent = 3;

  protected readonly Math = Math;
}

Parenthèses obligatoires sur les opérateurs unaires lorsqu'il sont à la base du calcul

<p>(-2) ** {{ b }} = {{ (-2) ** b }}</p>
<p>(-6) ** {{ c }} = {{ (-6) ** c }}</p>

export class ExponentialOperator {
  b = 3;
  c = 4;
}

Calcul où l’opérateur ** est associatif

<p>{{ base }} ** {{ exponent }} ** {{ a }} = {{ base ** exponent ** a }}</p>
<p>{{ base }} ** ({{ exponent }} ** {{ a }}) = {{ base ** (exponent ** a) }}</p>

export class ExponentialOperator {
  base = 2;
  exponent = 3;
  a = 2;
}

Base de calcul avec un chiffre négatif

<p>{{ negativeBase }} ** {{ b }} = {{ negativeBase ** b }}</p>
<p>{{ negativeBase }} ** {{ c }} = {{ negativeBase ** c }}</p>

export class ExponentialOperator {
  negativeBase = -2;
  b = 3;
  c = 4;
}
feat(compiler): support void and exponentiation operators in templates by mmalerba · Pull Request #59894 · angular/angular
Add support for the void operator in templates and host bindings. This is useful when binding a listener that may return false and unintentionally prevent the default event behavior. Ex: @Directive…

Lien vers la Pull Request de l'opérateur d'exponentiation

fix(compiler): fix a few bugs with the newly introduced `**` operator by mmalerba · Pull Request #60101 · angular/angular
Some fixes for exponentiation which was recently introduced in #59894 Makes exponentiation right-associative. For example, a ** b ** c should be equivalent to a ** (b ** c), not (a ** b) ** c Ensu…

Lien vers la Pull Request de fix de l'opérateur d'exponentiation

Tagged template literal

Encore une nouvelle feature côté template, cette fois-ci Angular introduit la prise en charge des tagged template literal. Un peu comme une extension des template literals. Ils permettent d'appliquer une fonction spécifique à un template literal pour le transformer ou le traiter avant qu'il ne soit utilisé. Voici un exemple simple pour illustrer comment ils fonctionnent.

@Component({
  selector: 'app-tag-template-literal',
  imports: [
    FormsModule
  ],
  template: `
    <div>
      <div>
        <input type="number" [(ngModel)]="quantity" id="quantity"/>
        @let options = ['Banane', 'Pomme', 'Bière', 'Tank', 'Saxophone'];
        <select [(ngModel)]="product" id="greeting" name="greeting">
          @for (option of options; track option) {
            <option [ngValue]="option">{{ option }}</option>
          }
        </select>
      </div>
      <p>{{ tag\`Bonjour, \${quantity()} x \${product()} please !\` }}</p>
    </div>
  `
})
export class TaggedTemplateLiteral {
  product = signal('Banane');
  quantity = signal(10);

  tag(strings: TemplateStringsArray, quantity: number, product: string) {
    return `${strings[0]}${quantity}${strings[1]}${product}${strings[2]}`;
  }

  // Autre possibilité 👇
  // tag(strings: TemplateStringsArray, ...values: any[]) {
  //   return `${strings[0]}${values[0]}${strings[1]}${values[1]}${strings[2]}`;
  // }
}

On peut remarquer que, dans le cas des tagged template literals, les parenthèses ne sont pas nécessaires pour appeler la fonction. Au lieu de cela, la fonction est directement invoquée en utilisant les backticks (`). Lorsqu'une fonction est utilisée comme un "tag" pour un template literal, elle reçoit automatiquement deux types d'arguments :

  1. Une liste de chaînes de caractères : Le premier argument est un tableau contenant toutes les parties statiques du template (le texte qui n'est pas interpolé). Ces chaînes de caractères sont les segments du template qui entourent les expressions dynamiques.
  2. Toutes les expressions interpolées : Les arguments suivants sont les valeurs des expressions dynamiques (celles qui sont placées à l'intérieur des ${} dans le template).

Si on reprend l’exemple ci-dessus, on retrouve les arguments suivants :

  • Le tableau strings contient les parties statiques du template : "Bonjour, ", " x ", et " please !".
  • Les valeurs dynamiques ${quantity} et ${product} sont collectées à partir du tableau values ou indiquées explicitement en arguments de la méthode : "10" et "Banane".
feat(compiler): support tagged template literals in expressions by mmalerba · Pull Request #59947 · angular/angular
Adds support for using tagged template literals in Angular templates. Ex: @Component({ template: &#39;{{ greet`Hello, ${name()}` }}&#39; }) export class MyComp { name = input(); greet(string…

Lien vers la Pull Request Tagged template literal

État des features

Angular 20 marque une étape importante dans l'évolution du framework en stabilisant plusieurs API et fonctionnalités. Voici une vue d'ensemble des principales API stabilisées, accompagnée d'explications détaillées et d'analogies pour mieux comprendre leur utilité.

linkedSignal

La linkedSignal API permet de créer des signaux réactifs qui sont automatiquement synchronisés avec une source de données externe. N'hésite pas à aller voir l'article sur la version 19 pour plus de détails.

feat(core): stabilize linkedSignal API by pkozlowski-opensource · Pull Request #60741 · angular/angular
The linkedSignal API is now considered stable.

Lien vers la Pull Request LinkedSignal


REQUEST, RESPONSE_INIT et REQUEST_CONTEXT

Ces tokens d'injection sont apparus dans la version 19 et permettent d'accéder aux objets de requête et de réponse pour les applications utilisant le rendu côté serveur (SSR) :

  • REQUEST: fournit un accès à l'objet de requête HTTP actuel, tel qu'il est reçu par le serveur. Cela permet d'accéder aux en-têtes, à l'URL, aux cookies et à d'autres informations pertinentes de la requête
  • REQUEST_CONTEXT: permet de transmettre des métadonnées personnalisées tout au long du cycle de vie de la requête. Il est particulièrement utile pour partager des informations spécifiques à la requête, comme le rôle de l'utilisateur ou la région.
  • RESPONSE_INIT: offre un moyen de configurer la réponse HTTP avant qu'elle ne soit envoyée au client. Cela inclut la définition d'en-têtes, le statut de la réponse et d'autres paramètres initiaux.
docs: mark `REQUEST` and related symbols as stable by AndrewKushnir · Pull Request #60717 · angular/angular
This commit removes the @developerPreview annotation from the REQUEST, RESPONSE_INIT and REQUEST_CONTEXT symbols, making them stable. Does this PR introduce a breaking change? Yes No

Lien vers la Pull Request REQUEST, REQUEST_CONEXT et RESPONSE_INIT


PendingTasks

PendingTasks est un service qui permet de suivre les tâches asynchrones personnalisées afin d'informer Angular que l'application n'est pas encore stable.

feat(core): Stabilize PendingTasks Injectable by atscott · Pull Request #60716 · angular/angular
This PR marks PendingTasks as stable, though the run function remains in dev preview. There are still questions around its return value, error handling, and whether it will be replaced by a differe…

Lien vers la Pull Request PendingTasks


AfterRenderRef, AfterRenderOptions, afterNextRender et afterEveryRender

  • afterNextRender: permet d'enregistrer un ou plusieurs callbacks qui seront exécutés une seule fois après le prochain cycle de détection des changements.
  • AfterRenderRef: Lors de l'enregistrement d'un callback avec afterNextRender, une instance de AfterRenderRef est retournée. Cette référence permet de gérer manuellement le cycle de vie du callback, notamment en le nettoyant explicitement si nécessaire.
  • AfterRenderOptions: L'interface AfterRenderOptions permet de configurer le comportement des callbacks enregistrés avec afterNextRender.
  • afterEveryRender: anciennement afterRender, il permet d’exécuter une fonction après chaque rendu du composant.
⚠️ Aucun schematic n’a été fourni pour accompagner ces changements, ce qui implique une migration manuelle dans les projets existants.
refactor(core): Promote `afterNextRender` to stable by JeanMeche · Pull Request #60792 · angular/angular
afterRender will not be promoted and is on its way out.

Lien vers la Pull Request AfterRenderRef, AfterRenderOptions et afterNextRender


withI18nSupport()

Par défaut, lors de l'hydratation, Angular ignore les blocs marqués avec l'attribut i18n, ce qui entraîne leur re-rendu complet côté client. Cela peut provoquer des décalages de mise en page et des problèmes de performance. La fonction withI18nSupport() permet à Angular de prendre en charge ces blocs i18n lors de l'hydratation, assurant une transition fluide entre le rendu serveur et le client.

feat(core): stabilize withI18nSupport() api by thePunderWoman · Pull Request #60889 · angular/angular
This stabilizes the withI18nSupport() feature of hydration. Does this PR introduce a breaking change? Yes No

Lien vers la Pull Request withI18nSupport()


toObservable

La fonction toObservable() permet de créer un RxJS Observable à partir d'un Signal.

Stabilize `toObservable` by alxhub · Pull Request #60449 · angular/angular

Lien vers la Pull Request toObservable


toSignal

La fonction toSignal() permet de créer un Signal à partir d'un RxJS Observable.

feat(core): mark the toSignal API as stable by pkozlowski-opensource · Pull Request #60442 · angular/angular
This commit marks the toSignal API as stable.

Lien vers la Pull Request toSignal


Zoneless Change Detection

Zoneless Change Detection passe en "developer preview", c'est le point d’entrée officiel pour la détection de changements sans Zone.js ! Traditionnellement, Angular utilise Zone.js pour intercepter les opérations asynchrones (comme les événements DOM, les requêtes HTTP ou les timers) et déclencher automatiquement la détection des changements. Bien que pratique, cette méthode peut entraîner des surcoûts en termes de performances et de complexité de déboggage. Avec Zoneless Change Detection, Angular abandonne sa dépendance à Zone.js. Désormais, les développeurs doivent informer explicitement Angular lorsqu'un changement nécessite une mise à jour de l'interface utilisateur. Les providers ont été renommés de provideExperimentalZonelessChangeDetection à provideZonelessChangeDetection.

feat(core): Move zoneless change detection to dev preview by atscott · Pull Request #60748 · angular/angular
This commit moves zoneless from experimental to developer preview. Update tag on provider API Remove &quot;experimental&quot; from provider name Move documentation from &quot;experimental features…

Lien vers la Pull Request Zoneless change detection


*ngIf, *ngFor et *ngSwitch

Après de nombreuses versions au service des développeurs, les directives historiques *ngIf, *ngFor et *ngSwitch sont désormais dépréciées. Elles devraient être supprimées d’ici Angular 22, soit dans environ un an.

Capture d’écran de l’éditeur de code montrant un message de dépréciation pour la directive *ngIf dans Angular 20.
refactor(common): Mark inputs of deprecated control flow directives a… by atscott · Pull Request #61089 · angular/angular
…s deprecated This updates the inputs of the deprecated control flow directives as also deprecated. Helpful for language services/tokenization tools

Lien vers la Pull Request de dépréciation des directives

flushEffect

La méthode TestBed.flushEffects(), introduite en Angular 17, est désormais dépréciée et cède sa place à TestBed.tick(). Cette nouvelle méthode exécute l’ensemble du processus de synchronisation d’Angular contrairement à flushEffects(), qui forçait l’exécution des effets de manière isolée. tick() reproduit fidèlement le comportement d’une application Angular en cours d’exécution, ce qui améliore la précision et la pertinence des tests.

refactor(core): replace `TestBed.flushEffects` with `tick` by pkozlowski-opensource · Pull Request #60959 · angular/angular
Instead of stabilizing the TestBed.flushEffects() API we intend to replace it with the tick() method (equivalent of ApplicationRef.tick(). The reasoning here is that we prefer tests running the ent…

Lien vers la Pull Request de remplacement de flushEffects par tick

feat(core): introduce TestBed.tick() by pkozlowski-opensource · Pull Request #60993 · angular/angular
This commit introduces the TestBed.tick() method that, similarly to the ApplicationRef.tick(), synchronizes state with the DOM. It can be used in unit tests to mimic framework&#39;s logic executed…

Lien vers la Pull Request d'introduction de tick

Resources

Avec la sortie d’Angular 20, le framework continue d’évoluer en introduisant des améliorations notables à l'API httpResource. Initialement proposée en version expérimentale dans Angular 19.2 , cette API vise à simplifier la gestion des requêtes HTTP en s'intégrant harmonieusement avec les concepts modernes du framework, tels que les signaux et les ressources. Initialement, le premier argument de httpResource pouvait recevoir une fonction retournant une valeur réactive ou une URL statique. Ce dernier n'est plus possible :

❌ Ancien usage (non réactif) – plus valable

readonly httpResource = httpResource<NoIdea>('/i_dont_know');

✅ Nouveau usage (réactif)

readonly userId = signal(666);

readonly userResource = httpResource<User>(() => `/users/${userId()}`);

Le second argument de httpResource est un objet d'options typé HttpResourceOptions, qui permet de contrôler le comportement de la ressource. Voici ses principales propriétés :

  • defaultValue : T
    La valeur retournée par la httpResource lorsqu'elle est en état d'inactivité, d'erreur ou de chargement.
  • equal?: (a: T, b: T) => boolean
    Si la fonction equal estime que les résultats sont équivalents, Angular considère que le signal n’a pas changé : il ne le marque donc pas comme « dirty », ce qui évite un nouveau cycle de détection des changements et prévient toute mise à jour inutile du template.
  • parse?: (response: unknown) => T
    Permet de transformer la réponse HTTP avant qu’elle soit renvoyée et utilisée par la httpResource.
  • injector?: Injector
    Par défaut l'injector du composant est utilisé, la resource est automatiquement nettoyé à la destruction du composant.

Exemple complet avec options :

readonly injector = inject(Injector);
readonly pokemonId = signal(1);

readonly resource = httpResource<Pokemon>(
  () => this.pokemonId() ? `https://tyradex.vercel.app/api/v1/pokemon/${this.pokemonId()}` : undefined,
  {
    defaultValue: { ... },
    parse: (response) => ({ ... }),
    equal: (a, b) => {
      const isEqual = a?.pokedex_id == b?.pokedex_id;
      console.log('isEqual', a?.pokedex_id, b?.pokedex_id, isEqual);
      return isEqual;
    },
    injector: this.injector
  }
);

Concernant la gestion des erreurs dans httpResource, celle-ci était relativement rudimentaire. En cas d’échec de la requête, l’erreur capturée dans le signal associé à la ressource était de type unknown. Cela obligeait les développeurs à caster manuellement l’erreur ou à écrire du code défensif pour savoir s’il s’agissait bien d’une instance de Error, ce qui n'était ni pratique ni sûr. Grâce à la PR #60610, la signature du signal d’erreur a été mise à jour pour renvoyer désormais une valeur typée Error | undefined. Ce que ça change concrètement :

  • Plus besoin de faire un check de type avec instanceof Error.
  • Le signal est explicitement undefined tant qu’aucune erreur n’est survenue.
  • On bénéficie d’un typage fort dès la lecture du signal.
constructor() {
  effect(() => {
    const error = this.resource.error(); // type: Error | undefined

    if (error) {
      console.error('Erreur détectée :', error.message);
    }
  });
}

De plus, la méthode reload() a été déplacée dans la sous-classe WritableResource, ce qui signifie que seules les ressources mutables peuvent être rechargées.

Côté resource et rxResource , quelques renommages notables à prendre en compte :

  • request a été renommée en params dans les options à fournir aux deux API.
  • loader a été renommée en stream dans rxResource.
⚠️ Là encore, aucun schematic n’automatise cette transition.
feat(core): Resources api change by Humberd · Pull Request #60610 · angular/angular
fix(core): getting resource value throws an error instead of returning undefined fix(core): narrow error type for resources API fix(core): move reload method from Resource to WritableResource build…

Lien vers la Pull Request du throw d'erreur pour `httpResource`

fix(common): support equality function in httpResource by cexbrayat · Pull Request #60026 · angular/angular
PR Checklist Please check if your PR fulfills the following requirements: The commit message follows our guidelines: https://github.com/angular/angular/blob/main/CONTRIBUTING.md#commit Tests for…

Lien vers la Pull Request d'ajout de la fonction equal dans `httpResource`

feat(common): introduce experimental `httpResource` by alxhub · Pull Request #59876 · angular/angular
httpResource is a new frontend to the HttpClient infrastructure. It declares a dependency on an HTTP endpoint. The request to be made can be reactive, updating in response to signals for the URL, m…

Lien vers la Pull Request de `httpResource`

Nouveau mot clé in pour les expressions

Le langage des templates s'enrichit une nouvelle fois en prenant en charge l'opérateur JavaScript in, permettant de vérifier si une propriété existe dans un objet directement, même si sa valeur est undefined ou null.

const user = {
  name: undefined,
  email: null
};

'name' in user     // ✅ true
'email' in user    // ✅ true
'age' in user      // ❌ false

Voici quelques cas d'utilisations :

1. Conditionner l’affichage avec @if

@if ('admin' in permissions) {
  <button>Accès admin</button>
}

➡️ Le bouton est affiché uniquement si l’objet permissions contient la clé "admin", quelle que soit sa valeur (true, false, null, undefined…).

2. Appliquer un style inline

<div [style.border]="'error' in result ? '1px solid red' : 'none'">
  {{ result.message }}
</div>

➡️ On affiche une bordure rouge si une propriété error existe dans un objet result.

3. Boucler avec @for et ajouter une classe selon la présence d’une propriété

@for (user of users) {
  <div [class.online]="'status' in user && user.status === 'online'">
    {{ user.name }}
  </div>
}

➡️ Chaque utilisateur ayant un statut online se voit appliquer une classe CSS spécifique.

feat(compiler): support the `in` keyword in Binary expression by JeanMeche · Pull Request #58432 · angular/angular
This commit adds the support for the in keyword as a relational operator, with the same precedence as the other relational operators (&lt;,&gt;, &lt;=, &gt;=). As a bigger picture, adding this brin…

Lien vers la Pull Request de keyword in

Création dynamique des composants

La méthode ViewContainerRef.createComponent() , déjà disponible depuis Angular 14, permet de créer dynamiquement des composants pour les insérer manuellement. Elle acceptait déjà un second argument d’options mais deux nouvelles propriétés majeures ont été introduites : bindings et directives.

    abstract createComponent<C>(componentType: Type<C>, options?: {
        index?: number;
        injector?: Injector;
        ngModuleRef?: NgModuleRef<unknown>;
        environmentInjector?: EnvironmentInjector | NgModuleRef<unknown>;
        projectableNodes?: Node[][];
        directives?: (Type<unknown> | DirectiveWithBindings<unknown>)[];
        bindings?: Binding[];
    }): ComponentRef<C>;
  • directives: d’attacher dynamiquement des directives au composant créé.
  • bindings:
    • inputBinding: Permet de lier une propriété d'entrée (input) du composant créé avec une valeur ou une fonction réactive.
    • outputBinding: Permet d’attacher une fonction de rappel à une propriété marquée output dans le composant créé. Le callback est invoqué automatiquement lors des émissions (emit).
    • twoWayBinding: Crée une liaison bidirectionnelle entre une propriété du composant créé et un signal local.

Prenons maintenant un exemple concret pour illustrer ces nouveautés. Nous avons une application avec un bouton qui ouvre une modal, dont le contenu et le style sont entièrement pilotés dynamiquement.

Le composant parent : App

@Component({
  selector: 'app-root',
  imports: [],
  template: `
    <button (click)="openModal()">Open modal</button>
    {{ data() }}
    <ng-container #modal></ng-container>
  `,,
  styleUrl: './app.scss'
})
export class App {
  title = signal('20');
  data = signal('');

  modalContainer = viewChild.required('modal', { read: ViewContainerRef });

  openModal() {
    if (!this.modalContainer().length) {
      this.modalContainer().createComponent(Modal, {
        bindings: [
          inputBinding('title', () => `Angular ${this.title()} est là !`),
          twoWayBinding('data', this.data),
          outputBinding('close', result => {
            console.log(result);
            if (result) {
              this.modalContainer().clear();
            }
          })
        ],
        directives: [
          {
            type: TitleColorDirective,
            bindings: [
              inputBinding('clazz', () => !this.data() ? 'deeppink' : 'yellow')
            ]
          }
        ]
      })

    }
  }
}
  • inputBinding('title', ...) injecte dynamiquement un titre personnalisé dans la modal.
  • twoWayBinding('data', this.data) permet de lier un champ de saisie de la modal avec une donnée dans le parent, automatiquement synchronisée.
  • outputBinding('close', ...) permet de réagir à la fermeture de la modal.

Le composant créé dynamiquement : Modal

@Component({
  selector: 'app-modal',
  imports: [FormsModule],
  template: `
    <h1>{{ title() }}</h1>
    <input [(ngModel)]="data" name="data"/>
    <button (click)="close.emit(true)">Youhou !</button>
    <button (click)="close.emit(false)">D’accord.</button>
  `,
  styleUrl: './modal.scss'
})
export class Modal {
  title = input.required<string>();
  data = model<string>();

  close = output<boolean>();
}
  • title est un input réactif injecté dynamiquement.
  • data est synchronisé via twoWayBinding().
  • close émet vers le parent au clic sur l'un des boutons.

La directive conditionnelle : TitleColorDirective

@Directive({
  selector: '[titleColorDirective]',
  host: {
    '[class]': 'clazz()'
  }
})
export class TitleColorDirective {
  clazz = input.required<string>();
}
  • La classe CSS appliquée au <h1> est dynamique selon le contenu de data.
  • Cette directive est injectée uniquement lorsque le composant est créé, via directives dans createComponent().
Support directives and bindings on dynamically-created components by crisbeto · Pull Request #60137 · angular/angular
These changes expand the APIs for creating components dynamically (createComponent, ViewContainerRef.createComponent and ComponentFactory.create) to allow users to apply directives on the component…

Lien vers la Pull Request de support des directives pour la création de composants dynamiques

feat(core): add support for two-way bindings on dynamically-created components by crisbeto · Pull Request #60342 · angular/angular
Builds on the changes from #60137 to add support for two-way bindings on dynamically-created components. Example usage: import {createComponent, signal, twoWayBinding} from &#39;@angular/core&#39;;…

Lien vers la Pull Request de création de composants dynamiques

Style guide

Suppression des suffixes .component, .service, etc.

L’une des évolutions les plus visibles est l’abandon des suffixes explicites dans les noms de fichiers et de classes. Désormais :

  • Ancienne convention : user-profile.component.ts → UserProfileComponent
  • Nouvelle convention recommandée : user-profile.ts → UserProfile

Cette recommandation vise à alléger les noms et à favoriser la lisibilité. Elle s’applique également à d’autres entités Angular (.pipe, .directive, .service, etc.).

🔹 Bon à savoir : Il ne s’agit pas d’une obligation. Si vous souhaitez conserver l’ancienne convention, Angular 20 permet de le faire très simplement via les schematics.

Conserver les anciens noms via angular.json

Pour garder l’ancienne convention de nommage après la migration vers Angular 20, vous pouvez configurer les schematics dans angular.json comme suit :

"schematics": {
    ...
    "@schematics/angular:component": {
      "type": "component"
    },
    "@schematics/angular:directive": {
      "type": "directive"
    },
    "@schematics/angular:service": {
      "type": "service"
    },
    "@schematics/angular:guard": {
      "typeSeparator": "."
    },
    "@schematics/angular:interceptor": {
      "typeSeparator": "."
    },
    "@schematics/angular:module": {
      "typeSeparator": "."
    },
    "@schematics/angular:pipe": {
      "typeSeparator": "."
    },
    "@schematics/angular:resolver": {
      "typeSeparator": "."
    }
  }

Cette configuration sera appliquée automatiquement lors de la montée de version :

Nouvelle structure de projet : disparition du dossier app/

Le dossier app/, historiquement présent dans tous les projets Angular, disparaît au profit d’une organisation par domaines fonctionnels :

-src/app/
+src/features/
+src/shared/
+src/core/

Angular recommande une structure modulaire et orientée métier :

  • features/ : contient les fonctionnalités métiers de l’application
  • shared/ : regroupe les composants ou modules réutilisables
  • core/ : contient les services globaux, guards, interceptors, etc.
🔍 Référence :
Angular v20 Style Guide – structure par fonctionnalités
Angular v19 Style Guide – structure 04-06

Nouveau style pour lier dynamiquement classes et styles CSS

Autre changement notable : Angular 20 recommande désormais l’utilisation directe de [style.X] et [class.X] dans les templates, plutôt que ngStyle et ngClass.

Avant :

<div [ngClass]="{ 'active': isActive }"></div>
<div [ngStyle]="{ color: color }"></div>

Maintenant recommandé :

<div [class.active]="isActive"></div>
<div [style.color]="color"></div>

Router

Resolver

Auparavant, il n’était pas possible d’accéder directement aux données résolues par un parent depuis un resolver enfant. Des discussions sur GitHub ont mis en évidence cette limitation, notamment dans l’issue #47287. Cette contrainte est désormais levée avec Angular 20 : les données résolues par une route parent sont automatiquement fusionnées dans le route.data des routes enfants. Ci-dessous un exemple :

export const routes: Routes = [
  {
    path: 'pokemons/:id',
    resolve: { pokemon: pokemonsResolver },
    children: [
      {
        path: 'types',
        // 👇 Ici, le resolver `typesResolver` a maintenant accès à `route.data['pokemon']`
        resolve: { types: typesResolver },
        loadComponent: () => import('./http-resource/http-resource')
      }
    ]
  }
];

Dans cet exemple, pokemonsResolver récupère un Pokémon via l’id de l’URL. Ensuite, la route enfant types utilise typesResolver, qui peut accéder directement à route.data['pokemon'] pour charger dynamiquement les types associés au Pokémon.

export const typesResolver: ResolveFn<Type[]> = (route, state) => {
  const pokemon = route.data['pokemon']; // disponible grâce au resolver parent
  return inject(TypesService).getTypesForPokemon(pokemon.id);
};
feat(router): Allow resolvers to read resolved data from ancestors by atscott · Pull Request #59860 · angular/angular
This commit updates the resolver execution to ensure that resolvers in children routes are able to read the resolved data from anything above them in the route tree. Because resolvers on one level…

Lien vers la Pull Request d'accès aux données du resolver parent

Redirection asynchrone

Jusqu’à Angular 19, les redirections dans la configuration des routes étaient limitées à des chaînes de caractères statiques ou un UrlTree :

{ path: '', redirectTo: 'home', pathMatch: 'full' }
ou
{ path: '', redirectTo: () => router.createUrlTree(['/home']), pathMatch: 'full' }

Il était impossible d’effectuer une redirection asynchrone directement dans la configuration des routes. Pour contourner cette limitation, il fallait  passer par des guards (canActivate) pour intercepter la navigation, ou déclencher un router.navigate() dans un composant. Avec Angular 20, la fonction redirectTo accepte désormais un retour asynchrone :

type MaybeAsync<T> = T | Promise<T> | Observable<T>;

Et plus précisément :

redirectTo: () => MaybeAsync<string | UrlTree>

Exemple avec fonction async :

{
  path: '',
  redirectTo: async (): Promise<string> => {
    const authService = inject(AuthService);
    const status = await authService.checkAuthStatus();
    return status.isLoggedIn ? 'dashboard' : 'login';
  },
  pathMatch: 'full'
}

Ici, la fonction redirectTo effectue un appel asynchrone via AuthService, et redirige dynamiquement vers 'dashboard' ou 'login'.

Conclusion

Angular 20 continue de faire évoluer le framework dans la bonne direction : plus de flexibilité, moins de contournements, et des APIs modernes pensées pour les développeurs.

La mise à jour du style guide officiel témoigne aussi d’une volonté d’harmoniser les pratiques de développement, en apportant clarté et cohérence aux projets Angular de toutes tailles.

Ces nouveautés ne sont pas de simples ajouts cosmétiques : elles traduisent une attention croissante portée à l’expérience développeur, à la lisibilité du code, et à la maintenabilité à long terme.

Que vous construisiez une application existante ou un nouveau projet from scratch, Angular 20 vous offre des fondations plus solides, plus intuitives et plus puissantes.

Il ne reste plus qu’à expérimenter, tester… et mettre à jour vos projets ! 🚀

📚 Pour aller plus loin

Dernier