1. 程式人生 > >建立angular自定義指令

建立angular自定義指令

——本文轉自他處,稍作修改,完全為了方便自己理解,如有侵犯,請告知。

指令(Directives)是所有AngularJS應用最重要的部分。儘管AngularJS已經提供了非常豐富的指令,但還是經常需要建立應用特定的指令。

一個Angular指令可以有以下的四種表現形式: 

1. 一個新的HTML元素(<data-picker></data-picker>) (A)

2. 元素的屬性(<input type=”text” data-picker/>) (E)

3. CSS class(<input type=”text” class=”data-picker”/>) (C)

4. 註釋(<!–directive:data-picker –>) (M)

當然,我們可以控制我們的指令在HTML中的表現形式。下面我們來看一下AngularJS中的一個典型的指令的寫法。指令註冊的方式與 controller 一樣,但是它返回的是一個擁有指令配置屬性的簡單物件(指令定義物件) 。

首先,建立一個指令:

var app = angular.module('myapp', []);
app.directive('helloWorld', function() {
  return {
      restrict: 'AE',
      replace: 'true',
      template: '<h1>Hello World!!</h1>'
  };
});
通過指令定義的元素屬性(A)和標籤屬性(E)來使用該指令:

元素屬性使用:

<hello-world/>或<hello:world/>
標籤屬性使用:
<div hello-world></div>或<div hello:world/>
通過上面的例子,我們看到restrict設定為AE,其意如上所說,共有四種定義,分別為A,E,C,M;

還有replace設定為true代表著生成的HTML內容是會替換掉定義此指令的HTML元素,即

<hello-world/>或<hello:world/>、<div hello-world></div>或<div hello:world/>
如果為false,則預設值,生成的模板會被插入到元素中去;

還有templat:這個屬性規定了指令被Angular編譯和連結(link)後生成的HTML標記。這個屬性值不一定要是簡單的字串。template 可以非常複雜,而且經常包含其他的指令,以及表示式({{ }})等。更多的情況下你可能會見到 templateUrl, 而不是 template。所以,理想情況下,你應該將模板放到一個特定的HTML檔案中,然後將 templateUrl 屬性指向它。

link和scope

如上模板並沒有太大意義,一般我們會在特定的scope下編譯。預設情況下,指令並不會建立新的子scope。預設情況下它使用父scope。也就是說,如果指令存在於一個controller下,它就會使用這個controller的scope。 如何運用scope,我們要用到一個叫做 link 的函式。它由指令定義物件中的link屬性配置。我們來改變一下我們的 helloWorld 指令,當用戶在一個輸入框中輸入一種顏色的名稱時,Hello World 文字的背景色自動發生變化。同時,當用戶在 Hello World 文字上點選時,背景色變回白色。 相應的HTML標記如下:
<body ng-controller="myController">
  <input type="text" ng-model="color" />
  <hello-world/>
</body>
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:指令的DOM。該DOM為jQuery包裝過的,所以可以通過jQuery來操作; attrs:一個包含了指令所在元素的屬性的標準化的引數物件;(理解的有點模糊) link函式主要用來為DOM元素新增事件監聽、監視模型屬性變化、以及更新DOM。在上面的指令程式碼片段中,我們添加了兩個事件, click,和 mouseover。click 處理函式用來重置 <p> 的背景色,而 mouseover 處理函式改變滑鼠為 pointer。在模板中有一個表示式 {{color}},當父scope中的 color 發生變化時,它用來改變 Hello World 文字的背景色。

compile函式

compile 函式在 link 函式被執行之前用來做一些DOM改造。它接收下面的引數:
1、tElement – 指令所在的元素
2
attrs – 元素上賦予的引數的標準化列表 要注意的是 compile 函式不能訪問 scope,並且必須返回一個 link 函式。但是如果沒有設定 compile 函式,你可以正常地配置 link 函式,(有了compile,就不能用link,link函式由compile返回)。compile函式可以寫成如下的形式:
app.directive('myDirective', function() {
  return {
    compile: function(tElem,attrs) {
      return function(scope,elem,attrs) {
        
      };
    }
  };
});
多數情況下我們只用到link函式,這是因為大部分的指令只需要考慮註冊事件監聽、監視模型、以及更新DOM等,這些都可以在 link 函式中完成。

