創建自定義 AngularJS 指令:Part 2 孤立作用域

創建自定義AngularJS指令系列


在本系列第一篇文章介紹了AngularJS自定義指令並展示了幾個簡單的例子在這篇文章中,我們要探討孤立作用域的話題,看看它在構建指令時的重要性。


什麼是孤立作用域?


在AngularJS應用中,默認情況下指令會繼承父作用域。例如:下邊指令繼承父作用域的customer對象的name屬性和street屬性:

angular.module('directivesModule').directive('mySharedScope', function () {
    return {
        template: 'Name: {{customer.name}} Street: {{customer.street}}'
    };
});
這種繼承的方式雖然可以達到我們的目的,但爲了使用這個指令,必須知道父作用域的許多信息。我們可以很容易地使用ng-include和HTML模板完成同樣的功能(這個在第一章中討論過)。如果父作用域發生了改變,這個指令將不能使用。

如果你想創建一個可複用的指令,你必須使用孤立作用域而不是繼承父作用域。下邊是一個對比繼承作用域孤立作用域的圖表:


 從這個圖表可以看出來,繼承作用域方式下,指令可以使用父作用域中的數據。而孤立作用域不能繼承父作用中的數據。 對於孤立作用域,就好比創建了一道堅實的牆壁,父作用域無法穿透這道牆壁去影響指令。下邊的圖片是對這個概念的一個比喻:

             


在指令中創建孤立作用域


在指令中生成孤立作用域的方法很簡單。只需要在指令中添加一個scope屬性就可以了,如下邊所示。它會自動的將指令地隔離指令和父作用域。

angular.module('directivesModule').directive('myIsolatedScope', function () {
    return {
        scope: {},
        template: 'Name: {{customer.name}} Street: {{customer.street}}'
    };
});

由於指令作用域孤立的,因此,父作用域的customer對象在指令中將不可用。這個指令在視圖中顯示結果如下(customer對象的namestreet屬性值沒有被渲染出來):


Name: Street:


由於指令使用孤立作用域與父作用域完全隔離開來,那麼,怎樣將數據傳入指令中實現數據綁定的目的呢?你可以使用@,=和&符號實現這些功能,這些符號乍一看比較奇怪,但當你真正明白它們代表的含義,你就不會感覺太糟糕。讓我們看下這些符號在指令中是怎樣使用的。


指令內部scope的屬性介紹


使用孤立作用域的指令,提供了三種不同的與外部互動的方式。這三種方式被稱爲指令內部Scope屬性,可以使用前邊提到的@,=和&符號來定義。這裏介紹是它們是怎樣工作的。


指令"@"類型的Scope屬性


@類型的Scope屬性作用是接收從外部傳入的字符串變量。例如,外部的控制器在它的$scope對象中定義了一個name屬性,你需要將這個屬性傳入具有孤立作用域的指令中。你可以通過使用@符號在指令的scope屬性中實現這個功能。下邊是一個一步一步解釋這個概念的高水平的例子:

  1. 一個外部控制器定義了$scope.name
  2. 這個$scope.name屬性需要傳入指令中。
  3. 指令創建了一個內部scope對象用於它的孤立作用域,其中有name屬性(注意:這個屬性名可以自定義,並不需要跟外部$scope對象的name屬性名一樣。只要保持標籤上的屬性名字和指令內部scope對象內的屬性名一致即可)。這裏使用了一致的名字scope { name: ‘@’ }
  4. @字符告訴指令,將要傳給name屬性的值是一個字符串。如果改變外部作用域的name屬性的值,指令的孤立作用域中也會自動同步改變name屬性的值。
  5. 這個模板現在綁定了孤立作用域的name屬性。

Here’s an example of putting all of the steps together. Assume that the following controller is defined within an app:

下邊是一個包含所有步驟的示例。假定下列控制器定義在一個應用中

var app = angular.module('directivesModule', []);

