En tant que développeur front, il est utile d'avoir plusieurs cordes à son arc, et de savoir être à l'aise dans plusieurs environnements. Savoir coder directement sans framework permet de mieux connaître la plateforme web, et maîtriser plusieurs technologies front permet de mieux comprendre les différents tradeoffs que chacun fait, et de pouvoir identifier les points de convergence à creuser. Dans cet article, nous aborderons Angular d'un point de vue de développeur React.
Le framework
Contrairement à React, qui n'est qu'une brique dans votre frontend, Angular est un framework qui est fourni avec les piles. Le routing de votre application ? Angular. Le client HTTP ? Angular. Les formulaires ? Angular encore.
Comparativement, React ne s'occupe pas de tout ça, et toute application est un assemblage délicat d'un routeur (react-router, tanstack router), d'un client http (fetch, axios), et de formulaires (react-hook-form, formik, ou encore tanstack form). La team React conseille même d'utiliser un framework comme Next.js ou Remix pour débuter en React.
Combattez votre instinct d'aller chercher ailleurs le meilleur outil et utilisez le framework d'abord. Je ne dis pas que vous devez absolument rester à l'intérieur du framework, mais vos collègues apprécieront que vous utilisiez les briques familières d'Angular.
Le compilateur
Les applications Angular sont avant tout compilées. Cette approche statique arrive dans React avec react-compiler, mais c'est une optimisation optionnelle qui vient en complément de la boucle de rendu dynamique.
L'avantage de tout compiler en Angular, c'est que l'on maîtrise l'intégralité de l'univers dans lequel s'exécute notre application. L'environnement est statiquement connu, et les dépendances sont résolues statiquement via l'injecteur. De la même façon, les traductions (qui sont encore une fois gérables via un composant Angular) peuvent être gérées statiquement, ce qui offre de gros avantages en termes de (auto-)complétion. Fini les multiples initialisations, on a la garantie que nos constructeurs sont appelés une seule fois dans le cycle de vie de notre application.
La CLI
L'élément central dans le développement en Angular, c'est la CLI. Appelée ng
, à l'instar du préfixe quasi-universel dans l'écosystème Angular, elle est au centre de tous nos besoins.
Elle peut bien évidemment compiler et servir notre application (en dev et en prod), mais elle peut également générer des squelettes de composants ou de services, extraire des fichiers de traductions, le tout via un mécanisme puissant de Schematics. Les migrations pour passer d'une version majeure du framework à une autre sont majoritairement automatisées, et tout ça passe par la CLI.
La CLI gère également la partie tests unitaire/intégration/end-to-end.
Il existe également une autre CLI appelée nx
qui va permettre de gérer tout le cycle de vie d'un monorepo, mais mon conseil est de rester sur ng
et de s'épargner toute la complexité supplémentaire que vient ajouter nx
(cherchons à rester dans le framework, encore une fois)
Les composants
Un composant Angular possède au minimum trois éléments:
- Un sélecteur. C'est le nom du composant dans le template lorsqu'il est utilisé. On va très souvent utiliser des noms de custom components, comme app-player ou app-sidebar. Là où les composants React ont plutôt un nommage en PascalCase, Angular suit la convention custom-elements html et utilise du kebab-case.
- Un template. C'est le html qui est rendu par le composant, comme la valeur de retour de nos composants react.
- Une classe Typescript qui va encapsuler tout le comportement.
@Component({
selector: 'profile-picture',
template: `<img src="profile-photo.jpg" alt="Your profile photo">`
})
class ProfilePicture { }
Composant minimal Angular
function ProfilePicture(){
return <img src="profile-photo.jpg" alt="Your profile photo" />
}
Composant minimal React
Il est tout à fait possible de définir un composant dans un seul fichier, cependant si celui-ci contient beaucoup de markup ou de comportement on voudra souvent séparer le template du composant et aussi y attacher du style. Pour cela on partira souvent de la commande ng generate component profile-picture
(la CLI n'est jamais très loin rappelez-vous).
Si on souhaite utiliser des composants à l'intérieur d'autres composants, il faut les déclarer dans la section imports. Cela facilite le travail du compilateur pour gérer les dépendances.
@Component({
selector: 'app-root',
template: `
<section>
<app-user />
</section>
`,
imports: [UserComponent],
})
export class AppComponent {}
Templating
Angular possède un langage de templating plus riche que JSX, avec quelques différences:
- Les composants ayant des noms en kebab-case, un template Angular ressemble assez à du html avec des Web Components.
- Pour interpoler une variable à l'intérieur du template, on utilise des doubles accolades à la mustache au lieu des simples en React.
<h1>Hello {{subject}}</h1>
- Si on veut lier une variable dans une propriété, on met la propriété entre crochets:
<button [disabled]="isSubmitting">Submit</button>
- Le langage de template Angular possède des structures natives pour gérer le flot de contrôle:
@if (condition) { ... } @else { ... }
à la place de{condition ? ( ... ) : ( ... )
@for ( item of list; track item.id ) { ... }
à la place de{ list.map(item => <Fragment key={item.id}> ... </Fragment> )}
@switch (condition) { @case(value) { ... } @default { ... } }
à la place de multiples{ condition === value && ( ... )}
@defer { ... } @loading { ... }
à la place de<Suspense fallback={...}> ... </Supense>
Events handlers
Les event handlers React sont calqués sur les events handlers DOM onClick, onKeyUp, etc.
function MyButton(){
const handleClick = () => { console.log('Doing things') }
return <button onClick={handleClick}>Do the thing!</button>
}
Les events handlers Angular sont eux définis dans la classe (c'est du comportement), et utilisés dans le template avec le nom de l'événement entre parenthèses.
@Component({
selector: 'my-button',
template: `<button (click)="handleClick()">Do the thing!</button>`
})
class MyButton {
handleClick(){
console.log('Doing things')
}
}
Lifecycle hooks
Historiquement, Angular possède des lifecycle hooks ngOnInit, ngOnDestroy, afterRender, etc. (https://angular.dev/guide/components/lifecycle). Ces hooks sont à éviter, tout comme ceux en React onMount, componentWillUnmount, componentDidUpdate, etc.
Les signaux sont la nouvelle syntaxe à utiliser, tout comme les hooks en React.
Les signals
Les signaux sont la dernière nouveauté d'Angular, et changent assez profondément la façon de coder les composants, un peu comme les hooks l'ont été pour React. On les déclare dans la classe un peu comme les hooks, la seule différence est qu'un signal est un peu comme un getter, il faut l'appeler pour récupérer la valeur.
@Component({
selector: 'profile-picture',
template: `<img [src]="profileUrl()" alt="Profile photo">`
})
class ProfilePicture {
profileUrl = signal("profile-photo.jpg")
}
Les signaux peuvent être dérivés les uns des autres via isEven = computed(() => numberVariable() % 2 === 0)
. Le compilateur va automatiquement détecter les signaux utilisés pour déduire les dépendances.
effect(() => {
console.log(`The current count is: ${count()}`);
});
Pas besoin de spécifier les dépendances, elles sont automatiquement déduites par le compilateur.
On conseille d'utiliser des signaux pour toute donnée dynamique.
Passage d'informations entre composants
Pour passer simplement de la donnée entre composants en React, on a deux choix: les props ou le contexte. Nous ne parlerons pas ici de stores globaux comme zustand, jotai ou redux.
L'équivalent Angular des props pour passer des données d'un parent à un enfant est le signal input()
.
// user.component.ts
class UserComponent {
occupation = input('');
}
// app.component.ts
@Component({
...
template: `<app-user occupation="Angular Developer"></app-user>`
})
class AppComponent {}
Pour remonter de la donnée dans l'autre sens, React utilise les callbacks de façon libérale. En Angular, on va utiliser un EventEmitter<T>
dans le composant enfant avec le signal output()
. Le composant enfant va pouvoir émettre des valeurs via this.mysignal.emit(value)
, valeur qui pourra être récupérée dans le parent via un event handler.
@Component({/*...*/})
export class ExpandablePanel {
panelToggled = output<boolean>();
togglePanel = () => {
// on récupère newState comme le nouvel état du panel
this.panelToggled.emit(newState)
}
}
Services
Pour toute interaction un peu plus complexe, on va définir des Services Angular (toujours via la CLI). Un service fondamentalement est une classe (car elle définit du comportement) décorée via le décorateur @Injectable
. On va généralement déclarer ce service à la racine via providedIn: 'root'
. Ces services sont ensuite injectés dans les composants via la fonction inject(UserService)
, un peu comme on ferait un useContext
en React.
Conclusion
Cet article fournit une base de comparaison assez limitée, je vous invite à regarder la documentation officielle qui est assez complète (Angular étant un framework assez complet, sa documentation couvre pas mal de sujets).
Un grand merci à Anthony Pena pour son expertise et ses conseils.