Jak zadeklarować zmienną w szablonie w Angular

201

Mam następujący szablon:

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

i chciałbym skończyć z:

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

Czy jest na to sposób?

Scipion
źródło
Jestem zainteresowany, aby dowiedzieć się, jaki jest wymóg / przypadek użycia, aby chcieć zmienić nazwę parametru wiązania, takiego jak ten przykład?
LDJ
31
Ma to na celu uniknięcie powtarzania czegoś takiego jak tab [element] .val według instancji. Wiem, że mogę rozwiązać problem w komponencie, ale właśnie zastanawiałem się, jak to zrobić w szablonie (nawet jeśli nie mogę tego rozwiązać).
Scipion
2
@LDJ jeden przykładowy przypadek użycia: wydajność. Użyj próbki stackblitz.com/angular/… <mat-checkbox [zaznaczone] = "descendantsAllSelected (node)" [indeterminate] = "descendantsPartIALSelected (node)" (change) = "todoItemSelectionToggle (node)"> {{node. item}} </mat-checkbox> w rzeczywistości descendantsPartIALSelected () wywołuje descendantsAllSelected (). Oznacza to, że kiedyś potomkowie AllSelected są wywoływani dwukrotnie. Jeśli istnieje zmienna lokalna, można tego uniknąć.
Steven.Xi
3
<div *ngIf="{name:'john'} as user1; let user"> <i>{{user1|json}}</i> <i>{{user|json}}</i> </div>
dasfdsa
@dasfdsa Wierzę user1 === user, więc albo to robisz, *ngIf="{name:'john'} as user1albo *ngIf="{name:'john'};let userjak w odpowiedzi yurzui .
CPHPython

Odpowiedzi:

174

Aktualizacja

Możemy po prostu stworzyć taką dyrektywę *ngIfi ją nazwać*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);
  }
}

dzięki tej *ngVardyrektywie możemy zastosować następujące

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

lub

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

lub

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

lub

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

Przykład plunkera Angular4 ngVar

Zobacz też

Oryginalna odpowiedź

Angular 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

widok

<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) Jeśli nie chcesz tworzyć opakowania, divktórego możesz użyćng-container

widok

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

Jak wspomniano w komentarzach @Keith

zadziała to w większości przypadków, ale nie jest to ogólne rozwiązanie, ponieważ polega na tym, że zmienna jest prawdomówna

Zobacz aktualizację dla innego podejścia.

yurzui
źródło
10
to zadziała w większości przypadków, ale nie jest to ogólne rozwiązanie, ponieważ polega na variablebyciu prawdomównym
Keith
6
@ Keith Dzięki za zwrócenie na to uwagi. Możesz
rzucić
3
To powinna być nowa odpowiedź, ponieważ 1) jest nowocześniejsza niż inne rozwiązanie 2) podsumowuje żądania ściągania powiązane z bieżącą odpowiedzią i oszczędza dużo czasu 3) zawiera przykłady zamiast wbudowanego łącza zewnętrznego. Dzięki za pokazanie tego. AFAIK jakikolwiek zawinięty obiekt {}oceni prawdę, więc to rozwiązanie jest dość solidne.
kvanberendonck
4
Na przykład przyciski rozwijane: *ngIf="{ expanded: false } as scope"a jeśli używasz bootstrap, możesz po prostu użyć [ngClass]="{ 'in': scope.expanded }"i (click)="scope.expanded = !scope.expanded"zamiast dodawać cokolwiek do js/ tsplików.
kvanberendonck
1
powiązany problem *ngIfz githubem (zwraca uwagę na proste, a nie niestandardowe rzeczy związane z Ngvar): github.com/angular/angular/angular/issues/14985
phil294
80

Brzydkie, ale:

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

W połączeniu z potokiem asynchronicznym:

<div *ngFor="let a of [aVariable | async]">
  <span>{{a.prop1}}</span>
  <span>{{a.prop2}}</span>
</div>
kajjtea
źródło
4
To właśnie wymyśliłem instynktownie - z tym też działa *ngFor="let a of [(someStream$ | async).someA]. Wydaje mi się, że użyty z <ng-container>to całkiem dobrze działa!
Angelos Pikoulas
2
W takim przypadku *ngFornależy pamiętać, że cała zagnieżdżona zawartość zostanie odtworzona, jeśli zmieni się wartość zmiennej, dopóki nie zostanie określona trackByfunkcja, która zwraca ten sam identyfikator dla wszystkich wartości.
Valeriy Katkov
74

Możesz zadeklarować zmienne w kodzie HTML, używając templateelementu w Angular 2 lub ng-templateAngular 4+.

