So deklarieren Sie eine Variable in einer Vorlage in Angular

199

Ich habe die folgende Vorlage:

<div>
  <span>{{aVariable}}</span>
</div>

und möchte am Ende mit:

<div "let a = aVariable">
  <span>{{a}}</span>
</div>

Gibt es eine Möglichkeit zu tun?

Scipion
quelle
Ich bin interessiert zu wissen, was die Anforderung / der Anwendungsfall ist, um den Namen eines Bindungsparameters wie dieses Beispiel ändern zu wollen.
LDJ
29
Es soll nur verhindert werden, dass so etwas wie tab [element] .val nach Instanz wiederholt wird. Ich weiß, dass ich das Problem in der Komponente lösen kann, aber ich habe mir nur angesehen, wie es in der Vorlage zu tun ist (obwohl ich möglicherweise nicht mit dieser Lösung enden kann).
Scipion
2
@LDJ Anwendungsbeispiel: Effizienz. Verwenden Sie das Beispiel von stackblitz.com/angular/… <mat-checkbox [checked] = " descantsAllSelected (node)" [unbestimmt] = "descantsPartiallySelected (node)" (change) = "todoItemSelectionToggle (node)"> {{node. item}} </ mat-checkbox> Tatsächlich ruft die descantsPartiallySelected () descantsAllSelected () auf. Es bedeutet, dass manchmal NachkommenAllSelected zweimal aufgerufen wird. Wenn es eine lokale Variable gibt, kann dies vermieden werden.
Steven.Xi
3
<div *ngIf="{name:'john'} as user1; let user"> <i>{{user1|json}}</i> <i>{{user|json}}</i> </div>
dasfdsa
@dasfdsa Ich glaube user1 === user, also tust du es entweder *ngIf="{name:'john'} as user1oder *ngIf="{name:'john'};let userwie in Yurzuis Antwort .
CPHPython

Antworten:

168

Aktualisieren

Wir können einfach eine Direktive wie erstellen *ngIfund sie nennen*ngVar

ng-var.directive.ts

@Directive({
    selector: '[ngVar]',
})
export class VarDirective {
  @Input()
  set ngVar(context: any) {
    this.context.$implicit = this.context.ngVar = context;
    this.updateView();
  }

  context: any = {};

  constructor(private vcRef: ViewContainerRef, private templateRef: TemplateRef<any>) {}

  updateView() {
    this.vcRef.clear();
    this.vcRef.createEmbeddedView(this.templateRef, this.context);
  }
}

Mit dieser *ngVarRichtlinie können wir Folgendes verwenden

<div *ngVar="false as variable">
      <span>{{variable | json}}</span>
</div>

oder

<div *ngVar="false; let variable">
    <span>{{variable | json}}</span>
</div>

oder

<div *ngVar="45 as variable">
    <span>{{variable | json}}</span>
</div>

oder

<div *ngVar="{ x: 4 } as variable">
    <span>{{variable | json}}</span>
</div>

Plunker Beispiel Angular4 ngVar

Siehe auch

Ursprüngliche Antwort

Winkel v4

1) div+ ngIf+let

<div *ngIf="{ a: 1, b: 2 }; let variable">
  <span>{{variable.a}}</span>
  <span>{{variable.b}}</span>
</div>

2) div+ ngIf+as

Aussicht

<div *ngIf="{ a: 1, b: 2, c: 3 + x } as variable">
  <span>{{variable.a}}</span>
  <span>{{variable.b}}</span>
  <span>{{variable.c}}</span>
</div>

component.ts

export class AppComponent {
  x = 5;
}

3) Wenn Sie keinen Wrapper erstellen möchten, wie divSie ihn verwenden könnenng-container

Aussicht

<ng-container *ngIf="{ a: 1, b: 2, c: 3 + x } as variable">
  <span>{{variable.a}}</span>
  <span>{{variable.b}}</span>
  <span>{{variable.c}}</span>
</ng-container>

Wie @Keith in den Kommentaren erwähnt

Dies funktioniert in den meisten Fällen, ist jedoch keine allgemeine Lösung, da die Variable wahrheitsgemäß ist

Siehe Update für einen anderen Ansatz.

