angular 指令的學習

前言

在開發alice項目中,使用了阿里的ng-zorro框架,因此會常常遇到不少框架本身寫的屬性,例以下面的柵格:css

<div nz-row>
    <div nz-col [nzSpan]="4">
        xxx
    </div>
</div>

其中的nz-row,nz-col,以及nzSpan就是框架自定義的html屬性,但他們到底是什麼,又是怎麼實現的,我卻歷來沒有想過,因此,趁着本週學習時間比較充裕,就去探索了一下。
首先,在webstorm經過Ctrl + 鼠標點擊nz-col查看它的源碼,如圖:html

clipboard.png

發現nz-col實際上是一個指令,就去angular上搜索了指令的內容web

1、angular指令概覽

在 Angular 中有三種類型的指令:json

  1. 組件: 擁有模板的指令,例如:<yunzhi-data-list></yunzhi-data-list>
  2. 屬性型指令: 改變元素、組件或其它指令的外觀和行爲的指令,上面的nz-row,nz-col就是這類指令
  3. 結構型指令: 經過添加和移除 DOM 元素改變 DOM 佈局的指令,例如:*ngFor,*ngIf

這三種指令中最經常使用的就是組件,因此我重點學習了後兩種指令。segmentfault

2、屬性型指令

自定義屬性型指令

目標:使用指令實現給html元素綁定背景色:app

<button appHighlight = "yellow">高亮</button>

clipboard.png

  1. 生成指令類文件,同生成組件同樣,使用CLI命令便可框架

    ng generate directive highlight

    而後就會多了這兩個文件:less

    clipboard.png

  2. 在指令的邏輯部分:webstorm

    export class HighlightDirective implements OnInit {
    // 使用@input綁定傳給指令的值
      @Input('appHighlight') highlightColor: string;
      constructor(private el: ElementRef) {    //  ElementRef就是該指令綁定的宿主元素
      }
    
      ngOnInit(): void {
      // 設置該元素的css樣式的背景色爲輸入值
        this.el.nativeElement.style.backgroundColor = this.highlightColor;
      }
    }
  3. 使用該指令:ide

    <button appHighlight="yellow">高亮</button>
  4. 效果:
    clipboard.png
  5. 綁定多個屬性:

    export class HighlightDirective implements OnInit {
      @Input('appHighlight') highlightColor: string;
      @Input() defaultColor: string;    // 默認顏色
      constructor(private el: ElementRef) {
      }
    
      ngOnInit(): void {
      // 若是highlightColor有值,則綁定highlightColor,沒有則綁定defaultColor
        this.el.nativeElement.style.backgroundColor = this.highlightColor || this.defaultColor;
      }
    }
  6. 使用:

    <button appHighlight="yellow" defaultColor="red">高亮</button>

    clipboard.png

    <button appHighlight defaultColor="red">高亮</button>

    clipboard.png

selector:選擇器

若是我寫的指令不想被人亂用,好比上述指令我只想在button中生效,就要用到指令的選擇器(selector
)了。
通常咱們命令生成指令文件後會有以下默認的代碼段:

@Directive({
  selector: '[appHighlight]'
})

這個selector裏的字符串就是咱們使用指令綁定到宿主元素的規則,或者說實例化指令對象的規則。

angular文檔提供的規則表以下:

  • element-name:根據元素名選取。
  • .class:根據類名選取。
  • [attribute]:根據屬性名選取。
  • [attribute=value]:根據屬性名和屬性值選取。
  • :not(sub_selector):只有當元素不匹配子選擇器 sub_selector 的時候才選取。
  • selector1, selector2:不管 selector1 仍是 selector2 匹配時都選取。

例如,默認的綁定方法就是把該指令當成屬性,如:<button appHighlight>高亮</button>

若是我想只綁定給button,就能夠這樣寫:

@Directive({
  selector: '[appHighlight] button'
})

這樣一來,當我綁定到其餘元素時,編譯器就會報錯:

