Mit Angular können Sie alles tun. Oder fast alles. Aber manchmal führt dieses heimtückische „fast“ dazu, dass der Entwickler Zeit verschwendet, indem er Problemumgehungen erstellt oder versucht zu verstehen, warum etwas passiert oder warum etwas nicht wie erwartet funktioniert.

Der Autor des Artikels, den wir heute übersetzen, sagt, er möchte Tipps teilen, die Angular-Entwicklern helfen, Zeit zu sparen. Er wird über die Fallstricke von Angular sprechen, die er (und nicht nur er) kennenlernen durfte.
Nr. 1. Die von Ihnen angewendete benutzerdefinierte Direktive funktioniert nicht
Sie haben also eine nette Angular-Direktive von Drittanbietern gefunden und beschlossen, sie mit Standardelementen in der Angular-Vorlage zu verwenden. Großartig! Versuchen wir Folgendes:
<span awesomeTooltip="'Tooltip text'"> </span>
Sie starten die Anwendung ... und nichts passiert. Sie schauen wie jeder normale, erfahrene Programmierer in die Chrome Developer Tools-Konsole. Und Sie sehen dort nichts. Die Direktive funktioniert nicht und Angular schweigt.
Dann beschließt ein kluger Kopf Ihres Teams, die Richtlinie in eckige Klammern zu setzen.
<span [awesomeTooltip]="'Tooltip text'"> </span>
Nachdem wir etwas Zeit verloren haben, sehen wir Folgendes in der Konsole.
Hier ist die Sache: Wir haben nur vergessen, das Modul mit der Direktive zu importierenJetzt liegt die Ursache des Problems auf der Hand: Wir haben nur vergessen, das Direktivenmodul in das Angular-Anwendungsmodul zu importieren.
Dies führt zu einer wichtigen Regel: Verwenden Sie niemals Direktiven ohne eckige Klammern.
Hier können Sie mit Direktiven experimentieren.
Nr. 2. ViewChild gibt undefiniert zurück
Angenommen, Sie haben einen Link zu einem Texteingabeelement erstellt, das in einer Winkelvorlage beschrieben ist.
<input type="text" name="fname" #inputTag>
Mit der Funktion RxJS
fromEvent erstellen Sie einen Stream, in den das, was in das Feld eingegeben wird, fällt. Dazu benötigen Sie einen Link zum Eingabefeld, der mit dem Angular
ViewChild
Dekorator abgerufen werden kann:
class SomeComponent implements AfterViewInit { @ViewChild('inputTag') inputTag: ElementRef; ngAfterViewInit(){ const input$ = fromEvent(this.inputTag.nativeElement, 'keyUp') } ... }
Hier erstellen wir mit der Funktion RxJS
fromEvent
einen Stream, in den die in das Feld eingegebenen Daten fallen.
Testen Sie diesen Code.
FehlerWas ist passiert?
Tatsächlich gilt hier die folgende Regel: Wenn
ViewChild
undefined
zurückgibt, suchen
*ngIf
in der Vorlage nach
*ngIf
.
<div *ngIf="someCondition"> <input type="text" name="fname" #inputTag> </div>
Hier ist er der Schuldige des Problems.
Überprüfen Sie außerdem die Vorlage auf andere strukturelle Anweisungen oder die
ng-template
über dem Problemelement.
Überlegen Sie sich mögliche Lösungen für dieses Problem.
▍Variante zur Lösung des Problems №1
Sie können das Vorlagenelement einfach ausblenden, wenn Sie es nicht benötigen. In diesem Fall bleibt das Element immer bestehen und
ViewChild
kann im
ngAfterViewInit
Hook einen Link dazu
ngAfterViewInit
.
<div [hidden]="!someCondition"> <input type="text" name="fname" #inputTag> </div>
▍Variante zur Lösung des Problems №2
Eine andere Möglichkeit, dieses Problem zu lösen, ist die Verwendung von Setzern.
class SomeComponent { @ViewChild('inputTag') set inputTag(input: ElementRef|null) { if(!input) return; this.doSomething(input); } doSomething(input) { const input$ = keysfromEvent(input.nativeElement, 'keyup'); ... } }
Sobald Angular der Eigenschaft
inputTag
einen bestimmten Wert
inputTag
, erstellen wir hier einen Stream aus den im Eingabefeld eingegebenen Daten.
Im Folgenden finden Sie einige nützliche Ressourcen zu diesem Problem:
- Hier können Sie lesen, dass die Ergebnisse von
ViewChild
in Angular 8 statisch und dynamisch sein können. - Wenn Sie Schwierigkeiten haben, mit RxJs zu arbeiten, schauen Sie sich diesen Videokurs an.
Nummer 3. Codeausführung beim Aktualisieren der mit * ngFor generierten Liste (nachdem die Elemente im DOM angezeigt wurden)
Angenommen, Sie haben eine interessante benutzerdefinierte Direktive zum Organisieren von scrollbaren Listen. Sie werden es auf eine Liste anwenden, die mit der
*ngFor
Angular
*ngFor
.
<div *ngFor="let item of itemsList; let i = index;" [customScroll] > <p *ngFor="let item of items" class="list-item">{{item}}</p> </div>
In solchen Fällen müssen Sie beim Aktualisieren der Liste
scrollDirective.update
etwas wie
scrollDirective.update
, um das Bildlaufverhalten unter Berücksichtigung der in der Liste vorgenommenen Änderungen zu konfigurieren.
Es scheint, dass dies mit dem
ngOnChanges
Hook möglich ist:
class SomeComponent implements OnChanges { @Input() itemsList = []; @ViewChild(CustomScrollDirective) scroll: CustomScrollDirective; ngOnChanges(changes) { if (changes.itemsList) { this.scroll.update(); } } ... }
Hier stehen wir zwar vor einem Problem. Der Hook wird aufgerufen, bevor der Browser die aktualisierte Liste anzeigt. Infolgedessen wird die Neuberechnung der Direktivenparameter zum Scrollen der Liste falsch durchgeführt.
Wie kann ich direkt nach Abschluss der Arbeit einen Anruf
*ngFor
?
Sie können dies tun, indem Sie diese 3 einfachen Schritte ausführen:
▍Schritt Nummer 1
*ngFor
Sie die Links zu den Elementen ein, auf die
*ngFor
(
#listItems
)
#listItems
.
<div [customScroll]> <p *ngFor="let item of items" #listItems>{{item}}</p> </div>
▍Schritt Nummer 2
ViewChildren
mit dem Angular
ViewChildren
Dekorator eine Liste dieser Elemente ab. Es gibt eine Entität vom Typ
QueryList
.
▍Schritt Nummer 3
Die
QueryList
Klasse verfügt über eine schreibgeschützte
Änderungseigenschaft , die bei jeder Änderung der Liste Ereignisse
QueryList
.
class SomeComponent implements AfterViewInit { @Input() itemsList = []; @ViewChild(CustomScrollDirective) scroll: CustomScrollDirective; @ViewChildren('listItems') listItems: QueryList<any>; private sub: Subscription; ngAfterViewInit() { this.sub = this.listItems.changes.subscribe(() => this.scroll.update()) } ... }
Jetzt ist das Problem behoben.
Hier können Sie mit dem entsprechenden Beispiel experimentieren.
Nummer 4. Probleme mit ActivatedRoute.queryParam, die auftreten, wenn Abfragen ohne Parameter ausgeführt werden können
Der folgende Code hilft uns, die Essenz dieses Problems zu verstehen.
Einige Fragmente dieses Codes sind Kommentare wie
#x
. Betrachten Sie sie:
- Im Hauptmodul der Anwendung haben wir die Routen definiert und dort das
RouterModule
hinzugefügt. Routen sind so konfiguriert, dass wir den Benutzer zur /home
Seite umleiten, wenn in der URL keine Route angegeben ist. - Als Komponente zum Herunterladen geben wir im Hauptmodul
AppComponent
. AppComponent
verwendet <router-outlet>
um die entsprechenden AppComponent
auszugeben.- Nun das Wichtigste. Wir müssen
queryParams
für die Route von der URL erhalten
Angenommen, wir haben die folgende URL:
https:
In diesem Fall
queryParams
aus:
{accessToken: 'someTokenSequence'}
Schauen wir uns die Arbeit im Browser an.
Testen einer Anwendung, die ein Routing-System implementiertHier haben Sie möglicherweise eine Frage zum Wesen des Problems. Wir haben die Parameter, alles funktioniert wie erwartet ...
Schauen Sie sich den obigen Screenshot des Browsers an und was in der Konsole angezeigt wird. Hier sehen Sie, dass das
queryParams
Objekt zweimal ausgegeben wird. Das erste Objekt ist leer und wird während des Initialisierungsprozesses des Angular-Routers ausgegeben. Erst danach erhalten wir ein Objekt, das die Anforderungsparameter enthält (in unserem Fall -
{accessToken: 'someTokenSequence'}
).
Das Problem ist, dass der Router nichts zurückgibt, wenn die URL keine Anforderungsparameter enthält. Das heißt, nach der Ausgabe des ersten leeren Objekts wird das zweite ebenfalls leere Objekt, das auf das Fehlen von Parametern hinweisen könnte, nicht ausgegeben.
Wenn eine Anforderung ohne Parameter ausgeführt wird, wird das zweite Objekt nicht ausgegebenInfolgedessen stellt sich heraus, dass der Code, wenn er ein zweites Objekt erwartet, von dem er Anforderungsdaten empfangen kann, nicht gestartet wird, wenn die URL keine Anforderungsparameter enthält.
Wie kann man dieses Problem lösen? Hier können uns RxJs helfen. Wir werden zwei beobachtbare Objekte basierend auf
ActivatedRoute.queryParams
erstellen. Überlegen Sie sich wie gewohnt eine schrittweise Lösung des Problems.
▍Schritt Nummer 1
Das erste beobachtbare Objekt,
paramsInUrl$
, gibt Daten zurück, wenn
queryParams
nicht leer ist:
export class AppComponent implements OnInit { constructor(private route: ActivatedRoute, private locationService: Location) { } ngOnInit() {
▍Schritt Nummer 2
Das zweite beobachtbare Objekt,
noParamsInUrl$
, gibt nur dann
noParamsInUrl$
leeren Wert zurück, wenn in der URL keine Anforderungsparameter gefunden wurden:
export class AppComponent implements OnInit { title = 'QueryTest'; constructor(private route: ActivatedRoute, private locationService: Location) { } ngOnInit() { ...
▍Schritt Nummer 3
Kombinieren Sie nun die beobachteten Objekte mit der RxJS-
Zusammenführungsfunktion :
export class AppComponent implements OnInit { title = 'QueryTest'; constructor(private route: ActivatedRoute, private locationService: Location) { } ngOnInit() {
Jetzt gibt das beobachtete
param$
-Objekt nur einmal
param$
Wert zurück - unabhängig davon, ob etwas in
queryParams
(ein Objekt mit Abfrageparametern wird
queryParams
) oder nicht (ein leeres Objekt wird
queryParams
).
Sie können hier mit diesem Code experimentieren.
Nr. 5. Langsame Seiten
Angenommen, Sie haben eine Komponente, die einige formatierte Daten anzeigt:
Diese Komponente löst zwei Probleme:
- Es zeigt ein Array von Elementen an (es wird angenommen, dass diese Operation einmal ausgeführt wird). Darüber hinaus wird formatiert, was durch Aufrufen der
formatItem
Methode angezeigt wird. - Es zeigt die Koordinaten der Maus an (dieser Wert wird offensichtlich sehr oft aktualisiert).
Sie erwarten nicht, dass diese Komponente Leistungsprobleme aufweist. Führen Sie daher nur einen Leistungstest durch, um alle Formalitäten zu erfüllen. Während dieses Tests treten jedoch einige Kuriositäten auf.
Viele formatItem-Aufrufe und ziemlich viel CPU-AuslastungWas ist los? Tatsache ist jedoch, dass Angular beim erneuten Zeichnen der Vorlage alle Funktionen aus der Vorlage aufruft (in unserem Fall die
formatItem
Funktion). Wenn in den Vorlagenfunktionen umfangreiche Berechnungen durchgeführt werden, belastet dies den Prozessor und wirkt sich darauf aus, wie Benutzer die entsprechende Seite wahrnehmen.
Wie kann ich das beheben? Es reicht aus, die in
formatItem
durchgeführten
formatItem
im Voraus
formatItem
und die bereits bereitstehenden Daten auf der Seite anzuzeigen.
Jetzt sieht der Leistungstest viel anständiger aus.
Nur 6 formatItem-Aufrufe und geringe ProzessorlastJetzt funktioniert die Anwendung viel besser. Die hier verwendete Lösung weist jedoch einige Funktionen auf, die nicht immer angenehm sind:
- Da wir die Koordinaten der Maus in der Vorlage
mousemove
, löst das mousemove
immer noch eine Änderungsprüfung aus. Da wir jedoch die Koordinaten der Maus benötigen, können wir dies nicht loswerden. - Wenn der
mousemove
Ereignishandler nur einige Berechnungen durchführen muss (die sich nicht auf die Anzeige auf der Seite auswirken), können Sie Folgendes tun, um die Anwendung zu beschleunigen:
- Sie können
NgZone.runOutsideOfAngular
in der Ereignishandlerfunktion verwenden. Dies verhindert, dass die Überprüfung von Änderungen mousemove
wenn das mousemove
(dies mousemove
sich nur auf diesen Handler aus). - Sie können den Patch zone.js für einige Ereignisse verhindern, indem Sie die folgende Codezeile in polyfills.ts verwenden . Dies wirkt sich auf die gesamte Angular-Anwendung aus.
* (window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove'];
Wenn Sie sich für die Verbesserung der Leistung von Angular-Anwendungen interessieren -
hier ,
hier ,
hier und
hier - nützliche Materialien dazu.
Zusammenfassung
Nachdem Sie diesen Artikel gelesen haben, sollten in Ihrem Angular-Entwickler-Arsenal 5 neue Tools angezeigt werden, mit denen Sie einige häufig auftretende Probleme lösen können. Wir hoffen, dass die Tipps, die Sie hier gefunden haben, Ihnen helfen, Zeit zu sparen.
Liebe Leser! Wissen Sie etwas, das Ihnen hilft, Zeit bei der Entwicklung von Angular-Anwendungen zu sparen?