Yurzui
quelle
10
Dies wird in den meisten Fällen funktionieren, aber es ist keine allgemeine Lösung, da es darauf beruht variable, wahr zu sein
Keith
6
@ Keith Danke, dass du darauf hingewiesen hast. Sie können einen Blick auf meine aktualisierte Antwort
werfen
3
Dies sollte die neue Antwort sein, da sie 1) moderner als die andere Lösung ist 2) die in der aktuellen Antwort verknüpften Pull-Anforderungen zusammenfasst und viel Zeit spart 3) die Beispiele inline und nicht über einen externen Link enthält. Danke, dass du das gezeigt hast. AFAIK jedes eingewickelte Objekt {}wird als wahr bewertet, daher ist diese Lösung ziemlich robust.
Kvanberendonck
4
Zum Beispiel erweiterbare Schaltflächen: *ngIf="{ expanded: false } as scope"Wenn Sie dann Bootstrap verwenden, können Sie einfach verwenden [ngClass]="{ 'in': scope.expanded }"und (click)="scope.expanded = !scope.expanded"anstatt etwas zu Ihren js/ tsDateien hinzuzufügen .
Kvanberendonck
1
verwandtes Github-Problem (weist darauf hin, dass ein einfaches *ngIfanstelle eines benutzerdefinierten Ngvar-Materials verwendet wird): github.com/angular/angular/issues/14985
phil294
79

Hässlich, aber:

<div *ngFor="let a of [aVariable]">
  <span>{{a}}</span>
</div>

Bei Verwendung mit asynchroner Pipe:

<div *ngFor="let a of [aVariable | async]">
  <span>{{a.prop1}}</span>
  <span>{{a.prop2}}</span>
</div>
Kayjtea
quelle
4
Das habe ich mir instinktiv ausgedacht - es funktioniert *ngFor="let a of [(someStream$ | async).someA]auch. Ich denke, mit einem verwendet <ng-container>es den Job ganz richtig!
Angelos Pikoulas
1
*ngForBeachten Sie in diesem Fall , dass der gesamte verschachtelte Inhalt neu erstellt wird, wenn sich der Variablenwert ändert, bis Sie eine trackByFunktion angeben , die für alle Werte dieselbe ID zurückgibt.
Valeriy Katkov
72

Sie können Variablen in HTML-Code deklarieren, indem Sie ein templateElement in Angular 2 oder verwendenng-template Angular 4+ verwenden.

Vorlagen haben ein Kontextobjekt, dessen Eigenschaften Variablen mithilfe der letBindungssyntax zugewiesen werden können . Beachten Sie, dass Sie einen Ausgang für die Vorlage angeben müssen, dieser kann jedoch ein Verweis auf sich selbst sein.

<ng-template let-a="aVariable" [ngTemplateOutletContext]="{ aVariable: 123 }" [ngTemplateOutlet]="selfie" #selfie>
  <div>
    <span>{{a}}</span>
  </div>
</ng-template>

<!-- Output
<div>
  <span>123</span>
</div>
-->

Sie können die Codemenge reduzieren, indem Sie die $implicitEigenschaft des Kontextobjekts anstelle einer benutzerdefinierten Eigenschaft verwenden.

<ng-template let-a [ngTemplateOutletContext]="{ $implicit: 123 }" [ngTemplateOutlet]="t" #t>
  <div>
    <span>{{a}}</span>
  </div>
</ng-template>

Das Kontextobjekt kann ein Literalobjekt oder ein anderer Bindungsausdruck sein. Sogar Rohre scheinen zu funktionieren, wenn sie von Klammern umgeben sind.

Gültige Beispiele für ngTemplateOutletContext:

  • [ngTemplateOutletContext]="{ aVariable: 123 }"
  • [ngTemplateOutletContext]="{ aVariable: (3.141592 | number:'3.1-5') }"
  • [ngTemplateOutletContext]="{ aVariable: anotherVariable }" benutzen mit let-a="aVariable"
  • [ngTemplateOutletContext]="{ $implicit: anotherVariable }" benutzen mit let-a
  • [ngTemplateOutletContext]="ctx"Wo ctxist ein öffentliches Eigentum
