好得很程序员自学网

<tfoot draggable='sEl'></tfoot>

Angular 5.x 学习笔记(1) - 模板语法

Angular 5.x 模板语法学习笔记

标签(空格分隔): Angular

Note on github.com

上手

官网Heroes 例子: Demo On Github 。

一、模板与数据绑定

1. 显示数据

selector 选择自定义标签的名称。

template 属性定义内联模板; templateUrl 属性定义外链模板。

值的声明和初始化。

 export class AppComponent {
    // 变量赋值的方式初始化组件
    // title = 'Tour of Heroes';
    // myHero = 'Vencent';
    title: string;
    myHero: string;
    Heroes = ['Windstorm', 'Vencent', 'Bombasto', 'Magneta', 'Tornado',];

    // 构造函数来声明和初始化属性
    constructor() {
      this.title = 'Tour of Heroes';
      this.myHero = this.Heroes[1];
    }
}
 

{{ [data] }} 插值表达式 (interpolation) ,模板绑定属性;
*ngFor="let [item] of [list]" 模板循环,ngFor
可以为任何 可迭代的 (iterable)  对象重复渲染条目;
ngIf
指令会根据一个布尔条件来显示或移除一个元素, *ngIf="[condition]" 。具体参见下一节(模板语法)。

实体类的声明。

 export class Hero {
   constructor(
   public id: number,
   public name: string) { }
}
 

public id: number,

参数作为属性的简写语法:

声明了一个构造函数参数及其类型 声明了一个同名的公共属性 new 出该类的一个实例时,把该属性初始化为响应的参数值 2. 模板语法

HTML是Angular模板的语言
HTML是Angular模板的语言; <script> 元素,它被禁用(忽略)了,以阻止脚本注入攻击的风险: 安全 。

插值表达式

{{...}}

 // 作为文本
<!-- "The sum of 1 + 1 is 2" -->
<p>The sum of 1 + 1 is {{1 + 1}}</p>
 
 // 作为属性值 image = img,由于此md编辑器会吞掉img标签
<image src="{{heroImageUrl}}" style="height:30px">
 

模板表达式

JavaScript 中那些具有或可能引发副作用的表达式是被禁止的:

赋值( = , += , -= ,...) new 运算符 使用 ; 或 , 的链式表达式 自增或自减操作符( ++ 和 -- ) 不支持位运算 | 和 & 具有新的 模板表达式运算符 ,比如 | 、 ?. 和 !

3.1 表达式上下文

典型的 表达式上下文 就是这个组件实例,它是各种绑定值的来源。
表达式中的上下文变量是由 模板变量 、 指令的上下文变量(如果有) 和 组件的成员 叠加而成的。 如果我们要引用的变量名存在于一个以上的命名空间中,那么, 模板变量是最优先的,其次是指令的上下文变量,最后是组件的成员 。

模板语句

模板语句用来响应由绑定目标(如 HTML 元素、组件或指令)触发的事件。

 <button (click)="deleteHero()">Delete hero</button>
 

模板语句解析器支持基本赋值 ( = ) 和表达式链 ( ; 和 , )。
模板语句禁止以下:

new 运算符 自增或自减操作符( ++ 和 -- ) 操作并赋值( += , -= ,...) 位操作符 | 和 & 模板表达式运算符 ,比如 | 、 ?. 和 !

4.1 语句上下文

典型的语句上下文就是当前组件的实例。

 <button (click)="onSave($event)">Save</button>
<button *ngFor="let hero of heroes" (click)="deleteHero(hero)">{{hero.name}}</button>
<form #heroForm (ngSubmit)="onSubmit(heroForm)"> ... </form>
 

模板上下文中的变量名的优先级高于组件上下文中的变量名 。在上面的 deleteHero(hero) 中, hero 是一个模板输入变量,而不是组件中的 hero 属性。

模板语句不能引用全局命名空间的任何东西。比如不能引用 window 或 document ,也不能调用 console.log 或 Math.max 。

绑定语法

数据方向 语法 绑定类型
单向
从数据源
到视图目标
{{expression}}
[target]="expression"
bind-target="expression"
插值表达式
Property
Attribute

