Le Compilateur de Compatibilité Angular (Angular Compatibility Compiler, ACC) est un outil puissant conçu pour faciliter la migration des applications Angular depuis l'ancien moteur de rendu (View Engine) vers le moteur Ivy tout en assurant la compatibilité avec les versions précédentes du framework. Cet outil s’avère indispensable dans le cadre des mises à jour progressives d'applications qui utilisent des bibliothèques non mises à jour ou des versions d'Angular antérieures à la version 9.

Le rôle principal de l'ACC est de permettre l’utilisation simultanée du moteur View Engine et du moteur Ivy, une fonctionnalité clé pour les équipes souhaitant migrer progressivement sans tout réécrire d'un coup. En effet, il permet aux développeurs d'implémenter des versions modernes d'Angular tout en maintenant la compatibilité avec les anciennes versions, réduisant ainsi les risques d'incompatibilité et de régressions durant la migration.

Dans un flux de travail CI/CD (Intégration Continue / Déploiement Continu), l’ACC est essentiel pour tester les nouvelles fonctionnalités dans un environnement de production tout en garantissant la stabilité des versions précédentes. L'intégration de l'ACC dans ce flux permet d’automatiser le processus de test, en s’assurant que les applications restent fonctionnelles lors de la mise à jour, sans introduire d’erreurs dans le code existant. Il est ainsi possible de vérifier en continu que le code fonctionne sous Ivy tout en étant compatible avec les anciennes versions via le View Engine, évitant ainsi des ruptures de compatibilité lors des déploiements successifs.

Le principal avantage de l'ACC réside dans sa capacité à faciliter la migration des applications sans nécessiter de refonte complète. Les projets plus complexes, notamment ceux ayant un grand nombre de dépendances externes ou de bibliothèques internes, peuvent adopter Ivy progressivement, ce qui améliore la productivité des équipes de développement en permettant de s’adapter à de nouveaux outils tout en préservant une stabilité maximale.

Lorsque vous travaillez avec l'ACC, il est crucial de comprendre que son utilisation dépend largement de la configuration spécifique de votre projet. Les options du compilateur Angular permettent de définir comment le compilateur doit gérer les composants hérités du View Engine. En fonction de ces paramètres, vous pouvez ajuster la manière dont les bibliothèques et les applications sont traitées, ce qui offre une flexibilité maximale pour gérer des projets avec des dépendances hétérogènes. De plus, en utilisant des outils comme le ng update et en exploitant les mises à jour automatiques d'Angular, il devient plus facile de maintenir une cohérence à travers différents modules et applications d'un même projet.

Dans un environnement de développement CI/CD, il est également essentiel de comprendre les implications de performance liées à l'utilisation du Compilateur de Compatibilité. Bien que l'outil soit conçu pour fonctionner en arrière-plan, son intégration dans le flux de travail doit être bien gérée afin de ne pas alourdir les processus de construction et de test. L’optimisation de la configuration du compilateur est donc un aspect clé pour garantir que les tests restent rapides et efficaces.

Les projets qui bénéficient le plus de l'ACC sont ceux qui ne peuvent pas se permettre une mise à jour complète immédiate à cause de la taille du code ou des dépendances externes spécifiques. Cependant, même pour ces projets, il est important de planifier une migration progressive et de garder à l'esprit que l'ACC est une solution transitoire. À terme, les applications doivent migrer entièrement vers Ivy pour bénéficier des meilleures performances et des nouvelles fonctionnalités d'Angular.

En conclusion, le Compilateur de Compatibilité Angular est un outil essentiel pour gérer la migration d’Angular View Engine vers Ivy, permettant une compatibilité entre les anciennes et nouvelles versions du framework. Son utilisation dans un flux CI/CD est un moyen efficace de maintenir la stabilité tout en modernisant les applications Angular progressivement. Toutefois, une gestion précise des options du compilateur et une planification des mises à jour régulières sont nécessaires pour garantir une transition sans heurts et optimisée.

Comment utiliser les composants de test dans Angular Ivy pour améliorer les tests d'interface utilisateur

