指令的Link函數和Scope


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

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的屬性。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM