Angular - * ngIf vs llamadas a funciones simples en la plantilla

14

Lo siento si esto ya se ha respondido aquí, pero no pude encontrar ninguna coincidencia para nuestro escenario específico, ¡así que aquí va!

Hemos tenido una discusión en nuestro equipo de desarrollo, con respecto a las llamadas a funciones en plantillas angulares. Ahora, como regla general, estamos de acuerdo en que no debe hacer esto. Sin embargo, hemos tratado de discutir cuándo podría estar bien. Déjame darte un escenario.

Digamos que tenemos un bloque de plantilla que está envuelto en un ngIf, que verifica múltiples parámetros, como aquí:

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

¿Habría una diferencia significativa en el rendimiento en comparación con algo como esto:

Modelo:

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

Mecanografiado:

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

Entonces, para resumir la pregunta, ¿la última opción tendría algún costo de rendimiento significativo?

Preferiríamos usar el segundo enfoque, en situaciones donde necesitamos verificar más de 2 condiciones, pero muchos artículos en línea dicen que las llamadas a funciones SIEMPRE son malas en las plantillas, pero ¿es realmente un problema en este caso?

Jesper
fuente
77
No, no lo haría. Y también es más limpio, ya que hace que la plantilla sea más legible, la condición sea más fácilmente comprobable y reutilizable, y tiene más herramientas a su disposición (todo el lenguaje TypeScript) para que sea lo más legible y eficiente posible. Sin embargo, elegiría un nombre mucho más claro que "userCheck".
JB Nizet
Muchas gracias por tu aporte :)
Jesper

Respuestas:

8

También intenté evitar las llamadas a funciones en las plantillas tanto como sea posible, pero su pregunta me inspiró a hacer una investigación rápida:

Agregué otro caso con userCheck()resultados de almacenamiento en caché

*ngIf="isUserChecked"

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

Preparó una demostración aquí: https://stackblitz.com/edit/angular-9qgsm9

Sorprendentemente parece que no hay diferencia entre

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

Y

*ngIf="userCheck()"

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

Y

*ngIf="isUserChecked"

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

Parece que es válido para una simple comprobación de propiedades, pero definitivamente habrá una diferencia si se trata de alguna async acciones, por ejemplo, captadores que esperan alguna API.

qiAlex
fuente
10

Esta es una respuesta bastante obstinada.

El uso de funciones como esta es perfectamente aceptable. Hará que las plantillas sean mucho más claras y no causará una sobrecarga significativa. Como JB dijo antes, también establecerá una base mucho mejor para las pruebas unitarias.

También creo que cualquier expresión que tenga en su plantilla será evaluada como una función por el mecanismo de detección de cambios, por lo que no importa si la tiene en su plantilla o en la lógica de sus componentes.

Simplemente mantenga la lógica dentro de la función al mínimo. Si no está sin embargo cautelosos acerca de cualquier función de un tal impacto en el rendimiento podría tener, yo recomiendo que usted ponga su ChangeDetectionStrategya OnPush, que se considera la mejor práctica de todos modos. Con esto, la función no se llamará en cada ciclo, solo cuando un Inputcambio, algún evento ocurra dentro de la plantilla, etc.

(usando etc, porque ya no sé la otra razón) .


Personalmente, nuevamente, creo que es aún mejor usar el patrón Observables, luego puedes usar la asynctubería, y solo cuando se emite un nuevo valor, la plantilla se vuelve a evaluar:

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

Luego puede usar en la plantilla de esta manera:

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

Otra opción sería usar ngOnChanges, si todas las variables dependientes del componente son entradas, y tiene mucha lógica para calcular una determinada variable de plantilla (que no es el caso que mostró):

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 puedes usar en tu plantilla de esta manera:

<ng-template *ngIf="userIsAuthorized">
 ...
</ng-template>
Poul Kruijt
fuente
Gracias por su respuesta, muy perspicaz. Sin embargo, para nuestro caso específico, cambiar la estrategia de detección no es una opción, ya que el componente en cuestión realiza una solicitud de obtención y, por lo tanto, el cambio no está relacionado con una entrada específica, sino con la solicitud de obtención. Sin embargo, esta es información muy útil para el desarrollo de componentes futuros donde el cambio depende de las variables de entrada
Jesper
1
@Jesper si el componente realiza una solicitud de obtención, entonces ya tiene una Observablesecuencia, lo que lo convertirá en un candidato perfecto para la segunda opción que mostré. De cualquier manera, me alegro de poder darte algunas ideas
Poul Kruijt
6

No se recomienda por muchas razones el principal:

Para determinar si userCheck () necesita ser re-renderizado, Angular necesita ejecutar la expresión userCheck () para verificar si su valor de retorno ha cambiado.

Debido a que Angular no puede predecir si el valor de retorno de userCheck () ha cambiado, necesita ejecutar la función cada vez que se ejecuta la detección de cambio.

Entonces, si la detección de cambios se ejecuta 300 veces, la función se llama 300 veces, incluso si su valor de retorno nunca cambia.

Explicación extendida y más problemas https://medium.com/showpad-engineering/why-you-should-never-use-function-calls-in-angular-template-expressions-e1a50f9c0496

El problema viene cuando si su componente es grande y asiste a muchos eventos de cambio, si su componente será pequeño y solo asiste a algunos eventos, no debería ser un problema.

Ejemplo con 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;
   }
})
);

Luego puede usarlo observable con una tubería asíncrona en su código.

anthony willis muñoz
fuente
2
Hola, solo quería señalar que encuentro que la sugerencia de usar una variable es muy engañosa. La variable no se actualizará es el valor cuando alguno de los valores combinados cambia
nsndvd
1
Y si la expresión está directamente en la plantilla, o es devuelta por una función, tendrá que ser evaluada en cada detección de cambio.
JB Nizet
Sí, es verdad que lo lamentaré por no hacer malas prácticas
anthony willis muñoz
@ anthonywillismuñoz Entonces, ¿cómo abordarías una situación como esta? Simplemente vive con las múltiples condiciones difíciles de leer en * ngIf?
Jesper
1
eso depende de tu situación, tienes algunas opciones en la publicación mediana. Pero creo que estás usando observables. Editará la publicación con un ejemplo para reducir la condición. si puedes mostrarme de dónde obtienes las condiciones.
anthony willis muñoz
0

Creo que JavaScript se creó con un objetivo para que un desarrollador no note la diferencia entre una expresión y una llamada de función con respecto al rendimiento.

En C ++ hay una palabra clave inlinepara marcar una función. Por ejemplo:

inline bool userCheck()
{
    return isAuthorized;
}

Esto se hizo para eliminar una llamada de función. Como resultado, el compilador reemplaza todas las llamadas de userCheckcon el cuerpo de la función. ¿Motivo para innovar inline? Un aumento de rendimiento.

Por lo tanto, creo que el tiempo de ejecución de una llamada de función con una expresión, probablemente, es más lento que la ejecución de la expresión solamente. Pero, también creo que no notarás una diferencia en el rendimiento si solo tienes una expresión en la función.

alexander.sivak
fuente