angularJs之指令詳解(一)

    指令(Directives)是全部AngularJS應用最重要的部分。儘管AngularJS已經提供了很是豐富的指令,但仍是常常須要建立應用特定的指令。這篇教程會爲你講述如何自定義指令,以及介紹如何在實際項目中使用。在這篇文章的最後(第二部分),我會指導你如何使用Angular指令來建立一個簡單的記事本應用。css

    概述

    一個指令用來引入新的HTML語法。指令是DOM元素上的標記,使元素擁有特定的行爲。舉例來講,靜態的HTML不知道如何來建立和展示一個日期選擇器控件。讓HTML能識別這個語法,咱們須要使用指令。指令經過某種方法來建立一個可以支持日期選擇的元素。咱們會按部就班地介紹這是如何實現的。 若是你寫過AngularJS的應用,那麼你必定已經使用過指令,無論你有沒有意識到。你確定已經用過簡單的指令,好比 ng-mode, ng-repeat, ng-show等。這些指令都賦予DOM元素特定的行爲。例如,ng-repeat 重複特定的元素,ng-show 有條件地顯示一個元素。若是你想讓一個元素支持拖拽,你也須要建立一個指令來實現它。指令背後基本的想法很簡單。它經過對元素綁定事件監聽或者改變DOM而使HTML擁有真實的交互性。html

    建立自定義指令

    一個Angular指令能夠有如下的四種表現形式:app

    1. 一個新的HTML元素<data-picker></data-picker>函數

    2. 元素的屬性<input type=」text」 data-picker/>性能

    3. CSS class<input type=」text」 class=」data-picker」/>spa

    4. 註釋<!–directive:data-picker –>rest

    固然,咱們能夠控制咱們的指令在HTML中的表現形式。下面咱們來看一下AngularJS中的一個典型的指令的寫法。指令註冊的方式與 controller 同樣,可是它返回的是一個擁有指令配置屬性的簡單對象(指令定義對象) 。下面的代碼是一個簡單的 Hello World 指令。code

    js代碼:
orm

var app = angular.module('myapp', []);
app.directive('helloWorld', function() {
  return {
      restrict: 'AE',
      replace: 'true',
      template: '<h3>Hello World!!</h3>'
  };
});

    html代碼:
htm

<div hello-world></div>

    在上面的代碼中,app.directive()方法在模塊中註冊了一個新的指令。這個方法的第一個參數是這個指令的名字。第二個參數是一個返回指令定義對象的函數。若是你的指令依賴於其餘的對象或者服務,好比 $rootScope, $http, 或者$compile,他們能夠在這個時間被注入。

咱們在指令定義過程當中使用了三個屬性來配置指令。咱們來一一介紹他們的做用。

    • restrict – 這個屬性用來指定指令在HTML中如何使用(有四種表示方式A E C M)。在上面的例子中,咱們使用了 ‘AE’。因此這個指令能夠被看成新的HTML元素或者屬性來使用。若是要容許指令被看成class來使用,咱們將 restrict 設置成 ‘AEC’。

    • template – 這個屬性規定了指令被Angular編譯和連接(link)後生成的HTML標記。這個屬性值不必定要是簡單的字符串。template 能夠很是複雜,並且常常包含其餘的指令,以及表達式({{ }})等。更多的狀況下你可能會見到 templateUrl, 而不是 template。因此,理想狀況下,你應該將模板放到一個特定的HTML文件中,而後將 templateUrl 屬性指向它。

    • replace – 這個屬性指明生成的HTML內容是否會替換掉定義此指令的HTML元素。在咱們的例子中,咱們用 <hello-world></hello-world>的方式使用咱們的指令,而且將 replace 設置成 true。因此,在指令被編譯以後,生成的模板內容替換掉了 <hello-world></hello-world>。最終的輸出是 <h3>Hello World!!</h3>。若是你將 replace 設置成 false,也就是默認值,那麼生成的模板會被插入到定義指令的元素中。

    link函數和scope

    指令生成出的模板其實沒有太多意義,除非它在特定的scope下編譯。默認狀況下,指令並不會建立新的子scope。更多的,它使用父scope。也就是說,若是指令存在於一個controller下,它就會使用這個controller的scope。 如何運用scope,咱們要用到一個叫作 link 的函數。它由指令定義對象中的link屬性配置。讓咱們來改變一下咱們的 helloWorld 指令,當用戶在一個輸入框中輸入一種顏色的名稱時,Hello World 文字的背景色自動發生變化。同時,當用戶在 Hello World 文字上點擊時,背景色變回白色。 這個 plunker 演示了這些概念。

相應的HTML標記以下:

<body ng-controller="MainCtrl">
  <input type="text" ng-model="color" placeholder="Enter a color" />
  <hello-world/>
</body>

修改後的 helloWorld 指令以下:

app.directive('helloWorld', function() {
  return {
    restrict: 'AE',
    replace: true,
    template: '<p style="background-color:{{color}}">Hello World',
    link: function(scope, elem, attrs) {
      elem.bind('click', function() {
        elem.css('background-color', 'white');
        scope.$apply(function() {
          scope.color = "white";
        });
      });
      elem.bind('mouseover', function() {
        elem.css('cursor', 'pointer');
      });
    }
  };
});

