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.
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.
Wenn wir ng build --prod
, können wir die Liste der generierten Dateien sehen.
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.
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.
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.
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.
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.
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.
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(); } }
Wenn wir jetzt zur Firebase-Konsole gehen, sehen wir das neu erstellte Element.
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.
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.
Als Google Cloud Partner stehen die Google-zertifizierten Experten von Toptal Unternehmen on demand für ihre wichtigsten Projekte zur Verfügung.