AngularDart4.0 指南- 模板语法二 顶

Class绑定

您可以使用Class绑定从元素的类属性添加和删除CSS类名称。

Class绑定语法类似于属性(property)绑定。 以前缀类开始,可选地跟一个点(.)和一个CSS类的名字替代括号内的元素属性:[class.class-name]。

以下示例显示如何使用class绑定来添加和删除应用程序的“special”类。 以下是如何设置没有绑定的属性:

<!-- standard class attribute setting  -->
<div class="bad curly special">Bad curly special</div>

你可以用一个绑定到所需的类名称的字符串替换它;这是一个全或无,替代绑定。

<!-- reset/override all class names with a binding  -->
<div class="bad curly special"
     [class]="badCurly">Bad curly</div>

你可以用一个绑定到所需的类名称的字符串替换它;这是一个全或无的替代绑定。

<!-- reset/override all class names with a binding  -->
<div class="bad curly special"
     [class]="badCurly">Bad curly</div>

最后,你可以绑定到一个specific类名。 当模板表达式计算结果为true时,Angular会添加类。 当表达式为false时,它将删除类。

<!-- toggle the "special" class on/off with a property -->
<div [class.special]="isSpecial">The class binding is special</div>

<!-- binding to `class.special` trumps the class attribute -->
<div class="special"
     [class.special]="!isSpecial">This one is not so special</div>

虽然这是切换单个类名的好方法,但是在同时管理多个类名时通常首选NgClass指令。

Style绑定

您可以使用Style绑定来设置内联样式。

样式绑定语法类似于属性(property)绑定。以前缀样式开始,后跟一个点(.)和一个CSS样式属性的名称代替括号内的元素属性,:[style.style-property]。

<button [style.color]="isSpecial ? 'red': 'green'">Red</button>
<button [style.background-color]="canSave ? 'cyan': 'grey'" >Save</button>

一些样式绑定样式有一个单位扩展名。 以下示例有条件地将字体大小设置为“em”和“%”单位。

<button [style.font-size.em]="isSpecial ? 3 : 1" >Big</button>
<button [style.font-size.%]="!isSpecial ? 150 : 50" >Small</button>

虽然这是设置单个样式的好方法,但是在同时设置多个内联样式时,通常首选NgStyle指令。 请注意,样式属性名称可以用dash-case(如上所示)或camelCase(如fontSize)书写。

样式属性命名 虽然在AngularDart中camelCasedash-case风格的属性命名方案是等价的,但只有dash-case命名法才能被dash:html包中CssStyleDeclaration的方法getPropertyValue()和setProperty()识别。 因此,使用样式属性名称的dash-case通常是首选。

事件绑定((event))

到目前为止,您所遇到的绑定指令可以在一个方向上流动数据:从一个组件到一个元素

用户不只是盯着屏幕。 他们在输入框中输入文字。 他们从列表中选择项目。 他们点击按钮。 这样的用户操作可能导致数据流向相反的方向:从元素到组件

了解用户操作的唯一方法是侦听某些事件,例如按键,鼠标移动,点击和触摸。 您通过Angular事件绑定声明您对用户操作的兴趣。

事件绑定语法由等号左边括号内的目标事件名称和右边带引号的模板语句组成。 以下事件绑定侦听按钮的单击事件,每当发生点击时调用组件的onSave()方法:

<button (click)="onSave()">Save</button>

目标事件

圆括号之间的名称 - 例如(click) - 标识目标事件。 在以下示例中,目标是按钮的单击事件。

<button (click)="onSave()">Save</button>

有些人更喜欢使用前缀on-替代方法,称为规范形式

<button on-click="onSave()">On Save</button>

元素事件可能是更常见的目标,但Angular首先查看名称是否匹配已知指令的事件属性,如下例所示:

<!-- `myClick` is an event on the custom `ClickDirective` -->
<div (myClick)="clickMessage=$event" clickable>click with myClick</div>

有关别名输入/输出属性的部分将进一步介绍myClick伪指令。

如果名称未能匹配已知指令的元素事件或输出属性,则Angular会报告“未知指令”错误。

$event和事件处理语句

在事件绑定中,Angular为目标事件设置了一个事件处理程序。

事件发生时,处理程序执行模板语句。 模板语句通常包含一个接收器,它响应事件执行一个动作,例如将HTML控件的值存储到模型中。

绑定通过一个名为$event的事件对象来传递关于该事件的信息,包括数据值。

事件对象的形状由目标事件决定。 如果目标事件是原生DOM元素事件,那么$event是一个DOM事件对象,具有诸如target和target.value的属性。