咱們注意到指令定義中的 link 函數。 它有三個參數:

    • scope – 指令的scope。在咱們的例子中,指令的scope就是父controller的scope。

    • elem – 指令的jQLite(jQuery的子集)包裝DOM元素。若是你在引入AngularJS以前引入了jQuery,那麼這個元素就是jQuery元素,而不是jQLite元素。因爲這個元素已經被jQuery/jQLite包裝了,因此咱們就在進行DOM操做的時候就不須要再使用 $()來進行包裝。

    • attr – 一個包含了指令所在元素的屬性的標準化的參數對象。舉個例子,你給一個HTML元素添加了一些屬性:,那麼能夠在 link 函數中經過 attrs.someAttribute 來使用它。

    link函數主要用來爲DOM元素添加事件監聽、監視模型屬性變化、以及更新DOM。在上面的指令代碼片斷中,咱們添加了兩個事件, click,和 mouseover。click 處理函數用來重置 <p> 的背景色,而 mouseover 處理函數改變鼠標爲 pointer。在模板中有一個表達式 {{color}},當父scope中的 color 發生變化時,它用來改變 Hello World 文字的背景色。

compile函數

    compile 函數在 link 函數被執行以前用來作一些DOM改造。它接收下面的參數:

    • tElement – 指令所在的元素

    • attrs – 元素上賦予的參數的標準化列表

    要注意的是 compile 函數不能訪問 scope,而且必須返回一個 link 函數。可是若是沒有設置 compile 函數,你能夠正常地配置 link 函數,(有了compile,就不能用link,link函數由compile返回)。compile函數能夠寫成以下的形式:

app.directive('test', function() {
  return {
    compile: function(tElem,attrs) {
      //do optional DOM transformation here
      return function(scope,elem,attrs) {
        //linking function here
      };
    }
  };
});

    大多數的狀況下,你只須要使用 link 函數。這是由於大部分的指令只須要考慮註冊事件監聽、監視模型、以及更新DOM等,這些均可以在 link 函數中完成。 可是對於像 ng-repeat 之類的指令,須要克隆和重複 DOM 元素屢次,在 link 函數執行以前由 compile 函數來完成。這就帶來了一個問題,爲何咱們須要兩個分開的函數來完成生成過程,爲何不能只使用一個?要回答好這個問題,咱們須要理解指令在Angular中是如何被編譯的!

指令是如何編譯的

    當應用引導啓動的時候,Angular開始使用 $compile 服務遍歷DOM元素。這個服務基於註冊過的指令在標記文本中搜索指令。一旦全部的指令都被識別後,Angular執行他們的 compile 方法。如前面所講的,compile 方法返回一個 link 函數,被添加到稍後執行的 link 函數列表中。這被稱爲編譯階段。若是一個指令須要被克隆不少次(好比 ng-repeat),compile函數只在編譯階段被執行一次,複製這些模板,可是link 函數會針對每一個被複制的實例被執行。因此分開處理,讓咱們在性能上有必定的提升。這也說明了爲何在 compile 函數中不能訪問到scope對象。 在編譯階段以後,就開始了連接(linking)階段。在這個階段,全部收集的 link 函數將被一一執行。指令創造出來的模板會在正確的scope下被解析和處理,而後返回具備事件響應的真實的DOM節點。

改變指令的scope

    默認狀況下,指令獲取它父節點的controller的scope。但這並不適用於全部狀況。若是將父controller的scope暴露給指令,那麼他們能夠隨意地修改 scope 的屬性。在某些狀況下,你的指令但願可以添加一些僅限內部使用的屬性和方法。若是咱們在父的scope中添加,會污染父scope。 其實咱們還有兩種選擇:

  • 一個子scope – 這個scope原型繼承子父scope。

  • 一個隔離的scope – 一個孤立存在不繼承自父scope的scope。

這樣的scope能夠經過指令定義對象中 scope 屬性來配置。下面的代碼片斷是一個例子:

app.directive('helloWorld', function() {
  return {
    scope: true,  // use a child scope that inherits from parent
    restrict: 'AE',
    replace: 'true',
    template: '<h3>Hello World!!</h3>'
  };
});

上面的代碼,讓Angular給指令建立一個繼承自父socpe的新的子scope。 另一個選擇,隔離的scope:

app.directive('helloWorld', function() {
  return {
    scope: {},  // use a new isolated scope
    restrict: 'AE',
    replace: 'true',
    template: '<h3>Hello World!!</h3>'
  };
});

    這個指令使用了一個隔離的scope。隔離的scope在咱們想要建立可重用的指令的時候是很是有好處的。經過使用隔離的scope,咱們可以保證咱們的指令是自包含的,能夠被很容易的插入到HTML應用中。 它內部不能訪問父的scope,所保證了父scope不被污染。 在咱們的 helloWorld 指令例子中,若是咱們將 scope 設置成 {},那麼上面的代碼將不會工做。 它會建立一個新的隔離的scope,那麼相應的表達式 {{color}} 會指向到這個新的scope中,它的值將是 undefined. 使用隔離的scope並不意味着咱們徹底不能訪問父scope的屬性。其實有一些技術能夠容許咱們訪問父scope的屬性,甚至監視他們的變化。咱們會在指令這個系列的第二部分中討論這些技術,以及一些更高級的概念,好比 Controller 函數。

相關文章
相關標籤/搜索