Cómo declarar una variable en una plantilla en angular

203

Tengo la siguiente plantilla:

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

y me gustaría terminar con:

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

¿Hay alguna forma de hacerlo?

Scipion
fuente
¿Estoy interesado en saber cuál es el requisito / caso de uso para querer cambiar el nombre de un parámetro de enlace como este ejemplo?
LDJ
31
Es solo para evitar repetir algo como tab [elemento] .val por instancia. Sé que puedo resolver el problema en el componente, pero solo estaba mirando cómo hacerlo en la plantilla (aunque no pueda terminar con esa solución).
Scipion
2
@LDJ un caso de uso de muestra: eficiencia. Use la muestra de stackblitz.com/angular/… <mat-checkbox [check] = "descendantsAllSelected (node)" [indeterminate] = "descendantsPartiallySelected (node)" (change) = "todoItemSelectionToggle (node)"> {{node. item}} </mat-checkbox> de hecho, los descedantsPartiallySelected () llama a descedantsAllSelected (). Significa que en algún momento descendientesAllSelected se llama dos veces. Si hay una variable local, esto se puede evitar.
Steven.Xi
3
<div *ngIf="{name:'john'} as user1; let user"> <i>{{user1|json}}</i> <i>{{user|json}}</i> </div>
dasfdsa
@dasfdsa, creo user1 === user, así lo haces *ngIf="{name:'john'} as user1o *ngIf="{name:'john'};let usercomo en la respuesta de yurzui .
CPHPython

Respuestas:

175

Actualizar

Podemos simplemente crear directivas como *ngIfy llamarlo*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);
  }
}

con esta *ngVardirectiva podemos usar lo siguiente

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

o

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

o

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

o

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

Ejemplo de Plunker Angular4 ngVar

Ver también

Respuesta original

V4 angular

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

ver

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

componente.ts

export class AppComponent {
  x = 5;
}

3) Si no quieres crear un contenedor como divpuedes usarng-container

ver

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

Como @Keith mencionó en los comentarios

esto funcionará en la mayoría de los casos, pero no es una solución general ya que depende de que la variable sea verdadera

Ver actualización para otro enfoque.

yurzui
fuente
10
Esto funcionará en la mayoría de los casos, pero no es una solución general ya que depende de variableestar Truthy
Keith
66
@Keith Gracias por señalar esto. Puedes echar un vistazo a mi respuesta actualizada
yurzui
3
Esta debería ser la nueva respuesta, ya que 1) es más moderna que la otra solución 2) resume las solicitudes de extracción vinculadas en la respuesta actual y ahorra mucho tiempo 3) incluye los ejemplos en línea en lugar de por enlace externo. Gracias por mostrar esto. AFAIK cualquier objeto envuelto {}se evaluará como verdadero, por lo que esta solución es bastante robusta.
kvanberendonck
44
Por ejemplo, botones expandibles: *ngIf="{ expanded: false } as scope"y luego, si está usando bootstrap, puede usar [ngClass]="{ 'in': scope.expanded }"y en (click)="scope.expanded = !scope.expanded"lugar de agregar cualquier cosa a sus archivos js/ ts.
kvanberendonck
1
problema relacionado con github (señala el uso de un simple *ngIfngvar en lugar de cosas personalizadas): github.com/angular/angular/issues/14985
phil294
81

Feo, pero:

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

Cuando se usa con tubería asíncrona:

<div *ngFor="let a of [aVariable | async]">
  <span>{{a.prop1}}</span>
  <span>{{a.prop2}}</span>
</div>
kayjtea
fuente
44
Ese es el que se me ocurrió por instinto, también funciona *ngFor="let a of [(someStream$ | async).someA]. ¡Supongo que si se usa con un <ng-container>bien, funciona bien!
Angelos Pikoulas
2
En este caso *ngFor, tenga en cuenta que todo el contenido anidado se volverá a crear si el valor de la variable cambia, hasta que especifique una trackByfunción que devuelva la misma identificación para todos los valores.
Valeriy Katkov
76