思考这个例子:

<input [value]="currentHero.name"
       (input)="currentHero.name=$event.target.value" >

此代码通过绑定到name属性来设置输入框value属性。 要监听值的更改,代码会绑定到输入框的输入事件。 当用户进行更改时,将引发输入事件,绑定在包含DOM事件对象$event的上下文中执行语句。

要更新name属性,可以通过路径$event.target.value来检索已更改的文本。

如果事件属于指令(回想组件是指令),则$event具有指令的所有能力。

自定义事件

指令通常使用StreamController来引发自定义事件。 该指令创建一个StreamController并将其stream作为属性公开。 该指令调用StreamController.add(payload)来触发一个事件,传递一个消息,可以是任何东西。 父指令通过绑定监听此属性并通过$event对象访问内容。payload:承载数据

考虑一个呈现英雄信息并响应用户操作的HeroDetailComponent。 虽然HeroDetailComponent有一个删除按钮,但不知道如何删除英雄本身。 最好的办法是触发一个事件,报告用户的删除请求。

以下是HeroDetailComponent的相关摘录:lib/src/hero_detail_component.dart (template)

template: '''
  <div>
    <img src="{{heroImageUrl}}">
    <span [style.text-decoration]="lineThrough">
      {{prefix}} {{hero?.name}}
    </span>
    <button (click)="delete()">Delete</button>
  </div>
''',

lib/src/hero_detail_component.dart (deleteRequest)

final _deleteRequest = new StreamController<Hero>();
@Output()
Stream<Hero> get deleteRequest => _deleteRequest.stream;

void delete() {
  _deleteRequest.add(hero);
}

组件定义了一个私有的StreamController属性,并通过deleteRequest获取器公开控制器的stream。 当用户点击Delete时,组件的delete()方法被调用,指示StreamController将Hero添加到stream中。

现在想象一个托管的父组件绑定到HeroDetailComponent的deleteRequest事件。

<hero-detail (deleteRequest)="deleteHero($event)" [hero]="currentHero"></hero-detail>

当触发deleteRequest事件时,Angular调用父组件的deleteHero方法,传递$event变量中的hero-to-delete(由HeroDetail发出)。

模板语句有附作用

deleteHero方法有一个附作用:删除一个英雄。 模板语句的附作用不只是好的,但可预期。

删除英雄更新模型,可能会触发其他更改,包括查询并保存到远程服务器。 这些变化通过系统渗透,并最终显示在相关视图。

双向绑定([(…)])

您经常希望显示数据属性,并在用户进行更改时更新该属性。

元素另一方面为元素更改事件组合设置特定元素属性和监听。

Angular为此提供了一个特殊的双向数据绑定语法, [(x)].  [(x)]语法将属性绑定的方括号[x]与事件绑定的圆括号(x)组合在一起。

[()] =香蕉盒 在一个盒子里形象化一个香蕉,记住圆括号在括号内。

当元素有一个名为x的可设置属性和一个名为xChange的对应事件时,[(x)]语法很容易演示。 这是一个适合模式的SizerComponent。 它有一个size值属性和一个伴随sizeChange事件:

lib/src/sizer_component.dart

import 'dart:async';
import 'dart:math';
import 'package:angular/angular.dart';
const _minSize = 8;
const _maxSize = _minSize * 5;
@Component(
  selector: 'my-sizer',
  template: '''
    <div>
      <button (click)="dec()" [disabled]="size <= minSize">-</button>
      <button (click)="inc()" [disabled]="size >= maxSize">+</button>
      <label [style.font-size.px]="size">FontSize: {{size}}px</label>
    </div>''',
)
class SizerComponent {
  // TODO: under Angular 4 we will be able to just export the const
  final int minSize = _minSize, maxSize = _maxSize;
  int _size = _minSize * 2;
  int get size => _size;
  @Input()
  void set size(/*String|int*/ val) {
    int z = val is int ? val : int.parse(val, onError: (_) => null);
    if (z != null) _size = min(maxSize, max(minSize, z));
  }
  final _sizeChange = new StreamController<int>();
  @Output()
  Stream<int> get sizeChange => _sizeChange.stream;
  void dec() => resize(-1);
  void inc() => resize(1);
  void resize(int delta) {
    size = size + delta;
    _sizeChange.add(size);
  }
}

初始size是来自属性绑定的输入值。 单击按钮可在最小/最大值限制内增加或减小size,然后用调整的大小触发(发出)sizeChange事件。