clipboard.png
若是我不想使用默認的指令前綴app,而是換成其餘的,如yunzhi,則如圖修改tslint.json文件

clipboard.png

修改後:

{
    "extends": "../tslint.json",
    "rules": {
        "directive-selector": [
            true,
            "attribute",
            "yunzhi",
            "kebab-case"
        ],
        "component-selector": [
            true,
            "element",
            "app",
            "kebab-case"
        ]
    }
}

指令選擇器也修改前綴:

@Directive({
  selector: '[yunzhi-highlight] button'
})

使用指令:

<button yunzhi-highlight>高亮</button>

3、結構型指令

自定義結構型指令

目標:編寫一個yunzhiUnless指令,做用與ngIf相反,當變量值爲false,顯示宿主元素,爲true則移除不顯示。

<p *yunzhiUnless="condition">測試-p</p>

clipboard.png

  1. 首先爲了讓指令更符合官方的規範,推薦在tsLint.json指令選擇器使用格式修改成cameCase,即默認設置

    "directive-selector": [
         true,
         "attribute",
         "yunzhi",
         "camelCase"
     ]
    @Directive({
      selector: '[yunzhiUnless]'
    })
  2. 新建指令unless,注入TemplateRef,ViewContainerRef(沒懂下面這句話,先跟着往下寫就行)

    使用TemplateRef取得 <ng-template> 的內容,並經過ViewContainerRef來訪問這個視圖容器
    export class UnlessDirective {
         constructor(private viewContainer: ViewContainerRef,
                 private templateRef: TemplateRef<any>) { }
     
     }
  3. 使用@Inout將一個布爾值綁定到yunzhiUnless屬性上,當condition爲false,顯示,爲true,不顯示

    @Input()
      set yunzhiUnless(condition: boolean) {
        if (!condition) {
          this.viewContainer.createEmbeddedView(this.templateRef);
        } else if (condition) {
          this.viewContainer.clear();
        }
      }
  4. 在模板中使用該指令

    <p *yunzhiUnless="condition">測試-p</p>
  5. 測試指令
    html:

    <p>
      condition={{condition | json}}
    </p>
    
    <label for="show">顯示</label>
    <input id="show" type="radio" [value]="false" [(ngModel)]="condition">
    <label for="unshow">不顯示</label>
    <input id="unshow" type="radio" [value]="true" [(ngModel)]="condition">
    
    <p *yunzhiUnless="condition">測試-p</p>

    ts:

    export class TestComponent {
      condition = false;
    }

效果:
clipboard.png

TemplateRef

如名字所示,TemplateRef 用於表示模板的引用,但咱們注意到這裏並無使用ng-template,爲何還能引用它呢?
這是由於*yunzhiUnless實際上是一個語法糖

<p *yunzhiUnless="condition">測試-p</p>

等同於:

<ng-template [yunzhiUnless]="condition">
  <p>測試-ng-template</p>
</ng-template>

若是沒有使用結構型指令,而僅僅把一些別的元素包裝進 <ng-template> 中,那些元素就是不可見的。

ViewContainerRef

ViewContainerRef 用於表示容器的引用,一般是span,div等

ViewContainerRef 對象提供了 createEmbeddedView() 方法,該方法接收 TemplateRef 對象做爲參數,並將模板中的內容做爲容器 (comment 元素) 的兄弟元素,插入到頁面中。

簡單理解就是以下圖,templateRef指向ng-template標籤,viewContainer指向ng-container標籤

clipboard.png

總結

簡單的瞭解了angular指令的實現後,發如今使用時減小了不少迷茫,有些之前不懂的細節也知道了爲何會這樣,不過也發現本身還有好多不懂的地方,好比模板和容器,DOM的一些操做等。

參考文章:
Angular 2.x 結構指令
angular官方文檔:屬性型指令
angular官方文檔:結構型指令

相關文章
相關標籤/搜索