独立作用域和函数参数

通过使用本地作用域属性,你可以传递一个外部的函数参数(如定义在控制器$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. Python 开源网上商城项目

    django-oscar  https://github.com/django-oscar/django-oscar#screenshots django-shop  https://github.c ...

  2. ALS

    最近看了一些关于ALS(肌萎缩性脊髓侧索硬化症)的电视剧和一本ALS患者的生活自述的书. 一次偶然的机会在一部日剧<我所存在的时间>中看到了ALS这种疾病,感觉这就像众病之王--癌症一样, ...

  3. SSH使用详解

    一.SSH基础 (1)什么是SSH? 传统的网络服务程序,如:ftp.pop和telnet在本质上都是不安全的,因为它们在网络上用明文传送口令和数据,别有用心的人非常容易就可以截获这些口令和数据.而且 ...

  4. box-shadow、drop-shadow 和 text-shadow

    1. box-shadow 合阴影, 对象选择器 {box-shadow:[投影方式] X轴偏移量 Y轴偏移量阴影模糊半径 阴影扩展半径 阴影颜色} box-shadow属性的参数设置取值: 阴影类型 ...

  5. android 决解启动屏白黑屏会延迟几秒的问题

    通常写启动屏,都有个很不喜欢的问题,就是会空白几秒才显示界面,而且界面还是很简单的! 解决办法 1 写一个透明的主题,一般启动屏都是不要bar的所以继承AppTheme.NoActionBar < ...

  6. json 序列化为数组

    我们通常从后台取到json格式的数据到前台进行展示,在这个过程中可能户遇到一些json格式不是自己想要的格式,今天本人就遇到一个棘手的问题,最后在师傅的协助下才进行了正确格式的转换. 可以说最悲哀的莫 ...

  7. 面向内容的标记语言--markdonw

    引言: 我们习惯用html来展示数据,尤其是结合了js以及css之后,更是让html变得非常的绚丽,可是有些时候在感受绚丽的同时,我们往往对我们本身想要了解的内容变得漠不关心了,其实并不是所有的知识都 ...

  8. 【Java EE 学习 55】【酒店会员管理系统项目总结】

    本酒店会员管理系统使用了SSH框架和传值播客提供的协同OA静态页面. 项目地址:https://github.com/kdyzm/HotelMembersManagement 一.需求分析 酒店会员管 ...

  9. 五分钟搭建起一个包含CRUD功能的JqGrid表格

    之前的项目也曾用过JgGrid对它的基本功能也是略有了解,网上有个国外的开源的项目,但是不适合个人的风格,所以花了3天空余的时间封装了下JqGrid,也算是参加开发工作10个月以来写的第一个比较完整的 ...

  10. android基础知识进阶

    1.android Activity的生命周期 http://blog.csdn.net/hpoi/article/details/4629065 2.android Service的生命周期 htt ...