在Angular中,如何在模板中声明变量

我有以下模板:

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

并希望以:

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

有办法吗?

559348 次浏览

更新3

https://github.com/angular/angular/issues/2451问题在Angular 4.0.0中已修复

另请参阅

更新2

这是不支持的。

有模板变量,但不支持分配任意值。它们只能用于引用它们应用的元素,导出指令或组件的名称以及结构指令的作用域变量,如ngFor

另见https://github.com/angular/angular/issues/2451

更新1

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

然后像这样初始化

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

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

然后用这个变量

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

(未测试)

  • #aVariable创建对VarDirective (exportAs: 'var')的引用
  • var="abc"实例化VarDirective并将字符串值"abc"传递给它的值输入。
  • aVariable.var读取分配给var指令var输入的值。

丑,但是:

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

当与async管道一起使用时:

<div *ngFor="let a of [aVariable | async]">
<span>\{\{a.prop1}}</span>
<span>\{\{a.prop2}}</span>
</div>

下面是我编写的一个指令,它扩展了exportAs装饰器参数的使用,并允许您使用字典作为局部变量。

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

你可以在模板中使用它:

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

当然,#local可以是任何有效的局部变量名。

更新

我们可以创建类似*ngIf的指令,并将其命名为*ngVar

ng-var.directive.ts

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


if (!this.hasView) {
this.vcRef.createEmbeddedView(this.templateRef, this.context);
this.hasView = true;
}
}


private context: {
$implicit: unknown;
ngVar: unknown;
} = {
$implicit: null,
ngVar: null,
};


private hasView: boolean = false;


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

对于这个*ngVar指令,我们可以使用以下语句

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

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

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

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

< a href = " https://plnkr.co/edit/ytkvDD3NcKDegglFnjcU?Angular4 ngVar . p=preview" rel="noreferrer">活塞

另请参阅

原来的答案

角v4

  1. div + ngIf + let

    \{\{variable.a}} \{\{variable.b}}
  2. div + ngIf + as

视图

<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;
}
  1. 如果你不想创建像div这样的包装器,你可以使用ng-container

视图

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

正如@Keith在评论中提到的

这将在大多数情况下工作,但它不是一个通解,因为它 依赖于变量为真

有关另一种方法,请参阅update。

你可以在html代码中通过使用Angular 2中的template元素或Angular 4+中的ng-template元素来声明变量。

模板有一个上下文对象,它的属性可以使用let绑定语法分配给变量。注意,您必须为模板指定一个出口,但它可以是对模板本身的引用。

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


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

可以通过使用上下文对象的$implicit属性而不是自定义属性来减少代码量。

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

上下文对象可以是文字对象或任何其他绑定表达式。其他有效例子:

<!-- Use arbitrary binding expressions -->
<ng-template let-sum [ngTemplateOutletContext]="{ $implicit: 1 + 1 }">


<!-- Use pipes -->
<ng-template let-formatPi [ngTemplateOutletContext]="{ $implicit: 3.141592 | number:'3.1-5' }">


<!-- Use the result of a public method of your component -->
<ng-template let-root [ngTemplateOutletContext]="{ $implicit: sqrt(2116) }">


<!--
You can create an alias for a public property of your component:
anotherVariable: number = 123;
-->
<ng-template let-aliased [ngTemplateOutletContext]="{ $implicit: anotherVariable }">


<!--
The entire context object can be bound from a public property:
ctx: { first: number, second: string } = { first: 123, second: "etc" }
-->
<ng-template let-a="first" let-b="second" [ngTemplateOutletContext]="ctx">

我建议这样:https://medium.com/@AustinMatherne/angular-let-directive-a168d4248138

这个指令允许你这样写:

<div *ngLet="'myVal' as myVar">
<span> \{\{ myVar }} </span>
</div>

它简单得多,不需要任何额外的东西。在我的例子中,我声明变量“open”,然后使用它。

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

如果你想获取函数的响应并将其设置为变量,你可以像下面这样在模板中使用它,使用ng-container来避免修改模板。

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

组件中的方法可以是

methodName(parameters: any): any {
return {name: 'Test name'};
}

我使用的是angular 6x,我最终使用了下面的代码片段。 我有一个需要从任务对象中查找user的场景。它包含用户数组,但我必须选择分配的用户

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

我喜欢创建一个指令来做这件事的方法(很好的调用@yurzui)。

我最终找到了一篇媒体文章Angular的“let”指令,它很好地解释了这个问题,并提出了一个自定义让指令,它最终以最小的代码更改为我的用例工作得很好。

以下是要点(在发布时)和我的修改:

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

我的主要改变是:

  • 将前缀从'ng'更改为'app'(你应该使用你的应用程序的自定义前缀是什么)
  • appLet: T改为appLet: T | null

