指令

作者: oWSQo | 来源:发表于2019-07-13 08:58 被阅读0次

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

    概述

    指令分类

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

    属性指令

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

    结构指令

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

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

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


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

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

    • ngAfterContentInit
    • ngAfterContentChecked
    • ngAfterViewInit
    • ngAfterViewChecked

    内置指令

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

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

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

    相关文章

      网友评论

          本文标题:指令

          本文链接:https://www.haomeiwen.com/subject/ryepjxtx.html