Angulaire - * ngIf vs appels de fonction simples dans le modèle

14

Désolé si cela a déjà été répondu ici, mais je n'ai trouvé aucune correspondance pour notre scénario spécifique, alors c'est parti!

Nous avons eu une discussion dans notre équipe de développement, concernant les appels de fonction dans les modèles angulaires. Maintenant, en règle générale, nous convenons que vous ne devriez pas faire cela. Cependant, nous avons essayé de discuter du moment où cela pourrait aller. Permettez-moi de vous donner un scénario.

Disons que nous avons un bloc de modèle qui est enveloppé dans un ngIf, qui vérifie plusieurs paramètres, comme ici:

<ng-template *ngIf="user && user.name && isAuthorized">
 ...
</ng-template>

Y aurait-il une différence significative de performances par rapport à quelque chose comme ceci:

Modèle:

<ng-template *ngIf="userCheck()">
 ...
</ng-template>

Manuscrit:

userCheck(): boolean {
  return this.user && this.user.name && this.isAuthorized;
}

Donc, pour résumer la question, la dernière option aurait-elle un coût de performance significatif?

Nous préférerions utiliser la 2ème approche, dans les situations où nous devons vérifier plus de 2 conditions, mais de nombreux articles en ligne disent que les appels de fonction sont TOUJOURS mauvais dans les modèles, mais est-ce vraiment un problème dans ce cas?

Jesper
la source
7
Non, ce ne serait pas le cas. Et c'est aussi plus propre, car il rend le modèle plus lisible, la condition plus facilement testable et réutilisable, et vous avez plus d'outils à votre disposition (tout le langage TypeScript) pour le rendre aussi lisible et efficace que possible. Je choisirais cependant un nom beaucoup plus clair que "userCheck".
JB Nizet
Merci beaucoup pour votre contribution :)
Jesper

Réponses:

8

J'ai également essayé d'éviter autant que possible les appels de fonctions dans les modèles, mais votre question m'a inspiré à faire une recherche rapide:

J'ai ajouté un autre cas avec des userCheck()résultats de mise en cache

*ngIf="isUserChecked"

...
// .ts
isUserChecked = this.userCheck()

J'ai préparé une démo ici: https://stackblitz.com/edit/angular-9qgsm9

Étonnamment, il semble qu'il n'y ait pas de différence entre

*ngIf="user && user.name && isAuthorized"

Et

*ngIf="userCheck()"

...
// .ts
userCheck(): boolean {
  return this.user && this.user.name && this.isAuthorized;
}

Et

*ngIf="isUserChecked"

...
// .ts
isUserChecked = this.userCheck()

Cela semble valide pour une simple vérification de propriété, mais il y aura certainement une différence s'il s'agit d' asyncactions, des getters qui attendent une API par exemple.

qiAlex
la source
10

Ceci est une réponse assez réfléchie.

L'utilisation de fonctions comme celle-ci est parfaitement acceptable. Cela rendra les modèles beaucoup plus clairs et ne causera pas de surcharge importante. Comme JB l'a dit auparavant, cela établira également une bien meilleure base pour les tests unitaires.

Je pense également que quelle que soit l'expression que vous avez dans votre modèle, elle sera évaluée en fonction par le mécanisme de détection des changements, donc peu importe si vous l'avez dans votre modèle ou dans la logique de votre composant.

Gardez la logique à l'intérieur de la fonction au minimum. Si vous vous méfiez toutefois de tout impact sur les performances qu'une telle fonction pourrait avoir, je vous conseille vivement de le ChangeDetectionStrategyfaire OnPush, ce qui est de toute façon considéré comme la meilleure pratique. Avec cela, la fonction ne sera pas appelée à chaque cycle, uniquement lorsqu'un Inputchangement, un événement se produit à l'intérieur du modèle, etc.

