Den Book-Monkey v4 updaten (3. Ausgabe)
30.06.2022
Das Angular-Ökosystem wird kontinuierlich verbessert. Das Release einer neuen Major-Version von Angular bedeutet keineswegs, dass alle Ideen verworfen werden und Ihre Software nach einem Update nicht mehr funktioniert. Die Grundideen von Angular sind seit Version 2 konsistent und auf Beständigkeit über einen langen Zeitraum ausgelegt. Die in unserem Buch beschriebenen Konzepte behalten ihre Gültigkeit.
Ein paar Änderungen haben sich jedoch seit der Veröffentlichung der 3. Ausgabe unseres Buchs ergeben. Diese wollen wir hier detailiert besprechen. Es geht vor allem daraum, dass seit Angular 12 diverse strikte Einstellungen für neue Projekte standardmäßig aktiviert sind. Als wir das Buch im Oktober 2020 veröffentlicht haben, war das noch nicht so. Sind die strikten Einstellungen aktiv, brechen nun leider einige gedruckte Beispiele, die sich aber mit moderatem Aufwand beheben lassen.
Der BookMonkey
Der "BookMonkey" ist das Demo-Projekt zum Buch. Anhand des Beispielprojekts führen wir Sie schrittweise an die Entwicklung mit Angular heran.
Alle Entwicklungsschritte im Buch stellen wir in separaten Repositorys zur Verfügung. Wenn man den Anleitungen im Buch folgt, sieht die eigene Codebasis im Idealfall genauso aus, wie unser Stand auf GitHub.
Einen bestehenden BookMonkey updaten
Wenn Sie unser Buch gleich nach der Veröffentlichung gekauft haben und alle Beispiele daraufhin nach Anleitung umgesetzt haben, dann haben Sie keinen großen Aufwand. Zum Zeitpunkt der Veröffentlichung war Angular 10 die neueste Version, kurz danach folgte Angular 11. Wurde Ihr BookMonkey in dieser Zeit erstellt, dann sind in ihrem Projekt noch keinen strikten Einstellungen aktiv.
Sie können die verwendete Angular-Version in der Datei package.json
ablesen.
Der Befehl ng version
liefert ebenfalls ausführliche Infos zur Angular-Version im jeweiligen Projekt.
Um auf den neuesten Stand von Angular zu gelangen, benutzen Sie bitte den Befehl ng update
in der Kommandozeile und folgen Sie den Anweisungen auf dem Bildschirm.
Lesen Sie dazu auch gerne unsere Blogposts mit den neuesten Änderungen zu Angular:
Einen neuen BookMonkey erstellen
Wenn Sie nach Juni 2021 wie im Buch beschrieben den BookMonkey mit ng new
erzeugen, so wird das Projekt standardmäßig mit strikten Einstellungen erstellt.
Dieser "Strict Mode" bewirkt eine Reihe an neuen Einstellungen, welche auf der offiziellen Website von Angular näher beschrieben sind.
Zum einen sind die Einstellungen von TypeScript restriktiver gesetzt.
Zum anderen kommt eine Reihe von Prüfungen im Angular-Compiler hinzu.
Diese sind in der Doku zu den Angular Compiler Options näher beschrieben.
Walkthrough: Den BookMonkey "refactoren"
Wir haben unseren "alten" BookMonkey (Stand Angular 10) mithilfe von ng update
aktualisiert und anschließend die strikten Einstellungen manuell aktiviert:
tsconfig.json
{
"compilerOptions": {
"strict": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true
},
"angularCompilerOptions": {
"strictInjectionParameters": true,
"strictInputAccessModifiers": true,
"strictTemplates": true
}
}
Sobald diese Einstellungen gewählt wurden, kompiliert das Projekt nicht mehr! Die gleiche Situation ergibt sich, wenn Sie mit einem strikten Projekt beginnen und die Beispiele aus dem Angular-Buch direkt übernehmen. In beiden Fällen müssen wir Alternativen für den gedruckten Code finden. Im Folgenden wollen wir nun die problematischen Codestellen kommentieren und mögliche Lösungen aufzeigen. Die Reihenfolge dieses Walkthroughs entspricht unseren Iterationen im Buch. Wenn Sie also den BookMonkey zum ersten Mal implementieren, dann halten Sie am Besten diese Anleitung gleich bereit.
Im Quellcode auf GitHub haben wir die betroffenen Stellen ebenfalls kurz kommentiert.
Kapitel 6.1: Strikte Initialisierung von Propertys
Gleich in der ersten Iteration zum Thema Komponenten (Kapitel 6.1) bei der BookListComponent
(src/app/book-list/book-list.component.ts
) erhalten wir einen der häufigsten Fehler:
Property 'books' has no initializer and is not definitely assigned in the constructor.
// VORHER: book-list.component.ts
export class BookListComponent implements OnInit {
books: Book[];
ngOnInit(): void {
this.books = [/* ... */]
}
}
Hier prüft der Type-Checker, dass jede in einer Klasse deklarierte Eigenschaft entweder...
1. einen Typ hat, der undefined
enthält oder
2. explizit initialisiert wird bzw. im Konstruktor zugewiesen wird.
Die Lifecycle-Methode ngOnInit()
wird hingegen erst nach Konstruktor ausgeführt.
Aus Sicht des TypeScript-Compilers ist ngOnInit()
also eine normale Methode der Klasse.
Die Zuweisung eines Propertys ist hier nur möglich, wenn es bereits definiert wurde.
Eine mögliche Lösung besteht darin, der Eigenschaft einen Typ zu geben, der undefined
enthält.
Denselben Effekt erhalten wir, wenn wir das Property mit einem Fragezeichen (?
) auf optional setzen.
// 1. mögliche Lösung
export class BookListComponent {
books: Book[] | undefined;
// ODER
books?: Book[];
}
Allerdings würde dies weitere Änderungen im Template und im Code zur Folge haben, da wir nun den Typ undefined
berücksichtigen müssen, sobald wir das Property verwenden.
Wir könnten ebenso das Array mit allen Werten sofort im Konstruktor initialisieren.
Dadurch müssen wir den bisherigen Typ (Array aus Book
) nicht ändern.
Auf die Methode ngOnInit()
können wir dann ganz verzichten:
// 2. mögliche Lösung
export class BookListComponent {
books: Book[];
constructor() {
this.books = [/* ... */]
}
}
Für unser Refactoring haben wir uns für die letzte Variante entschieden.
Wir haben das Property explizit mit einem leeren Array initialisiert.
Dadurch müssen wir den bestehenden Code kaum anpassen.
Die konkreten Werte werden weiterhin in der Methode ngOnInit()
zugewiesen:
// NACHHER: book-list.component.ts
// 3. mögliche Lösung
export class BookListComponent implements OnInit {
books: Book[] = [];
ngOnInit(): void {
this.books = [/* ... */]
}
}
Beim Start der Komponente ist books
also sofort mit einem leeren Array belegt.
Sobald ngOnInit()
ausgeführt wird (das geschieht etwas später), wird dieses leere Array überschrieben.
Kapitel 6.2: Properties mit @Input()
-Decorator
In der ersten Iteration erläutern wir im Kapitel 6.2 die Verwendung von Property Bindings, um Werte an eine Kindkomponente zu übergeben.
Dazu dekorieren wir das entsprechende Property in der Kindkomponente mit einem @Input()
-Decorator:
// VORHER: book-list-item.component.ts
export class BookListItemComponent implements OnInit {
@Input() book: Book;
ngOnInit(): void {
}
}
Erneut erhalten wir hier den Fehler, dass das Property nicht korrekt initialisiert wurde. Hier wäre es sehr aufwendig und unschön, das Property mit einem Dummy-Ersatzbuch zu initalisieren.
Die BookListItemComponent
wird zusammen mit *ngFor
verwendet.
Hier wird immer ein Buch über das Property Binding zur Verfügung gestellt:
<bm-book-list-item
*ngFor="let b of books"
[book]="b"></bm-book-list-item>
Das Input-Property wird aber erst zur Laufzeit von Angular durch das Property Binding zugewiesen. Diesen Umstand berücksichtigt die strikte Prüfung von TypeScript nicht. Laut TypeScript muss bereits zum Zeitpunkt der Initialisierung der Klasse ein Wert bereitstehen.
Da der Wert des Propertys aber erst zu einem späteren Zeitpunkt gesetzt wird, sollten wir dies auch folgerichtig im Code ausdrücken:
export class BookListItemComponent implements OnInit {
@Input() book: Book | undefined;
ngOnInit(): void {
}
}
Statt dieser Schreibweise können wir auch einen äquivalenten Shortcut verwenden und das Property als optional markieren:
// NACHER: book-list-item.component.ts
export class BookListItemComponent implements OnInit {
@Input() book?: Book;
ngOnInit(): void {
}
}
Wenn Sie möchten, können Sie auch gerne die nicht verwendete Methode ngOnInit()
entfernen:
// NACHER: book-list-item.component.ts
export class BookListItemComponent {
@Input() book?: Book;
}
Man kann Property Bindings in Angular leider nicht verpflichtend machen.
Daher empfehlen wir bei komplexen Input-Propertys grundsätzlich, den Wert undefined
zu berücksichtigen.
Da das Buch nun also undefined
sein kann, greift eine weitere Typprüfung:
optional (property) Book.thumbnails?: Thumbnail[] | undefined
Object is possibly 'undefined'. book-list-item.component.ts: Error occurs in the template of component BookListItemComponent.
<!-- VORHER: book-list-item.component.html -->
<img class="ui tiny image"
*ngIf="book.thumbnails && book.thumbnails[0] && book.thumbnails[0].url"
[src]="book.thumbnails[0].url">
<div class="content">
<div class="header">{{ book.title }}</div>
<div *ngIf="book.subtitle" class="description">{{ book.subtitle }}</div>
<div class="metadata">
<span *ngFor="let author of book.authors; last as l">
{{ author }}<span *ngIf="!l">, </span>
</span>
<br>
ISBN {{ book.isbn }}
</div>
</div>
Die Prüfung bemängelt zu Recht, dass das Property book
den Wert undefined
haben kann und dann auch der Zugriff auf book.thumbnails
oder book.isbn
fehlschlagen könnte.
Würden wir dies dennoch tun, dann käme es zur Laufzeit zu folgender Fehlermeldung: TypeError: Cannot read property of undefined
.
Dies ist einer der häufigsten Typfehler in JavaScript.
Er tritt immer dann auf, wenn auf einer undefinierten Variable eine Eigenschaft gelesen oder eine Funktion aufgerufen wird.
Es ist gut, dass uns die strenge Typprüfung schon zur Kompilierzeit vor diesem Problem bewahrt.
Wir haben das Markup wie folgt verbessert:
Das gesamte Template wird mit mit einem <ng-container>
und *ngIf
nur dann eingeblendet, wenn ein Buch vorhanden ist:
<!-- NACHER: book-list-item.component.html -->
<ng-container *ngIf="book">
<!-- Vorheriger Code -->
<img class="ui tiny image" ...>
<div class="content" ...></div>
</ng-container>
Der <ng-container>
ist ein Hilfselement, das nicht als DOM-Element gerendert wird.
Er sorgt nur für eine logische Gruppierung.
Innerhalb des Containers ist book
durch die Verwendung von *ngIf
immer definiert.
Kapitel 6.3: Weitere Property-Prüfungen
Im Kapitel zu den Event Bindings übergeben wir ein Buch von der Kindkomponente BookListComponent
zur Elternkomponente AppComponent
und zeigen damit eine Detailansicht an.
Das Buch speichern wir im Property book
.
Dieses Property sollten wir ebenso mit dem Fragezeichen als optional kennzeichnen, denn es ist nicht immer mit einem Wert belegt:
// VORHER: app.component.ts
export class AppComponent {
book: Book;
}
// NACHHER: app.component.ts
export class AppComponent {
book?: Book;
}
Das Template der AppComponent
muss in diesem Fall nicht angepasst werden.
Die Anzeige des Buchs geschieht in der BookDetailsComponent
.
Erneut müssen wir den Code aufgrund der strikten Prüfungen anpassen:
// VORHER: book-details.component.ts
export class BookDetailsComponent implements OnInit {
@Input() book: Book;
}
// NACHHER: book-details.component.ts
export class BookDetailsComponent implements OnInit {
@Input() book?: Book;
}
Die BookDetailsComponent
hat eine Methode getRating()
, welche nur eine Zahl akzeptiert.
Diese Methode wird im Template verwendet:
<!-- VORHER: book-details.component.html -->
<div class="four wide column">
<h4>Rating</h4>
<i class="yellow star icon"
*ngFor="let r of getRating(book.rating)"></i>
</div>
Damit der Code wieder kompiliert, müssen wir sicherstellen, dass es keinen Fall geben kann, bei dem das Rating undefined
ist.
Nun kann sowohl das Buch an sich undefined
sein, als auch dessen Property rating
.
Dies ergibt sich auch dem Interface Book
, wo das Rating als optional markiert ist:
export interface Book {
// [...]
rating?: number;
}
Auch hier haben wir wieder das umschließende <div>
für eine Prüfung verwendet.
Das Div-Element und sein Inhalt werden nur angezeigt, wenn book.rating
definiert ist:
<!-- NACHHER: book-details.component.html -->
<div class="four wide column" *ngIf="book.rating">
<h4>Rating</h4>
<i class="yellow star icon"
*ngFor="let r of getRating(book.rating)"></i>
</div>
Kapitel 8.2: Werte vom Router
Im Kapitel 8.2 stellen wir die Anwendung auf Routing um und ändern in diesem Zuge die BookDetailsComponent
.
Statt eines Input-Propertys verwenden wir nun die ISBN, die wir aus der aktuellen Route ermitteln.
Diese ISBN verwenden wir, um das richtige Buch vom BookStoreService
zu erhalten.
Im gedruckten Buch finden Sie den folgenden Code:
// VORHER: book-details.component.ts
export class BookDetailsComponent implements OnInit {
book: Book;
constructor(
private bs: BookStoreService,
private route: ActivatedRoute
) { }
ngOnInit(): void {
const params = this.route.snapshot.paramMap;
this.book = this.bs.getSingle(params.get('isbn'));
}
}
Das Property book
müssen wir mittels des Fragezeichens wieder als optional markieren.
Eine neue Herausforderung bietet dann allerdings folgende Fehlermeldung:
const params: ParamMap
Argument of type 'string | null' is not assignable to parameter of type 'string'.
Type 'null' is not assignable to type 'string'.
Die Methode ParamMap.get()
liefert entweder einen String zurück (wenn der Parameter verfügbar ist) oder null
(wenn der Parameter nicht in der Map vorhanden ist).
Erst zur Laufzeit der Anwendung kann sicher ermittelt werden, ob ein bestimmter Routen-Parameter verfügbar ist.
Um diesem Umstand gerecht zu werden, liefert get()
einen Union-Type von string | null
zurück.
Die Methode getSingle()
erwartet allerdings nur string
.
Vor den strikten Prüfungen von TypeScript war der gedruckte Code mit der Diskrepanz zwischen den beiden Typen valide, jetzt ist dies nicht mehr der Fall.
Wir können deshalb einen leeren String als Fallback-Wert definieren. Auf diese Weise wird immer ein String übergeben:
// NACHHER: book-details.component.ts
export class BookDetailsComponent implements OnInit {
book?: Book;
constructor(
private bs: BookStoreService,
private route: ActivatedRoute
) { }
ngOnInit(): void {
const params = this.route.snapshot.paramMap;
// verwendet den String ODER den leeren String falls der Ausdruck falsy ist
this.book = this.bs.getSingle(params.get('isbn') || '');
}
}
Das Template der BookDetailsComponent
müssen wir in diesem Fall nicht anpassen.
Bereits in der gedruckten Fassung haben wir den gesamten Block mit einem <div *ngIf="book">
geschützt.
Es wird natürlich nie geschehen, dass wir die Route ohne eine ISBN erreichen. Hätten wir das Routing anders konfiguriert (sodass keine ISBN notwendig ist), dann würden wir in diesem Fall einen leeren String an die Methode übergeben.
Eine weitere Möglichkeit besteht darin, die Typprüfung mit dem "Non-Null Assertion Operator" anzupassen.
Mit einem Ausrufezeichen (!
) teilen wir dem Compiler mit, dass der Wert niemals null
sein wird.
// alternative Möglichkeit
this.book = this.bs.getSingle(params.get('isbn')!);
Wir müssen uns dann aber auch wirklich sicher sein, dass dieser Fall wirklich niemals auftreten wird. Verwenden Sie die Non-Null Assertion daher bitte mit Vorsicht!
Auch der BookStoreService
benötigt eine kleine Korrektur.
Zuvor hatten wir den Rückgabewert für die Methode getSingle()
als Book
angegeben:
// NACHHER: book-store.service.ts
export class BookStoreService {
getSingle(isbn: string): Book {
return this.books.find(book => book.isbn === isbn);
}
}
Das war nicht ganz korrekt, denn wenn es bei der Suche mit find()
keinen Treffer gibt, dann ist der Rückgabewert undefined
.
Diese Nachlässigkeit unsererseits führt jetzt zu einem Fehler, daher lautet die korrekte Signatur wie folgt:
// NACHHER: book-store.service.ts
export class BookStoreService {
getSingle(isbn: string): Book | undefined {
return this.books.find(book => book.isbn === isbn);
}
}
Kapitel 10.1: Eine weitere Property-Prüfung
Im Kapitel zum Thema HTTP tauschen wir vor allem die Datenquelle vom BookStoreService
aus.
Erfreulicherweise behalten alle gezeigten Codebeispiele in diesem Kapitel ihre Gültigkeit – bis auf eine kleine Ausnahme.
Die BookDetailsComponent
hat nun eine Methode removeBook()
, welche in der gedruckten Fassung wie folgt aussieht:
// VORHER: book-details.component.ts
export class BookDetailsComponent implements OnInit {
book: Book;
removeBook() {
if (confirm('Buch wirklich löschen?')) {
this.bs.remove(this.book.isbn)
.subscribe(res => this.router.navigate(['../'], { relativeTo: this.route }));
}
}
}
Allerdings mussten wir bereits zuvor das Property book
mit einem Fragezeichen als optional kennzeichnen.
Nun besteht die Gefahr, dass beim Zugriff auf die ISBN per this.book.isbn
der Wert für das Buch undefined
ist.
Diesen Fall müssen wir ausschließen, damit TypeScript keine Beanstandungen mehr hat.
Wir haben uns dazu entschieden, gleich in der Fallunterscheidung zu prüfen, ob this.book
einen truthy Wert hat:
// NACHHER: book-details.component.ts
export class BookDetailsComponent implements OnInit {
book?: Book;
removeBook() {
if (this.book && confirm('Buch wirklich löschen?')) {
this.bs.remove(this.book.isbn)
.subscribe(res => this.router.navigate(['../'], { relativeTo: this.route }));
}
}
}
Kapitel 10.2: Typprüfung bei Events
Im Kapitel 10.2 gehen wir auf die Bibliothek RxJS genauer ein und erstellen die SearchComponent
.
Für die Suche haben wir folgendes Markup verwendet:
<!-- VORHER: search.component.html -->
<input type="text" class="prompt"
(keyup)="keyUp$.next($event.target.value)">
Bei jedem Tastendruck wird zunächst der Wert des Events ausgewertet und an das Subject mit der Methode next()
übergeben.
Leider ist aber das Property target
aber vom Typ EventTarget | null
.
Der Zugriff auf value
könnte demnach fehlschlagen.
TypeScript bemängelt dies entsprechend:
Object is possibly 'null'.
Property 'value' does not exist on type 'EventTarget'.
Um das Problem zu umgehen, greifen wir daher nun mithilfe der Elementreferenz #input
auf den Formularwert zu.
<!-- NACHHER: search.component.html -->
<input type="text" class="prompt" #input
(keyup)="keyUp$.next(input.value)">
Die Referenzvariable input
ist vom Typ HTMLInputElement
, und da diese immer vorhanden ist, können wir nun ohne Einschränkungen auf value
zugreifen.
Kapitel 12.2: Template-Driven Forms
Im Kapitel zu den Template-Driven Forms zeigen wir, wie man ein Formular zum Erstellen von Büchern realisiert.
Hierzu führen wir die Komponente CreateBookComponent
und deren Kindkomponente BookFormComponent
ein.
Zur Anzeige von Fehlermeldungen verwenden wir die FormMessagesComponent
.
Zunächst möchten wir uns für einen Fehler im gedruckten Buch entschuldigen:
Wir zeigen im Template der BookFormComponent
, wie man über Referenzvariablen auf Formular-Controls zugreifen kann.
Diese Stelle ist aber schon seit jeher fehlerhaft gewesen:
<!-- VORHER (fehlerhaft!): book-form.component.html -->
<input
name="title"
[(ngModel)]="book.title"
required
#titleInput="ngModel">
<bm-form-messages
[control]="titleInput"
controlName="title">
</bm-form-messages>
Die Referenz titleInput
zeigt auf die Direktive ngModel
– nicht auf ein Control!
Den benötigten Zugriff auf das Control erhalten wir stattdessen über das Property control
auf ngModel
.
<!-- NACHHER: book-form.component.html -->
<input
name="title"
[(ngModel)]="book.title"
required
#titleInput="ngModel">
<bm-form-messages
[control]="titleInput.control"
controlName="title">
</bm-form-messages>
Diese Änderung gilt auch für alle anderen Stellen in diesem Template.
Das bedeutet, diese Korrektur muss auch für isbnInput
, dateInput
sowie authorInput
durchgeführt werden.
Ein paar Zeilen später verwenden wir im Template der FormMessagesComponent
einen recht komplexen Ausdruck für das Two-Way Binding:
<!-- VORHER: book-form.component.html -->
<input
name="url"
[(ngModel)]="book.thumbnails[0].url"
placeholder="URL">
Laut dem Interface Book
ist das Property thumbnails
optional.
Das führt durch die strengeren Prüfungen natürlich nun zu einer Fehlermeldung:
optional (property) Book.thumbnails?: Thumbnail[] | undefined
Object is possibly 'undefined'.
Unter den heutigen Umständen hätten wir wohl einfach das Property nicht als optional deklariert.
Da wir aber diese zentrale Stelle im Zuge des Refactorings nicht abändern wollen,
haben wir uns an dieser Stelle für den "letzten Ausweg" entschieden.
Mit $any()
haben wir hier die Typprüfung deaktiviert!
Das ist ausdrücklich ein Workaround!
<!-- NACHHER: book-form.component.html -->
<input
name="url"
[(ngModel)]="$any(book).thumbnails[0].url"
placeholder="URL">
Auch der TypeScript-Teil der BookFormComponent
benötigt eine Anpassung.
Um das Formular resetten zu können, benötigen wir eine Referenz auf die Instanz von NgForm
.
Diese erhalten wir über den den Decorator @ViewChild()
:
// VORHER: book-form.component.ts
@ViewChild('bookForm', { static: true }) bookForm: NgForm;
Da das Property nicht sofort zugewiesen werden kann, müssen wir dieses ebenfalls mit dem Fragezeichen (?
) auf optional setzen:
// NACHHER: book-form.component.ts
@ViewChild('bookForm', { static: true }) bookForm?: NgForm;
Uns ist in diesem Zuge aufgefallen, dass der Name bookForm
mit der Elementreferenz #bookForm
im Template kollidiert.
Wir haben daher das Property daher auch gleich noch sauber zu form
umbenannt:
// NACHHER, mit Umbennenung: book-form.component.ts
@ViewChild('bookForm', { static: true }) form?: NgForm;
Folgerichtig müssen wir nun beim Resetten zuvor eine Existenzprüfung durchführen:
// VORHER: book-form.component.ts
submitForm() {
// [...]
this.bookForm.reset();
}
// NACHHER: book-form.component.ts
submitForm() {
// [...]
this.form?.reset();
}
Weitere Property-Prüfungen müssen wir dann noch in der FormMessagesComponent
berücksichtigen:
// VORHER: form-messages.component.ts
@Input() control: AbstractControl;
@Input() controlName: string;
Auch hier markieren wir die Propertys als optional, sonst müssten sie direkt zugewiesen werden.
Der Typ von control
muss auf AbstractControl | null
korrigiert werden, denn das ist der tatsächliche Rückgabetyp von FormGroup.get()
.
// NACHHER: form-messages.component.ts
@Input() control?: AbstractControl | null;
@Input() controlName?: string;
Das Property mit den fest eingebauten Fehlermeldungen des Formulars war bislang nur implizit typisiert:
// VORHER: form-messages.component.ts
private allMessages = {
title: {
required: 'Ein Buchtitel muss angegeben werden.'
},
// [...]
}
Damit wir das Objekt weiterhin in der Methode errorsForControl()
verwenden können, müssen wir den Typ konkretisieren:
// NACHHER: form-messages.component.ts
private allMessages: { [key: string]: { [key: string]: string } } = {
title: {
required: 'Ein Buchtitel muss angegeben werden.'
},
// [...]
}
Die verbesserte und korrekte Typisierung dieser Methode sieht dann wie folgt aus:
// VORHER: form-messages.component.ts
errorsForControl(): string[] {
const messages = this.allMessages[this.controlName];
// [...]
}
// NACHHER: form-messages.component.ts
errorsForControl(): string[] {
type allMessagesKey = keyof FormMessagesComponent['allMessages'];
const messages = this.allMessages[this.controlName as keyof allMessagesKey];
// [...]
}
Es geht weiter …
Wir ergänzen diesen Blogartikel von Zeit zu Zeit. Alle notwendigen Änderungen haben wir auf GitHub direkt im Code kommentiert.
Wenn Sie Fehler finden oder diesen Blogpost ergänzen möchten, freuen wir uns über eine E-Mail oder einen Pull Request auf GitHub!
Alle Änderungen
Hier sehen Sie noch einmal alle notwendigen Änderungen am Code als Differenzanzeige.
- Iteration 1: Komponenten (Kapitel 6.1)
- Iteration 1: Property-Bindings (Kapitel 6.2)
- Iteration 1: Event-Bindings (Kapitel 6.3)
- Iteration 2: Dependency Injection (Kapitel 8.1)
- Iteration 2: Routing (Kapitel 8.2)
- Iteration 3: HTTP (Kapitel 10.1)
- Iteration 3: RxJS (Kapitel 10.2)
- Iteration 3: Interceptoren (Kapitel 10.3)
- Iteration 4: Template-Driven Forms (Kapitel 12.2)
- Iteration 4: Reactive Forms (Kapitel 12.3)
- Iteration 4: Eigene Validatoren (Kapitel 12.4)
- Iteration 5: Pipes (Kapitel 13.1)
- Iteration 5: Direktiven (Kapitel 13.2)
- Iteration 6: Module (Kapitel 14.1)
- Iteration 6: Lazy Loading (Kapitel 14.2)
- Iteration 6: Guards (Kapitel 14.3)
- Iteration 7: Internationalisierung (Kapitel 15.1)
Wir wünschen Ihnen viel Spaß mit Angular! Haben Sie Fragen zur neuen Version, zum Update oder zu Angular? Schreiben Sie uns!
Viel Spaß wünschen
Danny, Ferdinand und Johannes
Titelbild: Photo by Dmitriy Demidov on Unsplash
Suggestions? Feedback? Bugs? Please fork/edit this page on Github.