相关知识
管道
每个绑定都有自己的管道实例。
每个管道实例都有自己数据。
内置管道
date
json
async
currency
persent
decimal
lowerCase
upperCase
....
使用
{{ 组件成员 | 管道名称 }}
传参
管道名称 :
参数值,多个参数使用多个冒号分隔,参数可以是任何合法的 JS 表达式/组件成员
可以使用管道运算符|来串联多个管道
自定义管道
import { Pipe, PipeTransform } from "@angular/core";
@Pipe({
name: "pow"
})
export class PowPipe implements PipeTransform {
transform(value: number, pow = 1): number {
return value ** pow;
}
}
在 shared.module.ts
导入
在模版中使用
<!-- 16 -->
{{ 2 | pow: 4 }}
管道的执行时机
angular 会在每次变更检测的时候都去执行管道(事实是,只有非纯管道才会在 angular 每次的变更检测时都执行,纯管道只会在绑定的值发生变化或者引用发生变化的时候执行)
变更检测的执行时机
在每次 DOM 事件(事件、定时器、网络请求)之后运行
管道的变更检测
纯管道:检测原始类型和对象引用的纯变更
默认情况下,管道会被定义成纯的(pure)。这样 Angular 只有在检测到输入值发生了纯变更时才会执行该管道。纯变更是对原始输入值(比如 String、Number、Boolean 或 Symbol )的变更,或是对对象引用的变更(比如 Date、Array、Function、Object)。
非纯管道:检测复合对象中的非纯变更
要在复合对象内部进行更改后执行自定义管道(例如更改数组元素),就需要把管道定义为 impure 以检测非纯的变更。每当按键或鼠标移动时,Angular 都会检测到一次变更,从而执行一个非纯管道。(效率很低)
@Pipe({
name: 'pipeName',
pure: false
})
AsyncPipe
AsyncPipe 可以将一个 Observable 对象作为输入,自动订阅这个 Observable,并且在组件销毁的时候自动取消订阅。不需要我们在组件的 class 文件中进行手动操作。AsyncPipe 是一个非纯管道。
管道操作符的优先级
管道操作符要比三目运算符(?:)的优先级高,这意味着 a ? b : c | x
会被解析成 a ? b : (c | x)
。
属性、类、style 绑定
属性绑定
[属性名]='成员/表达式'
/ 属性名='{{ 成员/表达式 }}'
/ 属性名='值'
(此时右侧会被当作为字符串)
无论是哪种形式,都需要在模版语句上加引号
所以我能不能说模版语句需要使用引号包裹
clospan 和 colSpan
最容易混淆的地方是 colspan 这个 Attribute 和 colSpan 这个 Property ????。请注意,这两个名称只有一个字母的大小写不同。
<tr>
<td colspan="{{1 + 1}}">Three-Four</td>
</tr>
报错:
Template parse errors:
Can't bind to 'colspan' because it isn't a known built-in property
如消息中所示,<td>
元素没有 colspan Property。这是正确的,因为 colspan 是一个 Attribute — colSpan(带大写 S)才是相应的 Property。插值和 Property 绑定只能设置 Property,不能设置 Attribute。
而原生 html 上的属性是 attribute,其对应的 angular 的 property 就是其小驼峰的形式
设置指令的属性
为指令绑定属性的时候需要将指令放在[]中
<p [ngClass]="classes">
[ngClass] binding to the classes property making this blue
</p>
属性绑定与安全性
属性绑定可以帮助确保内容的安全。
evilTitle = 'Template <script>alert("evil never sleeps")</script> Syntax';
组件模板对内容进行插值
<p>
<span>"{{evilTitle}}" is the <i>interpolated</i> evil title.</span>
</p>
浏览器不会处理 HTML,而是原样显示它,如下所示
"Template <script>alert("evil never sleeps")</script> Syntax" is the interpolated evil title.
Angular 不允许带有 <script>
标记的 HTML,既不能用于插值也不能用于属性绑定,这样就会阻止运行 JavaScript。
但是,在以下示例中,Angular 在显示值之前会先对它们进行无害化处理
<!--
Angular generates a warning for the following line as it sanitizes them
WARNING: sanitizing HTML stripped some content (see https://g.co/ng/security#xss).
-->
<p>
"<span [innerHTML]="evilTitle"></span>" is the <i>property bound</i> evil
title.
</p>
插值处理 <script>
标记的方式与属性绑定的方式不同,但这两种方法都可以使内容无害。以下是经过无害化处理的 evilTitle 示例的浏览器输出。
"Template Syntax" is the property bound evil title.
也就是说插值和属性绑定这两种方式都可以对内容进行安全处理,只不过方式不同,前者是对标签进行转译,后者是标签中的内容不会被展示
属性绑定和插值
通常,插值和属性绑定可以达到相同的结果。
<p><img src="{{itemImageUrl}}" /> is the <i>interpolated</i> image.</p>
<p><img [src]="itemImageUrl" /> is the <i>property bound</i> image.</p>
<p>
<span>"{{interpolationTitle}}" is the <i>interpolated</i> title.</span>
</p>
<p>
"<span [innerHTML]="propertyTitle"></span>" is the
<i>property bound</i> title.
</p>
将数据值渲染为字符串时,可以使用任一种形式,只是插值形式更易读。但是,要将元素属性设置为非字符串数据值时,必须使用属性绑定。
Attribute 绑定、类绑定和样式绑定
attribute 绑定
尽可能使用 property 绑定,如果你没法使用 property 绑定的时候在使用 attribute 绑定(绑定原生 HTML 元素的属性,或者 data 自定义属性)
在 Attribute 名称前面加上前缀 attr,后跟一个点 .原生html属性名
。然后,使用解析为字符串的表达式设置 Attribute 值
<p [attr.attribute-you-are-targeting]="expression"></p>
<!-- expression calculates colspan=2 -->
<tr>
<td [attr.colspan]="1 + 1">One-Two</td>
</tr>
当表达式解析为 null 或 undefined 时,Angular 会完全删除该 Attribute。
绑定到 class attribute
单个 class 类绑定
前缀 class 后跟一个点和 CSS 类的名称,例如 [class.sale]="onSale"
。onSale 为真值时添加类,在表达式为假值时(undefined 除外)删除类
多个 class 类绑定
[class]="classExpression"
classExpression
的取值;ngClass也可以做同样的事情。ngClass 只支持表达式绑定
用空格分隔的类名字符串
以类名作为键名并将真或假表达式作为值的对象
classExpression = { foo: true, bar: false };
- 类名的数组
对于引用类型,则需要引用发生改变,才可以更新 class
绑定属性到 style attribute
绑定单一样式
前缀 style 后跟一个点和 CSS style Attribute 的名称,例如 [style.width]="width"
。 Angular 会将该 Attribute 设置为绑定表达式的值,这个值通常是一个字符串。
可以使用中划线或者是小驼峰
<nav [style.background-color]="expression"></nav>
<nav [style.backgroundColor]="expression"></nav>
绑定多个样式
[style]="styleExpression"
。styleExpression 可以是如下格式之一:ngStyle也可以做同样的事情。ngStyle 只支持对象绑定,不支持字符串
样式的字符串列表,例如 "width: 100px; height: 100px; background-color: cornflowerblue;"
一个对象,其键名是样式名,其值是样式值,比如 {width: '100px', height: '100px', backgroundColor: 'cornflowerblue'}
当把 [style] 绑定到对象表达式时,该对象的引用必须改变,这样 Angular 才能更新这个类列表
使用[style]来代替[ngStyle]的指令绑定,因为未来 angular 可能要移除[ngStyle]
样式绑定的优先级
- 类或样式绑定越具体,其优先级就越高。 以下优先级由高到低
属性绑定(例如,
<div [class.foo]="hasFoo">
或<div [style.color]="color">
)映射表绑定(例如,
<div [class]="classExpr">
或<div [style]="styleExpr">
)静态值(例如
<div class="foo">
或<div style="color: blue">
)
- 绑定总是优先于静态属性,及直接使用 html 元素的 class 属性/style 属性添加的样式
事件绑定
(js事件名 - on) = '事件处理函数'
eg: (click)='onClick()'
等号左侧括号内的目标事件名和右侧引号内的模板语句组成
EventEmitter自定义事件
组件创建一个 EventEmitter 并将其对外暴露为属性。
然后,该组件调用 EventEmitter.emit(data) 发出事件,传入消息数据,该消息数据可以是任何东西。
父组件通过绑定到该属性来监听事件,并通过传入的 $event 对象接收数据。
// This component makes a request but it can't actually delete a hero.
@Output() deleteRequest = new EventEmitter<Item>();
delete() {
this.deleteRequest.emit(this.item);
this.displayNone = this.displayNone ? '' : 'none';
this.lineThrough = this.lineThrough ? '' : 'line-through';
}
父组件
<app-item-detail (deleteRequest)="deleteItem($event)" [item]="currentItem"></app-item-detail>
$event在原生事件中代码Event对象;在自定义事件中就是我们emit的数据
双向绑定
[(ngModel)]
Angular 的双向绑定语法是方括号和圆括号的组合 [()]。[] 进行属性绑定,() 进行事件绑定
双向绑定工作原理
@Output() 属性的名字必须遵循 inputChange 模式,其中 input 是相应 @Input() 属性的名字。例如,如果 @Input() 属性为 size ,则 @Output() 属性必须为 sizeChange 。
export class SizerComponent {
@Input() size!: number | string;
@Output() sizeChange = new EventEmitter<number>();
dec() { this.resize(-1); }
inc() { this.resize(+1); }
resize(delta: number) {
this.size = Math.min(40, Math.max(8, +this.size + delta));
this.sizeChange.emit(this.size);
}
}
<app-sizer [(size)]="fontSizePx"></app-sizer>
fontSizePx = 16; // 初始化app-sizer的size属性
sizeChange事件将修改副组件的fontSizePx属性,同理父组件的fontSizePx属性也会更新子组件的size属性。
双向绑定语法是属性绑定和事件绑定的组合的简写形式。拆成单独的属性绑定和事件绑定形式的 SizerComponent 代码如下:
<app-sizer [size]="fontSizePx" (sizeChange)="fontSizePx=$event"></app-sizer>
表单的双向数据绑定使用 ngModel
模版变量
使用 #变量名 在模版文件中获取组件实例或者DOM元素
语法
在模板中,要使用井号 # 来声明一个模板变量。
<input #phone placeholder="phone number" />
可以在该组件模版的任意地方使用该变量
<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 是如何为模板变量赋值的
- 如果在组件上声明变量,该变量就会引用该组件实例。
- 如果在标准的 HTML 标记上声明变量,该变量就会引用该元素。
- 如果你在
<ng-template>
元素上声明变量,该变量就会引用一个 TemplateRef 实例来代表此模板。 - 如果该变量在右侧指定了一个名字,比如
#var="ngModel"
,那么该变量就会引用所在元素上具有这个 exportAs 名字的指令或组件。
ngForm指令与模版引用变量
<form #itemForm="ngForm" (ngSubmit)="onSubmit(itemForm)">
<label for="name">Name</label>
<input type="text" id="name" class="form-control" name="name" ngModel required />
<button type="submit">Submit</button>
</form>
<div [hidden]="!itemForm.form.valid">
<p>{{ submitMessage }}</p>
</div>
使用了 NgForm 之后,itemForm 就是对 NgForm 指令的引用
模版引用变量的作用域
可以在包含此模板变量的模板中的任何地方引用它。而 结构型指令(如 ngIf 和 ngFor 或 <ng-template>
同样充当了模板的边界。你不能在这些边界之外访问其中的模板变量。
同名变量在模板中只能定义一次
模版输入变量
<ng-template #hero let-hero let-i="index" let-odd="isOdd">
<div [class]="{'odd-row': odd}">{{i}}:{{hero.name}}</div>
</ng-template>
let-hero中的hero以及i以及isOdd都是模版输入变量,仅仅能在ng-template内部访问,hero则是模版引用变量,在整个模版中都是可以访问的