指令

2019-07-13  本文已影响0人  oWSQo

指令作用在特定的DOM元素上,可以扩展这个元素的功能,为元素增加新的行为。本质上,组件可以被理解为一种带有视图的指令。组件继承自指令,是指令的一个子类,通常被用来构建UI控件。

概述

指令分类

在angular中包含三种类型的指令:属性指令、结构指令和组件。

属性指令

以元素属性的形式来使用的指令。属性指令通常被用来改变元素的外观和行为。

结构指令

结构指令可以用来改变DOM树的结构。结构指令可以根据模板表达式的值,增加或删除DOM元素,从而改变DOM的布局。结构指令与属性指令的使用方式相同,都是以元素属性的形式来使用。区别在于使用场景不同。

<p *ngIf="condition">condition为true时,能看到这句话</p>
组件

组件是被用来构造带有自定义行为的可重用视图。组件与指令的结构类似,均使用装饰器描述元数据,二者均在各自对应的类中实现具体业务逻辑。差别在于组件中包含了模板。
组件作为指令的一个子类,它的部分生命周期钩子与指令的相同。


组件与指令相同的生命周期钩子方法

因为指令中不具有模板,因此在组件中,围绕模板视图的初始化和更新的生命周期钩子方法,是组件独有而指令所不具有的。

内置指令

angular内置指令可分为三类:通用指令、路由指令和表单指令。

通用指令

通用指令包括:NgClassNgStyleNgIfNgSwitchNgSwitchCaseNgSwitchDefaultNgForNgTemplateOutletNgPluralNgPluralCase
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是一个布尔类型的数据值,当valuetrue时则添加对应的类名到模板中,反之则移除。

//...
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节点上,并移除其他备选模板元素。这三个相互合作的指令分别表示:

<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模块包含NgModelNgModelGroupNgForm指令和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模块包含的指令中,第一部分为表单元素访问器指令,它包含的指令如下:

这些访问器指令是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;
}

对应不同的数据输入类型,表单指令集合提供了各自的访问器类。

InternalFormsSharedModule模块包含的指令中,第二部分为选择框选项指令,它包含的指令如下:

它们动态地标记选择框的<option>选项,当选项变更时,angular会收到通知。
InternalFormsSharedModule模块包含的指令中,第三部分为表单验证指令,它包含的指令如下:

InternalFormsSharedModule模块包含的指令中,最后一部分为控件状态指令,它包含的指令如下:

上述指令是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 {}

自定义结构指令

实现结构指令

创建自定义结构指令与创建自定义属性指令类似,步骤:

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树中,反之会被移除。

上一篇下一篇

猜你喜欢

热点阅读