改變指令的scope

預設情況下,指令獲取它父節點的controller的scope。但這並不適用於所有情況。如果將父controller的scope暴露給指令,那麼他們可以隨意地修改 scope 的屬性。在某些情況下,你的指令希望能夠新增一些僅限內部使用的屬性和方法。如果我們在父的scope中新增,會汙染父scope。 其實我們還有兩種選擇: 1、子scope :這個scope原型繼承子父scope。 2、隔離的scope –:孤立存在不繼承自父scope的scope。

這樣的scope可以通過指令定義物件中 scope 屬性來配置:
繼承父scope的子scope:
app.directive('helloWorld', function() {
  return {
    scope: true, 
    restrict: 'AE',
    replace: 'true',
    template: '<h3>Hello World!!</h3>'
  };
});
隔離的scope:
app.directive('helloWorld', function() {
  return {
    scope: {}, 
    restrict: 'AE',
    replace: 'true',
    template: '<h3>Hello World!!</h3>'
  };
});

通過隔離scope,我們可以建立重用的指令非常有好處,我們能夠保證我們的指令是自包含的,可以被很容易的插入到HTML應用中。 它內部不能訪問父的scope,所保證了父scope不被汙染。

隔離scope和父scope之間的資料繫結

隔離指令的scope會帶來很多的便利,尤其是在你要操作多個scope模型的時候。但有時為了使程式碼能夠正確工作,你也需要從指令內部訪問父scope的屬性。

選擇一:使用 @ 實現單向文字繫結

在下面的指令定義中,我們指定了隔離scope中的屬性 color 繫結到指令所在HTML元素上的引數 colorAttr。在HTML標記中,你可以看到 {{color}}表示式被指定給了 color-attr 引數。當表示式的值發生改變時,color-attr 引數也跟著改變。隔離scope中的 color 屬性的值也相應地被改變。
app.directive('helloWorld', function() {
  return {
    scope: {
      color: '@colorAttr'
    },
    ....
  };
});
<body ng-controller="MainCtrl">
  <input type="text" ng-model="color" />
  <hello-world color-attr="{{color}}"/>
</body>
注意點:
當隔離scope屬性和指令元素引數的名字一樣時,我們可以更簡單的方式設定scope繫結:
app.directive('helloWorld', function() {
  return {
    scope: {
      color: '@'
    },
    ....
  };
});
<hello-world color="{{color}}"/>

選擇二:使用 = 實現雙向繫結

我們把指令的定義改變成下面的樣子:
app.directive('helloWorld', function() {
  return {
    scope: {
      color: '='
    },
    ....
  };
});
<body ng-controller="MainCtrl">
  <input type="text" ng-model="color" />
  <hello-world color="color"/>
</body>
與 @ 不同,這種方式讓你能夠給屬性指定一個真實的scope資料模型,而不是簡單的字串。這樣你就可以傳遞簡單的字串、陣列、甚至複雜的物件給隔離scope。同時,還支援雙向的繫結。每當父scope屬性變化時,相對應的隔離scope中的屬性也跟著改變,反之亦然。和之前的一樣,你也可以監視這個scope屬性的變化。

選擇三:使用 & 在父scope中執行函式

有時候從隔離scope中呼叫父scope中定義的函式是非常有必要的。為了能夠訪問外部scope中定義的函式,我們使用 &。比如我們想要從指令內部呼叫 sayHello() 方法。
app.directive('sayHello', function() {
  return {
    scope: {
      sayHelloIsolated: '&'
    },
    ....
  };
});
<body ng-controller="MainCtrl">
  <input type="text" ng-model="color" />
  <say-hello sayHelloIsolated="sayHello()"/>
</body>