Tous les avantages, pas de tracas : un didacticiel Angular 9

Publié: 2022-03-11

"Chaque année, Internet tombe en panne", dit le dicton, et les développeurs doivent généralement aller le réparer. Avec la version 9 tant attendue d'Angular, on pourrait penser que cela s'appliquerait, et les applications développées sur des versions antérieures devraient passer par un processus de migration majeur.

Mais ce n'est pas le cas ! L'équipe Angular a complètement repensé son compilateur, ce qui a entraîné des versions plus rapides, des tests plus rapides, des tailles de bundle plus petites et, surtout, une compatibilité descendante avec les anciennes versions. Avec Angular 9, les développeurs bénéficient essentiellement de tous les avantages sans aucun tracas.

Dans ce didacticiel Angular 9, nous allons créer une application Angular à partir de zéro. Nous utiliserons certaines des dernières fonctionnalités d'Angular 9 et passerons en revue d'autres améliorations en cours de route.

Tutoriel Angular 9 : Commencer avec une nouvelle application Angular

Commençons par notre exemple de projet Angular. Tout d'abord, installons la dernière version de la CLI d'Angular :

 npm install -g @angular/cli

Nous pouvons vérifier la version CLI angulaire en exécutant ng version .

Ensuite, créons une application Angular :

 ng new ng9-app --create-application=false --strict

Nous utilisons deux arguments dans notre ng new :

  • --create-application=false indiquera à la CLI de générer uniquement des fichiers d'espace de travail. Cela nous aidera à mieux organiser notre code lorsque nous avons besoin de plusieurs applications et de plusieurs bibliothèques.
  • --strict ajoutera des règles plus strictes pour appliquer davantage de typage TypeScript et de propreté du code.

En conséquence, nous avons un dossier et des fichiers d'espace de travail de base.

Une capture d'écran d'un IDE montrant le dossier ng9-app, contenant node_modules, .editorconfig, .gitignore, angular.json, package-lock.json, package.json, README.md, tsconfig.json et tslint.json.

Maintenant, ajoutons une nouvelle application. Pour ce faire, nous allons lancer :

 ng generate application tv-show-rating

Nous serons invités :

 ? Would you like to share anonymous usage data about this project with the Angular Team at Google under Google's Privacy Policy at https://policies.google.com/privacy? For more details and how to change this setting, see http://angular.io/analytics. No ? Would you like to add Angular routing? Yes ? Which stylesheet format would you like to use? SCSS

Maintenant, si nous exécutons ng serve , nous verrons l'application fonctionner avec son échafaudage initial.

Une capture d'écran de l'échafaudage d'Angular 9, avec un avis indiquant que "l'application de classement d'émissions de télévision est en cours d'exécution!" Il y a aussi des liens vers des ressources et les prochaines étapes.

Si nous ng build --prod , nous pouvons voir la liste des fichiers générés.

Une capture d'écran de la sortie "ng build --prod" d'Angular 9. Cela commence par "Génération de bundles ES5 pour le chargement différentiel..." Après cela, il répertorie plusieurs morceaux de fichiers JavaScript - runtime, polyfills et main, chacun avec une version -es2015 et -es5 - et un fichier CSS. La dernière ligne donne un horodatage, un hachage et une durée d'exécution de 23 881 millisecondes.

Nous avons deux versions de chaque fichier. L'un est compatible avec les anciens navigateurs, et l'autre est compilé ciblant ES2015, qui utilise des API plus récentes et nécessite moins de polyfills pour s'exécuter sur les navigateurs.

Une grande amélioration d'Angular 9 est la taille du bundle. Selon l'équipe Angular, vous pouvez voir une diminution allant jusqu'à 40% pour les grandes applications.

Pour une application nouvellement créée, la taille du bundle est assez similaire à celle d'Angular 8, mais à mesure que votre application grandit, vous verrez la taille du bundle devenir plus petite par rapport aux versions précédentes.

Une autre fonctionnalité introduite dans Angular 9 est la possibilité de nous avertir si l'un des fichiers CSS de style composant dépasse un seuil défini.

Une capture d'écran de la section "budgets" d'un fichier de configuration Angular 9 JSON, avec deux objets dans un tableau. Le premier objet a "type" défini sur "initial", "maximumWarning" défini sur "2mb" et "maximumError" défini sur "5mb". Le deuxième objet a "type" défini sur "anyComponentStyle", "maximumWarning" défini sur "6kb" et "maximumError" défini sur "10kb".

Cela nous aidera à détecter les importations de styles incorrects ou les fichiers de style de composants volumineux.