Puede declarar variables en código html utilizando un templateelemento en Angular 2 o ng-templateen Angular 4+.

Las plantillas tienen un objeto de contexto cuyas propiedades pueden asignarse a variables mediante letla sintaxis de enlace. Tenga en cuenta que debe especificar una salida para la plantilla, pero puede ser una referencia a sí misma.

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

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

Puede reducir la cantidad de código utilizando la $implicitpropiedad del objeto de contexto en lugar de una propiedad personalizada.

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

El objeto de contexto puede ser un objeto literal o cualquier otra expresión vinculante. Incluso las tuberías parecen funcionar cuando están rodeadas de paréntesis.

Ejemplos válidos de ngTemplateOutletContext:

  • [ngTemplateOutletContext]="{ aVariable: 123 }"
  • [ngTemplateOutletContext]="{ aVariable: (3.141592 | number:'3.1-5') }"
  • [ngTemplateOutletContext]="{ aVariable: anotherVariable }" usar con let-a="aVariable"
  • [ngTemplateOutletContext]="{ $implicit: anotherVariable }" usar con let-a
  • [ngTemplateOutletContext]="ctx"donde ctxes una propiedad publica
Steven Liekens
fuente
Para que funcione, tuve que cambiar su código de '<template ...' a '<ng-template ...'.
Humppakäräjät
2
Sí, solo puede usar <template>en Angular 2. Puede usar uno <template>o <ng-template>en Angular 4, pero solo debe usar <ng-template>. Angular 5 dejó caer el soporte para <template>.
Steven Liekens
¿Para qué sirve t?
matttm
1
@matttm #tes una variable de plantilla que almacena el ng-template. Se utiliza [ngTemplateOutlet]="t"para hacer que la plantilla ng haga referencia a sí misma.
Steven Liekens
Esto es extraño, ¡pero funciona! Angular debería hacerlo más simple, con una directiva variable incorporada. Gracias.
TetraDev
57

actualización 3

El problema 2451 se corrige en Angular 4.0.0

Ver también

actualización 2

Esto no es compatible.

Hay variables de plantilla pero no es compatible para asignar valores arbitrarios. Solo se pueden usar para referirse a los elementos a los que se aplican, los nombres exportados de directivas o componentes y las variables de alcance para directivas estructurales como ngFor,

Ver también https://github.com/angular/angular/issues/2451

Actualización 1

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

e inicializarlo como

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

o

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

y usar la variable como

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

(no probado)

  • #aVariablecrea una referencia a VarDirective( exportAs: 'var')
  • var="abc"crea una instancia de VarDirectivey pasa el valor de la cadena "abc"a su entrada de valor.
  • aVariable.varlee el valor asignado a la entrada de vardirectivas var.
Günter Zöchbauer
fuente
¿No sería posible crear una directiva estructural para hacerlo?
Scipion
Si necesita esto repetidamente, entonces una directiva podría hacer lo que quiera. Una directiva estructural crea su propia vista, eso probablemente no sea lo que desea.
Günter Zöchbauer
1
@ GünterZöchbauer, muy buenas cosas. Sé que probablemente sea una mejor práctica tener variables calculadas / preparadas en el component.tsarchivo. Pero es mucho más fácil para mí tenerlos a la vista en algunos casos debido a un esquema de sincronización que estoy implementando en toda mi aplicación. Aprovecho las reglas de referencia de JavaScript cuando diferentes variables apuntan al mismo objeto.
AmmarCSE
Me aparece un error como There is no directive with "exportAs" set to "var". ¿Alguien puede decirme qué error cometí? He usado la directiva anterior.
Partha Sarathi Ghosh
Quizás no agregaste la directiva declarations: [...]de @NgModule(). Si este no es el problema, cree una nueva pregunta y proporcione el código que permita diagnosticar el problema.
Günter Zöchbauer
11

Aquí hay una directiva que escribí que amplía el uso del parámetro decorador exportAs y le permite usar un diccionario como una variable local.

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

Puede usarlo de la siguiente manera en una plantilla:

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

Por supuesto, #local puede ser cualquier nombre de variable local válido.