样式
单向
从视图目标
到数据源
(target)="statement"
on-target="statement"
事件
双向 [(target)]="expression"
bindon-target="expression"
双向
HTML attribute value 指定了初始值; DOM value property 是当前值。 模板绑定是通过 property 和事件来工作的,而不是 attribute 。 插值表达式和属性绑定只能设置属性,不能设置 attribute。 在 Angular 的世界中,attribute 唯一的作用是用来初始化元素和指令的状态。

如图 5-1 所示,创建了一个 input 元素,初始设置它的属性(Attribute)为 "Bob",在获取它的 Attribute 和 使用ng双向绑定值时,用户输入值(123)和 Attribute 的值(Bob)是不一样的。

5.1 属性绑定 ( [属性名] )

单向输入

绑定目标

 <img [src]="heroImageUrl">
 

消除副作用

只绑定数据属性和那些只返回值而不做其它事情的方法。

返回恰当的类型

模板表达式应该返回目标属性所需类型的值。如果目标属性想要个数字,就返回数字。HeroDetail组件的hero属性想要一个Hero对象,那就在属性绑定中精确地给它一个Hero对象:

 <app-hero-detail [hero]="currentHero"></app-hero-detail>
 

别忘了方括号

方括号告诉 Angular 要计算模板表达式。

一次性字符串初始化

目标属性接受字符串值。 字符串是个固定值,可以直接合并到模块中。 这个初始值永不改变。

下面这个例子把HeroDetailComponent的prefix属性初始化为固定的字符串,而不是模板表达式。

 <app-hero-detail prefix="You are my" [hero]="currentHero"></app-hero-detail>
 

属性绑定还是插值表达式?

 <p><span>"{{titles}}" is the <i>interpolated</i> title.</span></p>
<p>"<span [innerHTML]="titles"></span>" is the <i>property bound</i> title.</p>
 

倾向于插值表达式
但数据类型不是字符串时,就必须使用属性绑定了,如图 5-2 所示:

内容安全

不管是插值表达式还是属性绑定,都不会允许带有 script 标签的 HTML 泄漏到浏览器中。 只渲染没有危害的内容 。比如下面的绑定:

 <p [innerHTML]="scripts"></p>
 

5.2 attribute、class 和 style 绑定

attribute 绑定

这是“绑定到目标属性 (property)”这条规则中唯一的例外。这是唯一的能创建和设置 attribute 的绑定形式。

由attr前缀,一个点 (.) 和 attribute 的名字组成。
把[attr.colspan]绑定到一个计算值:

 <table border="1">
    <!-- 设置 colspan=2 -->
    <tr><td [attr.colspan]="1 + 1">1-2</td></tr>
    <tr><td>5</td><td>6</td></tr>
</table>
 

运行结果:

attribute 绑定的主要用例之一是设置 ARIA attribute(译注:ARIA指可访问性,用于给残障人士访问互联网提供便利):

 <!-- 创建和设置辅助技术的 ARIA 属性 -->
<button [attr.aria-label]="actionName">{{actionName}} with Aria</button>
 

CSS 类绑定

由class前缀,一个点 (.)和 CSS 类的名字组成, [class.class-name] 。

 <!-- 使用绑定重置/覆盖所有类名 -->
<div class="bad curly special" [class]="badCurly">Bad curly</div>
 


可以绑定到特定的类名。

 <!-- 用属性打开/关闭“special”类 -->
<div [class.special]="isSpecial">The class binding is special</div>
<!-- 绑定`class.special`优先级高于class属性 -->
<div class="special" [class.special]="!isSpecial">This one is not so special</div>
 

样式绑定

设置内联样式。由style前缀,一个点 (.)和 CSS 样式的属性名组成, [style.style-property] 。

 <button [style.color]="isSpecial ? 'red': 'green'">Red</button>
 

有些样式绑定中的样式带有单位:

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

5.3 事件绑定 ( (事件名) )

事件绑定语法由 等号左侧带圆括号的目标事件 和 右侧引号中的模板语句 组成。

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