(en utilisant etc, parce que je ne connais plus l'autre raison) .


Personnellement, encore une fois, je pense qu'il est encore plus agréable d'utiliser le modèle Observables, vous pouvez ensuite utiliser le async tuyau, et seulement lorsqu'une nouvelle valeur est émise, le modèle est réévalué:

userIsAuthorized$ = combineLatest([
  this.user$,
  this.isAuthorized$
]).pipe(
  map(([ user, authorized ]) => !!user && !!user.name && authorized),
  shareReplay({ refCount: true, bufferSize: 1 })
);

Vous pouvez ensuite simplement utiliser dans le modèle comme ceci:

<ng-template *ngIf="userIsAuthorized$ | async">
 ...
</ng-template>

Une autre option serait d’utiliser ngOnChanges , si toutes les variables dépendantes du composant sont des entrées, et que vous avez beaucoup de logique pour calculer une certaine variable de modèle (ce qui n'est pas le cas que vous avez montré):

export class UserComponent implements ngOnChanges {
  userIsAuthorized: boolean = false;

  @Input()
  user?: any;

  @Input()
  isAuthorized?: boolean;

  ngOnChanges(changes: SimpleChanges): void {
    if (changes.user || changes.isAuthorized) {
      this.userIsAuthorized = this.userCheck();
    }
  }

  userCheck(): boolean {
    return this.user && this.user.name && this.isAuthorized || false;
  }
}

Que vous pouvez utiliser dans votre modèle comme ceci:

<ng-template *ngIf="userIsAuthorized">
 ...
</ng-template>
Poul Kruijt
la source
Merci pour votre réponse, très perspicace. Cependant, pour notre cas spécifique, la modification de la stratégie de détection n'est pas une option, car le composant en question exécute une requête get, et donc le changement n'est pas lié à une entrée spécifique, mais plutôt à la requête get. Néanmoins, ce sont des informations très utiles pour le développement de futurs composants où le changement dépend des variables d'entrée
Jesper
1
@Jesper si le composant effectue une requête get, alors vous avez déjà un Observableflux, ce qui en fera un candidat parfait pour la 2ème option que j'ai montrée. Quoi qu'il en soit, je suis heureux de pouvoir vous donner quelques idées
Poul Kruijt
6

N'est pas recommandé pour de nombreuses raisons, le principal:

Pour déterminer si userCheck () doit être restitué, Angular doit exécuter l'expression userCheck () pour vérifier si sa valeur de retour a changé.

Dans la mesure où Angular ne peut pas prédire si la valeur de retour de userCheck () a changé, il doit exécuter la fonction à chaque exécution de la détection de changement.

Ainsi, si la détection des modifications s'exécute 300 fois, la fonction est appelée 300 fois, même si sa valeur de retour ne change jamais.

Explication étendue et autres problèmes https://medium.com/showpad-engineering/why-you-should-never-use-function-calls-in-angular-template-expressions-e1a50f9c0496

Le problème qui se pose lorsque si votre composant est important et participe à de nombreux événements de changement, si votre composant est peu important et ne participe qu'à quelques événements ne devrait pas être un problème.

Exemple avec observables

user$;
isAuth$
userCheck$;

userCheck$ = user$.pipe(
switchMap((user) => {
    return forkJoin([of(user), isAuth$]);
 }
)
.map(([user, isAuthenticated])=>{
   if(user && user.name && isAuthenticated){
     return true;
   } else {
     return false;
   }
})
);

Ensuite, vous pouvez l'utiliser dans le code observable avec async.

anthony willis muñoz
la source
2
Salut, je voulais juste souligner que je trouve la suggestion d'utiliser une variable est trompeuse au sérieux .. La variable ne sera pas mise à jour est la valeur lorsque l' une des valeurs combinées des changements
nsndvd
1
Et que l'expression soit directement dans le modèle, ou renvoyée par une fonction, elle devra être évaluée à chaque détection de changement.
JB Nizet
Oui que son vrai désolé va éditer pour ne pas faire de mauvaises pratiques
anthony willis muñoz
@ anthonywillismuñoz Alors, comment aborderiez-vous une situation comme celle-ci? Vivez simplement avec les multiples conditions difficiles à lire dans le * ngIf?
Jesper
1
cela dépend de votre situation, vous avez quelques options dans le poste moyen. Mais je pense que vous utilisez des observables. Modifie le message avec un exemple pour réduire la condition. si vous pouvez me montrer d'où vous obtenez les conditions.
anthony willis muñoz
0

Je pense que JavaScript a été créé avec un objectif afin qu'un développeur ne remarque pas la différence entre une expression et un appel de fonction en termes de performances.

En C ++, il existe un mot-clé inlinepour marquer une fonction. Par exemple:

inline bool userCheck()
{
    return isAuthorized;
}

Cela a été fait afin d'éliminer un appel de fonction. Par conséquent, le compilateur remplace tous les appels de userCheckpar le corps de la fonction. Pourquoi innover inline? Une amélioration des performances.

Par conséquent, je pense que le temps d'exécution d'un appel de fonction avec une expression, probablement, est plus lent que l'exécution de l'expression uniquement. Mais, je pense également que vous ne remarquerez pas de différence de performance si vous n'avez qu'une seule expression dans la fonction.

alexander.sivak
la source