下面是一个示例,其中AppComponent.fontSizePx是双向绑定到SizerComponent的:

<my-sizer [(size)]="fontSizePx" #mySizer></my-sizer>
<div [style.font-size.px]="mySizer.size">Resizable Text</div>

AppComponent.fontSizePx建立初始SizerComponent.size的值。 单击按钮通过双向绑定更新AppComponent.fontSizePx。 修改后的size值流向样式绑定,使显示的文本变大或变小。

双向绑定语法实际上只是属性(property)绑定和事件绑定的语法糖。 Angular desugars将SizerComponent绑定到这里:

<my-sizer [size]="fontSizePx" (sizeChange)="fontSizePx=$event"></my-sizer>

$event变量包含SizerComponent.sizeChange事件的有效数据。 当用户单击按钮时,Angular将$event值分配给AppComponent.fontSizePx。

显然,与单独的属性和事件绑定相比,双向绑定语法相当方便。

使用HTML表单元素(如<input>和<select>)的双向绑定会很方便。 但是,没有原生HTML元素遵循x值和xChange事件模式。

幸运的是,Angular NgModel指令是一个使元素形成双向绑定的桥梁。

内置指令

早期版本的Angular包含了七十多个内置指令。 社区贡献了更多,并且为内部应用程序创建了无数私人指令。

在Angular中你不需要这些指令。 通常,您可以使用功能更强大,表现力更强的Angular绑定系统获得相同的结果。 当你可以写一个简单的绑定时为什么要创建一个指令来处理点击呢?

<button (click)="onSave()">Save</button>

您仍然可以从简化复杂任务的指令中受益。 Angular仍然附带内置的指令; 只是没有那么多。 你将会写你自己的指令,只是不多。

该部分回顾了一些最常用的内置指令,归类为属性(attribute)指令结构指令

内置的属性(attribute)指令

属性指令监听并修改其他HTML元素,属性(attribute),属性(property)和组件的行为。 它们通常应用于元素,就好像它们是HTML属性一样,因此也就是名称。

属性指令指南中介绍了许多细节。 许多Angular包(如RouterForms包)都定义了自己的属性指令。 本节介绍最常用的属性指令:

  • NgClass:添加和删除一组CSS类。
  • NgStyle:添加和删除一组HTML样式。
  • NgModel:双向数据绑定到HTML表单元素。

NgClass

您通常通过动态添加和删除CSS类来控制元素的显示方式。 你可以绑定到ngClass来同时添加或删除多个类。

class绑定是添加或删除单个类的好方法。

<!-- toggle the "special" class on/off with a property -->
<div [class.special]="isSpecial">The class binding is special</div>

要同时添加或删除许多CSS类,NgClass指令可能是更好的选择。

尝试绑定ngClass到一个key:value 控制Map。 对象的每个键都是一个CSS类的名字; 如果应该添加类,则其值为true,如果应该删除则为false。

考虑一个设置组件属性的组件方法setCurrentClasses,currentClasses,该对象基于三个其他组件属性的true / false状态添加或删除三个类:

Map<String, bool> currentClasses = <String, bool>{};
void setCurrentClasses() {
  currentClasses = <String, bool>{
    'saveable': canSave,
    'modified': !isUnchanged,
    'special': isSpecial
  };
}

将ngClass属性绑定添加到currentClasses,相应地设置元素的类:

<div [ngClass]="currentClasses">This div is initially saveable, unchanged, and special</div>

由您决定最初调用setCurrentClasses(),以及何时依赖属性发生更改。

NgStyle

您可以根据组件的状态动态设置内联样式。 使用NgStyle,您可以同时设置多个内联样式。 样式绑定是设置单个样式值的简单方法。

<div [style.font-size]="isSpecial ? 'x-large' : 'smaller'" >
  This div is x-large or smaller.
</div>

要同时设置多个内联样式,NgStyle指令可能是更好的选择。

尝试绑定ngStyle到一个key:value控制Map。 对象的每个键都是一个样式名称;它的值是适合那种样式。

考虑一个设置组件属性的组件方法setCurrentStyles,currentStyles,一个定义了三种样式的对象,基于另外三个组件属性的状态:

Map<String, String> currentStyles = <String, String>{};
void setCurrentStyles() {
  currentStyles = <String, String>{
    'font-style': canSave ? 'italic' : 'normal',
    'font-weight': !isUnchanged ? 'bold' : 'normal',
    'font-size': isSpecial ? '24px' : '12px'
  };
}

添加ngStyle属性绑定到currentStyles将相应地设置元素的样式:

