Avec Angular, vous pouvez tout faire. Ou presque tout. Mais parfois, cette insidieuse «presque» conduit au fait que le développeur perd du temps en créant des solutions de contournement, ou en essayant de comprendre pourquoi quelque chose se passe, ou pourquoi quelque chose ne fonctionne pas comme prévu.

L'auteur de l'article que nous traduisons aujourd'hui dit qu'il souhaite partager des conseils qui aideront les développeurs Angular à gagner du temps. Il va parler des pièges d'Angular, qu'il (et pas seulement lui) a eu la chance de rencontrer.
N ° 1. La directive personnalisée que vous avez appliquée ne fonctionne pas
Donc, vous avez trouvé une belle directive angulaire tierce et avez décidé de l'utiliser avec des éléments standard dans le modèle angulaire. Super! Essayons de faire ceci:
<span awesomeTooltip="'Tooltip text'"> </span>
Vous démarrez l'application ... Et rien ne se passe. Vous, comme tout programmeur expérimenté, consultez la console Chrome Developer Tools. Et vous n'y voyez rien. La directive ne fonctionne pas et Angular est silencieux.
Ensuite, une tête brillante de votre équipe décide de placer la directive entre crochets.
<span [awesomeTooltip]="'Tooltip text'"> </span>
Après cela, après avoir perdu un peu de temps, nous voyons ce qui suit dans la console.
Voici la chose: nous avons juste oublié d'importer le module avec la directiveMaintenant, la cause du problème est assez évidente: nous avons juste oublié d'importer le module directive dans le module d'application Angular.
Cela conduit à une règle importante: ne jamais utiliser de directives sans crochets.
Vous pouvez expérimenter avec des directives
ici .
N ° 2. ViewChild renvoie undefined
Supposons que vous ayez créé un lien vers un élément d'entrée de texte décrit dans un modèle angulaire.
<input type="text" name="fname" #inputTag>
Vous allez, en utilisant la fonction RxJS
fromEvent , créer un flux dans lequel tombera ce qui est entré dans le champ. Pour ce faire, vous avez besoin d'un lien vers le champ de saisie, qui peut être obtenu à l'aide du décorateur Angular
ViewChild
:
class SomeComponent implements AfterViewInit { @ViewChild('inputTag') inputTag: ElementRef; ngAfterViewInit(){ const input$ = fromEvent(this.inputTag.nativeElement, 'keyUp') } ... }
Ici, en utilisant la fonction RxJS
fromEvent
, nous créons un flux dans lequel les données entrées dans le champ tomberont.
Testez ce code.
ErreurQu'est-il arrivé?
En fait, la règle suivante s'applique ici: si
ViewChild
retourne
undefined
, recherchez
*ngIf
dans le modèle.
<div *ngIf="someCondition"> <input type="text" name="fname" #inputTag> </div>
Ici, il est le coupable du problème.
De plus, vérifiez le modèle pour d'autres directives structurelles ou
ng-template
au
ng-template
dessus de l'élément problème.
Considérez les solutions possibles à ce problème.
▍Variant de résoudre le problème №1
Vous pouvez simplement masquer l'élément de modèle si vous n'en avez pas besoin. Dans ce cas, l'élément continuera toujours d'exister et
ViewChild
pourra lui renvoyer un lien dans le hook
ngAfterViewInit
.
<div [hidden]="!someCondition"> <input type="text" name="fname" #inputTag> </div>
▍Variant de résoudre le problème №2
Une autre façon de résoudre ce problème consiste à utiliser des setters.
class SomeComponent { @ViewChild('inputTag') set inputTag(input: ElementRef|null) { if(!input) return; this.doSomething(input); } doSomething(input) { const input$ = keysfromEvent(input.nativeElement, 'keyup'); ... } }
Ici, dès que Angular attribue une valeur spécifique à la propriété
inputTag
, nous créons un flux à partir des données entrées dans le champ de saisie.
Voici quelques ressources utiles liées à ce problème:
- Ici, vous pouvez lire que les résultats de
ViewChild
dans Angular 8 peuvent être statiques et dynamiques. - Si vous éprouvez des difficultés à travailler avec les RxJ, consultez ce cours vidéo.
Numéro 3. Exécution de code lors de la mise à jour de la liste générée avec * ngFor (après que les éléments soient apparus dans le DOM)
Supposons que vous ayez une directive personnalisée intéressante pour organiser les listes déroulantes. Vous allez l'appliquer à une liste créée à l'aide de la
*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>
Habituellement, dans de tels cas, lors de la mise à jour de la liste, vous devez appeler quelque chose comme
scrollDirective.update
pour configurer le comportement de défilement, en tenant compte des modifications qui se sont produites dans la liste.
Il peut sembler que cela peut être fait en utilisant le hook
ngOnChanges
:
class SomeComponent implements OnChanges { @Input() itemsList = []; @ViewChild(CustomScrollDirective) scroll: CustomScrollDirective; ngOnChanges(changes) { if (changes.itemsList) { this.scroll.update(); } } ... }
Certes, nous sommes ici face à un problème. Le hook est appelé avant que le navigateur affiche la liste mise à jour. Par conséquent, le recalcul des paramètres de directive pour faire défiler la liste est effectué de manière incorrecte.
Comment passer un appel juste après que
*ngFor
fini de fonctionner?
Vous pouvez le faire en suivant ces 3 étapes simples:
▍Etape numéro 1
Placez les liens vers les éléments où
*ngFor
(
#listItems
) est
#listItems
.
<div [customScroll]> <p *ngFor="let item of items" #listItems>{{item}}</p> </div>
▍Étape numéro 2
Obtenez une liste de ces éléments à l'aide du décorateur Angular
ViewChildren
. Il renvoie une entité de type
QueryList
.
▍Étape numéro 3
La classe
QueryList
possède une propriété de
modifications en lecture seule qui
QueryList
événements chaque fois que la liste change.
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()) } ... }
Maintenant, le problème est résolu.
Ici, vous pouvez expérimenter avec l'exemple correspondant.
Numéro 4. Problèmes avec ActivatedRoute.queryParam qui se produisent lorsque des requêtes peuvent être exécutées sans paramètres
Le code suivant nous aidera à comprendre l'essence de ce problème.
Certains fragments de ce code sont des commentaires comme le
#x
. Considérez-les:
- Dans le module principal de l'application, nous avons défini les routes et y
RouterModule
ajouté le RouterModule
. Les itinéraires sont configurés de sorte que si un itinéraire n'est pas fourni dans l'URL, nous redirige l'utilisateur vers la page /home
. - En tant que composant à télécharger, nous
AppComponent
dans le module principal AppComponent
. AppComponent
utilise <router-outlet>
pour sortir les composants de route appropriés.- Maintenant, la chose la plus importante. Nous devons obtenir
queryParams
pour l'itinéraire à partir de l'URL
Supposons que nous ayons l'URL suivante:
https:
Dans ce cas,
queryParams
ressemblera à ceci:
{accessToken: 'someTokenSequence'}
Regardons le travail de tout cela dans le navigateur.
Test d'une application qui implémente un système de routageIci, vous pouvez avoir une question sur l'essence du problème. Nous avons les paramètres, tout fonctionne comme prévu ...
Jetez un œil à la capture d'écran ci-dessus du navigateur et à ce qui s'affiche dans la console. Ici, vous pouvez voir que l'objet
queryParams
est émis deux fois. Le premier objet est vide; il est émis lors du processus d'initialisation du routeur angulaire. Ce n'est qu'après cela que nous obtenons un objet qui contient les paramètres de la demande (dans notre cas -
{accessToken: 'someTokenSequence'}
).
Le problème est que s'il n'y a pas de paramètres de requête dans l'URL, le routeur ne renverra rien. C'est-à-dire qu'après avoir émis le premier objet vide, le deuxième objet, également vide, ce qui pourrait indiquer l'absence de paramètres, ne sera pas émis.
Le deuxième objet lors de l'exécution d'une demande sans paramètres n'est pas émisPar conséquent, il s'avère que si le code attend un deuxième objet à partir duquel il peut recevoir des données de demande, il ne sera pas lancé s'il n'y avait aucun paramètre de demande dans l'URL.
Comment résoudre ce problème? Ici, les RxJ peuvent nous aider. Nous allons créer deux objets observables basés sur
ActivatedRoute.queryParams
. Comme d'habitude - envisagez une solution étape par étape au problème.
▍Etape numéro 1
Le premier objet observable,
paramsInUrl$
,
paramsInUrl$
données si
queryParams
pas vide:
export class AppComponent implements OnInit { constructor(private route: ActivatedRoute, private locationService: Location) { } ngOnInit() {
▍Étape numéro 2
Le deuxième objet observable,
noParamsInUrl$
,
noParamsInUrl$
valeur vide uniquement si aucun paramètre de requête n'a été trouvé dans l'URL:
export class AppComponent implements OnInit { title = 'QueryTest'; constructor(private route: ActivatedRoute, private locationService: Location) { } ngOnInit() { ...
▍Étape numéro 3
Combinez maintenant les objets observés à l'aide de la fonction de
fusion RxJS:
export class AppComponent implements OnInit { title = 'QueryTest'; constructor(private route: ActivatedRoute, private locationService: Location) { } ngOnInit() {
Désormais, l'objet
param$
observé
param$
valeur
param$
seule fois, que quelque chose soit
queryParams
dans
queryParams
(un objet avec des paramètres de requête est
queryParams
) ou non (un objet vide est
queryParams
).
Vous pouvez expérimenter avec ce code
ici .
N ° 5. Pages lentes
Supposons que vous ayez un composant qui affiche des données formatées:
Ce composant résout deux problèmes:
- Il affiche un tableau d'éléments (on suppose que cette opération est effectuée une fois). De plus, il formate ce qui est affiché en appelant la méthode
formatItem
. - Il affiche les coordonnées de la souris (cette valeur sera évidemment mise à jour très souvent).
Vous ne vous attendez pas à ce que ce composant rencontre des problèmes de performances. Par conséquent, exécutez un test de performance uniquement pour respecter toutes les formalités. Cependant, certaines bizarreries apparaissent lors de ce test.
Beaucoup d'appels formatItem et beaucoup de charge CPUQuelle est la question? Mais le fait est que lorsque Angular redessine le modèle, il appelle toutes les fonctions du modèle (dans notre cas, la fonction
formatItem
). Par conséquent, si des calculs lourds sont effectués dans les fonctions de modèle, cela met à rude épreuve le processeur et affecte la façon dont les utilisateurs perçoivent la page correspondante.
Comment y remédier? Il suffit d'effectuer à l'avance les calculs effectués dans
formatItem
, et d'afficher les données déjà prêtes sur la page.
Maintenant, le test de performance semble beaucoup plus décent.
Seulement 6 appels de formatItem et faible charge de processeurMaintenant, l'application fonctionne beaucoup mieux. Mais la solution utilisée ici présente des fonctionnalités qui ne sont pas toujours agréables:
- Puisque nous affichons les coordonnées de la souris dans le modèle, l'événement
mousemove
déclenche toujours une vérification des modifications. Mais, comme nous avons besoin des coordonnées de la souris, nous ne pouvons pas nous en débarrasser. - Si le
mousemove
événements mousemove
n'a besoin que d'effectuer certains calculs (qui n'affectent pas ce qui est affiché sur la page), alors pour accélérer l'application, vous pouvez procéder comme suit:
- Vous pouvez utiliser
NgZone.runOutsideOfAngular
dans la fonction de gestionnaire d'événements. Cela empêche la vérification des modifications de démarrer lorsque l'événement mousemove
(cela n'affectera que ce gestionnaire). - Vous pouvez empêcher le correctif zone.js pour certains événements en utilisant la ligne de code suivante dans polyfills.ts . Cela affectera l'ensemble de l'application angulaire.
* (window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove'];
Si vous êtes intéressé par les questions d'amélioration des performances des applications angulaires -
ici ,
ici ,
ici et
ici - des documents utiles à ce sujet.
Résumé
Maintenant que vous avez lu cet article, 5 nouveaux outils devraient apparaître dans votre arsenal de développeur angulaire avec lesquels vous pouvez résoudre certains problèmes courants. Nous espérons que les conseils que vous trouverez ici vous permettront de gagner du temps.
Chers lecteurs! Savez-vous quelque chose qui vous aide à gagner du temps lors du développement d'applications angulaires?
