AngularJS提供了很多指令可以協助我們操作DOM、處理事件、資料繫結、綁定控制器與範圍(ngView)等等。例如ngClick、ngShow、ngHide、ngRepeat以及其它很多AngularJS核心的指令都可以協助我們很輕鬆的使用這個架構。
雖然內建的指令已經覆蓋了大部分的使用情境,但在實際使用中為了簡化操作或組件重用等我們經常需要建立自己的指令。在這個系列的文章中我將一步步帶你瞭解AngularJS指令是如何工作的,以及如何開始使用/建立它們。
在這個系列的文章中我們假定你已經知道指令是什麼並且知道如何使用它們。如果你還不知道指令如何使用可以點擊這裡瞭解一些基本的用法。
編寫AngularJS指令難嗎?
AngularJS的指令對第一次接觸它的人來說可能會嚇一跳。它有很多選項,有一些複雜的特性,對第一次使用來說確實有些挑戰。一旦你對它有一些瞭解你會發現其實並沒有那麼糟。如果比喻成樂器的話,當你第一次彈鋼琴或吉他時你會感覺無從下手以及難以駕馭,然而在經過一段時間必要的練習後你會慢慢開始熟練甚至彈出一些優美的曲子。
開始自訂指令
為什麼需要自訂指令?想一下如果你正在執行一項任務:把客戶資料的集合轉換成指定的格式輸出到一個表格中——你當然可以選擇直接添加DOM來完成,但這樣做的話會使測試和控制變得困難而且使關注分離,這在AngularJS中是很不好的實現,你肯定不想這麼做。作為替代方案,你應該自訂一個指令來完成上面的操作。另一方面,你可能有一些資料繫結會多次在不同的視圖中出現並且你想重用這些資料繫結。當使用ngInclude載入一個子視圖時,指令仍然能很好的工作。當然,指令還有很多實用的情境,上面說的也只是表面。
讓我們直接來看一個基本的指令的例子。假設我們在程式中定義了以下模組和控制器:
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: {{customer.name}}
<br />
Street: {{customer.street}}
一種重用方法是把這部分HTML寫在一個子視圖中(在這裡我們命名為myChildView.html),並且在父視圖中使用ngInclude來使用它。這使得myChildView.html在程式中得到重用。
<div ng-include="'myChildView.html'"></div>
雖然這能夠完成任務,顯然另一種更好的方案是把資料繫結運算式寫到一個自訂指令中。要建立一個指令,首先需要建立一個指令所屬的目標模組(module),並在模組上調用directive()方法。directive()方法有一個名稱和一個函數作為參數,以下是一個簡單的指令嵌入資料繫結運算式的例子。
angular.module('directivesModule')
.directive('mySharedScope', function () {
return {
template: 'Name: {{customer.name}}<br /> Street: {{customer.street}}'
};
});
這是不是說可以使用自訂指令來代替ngInclude指令載入子視圖? 不止如此。指令可以通過很少一部分代碼來完成很多功能,它可以使DOM與商務邏輯之間的關係變得更簡單。下面是一個簡單的把mySharedScope指令綁定到一個<div>元素上的例子:
<div my-shared-scope></div>
當指令執行後將會輸出下面的基於控制器中的資料:
Name: David
Street: 1234 Anywhere St.
有一點你可能會引起注意的是mySharedScope指令在視圖中被引用的名字是my-shared-scope。為什麼是這樣?其實指令在命名時是使用的駝峰命名法,而引用時使用連字號方式。例如,當你使用ngRepeat指令時,實際的連字號寫法是ng-repeat。
在這個指令中還有另一個有趣的事情是它總是預設繼承視圖的範圍(scope)。如果提前綁定控制器(CustomersController)到視圖,這時範圍的customer屬性中在指令中就是可用的。這種共用範圍的方式在你瞭解指令所處的父範圍時可以工作得很好,但是在你需要複用一個指令時你往往不能很好的瞭解或控制它所在的父範圍,這時我們就可以使用獨立範圍。關於獨立範圍的具體用法將會在後面的文章中詳述。
指令的屬性
在上面的mySharedScope指令中我們只是在函數中返回了一個僅包含template屬性的對象字面量,這個屬性被用來定義指令產生HTML的模板(在這個例子中是一個簡單的綁定運算式),那麼還有哪些其它可用的屬性呢?
自訂指令一般會通過返回一個對象字面量來定義指令所需的屬性,例如模板、控制器(如果需要的話)、DOM作業碼等等。有一些不同的屬性可以被使用(你可以在這裡找到完整的屬性列表)。下面是一些你可能遇到的常用的比較關鍵的屬性,以及一個簡單的使用它們的例子:
angular.module('moduleName')
.directive('myDirective', function () {
return {
restrict: 'EA', //E = element(元素), A = attribute(屬性), C = class(類), M = comment(注釋)
scope: {
// @ 讀取屬性值,
// = 雙向資料繫結,
// & 使用函數
title: '@' },
template: '<div>{{ myVal }}</div>',
templateUrl: 'mytemplate.html',
controller: controllerFunction, // 可以在指令中嵌入自訂控制器
link: function ($scope, element, attrs) { } // DOM操作
}
});
以下是對部分屬性的一些簡要說明:
屬性 描述
restrict 檢測指令可用的位置(是元素、屬性、CSS類中還是注釋中)
scope 用來建立一個新的子範圍或獨立範圍
template 用來定義指令輸出的內容,可以包含HTML、資料繫結運算式,甚至包含其它指令
templateUrl 提供一個指令使用的模板的路徑,也可以是一個使用<script>標籤定義的模板的ID
controller 用來定義一個控制器以聯絡視圖模板
link 主要用來處理一些DOM操作的任務
操作DOM
除了在模板中進行資料繫結操作外,指令也可以被用來操作DOM。這使用了前面提到的link函數。
link函數通常會接收3個參數(在某些情況下還會有其它參數),包含當前範圍、與指令相關聯的DOM元素、以及元素上綁定的屬性。下面是一個使用指令處理點擊、滑鼠移入、滑鼠移出事件的例子:
app.directive('myDomDirective', function () {
return {
link: function ($scope, element, attrs) {
element.bind('click', function () {
element.html('You clicked me!');
});
element.bind('mouseenter', function () {
element.css('background-color', 'yellow');
});
element.bind('mouseleave', function () {
element.css('background-color', 'white');
});
}
};
});
要想使用這個指令你需要在你的視圖中添加以下代碼:
<div my-dom-directive>Click Me!</div>
當滑鼠移入或移出時,<div>的背景顏色將會在黃色和白色(雖然在這個例子中使用了內聯樣式,但使用CSS類將會更好)。當目標元素被點擊,內部的HTML就會變成“You clicked me!”。指令在AngularJS中是唯一可以直接操作DOM的服務,學會使用它將會對你的學習和使用很有協助。
格式化AngularJS指令代碼
雖然mySharedScope和myDomDirective指令啟動並執行很好,但是我更喜歡在定義指令和其它AngularJS組件時使用一些特定的格式,像下面這樣:
(function () {
var directive = function () {
return {
};
};
angular.module('moduleName')
.directive('directiveName', directive);
}());
這段代碼使用了一個自執行函數包圍了所有邏輯代碼以防止全域命名空間汙染。使用directive變數定義指令函數,最後,在模組上調用directive()函數並傳遞directive變數進去。有很多技巧可以被用來格式化代碼,但我比較喜歡上面這種。
總結
這是這個系列的第一篇文章,你可以瞭解到一些基本的指令知識並且學會如何去建立一個簡單的指令。這僅僅是表面!在下一篇文章中我們將會討論獨立範圍以及不同的資料繫結方式。