<div [ngStyle]="currentStyles">
  This div is initially italic, normal weight, and extra large (24px).
</div>

由您决定最初调用setCurrentStyles(),以及何时依赖属性发生更改。

NgModel - 与[(ngModel)]形成元素的双向绑定

在开发数据输入表单时,通常都会显示数据属性,并在用户进行更改时更新该属性。

使用NgModel指令进行双向数据绑定使得这一切变得简单。 这是一个例子:

<input [(ngModel)]="currentHero.name">

里面[(ngModel)]

回顾一下名称绑定,请注意,您可以通过单独绑定<input>元素的value属性和输入事件来获得相同的结果。

<input [value]="currentHero.name"
       (input)="currentHero.name=$event.target.value" >

这很麻烦。 谁可以记住要设置哪个元素属性以及哪个元素事件发出用户更改? 如何从输入框中提取当前显示的文本,以便更新数据属性? 谁想每一次都看看?

该ngModel指令隐藏了自己的ngModel输入属性和ngModelChange输出属性背后的这些繁重的细节。

<input
  [ngModel]="currentHero.name"
  (ngModelChange)="currentHero.name=$event">

ngModel 数据属性设置元素的值属性,ngModelChange事件属性监听元素值的变化。 细节是特定于每种元素,因此NgModel指令只适用于ControlValueAccessor支持的元素以使元素适配这个协议。<input>框是其中的一个元素。 Angular为所有基本的HTML表单元素提供值访问器,Forms指南展示了如何绑定到它们。 您不能将[(ngModel)]应用到非表单原生元素或第三方自定义组件,除非您编写了一个合适的值存取器,这个技术超出了本指南的范围。 您不需要为您编写的Angular组件添加值存取器,因为您可以将值和事件属性命名为适合Angular基本的双向绑定语法,并完全跳过NgModel。上面显示sizer是这种技术的一个例子。

单独的ngModel绑定是对绑定到元素原生属性的改进。 你可以做得更好。

你不应该提到数据属性两次。 Angular应该能够捕获组件的数据属性,并使用[(ngModel)]语法将其设置为一个声明:

<input [(ngModel)]="currentHero.name">

[(ngModel)]是你需要的吗? 是否有理由回到扩展的形式?

[(ngModel)]语法只能设置数据绑定属性。 如果您需要做更多或不同的事情,您可以编写扩展表单。

以下人为的例子强制输入值为大写:

<input
  [ngModel]="currentHero.name"
  (ngModelChange)="setUppercaseName($event)">

这里有所有的变化,包括大写版本:

内置结构指令

结构指令负责HTML布局。 它们通常通过添加,删除和操作它们所连接的主机元素来对DOM的结构进行调整或重塑。

结构指令”指南介绍了结构指令的深入细节,您将在其中学习以下内容:

本节介绍常见的结构指令:

  • NgIf:有条件地从DOM中添加或删除元素。
  • NgFor:为列表中的每个项目重复一个模板。
  • NgSwitch:只显示多个可能元素中的一个。

NgIf 

您可以通过向该元素应用NgIf指令(称为宿主元素)来添加或移除DOM中的元素。 在此示例中,将指令绑定到条件表达式,如isActive。

<hero-detail *ngIf="isActive"></hero-detail>

不要忘记ngIf前面的星号(*)。

非true/false的值 当isActive表达式返回true值时,NgIf将HeroDetailComponent添加到DOM。 当表达式为false时,NgIf从DOM中删除HeroDetailComponent,销毁该组件及其所有子组件。 在Dart模式下,Dart期望布尔值(类型为bool的)为true或false。 即使在生产模式中,Dart唯一真实的是true, 所有其它值是false。 另一方面,TypeScript和JavaScript将许多值(包括非空对象)视为true。 例如,TypeScript Angular程序通常具有诸如* ngIf =“currentHero”的代码,其中Dart程序具有诸如* ngIf =“currentHero!= null”之类的代码。 将TypeScript代码转换为Dart代码时,请注意真/假问题。 例如,忘记!= null可能会导致检查模式中的异常,例如“EXCEPTION:type ‘Hero’ is not a subtype of type ‘bool’ of ‘boolean expression’”. 有关更多信息,请参阅Dart语言导览中的布尔值

Dart 2.0注意:检查模式不会在飞镖2.0。 有关更多信息,请参阅Dart 2.0更新

显示/隐藏不是一回事

您可以使用类或样式绑定来控制元素的可见性:

<!-- isSpecial is true -->
<div [class.hidden]="!isSpecial">Show with class</div>
<div [class.hidden]="isSpecial">Hide with class</div>

