独立作用域和函数参数

通过使用本地作用域属性,你可以传递一个外部的函数参数(如定义在控制器$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在背后做了一些什么总是一件有趣的事情。

结尾

从上面可以看到&的传参过程还是有点困难的。然而一旦学会了如何使用,整个过程其实并不算太难用。

【转】angular指令入坑的更多相关文章

  1. Angular 从入坑到挖坑 - 组件食用指南

    一.Overview angular 入坑记录的笔记第二篇,介绍组件中的相关概念,以及如何在 angular 中通过使用组件来完成系统功能的实现 对应官方文档地址: 显示数据 模板语法 用户输入 组件 ...

  2. Angular 从入坑到挖坑 - 表单控件概览

    一.Overview angular 入坑记录的笔记第三篇,介绍 angular 中表单控件的相关概念,了解如何在 angular 中创建一个表单,以及如何针对表单控件进行数据校验. 对应官方文档地址 ...

  3. Angular 从入坑到挖坑 - Router 路由使用入门指北

    一.Overview Angular 入坑记录的笔记第五篇,因为一直在加班的缘故拖了有一个多月,主要是介绍在 Angular 中如何配置路由,完成重定向以及参数传递.至于路由守卫.路由懒加载等&quo ...

  4. Angular 从入坑到挖坑 - 模块简介

    一.Overview Angular 入坑记录的笔记第七篇,介绍 Angular 中的模块的相关概念,了解相关的使用场景,以及知晓如何通过特性模块来组织我们的 Angular 应用 对应官方文档地址: ...

  5. Angular 从入坑到挖坑 - Angular 使用入门

    一.Overview angular 入坑记录的笔记第一篇,完成开发环境的搭建,以及如何通过 angular cli 来创建第一个 angular 应用.入坑一个多星期,通过学习官方文档以及手摸手的按 ...

  6. Angular 从入坑到挖坑 - HTTP 请求概览

    一.Overview angular 入坑记录的笔记第四篇,介绍在 angular 中如何通过 HttpClient 类发起 http 请求,从而完成与后端的数据交互. 对应官方文档地址: Angul ...

  7. Angular 从入坑到挖坑 - 路由守卫连连看

    一.Overview Angular 入坑记录的笔记第六篇,介绍 Angular 路由模块中关于路由守卫的相关知识点,了解常用到的路由守卫接口,知道如何通过实现路由守卫接口来实现特定的功能需求,以及实 ...

  8. web前端入坑第五篇:秒懂Vuejs、Angular、React原理和前端发展历史

    秒懂Vuejs.Angular.React原理和前端发展历史 2017-04-07 小北哥哥 前端你别闹 今天来说说 "前端发展历史和框架" 「前端程序发展的历史」 「 不学自知, ...

  9. vue2.x入坑总结—回顾对比angularJS/React的一统

    从感性的角度讲,我是不屑于用VUE,觉得react套件用起来更顺手,但是vue现在越来火,所以也不得入vue(杂烩汤)的坑.vue/anguarJS/React,三者对关系现在就是: https:// ...

随机推荐

  1. .NET MVC Filter异常处理

    MVC程序中自带的HandleErrorAttribute,来处理异常,不在显示黄页.前提是在web.config 中 system.web中关闭customerError选项. 但是很多情况下调试异 ...

  2. selenium webdriver 建行软键盘输入密码

    driver.get("https://ibsbjstar.ccb.com.cn/app/V5/CN/STY1/login.jsp"); driver.manage().timeo ...

  3. NetBeans建立跳过测试构建的快捷方式

    在项目浏览器中右键项目->属性,如图进行设置: 此后按下图即可运行自定义行为:

  4. [java]OutOfMemoryError 原因及解决办法

    导致OutOfMemoryError异常的常见原因有以下几种: 内存中加载的数据量过于庞大,如一次从数据库取出过多数据: 集合类中有对对象的引用,使用完后未清空,使得JVM不能回收: 代码中存在死循环 ...

  5. caffe机器学习自带图片分类器classify.py实现输出预测结果的概率及caffe的web_demo例子运行实例

    caffe机器学习环境搭建及python接口编译参见我的上一篇博客:机器学习caffe环境搭建--redhat7.1和caffe的python接口编译 1.运行caffe图片分类器python接口 还 ...

  6. nagios检测http

    /usr/local/nagios/etc/server/下相应的地址检测加上以下一段   (server下的cfg文件是检测相应服务器的模块)  define service{        use ...

  7. Linux下删除文件的原理

    Linux下文件删除的原理 Lniux下控制文件真正被删除的计数器 Linux是link的数量来控制文件删除的.只有当一个文件不存在任何link的时候,这个文件才会被删除.一般来讲,每个文件都有两个l ...

  8. 前端自学vs跟大神系统学?你看着办

    前端自学vs跟大神系统学?你看着办 一名广告专业学生,在大三的时候对于广告行业的前景不是很看好,转而自学web前端,刚开始接触的前端语言是html(html应该不算编程语言),上手很容易,在w3csh ...

  9. LInux Shell 快捷键

    CTRL 键相关的快捷键: Ctrl + a - Jump to the start of the lineCtrl + b - Move back a charCtrl + c - Terminat ...

  10. NoSQL与RDBMS的九点区别联系

    原文链接:http://blog.sina.com.cn/s/blog_5373fb0b0101ft8a.html     1 理解ACID与BASE的区别(ACID是关系型数据库强一致性的四个要求, ...