Angular - * ngIf vs einfache Funktionsaufrufe in der Vorlage

14

Es tut uns leid, wenn dies hier bereits beantwortet wurde, aber ich konnte keine Übereinstimmung für unser spezielles Szenario finden.

Wir haben in unserem Entwicklungsteam eine Diskussion über Funktionsaufrufe in eckigen Vorlagen geführt. Als allgemeine Faustregel stimmen wir zu, dass Sie dies nicht tun sollten. Wir haben jedoch versucht zu diskutieren, wann es in Ordnung sein könnte. Lassen Sie mich Ihnen ein Szenario geben.

Angenommen, wir haben einen Vorlagenblock, der in eine ngIf eingeschlossen ist und nach mehreren Parametern sucht, wie hier:

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

Würde es einen signifikanten Leistungsunterschied im Vergleich zu so etwas geben:

Vorlage:

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

Typoskript:

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

Um die Frage zusammenzufassen: Hätte die letzte Option erhebliche Leistungskosten?

Wir würden es vorziehen, den 2. Ansatz zu verwenden, wenn wir mehr als zwei Bedingungen überprüfen müssen. In vielen Online-Artikeln heißt es jedoch, dass Funktionsaufrufe in Vorlagen IMMER schlecht sind. Ist dies in diesem Fall wirklich ein Problem?

Jesper
quelle
7
Nein, würde es nicht. Und es ist auch sauberer, da die Vorlage besser lesbar, der Zustand leichter testbar und wiederverwendbar ist und Sie über mehr Tools (die gesamte TypeScript-Sprache) verfügen, um sie so lesbar und effizient wie möglich zu gestalten. Ich würde jedoch einen viel klareren Namen als "userCheck" wählen.
JB Nizet
Vielen Dank für Ihre Eingabe :)
Jesper

Antworten:

8

Ich habe auch versucht, Funktionsaufrufe in Vorlagen so weit wie möglich zu vermeiden, aber Ihre Frage hat mich zu einer schnellen Recherche inspiriert:

Ich habe einen weiteren Fall mit Caching- userCheck()Ergebnissen hinzugefügt

*ngIf="isUserChecked"

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

Hier wurde eine Demo vorbereitet: https://stackblitz.com/edit/angular-9qgsm9

Überraschenderweise scheint es keinen Unterschied zu geben

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

Und

*ngIf="userCheck()"

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

Und

*ngIf="isUserChecked"

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

Dies scheint für eine einfache Eigenschaftsprüfung gültig zu sein, aber es wird definitiv einen Unterschied geben, wenn es um asyncAktionen geht, zum Beispiel um Getter, die auf eine API warten.

qiAlex
quelle
10

Dies ist eine ziemlich einfühlsame Antwort.

Die Verwendung solcher Funktionen ist durchaus akzeptabel. Dadurch werden die Vorlagen viel übersichtlicher und es entsteht kein erheblicher Overhead. Wie JB bereits sagte, wird es auch eine viel bessere Basis für Unit-Tests schaffen.

Ich denke auch, dass jeder Ausdruck, den Sie in Ihrer Vorlage haben, vom Änderungserkennungsmechanismus als Funktion ausgewertet wird. Es spielt also keine Rolle, ob Sie ihn in Ihrer Vorlage oder in Ihrer Komponentenlogik haben.

Halten Sie einfach die Logik innerhalb der Funktion auf ein Minimum. Wenn Sie jedoch Bedenken hinsichtlich der Auswirkungen einer solchen Funktion auf die Leistung haben, empfehle ich Ihnen dringend, Ihre ChangeDetectionStrategyzu verwenden OnPush, was ohnehin als Best Practice gilt. Damit wird die Funktion nicht in jedem Zyklus aufgerufen, nur wenn sich etwas Inputändert, ein Ereignis in der Vorlage auftritt usw.

(mit etc, weil ich den anderen Grund nicht mehr kenne) .


Ich persönlich denke, es ist noch schöner, das Observables-Muster zu verwenden. Sie können dann die asyncPipe verwenden, und nur wenn ein neuer Wert ausgegeben wird, wird die Vorlage neu bewertet:

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

Sie können dann einfach in der Vorlage wie folgt verwenden:

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

Eine weitere Option wäre die Verwendung ngOnChanges, wenn alle von der Komponente abhängigen Variablen Eingaben sind und Sie eine Menge Logik haben, um eine bestimmte Vorlagenvariable zu berechnen (was nicht der Fall ist, den Sie gezeigt haben):

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