<!-- HeroDetail is in the DOM but hidden -->
<hero-detail [class.hidden]="isSpecial"></hero-detail>

<div [style.display]="isSpecial ? 'block' : 'none'">Show with style</div>
<div [style.display]="isSpecial ? 'none'  : 'block'">Hide with style</div>

隐藏一个元素与用NgIf去除一个元素是完全不同的。

当你隐藏一个元素时,该元素及其所有的后代仍然保留在DOM中。 这些元素的所有组件都保留在内存中,Angular可能会继续检查更改。 您的应用可能会占用相当可观的计算资源,会降低用户不可见的性能。

当NgIf为false时,Angular从DOM中删除元素及其后代。 它摧毁了他们的组件,潜在地释放了大量的资源,从而带来了更加快速的用户体验。

展示/隐藏技术适合少数几个后代的元素。 警惕隐藏大型组件树; NgIf可能是更安全的选择。

警惕null

ngIf指令通常用于防止null。 显示/隐藏是无用的。 如果嵌套表达式试图访问null属性,Angular会抛出一个错误。

这里我们看到NgIf守护两个<div>。 currentHero名称仅在有currentHero时出现。 nullHero从不显示。

<div *ngIf="currentHero != null">Hello, {{currentHero.name}}</div>
<div *ngIf="nullHero != null">Hello, {{nullHero.name}}</div>

另请参阅下面描述的安全导航操作符

NgFor

NgFor是一个迭代指令 - 一种呈现项目列表的方式。 您可以定义一个HTML块来定义应该如何显示单个项目。 您告诉Angular将该块用作呈现列表中每个项目的模板。

下面是NgFor应用于<div>的例子:

<div *ngFor="let hero of heroes">{{hero.name}}</div>

您也可以将NgFor应用于组件元素,如下例所示:

<hero-detail *ngFor="let hero of heroes" [hero]="hero"></hero-detail>

不要忘记ngFor前面的星号(*)。

分配给* ngFor的文本是指导迭代器进程的指令。

*ngFor微语法

分配给* ngFor的字符串不是模板表达式。 这是一种微语法 - Angular解释的一种小语言。 字符串“let hero of heroes”是指:

取英雄列表中的每个英雄,将其存储在本地英雄循环变量中,并使其可用于每次迭代的模板HTML。

Angular把这条指令翻译成一个围绕宿主元素的<template>,然后重复使用这个模板为列表中的每个英雄创建一组新的元素和绑定。

在“结构指令”指南中了解微语法。

模板输入变量

hero之前的let关键字创建一个名为hero的模板输入变量。 ngFor指令迭代由父组件的heroes属性返回的heroes,并在每次迭代期间将hero设置为列表中的当前项目。

要访问hero的属性,请参考ngFor宿主元素(或其后代内)中的hero输入变量。在这里,英雄首先在插值中被引用,然后传递给<hero-detail>组件的hero属性绑定。

<div *ngFor="let hero of heroes">{{hero.name}}</div>
<hero-detail *ngFor="let hero of heroes" [hero]="hero"></hero-detail>

在“结构指令”指南中了解有关模板输入变量的更多信息。

* ngFor与index(索引)

NgFor指令上下文的index属性返回每个迭代中项目的从零开始的索引。 您可以捕获模板输入变量中的index,并在模板中使用它。

下一个示例捕获名为i的变量中的索引,并使用像这样的英雄名称来显示它。

<div *ngFor="let hero of heroes; let i=index">{{i + 1}} - {{hero.name}}</div>

了解其他NgFor上下文值,例如NgFor API参考中的last,even和odd。

*ngFor和trackBy

NgFor指令可能表现不佳,特别是在大型列表中。 对一个项目,删除项目或添加项目的小改动可以触发DOM操作的级联。

例如,重新查询服务器可能会重置所有新的英雄对象的列表。

大多数,如果不是全部,以前显示的英雄。 你知道这一点,因为每个英雄的ID没有改变。 但是Angular只能看到新的对象引用列表。 它别无选择,只能拆除旧的DOM元素并插入所有新的DOM元素。

Angular可以通过trackBy避免这种流失。 向组件添加一个返回NgFor应跟踪值的方法。 在这个例子中,这个值就是英雄的ID。

int trackByHeroes(int index, Hero hero) => hero.id;

在微语法表达式中,将trackBy设置为此方法。

<div *ngFor="let hero of heroes; trackBy: trackByHeroes">
  ({{hero.id}}) {{hero.name}}
</div>

