指令作用在特定的DOM元素上,可以扩展这个元素的功能,为元素增加新的行为。本质上,组件可以被理解为一种带有视图的指令。组件继承自指令,是指令的一个子类,通常被用来构建UI控件。
概述
指令分类
在angular中包含三种类型的指令:属性指令、结构指令和组件。
属性指令
以元素属性的形式来使用的指令。属性指令通常被用来改变元素的外观和行为。
结构指令
结构指令可以用来改变DOM树的结构。结构指令可以根据模板表达式的值,增加或删除DOM元素,从而改变DOM的布局。结构指令与属性指令的使用方式相同,都是以元素属性的形式来使用。区别在于使用场景不同。
<p *ngIf="condition">condition为true时,能看到这句话</p>
组件
组件是被用来构造带有自定义行为的可重用视图。组件与指令的结构类似,均使用装饰器描述元数据,二者均在各自对应的类中实现具体业务逻辑。差别在于组件中包含了模板。
组件作为指令的一个子类,它的部分生命周期钩子与指令的相同。
组件与指令相同的生命周期钩子方法
因为指令中不具有模板,因此在组件中,围绕模板视图的初始化和更新的生命周期钩子方法,是组件独有而指令所不具有的。
- ngAfterContentInit
- ngAfterContentChecked
- ngAfterViewInit
- ngAfterViewChecked
内置指令
angular内置指令可分为三类:通用指令、路由指令和表单指令。
通用指令
通用指令包括:NgClass
、NgStyle
、NgIf
、NgSwitch
、NgSwitchCase
、NgSwitchDefault
、NgFor
、NgTemplateOutlet
、NgPlural
、NgPluralCase
。
angular将上述通用指令包含在CommonModule
模块中,当使用通用指令时,应首先在应用的模块中引入CommonModule
模块。
//app.module.ts
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AppComponent } from './app.component';
@NgModule({
imports:[BrowserModule],
declarations:[AppComponent],
bootstrap:[AppComponent]
})
export class AppModule {}
//app.component.ts
import { Component } from '@angular/core';
@Component({
selector:'my-app',
template:`
<p *ngIf="condition"></p>
`
})
export class AppComponent {
condition=true;
}
在上述app.module.ts
的@NgModule
装饰器中,imports
属性没有主动引入CommonModule
模块。这是因为angular的BrowserModule
模块已包含了CommonModule
模块,因此在AppComponent
组件中,可以直接使用NgIf
指令。
NgClass
在属性绑定中,CSS类绑定的方式能为标签元素添加或移除单个类。
通过NgClass
指令可以同时添加或移除多个类。NgClass
绑定一个由形如CSS类名:value
的对象,其中,value
是一个布尔类型的数据值,当value
为true
时则添加对应的类名到模板中,反之则移除。
//...
setClasses(){
let classes={
red:this.red,
font14:!this.font14,
title:this.isTitle
};
return classes;
}
<div [ngClass]="setClasses()">红色标题文字</div>
NgStyle
Style
样式绑定的方式能够给模板元素设置单一的样式。而NgStyle
指令可以为模板元素设置多个内联样式,NgStyle
绑定一个形如CSS属性名:value
的对象,其中value
为具体的CSS样式。
//...
setStyles(){
let styles={
'color':this.red?'red':'blue',
'font-size':!this.font14?'14px':'16px',
'font-weight':this.isSpecial?'bold':'normal'
};
return styles;
}
<div [ngStyle]="setStyles()">红色加粗文字</div>
NgIf
NgIf
指令绑定一个布尔类型的表达式,当表达式返回true
时,可以在DOM树节点上添加一个元素及其子元素,反之则移除。
<h3 *ngIf="collect.length===0" class="no-collection">未收藏</h3>
NgSwitch
NgSwitch指令需要结合NgSwitchCase和NgSwitchDefault指令来使用,根据NgSwitch绑定的模板表达式的返回值来决定添加哪个模板元素到DOM节点上,并移除其他备选模板元素。这三个相互合作的指令分别表示:
- ngSwitch:绑定到一个返回控制条件的值的表达式
- ngSwitchCase:绑定到一个返回匹配条件的值的表达式
- ngSwitchDefault:用于标记默认元素的属性
<span [ngSwitch]="contactName">
<span *ngSwitchCase="'zhangsan'">张三</span>
<span *ngSwitchCase="'lisi'">李四<span>
<span *ngSwitchDefault>无名氏</span>
</span>
NgFor
<li *ngFor="let contact of contacts">
<list-li [contact]="contact" (routerNavigate)="routerNavigate($event)"></list-li>
</li>
赋值给*ngFor
的字符串并不是一个模板表达式,前面的星号不能省略,这是angular提供的一种语法糖。
NgFor中的索引
NgFor
指令支持一个可选的index
索引,在循环迭代过程中,其下标范围是0<=index<数组的长度
。可以通过模板输入变量来捕获这个index
,并应用在模板中。
<div *ngFor="let contact of contacts;let i=index">{{i+1}}-{{contact.id}}</div>
NgForTrackBy
比如在一个通讯录中,当重新从服务器拉取列表数据,拉取到的数据可能包含很多之前显示过的数据,angular不知道哪些列表数据在数据更新前渲染过,只能清理旧列表的DOM元素,并用新的列表数据填充DOM元素来重建一个新列表。这种情况下,可以通过追踪函数来避免这种重复渲染的性能浪费。
trackByContacts(index:number,contact:Contact){
return contact.id;
}
<div *ngFor="let contact of contacts;trackBy:trackByContacts">
{{contact.id}}
</div>
如果检查出同一个联系人的属性发生了变化,angular会更新DOM元素,反之就会留下这个DOM元素。
表单指令
表单指令包含了一系列在angular表单中使用的指令,这些指令分别被包含在三个模块中:
- FormsModule
- ReactiveFormsModule
- InternalFormsSharedModule
FormsModule
模块包含NgModel
、NgModelGroup
、NgForm
指令和InternalFormsSharedModule
模块中包含的指令。
FormsModule模块
FormsModule模块包含NgModel、NgModelGroup、NgForm指令和InternalFormsSharedModule模块中包含的指令。
ReactiveFormsModule模块
ReactiveFormsModule模块包含FormControlDirective、FormGroupDirective、FormControlName、FormGroupName、FormArrayName指令和InternalFormsSharedModule模块中包含的指令。
1.FormControlDirective指令
该指令可以将一个已有的FormControl实例绑定到一个DOM元素。
@Component({
selector:'my-app',
template:`
<div>
<h2>FormControl例子</h2>
<form>
<p>绑定到input标签:
<input type="text" [formControl]="loginControl" />
</p>
<p>获得input的值:{{loginControl.value}}</p>
</form>
</div>
`
})
export class App{
loginControl:FormControl=new FormControl('');
}
2.FormGroupDirective指令
该指令可以将一个已有的表单组合绑定到一个DOM元素。
@Component({
selector:'my-app',
template:`
<div>
<h2>FormGroup例子</h2>
<form [formGroup]="loginForm">
<p>用户名:
<input type="text" formControlName="name" />
</p>
</form>
<p>LoginForm的值:</p>
<pre>{{loginForm.value|json}}</pre>
</div>
`
})
export class App{
loginForm:FormGroup;
constructor(){
this.loginForm=new FormGroup({
name:new FormControl("");
password:new FormControl("")
});
}
}
3.FormControlName指令
该指令将一个已有的表单控件与一个DOM元素绑定,绑定时使用FormControlName指令为表单控件指定一个别名。这一指令仅作为FormGroupDirective指令的子元素使用。
4.InternalFormsSharedModule模块
引入FormsModule和ReactiveFormsModule模块时,可以使用InternalFormsSharedModule模块包含的指令。
InternalFormsSharedModule模块包含的指令中,第一部分为表单元素访问器指令,它包含的指令如下:
- DefaultValueAccessor
- NumberValueAccessor
- CheckboxControlValueAccessor
- RadioControlValueAccessor
- SelectControlValueAccessor、SelectorMultipleControlValueAccessor
这些访问器指令是angular表单的内部指令,在应用中无须主动使用。它们是DOM元素和表单输入控件之间的桥梁。其中ControlValueAccessor是其他访问器指令的父接口,抽象了angular控件与DOM元素交互的公共方法。ControlAccessor接口定义了三个方法,即向DOM元素写入新值的方法writeValue(),用于监听DOM元素值变更的方法registerOnChange()以及用于监听DOM元素触摸事件的方法registerOnTouched()。
export interface ControlValueAccessor{
writeValue(obj:any):void;
registerOnChange(fn:any):void;
registerOnTouched(fn:any):void;
}
对应不同的数据输入类型,表单指令集合提供了各自的访问器类。
- DefaultValueAccessor为text文本输入框的访问器
- CheckboxControlValueAccessor为checkbox复选框的访问器
- RadioControlValueAccessor为radio单选框的访问器,RadioButtonState保存单选框的选中状态和选中的值
- NumberValueAccessor为number数字输入框的访问器
- SelectControlValueAccessor为select下拉框的访问器,NgSelectOption指令动态地标记<option>选项,选项变更时,angular会收到通知
InternalFormsSharedModule模块包含的指令中,第二部分为选择框选项指令,它包含的指令如下:
- NgSelectOption
- NgSelectMultipleOption
它们动态地标记选择框的<option>选项,当选项变更时,angular会收到通知。
InternalFormsSharedModule模块包含的指令中,第三部分为表单验证指令,它包含的指令如下:
- RequiredValidator指令为表单输入元素增加required约束
- MinLengthValidator指令为表单输入元素增加minLength约束
- MaxLengthValidator指令为表单输入元素增加maxLength约束
- PatternValidator指令为表单元素增加正则表达式约束,输入内容必须符合正则表达式定义的模式
InternalFormsSharedModule模块包含的指令中,最后一部分为控件状态指令,它包含的指令如下:
- NgControlStatus
- NgControlStatusGroup
上述指令是angular表单内部指令,在应用中无需主动地使用它,他会自动根据控件是否通过验证、是否被触摸等状态来设置元素的CSS类。
路由指令
路由指令包括:RouterLink、RouterOutlet和RouterLinkActive。
RouterOutlet指令是一个占位符,路由跳转时,angular会查找当前匹配的组件并将组件插入到RouterOutlet中;RouterLinkActive指令在当前路径与元素设置的链接匹配时为元素添加CSS样式;RouterLink指令使得应用可以链接到特定组件。
自定义属性指令
实现属性指令
一个属性指令需要一个控制器类,该控制器类使用@Directive装饰器来装饰。@Directive装饰器指定了用以标记指令所关联属性的选择器,控制器类实现了指令所对应的行为。
//beautifulBackground.directive.ts
import { Directive,ElementRef } from '@angular/core';
@Directive({
selector:'[myBeautifulBackground]'
})
export class BeautifulBackgroundDirective{
constructor(el:ElementRef){
el.nativeElement.style.backgroundColor='yellow';
}
}
从angular核心模块@angular/core中引入了Directive和ElementRef。Directive被用作@Directive装饰器,ElementRef则用来访问DOM元素。随后,在@Directive装饰器中以键值对的形式定义了指令的元数据。在配置对象中,使用selector属性来标识该属性指令所关联的元素名称,[myBeautifulBackground]是指令所对应的CSS选择器,[]
在CSS选择器中表示元素属性匹配。所以当指令运行时,angular会在模板中匹配所有包含属性名称myBeautifulBackground的DOM元素。
在@Directive元数据下面是该自定义指令的控制器类,该类实现了指令所包含的逻辑。export关键字被用来将指令导出供其他组件访问。
angular会为每一个匹配的DOM元素创建一个指令实例i,同时将ElementRef作为参数注入到控制器构造函数。使用ElementRef服务,可以在代码中通过其nativeElement属性直接访问DOM元素,这样就可以通过DOM API设置元素的背景颜色。
接下来将指令应用到组件中。
//app.component.ts
import { Component } from '@angular/core';
@Component({
selector:'my-app',
template:`<div myBeautifulBackground>按钮</div>`
})
export class AppComponent {}
//app.module.ts
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AppComponent } from './app.component';
import { BeautifulBackgroundDirective } from './beautifulBackground.directive';
@NgModule({
imports:[BrowserModule],
declarations:[AppComponent,BeautifulBackgroundDirective],
bootstrap:[AppComponent]
})
export class AppModule {}
自定义结构指令
实现结构指令
创建自定义结构指令与创建自定义属性指令类似,步骤:
- 引入@Directive装饰器
- 添加CSS属性选择器,用来标识指令
- 声明一个input属性用以绑定表达式
- 将装饰器应用在指令实现类上
import { Directive,Input } from '@angular/core';
@Directive({
selector:'[myUnless]'
})
export class UnlessDirective {
@Input('myUnless') condition:boolean;
}
Unless指令需要访问组件模板内容,并且需要可以渲染组件模板内容的工具。使用TemplateRef和ViewContainerRef服务可以实现这一目标,TemplateRefr可以用来方位组件的模板,ViewContainerRef可作为视图内容渲染器,将模板内容插入至DOM中。TemplateRef和ViewContainerRef服务来自'@angular/core',在指令的构造函数中,需要将它们作为依赖注入,赋值给指令的变量。
//...
constructor(
private templateRef:TemplateRef<any>,
private viewContainer:viewContainerRef
){}
//...
在组件中使用Unless指令时,需要为指令绑定一个值为布尔类型的表达式或变量,指令根据绑定结果增加或删除模板内容。为了在接收到绑定结果时实现这一逻辑,需要为condition属性设置一个set方法。
@Input('myUnless')
set condition(newCondition:boolean){
if(!newCondition){
this.viewContainer.createEmbeddedView(this.templateRef);
}else{
this.viewContainer.clear();
}
}
通过调用渲染器的createEmbeddedView()和clear()方法,实现了根据输入属性的值,添加和删除模板内容的作用。
最终Unless指令的代码:
import { Directive,Input,TemplateRef,ViewContainerRef } from '@angular/core';
@Directive({
selector:'[myUnless]'
})
export class UnlessDirective {
@Input('myUnless')
set condition(newCondition:boolean){
if(!newCondition){
this.viewContainer.createEmbeddedView(this.templateRef);
}else{
this.viewContainer.clear();
}
constructor(
private templateRef:TemplateRef<any>,
private viewContainer:viewContainerRef
){}
}
<p *myUnless="boolValue">myUnless</p>
当boolValue的值是false时,段落中的内容会被添加到DOM树中,反之会被移除。
网友评论