Steven Liekens
quelle
Damit es funktioniert, musste ich Ihren Code von '<template ...' in '<ng-template ...' ändern.
Humppakäräjät
2
Ja, Sie können nur <template>in Angular 2 verwenden. Sie können entweder <template>oder <ng-template>in Angular 4 verwenden, aber Sie sollten nur verwenden <ng-template>. Winkel 5 fallen gelassen für <template>.
Steven Liekens
Wofür ist das t?
Matttm
1
@matttm #tist eine Vorlagenvariable, in der die gespeichert wird ng-template. Es wird verwendet [ngTemplateOutlet]="t", um die ng-Template-Referenz selbst zu erstellen.
Steven Liekens
Das ist bizarr, aber es funktioniert! Angular sollte dies mit einer integrierten Variablenanweisung vereinfachen. Vielen Dank.
TetraDev
57

Update 3

Problem 2451 wurde in Angular 4.0.0 behoben

Siehe auch

Update 2

Dies wird nicht unterstützt.

Es gibt Vorlagenvariablen, aber es wird nicht unterstützt, beliebige Werte zuzuweisen. Sie können nur verwendet werden, um auf die Elemente zu verweisen, auf die sie angewendet werden, exportierte Namen von Direktiven oder Komponenten und Bereichsvariablen für strukturelle Direktiven wiengFor ,

Siehe auch https://github.com/angular/angular/issues/2451

Update 1

@Directive({
  selector: '[var]',
  exportAs: 'var'
})
class VarDirective {
  @Input() var:any;
}

und initialisiere es wie

<div #aVariable="var" var="abc"></div>

oder

<div #aVariable="var" [var]="'abc'"></div>

und benutze die Variable wie

<div>{{aVariable.var}}</div>

(nicht getestet)

  • #aVariable erstellt einen Verweis auf die VarDirective ( exportAs: 'var')
  • var="abc" instanziiert die VarDirective und übergibt den Zeichenfolgenwert "abc"an seine Werteingabe.
  • aVariable.varliest den Wert, der der Eingabe der varDirektiven zugewiesen ist var.
Günter Zöchbauer
quelle
Wäre es nicht möglich, eine strukturelle Richtlinie dafür zu erstellen?
Scipion
Wenn Sie dies wiederholt benötigen, kann eine Direktive das tun, was Sie wollen. Eine strukturelle Richtlinie schafft eine eigene Ansicht, das ist wahrscheinlich nicht das, was Sie wollen.
Günter Zöchbauer
1
@ GünterZöchbauer, sehr gutes Zeug. Ich weiß, dass es wahrscheinlich besser ist, Variablen in der component.tsDatei berechnen / vorbereiten zu lassen . Aufgrund eines Synchronisierungsschemas, das ich in meiner App implementiere, ist es für mich in einigen Fällen viel einfacher, sie in der Ansicht zu haben. Ich nutze die Javascript-Referenzregeln, wenn verschiedene Variablen auf dasselbe Objekt verweisen.
AmmarCSE
Ich bekomme einen Fehler wie There is no directive with "exportAs" set to "var". Kann mir bitte jemand sagen, welchen Fehler ich gemacht habe? Ich habe die obige Richtlinie verwendet.
Partha Sarathi Ghosh
Vielleicht haben Sie die Direktive declarations: [...]von nicht hinzugefügt @NgModule(). Wenn dies nicht das Problem ist, erstellen Sie bitte eine neue Frage und geben Sie den Code an, mit dem das Problem diagnostiziert werden kann.
Günter Zöchbauer
11

Hier ist eine Direktive, die ich geschrieben habe und die die Verwendung des exportAs decorator-Parameters erweitert und es Ihnen ermöglicht, ein Wörterbuch als lokale Variable zu verwenden.

import { Directive, Input } from "@angular/core";
@Directive({
    selector:"[localVariables]",
    exportAs:"localVariables"
})
export class LocalVariables {
    @Input("localVariables") set localVariables( struct: any ) {
        if ( typeof struct === "object" ) {
            for( var variableName in struct ) {
                this[variableName] = struct[variableName];
            }
        }
    }
    constructor( ) {
    }
}

Sie können es wie folgt in einer Vorlage verwenden:

<div #local="localVariables" [localVariables]="{a: 1, b: 2, c: 3+2}">
   <span>a = {{local.a}}</span>
   <span>b = {{local.b}}</span>
   <span>c = {{local.c}}</span>
</div>

Natürlich kann #local ein beliebiger gültiger lokaler Variablenname sein.

Aaron
quelle
Übergibt einen 'Production'-Build nicht wie er ist (wird auch von IDEs als Fehler angezeigt). In [key: string]: any;auf die Classdieses zu erhalten , um.
Charly
11

