> https://docs.angularjs.org/api/ng/service/$compile
# 創建自定義angularJS指令
* `@` 用來傳遞一個字符串值到指令
`@`字符告訴指令這個新的name屬性是一個來自外部作用域的字符串值。如果外部作用域中這個name的值被修改了,指令中的這 個值也會自動更新;
`@`在只需要給指令傳遞字符串值時很方便實用,但在需要把在指令中對值的改變反映到外部作用域時卻無能為力。在需要創建在 指令的獨立作用域和外部作用域中的雙向綁定時
* `=` 用于創建一個雙向綁定的對象
`=`告訴指令傳入指令本地作用域中的對象需要使用雙向綁定的方式。如果外部作用域中的屬性值變動,指令本地作用域中的值也 會自動更新;如果指令中修改了這個值,外部作用域中對應的也會同步被修改
* `&`允許傳入一個可被指令內部調用的函數
` &` 本地作用域屬性允許指令調用方傳遞一個可被指令內部調用的函數。例如,假設你在寫一個指令,終端用戶點擊指令中的一個按鈕并需要在控制器中觸發一個事件。你不能把點擊事件硬編碼在指令的代碼內部,這樣的話外部的控制器就無法知道指令內部到底發生了什么。在需要時觸發一個事件可以很好的解決這個問題(使用`$emit`或`$broadcast`),但是控制器需要知道具體偵聽的事件名是什么所以也不是最優的。
&字符從根本上來說相當于: “嘿,給我一個函數我可以在指令中發生某些事件時調用它”;
指令中的模板可以包含一個按鈕,當按鈕被點擊時,action(外部函數的引用)函數將會被調用。
scope屬性的值可以為一個bool型,值為false時不使用獨立作用域,和不寫此屬性沒區別。
scope中定義的屬性名要使用駝峰命名的方式,而在模板中使用的時候要使用連字符語法,假設有一個指令叫datePicker,scope部分定義如下:
~~~
scope: {
isOpen: "=",
currentDate: "=",
onChange: "&"
}
~~~
視圖中使用方式如下(假設引號里面的函數和作用域屬性是已經在控制器中定義的):
~~~
<div date-picker
is-open="openState"
current-date="currentDate"
on-change="dateChange()"
></div>
~~~
另外,如果`scope`中的一些屬性是可選的(如上面例子中,`isOpen`默認為false,指令的使用者可以選擇不傳遞這個屬性),在使用這個指令的時候AngularJS就會報錯,也就是說`scope`定義的屬性在調用指令時都需要被傳遞(不傳遞會報錯,但不影響程序運行)。解決這個問題的話可以在可選參數后面加一個問號`?`標識這個屬性是可選的,修改后的指令`scope`部分如下:
~~~
scope: {
isOpen: "=?", // 注意這里的問號,指定這個參數是可選的
currentDate: "=",
onChange: "&"
}
~~~
## 指令的函數參數
需要注意的是***指令的控制器通過調用$scope.add(name)來嘗試調用外部函數并傳遞一個參數過去。這樣可以工作嗎?***實際上在外部函數中輸出這個參數得到的卻是`undefined`,這可能讓你抓破腦袋都想不通為什么。那么接下來我們該做什么呢?
### 選擇1:使用對象字面量
一種方法是傳遞一個對象字面量。下面是演示如何把`name`傳遞到外部函數中的例子:
~~~
angular.module('directivesModule')
.directive('isolatedScopeWithController', function () {
return {
restrict: 'EA',
scope: {
datasource: '=',
add: '&',
},
controller: function ($scope) {
...
$scope.addCustomer = function () {
// 調用外部函數
var name = 'New Customer Added by Directive';
$scope.add({ name: name });
// Add new customer to directive scope
$scope.customers.push({
name: name,
street: counter + ' Main St.'
});
};
},
template: '<button ng-click="addCustomer()">Change Data</button>' +
'<ul><li ng-repeat="cust in customers">{{ cust.name }}</li></ul>'
};
});
~~~
需要注意的是`$scope.add()`方法調用時現在傳遞了一個對象字面量作為參數。很不幸,這樣仍然不能工作!什么原因呢?傳遞給`$scope.add()`的對象字面量中定義的name屬性在分配給指令時同樣也需要在外部函數中被定義。非常重要的一點是,在視圖中寫的參數名必須要與對象字面量中的名字匹配。下面是一個例子:
`<div isolated-scope-with-controller datasource="customers" add="addCustomer(name)"></div>`
可以看到在視圖中使用指令時,`addCustomer()`方法添加了個參數`name`。這個name必須要與指令中調用`$scope.add()`時傳入的對象字面量中的name相匹配。如此一來指令就能正確工作了。
### 選擇2:存儲一個函數引用并調用它
上面那種方式的問題在于在使用指令時必須要給函數傳遞參數而且參數名必須在指令內以對象字面量的形式被定義。如果任何一點不匹配將無法工作。雖然這種方法可以完成需求,但仍然有很多問題。例如如果指令沒有完善的使用說明文檔就很難知道指令中需要傳遞的參數名究竟是什么,這時就不得不去翻指令源碼查看參數內容了。
另一種可行的方法是在指令上定義一個函數但在函數名后面不加圓括號,如下:
`<div isolated-scope-with-controller-passing-parameter2 datasource="customers" add="addCustomer"></div>`
為了傳遞參數到外部的`addCustomer`函數你需要在指令中做以下事情。把`$scope.add()(name)`代碼放到可被`addCustomer`調用的方法下面:
~~~
angular.module('directivesModule')
.directive('isolatedScopeWithControllerPassingParameter2', function () {
return {
restrict: 'EA',
scope: {
datasource: '=',
add: '&',
},
controller: function ($scope) {
...
$scope.addCustomer = function () {
// 調用外部函數
var name = 'New Customer Added by Directive';
$scope.add()(name);
...
};
},
template: '<button ng-click="addCustomer()">Change Data</button><ul>' +
'<li ng-repeat="cust in customers">{{ cust.name }}</li></ul>'
};
});
~~~
為什么這種方法可以工作?這個需要從&的另一個主要作用說起。&在指令中主要的作用是計算表達式,即在控制器調用以&定義的作用域屬性時AngularJS會計算出這個表達式的值并返回。例如在視圖中輸入`add="x = 42 + 2"`,那么在指令中讀取`$scope.add()`時將會返回這個表達式的計算結果(44),任何一個有效的AngularJS的表達式都可以是add屬性的值并在讀取add屬性時被計算。所以當我們在視圖中輸入不帶圓括號的函數`add="customers"`時,指令中`$scope.add()`實際返回的是在控制器中定義的函數`customers()`。所以在指令中調用`$scope.add()(name)`就相當于調用控制器的`customers(name)`。
在指令中輸出`$scope.add()`將會得到以下內容(正好驗證上面所說):
### `&`背后的運行機制
如果你對&的運行機制感興趣,當&本地作用域屬性被調用(例如上面例子中的a dd本地作用域屬性),下面的代碼將會執行:
~~~
case '&':
parentGet = $parse(attrs[attrName]);
isolateScope[scopeName] = function(locals) {
return parentGet(scope, locals);
};
break;
~~~
上面的`attrName`變量相當于前面例子中指令本地作用域屬性中的add。調用`$parse`返回的`parentGet`函數 如下:
~~~
function (scope, locals) {
var args = [];
var context = contextGetter ? contextGetter(scope, locals) : scope;
for (var i = 0; i < argsFn.length; i++) {
args.push(argsFn[i](scope, locals));
}
var fnPtr = fn(scope, locals, context) || noop;
ensureSafeObject(context, parser.text);
ensureSafeObject(fnPtr, parser.text);
// IE stupidity! (IE doesn't have apply for some native functions)
var v = fnPtr.apply
? fnPtr.apply(context, args)
: fnPtr(args[0], args[1], args[2], args[3], args[4]);
return ensureSafeObject(v, parser.text);
}
~~~
處理代碼映射對象字面量屬性到外部函數參數并調用函數。
雖然沒有必要一定去理解如何使用&本地作用域屬性,但是去深入發掘`AngularJS`在背后做了一些什么總是一件有趣的事情。
結尾
從上面可以看到`&`的傳參過程還是有點困難的。然而一旦學會了如何使用,整個過程其實并不算太難用。
- 步入JavaScript的世界
- 二進制運算
- JavaScript 的版本是怎么回事?
- JavaScript和DOM的產生與發展
- DOM事件處理
- js的并行加載與順序執行
- 正則表達式
- 當遇上this時
- Javascript中apply、call、bind
- JavaScript的編譯過程與運行機制
- 執行上下文(Execution Context)
- javascript 作用域
- 分組中的函數表達式
- JS之constructor屬性
- Javascript 按位取反運算符 (~)
- EvenLoop 事件循環
- 異步編程
- JavaScript的九個思維導圖
- JavaScript奇淫技巧
- JavaScript:shim和polyfill
- ===值得關注的庫===
- ==文章==
- JavaScript框架
- Angular 1.x
- 啟動引導過程
- $scope作用域
- $q與promise
- ngRoute 和 ui-router
- 雙向數據綁定
- 規范和性能優化
- 自定義指令
- Angular 事件
- lodash
- Test