AngularJS 的 scope 選項與繫結策略

oschina發表於2015-05-02

開門見山地說,scope:{}使指令與外界隔離開來,使其模板(template)處於non-inheriting(無繼承)的狀態,當然除非你在其中使用了transclude嵌入,這點之後的筆記會再詳細記錄的。但是這顯然不符合實際開發中的需求,因為實際上,我們經常想要我們的指令能夠在特定的情況下與外界進行資料上的互動,這就需要藉助繫結策略之手了。

大家知道,當scope選項寫為scope:{}這種形式的時候,就已經為指令生成了隔離作用域,現在,我們來看看繫結策略的三種形式:& 、= 、@。

首先是@,它將本地作用域和DOM中的屬性值繫結起來(且這個屬性的值必須是父級作用域中的),什麼意思呢?說的簡單一點就是假設你在模板中有個雙花括號表示式,然後我們把表示式裡的內容和html中指令裡特定名字的屬性繫結起來,還是不懂?看看下面的程式碼:

JS程式碼:

directive("direct",function(){

        return{

            restrict: 'ECMA',

            template: '<div>指令中:{{ name }}</div>',

            scope:{

              name:'@forName'

            }
         } 
  })
.controller("nameController",function($scope){
      $scope.Name="張三"; 
});

HTML程式碼:

<div ng-controller="nameController">
   <direct for-name="{{ Name }}"></direct>
<div>

執行結果可想而知,{{ name }}成功地與父控制器中的Name繫結起來了。當然這裡也可以這樣寫

name:’@’ 這樣寫的話,就預設DOM中的屬性名為name了意即 for-name=”{{ Name }}”可簡寫為name=”{{ Name }}”;其實,另外兩個符號=和&也有這樣的簡寫規則,方便起見接下來都使用這種寫法。

@到此為止,接下來就是’='了。=與@的不同點在於,@是針對字串(準確來說是表示式expression)而用,但=是針對某個物件的引用,

這麼說可能不太專業,但就拿上邊的例子而言,我們在html中,把Name這個字串通過一對雙花括號傳遞給for-name屬性,但如果我們用了=,這裡傳入的Name就不應該是一個字串,而是一個物件的引用。這不是一個很一目瞭然的概念,所以我用接下來的兩個例子詮釋它的含義。

第一個例子:陣列中的物件的引用

JS程式碼:

directive("direct",function(){

        return{

            restrict: 'ECMA',

            template: '<div>指令中:{{ case.name }}</div>',

            scope:{

              case:'='

            }

         } 

  })

.controller("nameController",function($scope){

      $scope.data=[{name:"張三"},{name:"李四"}]; 

});

HTML程式碼:

<div ng-controller="nameController">

   <direct case="data[0]"></direct>

   <direct case="data[1]"></direct> 
<div>

結果就是,一個張三,一個李四。這個例子中,data是一個物件陣列,裡面包含了兩個物件,所以,我們分別把兩個物件傳遞給了case這個屬性,case屬性就把這個物件的引用傳遞給了模板中我們寫的{{ case.name }}中的case;而如果你在=後邊加上了自己定義的名字,那隻要把html裡case屬性換成那個名字就可以了。

第二個例子:經典的雙向輸入框

按照Angular的入門案例,建立兩個雙向繫結的輸入框,最簡單的實現方式就是:

<input ng-model="test"/>
 <input ng-model="test"/>

使用ng-model指令就可以做到了。接著,我們在自己的指令中實現這個效果。

JS程式碼:

directive("direct",function(){

        return{

            restrict: 'ECMA',

            template: '<div>指令中:<input ng-model="model"/></div>',

            scope:{

              model:'='

            }

         } 

  })

.controller("nameController",function($scope){

      $scope.data=[{name:"張三"},{name:"李四"}]; 

});

HTML程式碼:

 <div ng-controller="nameController">

        父級scope中:<input ng-model="mark"/>

        <direct model="mark"/></direct>
 </div>

這就完成了,其實只不過是加了一點小把戲,把ng-model換成了model而已。

注意到,這兩個例子中,都是使用物件的引用,而不是單純的字串,這也是=可以進行雙向繫結的關鍵。

最後是&符號。它的含義是:對父級作用域進行繫結,並將其中的屬性包裝成一個函式,注意,是屬性,意即,任何型別的屬性都會被包裝成一個函式,比如一個單純的字串,或是一個物件陣列,或是一個函式方法,如果是字串、物件陣列和無參的函式,那麼可想而知,它們都會被包裝成一個無參的函式,若是有參的函式方法則反之,並且我們需要為其傳入一個物件。現在,分別針對有參和無參兩種情況舉例。

無參情況↓

JS程式碼:

.directive("direct",function(){

        return{

            restrict: 'ECMA',

            template: '<div>{{ title }}</div>'+'<div><ul><li ng-repeat="x in contents">{{ x.text }}<                       /li></ul></div>',

            scope:{

              getTitle:'&', 
              getContent:'&'             
         },
            controller:function($scope){ 
               $scope.title=$scope.getTitle();     //呼叫無參函式  
               $scope.contents=$scope.getContent();    //呼叫無參函式 
           } 
      } 
 })

.controller("nameController",function($scope){

    $scope.title="標題";

    $scope.contents =[{text:1234},{text:5678}]; 
});

HTML程式碼:

<div ng-controller="nameController">
      <direct get-title="title" get-content="contents"></direct> 
  </div>

這個例子有幾個注意點:

1.指令的本地屬性(即模板裡花括號中的屬性)需要從本地取值,所以使用了controller選項,而在controller選項中,兩個無參方法分別返回了父級scope中的title字串和contents物件陣列。

2.在HTML中,我們把設定了get-title和get-content的屬性值為title和contents,這實際上就完成了與父級scope的繫結,因為我們才可以從那兒取得實質的內容。

OK,有參情況↓

JS程式碼:

.directive("direct",function(){ 
return{
            restrict: 'ECMA',
            template: '<div><input ng-model="model"/></div>'+'<div><button ng-click="show({name:model})">show</button>',
            scope:{
                show:'&'              
            }                      
         }
    })

    .controller("nameController",function($scope){
        $scope.showName=function(name){ 

          alert(name); 
         } 
     });

HTML程式碼:

<div ng-controller="nameController">

      <direct show="showName(name)"></direct> 

  </div>

這個例子中,通過模板中的ng-click觸發了show函式並將一個叫做model的物件作為name引數傳遞了進去,而在html中,我們把show的屬性值設為showName(name)。這其中的道理跟無參的例子是大同小異的。

總結:

為什麼Angular要為我們提供這樣一套繫結策略呢?就是因為它想讓我們在為指令建立隔離作用域的同時,還能訪問到父級中的屬性,這就像,你在隔離作用域身上打了一個洞,然後用一條管道,把指令內部和外界的屬性給連起來(繫結),並且一切的通訊都只能通過這條管道來實行。這是我目前能做到的最深刻的理解了,可能還有需要的補充和糾正的地方,希望大家能抽空指點我一下,感激不盡!

相關文章