这是trackBy效果的一个例子。 “Reset heroes”用相同的 hero.ids创建新的heroes。 “Change ids”用新的hero.ids创造新的heroes 。

  • 没有trackBy,这两个按钮都会触发完整的DOM元素替换。
  • 有了trackBy,只有更改id触发器元素替换。

NgSwitch指令

NgSwitch就像Dart switch语句。 它可以根据切换条件从几个可能的元素中显示一个元素。 Angular只把选中的元素放入DOM中。

NgSwitch实际上是一组三个协作指令:NgSwitch,NgSwitchCase和NgSwitchDefault,如本例所示。

<div [ngSwitch]="currentHero.emotion">
  <happy-hero    *ngSwitchCase="'happy'"    [hero]="currentHero"></happy-hero>
  <sad-hero      *ngSwitchCase="'sad'"      [hero]="currentHero"></sad-hero>
  <confused-hero *ngSwitchCase="'confused'" [hero]="currentHero"></confused-hero>
  <unknown-hero  *ngSwitchDefault           [hero]="currentHero"></unknown-hero>
</div>

您可能会遇到旧代码中的NgSwitchWhen指令。 这是NgSwitchCase的弃用名称。

NgSwitch是控制器指令。将其绑定到返回switch值的表达式。本例中的emotion值是一个字符串,但是switch值可以是任何类型。

绑定到[ngSwitch]。 如果您尝试设置*ngSwitch,则会出现错误,因为NgSwitch是一个属性指令,而不是结构指令。 它改变了其同伴指令的行为。 它不直接操作DOM。

绑定到*ngSwitchCase和*ngSwitchDefault。 NgSwitchCase和NgSwitchDefault指令是结构指令,因为它们添加或删除DOM中的元素。

  • NgSwitchCase在其绑定值等于交换机值时将其元素添加到DOM。
  • 当没有选择NgSwitchCase时,NgSwitchDefault将其元素添加到DOM。

switch指令对于添加和删除组件元素特别有用。本示例在hero_switch_components.dart文件中定义的四个“emotional hero”组件之间进行切换。每个组件都有一个绑定到父组件的currentHero的英雄输入属性。

switch指令也适用于原生元素和Web组件。 例如,您可以使用以下代替<confused-hero>switch选项。

<div *ngSwitchCase="'confused'">Are you as confused as {{currentHero.name}}?</div>

