Comment déclarer une variable dans un modèle dans Angular

203

J'ai le modèle suivant:

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

et aimerait finir avec:

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

Y a-t-il un moyen de faire?

Scipion
la source
Je suis intéressé de savoir quelle est l'exigence / cas d'utilisation pour vouloir changer le nom d'un paramètre de liaison tel que cet exemple?
LDJ
31
C'est juste pour éviter de répéter quelque chose comme tab [élément] .val par instance. Je sais que je peux résoudre le problème dans le composant, mais je cherchais juste comment faire dans le modèle (même si je ne peux pas me retrouver avec cette solution).
Scipion
2
@LDJ un exemple d'utilisation: l'efficacité. Utilisez l'exemple de stackblitz.com/angular/... <mat-checkbox [checked] = "descendantsAllSelected (node)" [indeterminate] = "descendantsPartiallySelected (node)" (change) = "todoItemSelectionToggle (node)"> {{node. item}} </mat-checkbox> en fait les descendantsPartiallySelected () appelle descendantsAllSelected (). Cela signifie que parfois descendantsAllSelected est appelé deux fois. S'il y a une variable locale, cela peut être évité.
Steven.Xi
3
<div *ngIf="{name:'john'} as user1; let user"> <i>{{user1|json}}</i> <i>{{user|json}}</i> </div>
dasfdsa
@dasfdsa je crois user1 === user, donc vous faites *ngIf="{name:'john'} as user1ou *ngIf="{name:'john'};let usercomme dans la réponse de yurzui .
CPHPython

Réponses:

175

Mettre à jour

Nous pouvons simplement créer une directive comme *ngIfet l'appeler*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);
  }
}

avec cette *ngVardirective, nous pouvons utiliser ce qui suit

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

ou

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

ou

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

ou

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

Exemple de Plunker Angular4 ngVar

Voir également

Réponse originale

Angulaire 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

vue

<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) Si vous ne voulez pas créer de wrapper comme divvous pouvez utiliserng-container

vue

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

Comme @Keith l'a mentionné dans les commentaires

cela fonctionnera dans la plupart des cas mais ce n'est pas une solution générale car elle repose sur la véracité de la variable

Voir la mise à jour pour une autre approche.

