在本系列的第二篇文章中,我介绍了孤立作用域,以及它怎样使指令更具有复用性。孤立作用域一个大的方面是:依赖符号@,=和&进行数据和函数传递的指令内部scope属性。通过这些属性,你可以给指令传入或从指令传出数据。如果你是初次接触指令内部作用域,我建议你首先阅读本系列的第一和第二篇文章。在这篇文章中,我将介绍如何给传入指令的函数添加参数。
孤立作用域和函数参数
在使用指令内部作用域属性时,你可以将一个外部函数(例如:在外部控制器的$scope上定义的函数)传递给指令。这个是通过&符类型的内部属性实现的。下边例子是一个名为name的内部属性,它和一个外部函数相关联:
angular.module('directivesModule').directive('isolatedScopeWithController', function () {
return {
restrict: 'EA',
scope: {
datasource: '=',
add: '&',
},
controller: function ($scope) {
...
$scope.addCustomer = function () {
//Call external scope's function
var name = 'New Customer Added by Directive';
$scope.add();
//Add new customer to directive scope
$scope.customers.push({
name: name
});
};
},
template: '<button ng-click="addCustomer()">Change Data</button><ul>
<li ng-repeat="cust in customers">{{ cust.name }}</li></ul>'
};
});
指令的使用者可以通过下边方式传递一个外部函数给指令:
<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.'
};
};
}]);
Passing a parameter out of the directive to the external function is quite straightforward once you know the trick but requires a bit of upfront knowledge to get it working properly. Here’s what most devs try to do initially:
当你掌握了诀窍时,给传入指令的外部函数添加参数将变得很简单,但是想要让它很好的工作还是需要一点必备的知识。大部分指令初学者做法如下:
angular.module('directivesModule').directive('isolatedScopeWithController', function () {
return {
restrict: 'EA',
scope: {
datasource: '=',
add: '&',
},
controller: function ($scope) {
...
$scope.addCustomer = function () {
//Call external scope's function
var name = 'New Customer Added by Directive';
$scope.add(name);
//Add new customer to directive scope
$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 () {
//Call external scope's 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({ name: name })传递一个对象类型的参数,对象的属性name就是我们想要传递的参数。不幸的是,这个函数仍然不能被执行!为什么呢?这个name参数必须写在外部函数被关联到指令属性的地方。具体做法如下边例子:
<div isolated-scope-with-controller datasource="customers" add="addCustomer(name)"></div>
需要特别注意的一点是:这里的传递的是一个参数,而到外部函数执行时,参数是一个字符串。并且,指令内部
$scope.add({ name: "..."})的
name属性名必须和上边代码中div标签属性中
add="addCustomer(name)"的参数名
name完全一样。
可以看出,外部函数的参数名在指令内部调用函数时对象类型参数的属性中匹配,匹配到相同名字的属性时,将其值赋给外部参数,这样参数传递成功。虽让不是很直观,但是确实是一种有效的方法。
方法2:存储一个函数引用并调用它
使用方法1的要点是保证指令使用时参数名已经被定义,并且,它会匹配指令传出对象参数中的属性名,如果没有任何一个属性名匹配上,这种方法就行不通。即使这种方法可以达到目的,但有许多复杂的东西,我称之为"局限性知识",而且,如果指令说明文档不是很详细时,将无法在不看指令源码情况下给外部函数参数命名。
另外一个方法是在html标签属性中定义函数时,不添加圆括号。如下所示:
<div isolated-scope-with-controller-passing-parameter2 datasource="customers" add="addCustomer"></div>
给外部函数
addCustomer传递参数,你可以像下边这样。将
$scope.add()(name)放到指令代码中,看看
addCustomer函数是怎样在传递一个
name参数的情况下被调用的。
angular.module('directivesModule').directive('isolatedScopeWithControllerPassingParameter2', function () {
return {
restrict: 'EA',
scope: {
datasource: '=',
add: '&',
},
controller: function ($scope) {
...
$scope.addCustomer = function () {
//Call external scope's 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>'
};
});
这种方法的原理是什么?它为什么就可以呢?在$scope.add()(name)代码处打断点调试,在浏览器控制台中执行
$scope.add()代码,输出结果如下。可以看出
$scope.add()得到函数的定义。这是因为当这个函数被绑定给
add内部属性时,没有加圆括号导致的,如上边所示。
第二个圆括号($scope.add()(name))的作用是调用这个函数,并传递name参数的值。这当然要求指令的使用者移除圆括号(在你的项目组中,这或许是或者不是'正常的'),但是相比方法1,在我看来,这种方法更加干净利索。
&类型的内部属性-透过现象看本质
如果你对一些导致结果的内部过程感兴趣,当一个&类型的内部属性在一个指令中被调用时(例如:上边的add内部属性),下边的代码就是被调用时执行的代码:
case '&':
parentGet = $parse(attrs[attrName]);
isolateScope[scopeName] = function(locals) {
return parentGet(scope, locals);
};
break;
attrName代表上边例子中指令内部属性
add。
parentGet函数是
$parse函数执行的结果,其内容如下所示:
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);
}
这段代码处理对象属性和外部函数参数的映射,并调用这个函数。当你使用&类型的内部属性时,你并不需要知道这些,但可以了解到现象背后的原理,是一件有意思的事。
总结
总之,在指令中传递参数有些复杂和曲折。但是,如果你知道了这个过程是怎样工作的,使用它并不困难。