Avec l’évolution d’Angular Ivy, une nouvelle approche a vu le jour pour faciliter les tests de composants Angular en rendant les interactions plus naturelles et moins sensibles aux changements structurels du DOM. En particulier, les composants de test permettent de séparer l’interface de test de l'implémentation du DOM, en s’inspirant du modèle "Page Object" largement adopté dans le développement des tests automatisés.

Dans ce chapitre, nous allons explorer comment utiliser les composants de test avec Angular et Angular Material, et comment créer nos propres composants de test afin de rendre les tests plus modulaires et moins dépendants des détails internes de l’interface utilisateur.

Utilisation des composants de test Angular Material

Prenons un exemple simple avec un composant Angular Material, comme un bouton ou un champ de saisie. Imaginons que nous souhaitions tester un champ de saisie qui permet de sélectionner une couleur pour l’arrière-plan d'un en-tête. Pour simuler cette interaction utilisateur, Angular Ivy fournit des composants de test (harnesses), qui sont des abstractions permettant d’interagir avec les éléments DOM sans avoir à manipuler directement le DOM.

Dans le cas d’un champ de saisie MatInputHarness, nous pouvons, par exemple, tester la valeur de l’arrière-plan en simulant l’action de l’utilisateur et en récupérant la valeur sélectionnée :

typescript
it('devrait être capable de lire la couleur de fond par défaut de l\'en-tête', async () => {
const headerBackground: MatInputHarness = await loader.getHarness(
MatInputHarness.with({ selector: '#headerBackground' }) );
expect(await headerBackground.getValue()).toBe('#00aa00');
});