Aaron
fuente
No pasa una compilación de 'producción' tal cual (también se muestra como errores por IDE). Agregue [key: string]: any;al Classpara evitar esto.
Charly
7

En caso de que desee obtener la respuesta de una función y establecerla en una variable, puede usarla de la siguiente manera en la plantilla, ng-containerpara evitar modificar la plantilla.

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

Y el método en el componente puede ser algo como

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

Si necesita soporte para autocompletar desde dentro en sus plantillas del Servicio de lenguaje angular :

Sincrónico:

myVar = { hello: '' };

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

Usando tubería asíncrona:

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

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

Estoy usando angular 6x y terminé usando el fragmento a continuación. Tengo un escenario donde tengo que encontrar usuarios de un objeto de tarea. Contiene una variedad de usuarios, pero tengo que elegir el usuario asignado.

<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>
El mecánico
fuente
1

Es mucho más simple, no necesita nada adicional. En mi ejemplo, declaro la variable "abrir" y luego la uso.

   <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
fuente
si nombra una etiqueta, no está declarando variable
Amirreza
1
@Amirreza, para ser precisos, estoy usando ElementRef para almacenar temporalmente un valor.
Jack Rus
¡Increíble! Tuve que usar "?"porque tenía el mensaje "El identificador 'valor' no está definido" como este => "abrir? .Valor" ¡Pero está funcionando!
A. Morel
1

Me gustó el enfoque de crear una directiva para hacer esto (buena llamada @yurzui).

Terminé encontrando un artículo medio de la Directiva angular "let" que explica muy bien este problema y propone una directiva let personalizada que terminó funcionando muy bien para mi caso de uso con cambios mínimos de código.

Aquí está la esencia (en el momento de la publicación) con mis modificaciones:

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

Mis principales cambios fueron:

  • cambiar el prefijo de 'ng' a 'aplicación' (debe usar el prefijo personalizado de su aplicación)
  • cambiando appLet: TaappLet: T | null

No estoy seguro de por qué el equipo de Angular no acaba de hacer una directiva ngLet oficial, sino todo lo demás.

El crédito del código fuente original va a @AustinMatherne

Keego
fuente
Este fue mi enfoque favorito en la página y funcionó para mí.
Skychan
1

Respuesta corta que ayuda a alguien

  • La variable de referencia de plantilla a menudo hace referencia al elemento DOM dentro de una plantilla.
  • También referencia a componente angular o web y directiva.
  • Eso significa que puede acceder fácilmente a la variable en cualquier lugar de una plantilla

ingrese la descripción de la imagen aquí

ingrese la descripción de la imagen aquí

  • Declarar la variable de referencia usando el símbolo hash (#)
  • Puede pasar una variable como parámetro en un evento

ingrese la descripción de la imagen aquí

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

* Sin embargo, puede usar el decorador ViewChild para referenciarlo dentro de su componente.

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

Referencia firstNameInput variable dentro de Component

@ViewChild('firstNameInput') nameInputRef: ElementRef;

Después de eso, puede usar this.nameInputRef en cualquier lugar dentro de su Componente.

Trabajando con ng-template

En el caso de ng-template, es un poco diferente porque cada plantilla tiene su propio conjunto de variables de entrada.

ingrese la descripción de la imagen aquí

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

Mano
fuente
1

Para aquellos que decidieron usar una directiva estructural como reemplazo de *ngIf, tenga en cuenta que el contexto de la directiva no está marcado de forma predeterminada. Para crear una ngTemplateContextGuardpropiedad de directiva de tipo seguro , debe agregarse, consulte Escribir el contexto de la directiva . Por ejemplo:

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 directiva se puede usar igual que *ngIf, excepto que puede almacenar valores falsos :

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

El único inconveniente en comparación con *ngIfes que Angular Language Service no puede determinar el tipo de variable, por lo que no hay finalización de código en las plantillas. Espero que se solucione pronto.

Valeriy Katkov
fuente
Esto funciona pero intellisense no. Estoy usando angular 8.
Tx_monster