獨立範圍和函數參數
通過使用本地範圍屬性,你可以傳遞一個外部的函數參數(如定義在控制器$scope中的函數)到指令。這些使用&就可以完成。下面是一個例子,定義一個叫做add的本地範圍屬性用來儲存傳入函數的引用:
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();
// 添加新的`customer`到指令範圍
$scope.customers.push({
name: name
});
};
},
template: '<button ng-click="addCustomer()">Change Data</button><ul>
<li ng-repeat="cust in customers">{{ cust.name }}</li></ul>'
};
});
指令的消費者可以通過定義一個add屬性的方式傳遞一個外部的函數到指令。如下:
<div isolated-scope-with-controller datasource="customers" add="addCustomer()"></div>
在這個例子中,函數addCustomer()將會在使用者點擊指令中建立的按鈕時被調用。沒有參數傳入,所以這裡是一個相對簡單的操作。
如何向addCustomer()函數中傳遞參數呢?例如,假設addCustomer()函數顯示在下面的控制器下並且當函數被調用時需要傳遞一個name參數到指令中:
var app = angular.module('directivesModule', []);
app.controller('CustomersController', ['$scope', function ($scope) {
var counter = 0;
$scope.customer = {
name: 'David',
street: '1234 Anywhere St.'
};
$scope.customers = [];
$scope.addCustomer = function (name) {
counter++;
$scope.customers.push({
name: (name) ? name : 'New Customer' + counter,
street: counter + ' Cedar Point St.'
});
};
$scope.changeData = function () {
counter++;
$scope.customer = {
name: 'James',
street: counter + ' Cedar Point St.'
};
};
}]);
從指令內傳遞一個參數到外部函數在你瞭解它的工作方式後會顯得特別簡單,下面是一般開發人員起初可能會嘗試的寫法:
angular.module('directivesModule')
.directive('isolatedScopeWithController', function () {
return {
restrict: 'EA',
scope: {
datasource: '=',
add: '&',
},
controller: function ($scope) {
...
$scope.addCustomer = function () {
// 調用外部函數,注意這裡直接傳遞了一個 name 參數
var name = 'New Customer Added by Directive';
$scope.add(name);
// 添加新的`customer`
$scope.customers.push({
name: name
});
};
},
template: '<button ng-click="addCustomer()">Change Data</button><ul>' +
'<li ng-repeat="cust in customers">{{ cust.name }}</li></ul>'
};
});
需要注意的是指令的控制器通過調用$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。調用$pares返回的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在背後做了一些什麼總是一件有趣的事情。
結尾
從上面可以看到&的傳參過程還是有點困難的。然而一旦學會了如何使用,整個過程其實並不算太難用。