不知道为什么Angular团队还没有发布一个官方的ngLet指令。

原始源代码归功于@AustinMatherne

对某人有帮助的简短回答

    模板引用变量通常引用DOM元素 模板。李< / >
  • 也可以参考angular或web组件和指令。
  • 这意味着您可以轻松地访问模板中的任何地方的变量

enter image description here

enter image description here

  • 使用哈希符号(#)声明引用变量
  • 是否能够将变量作为事件的参数传递

enter image description here

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

但是,你可以使用ViewChild装饰器在你的组件中引用它。

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

在Component中引用firstNameInput变量

@ViewChild('firstNameInput') nameInputRef: ElementRef;

之后,你可以在组件中的任何地方使用this.nameInputRef。

使用ng-template

在ng-template的情况下,略有不同,因为每个模板都有自己的输入变量集

enter image description here

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

如果你需要Angular语言服务中模板内部的自动补全支持:

同步:

myVar = { hello: '' };


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

使用async管道:

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


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

对于那些决定使用结构指令替换*ngIf的人,请记住,默认情况下指令上下文不进行类型检查。要创建类型安全的指令,应该添加ngTemplateContextGuard属性,参见输入指令的上下文。例如:

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


该指令可以像*ngIf一样使用,除了它可以存储值:

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

*ngIf相比,唯一的缺点是Angular语言服务无法找出变量类型,因此模板中没有代码补全功能。我希望它能很快修好。

由于- 把angular 8应用迁移到angular 9时遇到了奇怪的问题, @yurzui的原始答案从Angular 9开始将无法工作。 然而,你仍然可以通过拥有ngVar指令并像

那样使用它来受益
<ng-template [ngVar]="variable">
your code
</ng-template>

尽管它可能会导致IDE警告:"变量未定义"

一个简单的解决方案可以满足我的需求:

 <ng-container *ngIf="lineItem.productType as variable">
\{\{variable}}
</ng-container>

 <ng-container *ngIf="'ANY VALUE' as variable">
\{\{variable}}
</ng-container>

我正在使用Angular版本:12。这似乎也适用于其他版本。

在Angular 12中:

  <div *ngIf="error$ | async as error">
<span class="text-warn">\{\{error.message}}</span>
</div>

我试图做一些类似的事情,看起来这已经在angular的更新版本中被修复。

    <div *ngIf="things.car; let car">
Nice \{\{ car }}!
</div>
<!-- Nice Honda! -->

我是https://www.npmjs.com/package/ng-let的作者

将数据作为局部变量共享到html组件模板的结构指令。

源代码:

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


interface NgLetContext<T> {
ngLet: T;
$implicit: T;
}


@Directive({
// tslint:disable-next-line: directive-selector
selector: '[ngLet]'
})
export class NgLetDirective<T> {


private context: NgLetContext<T | null> = { ngLet: null, $implicit: null };
private hasView: boolean = false;


// eslint-disable-next-line no-unused-vars
constructor(private viewContainer: ViewContainerRef, private templateRef: TemplateRef<NgLetContext<T>>) { }


@Input()
set ngLet(value: T) {
this.context.$implicit = this.context.ngLet = value;
if (!this.hasView) {
this.viewContainer.createEmbeddedView(this.templateRef, this.context);
this.hasView = true;
}
}


/** @internal */
public static ngLetUseIfTypeGuard: void;


/**
* Assert the correct type of the expression bound to the `NgLet` input within the template.
*
* The presence of this static field is a signal to the Ivy template type check compiler that
* when the `NgLet` structural directive renders its template, the type of the expression bound
* to `NgLet` should be narrowed in some way. For `NgLet`, the binding expression itself is used to
* narrow its type, which allows the strictNullChecks feature of TypeScript to work with `NgLet`.
*/
static ngTemplateGuard_ngLet: 'binding';


/**
* Asserts the correct type of the context for the template that `NgLet` will render.
*
* The presence of this method is a signal to the Ivy template type-check compiler that the
* `NgLet` structural directive renders its template with a specific context type.
*/
static ngTemplateContextGuard<T>(dir: NgLetDirective<T>, ctx: any): ctx is NgLetContext<Exclude<T, false | 0 | '' | null | undefined>> {
return true;
}
}

用法:

import { Component } from '@angular/core';
import { defer, Observable, timer } from 'rxjs';


@Component({
selector: 'app-root',
template: `
<ng-container *ngLet="timer$ | async as time"> <!-- single subscription -->
<div>
1: \{\{ time }}
</div>
<div>
2: \{\{ time }}
</div>
</ng-container>
`,
})
export class AppComponent {
timer$: Observable<number> = defer(() => timer(3000, 1000));
}