Ich würde dies vorschlagen: https://medium.com/@AustinMatherne/angular-let-directive-a168d4248138

Mit dieser Anweisung können Sie Folgendes schreiben:

<div *ngLet="'myVal' as myVar">
  <span> {{ myVar }} </span>
</div>
monkeythedev
quelle
1
so, aber * ist für strukturelle Richtlinien reserviert, die dies sowieso nicht ist +1
danday74
7

Wenn Sie die Antwort einer Funktion abrufen und in eine Variable umwandeln möchten, können Sie sie wie folgt in der Vorlage verwenden ng-container, um Änderungen an der Vorlage zu vermeiden.

<ng-container *ngIf="methodName(parameters) as respObject">
  {{respObject.name}}
</ng-container>

Und die Methode in der Komponente kann so etwas wie sein

methodName(parameters: any): any {
  return {name: 'Test name'};
}
Philip John
quelle
5

Wenn Sie Unterstützung für die automatische Vervollständigung von innen in Ihren Vorlagen vom Angular Language Service benötigen :

Synchron:

myVar = { hello: '' };

<ng-container *ngIf="myVar; let var;">
  {{var.hello}}
</ng-container>

Verwenden einer asynchronen Pipe:

myVar$ = of({ hello: '' });

<ng-container *ngIf="myVar$ | async; let var;">
  {{var.hello}}
</ng-container>
Stephen Paul
quelle
2

Es ist viel einfacher, es ist nichts zusätzliches erforderlich. In meinem Beispiel deklariere ich die Variable "open" und verwende sie dann.

   <mat-accordion class="accord-align" #open>
      <mat-expansion-panel hideToggle="true" (opened)="open.value=true" (closed)="open.value=false">
        <mat-expansion-panel-header>
          <span class="accord-title">Review Policy Summary</span>
          <span class="spacer"></span>
          <a *ngIf="!open.value" class="f-accent">SHOW</a>
          <a *ngIf="open.value" class="f-accent">HIDE</a>
        </mat-expansion-panel-header>
        <mat-divider></mat-divider>
        <!-- Quote Details Component -->
        <quote-details [quote]="quote"></quote-details>
      </mat-expansion-panel>
    </mat-accordion>
Jack Rus
quelle
Wenn
1
@Amirreza, um genau zu sein, verwende ich ElementRef, um einen Wert vorübergehend zu speichern.
Jack Rus
Genial! Ich musste verwenden, "?"weil ich die Meldung "Bezeichner 'Wert' ist nicht definiert" wie folgt hatte => "offen? .Wert" Aber es funktioniert !!
A. Morel
2

Ich verwende Angular 6x und habe am Ende das folgende Snippet verwendet. Ich habe ein Szenario, in dem ich Benutzer aus einem Aufgabenobjekt finden muss. Es enthält eine Reihe von Benutzern, aber ich muss den zugewiesenen Benutzer auswählen.

<ng-container *ngTemplateOutlet="memberTemplate; context:{o: getAssignee(task) }">
</ng-container>
<ng-template #memberTemplate let-user="o">
  <ng-container *ngIf="user">
    <div class="d-flex flex-row-reverse">
      <span class="image-block">
        <ngx-avatar placement="left" ngbTooltip="{{user.firstName}} {{user.lastName}}" class="task-assigned" value="28%" [src]="user.googleId" size="32"></ngx-avatar>
      </span>
    </div>
  </ng-container>
</ng-template>
Der Mechaniker
quelle
1

Ich mochte den Ansatz, eine Direktive zu erstellen, um dies zu tun (guter Ruf @yurzui).

Am Ende fand ich eine mittlere Artikel- Angular-Let-Direktive, die dieses Problem gut erklärt und eine benutzerdefinierte Let-Direktive vorschlägt , die für meinen Anwendungsfall mit minimalen Codeänderungen hervorragend funktioniert.

Hier ist das Wesentliche (zum Zeitpunkt der Veröffentlichung) mit meinen Änderungen:

import { Directive, Input, TemplateRef, ViewContainerRef } from '@angular/core'

interface LetContext <T> {
  appLet: T | null
}

@Directive({
  selector: '[appLet]',
})
export class LetDirective <T> {
  private _context: LetContext <T> = { appLet: null }

