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.
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.
Si nous ng build --prod
, nous pouvons voir la liste des fichiers générés.
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.
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.
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.
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.
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.
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.
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(); } }
Maintenant, lorsque nous allons dans la console Firebase, nous verrons l'élément nouvellement créé.
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.
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.
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.