yurzui
la source
10
cela fonctionnera dans la plupart des cas, mais ce n'est pas une solution générale car elle repose sur la variablevérité
Keith
6
@Keith Merci de l'avoir signalé. Vous pouvez jeter un oeil à ma réponse mise à jour
yurzui
3
Cela devrait être la nouvelle réponse car elle 1) est plus moderne que l'autre solution 2) résume les pull requests liées dans la réponse actuelle et fait gagner beaucoup de temps 3) inclut les exemples en ligne plutôt que par lien externe. Merci de l'avoir montré. AFAIK tout objet enveloppé {}évaluera à la vérité, donc cette solution est assez robuste.
kvanberendonck
4
Par exemple, des boutons extensibles: *ngIf="{ expanded: false } as scope"et ensuite, si vous utilisez bootstrap, vous pouvez simplement utiliser [ngClass]="{ 'in': scope.expanded }"et (click)="scope.expanded = !scope.expanded"au lieu d'ajouter quoi que ce soit à vos fichiers js/ ts.
kvanberendonck
1
problème lié à github (indique l'utilisation d'un *ngIftruc simple au lieu d'un ngvar personnalisé): github.com/angular/angular/issues/14985
phil294
81

Moche, mais:

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

Lorsqu'il est utilisé avec un tuyau asynchrone:

<div *ngFor="let a of [aVariable | async]">
  <span>{{a.prop1}}</span>
  <span>{{a.prop2}}</span>
</div>
kayjtea
la source
4
C'est celui que j'ai inventé instinctivement - cela fonctionne *ngFor="let a of [(someStream$ | async).someA]aussi. Je suppose qu'utilisé avec un <ng-container>il sert parfaitement le travail!
Angelos Pikoulas
2
Dans le cas de *ngFor, gardez à l'esprit que tout le contenu imbriqué sera recréé si la valeur de la variable change, jusqu'à ce que vous spécifiiez une trackByfonction qui renvoie le même identifiant pour toutes les valeurs.
Valeriy Katkov le
76

Vous pouvez déclarer des variables en code html en utilisant un templateélément dans Angular 2 ou ng-templatedans Angular 4+.

Les modèles ont un objet de contexte dont les propriétés peuvent être affectées à des variables à l'aide de letla syntaxe de liaison. Notez que vous devez spécifier une sortie pour le modèle, mais cela peut être une référence à lui-même.

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

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

Vous pouvez réduire la quantité de code en utilisant la $implicitpropriété de l'objet de contexte au lieu d'une propriété personnalisée.

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

L'objet de contexte peut être un objet littéral ou toute autre expression de liaison. Même les tuyaux semblent fonctionner lorsqu'ils sont entourés de parenthèses.

Exemples valides de ngTemplateOutletContext:

  • [ngTemplateOutletContext]="{ aVariable: 123 }"
  • [ngTemplateOutletContext]="{ aVariable: (3.141592 | number:'3.1-5') }"
  • [ngTemplateOutletContext]="{ aVariable: anotherVariable }" utiliser avec let-a="aVariable"
  • [ngTemplateOutletContext]="{ $implicit: anotherVariable }" utiliser avec let-a
  • [ngTemplateOutletContext]="ctx"ctxest une propriété publique
Steven Liekens
la source
Pour que cela fonctionne, j'ai dû changer votre code de «<template ...» à «<ng-template ...».
Humppakäräjät
2
Oui, vous ne pouvez utiliser que <template>dans Angular 2. Vous pouvez utiliser soit<template> ou l' ou <ng-template>dans Angular 4, mais vous ne devez utiliser que <ng-template>. Angular 5 a abandonné le support pour <template>.
Steven Liekens
Quel est le tpour?
matttm
1
@matttm #test une variable de modèle qui stocke le fichier ng-template. Il est utilisé dans[ngTemplateOutlet]="t" pour créer la référence ng-template elle-même.
Steven Liekens
C'est bizare, mais ça marche! Angular devrait rendre cela plus simple, avec une directive de variable intégrée. Merci.
TetraDev
57

mise à jour 3

Le problème 2451 est résolu dans Angular 4.0.0

Voir également

mise à jour 2

Ce n'est pas pris en charge.

Il existe des variables de modèle mais il n'est pas pris en charge pour attribuer des valeurs arbitraires. Ils ne peuvent être utilisés que pour faire référence aux éléments auxquels ils sont appliqués, aux noms exportés de directives ou de composants et aux variables de portée pour les directives structurelles telles quengFor ,

Voir aussi https://github.com/angular/angular/issues/2451

Mise à jour 1

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

et initialisez-le comme

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

ou

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

et utilisez la variable comme

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

(pas testé)

  • #aVariable crée une référence au VarDirective ( exportAs: 'var')
  • var="abc" instancie le VarDirective et transmet la valeur de chaîne "abc"à son entrée de valeur.
  • aVariable.varlit la valeur affectée à l' entrée des vardirectives var.
Günter Zöchbauer
la source
Ne serait-il pas possible de créer une directive structurelle pour ce faire?
Scipion
Si vous en avez besoin à plusieurs reprises, une directive peut faire ce que vous voulez. Une directive structurelle crée sa propre vue, ce n'est probablement pas ce que vous voulez.
Günter Zöchbauer
1
@ GünterZöchbauer, de très bonnes choses. Je sais que c'est probablement une meilleure pratique d'avoir des variables calculées / préparées dans le component.tsfichier. Mais c'est tellement plus facile pour moi de les avoir dans la vue dans certains cas en raison d'un schéma de synchronisation que j'implémente dans mon application. Je profite des règles de référence javascript lorsque différentes variables pointent vers le même objet.
AmmarCSE
Je reçois une erreur comme There is no directive with "exportAs" set to "var". Quelqu'un peut-il me dire quelle erreur j'ai fait? J'ai utilisé la directive ci-dessus.
Partha Sarathi Ghosh
Peut-être n'avez-vous pas ajouté la directive à declarations: [...]of @NgModule(). Si ce n'est pas le problème, veuillez créer une nouvelle question et fournir le code qui permet de diagnostiquer le problème.
Günter Zöchbauer
11

Voici une directive que j'ai écrite qui développe l'utilisation du paramètre de décorateur exportAs, et vous permet d'utiliser un dictionnaire comme variable locale.

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

Vous pouvez l'utiliser comme suit dans un modèle:

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

Bien sûr, #local peut être n'importe quel nom de variable locale valide.

Aaron
la source
Ne passe pas une construction de «production» telle quelle (apparaît également comme des erreurs par les IDE). Ajoutez [key: string]: any;à la Classpour contourner ce problème.
Charly le
7

Si vous souhaitez obtenir la réponse d'une fonction et la définir dans une variable, vous pouvez l'utiliser comme suit dans le modèle, en utilisant ng-containerpour éviter de modifier le modèle.

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

Et la méthode dans le composant peut être quelque chose comme

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

Si vous avez besoin d'une prise en charge de la saisie semi-automatique de l'intérieur dans vos modèles du service de langage angulaire :

Synchrone:

myVar = { hello: '' };

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

Utilisation du tube asynchrone:

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

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

J'utilise angular 6x et j'ai fini par utiliser l'extrait ci-dessous. J'ai un scénario où je dois trouver un utilisateur à partir d'un objet de tâche. il contient un tableau d'utilisateurs mais je dois choisir l'utilisateur assigné.

<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>
La mécanique
la source
1

C'est beaucoup plus simple, pas besoin de rien de plus. Dans mon exemple, je déclare la variable «ouverte» puis je l'utilise.

   <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
la source
vous nommez une balise, ce n'est pas une déclaration de variable
Amirreza
1
@Amirreza, pour être précis, j'utilise ElementRef pour stocker temporairement une valeur.
Jack Rus
Impressionnant! J'ai dû utiliser "?"parce que j'avais le message "L'identifiant 'valeur' ​​n'est pas défini" comme ça => "open? .Value" Mais ça marche !!
A. Morel
1

J'ai aimé l'approche consistant à créer une directive pour ce faire (bon appel @yurzui).

J'ai fini par trouver un article Medium Directive "let" angulaire qui explique bien ce problème et propose une directive let personnalisée qui a fonctionné très bien pour mon cas d'utilisation avec des changements de code minimes.

Voici l'essentiel (au moment de la publication) avec mes modifications:

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

Mes principaux changements ont été:

  • changer le préfixe de 'ng' à 'app' (vous devez utiliser le préfixe personnalisé de votre application)
  • changer appLet: TenappLet: T | null

Je ne sais pas pourquoi l'équipe Angular n'a pas seulement fait une directive officielle ngLet, mais quoi.

Le crédit du code source original revient à @AustinMatherne

Keego
la source
C'était mon approche préférée sur la page et cela a fonctionné pour moi.
Skychan
1

Réponse courte qui aide quelqu'un

  • La variable de référence de modèle fait souvent référence à un élément DOM dans un modèle.
  • Référence également au composant angulaire ou Web et à la directive.
  • Cela signifie que vous pouvez facilement accéder à la variable n'importe où dans un modèle

entrez la description de l'image ici

entrez la description de l'image ici

  • Déclarer la variable de référence à l'aide du symbole de hachage (#)
  • Peut passer une variable en paramètre sur un événement

entrez la description de l'image ici

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

* Cependant, vous pouvez utiliser le décorateur ViewChild pour le référencer dans votre composant.

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

Référence variable firstNameInput à l'intérieur du composant

@ViewChild('firstNameInput') nameInputRef: ElementRef;

Après cela, vous pouvez utiliser this.nameInputRef n'importe où dans votre Component.

Travailler avec ng-template

Dans le cas de ng-template, c'est un peu différent car chaque template a son propre ensemble de variables d'entrée.

entrez la description de l'image ici

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

Mano
la source
1

Pour ceux qui ont décidé d'utiliser une directive structurelle en remplacement de *ngIf, gardez à l'esprit que le contexte de la directive n'est pas de type vérifié par défaut. Pour créer une ngTemplateContextGuardpropriété de directive de type sécurisé , vous devez ajouter, voir Saisie du contexte de la directive . Par exemple:

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

La directive peut être utilisée comme *ngIf, sauf qu'elle peut stocker de fausses valeurs:

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

Le seul inconvénient par rapport à *ngIfest que Angular Language Service ne peut pas déterminer le type de variable, il n'y a donc pas de complétion de code dans les modèles. J'espère que cela sera corrigé bientôt.

Valeriy Katkov
la source
Cela fonctionne, mais pas intellisense. J'utilise angular 8.
Tx_monster il y a