$event 和事件处理语句

绑定会通过名叫 $event 的事件对象传递关于此事件的信息(包括数据值)。

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

执行效果:

使用 EventEmitter 实现自定义事件

指令使用 Angular EventEmitter 来触发自定义事件。
指令创建一个EventEmitter实例,并且把它作为属性暴露出来。
指令调用 EventEmitter.emit(payload) 来触发事件,可以传入任何东西作为消息载荷。
父指令通过绑定到这个属性来监听事件,并通过 $event 对象来访问载荷。

5.4 双向数据绑定 ( [(...)] )

[(x)] 语法结合了属性绑定的方括号 [x] 和事件绑定的圆括号 (x) 。
sizer.component.ts :

 import { Component, EventEmitter, Input, Output } from '@angular/core';
@Component({
	selector: 'app-sizer',
	templateUrl: './sizer.component.html'
})
export class SizerComponent {
	@Input() size: number | string;
	@Output() sizeChange = new EventEmitter<number>();

	dec() { this.resize(-1); }
	inc() { this.resize(+1); }

	resize(delta: number) {
		console.log(delta, +this.size)
		this.size = Math.min(40, Math.max(8, +this.size + delta));
		console.log(this.size)
		this.sizeChange.emit(this.size);
	}
}
 

sizer.component.html :

 <div>
	<button (click)="dec()" title="smaller">-</button>
	<button (click)="inc()" title="bigger">+</button>
	<label [style.font-size.px]="size">FontSize: {{size}}px</label>
</div>
 

app.component.ts :

 <app-sizer [(size)]="fontSizePx"></app-sizer>
<!-- 等价于下面的写法 -->
<!-- <app-sizer [size]="fontSizePx" (sizeChange)="fontSizePx=$event"></app-sizer> -->
<div [style.font-size.px]="fontSizePx">Resizable Text</div>
 

app.component.ts :

 fontSizePx: number = 12;
 

同时注意在 app.module.ts 引入 sizer.component.ts 。
运行结果如图 5-9 所示:

内置指令

分为 属性型指令 和 结构型指令 。

6.1 常用内置属性型指令

属性型指令会监听和修改其它HTML元素或组件的行为、元素属性(Attribute)、DOM属性(Property)。
它们通常会作为HTML属性的名称而应用在元素上。

NgClass - 添加或移除一组CSS类 NgStyle - 添加或移除一组CSS样式 NgModel - 双向绑定到HTML表单元素

NgClass 指令

可以同时添加或移除多个类。
CSS 类绑定 是添加删除单个类的最佳途径。
而同时添加或移除多个 CSS 类,更好的选择可能是 NgClass 。

 // NgClass
canSave: boolean     = true;
isUnchanged: boolean = false;
isSpecial: boolean   = true;
currentClasses: {};
setCurrentClasses() {
	this.currentClasses = {
		'saveable': this.canSave,
		'modified': !this.isUnchanged,
		'special': this.isSpecial
	}
}
ngOnInit(): void {
	// 初始化 currentClasses
	this.setCurrentClasses();
}
 

把 NgClass 属性绑定到 currentClasses ,根据它来设置此元素的CSS类:

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

运行结果如图 6-1所示:

NgStyle 指令

NgStyle 绑定可以同时设置多个内联样式。
同样的, 样式绑定 也是设置单一样式值的简单方式。
NgStyle 是同时设置多个内联样式更好的选择。

 // ngStyle
currentStyles: {};
setCurrentStyles() {
	this.currentStyles = {
		'font-style': this.canSave ? 'italic' : 'normal',
		'font-weight': !this.isUnchanged ? 'bold' : 'normal',
		'font-size': this.isSpecial ? '24px' : '12px'
	}
}
 

把 NgStyle 属性绑定到 currentStyles 设置元素样式:

 <div [ngStyle]="currentStyles">
    This div is initially by ngStyle.
</div>
 

运行结果如图 6-2所示:

NgModel - 使用[(ngModel)]双向绑定到表单元素