Szablony mają obiekt kontekstowy, którego właściwości można przypisać do zmiennych za pomocą letskładni wiązania. Pamiętaj, że musisz określić wylot dla szablonu, ale może to być odniesienie do samego siebie.

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

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

Można zmniejszyć ilość kodu, używając $implicitwłaściwości obiektu kontekstu zamiast właściwości niestandardowej.

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

Obiekt kontekstu może być obiektem dosłownym lub dowolnym innym wyrażeniem wiążącym. Wydaje się, że nawet rury działają w nawiasach.

Prawidłowe przykłady ngTemplateOutletContext:

  • [ngTemplateOutletContext]="{ aVariable: 123 }"
  • [ngTemplateOutletContext]="{ aVariable: (3.141592 | number:'3.1-5') }"
  • [ngTemplateOutletContext]="{ aVariable: anotherVariable }" używać z let-a="aVariable"
  • [ngTemplateOutletContext]="{ $implicit: anotherVariable }" używać z let-a
  • [ngTemplateOutletContext]="ctx"gdzie ctxjest własność publiczna
Steven Liekens
źródło
Aby działało, musiałem zmienić kod z „<szablon ...” na „<ng-szablon ...”.
Humppakäräjät
2
Tak, można używać tylko <template>w kątowej 2. Można użyć jednej <template>lub <ng-template>w kątowym 4, ale należy używać tylko <ng-template>. Angular 5 porzucił wsparcie dla <template>.
Steven Liekens
Do czego służy t?
matttm
1
@matttm #tto zmienna szablonu, która przechowuje ng-template. Służy [ngTemplateOutlet]="t"do tworzenia odniesienia do samego szablonu ng.
Steven Liekens
To dziwne, ale działa! Angular powinien to uprościć dzięki wbudowanej zmiennej dyrektywie. Dzięki.
TetraDev
57

aktualizacja 3

Problem 2451 został naprawiony w Angular 4.0.0

Zobacz też

aktualizacja 2

To nie jest obsługiwane.

Istnieją zmienne szablonów, ale przypisywanie dowolnych wartości nie jest obsługiwane. Można ich używać tylko w odniesieniu do elementów, do których są stosowane, eksportowanych nazw dyrektyw lub komponentów i zmiennych zakresu dla dyrektyw strukturalnych, takich jak ngFor,

Zobacz także https://github.com/angular/angular/issues/2451

Aktualizacja 1

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

i zainicjuj to jak

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

lub

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

i użyj zmiennej like

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

(nie testowany)

  • #aVariabletworzy odniesienie do VarDirective( exportAs: 'var')
  • var="abc"tworzy instancję VarDirectivei przekazuje wartość ciągu "abc"do wartości wejściowej.
  • aVariable.varodczytuje wartość przypisaną do danych wejściowych vardyrektywvar
Günter Zöchbauer
źródło
Czy nie byłoby możliwe stworzenie w tym celu dyrektywy strukturalnej?
Scipion
Jeśli potrzebujesz tego wielokrotnie, dyrektywa może zrobić to, co chcesz. Dyrektywa strukturalna tworzy własny pogląd, prawdopodobnie nie tego chcesz.
Günter Zöchbauer
1
@ GünterZöchbauer, bardzo dobre rzeczy. Wiem, że prawdopodobnie lepszą praktyką jest obliczanie / przygotowywanie zmiennych w component.tspliku. Ale o wiele łatwiej jest mi je wyświetlić w niektórych przypadkach ze względu na schemat synchronizacji, który wdrażam w całej mojej aplikacji. Korzystam z reguł odwołań javascript, gdy różne zmienne wskazują ten sam obiekt.
AmmarCSE,
Otrzymuję błąd jak There is no directive with "exportAs" set to "var". Czy ktoś może mi powiedzieć, jaki popełniłem błąd? Użyłem powyższej dyrektywy.
Partha Sarathi Ghosh
Być może nie dodać do dyrektywy declarations: [...]o @NgModule(). Jeśli to nie jest problem, utwórz nowe pytanie i podaj kod umożliwiający zdiagnozowanie problemu.
Günter Zöchbauer
11

Oto dyrektywa, którą napisałem, która rozszerza zastosowanie parametru dekoratora exportAs i pozwala na użycie słownika jako zmiennej lokalnej.

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( ) {
    }
}

Możesz użyć go w szablonie w następujący sposób:

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

Oczywiście #local może być dowolną poprawną nazwą zmiennej lokalnej.

Aaron
źródło
Nie przekazuje kompilacji „produkcyjnej” w takiej postaci, w jakiej jest (pokazuje również jako błędy według IDE). Dodaj [key: string]: any;do, Classaby obejść ten problem.
Charly,
7