Ici, l’objectif est de tester que la couleur de fond par défaut est bien celle attendue (#00aa00). En utilisant le composant de test MatInputHarness, nous n’avons pas besoin de manipuler directement le DOM ou de vérifier l’état de l’élément à l’aide de méthodes comme fixture.detectChanges(). Le composant de test gère ces interactions en arrière-plan, ce qui rend le test plus stable et moins sensible aux changements internes du DOM.

Un autre exemple pourrait être de tester la possibilité de changer cette couleur. Grâce à MatInputHarness, il devient facile d’assigner une nouvelle valeur à l’élément de saisie et de vérifier si la nouvelle valeur est bien enregistrée :

typescript
it('devrait être capable de changer la couleur de fond de l\'en-tête', async () => {
const headerBackground: MatInputHarness = await loader.getHarness(
MatInputHarness.with({ selector: '#headerBackground' }) ); await headerBackground.setValue('#ffbbcc');
expect(themeService.getSetting('headerBackground')).toBe('#ffbbcc');
});

Grâce à cette approche, nous évitons d’avoir à effectuer des manipulations complexes du DOM tout en garantissant que l’interaction se déroule comme prévu. La gestion des changements de DOM est ainsi laissée à l’harness, ce qui simplifie le code de test et le rend plus fiable.

Création de composants de test personnalisés

Si nous souhaitons tester un composant plus complexe, comme une vidéo dans une application de formation, la méthode décrite ci-dessus peut être adaptée. Supposons que nous ayons un composant Video qui affiche des vidéos YouTube à partir d’un composant Course. Tester ce flux de manière traditionnelle, avec des manipulations directes du DOM, serait difficile et peu fiable. Une meilleure approche consiste à créer un composant de test personnalisé pour chaque niveau de l’application.

Prenons l’exemple suivant d’un composant VideoHarness qui encapsule l’élément DOM correspondant au sélecteur workspace-video. Cela permet de masquer la complexité et de se concentrer sur l’interaction avec le composant au niveau de l’interface.

typescript
export class VideoHarness extends ComponentHarness {
static hostSelector = 'workspace-video'; protected getTextElement = this.locatorFor('.text');
async getText(): Promise<string> {
const textElement = await this.getTextElement(); return textElement.text(); }
textEquals(video: IVideo, text: string): boolean {
return text?.toLowerCase().trim().includes(video.title.trim().toLowerCase()); } }

Dans cet exemple, la méthode getText() extrait le texte du titre de la vidéo, tandis que textEquals() permet de comparer ce texte à un titre attendu. Nous pouvons ensuite tester l’affichage des vidéos dans le cadre d’un cours donné.

typescript
it('devrait afficher le titre de la vidéo en texte lors de son affichage', async () => {
const renderedVideos = await loader.getAllHarnesses(VideoHarness);
courseService.
getCourse('1').subscribe((course) => {
renderedVideos.forEach(async (video: VideoHarness) => {
const text = await video.getText() || ""; expect(course.videos.find((v) => video.textEquals(v, text))).toBeTruthy(); }); }); });

Cette approche nous permet de tester le rendu de vidéos sans avoir à interagir directement avec le DOM. En masquant les détails d’implémentation, comme ceux relatifs au lecteur YouTube intégré, le test devient plus robuste et moins susceptible de casser lors des mises à jour du DOM ou de l’interface utilisateur.

Conclusion

Les composants de test d’Angular Ivy, tels que les ComponentHarness, offrent une méthode puissante pour simplifier les tests d’interfaces utilisateur tout en améliorant leur robustesse et leur maintenabilité. En utilisant ces composants, nous pouvons tester nos composants de manière plus ciblée, en simulant les interactions de l’utilisateur sans être dépendants des détails internes de l’implémentation du DOM. De plus, la possibilité de créer des composants de test personnalisés permet d’adapter les tests aux spécificités de chaque application, rendant ainsi les tests plus flexibles et modulaires.

Comment utiliser les tests de composants Angular avec des portées de fournisseurs supplémentaires

Dans le cadre du développement d'applications avec Angular, il est souvent nécessaire de tester des composants spécifiques, comme un composant vidéo, tout en permettant la réutilisation et l'intégration de ces composants dans diverses situations et modules de l'application. Cet exemple illustre comment adapter un composant vidéo, en particulier, à l'aide de tests d'unités pour garantir son bon fonctionnement dans une application Angular, tout en introduisant la notion de portées de fournisseurs supplémentaires pour une gestion plus fine des dépendances dans l'application.

Prenons l'exemple de la création d'un test pour un composant vidéo dans une application Angular. Nous avons un test qui vérifie la disponibilité de l'ID du vidéo lorsqu'un composant vidéo est rendu. Voici une structure de test de base pour cela :

typescript
it('should have the videoId available when rendering the video', async () => {
const renderedVideos = await loader.getAllHarnesses(VideoHarness);
renderedVideos.
forEach( async(video: VideoHarness) => { const videoId = await video.getVideoId(); expect(videoId).toBeTruthy(); }); });

Dans cet exemple, le test repose sur la fonction getVideoId() du VideoHarness, qui extrait l'ID de la vidéo à partir d'un élément YoutubePlayerHarness situé dans le composant vidéo. Ce test simple vérifie que l'ID vidéo est bien présent lorsqu'un composant vidéo est rendu, garantissant ainsi que l'intégration du composant vidéo dans le module se fait correctement.

La structure du VideoHarness

La classe VideoHarness est un composant de test qui encapsule la logique permettant de tester les éléments de la vidéo. Voici un exemple de cette classe :

typescript
export class VideoHarness extends ComponentHarness { static hostSelector = 'workspace-video';
protected getTextElement = this.locatorFor('.text');
protected getVideoElement = this.locatorFor(YoutubePlayerHarness);
async getText(): Promise<string> {
const textElement = await this.getTextElement(); return textElement.text(); }
async getVideoId(): Promise<string> {
const videoElement = await this.getVideoElement(); return videoElement.getVideoId(); }
textEquals(video: IVideo, text: string): boolean {
return text?.toLowerCase().trim().includes(video.title.trim().toLowerCase()); } }

Le VideoHarness permet d'interagir avec le composant vidéo et d'effectuer des vérifications sur ses éléments comme le texte et l'ID vidéo. À ce stade, le composant vidéo peut être testé et utilisé de manière isolée dans toute l'application, garantissant sa réutilisation dans différents contextes et modules.

Introduction aux portées de fournisseurs supplémentaires

Dans cette partie, nous abordons l'usage des portées de fournisseurs dans Angular pour un contrôle plus précis de la gestion des dépendances. Un cas d'utilisation typique est celui des services que l'on souhaite injecter dans des modules ou composants spécifiques, tout en permettant une configuration différente pour chaque instance. Prenons l'exemple du ThemeService, un service qui gère les paramètres de thème pour l'application. En utilisant la portée de fournisseur any, nous pouvons créer des instances distinctes de ce service selon les modules ou composants dans lesquels il est utilisé.

Le code suivant montre comment configurer un service de thème configurable :

typescript
@Injectable({
providedIn: 'any', }) export class ThemeService { constructor(@Inject(themeToken) private theme: ITheme) {} public setSetting(name: string, value: string): void { this.setItem(name, value); } public getSetting(name: string): string { switch (name) { case 'background': return this.getItem(name) ?? this.theme.background; case 'tileBackground': return this.getItem(name) ?? this.theme.tileBackground; case 'headerBackground': return this.getItem(name) ?? this.theme.headerBackground; case 'textSize': return this.getItem(name) ?? this.theme.textSize; case 'videoSize': return this.getItem(name) ?? this.theme.videoSize; } return 'white'; } private setItem(name: string, value: string): void {
localStorage.setItem(this.prefix(name), value);
}
private getItem(name: string): string | null {
return localStorage.getItem(this.prefix(name));
}
private prefix(name: string): string {
return this.theme.id + '_' + name;
} }

Dans cet exemple, le service ThemeService est marqué avec la portée de fournisseur any, ce qui permet de configurer des instances spécifiques pour chaque module qui injecte ce service. Le service gère des paramètres comme la couleur d'arrière-plan, la taille du texte et la taille de la vidéo. La configuration des thèmes peut être modifiée à la volée et adaptée à chaque module de l'application.

Révision de la portée du fournisseur racine

La portée root est la portée par défaut dans Angular, où un service est injecté une seule fois dans toute l'application. Cependant, dans certaines situations, il peut être nécessaire de créer des instances spécifiques de services par module ou composant. Pour ce faire, il est possible de configurer un service en utilisant des portées plus spécifiques, comme la portée any ou même une portée spécifique au fournisseur du module.

L'utilisation de cette approche permet d’alléger les modules Angular, évitant ainsi des injections redondantes ou inutiles. Par exemple, dans un cas où l’on souhaite avoir un thème personnalisé pour un sous-ensemble d’utilisateurs, le service ThemeService peut être configuré avec un fournisseur spécifique à ce sous-ensemble.

Conclusion

Dans les applications Angular modernes, l'utilisation de composants réutilisables comme le VideoHarness et des services configurables comme le ThemeService permet une meilleure modularité et une gestion plus fine des dépendances. Les portées de fournisseurs supplémentaires, en particulier la portée any, offrent un contrôle avancé sur l’injection de services, permettant de créer des instances spécifiques à chaque module et d’optimiser la gestion des ressources.

Dans les prochaines étapes, il sera crucial d'apprendre à gérer la configuration dynamique des services et à intégrer efficacement les portées de fournisseurs pour garantir une application performante et bien structurée.

Comment tester des composants Angular avec des icônes SVG personnalisées dans un environnement Ivy ?

Dans les applications Angular où nous ajoutons des icônes SVG personnalisées, il est fréquent de rencontrer des erreurs lors des tests des composants utilisant ces icônes. Pour résoudre ce problème, Ivy introduit le FakeMatIconRegistry, qui remplace le MatIconRegistry traditionnel, lequel gère normalement la résolution des ressources statiques liées aux icônes. Cette nouvelle approche permet de tester des composants sans dépendre de la résolution des icônes, ce qui peut simplifier les tests.

Pour utiliser cette fonctionnalité, il suffit d'importer le module MatIconTestingModule depuis le sous-package @angular/material/icon/testing. Prenons l'exemple d'un composant représentant un bouton de lancement de vaisseau spatial. Ce bouton utilise une icône personnalisée dans son interface, et l'objectif est de tester son comportement lorsque l'utilisateur interagit avec lui.

Voici un exemple de code pour le composant SpaceshipLaunchButtonComponent :

typescript
@Component({ changeDetectionStrategy: ChangeDetectionStrategy.OnPush, selector: 'spaceship-launch-button', template: ` <button mat-icon-button (click)="onClick()"> <mat-icon svgIcon="spaceship"></mat-icon> </button> `, }) export class SpaceshipLaunchButtonComponent { constructor(private spaceship: SpaceshipService) {} onClick(): void { this.spaceship.launch(); } }

Dans cet exemple, le bouton est configuré pour lancer une action launch() lorsqu'il est cliqué. Cette fonctionnalité est liée à un service, SpaceshipService, que nous souhaitons simuler dans nos tests.

Pour tester ce composant dans un environnement Angular, nous utilisons la configuration suivante pour le module de test :

typescript
import { TestBed } from '@angular/core/testing';
import { MatIconModule } from '@angular/material/icon';
import { MatIconTestingModule } from '@angular/material/icon/testing';
import { By } from '@angular/platform-browser';
import { SpaceshipLaunchButtonComponent } from './spaceship-launch-button.component';
const MouseClickEvent = { Left: { button: 0 }, Right: { button: 2 }, }; describe('SpaceshipLaunchButtonComponent', () => { let fixture: ComponentFixture<SpaceshipLaunchButtonComponent>;
let serviceSpy: jasmine.SpyObj<SpaceshipService>;
beforeEach(() => { serviceSpy = jasmine.createSpyObj(SpaceshipService.name, ['launch']); TestBed.configureTestingModule({ declarations: [SpaceshipLaunchButtonComponent], imports: [MatIconModule, MatIconTestingModule],
providers: [{ provide: SpaceshipService, useValue: serviceSpy }],
});
TestBed.compileComponents(); fixture = TestBed.createComponent(SpaceshipLaunchButtonComponent); fixture.detectChanges(); }); it('launches the spaceship when clicked', () => { const button = fixture.debugElement.query(By.css('button')); button.triggerEventHandler('click', MouseClickEvent.Left);
expect(serviceSpy.launch).toHaveBeenCalledTimes(1);
}); });

Ici, nous avons configuré notre module de test en important à la fois MatIconModule et MatIconTestingModule. Le premier module permet d'afficher des icônes dans le composant testé, tandis que le second remplace le MatIconRegistry par un FakeMatIconRegistry, ce qui rend possible l'affichage d'icônes personnalisées même lorsqu'elles ne sont pas présentes dans les ressources du test.

Il est important de noter que, même si FakeMatIconRegistry ne fournit pas de véritable contenu SVG pour les icônes, il permet néanmoins d'effectuer des tests sur la logique du composant sans se soucier de la gestion des assets graphiques. Ce comportement est crucial pour la réalisation de tests unitaires et d'intégration dans un environnement où des icônes SVG externes sont utilisées.

Le test décrit dans cet exemple vérifie que, lorsque l'utilisateur clique sur le bouton, la méthode launch() du service SpaceshipService est bien appelée. Ce type de test est essentiel pour garantir que l'interaction utilisateur déclenche correctement l'action attendue.

Les implications pratiques pour le développement Angular

L'introduction de FakeMatIconRegistry dans Angular Ivy représente un pas en avant significatif pour la simplification des tests de composants utilisant des icônes SVG personnalisées. En remplaçant le MatIconRegistry classique, ce mécanisme permet aux développeurs de tester des composants sans avoir besoin de gérer les fichiers SVG eux-mêmes, ce qui allège la configuration des tests.

D'un autre côté, l'utilisation de MatIconTestingModule pour simuler le rendu des icônes offre une plus grande flexibilité et prévisibilité dans les tests d'interface utilisateur. Cela permet aux équipes de se concentrer sur la logique métier des composants sans s'encombrer des problèmes liés au rendu des assets externes, ce qui accélère le processus de développement.

Il est également essentiel de comprendre que l'approche de testing avec Ivy ne se limite pas à la simple simulation d'icônes. En effet, avec les améliorations apportées par Ivy, il est possible d'adopter une approche de test plus robuste, qui inclut une gestion plus stricte des types et des dépendances. Cette évolution contribue à rendre le processus de test à la fois plus fiable et plus rapide, ce qui est crucial pour les applications Angular complexes.

Enfin, il est nécessaire de garder à l'esprit que l'intégration des nouvelles fonctionnalités de TypeScript, telles que la vérification stricte des types et la prise en charge des propriétés nullables et des chaînes optionnelles, doit être prise en compte lors de la refactorisation ou de l'ajout de nouvelles fonctionnalités dans une application Angular. Ces outils et fonctionnalités sont des atouts précieux pour garantir la qualité du code et éviter les erreurs lors du développement.