Angular modern code style
Angular évolue pas mal depuis 3 ans et la version 16. Pas mal de choses ont changés dans la manière d'écrire du code, et c'est parfois compliqué de suivre tout ce qu'on peut faire et comment le faire bien. Revenons sur tout ça !
Note : tout ce que je vais dire dans cet article, il est possible de le retrouver de manière "interactive" sur une petite application que j'ai créée (avec Angular évidemment) et déployée ici. Cette application "guideline" est pensée pour montrer comment passer d'un composant utilisant les anciens code-styles vers un composant plus au goût du jour.
Écosystème et quotidien de dev🔗
Avant même de parler de code style, il faut rappeler qu'Angular c'est plus qu'un framework : c'est un écosystème de librairies et d'outils pour nous aider au quotidien, pour répondre à différent besoin, certains officiels, d'autres non.
Le premier à citer pour moi c'est l'Angular DevTools, car il va vous permettre d’introspecter votre application pendant la phase de développement pendant qu'elle tourne. C'est très pratique, ça donne une autre vision de l'application, et même si ça demande un peu d'habitude, ça peut vraiment aider à comprendre plus vite ce qui coince.
Ensuite pour moi en 2026, on est obligé de parler IA. Angular propose un fichier AGENTS.md (ou ̀CLAUDE.md, etc.) qui est très complet, et il donne une bonne base pour comprendre comment on doit faire du Angular dans le monde moderne. Il y a évidemment quelques portes ouvertes qui sont enfoncées (ce fichier est prévu pour cadrer un Agent IA à la base, donc c'est logique), mais il y a un début de piste pour accompagner chaque développeur aussi.
Une étape du quotidien à ne pas négliger c'est les migrations de versions. Si on les fait régulièrement et en suivant des bonnes pratiques, c'est très simple, sinon ça peut devenir un calvaire. C'est facile d'être accompagné pour cette étape, et il faut pour moi commencer par aller lire le blog officiel qui va annoncer les nouveautés importantes de chaque mise à jour. Je recommande aussi d'aller voir le blog de la Ninja Squad où pour chaque version on retrouvera un billet beaucoup plus détaillé, rentrant en profondeur dans ce qui change et s'il faut faire attention à certains points. Il faut aussi inspecter les dépendances du projet, qui peuvent nous lier à une version Angular et nous empêcher de migrer. Pour réaliser la migration il faut absolument suivre le guide de mise à jour officiel, car il va nous indiquer tout ce qu'il faut absolument vérifier avant de commencer la mise à jour, la commande à lancer pour réaliser la mise à jour et ce qu'il faut valider après coup. Personnellement pour chaque migration, je vais m'assurer que je n'ai rien oublié côté script de migration via Angular Can I Use et vérifier aussi quelles nouvelles fonctionnalités je peux maintenant utiliser (particulièrement utile si vous avez un peu de retard sur les versions !). On termine par un coup d'œil pour s'assurer qu'aucune dépendance liée à Angular n'a été oublié et qu'un npm ci (après suppression des node_modules) fonctionne toujours bien.
Parlons composant simple🔗
Même si tout le monde n'est pas d'accord avec ça : je vous recommande d'utiliser des Single File Component (SFC). C'est quoi un SFC ? Simplement un composant qui est entièrement écrit dans le fichier TypeScript, avec le template dans la propriété template (et non templateUrl, qui permet de pointer un fichier HTML), et le style dans styles. Ça va vous débloquer plusieurs choses : vous allez moins naviguer de fichier en fichier, vous serez incité à créer des petits composants (réutilisable ou non) pour ne pas avoir un fichier trop gros. Un composant n'est qu'une classe TypeScript et un template, ne sacralisez pas la création d'un composant : faites des petits composants avec une responsabilité limitée et tout deviendra plus simple et plus maintenable dans le temps. Rappelez-vous aussi que rien n'interdit en Angular d'avoir plusieurs composants / directives dans le même fichier !
Il faut évidemment utiliser les nouveaux control flow. Adieu les *ngIf, *ngFor et *ngSwitch, bonjour les @if, @for et @switch. Ils sont plus simples à utiliser, beaucoup plus performant, bien mieux typé, ne demande aucun import. Pour le @for vous serez aussi obligé de passer un track pour qu'Angular sache comment il doit identifier chaque entrée du @for et limiter les re-rendu inutile. Il faut autant que possible éviter de passer $index, qui risque de dégrader les performances si vous avez besoin de re-rendre souvent la liste à cause d'ajout / suppression.
Côté entrée/sortie, exit aussi des @Input() et @Output(), bienvenue aux input() et output(). Je dirais même que vous voulez au maximum écrire input.required<TheInputType>() pour indiquer explicitement quelles sont les entrées obligatoires au bon fonctionnement de votre composant, et aussi leur type pour que ce soit vérifié à la compilation ! Comme nos entrées / sorties sont censés être immuables, profitez-en pour les passer systématiquement en public readonly, de cette façon c'est TypeScript qui s'assurera que vous ne fassiez pas d'erreur.
Dans la même logique, ajoutez private readonly devant chaque propriété que vous définissez dans votre classe, protected readonly si vous avez besoin d'y accéder dans le template. Encapsuler au maximum vos composants est toujours une bonne chose, et protéger au maximum les états internes est important. Au passage ça donnera une indication claire aux autres développeurs sur où est utilisé un élément (private = uniquement dans la classe TypeScript, protected = dans la classe et/ou le template, public = à l'extérieur). En TypeScript, ne pas indiquer de modificateur de portée impliquera que l'élément sera public implicitement.
Même si historiquement on conseillait d'utiliser beaucoup plus les Pipe pour les formatages, et au final à l'usage, on se rend compte qu'il vaut mieux utiliser simplement un computed(). La syntaxe est plus courte, on retrouve quasiment les mêmes avantages que les Pipes (à part la réutilisabilité), c'est assez naturel quand on manipule beaucoup de Signal.
Respecter le naming convention d'Angular c'est important aussi. Les basiques c'est avoir un app- au début du sélecteur de chaque composant, utiliser systématiquement du kebab-case pour le nom du sélecteur, du PascalCase pour les noms de composant (sans spécifier Component à la fin). Techniquement ça fonctionne bien sans ça, mais ça permet de plus facilement s'y retrouver, plus facilement suivre ce qu'on fait, être raccord entre les différents membres de l'équipe.
Pour finir : ne pas oublier la plateforme. Quand vous faite du Angular, vous allez utiliser la plateforme web, utilisez une solution native de la plateforme web plutôt qu'une solution Angular sera toujours mieux !
Voici un exemple avec deux composants qui respectent ce code style :
import { Component, computed, input } from "@angular/core";
interface Person {
id: string;
firstname: string;
lastname: string;
}
@Component({
selector: "app-person-card",
template: `
<div class="card">
<h3>{{fullName()}}</h3>
</div>
`,
styles: `
.card {
border: 1px solid light-dark(lightblue, orange);
padding: 0.5em 1rem;
}
`,
})
export class PersonCard {
public readonly person = input.required<Person>();
protected readonly fullName = computed(() => `${this.person().firstname} ${this.person().lastname}`);
}
const PEOPLE: Person[] = [
{ id: "0", firstname: "Aïcha", lastname: "Diallo" },
{ id: "1", firstname: "Lucas", lastname: "Martinez" },
{ id: "2", firstname: "Léa", lastname: "Nguyen" },
{ id: "3", firstname: "Karim", lastname: "Benali" },
{ id: "4", firstname: "Mei", lastname: "Chen" },
{ id: "5", firstname: "Élodie", lastname: "Moreau" },
];
@Component({
selector: "app-person-list",
template: `
@for (person of PEOPLE; track person.id) {
<app-person-card [person]="person" />
}
`,
styles: `
:host {
display: flex;
gap: 0.25em;
flex-wrap: wrap;
}
`,
imports: [PersonCard],
})
export class PersonList {
protected readonly PEOPLE = PEOPLE;
}
Et on ajoute un service !🔗
Le grand classique c'est de voir des choses comme ça :
@Component({ ... })
export class PersonList implements OnInit {
protected people: Person[] = [];
constructor(private http: HttpClient) {}
ngOnInit() {
this.http.get("/assets/demo/components-and-services/people.json").subscribe((people: any) => {
this.people = people;
});
}
}
Trop de problèmes… On passe toujours pas de l'injection dans le constructeur, on a toujours un ngOnInit(), on utilise le HttpClient dans le composant directement, on fait un subscribe (sans unsubscribe de surcroit), on ne type correctement…
En Angular moderne, vous pouvez faire beaucoup plus simple. Déjà vous pouvez passer directement par inject() pour injecter vos différents services en propriété (private readonly évidemment !). Comme on a besoin que d'un seul appel, on peut directement faire d'un coup l'injection et l'appel, en typant directement le inject(HttpClient).get<Person[]>(...) pour éviter un any. On peut éviter le .subscribe() en passant par un toSignal(...) et laisser Angular gérer ça pour nous.
Tant qu'à faire on peut déplacer toute cette logique d'appel vers un service dédié. Si on a besoin, on pourra alors même faire un cache interne au service pour s'assurer qu'on ne fait pas un appel HTTP pour chaque utilisation du service.
En résumé, l'idée c'est de viser quelque chose qui ressemble à ça :
import { HttpClient } from "@angular/common/http";
import { Component, Injectable, inject } from "@angular/core";
import { toSignal } from "@angular/core/rxjs-interop";
import { catchError, of, shareReplay } from "rxjs";
import { PersonCard } from "./person-card";
export interface Person {
id: string;
firstname: string;
lastname: string;
}
@Injectable({ providedIn: "root" })
export class PeopleService {
private readonly http = inject(HttpClient);
private readonly people$ = this.httpGetPeople().pipe(shareReplay(1));
getPeople() {
return this.people$;
}
private httpGetPeople() {
return this.http.get<Person[]>("/assets/demo/components-and-services/people.json").pipe(
catchError((err) => {
console.error(err);
return of([]);
}),
);
}
}
@Component({
selector: "app-person-list",
template: `
@for (person of people(); track person.id) {
<app-person-card [person]="person" />
}
`,
styles: `
:host {
display: flex;
gap: 0.25em;
flex-wrap: wrap;
}
`,
imports: [PersonCard],
})
export class PersonList {
protected readonly people = toSignal(inject(PeopleService).getPeople(), { initialValue: [] });
}
Côté composant c'est vraiment beaucoup plus simple, on retrouve juste une déclaration d'un accès à une liste de personne. Dans le service on retrouve notre appel HTTP mais aussi une gestion d'erreur (simpliste mais présente pour l'exemple), on retrouve aussi une mécanique simple de partage de ressource entre différent appelants (comme on pose dès le début un Observable people$ qui est un appel HTTP suivi d'un shareReplay(1), le premier subscriber va déclencher l'appel HTTP, les suivants consommeront directement la réponse). Au passage cette mécanique nous permet d'encapsuler entièrement l'appel HTTP, donc si demain on veut changer quoi que ce soit (changer l'URL, ajouter une transformation avant/après chaque appel, etc.) on pourra le faire en impactant pas les appelant ou à minima en sachant très facilement qui dépend de cet appel (seulement les appelants de getPeople() qui ne peuvent que lire cet Observable et rien de plus).
Conclusion🔗
On trouve beaucoup de ressources en ligne qui indiquent comment faire bien un point particulier en Angular, on retrouve peu de contenu qui vous donne un guide pour passer sur le code style moderne avec les explications qui vont bien. Je ne sais pas si ce que j'ai fait répondra vraiment à ce besoin, mais je vais essayer de maintenir mon application à jour au fil des versions, d'améliorer le contenu au fil du temps, pour le rendre toujours utile au plus grand nombre.
Sources :
- Guide interactif
- Angular DevTools
- Blog officiel Angular
- Blog de la Ninja Squad
- Guide de mise à jour officiel
- Angular Can I Use
Crédit photo : Générée via Mistral AI avec le prompt suivant :
A serene Studio Ghibli-inspired scene of a Zen garden where Angular component flowers bloom, viewed from a close-up perspective. Each flower has a label with modern Angular features like '@for', 'computed()', 'toSignal', and 'inject()'. The flowers representing SFC (Single File Component) are peonies with petals shaped like TypeScript files. A magical watering can (teapot-shaped) is watering the flowers, making them grow, while weeds labeled '*ngFor without track' and 'any' wither away. A red panda, sitting cross-legged, is trimming a bush shaped like the 'PersonCard' component. In the background, a torii gate has a sign reading 'Angular DevTools'. The atmosphere is peaceful with green/blue gradients and golden reflections on the petals. The scene is set in autumn with a warm, golden light, wooden textures, and 8-10 floating maple leaves in red, orange, and yellow. The red panda is in the bottom right, less than 1/3 of the height, with a glowing halo around the high-tech elements.