双向绑定表单元素。
需要引入 FormModule 。

 import { FormsModule } from '@angular/forms';

@NgModule({
    imports: [
        FormsModule
    ]
})
export class AppModule { }
 

ngModel 可以通过之前的属性绑定和事件绑定实现:

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

ngModel 的展开实现(自己的输入( ngModel )属性和输出( ngModelChange )属性):

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

ngModel 的合并实现方法是 [(ngModel)] :

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

[(ngModel)] 只能设置数据绑定属性,如果有特别需求也可以展开形式:

app.component.html :

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

app.component.ts :

 setUppercaseName(name) {
	this.currentHero.name = name.toUpperCase();
}
 

执行效果如下图(图 6-3)所示:

6.2 常用内置结构型指令

结构型指令的职责是HTML布局。添加、移除和操纵DOM元素。
注意指令前面的 * 号。

NgIf - 根据条件把一个元素添加到DOM中或从DOM移除 NgSwitch - 一组在替代视图之间切换的指令 NgForOf - 对列表中的每个条目重复套用一个模板

NgIf 指令

和vue的 v-if , v-show 一样,不同于隐藏元素。
ngIf 可以 防范空指针错误

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

NgFor 指令

重复器指令。可以用在简单的元素上,也可以用在一个组件元素上。
赋值给 *ngFor 的文本是用于指导重复器如何工作的指令 - 微语法(microsyntax)。

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

trackBy 可以防止渲染全部条目。

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

如图 6-4 所示,如果没有trackBy,这些按钮都会触发完全的DOM元素替换;有了trackBy,则只有修改了id的按钮才会触发元素替换。

NgSwitch 指令

可以从多个可能的元素中根据switch条件来显示某一个。包括三个相互协作指令: NgSwitch 、 NgSwitchCase 、 NgSwitchDefault 。

 Pick ur favorite hero:
<div class="favorite-hero">
    <label *ngFor="let hero of heroes">
        <input type="radio" name="hero" value="{{hero}}" (change)="selectHero(hero)"> {{hero.name}}
    </label>

    <div class="hero-desc" *ngIf="selectedHero" [ngSwitch]="selectedHero.id">
        <p *ngSwitchCase="1">Wow. U like {{ selectedHero.name }}, let's play!</p>
        <p *ngSwitchCase="2">Hey man, this is {{ selectedHero.name }}, fk u!</p>
        <p *ngSwitchCase="3">U like {{ selectedHero.name }}? R u sure?</p>
        <p *ngSwitchCase="4">Let's fk {{ selectedHero.name }}, go go go!</p>
        <p *ngSwitchDefault>Little {{ selectedHero.name }} is a cat.</p>
    </div>
</div>
 
 selectedHero: Hero;
    selectHero(hero: Hero): void {
    this.selectedHero = hero;
}
 

运行效果如图 6-5 所示:

