Alle Vorteile, kein Ärger: Ein Angular 9-Tutorial

Veröffentlicht: 2022-03-11

„Jedes Jahr geht das Internet kaputt“, sagt das Sprichwort, und Entwickler müssen normalerweise hingehen und es reparieren. Mit der lang erwarteten Angular-Version 9 könnte man meinen, dass dies zutreffen würde, und Apps, die auf früheren Versionen entwickelt wurden, müssten einen großen Migrationsprozess durchlaufen.

Aber das ist nicht der Fall! Das Angular-Team hat seinen Compiler komplett neu gestaltet, was zu schnelleren Builds, schnelleren Testläufen, kleineren Bundle-Größen und vor allem zu Abwärtskompatibilität mit älteren Versionen führte. Mit Angular 9 erhalten Entwickler im Grunde alle Vorteile ohne jeglichen Aufwand.

In diesem Angular 9-Tutorial erstellen wir eine Angular-Anwendung von Grund auf neu. Wir werden einige der neuesten Funktionen von Angular 9 verwenden und dabei andere Verbesserungen durchgehen.

Angular 9 Tutorial: Beginnen mit einer neuen Angular-Anwendung

Beginnen wir mit unserem Angular-Projektbeispiel. Lassen Sie uns zunächst die neueste Version von Angulars CLI installieren:

 npm install -g @angular/cli

Wir können die Angular-CLI-Version überprüfen, indem wir ng version .

Als Nächstes erstellen wir eine Angular-Anwendung:

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

Wir verwenden zwei Argumente in unserem ng new Befehl:

  • --create-application=false die CLI an, nur Arbeitsbereichsdateien zu generieren. Dies hilft uns, unseren Code besser zu organisieren, wenn wir mehr als eine App und mehrere Bibliotheken benötigen.
  • --strict fügt strengere Regeln hinzu, um mehr TypeScript-Eingabe und Code-Sauberkeit zu erzwingen.

Als Ergebnis haben wir einen grundlegenden Arbeitsbereichsordner und Dateien.

Ein Screenshot einer IDE, der den Ordner ng9-app zeigt, der node_modules, .editorconfig, .gitignore, angle.json, package-lock.json, package.json, README.md, tsconfig.json und tslint.json enthält.

Lassen Sie uns nun eine neue App hinzufügen. Dazu führen wir Folgendes aus:

 ng generate application tv-show-rating

Wir werden aufgefordert:

 ? 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

Wenn wir nun ng serve ausführen, sehen wir, dass die App mit ihrem anfänglichen Gerüst ausgeführt wird.

Ein Screenshot des Gerüsts von Angular 9 mit dem Hinweis „TV-Show-Rating-App läuft!“ Es gibt auch Links zu Ressourcen und nächsten Schritten.

Wenn wir ng build --prod , können wir die Liste der generierten Dateien sehen.

Ein Screenshot der Ausgabe „ng build --prod“ von Angular 9. Es beginnt mit „Erstellen von ES5-Bundles für differenzielles Laden …“. Danach werden mehrere JavaScript-Dateiblöcke aufgelistet – Runtime, Polyfills und Main, jeweils mit einer -es2015- und -es5-Version – und eine CSS-Datei. Die letzte Zeile gibt einen Zeitstempel, einen Hash und eine Laufzeit von 23.881 Millisekunden an.

Wir haben zwei Versionen jeder Datei. Einer ist mit älteren Browsern kompatibel, der andere ist für ES2015 kompiliert, das neuere APIs verwendet und weniger Polyfills für die Ausführung auf Browsern erfordert.

Eine große Verbesserung von Angular 9 ist die Bündelgröße. Laut dem Angular-Team können Sie bei großen Apps einen Rückgang von bis zu 40 % feststellen.

Bei einer neu erstellten App ist die Bundle-Größe ziemlich ähnlich der von Angular 8, aber wenn Ihre App wächst, werden Sie feststellen, dass die Bundle-Größe im Vergleich zu früheren Versionen kleiner wird.

Eine weitere in Angular 9 eingeführte Funktion ist die Möglichkeit, uns zu warnen, wenn eine der CSS-Dateien des Komponentenstils größer als ein definierter Schwellenwert ist.

Ein Screenshot des Abschnitts „Budgets“ einer Angular 9 JSON-Konfigurationsdatei mit zwei Objekten in einem Array. Beim ersten Objekt ist „type“ auf „initial“, „maximumWarning“ auf „2mb“ und „maximumError“ auf „5mb“ gesetzt. Beim zweiten Objekt ist „type“ auf „anyComponentStyle“, „maximumWarning“ auf „6kb“ und „maximumError“ auf „10kb“ gesetzt.