app.controller('CustomersController', ['$scope', function ($scope) {
    var counter = 0;
    $scope.customer = {
        name: 'David',
        street: '1234 Anywhere St.'
    };
            
    $scope.customers = [
        {
            name: 'David',
            street: '1234 Anywhere St.'
        },
        {
            name: 'Tina',
            street: '1800 Crest St.'
        },
        {
            name: 'Michelle',
            street: '890 Main St.'
        }
    ];

    $scope.addCustomer = function () {
        counter++;
        $scope.customers.push({
            name: 'New Customer' + counter,
            street: counter + ' Cedar Point St.'
        });
    };

    $scope.changeData = function () {
        counter++;
        $scope.customer = {
            name: 'James',
            street: counter + ' Cedar Point St.'
        };
    };
}]);


下邊的指令代碼創建了一個孤立作用域,允許name屬性和外部傳進來的值進行綁定:

angular.module('directivesModule').directive('myIsolatedScopeWithName', function () {
    return {
        scope: {
            name: '@'
        },
        template: 'Name: {{ name }}'
    };
});

上邊定義的指令可以像下邊這樣使用:

<div my-isolated-scope-with-name name="{{ customer.name }}"></div>

請注意$scope.customer.name的值是怎樣綁定到指令孤立作用域中的name屬性上的。上邊代碼在瀏覽器中渲染出來的效果如下:


Name: David


正如上邊提到的,如果$scope.customer.name的值發生變化,指令將自動檢測到這種變化,同時改變內部name屬性的值,並且將結果渲染到頁面中。然而,如果指令內部改變了name屬性 的值,外部的$scope.customer.name卻不能檢測到這種變化,因此不會發生變化。這種綁定方式可以稱作”單向綁定“。如果你要保持指令內外的數據同步變化(雙向綁定),可以使用下邊將要講到的"=類型的Scope屬性".

如果你要使用一個和你外部作用域屬性名不同的標籤屬性名傳入指令內部屬性名,你可以使用下邊語法方式:

angular.module('directivesModule').directive('myIsolatedScopeWithName', function () {
    return {
        scope: {
            name: '@someOtherName'
        },
        template: 'Name: {{ name }}'
    };
});


指令"="類型的Scope屬性


在上邊的例子中,當要給指令傳入一個字符串類型的變量時,使用@符號可以很好的實現這個功能。然而,當指令內部變量發生改變時,外部作用域中對應變量不會同步改變。因此,當你要創建一個外部作用域和指令的孤立作用域雙向綁定的指令時,可以使用=符號。下邊是一個一步一步闡述這個概念的示例:

  1. 一個控制器定義了一個$scope.person對象。
  2. 這個$scope.person對象需要以雙向綁定的方式傳入指令。
  3. 指令創建一個名字叫customer的內部作用域屬性在它的孤立作用域中。這裏是scope { customer: ‘=’ }
  4. 這個=符號告訴指令:這個被傳入customer屬性的外部對象應該使用雙向綁定的方式。如果外部對象值發生改變,指令的customer屬性也自動更新;如果指令的customer屬性發生改變,外部作用域的對象值也會同步更新。
  5. 這個指令中的模板現在可以使用綁定到孤立作用域的customer 屬性。

下邊示例是一個在孤立作用域中使用=符號定義屬性的指令:

angular.module('directivesModule').directive('myIsolatedScopeWithModel', function () {
    return {
        scope: {
            customer: '=' //Two-way data binding
        },
        template: '<ul><li ng-repeat="prop in customer">{{ prop }}</li></ul>'
    };
});

在這個示例中,指令獲取到customer屬性中的對象,並使用ng-repeat指令遍歷這個對象中的所有屬性,然後將屬性值使用<li>元素展示出來。

給指令傳入數據通過下邊代碼方式:

<div my-isolated-scope-with-model customer="customer"></div>

