Angolare - * ngIf rispetto alle semplici chiamate di funzione nel modello


14

Scusate se qui è già stata data una risposta, ma non sono riuscito a trovare alcuna corrispondenza per il nostro scenario specifico, quindi ecco qui!

Abbiamo discusso nel nostro team di sviluppo in merito alle chiamate di funzione in modelli angolari. Ora, come regola generale, siamo d'accordo che non dovresti farlo. Tuttavia, abbiamo provato a discutere quando potrebbe andare bene. Lascia che ti dia uno scenario.

Diciamo che abbiamo un blocco modello racchiuso in un ngIf, che controlla più parametri, come qui:

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

Ci sarebbe una differenza significativa nelle prestazioni rispetto a qualcosa del genere:

Modello:

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

Dattiloscritto:

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

Quindi, per riassumere la domanda, l'ultima opzione avrebbe costi significativi per le prestazioni?

Preferiremmo usare il secondo approccio, in situazioni in cui dobbiamo controllare più di 2 condizioni, ma molti articoli online dicono che le chiamate di funzione sono SEMPRE cattive nei template, ma è davvero un problema in questo caso?


7
No, non lo farebbe. Ed è anche più pulito, in quanto rende il modello più leggibile, la condizione più facilmente testabile e riutilizzabile e hai più strumenti a tua disposizione (l'intero linguaggio TypeScript) per renderlo il più leggibile ed efficiente possibile. Vorrei scegliere un nome molto più chiaro di "userCheck", però.
JB Nizet,

Grazie mille per il tuo contributo :)
Jesper

Risposte:


8

Ho anche cercato di evitare il più possibile le chiamate alle funzioni nei template, ma la tua domanda mi ha ispirato a fare una rapida ricerca:

Ho aggiunto un altro caso con userCheck()risultati di memorizzazione nella cache

*ngIf="isUserChecked"

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

Ho preparato una demo qui: https://stackblitz.com/edit/angular-9qgsm9

Sorprendentemente sembra che non ci sia differenza tra

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

E

*ngIf="userCheck()"

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

E

*ngIf="isUserChecked"

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

Sembra che sia valido per un semplice controllo della proprietà, ma sicuramente ci sarà una differenza se si tratta di asyncazioni, ad esempio quelli che stanno aspettando qualche API.


10

Questa è una risposta piuttosto supponente.

L'uso di funzioni come questa è perfettamente accettabile. Renderà i modelli molto più chiari e non causerà alcun sovraccarico significativo. Come detto in precedenza JB, costituirà una base molto migliore per i test unitari.

Penso anche che qualunque espressione tu abbia nel tuo modello, sarà valutata come una funzione dal meccanismo di rilevamento delle modifiche, quindi non importa se ce l'hai nel tuo modello o nella logica dei tuoi componenti.

Tieni al minimo la logica all'interno della funzione. Se siete comunque diffidare di qualsiasi impatto sulle prestazioni di un tale funzione potrebbe avere, vi consiglio vivamente di mettere la vostra ChangeDetectionStrategya OnPush, che è considerato migliori pratiche in ogni modo. Con questo, la funzione non verrà chiamata ad ogni ciclo, solo quando Inputcambia, si verifica un evento all'interno del modello, ecc.

(usando etc, perché non conosco più l'altro motivo) .


Personalmente, ancora una volta, penso che sia ancora più bello usare il modello Observables, puoi quindi usare la asyncpipe e solo quando viene emesso un nuovo valore, il modello viene rivalutato:

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

È quindi possibile utilizzare nel modello in questo modo:

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

Un'altra opzione sarebbe quella di utilizzare ngOnChanges, se tutte le variabili dipendenti dal componente sono Input, e hai molta logica in atto per calcolare una determinata variabile di modello (che non è il caso che hai mostrato):

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;
  }
}

Che puoi usare nel tuo modello in questo modo:

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

Grazie per la tua risposta, molto perspicace. Per il nostro caso specifico, tuttavia, la modifica della strategia di rilevamento non è un'opzione, poiché il componente in questione esegue una richiesta get, e quindi la modifica non è correlata a un input specifico, ma piuttosto alla richiesta get. Tuttavia, si tratta di informazioni molto utili per lo sviluppo di componenti futuri in cui il cambiamento dipende dalle variabili di input
Jesper

1
@Jesper se il componente esegue una richiesta get, allora hai già un Observableflusso, che lo renderà un candidato perfetto per la seconda opzione che ho mostrato. Ad ogni modo, felice di poterti dare alcune intuizioni
Poul Kruijt il

6

Non è raccomandato per molte ragioni il principale:

Per determinare se è necessario eseguire nuovamente il rendering di userCheck (), Angular deve eseguire l'espressione userCheck () per verificare se il valore di ritorno è stato modificato.

Poiché Angular non è in grado di prevedere se il valore di ritorno di userCheck () è stato modificato, è necessario eseguire la funzione ogni volta che viene eseguito il rilevamento delle modifiche.

Pertanto, se il rilevamento delle modifiche viene eseguito 300 volte, la funzione viene chiamata 300 volte, anche se il suo valore di ritorno non cambia mai.

Spiegazione estesa e altri problemi https://medium.com/showpad-engineering/why-you-should-never-use-function-calls-in-angular-template-expressions-e1a50f9c0496

Il problema si presenta quando se il tuo componente è grande e partecipi a molti eventi di cambiamento, se il tuo componente sarà piccolo e parteciperai ad alcuni eventi non dovrebbe essere un problema.

Esempio con osservabili

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

Quindi puoi usarlo come osservabile con una pipe asincrona nel tuo codice.


2
Salve, volevo solo sottolineare che trovo seriamente fuorviante il suggerimento di utilizzare una variabile .. La variabile non si aggiorna è valore quando uno qualsiasi dei valori combinati cambia
nsndvd

1
E se l'espressione è direttamente nel modello o restituita da una funzione, dovrà essere valutata ad ogni rilevamento di modifica.
JB Nizet,

Sì, la sua vera scusa modificherà per non fare cattive pratiche
Anthony Williams muñoz il

@ anthonywillismuñoz Quindi come affronteresti una situazione come questa? Basta vivere con le condizioni multiple e difficili da leggere in * ngIf?
Jesper

1
dipende dalla tua situazione, hai alcune opzioni nel post medio. Ma penso che tu stia usando osservabili. Modificherà il post con un esempio per ridurre la condizione. se puoi mostrarmi da dove ottieni le condizioni.
Anthony Williams Muñoz il

0

Penso che JavaScript sia stato creato con un obiettivo in modo che uno sviluppatore non noti la differenza tra un'espressione e una chiamata di funzione per quanto riguarda le prestazioni.

In C ++ c'è una parola chiave inlineper contrassegnare una funzione. Per esempio:

inline bool userCheck()
{
    return isAuthorized;
}

Ciò è stato fatto per eliminare una chiamata di funzione. Di conseguenza, il compilatore sostituisce tutte le chiamate userCheckcon il corpo della funzione. Motivo per l'innovazione inline? Un aumento delle prestazioni.

Pertanto, penso che il tempo di esecuzione di una chiamata di funzione con un'espressione, probabilmente, sia più lento dell'esecuzione dell'espressione. Ma penso anche che non noterai una differenza nelle prestazioni se hai una sola espressione nella funzione.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.