W przypadku, gdy chcesz uzyskać odpowiedź funkcji i ustawić ją w zmiennej, możesz użyć jej w szablonie w następujący sposób, ng-containeraby uniknąć modyfikacji szablonu.

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

Metoda w komponencie może być podobna

methodName(parameters: any): any {
  return {name: 'Test name'};
}
Philip John
źródło
5

Jeśli potrzebujesz wsparcia autouzupełniania z poziomu szablonów z Angular Language Service :

Synchroniczny:

myVar = { hello: '' };

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

Za pomocą potoku asynchronicznego:

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

<ng-container *ngIf="myVar$ | async; let var;">
  {{var.hello}}
</ng-container>
Stephen Paul
źródło
2

Używam kątowego 6x i skończyło się na tym, że użyłem poniżej fragmentu kodu. Mam scenerię, w której muszę znaleźć użytkownika z obiektu zadania. zawiera tablicę użytkowników, ale muszę wybrać przypisanego użytkownika.

<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>
Mechanik
źródło
1

Jest to o wiele prostsze, bez potrzeby niczego dodatkowego. W moim przykładzie deklaruję zmienną „open”, a następnie jej używam.

   <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
źródło
nazywając tag, nie deklaruje on zmiennej
Amirreza
1
@Amirreza, a ściślej używam ElementRef do tymczasowego przechowywania wartości.
Jack Rus
Niesamowite! Musiałem użyć, "?"ponieważ miałem komunikat „Wartość identyfikatora” nie jest zdefiniowana ”jak to =>„ open? .Value ”Ale to działa !!
A. Morel,
1

Podobało mi się podejście polegające na stworzeniu w tym celu dyrektywy (dobre połączenie @yurzui).

Skończyło się na tym, że znalazłem dyrektywę Angular „let” w artykule Medium, która ładnie wyjaśnia ten problem i proponuje niestandardową dyrektywę let, która ostatecznie świetnie działa w moim przypadku użycia z minimalnymi zmianami kodu.

Oto sedno (w momencie publikowania) moich modyfikacji:

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

Moje główne zmiany to:

  • zmiana prefiksu z „ng” na „app” (należy użyć dowolnego niestandardowego prefiksu aplikacji)
  • zmiana appLet: TnaappLet: T | null

Nie jestem pewien, dlaczego zespół Angular nie tylko wydał oficjalną dyrektywę ngLet, ale co tam.

Oryginalny kod źródłowy trafia do @AustinMatherne

Keego
źródło
To było moje ulubione podejście na stronie i zadziałało dla mnie.
Skychan
1

Krótka odpowiedź, która komuś pomaga

  • Zmienna odniesienia szablonu często odwołuje się do elementu DOM w szablonie.
  • Również odniesienie do komponentu i dyrektywy kątowej lub sieci.
  • Oznacza to, że możesz łatwo uzyskać dostęp do zmiennej w dowolnym miejscu szablonu

wprowadź opis zdjęcia tutaj

wprowadź opis zdjęcia tutaj

  • Deklaracja zmiennej referencyjnej za pomocą symbolu skrótu (#)
  • Potrafi przekazać zmienną jako parametr zdarzenia

wprowadź opis zdjęcia tutaj

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

* Można jednak użyć dekoratora ViewChild, aby odwołać się do niego wewnątrz komponentu.

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

Odwołanie do zmiennej firstNameInput wewnątrz komponentu

@ViewChild('firstNameInput') nameInputRef: ElementRef;

Następnie możesz użyć this.nameInputRef w dowolnym miejscu w swoim Component.

Praca z szablonem ng

W przypadku ng-template jest nieco inaczej, ponieważ każdy szablon ma swój własny zestaw zmiennych wejściowych.

wprowadź opis zdjęcia tutaj

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

Mano
źródło
1

Dla tych, którzy zdecydowali się zastosować dyrektywę strukturalną jako zamiennik *ngIf, należy pamiętać, że kontekst dyrektywy nie jest domyślnie sprawdzany pod względem typu. Aby utworzyć bezpieczną dyrektywę typu ngTemplateContextGuardnależy dodać właściwość, patrz Pisanie kontekstu dyrektywy . Na przykład:

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

Dyrektywa może być używana tak samo *ngIf, z tym wyjątkiem, że może przechowywać fałszywe wartości:

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

Jedyną wadą w porównaniu z *ngIftym jest to, że Angular Language Service nie jest w stanie ustalić typu zmiennej, więc nie ma uzupełniania kodu w szablonach. Mam nadzieję, że zostanie to wkrótce naprawione.

Walerij Katkow
źródło