模板引用变量(#var)

模板引用变量通常用来引用变量中的某个DOM元素,它还可以引用Angular组件或指令或Web Component。
使用井号(#)来声明引用变量。 #phone 的意思就是声明一个名叫 phone 的变量来引用 <input> 元素。
通俗来讲, #[name] 表示的是DOM元素,而在DOM其他地方可以直接调用这个 name ,取到的是DOM元素。

比如下面的例子:

 <input type="text" #phone placeholder="phone number" [(ngModel)]="phoneNum">

<button type="button" name="button" (click)="callPhone(phone.value)">console.log phone</button>
 
 phoneNum: string = '15342018244';
callPhone(phone: string): void {
	console.log(phone);
}
 

运行效果如图 7-1 所示。

指令也可以修改这种行为,让这个值引用到别处,比如它自身。 NgForm 指令就是这么做的。

 <form (ngSubmit)="onSubmit(heroForm)" #heroForm="ngForm">
<!--  这里的heroForm实际上是一个Angular NgForm 指令的引用, 因此具备了跟踪表单中的每个控件的值和有效性的能力。 -->
    <div class="form-group">
        <label for="name">Name
            <input type="text" name="name" required [(ngModel)]="submitHero.name">
        </label>
    </div>
    <button type="submit" [disabled]="!heroForm.form.valid">Submit</button>
</form>
<div [hidden]="!heroForm.form.valid">
    {{submitMessage}}
</div>
 
 submitHero: Hero = new Hero(null, '');
submitMessage: string;
onSubmit(hero): void {
	console.log(hero.form.value);
	this.submitMessage = 'success!';
}
 

运行效果如图 7-2 所示:

可以用 ref- 前缀代替 # 。

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

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

所有组件皆为指令

绑定目标
等号左侧、绑定符: () , [] , [()] 中的属性/事件名、只能绑定到显示标记为输入或输出属性。
绑定源
等号右侧、引号 "" 或插值符号 {{}} 中的部分、源指令中的每个成员都会自动在绑定中可用。

8.1 声明输入和输出属性

目标属性必须被显示的标记为输入或输出。

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

src/app/hero-detail.component.ts :

 @Input()  hero: Hero;
@Output() deleteRequest = new EventEmitter<Hero>();
 

还可以在指令元数据的 inputs 或 outputs 数组中标记处这些成员。

 @Component({
    inputs: ['hero'],
    outputs: ['deleteRequest']
});
 

但是两者 不能 同时使用。

8.2 输入输出选择

输入属性:接收数据值;
输出属性:暴露时间生产者。
输入和输出两个词是从目标指令角度说的。

8.3 给输入/输出属性起别名

把别名传进@Input/@Output装饰器,就可以为属性指定别名:

 @Output('myClick') clicks = new EventEmitter<string>();
 

也可以在 inputs 和 outputs 数组中为属性指定别名:

 @Directive({
    outputs: ['clicks: myClick']
});
 

模板表达式操作符

管道
安全导航操作符

9.1 管道操作符(|)

适用于小型转换。
管道是一个简单的函数,它接受一个输入值,并返回转换结果。

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

管道操作符可以串联:

 <div>
    Title through a pupe chain: {{ title | uppercase | lowercase }}
</div>
 

还可以使用参数:

 <div>
    Birthday: {{ currentHero?.birthdate | date: 'longDate' }}
</div>
 

json 管道用于调试绑定:

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

运行结果如图 9-1 所示:

9.2 安全导航操作符(?.)和空属性路径

?. 安全导航操作符用来 保护出现在属性路径中的 null 和 undefined 值 ,保护视图渲染。

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

如果 currentHero 不存在,那么上面的代码不加 ?. 会发生什么?
草,整个视图都不见了。
用于替代 *ngIf 和 && 解决方案。

 <code>*ngIf 解决方案</code>
<p><code>fuck.name: </code><span *ngIf="fuck">{{ fuck.name }}</span></p>

<code>&& 解决方案</code>
<p><code>fuck.name: </code><span>{{ fuck && fuck.name }}</span></p>
 

9.3 非空断言操作符(!)

值得一提的是 非空断言操作符不会防止出现null或undefined 。 它只是告诉 TypeScript 的类型检查器对特定的属性表达式,不做 " 严格空值检查 "。

如果我们打开了 严格空值检查 ,那就要用到这个模板操作符,而其它情况下则是可选的。

在 TypeScript 2.0 中,我们可以使用 --strictNullChecks 标志强制开启 严格空值检查 。TypeScript就会确保不存在意料之外的null或undefined。
在这种模式下,有类型的变量默认是不允许null或undefined值的,如果有

未赋值的变量, 或者试图把null或undefined赋值给不允许为空的变量, 如果类型检查器在运行期间无法确定一个变量是null或undefined,
类型检查器就会抛出一个错误。

在用 *ngIf 来检查过 hero 是已定义的之后,就可以断言 hero 属性一定是已定义的。

 <div *ngIf="hero">
    The hero's name is {{ hero!.name }}.
</div>
 

到这里 模板语法的概述 就算是完成了。
以上贴图源码在 GitHub 。
以上最后更新: 2017年11月6日16:48:28

查看更多关于Angular 5.x 学习笔记(1) - 模板语法的详细内容...

  阅读:34次