Dies wird uns helfen, fehlerhafte Style-Importe oder riesige Komponenten-Style-Dateien zu erkennen.

Hinzufügen eines Formulars zum Bewerten von Fernsehsendungen

Als Nächstes fügen wir ein Formular zum Bewerten von Fernsehsendungen hinzu. Dazu installieren wir zuerst bootstrap und ng-bootstrap :

 npm install bootstrap @ng-bootstrap/ng-bootstrap

Eine weitere Verbesserung gegenüber Angular 9 ist i18n (Internationalisierung). Zuvor mussten Entwickler für jedes Gebietsschema in einer App einen vollständigen Build ausführen. Mit Angular 9 können wir stattdessen eine App einmal erstellen und alle i18n-Dateien in einem Post-Build-Prozess generieren, wodurch die Build-Zeit erheblich verkürzt wird. Da ng-bootstrap eine Abhängigkeit von i18n hat, fügen wir das neue Paket zu unserem Projekt hinzu:

 ng add @angular/localize

Als Nächstes fügen wir das Bootstrap-Design zur styles.scss unserer App hinzu:

 @import "~bootstrap/scss/bootstrap";

Und wir werden NgbModule und ReactiveFormsModule in unser AppModule auf app.module.ts :

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

Als Nächstes aktualisieren wir app.component.html mit einem Grundraster für unser Formular:

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

Und generieren Sie die Formularkomponente:

 ng gc TvRatingForm

Aktualisieren wir tv-rating-form.component.html und fügen das Formular zum Bewerten von Fernsehsendungen hinzu.

 <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>

Und tv-rating-form.component.ts sieht so aus:

 // ... 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(); } }

Zum Schluss fügen wir das Formular zu app.component.html :

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

An diesem Punkt haben wir einige grundlegende UI-Funktionen. Wenn wir jetzt ng serve erneut ausführen, können wir es in Aktion sehen.

Ein Screencap einer Angular 9-Tutorial-App, die ein Formular mit dem Titel „TV SHOW“ mit einem Dropdown-Menü mit einer Handvoll Sendungstiteln, einem Sternzähler und einer OK-Schaltfläche zeigt. In der Animation wählt der Benutzer eine Show aus, wählt eine Bewertung aus und klickt dann auf die Schaltfläche OK.

Bevor wir fortfahren, werfen wir einen kurzen Blick auf einige interessante neue Angular 9-Funktionen, die hinzugefügt wurden, um das Debuggen zu erleichtern. Da dies eine sehr häufige Aufgabe in unserer täglichen Arbeit ist, lohnt es sich zu wissen, was sich geändert hat, um unser Leben ein wenig einfacher zu machen.

Debuggen mit Angular 9 Ivy

Eine weitere große Verbesserung, die in Angular 9 und Angular Ivy eingeführt wurde, ist das Debugging-Erlebnis. Der Compiler kann jetzt mehr Fehler erkennen und sie „lesbarer“ ausgeben.

Sehen wir es uns in Aktion an. Zuerst aktivieren wir die Vorlagenprüfung in tsconfig.json :

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

Wenn wir nun das Array tvShows aktualisieren und name in title umbenennen:

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

… erhalten wir einen Fehler vom Compiler.

Ein Screenshot der Compiler-Ausgabe von Angular 9/Angular Ivy mit einem Dateinamen und einer Position, die besagt: „Fehler TS2339: Property ‚name‘ does not exist on type ‚{ title: string; }‘.“ Es zeigt auch die betreffende Codezeile und unterstreicht die Referenz, in diesem Fall in der Datei tv-rating-form.component.html, wo tvShow.name erwähnt wird. Danach wird der Verweis auf diese HTML-Datei auf die entsprechende TypeScript-Datei zurückgeführt und ebenfalls hervorgehoben.

Diese Typprüfung ermöglicht es uns, Tippfehler und die falsche Verwendung von TypeScript-Typen zu verhindern.

Angular Ivy-Validierung für @Input()

Eine weitere gute Validierung, die wir erhalten, ist mit @Input() . Zum Beispiel könnten wir dies zu tv-rating-form.component.ts :

 @Input() title: string;

…und binden Sie es in app.component.html :

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

…und dann app.component.ts wie folgt ändern:

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

Wenn wir diese drei Änderungen vornehmen, erhalten wir eine andere Art von Fehler vom Compiler.

Ein Screenshot der Compiler-Ausgabe von Angular 9/Angular Ivy in einem Format ähnlich dem vorherigen, das app.component.html mit „Fehler TS 2322: Typ ‚null‘ ist nicht zuweisbar zu Typ ‚Zeichenfolge‘“ hervorhebt.