  constructor(_viewContainer: ViewContainerRef, _templateRef: TemplateRef <LetContext <T> >) {
    _viewContainer.createEmbeddedView(_templateRef, this._context)
  }

  @Input()
  set appLet(value: T) {
    this._context.appLet = value
  }
}

Meine wichtigsten Änderungen waren:

  • Ändern des Präfixes von 'ng' in 'app' (Sie sollten das benutzerdefinierte Präfix Ihrer App verwenden).
  • wechseln appLet: TzuappLet: T | null

Ich bin mir nicht sicher, warum das Angular-Team nicht nur eine offizielle ngLet-Direktive erlassen hat, sondern was auch immer.

Das ursprüngliche Quellcode-Guthaben geht an @AustinMatherne

Keego
quelle
Dies war mein Lieblingsansatz auf der Seite und es hat bei mir funktioniert.
Skychan
1

Kurze Antwort, die jemandem hilft

  • Vorlagenreferenzvariable verweist häufig auf das DOM-Element in einer Vorlage.
  • Verweis auch auf Winkel- oder Webkomponente und Direktive.
  • Das heißt, Sie können überall in einer Vorlage problemlos auf die Variable zugreifen

Geben Sie hier die Bildbeschreibung ein

Geben Sie hier die Bildbeschreibung ein

  • Deklarieren Sie die Referenzvariable mit dem Hash-Symbol (#).
  • Kann eine Variable als Parameter für ein Ereignis übergeben

Geben Sie hier die Bildbeschreibung ein

  show(lastName: HTMLInputElement){
    this.fullName = this.nameInputRef.nativeElement.value + ' ' + lastName.value;
    this.ctx.fullName = this.fullName;
  }

* Sie können jedoch ViewChild Decorator verwenden, um in Ihrer Komponente darauf zu verweisen.

import {ViewChild, ElementRef} from '@angular/core';

Referenz firstNameInput-Variable in Component

@ViewChild('firstNameInput') nameInputRef: ElementRef;

Danach können Sie this.nameInputRef an einer beliebigen Stelle in Ihrer Komponente verwenden.

Arbeiten mit ng-template

Im Fall von ng-template ist dies etwas anders, da jede Vorlage ihre eigenen Eingabevariablen hat.

Geben Sie hier die Bildbeschreibung ein

https://stackblitz.com/edit/angular-2-template-reference-variable

Mano
quelle
0

Beachten Sie für diejenigen, die sich entschieden haben, eine strukturelle Direktive als Ersatz für zu verwenden *ngIf, dass der Kontext der Direktive nicht standardmäßig typgeprüft ist. Informationen zum Erstellen einer typsicheren Direktiveneigenschaft ngTemplateContextGuardfinden Sie unter Typisieren des Kontextes der Direktive . Beispielsweise:

import { Directive, Input, TemplateRef, ViewContainerRef } from '@angular/core';

@Directive({
    // don't use 'ng' prefix since it's reserved for Angular
    selector: '[appVar]',
})
export class VarDirective<T = unknown> {
    // https://angular.io/guide/structural-directives#typing-the-directives-context
    static ngTemplateContextGuard<T>(dir: VarDirective<T>, ctx: any): ctx is Context<T> {
        return true;
    }

    private context?: Context<T>;

    constructor(
        private vcRef: ViewContainerRef,
        private templateRef: TemplateRef<Context<T>>
    ) {}

    @Input()
    set appVar(value: T) {
        if (this.context) {
            this.context.appVar = value;
        } else {
            this.context = { appVar: value };
            this.vcRef.createEmbeddedView(this.templateRef, this.context);
        }
    }
}

interface Context<T> {
    appVar: T;
}

Die Direktive kann genauso verwendet werden *ngIf, außer dass sie falsche Werte speichern kann :

<ng-container *appVar="false as value">{{value}}</ng-container>

<!-- error: User doesn't have `nam` property-->
<ng-container *appVar="user as user">{{user.nam}}</ng-container>

<ng-container *appVar="user$ | async as user">{{user.name}}</ng-container>

Der einzige Nachteil im Vergleich dazu *ngIfist, dass Angular Language Service den Variablentyp nicht ermitteln kann, sodass die Vorlagen keinen Code vervollständigen. Ich hoffe es wird bald behoben.

Valeriy Katkov
quelle