模板引用变量(#var)

模板引用变量通常是对模板内DOM元素的引用。 它也可以是对Angular组件或指令或Web组件的引用。

使用hash符号(#)来声明一个引用变量。 #phone在<input>元素上声明了一个phone变量。

<input #phone placeholder="phone number">

您可以参考模板中任何位置的模板引用变量。 在<input>上声明的phone变量在模板另一端的<button>中被使用

<input #phone placeholder="phone number">

<!-- lots of other elements -->

<!-- phone refers to the input element; pass its `value` to an event handler -->
<button (click)="callPhone(phone.value)">Call</button>

如何获得引用变量的值

在大多数情况下,Angular将引用变量的值设置为声明的元素。在前面的例子中, phone是指电话号码 <input>框。电话按钮点击处理程序将输入值传递给组件的callPhone方法。但是一个指令可以改变这种行为,并将其值设置为别的东西,比如本身。 NgForm指令这样做。

以下是Forms指南中表单示例的简化版本。

<form (ngSubmit)="onSubmit(heroForm)" #heroForm="ngForm">
    <div class="form-group">
        <label for="name">Name
            <input class="form-control"
                   ngControl="name"
                   required
                   [(ngModel)]="hero.name">
        </label>
    </div>
    <button type="submit" [disabled]="!heroForm.form.valid">Submit</button>
</form>
<div [hidden]="!heroForm.form.valid">
    {{submitMessage}}
</div>

在这个例子中,一个模板引用变量heroForm出现三次,被大量的HTML隔开。heroForm的值是什么? heroForm是一个Angular NgForm指令的引用,可以跟踪表单中每个控件的值和有效性。

原生<form>元素没有form属性。 但是NgForm指令有,它解释了如果heroForm.form.valid无效并且将整个表单控件树传递给父组件的onSubmit方法,您可以禁用提交按钮。

模板引用变量警告说明

模板引用变量(#phone)与模板输入变量(let phone)不同,如您在*ngFor中可能看到的那样。 了解“结构指令”指南中的差异。

引用变量的范围是整个模板。 不要在同一模板中多次定义相同的变量名称。 运行时值将是不可预知的。

你可以使用ref-前缀替代#。 本示例将fax变量声明为ref-fax,而不是#fax。

<input ref-fax placeholder="fax number">
<button (click)="callFax(fax.value)">Fax</button>

输入和输出属性(@Input和@Output)

到目前为止,该页面主要集中在绑定到模板表达式中的组件成员以及出现在绑定声明右侧的语句上。 该位置的成员是数据绑定

本节重点讨论对目标的绑定,它们是绑定声明左侧的指令属性。这些指令属性必须声明为输入输出

请记住:所有组件都是指令。

请注意数据绑定目标和数据绑定之间的重要区别。 绑定的目标是在=的左边。 源位于=的右侧。 绑定的目标是绑定标点符号中的属性或事件:[],()或[()]。 源是在引号(“”)内或插值({{}})内。 source指令的每个成员都可以自动获得绑定。 您不必在模板表达式或语句中使用任何特殊的操作来访问指令成员。 您对目标指令的成员访问权限有限。 您只能绑定到明确标识为输入和输出的属性。

在下面的代码片段中,iconUrl和onSave是AppComponent的数据绑定成员,并且在等号(=)右侧的引用语法中被引用。

<img [src]="iconUrl"/>
<button (click)="onSave()">Save</button>

它们既不是输入也不是输出。 他们是绑定的来源。 目标是本地的<img>和<button>元素。

现在看另一个片段,其中HeroDetailComponent是equals(=)左边绑定的目标。

<hero-detail [hero]="currentHero" (deleteRequest)="deleteHero($event)">
</hero-detail>

HeroDetailComponent.hero和HeroDetailComponent.deleteRequest都在绑定声明的左侧。 HeroDetailComponent.hero在括号内; 它是一个属性绑定的目标. HeroDetailComponent.deleteRequest在括号内; 它是事件绑定的目标。

声明输入和输出属性

目标属性必须明确标记为输入或输出。

在HeroDetailComponent中,这些属性使用注解标记为输入或输出属性。

@Input()
Hero hero;
final _deleteRequest = new StreamController<Hero>();
@Output()
Stream<Hero> get deleteRequest => _deleteRequest.stream;

输入还是输出?

input属性通常接收数据值。 Output属性公开事件生成器,如Stream对象。

术语inputOutput反映了目标指令的视角。

HeroDetailComponent.hero是HeroDetailComponent角度的输入属性,因为数据从模板绑定表达式流入该属性。

HeroDetailComponent.deleteRequest是从HeroDetailComponent角度来看的一个输出属性,因为在模板绑定语句中,事件流出该属性并处理该处理程序。

别名输入/输出属性

有时输入/输出属性的公共名称应与内部名称不同。

属性指令通常是这种情况。指令消费者希望绑定到指令的名称。 例如,当您使用myClick选择器将指令应用于<div>标记时,您希望绑定到的事件属性也称为myClick。

<div (myClick)="clickMessage=$event" clickable>click with myClick</div>

但是,指令名称是指令类中属性名称通常来说是一个糟糕的选择。 指令名很少描述属性的作用。 myClick指令名称对于发出点击消息的属性不是一个好名字。

幸运的是,您可以创建符合常规期望的属性的公共名称,同时在内部使用不同的名称。 在上面的示例中,代码通过myClick别名绑定到指令自己的click属性。

要为属性名称指定别名,请将别名传递到输入/输出装饰器,如下所示:

final _onClick = new StreamController<String>();
// @Output(alias) propertyName = ...
@Output('myClick')
Stream<String> get clicks => _onClick.stream;

模板表达式运算符

模板表达式语言使用Dart语法的一个子集,辅以几个特殊的运算符,用于特定的场景。接下来的部分将介绍这些操作符中的两个:管道安全导航操作符。

管道操作符(|)

在准备使用绑定之前,表达式的结果可能需要进行一些转换。 例如,您可以将数字显示为货币,强制文本为大写,或筛选列表并对其进行排序。

对于这些小型转换来说,Angular 管道是一个很好的选择。 管道是简单的函数,它接受一个输入值并返回一个转换后的值。 使用管道运算符(|),它们很容易在模板表达式中应用:

<div>Title through uppercase pipe: {{title | uppercase}}</div>

管道运算符将左边表达式的结果传递给右边的管道函数。

您可以通过多个管道链接表达式:

<!-- Pipe chaining: convert title to uppercase, then to lowercase -->
<div>
  Title through a pipe chain:
  {{title | uppercase | lowercase}}
</div>

您也可以将参数应用于管道:

<!-- pipe with configuration argument => "February 25, 1970" -->
<div>Birthdate: {{currentHero?.birthdate | date:'longDate'}}</div>

json管道可以帮助调试绑定:

<div>{{currentHero | json}}</div>

生成的输出如下所示:

{ "id": 0, "name": "Hercules", "emotion": "happy",
  "birthdate": "1970-02-25T08:00:00.000Z",
  "url": "http://www.imdb.com/title/tt0065832/",
  "rate": 325 }

安全导航运算符(?.)和null属性路径

Angular安全导航运算符(?.)与Dart条件成员访问运算符一样,是防止属性路径中的空值的便利方法。 在这里,如果currentHero为空,则防止视图呈现失败。

The current hero's name is {{currentHero?.name}}

当以下数据绑定title属性为null时会发生什么?

The title is {{title}}

视图仍然呈现,但显示的值是空白的; 你只看到“The title is”没有任何东西。 这是合理的行为。 至少该应用程序不会崩溃。

假设模板表达式涉及一个属性路径,就像在下一个例子中显示一个空的英雄的name一样。

The null hero's name is {{nullHero.name}}

Dart抛出异常,Angular也抛出异常:

EXCEPTION: The null object does not have a getter 'name'.

更糟的是,整个视图消失。

如果hero属性不能为空,这将是合理的行为。 如果它永远不能为空,但它是空的,这是一个应该被捕获和修复的编程错误。 抛出异常是正确的。

另一方面,属性路径中空值时不时出现可能还好,特别是当数据现在为空,将来将返回数据。

在等待数据的时候,视图应该没有怨言地呈现,而null属性路径应该像title属性一样显示为空白。

不幸的是,当currentHero为空时,应用程序崩溃。

你可以用*ngIf来解决这个问题。

<!--No hero, div not displayed, no error -->
<div *ngIf="nullHero != null">The null hero's name is {{nullHero.name}}</div>

这些方法有好处,但可能会是累赘,特别是如果属性路径很长。 想象一下,在诸如a.b.c.d这样的长属性路径中的某个地方防止空值。

Angular安全导航操作符(?.)是一种更为流畅和方便的方法来防止在属性路径中出现空。表达式在达到第一个空值时会被释放。 显示器是空白的,但应用程序保持滚动没有错误。

<!-- No hero, no problem! -->
The null hero's name is {{nullHero?.name}}

它适用于很长的属性路径,如a?.b?.c?.d。

概要

您已经完成了对模板语法的概览。 现在是时候把这些知识应用到你自己的组件和指令上。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏河湾欢儿的专栏

Vue.js基础特性

计算属性 computed 与data,el,methods属性一样,都是vm实例的属性(选项) 理解其大致意思即可

15410
来自专栏Google Dart

AngularDart Material Design 输入 顶

Selector: <material-input:not(material-input[multiline])>

14540
来自专栏小蠢驴iOS专题

iOS中纯代码创建的UI控件使用weak还是strong

22940
来自专栏开源项目

Vue 2.0 学习总结,精华全在这里了

摘要:年后公司项目开始上vue2.0,自己对学习进行了总结,希望对大家有帮助! 1Vue 介绍 Vue 是什么? https://vuefe.cn/guide ...

320110
来自专栏PHP在线

jQuery学习笔记

jQuery大部分功能需要根据文档的DOM模型来工作,首先需要正确地解析到整个文档的DOM模型结构。使用jQuery需要在整个文档被浏览器完全加载后才开始进行。

11520
来自专栏HTML5学堂

2016.07 第2周 群问题分享

HTML+CSS 移动端中1px的边框如何实现 2016.07.04~2016.07.08 核心概念: viewport、CSS3属性 参考答案: 一、通过设置...

29860
来自专栏极客生活

数据分析Excel技能之自动填充

将光标移动到选中的单元格的右下角的那个节点上光标会变成实心加号。然后可以上下左右拖动光标就会自动填充当前单元格中的内容。 根据当前单元格中的内容格式不同,ex...

11630
来自专栏IMWeb前端团队

Zepto源码分析之form模块

前言 JavaScript最初的一个应用场景就是分担服务器处理表单的责任,打破处处依赖服务器的局面,这篇文章主要介绍zepto中form模块关于表单处理的几个...

212100
来自专栏前端说吧

JS-DOM2级封装练习题--点击登录弹出登录对话框

34670
来自专栏web前端教室

WEB前端零基础课-1022本周总结

v-if,根据true或是false,来决定是否插入到页面当中,dom节点,不在页面中

11510

扫码关注云+社区

领取腾讯云代金券