Falls wir es umgehen wollen, können wir $any() in der Vorlage verwenden, um den Wert in any umzuwandeln und den Fehler zu beheben:

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

Der richtige Weg, dies zu beheben, wäre jedoch, den title des Formulars nullable zu machen:

 @Input() title: string | null ;

Der ExpressionChangedAfterItHasBeenCheckedError in Angular 9 Ivy

Einer der gefürchtetsten Fehler in der Angular-Entwicklung ist der ExpressionChangedAfterItHasBeenCheckedError . Glücklicherweise gibt Ivy den Fehler klarer aus, sodass es einfacher ist, die Ursache des Problems zu finden.

Lassen Sie uns also einen ExpressionChangedAfterItHasBeenCheckedError -Fehler einführen. Dazu generieren wir zunächst einen Dienst:

 ng gs Title

Als Nächstes fügen wir ein BehaviorSubject und Methoden hinzu, um auf Observable zuzugreifen und einen neuen Wert auszugeben.

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

Danach fügen wir dies zu app.component.html :

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

Und in app.component.ts wir den TitleService :

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

Schließlich fügen wir in tv-rating-form.component.ts TitleService und aktualisieren den Titel der AppComponent , was einen ExpressionChangedAfterItHasBeenCheckedError -Fehler auslöst.

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

Jetzt können wir den detaillierten Fehler in der Entwicklungskonsole des Browsers sehen, und ein Klick auf app.component.html zeigt uns, wo der Fehler liegt.

Eine Bildschirmabdeckung der Entwicklungskonsole des Browsers, die die Meldung von Angular Ivy über den Fehler „ExpressionChangedAfterItHasBeenCheckedError“ zeigt. Ein Stack-Trace in rotem Text gibt den Fehler zusammen mit vorherigen und aktuellen Werten und einem Hinweis an. In der Mitte des Stack-Trace befindet sich die einzige Zeile, die sich nicht auf core.js bezieht. Der Benutzer klickt darauf und gelangt zu der Zeile von app.component.html, die den Fehler verursacht.

Wir können diesen Fehler beheben, indem wir den Dienstaufruf mit setTimeout :

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

Um zu verstehen, warum der Fehler ExpressionChangedAfterItHasBeenCheckedError auftritt, und um andere Möglichkeiten zu untersuchen, ist der Beitrag von Maxim Koretskyi zu diesem Thema lesenswert.

Angular Ivy ermöglicht uns eine klarere Darstellung von Fehlern und hilft, die TypeScript-Eingabe in unserem Code zu erzwingen. Im folgenden Abschnitt behandeln wir einige gängige Szenarien, in denen wir Ivy und Debugging nutzen.

Schreiben eines Tests für unsere Angular 9-App mit Komponentenbäumen

In Angular 9 wurde eine neue Test-API namens Component Harness eingeführt. Die Idee dahinter ist, alle Aufgaben zu beseitigen, die für die Interaktion mit dem DOM erforderlich sind, wodurch es viel einfacher zu handhaben und stabiler zu betreiben ist.

Die Komponenten-Harness-API ist in der @angular/cdk Bibliothek enthalten, also installieren wir sie zuerst in unserem Projekt:

 npm install @angular/cdk

Jetzt können wir einen Test schreiben und Komponentenbäume nutzen. Lassen Sie uns in tv-rating-form.component.spec.ts den Test einrichten:

 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(); })); // ... });

Als Nächstes implementieren wir ein ComponentHarness für unsere Komponente. Wir werden zwei Kabelbäume erstellen: einen für TvRatingForm und einen für NgbRating . ComponentHarness erfordert ein static Feld, hostSelector , das den Wert des Selektors der Komponente annehmen sollte.

 // ... 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'; } // ...

Für unser TvRatingFormHarness erstellen wir einen Selektor für die Senden-Schaltfläche und eine Funktion zum Auslösen eines click . Sie können sehen, wie viel einfacher die Implementierung wird.

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

Als Nächstes fügen wir Methoden zum Festlegen einer Bewertung hinzu. Hier verwenden wir locatorForAll , um nach allen <span> -Elementen zu suchen, die die Sterne darstellen, auf die der Benutzer klicken kann. Die Bewertungsfunktion holt rate einfach alle möglichen Bewertungssterne und klickt auf denjenigen, der dem gesendeten Wert entspricht.

 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(); } }

Das letzte fehlende Stück ist die Verbindung von TvRatingFormHarness mit NgbRatingHarness . Dazu fügen wir einfach den Locator zur Klasse TvRatingFormHarness .

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

Lassen Sie uns nun unseren Test schreiben:

 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}'); }); });