Was Sie in Ihrer Vorlage wie folgt verwenden können:

<ng-template *ngIf="userIsAuthorized">
 ...
</ng-template>
Poul Kruijt
quelle
Vielen Dank für Ihre Antwort, sehr aufschlussreich. Für unseren speziellen Fall ist das Ändern der Erkennungsstrategie jedoch keine Option, da die betreffende Komponente eine Abrufanforderung ausführt und sich die Änderung daher nicht auf eine bestimmte Eingabe bezieht, sondern auf die Abrufanforderung. Dies sind jedoch sehr nützliche Informationen für die Entwicklung zukünftiger Komponenten, bei denen Änderungen von Eingabevariablen abhängen
Jesper,
1
@Jesper Wenn die Komponente eine Get-Anfrage ausführt, haben Sie bereits einen ObservableStream, der sie zu einem perfekten Kandidaten für die zweite Option macht, die ich gezeigt habe.
Wie auch immer
6

Wird aus vielen Gründen der Auftraggeber nicht empfohlen:

Um festzustellen, ob userCheck () neu gerendert werden muss, muss Angular den Ausdruck userCheck () ausführen, um zu überprüfen, ob sich sein Rückgabewert geändert hat.

Da Angular nicht vorhersagen kann, ob sich der Rückgabewert von userCheck () geändert hat, muss die Funktion jedes Mal ausgeführt werden, wenn die Änderungserkennung ausgeführt wird.

Wenn die Änderungserkennung 300 Mal ausgeführt wird, wird die Funktion 300 Mal aufgerufen, auch wenn sich ihr Rückgabewert nie ändert.

Erweiterte Erklärung und weitere Probleme https://medium.com/showpad-engineering/why-you-should-never-use-function-calls-in-angular-template-expressions-e1a50f9c0496

Das Problem, das auftritt, wenn Ihre Komponente groß ist und an vielen Änderungsereignissen teilnimmt, wenn Ihre Komponente klein ist und nur an einigen Ereignissen teilnimmt, sollte kein Problem sein.

Beispiel mit Observablen

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

Dann können Sie es die mit asynchroner Pipe beobachtbare Pipe in Ihrem Code verwenden.

Anthony Willis Muñoz
quelle
2
Hallo, ich wollte nur darauf hinweisen, dass der Vorschlag, eine Variable zu verwenden, ernsthaft irreführend ist. Die Variable wird nicht aktualisiert, wenn sich einer der kombinierten Werte ändert
nsndvd
1
Unabhängig davon, ob sich der Ausdruck direkt in der Vorlage befindet oder von einer Funktion zurückgegeben wird, muss er bei jeder Änderungserkennung ausgewertet werden.
JB Nizet
Ja, dass es wirklich leid tut, dass es keine schlechten Praktiken gibt
Anthony Willis Muñoz
@ anthonywillismuñoz Wie würden Sie sich einer solchen Situation nähern? Lebe einfach mit den vielen schwer lesbaren Bedingungen in der * ngIf?
Jesper
1
Das hängt von Ihrer Situation ab. Sie haben einige Optionen im mittleren Beitrag. Aber ich denke, dass Sie Observable verwenden. Bearbeitet den Beitrag mit einem Beispiel, um die Bedingung zu reduzieren. wenn du mir zeigen kannst, woher du die Bedingungen bekommst.
Anthony Willis Muñoz
0

Ich denke, dass JavaScript mit dem Ziel erstellt wurde, dass ein Entwickler den Unterschied zwischen einem Ausdruck und einem Funktionsaufruf in Bezug auf die Leistung nicht bemerkt.

In C ++ gibt es ein Schlüsselwort inlinezum Markieren einer Funktion. Zum Beispiel:

inline bool userCheck()
{
    return isAuthorized;
}

Dies wurde durchgeführt, um einen Funktionsaufruf zu eliminieren. Infolgedessen ersetzt der Compiler alle Aufrufe von userCheckdurch den Hauptteil der Funktion. Grund zur Innovation inline? Ein Leistungsschub.

Daher denke ich, dass die Ausführungszeit eines Funktionsaufrufs mit einem Ausdruck wahrscheinlich langsamer ist als die Ausführung nur des Ausdrucks. Ich denke aber auch, dass Sie keinen Leistungsunterschied bemerken werden, wenn Sie nur einen Ausdruck in der Funktion haben.

alexander.sivak
quelle