注意一點:在使用=符號方式給指令傳入數據時,你傳入的是一個對象,而不是對象的屬性值(指令實際上只是需要這個屬性值),這樣做是比較規範的方式,建議讀者都是用這種方式,以避免有些特殊問題。

這個例子中,外部控制器的customer對象,傳入指令內部作用域的customer屬性。指令使用ng-repeat遍歷customer的所有屬性,並將其值全部顯示出來。下邊是這個指令運行後在頁面中顯示的內容:

  • David
  • 1234 Anywhere St.


指令"&"類型的Scope屬性


到目前爲止,你已經明白怎樣給指令傳入字符串變量(使用@符號),也明白了雙向數據綁定一個外部變量的技術(使用=符號)。最後,我們學習怎樣使用&符號綁定一個外部函數到指令中。

&類型的scope屬性允許自定義指令接收一個可使用的外部函數。例如,假設你寫了一個指令,在指令中的template模板中,有一個button按鈕,當點擊這個按鈕時,指令外部的控制器需要知道這個操作,並且做出相應的處理。你不能在指令中編寫click的回調函數,因爲外部控制器不能監聽到發生了什麼事。使用一個事件可以完成這個功能(使用$emit or $broadcast),但控制器不知道這個事件名,因此也無法監聽。這種做法也並不是最優的。

一種比較好的做法是給自定義指令傳入一個函數,當指令需要時調用這個函數。當指令需要使用這個被傳入的函數時(例如:監聽到有人點擊了這個按鈕),就可以直接調用它。這種方式,自定義指令對發生的事件有百分之百的控制權,並授權從外傳入的函數控制權。下邊是一個一步一步闡述這個概念的例子:

  1. 外部控制器定義了一個$scope.click函數。
  2. $scope.click函數需要傳入指令中,以便按鈕被點擊時,指令能夠調用它。
  3. 指令在它的孤立作用域中創建了一個內部的action屬性。在這裏是scope { action: ‘&’ }。在這個例子中,action僅僅是click的一個別名。當action被使用時,被傳入的click函數將被調用。
  4. &字符告訴外部作用域:”嗨,傳給我一個函數,以便當指令內部發生某事時,我可以調用它“。
  5. 指令內部的模板包含一個按鈕,當點擊按鈕時,action函數(實際是外部傳入的函數)將被調用。

下邊是一個&型scope屬性的使用例子:

angular.module('directivesModule').directive('myIsolatedScopeWithModelAndFunction', function () {
    return {
        scope: {
            datasource: '=',
            action: '&'
        },
        template: '<ul><li ng-repeat="prop in datasource">{{ prop }}</li></ul> ' +
                  '<button ng-click="action()">Change Data</button>'
    };
});


可也看到,指令模板代碼中有下邊的DOM,模板使用了action屬性,並在按鈕被點擊時調用它。無論外部傳入了什麼函數給action,都將執行它。

<button ng-click="action()">Change Data</button>

Here’s an example of using the directive. I’d of course recommend picking a shorter name for your “real life” directives.

下邊是一個使用這個指令的例子。當然,我推薦你自己寫指令時,定義一個比較短的指令名。

<div my-isolated-scope-with-model-and-function 
     datasource="customer" 
     action="changeData()">
</div>

changeData()函數被定義在這篇博文開始部分的控制器中,它會被傳入指令的action屬性中。它的作用是改變name和address:

$scope.changeData = function () {
      counter++;
      $scope.customer = {
          name: 'James',
          street: counter + ' Cedar Point St.'
      };
};

總結


到目前爲止,你已經瞭解到AngularJS指令中一些關鍵並且有用的方面,例如:模板、孤立作用域、內部scope屬性。

作爲回顧,孤立作用域在指令中使用scope屬性創建,scope是一個對象類型的屬性。三種類型的scope屬性可以被添加到孤立作用域上:

  • @  用作給指令傳入字符串變量
  • =    用於給傳入指令的對象創建雙向綁定功能
  • &    用於給指令傳入一個外部函數,並可以在指令中調用

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章