Beachten Sie, dass wir für unsere select innerhalb des Formulars das Festlegen des Werts über einen Kabelbaum nicht implementiert haben. Das liegt daran, dass die API die Auswahl einer Option immer noch nicht unterstützt. Aber das gibt uns die Möglichkeit, hier zu vergleichen, wie die Interaktion mit Elementen vor der Verwendung von Komponentenbäumen aussah.

Eine letzte Sache, bevor wir die Tests durchführen. Wir müssen app.component.spec.ts reparieren, da wir title auf null aktualisiert haben.

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

Wenn wir jetzt ng test ausführen, wird unser Test bestanden.

Ein Screenshot von Karma, der Tests auf unserer Angular 9-App durchführt. Es zeigt „Ran 2 of 6 specs“ mit der Meldung „Incomplete: fit() or fdescribe() was found, 2 specs, 0 failures, randomized with seed 69573.“ Die beiden Tests der TvRatingFormComponent sind hervorgehoben. Die drei Tests von AppComponent und der eine Test von TitleService sind alle grau.

Zurück zu unserer Beispiel-App Angular 9: Daten in einer Datenbank speichern

Lassen Sie uns unser Angular 9-Tutorial abschließen, indem wir eine Verbindung zu Firestore hinzufügen und die Bewertungen in der Datenbank speichern.

Dazu müssen wir ein Firebase-Projekt erstellen. Dann installieren wir die erforderlichen Abhängigkeiten.

 npm install @angular/fire firebase

In den Projekteinstellungen der Firebase-Konsole erhalten wir ihre Konfiguration und fügen sie zu environment.ts und environment.prod.ts hinzu:

 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}' } };

Danach importieren wir die erforderlichen Module in 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, ], // ... })

Als Nächstes fügen wir in tv-rating-form.component.ts den Dienst AngularFirestore ein und speichern eine neue Bewertung bei der Formularübermittlung:

 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(); } } 

Eine Bildschirmabdeckung einer Angular 9-Tutorial-App, die ein Formular mit dem Titel „TV SHOW“ unter einem größeren Seitentitel „neuer Titel!“ zeigt. Auch hier gibt es ein Dropdown-Menü, das eine Handvoll Showtitel auflistet, einen Sternzähler und eine OK-Schaltfläche, und wieder wählt der Benutzer eine Show aus, wählt eine Bewertung aus und klickt dann auf die OK-Schaltfläche.

Wenn wir jetzt zur Firebase-Konsole gehen, sehen wir das neu erstellte Element.

Ein Screenshot der Firebase-Konsole. In der linken Spalte befindet sich joaq-lab mit einigen Sammlungen: Teilnehmer, Rennen, Bewertungen, Tests und Benutzer. Das Bewertungselement ist ausgewählt und wird in der mittleren Spalte mit einer ausgewählten ID angezeigt – es ist das einzige Dokument. Die rechte Spalte zeigt zwei Felder: „rating“ ist auf 4 gesetzt und „tvShow“ ist auf „Mad men“ gesetzt.

Lassen Sie uns abschließend alle Bewertungen in AppComponent . Dazu erhalten wir in app.component.ts die Daten aus der Sammlung:

 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(); } }

… und in app.component.html fügen wir eine Liste mit Bewertungen hinzu:

 <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>

So sieht unsere Angular 9-Tutorial-App aus, wenn alles zusammengefügt ist.

Eine Bildschirmabdeckung einer Angular 9-Tutorial-App, die ein Formular mit dem Titel „TV SHOW“ unter einem größeren Seitentitel „neuer Titel!“ zeigt. Auch hier gibt es ein Dropdown-Menü mit einer Handvoll Showtiteln, einem Sternzähler und einer OK-Schaltfläche. Diesmal steht in einer rechten Spalte bereits „Mad men (4),“ und die User bewerten Lost mit drei Sternen, gefolgt von „Mad men“ wieder mit vier Sternen. Die rechte Spalte bleibt alphabetisch nach den beiden neuen Ratings geordnet.

Angular 9 und Angular Ivy: Bessere Entwicklung, bessere Apps und bessere Kompatibilität

In diesem Angular 9-Tutorial haben wir das Erstellen eines einfachen Formulars, das Speichern von Daten in Firebase und das Abrufen von Elementen daraus behandelt.

Dabei haben wir gesehen, welche Verbesserungen und neuen Features in Angular 9 und Angular Ivy enthalten sind. Eine vollständige Liste finden Sie im neuesten Release-Beitrag des offiziellen Angular-Blogs.


Google Cloud-Partner-Logo.

Als Google Cloud Partner stehen die Google-zertifizierten Experten von Toptal Unternehmen on demand für ihre wichtigsten Projekte zur Verfügung.