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