Ajouter un formulaire pour évaluer les émissions de télévision

Ensuite, nous ajouterons un formulaire pour évaluer les émissions de télévision. Pour cela, nous allons d'abord installer bootstrap et ng-bootstrap :

 npm install bootstrap @ng-bootstrap/ng-bootstrap

Une autre amélioration sur Angular 9 est i18n (internationalisation). Auparavant, les développeurs devaient exécuter une version complète pour chaque paramètre régional d'une application. Angular 9 nous permet à la place de créer une application une fois et de générer tous les fichiers i18n dans un processus post-construction, ce qui réduit considérablement le temps de construction. Étant donné que ng-bootstrap dépend d'i18n, nous ajouterons le nouveau package à notre projet :

 ng add @angular/localize

Ensuite, nous ajouterons le thème Bootstrap au styles.scss de notre application :

 @import "~bootstrap/scss/bootstrap";

Et nous inclurons NgbModule et ReactiveFormsModule dans notre AppModule sur app.module.ts :

 // ... import { ReactiveFormsModule } from '@angular/forms'; import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; @NgModule({ imports: [ // ... ReactiveFormsModule, NgbModule ], })

Ensuite, nous mettrons à jour app.component.html avec une grille de base pour notre formulaire :

 <div class="container"> <div class="row"> <div class="col-6"> </div> </div> </div>

Et générez le composant de formulaire :

 ng gc TvRatingForm

Mettons à jour tv-rating-form.component.html et ajoutons le formulaire pour évaluer les émissions de télévision.

 <form [formGroup]="form" (ngSubmit)="submit()" class="mt-3"> <div class="form-group"> <label>TV SHOW</label> <select class="custom-select" formControlName="tvShow"> <option *ngFor="let tvShow of tvShows" [value]="tvShow.name">{{tvShow.name}}</option> </select> </div> <div class="form-group"> <ngb-rating [max]="5" formControlName="rating"></ngb-rating> </div> <button [disabled]="form.invalid || form.disabled" class="btn btn-primary">OK</button> </form>

Et tv-rating-form.component.ts ressemblera à ceci :

 // ... export class TvRatingFormComponent implements OnInit { tvShows = [ { name: 'Better call Saul!' }, { name: 'Breaking Bad' }, { name: 'Lost' }, { name: 'Mad men' } ]; form = new FormGroup({ tvShow: new FormControl('', Validators.required), rating: new FormControl('', Validators.required), }); submit() { alert(JSON.stringify(this.form.value)); this.form.reset(); } }

Enfin, ajoutons le formulaire à app.component.html :

 <!-- ... --> <div class="col-6"> <app-tv-rating-form></app-tv-rating-form> </div>

À ce stade, nous avons quelques fonctionnalités de base de l'interface utilisateur. Maintenant, si nous exécutons à nouveau le ng serve , nous pouvons le voir en action.

Une capture d'écran d'une application de didacticiel Angular 9 montrant un formulaire intitulé "TV SHOW", avec une liste déroulante répertoriant une poignée de titres d'émissions, un compteur d'étoiles et un bouton OK. Dans l'animation, l'utilisateur sélectionne une émission, sélectionne une note, puis clique sur le bouton OK.

Avant de poursuivre, jetons un coup d'œil à quelques nouvelles fonctionnalités intéressantes d'Angular 9 qui ont été ajoutées pour faciliter le débogage. Comme il s'agit d'une tâche très courante dans notre travail quotidien, il est utile de savoir ce qui a changé pour nous faciliter un peu la vie.

Débogage avec Angular 9 Ivy

Une autre grande amélioration introduite dans Angular 9 et Angular Ivy est l'expérience de débogage. Le compilateur peut maintenant détecter plus d'erreurs et les jeter d'une manière plus « lisible ».

Voyons-le en action. Tout d'abord, nous allons activer la vérification des modèles dans tsconfig.json :

 { // ... "angularCompilerOptions": { "fullTemplateTypeCheck": true, "strictInjectionParameters": true, "strictTemplates": true } }

Maintenant, si nous mettons à jour le tableau tvShows et renommons name en title :

 tvShows = [ { title: 'Better call Saul!' }, { title: 'Breaking Bad' }, { title: 'Lost' }, { title: 'Mad men' } ];

… nous aurons une erreur du compilateur.

Une capture d'écran de la sortie du compilateur Angular 9/Angular Ivy, avec un nom de fichier et une position, indiquant "erreur TS2339 : la propriété 'nom' n'existe pas sur le type '{ titre : chaîne ; }'." Il montre également la ligne de code en question et souligne la référence, dans ce cas dans le fichier tv-rating-form.component.html où tvShow.name est mentionné. Après cela, la référence à ce fichier HTML est tracée vers le fichier TypeScript correspondant et mise en surbrillance de la même manière.

Cette vérification de type nous permettra d'éviter les fautes de frappe et l'utilisation incorrecte des types TypeScript.

Validation angulaire de lierre pour @Input()

Une autre bonne validation que nous obtenons est avec @Input() . Par exemple, nous pourrions ajouter ceci à tv-rating-form.component.ts :

 @Input() title: string;

…et liez-le dans app.component.html :

 <app-tv-rating-form [title]="title"></app-tv-rating-form>

… puis modifiez app.component.ts comme suit :

 // ... export class AppComponent { title = null; }

Si nous apportons ces trois modifications, nous obtiendrons un autre type d'erreur du compilateur.

Une capture d'écran de la sortie du compilateur Angular 9/Angular Ivy, dans un format similaire au précédent, mettant en évidence app.component.html avec "error TS 2322: Type 'null' is not assignable to type 'string'."

Au cas où nous voudrions le contourner, nous pouvons utiliser $any() sur le modèle pour convertir la valeur en any et corriger l'erreur :

 <app-tv-rating-form [title]="$any(title)"></app-tv-rating-form>

La bonne façon de résoudre ce problème, cependant, serait de rendre le title sur le formulaire nullable :

 @Input() title: string | null ;

L' ExpressionChangedAfterItHasBeenCheckedError dans Angular 9 Ivy

L'une des erreurs les plus redoutées dans le développement angulaire est ExpressionChangedAfterItHasBeenCheckedError . Heureusement, Ivy affiche l'erreur de manière plus claire, ce qui permet de trouver plus facilement d'où vient le problème.

Introduisons donc une erreur ExpressionChangedAfterItHasBeenCheckedError . Pour ce faire, nous allons d'abord générer un service :

 ng gs Title

Ensuite, nous allons ajouter un BehaviorSubject et des méthodes pour accéder à Observable et émettre une nouvelle valeur.

 export class TitleService { private bs = new BehaviorSubject < string > (''); constructor() {} get title$() { return this.bs.asObservable(); } update(title: string) { this.bs.next(title); } }

Après cela, nous ajouterons ceci à app.component.html :

 <!-- ... --> <div class="col-6"> <h2> {{title$ | async}} </h2> <app-tv-rating-form [title]="title"></app-tv-rating-form> </div>

Et dans app.component.ts , nous injecterons le TitleService :

 export class AppComponent implements OnInit { // ... title$: Observable < string > ; constructor( private titleSvc: TitleService ) {} ngOnInit() { this.title$ = this.titleSvc.title$; } // ... }

Enfin, dans tv-rating-form.component.ts , nous injecterons TitleService et mettrons à jour le titre de AppComponent , ce qui générera une erreur ExpressionChangedAfterItHasBeenCheckedError .

 // ... constructor( private titleSvc: TitleService ) { } ngOnInit() { this.titleSvc.update('new title!'); }

Nous pouvons maintenant voir l'erreur détaillée dans la console de développement du navigateur, et cliquer sur app.component.html nous indiquera où se trouve l'erreur.

Une capture d'écran de la console de développement du navigateur, montrant le signalement par Angular Ivy de l'erreur ExpressionChangedAfterItHasBeenCheckedError. Une trace de pile en texte rouge donne l'erreur, ainsi que les valeurs précédentes et actuelles, et un indice. Au milieu de la trace de la pile se trouve la seule ligne ne faisant pas référence à core.js. L'utilisateur clique dessus et est amené à la ligne app.component.html qui cause l'erreur.

Nous pouvons corriger cette erreur en enveloppant l'appel de service avec setTimeout :

 setTimeout(() => { this.titleSvc.update('new title!'); });

Pour comprendre pourquoi l'erreur ExpressionChangedAfterItHasBeenCheckedError se produit et explorer d'autres possibilités, l'article de Maxim Koretskyi sur le sujet vaut la peine d'être lu.

Angular Ivy nous permet d'avoir des erreurs présentées de manière plus claire et aide à appliquer le typage TypeScript dans notre code. Dans la section suivante, nous couvrirons certains scénarios courants dans lesquels nous tirerons parti d'Ivy et du débogage.

Rédaction d'un test pour notre application Angular 9 avec des harnais de composants

Dans Angular 9, une nouvelle API de test a été introduite appelée harnais de composants . L'idée sous-jacente est de supprimer toute la corvée nécessaire pour interagir avec le DOM, ce qui le rend beaucoup plus facile à utiliser et plus stable à exécuter.

L'API de faisceau de composants est incluse dans la bibliothèque @angular/cdk , alors installons-la d'abord sur notre projet :

 npm install @angular/cdk

Nous pouvons maintenant écrire un test et tirer parti des faisceaux de composants. Dans tv-rating-form.component.spec.ts , configurons le test :

 import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; import { ReactiveFormsModule } from '@angular/forms'; describe('TvRatingFormComponent', () => { let component: TvRatingFormComponent; let fixture: ComponentFixture < TvRatingFormComponent > ; beforeEach(async (() => { TestBed.configureTestingModule({ imports: [ NgbModule, ReactiveFormsModule ], declarations: [TvRatingFormComponent] }).compileComponents(); })); // ... });

Ensuite, implémentons un ComponentHarness pour notre composant. Nous allons créer deux harnais : un pour TvRatingForm et un autre pour NgbRating . ComponentHarness nécessite un champ static , hostSelector , qui doit prendre la valeur du sélecteur du composant.

 // ... import { ComponentHarness, HarnessLoader } from '@angular/cdk/testing'; import { TestbedHarnessEnvironment } from '@angular/cdk/testing/testbed'; class TvRatingFormHarness extends ComponentHarness { static hostSelector = 'app-tv-rating-form'; } class NgbRatingHarness extends ComponentHarness { static hostSelector = 'ngb-rating'; } // ...

Pour notre TvRatingFormHarness , nous allons créer un sélecteur pour le bouton submit et une fonction pour déclencher un click . Vous pouvez voir à quel point cela devient plus facile à mettre en œuvre.

 class TvRatingFormHarness extends ComponentHarness { // ... protected getButton = this.locatorFor('button'); async submit() { const button = await this.getButton(); await button.click(); } }

Ensuite, nous ajouterons des méthodes pour définir une note. Ici, nous utilisons locatorForAll pour rechercher tous les éléments <span> qui représentent les étoiles sur lesquelles l'utilisateur peut cliquer. La fonction rate récupère simplement toutes les étoiles des notes possibles et clique sur celle correspondant à la valeur envoyée.

 class NgbRatingHarness extends ComponentHarness { // ... protected getRatings = this.locatorForAll('span:not(.sr-only)'); async rate(value: number) { const ratings = await this.getRatings(); return ratings[value - 1].click(); } }

La dernière pièce manquante est de connecter TvRatingFormHarness à NgbRatingHarness . Pour ce faire, nous ajoutons simplement le localisateur sur la classe TvRatingFormHarness .

 class TvRatingFormHarness extends ComponentHarness { // ... getRating = this.locatorFor(NgbRatingHarness); // ... }

Maintenant, écrivons notre test :

 describe('TvRatingFormComponent', () => { // ... it('should pop an alert on submit', async () => { spyOn(window, 'alert'); const select = fixture.debugElement.query(By.css('select')).nativeElement; select.value = 'Lost'; select.dispatchEvent(new Event('change')); fixture.detectChanges(); const harness = await TestbedHarnessEnvironment.harnessForFixture(fixture, TvRatingFormHarness); const rating = await harness.getRating(); await rating.rate(1); await harness.submit(); expect(window.alert).toHaveBeenCalledWith('{"tvShow":"Lost","rating":1}'); }); });

Notez que pour notre select dans le formulaire, nous n'avons pas implémenté la définition de sa valeur via un harnais. C'est parce que l'API ne prend toujours pas en charge la sélection d'une option. Mais cela nous donne une chance de comparer ici à quoi ressemblait l'interaction avec les éléments avant les harnais de composants.

Une dernière chose avant de lancer les tests. Nous devons corriger app.component.spec.ts puisque nous avons mis à jour title pour qu'il soit null .

 describe('AppComponent', () => { // ... it(`should have as title 'tv-show-rating'`, () => { const fixture = TestBed.createComponent(AppComponent); const app = fixture.componentInstance; expect(app.title).toEqual(null); }); });

Maintenant, lorsque nous exécutons ng test , notre test réussit.

Une capture d'écran de Karma exécutant des tests sur notre application Angular 9. Il affiche "Ran 2 of 6 specs" avec le message "Incomplete: fit() or fdescribe() was found, 2 specs, 0 failures, randomized with seed 69573." Les deux tests de TvRatingFormComponent sont mis en évidence. Les trois tests d'AppComponent et le test unique de TitleService sont tous gris.

Retour à notre exemple d'application Angular 9 : enregistrement de données dans une base de données

Terminons notre didacticiel Angular 9 en ajoutant une connexion à Firestore et en enregistrant les notes dans la base de données.

Pour ce faire, nous devons créer un projet Firebase. Ensuite, nous installerons les dépendances requises.

 npm install @angular/fire firebase

Dans les paramètres du projet de la console Firebase, nous obtiendrons sa configuration et les ajouterons à environment.ts et environment.prod.ts :

 export const environment = { // ... firebase: { apiKey: '{your-api-key}', authDomain: '{your-project-id}.firebaseapp.com', databaseURL: 'https://{your-project-id}.firebaseio.com', projectId: '{your-project-id}', storageBucket: '{your-project-id}.appspot.com', messagingSenderId: '{your-messaging-id}', appId: '{your-app-id}' } };

Après cela, nous importerons les modules nécessaires dans app.module.ts :

 import { AngularFireModule } from '@angular/fire'; import { AngularFirestoreModule } from '@angular/fire/firestore'; import { environment } from '../environments/environment'; @NgModule({ // ... imports: [ // ... AngularFireModule.initializeApp(environment.firebase), AngularFirestoreModule, ], // ... })

Ensuite, dans tv-rating-form.component.ts , nous injecterons le service AngularFirestore et enregistrerons une nouvelle note lors de la soumission du formulaire :

 import { AngularFirestore } from '@angular/fire/firestore'; export class TvRatingFormComponent implements OnInit { constructor( // ... private af: AngularFirestore, ) { } async submit(event: any) { this.form.disable(); await this.af.collection('ratings').add(this.form.value); this.form.enable(); this.form.reset(); } } 

Une capture d'écran d'une application de didacticiel Angular 9 montrant un formulaire intitulé "TV SHOW" sous un titre de page plus grand "nouveau titre!" Encore une fois, il a une liste déroulante répertoriant une poignée de titres d'émissions, un compteur d'étoiles et un bouton OK, et encore une fois l'utilisateur sélectionne une émission, sélectionne une note, puis clique sur le bouton OK.

Maintenant, lorsque nous allons dans la console Firebase, nous verrons l'élément nouvellement créé.

Une capture d'écran de la console Firebase. Dans la colonne de gauche se trouve joaq-lab avec quelques collections : participants, courses, évaluations, tests et utilisateurs. L'élément d'évaluation est sélectionné et apparaît dans la colonne du milieu avec un identifiant sélectionné. Il s'agit du seul document. La colonne de droite affiche deux champs : "rating" est défini sur 4 et "tvShow" est défini sur "Mad men".

Enfin, listons toutes les évaluations dans AppComponent . Pour ce faire, dans app.component.ts , nous allons récupérer les données de la collection :

 import { AngularFirestore } from '@angular/fire/firestore'; export class AppComponent implements OnInit { // ... ratings$: Observable<any>; constructor( // ... private af: AngularFirestore ) { } ngOnInit() { // ... this.ratings$ = this.af.collection('ratings').valueChanges(); } }

…et dans app.component.html , nous ajouterons une liste de notes :

 <div class="container"> <div class="row"> // ... <div class="col-6"> <div> <p *ngFor="let rating of ratings$ | async"> {{rating.tvShow}} ({{rating.rating}}) </p> </div> </div> </div> </div>

Voici à quoi ressemble notre application de didacticiel Angular 9 une fois assemblée.

Une capture d'écran d'une application de didacticiel Angular 9 montrant un formulaire intitulé "TV SHOW" sous un titre de page plus grand "nouveau titre!" Encore une fois, il a une liste déroulante répertoriant une poignée de titres d'émissions, un compteur d'étoiles et un bouton OK. Cette fois, une colonne de droite répertorie déjà "Mad men (4)" et l'utilisateur évalue Lost à trois étoiles, suivi de "Mad men" à nouveau à quatre étoiles. La colonne de droite reste classée par ordre alphabétique après les deux nouvelles notations.

Angular 9 et Angular Ivy : meilleur développement, meilleures applications et meilleure compatibilité

Dans ce didacticiel Angular 9, nous avons couvert la création d'un formulaire de base, l'enregistrement de données dans Firebase et la récupération d'éléments à partir de celui-ci.

En cours de route, nous avons vu quelles améliorations et nouvelles fonctionnalités sont incluses dans Angular 9 et Angular Ivy. Pour une liste complète, vous pouvez consulter le dernier article de publication du blog officiel Angular.


Badge de partenaire Google Cloud.

En tant que partenaire Google Cloud, les experts certifiés Google de Toptal sont à la disposition des entreprises à la demande pour leurs projets les plus importants.