码迷,mamicode.com
首页 > Web开发 > 详细

AngularJS 学习笔记 -- 指令(Directive)

时间:2016-04-17 22:47:54      阅读:297      评论:0      收藏:0      [点我收藏+]

标签:

AngularJS 指令学习笔记

AngularJS怎样处理指令其实是依赖于指令定义时返回的对象属性的,所以要想深入理解如何定义一个指令,首相需要理解指令定义时各个参数的含义。

完整的AngularJS指令参数

angular.module(‘app‘, [])
    .directive(‘demoDirective‘, function (){ // 依据官方规范,指令的定义时应该严格遵循驼峰式命名规则,使用时采用‘-‘连接单词
        return {
            restrict : String in [‘E‘, ‘A‘, ‘C‘, ‘M‘],
            priority : Number,
            terminal : Boolean,
            template : String or Template Function : function (tElement, tAttrs) {...},
            templateUrl : String,
            require : String or String Array,
            replace : Boolean or String,
            scope : Boolean or Object,
            transclude : false or element,
            controller : String or function (scope, element, attrs, transclude, otherInjectable) {...},
            controllerAs : String,
            link : function (scope, element, attrs, otherController) {...},
            compile : function (element, attrs, transclude) {
                return object || function (...) {...}
            }
        };
    });

上面的这么多参数如果按照功能划分的话,大致上可以分为如下三类:
1. 描述指令或是DOM自身特性的内部参数
2. 连接指令外界的参数,该类参数使得指令可与其他指令或控制器沟通
3. 描述指令自身行为的参数

内部参数

·restrict :’E’ – Element , ‘A’ – Attribute ,’C’ – Class , ‘M’ – comMent
·priority :指令执行的优先级,默认值为0
·template : 与指令关联的HTML模板,
·**templateUrl :与指令关联的HTML模板路径,
·replace :是否采用HTML模板替换原有的元素

对外参数

scope

scope 参数的作用是:隔离指令与所在控制器(父级作用域的控制器)间的作用域,隔离指令与指令之间的作用域。
scope的值是可选的,可选值分别为:false,true,object,默认情况下为false;
·false 共享父作用域
·true 继承父作用域,并且新建独立作用域
·object 不继承父作用域,创建新的独立作用域

关于scope可选值的不同结果对比:

<!DOCTYPE html>
<html lang="zh_CN" ng-app="app">
<head>
    <meta charset="UTF-8">
    <title>AngularJS Index</title>
    <script type="text/javascript" src="lib/jquery-1.12.3.js"></script>
    <script type="text/javascript" src="lib/angular.min.js"></script>
    <script type="text/javascript" src="js/app.js"></script>
    <style>
        .box {
            max-width: 680px;
            min-height: 80px;
            margin: 10px;
            padding: 10px 15px;
            border: solid 1px #2a3ca2;
        }

        .box span, .box input {
            display: block;
        }

        input[type="text"] {
            width: 280px;
            margin: 5px 0;
        }
    </style>
</head>
<body>
    <div ng-controller="parentCtrl">
        <h3>scope参数选择不同值时的作用域情况对比</h3>
        <span>parent</span>
        <div class="box" >
            <span>{{parentName}}</span>
            <input type="text" ng-model="parentName">
            <span>{{scopeName}}</span>
            <input type="text" ng-model="scopeName">
        </div>
        <span>scop=False</span>
        <dir-f class="box"></dir-f> 
        <span>scope=True</span>
        <dir-t class="box"></dir-t> 
        <span>scope={}</span>
        <dir-o-n class="box"></dir-o-n> 
        <span>scope={attrs}</span>
        <dir-o class="box"></dir-o> 
    </div>
</body>
</html>

app.js

;(function($) {
    ‘use strict‘;
    angular.module(‘app‘, [])
        .controller(‘parentCtrl‘, [‘$scope‘, function($scope) {
            $scope.parentName = ‘parentName‘;
            $scope.scopeName = ‘scopeNameInParentScope‘;
        }])
        .directive(‘dirF‘, [function() {
            // Runs during compile
            return {
                // name: ‘‘,
                // priority: 1,
                // terminal: true,
                // scope: {}, // {} = isolate, true = child, false/undefined = no change
                // controller: function($scope, $element, $attrs, $transclude) {},
                // require: ‘ngModel‘, // Array = multiple requires, ? = optional, ^ = check parent elements
                // restrict: ‘A‘, // E = Element, A = Attribute, C = Class, M = Comment
                // template: ‘‘,
                // templateUrl: ‘‘,
                // replace: true,
                // transclude: true,
                // compile: function(tElement, tAttrs, function transclude(function(scope, cloneLinkingFn){ return function linking(scope, elm, attrs){}})),
                //link: function($scope, iElm, iAttrs, controller) {}
                restrict: ‘E‘,
                scope: false,
                replace: true,
                template: [
                    ‘<div>‘,
                    ‘   <span>parentName</span>‘,
                    ‘   <span>{{ parentName }}</span>‘,
                    ‘   <input type="text" ng-model="parentName" />‘,
                    ‘   <span>scopeName</span>‘,
                    ‘   <span>{{ scopeName }}</span>‘,
                    ‘   <input type="text" ng-model="scopeName" />‘,
                    ‘</div>‘
                ].join(‘‘),
                controller : [‘$scope‘, function ($scope){
                    $scope.scopeName = ‘123456‘;
                }]
            };
        }])
        .directive(‘dirT‘, [function() {
            return {
                restrict: ‘E‘,
                scope: true,
                replace: true,
                template: [
                    ‘<div>‘,
                    ‘   <span>parentName</span>‘,
                    ‘   <span>{{ parentName }}</span>‘,
                    ‘   <input type="text" ng-model="parentName" />‘,
                    ‘   <span>scopeName</span>‘,
                    ‘   <span>{{ scopeName }}</span>‘,
                    ‘   <input type="text" ng-model="scopeName" />‘,
                    ‘</div>‘
                ].join(‘‘),
                controller: [‘$scope‘, function ($scope) {
                    $scope.parentName = ‘parentNameInDirectiveTrue‘;
                    //$scope.scopeName = ‘scopeNameInDirectiveTrue‘;
                    // 在当前$scope中以已经生明的情况下,$scope.$watch监听的是自身的parentName
                    $scope.$watch(‘parentName‘, function(newValue, oblValue) {
                        console.log(‘directive-true -->> from watch scope parentName = ‘ + newValue);
                    });
                    // 监听父作用域中的parentName
                    $scope.$parent.$watch(‘parentName‘, function(newValue, oldValue) {
                        console.log(‘directive-true -->> from watch parent scope parentName = ‘ + newValue);
                    });
                }]
            };
        }])
        .directive(‘dirON‘, [function() {
            return {
                restrict: ‘E‘,
                scope: {
                },
                replace: true,
                template: [
                    ‘<div>‘,
                    ‘   <span>parentName</span>‘,
                    ‘   <span>{{ parentName }}</span>‘,
                    ‘   <input type="text" ng-model="parentName" />‘,
                    ‘   <span>scopeName</span>‘,
                    ‘   <span>{{ scopeName }}</span>‘,
                    ‘   <input type="text" ng-model="scopeName" />‘,
                    ‘</div>‘
                ].join(‘‘),
                controller: [‘$scope‘, function ($scope) {
                    $scope.parentName = ‘parentNameInDirectiveObjectWithNull‘;
                    //$scope.scopeName = ‘scopeNameInDirectiveObjectWithNull‘;
                    // 在当前$scope中以已经生明的情况下,$scope.$watch监听的是自身的parentName
                    $scope.$watch(‘parentName‘, function(newValue, oblValue) {
                        console.log(‘directive-Object-Null -->> from watch scope parentName = ‘ + newValue);
                    });
                    // 监听父作用域中的parentName
                    $scope.$parent.$watch(‘parentName‘, function(newValue, oldValue) {
                        console.log(‘directive-Object-Null -->> from watch parent scope parentName = ‘ + newValue);
                    });
                }]
            }
        }])
        .directive(‘dirO‘, [function() {
            return {
                restrict: ‘E‘,
                scope: {
                    parentName : ‘@‘    
                },
                replace: true,
                template: [
                    ‘<div>‘,
                    ‘   <span>parentName</span>‘,
                    ‘   <span>{{ parentName }}</span>‘,
                    ‘   <input type="text" ng-model="parentName" />‘,
                    ‘   <span>scopeName</span>‘,
                    ‘   <span>{{ scopeName }}</span>‘,
                    ‘   <input type="text" ng-model="scopeName" />‘,
                    ‘</div>‘
                ].join(‘‘),
                controller: [‘$scope‘, function ($scope) {
                    $scope.parentName = ‘parentNameInDirectiveObject‘;
                    //$scope.scopeName = ‘scopeNameInDirectiveObject‘;
                    // 在当前$scope中以已经生明的情况下,$scope.$watch监听的是自身的parentName
                    $scope.$watch(‘parentName‘, function(newValue, oblValue) {
                        console.log(‘directive-Object -->> from watch scope parentName = ‘ + newValue);
                    });
                    // 监听父作用域中的parentName
                    $scope.$parent.$watch(‘parentName‘, function(newValue, oldValue) {
                        console.log(‘directive-Object -->> from watch parent scope parentName = ‘ + newValue);
                    });
                    // 监听父作用域中的scopeName
                    $scope.$parent.$watch(‘scopeName‘, function(newValue, oldValue) {
                        console.log(‘directive-Object -->> from watch parent scope scopeName = ‘ + newValue);
                    });
                }]
            }
        }]);

}(jQuery));

实验结果:
·scope取值为false时,指令和父作用域共同使用用同一个作用域–示例程序中的scopName
·scope取值为true时,指令和父作用域关系采用JavaScript的原型继承方式实现
·scope取值为object时,指令将采用完全独立的作用域,通过scope.parent引用父作用域

scope取值为非空对象时属性扩展特性

自定义指令中,当scope取值为非空对象时,指令会将该对象处理成子域scope的扩展属性。这一扩展属性肩负起了指令和父作用域通信的任务。
scoep取值为对象时,对象的属性有三种不同的绑定策略,分别是:
‘@’ or ‘@alias’ | ‘=’ or ‘=alias’ | ‘&’
关于三者间的区别直接上代码,注意代码中的注释部分,解释了三者的区别:
html

<div ng-controller="parentScopeCtrl">
        <div class="box">
            <p>parentScope :</p>
            <span>id</span>
            <span>{{ id }}</span>
            <input type="text" ng-model="id">
            <span>gender</span>
            <span>{{ gender }}</span>
            <input type="text" ng-model="gender">
            <span>demo</span>
            <span>{{ demo }}</span>
            <input type="text" ng-model="demo">
            <span>other</span>
            <span>{{ other }}</span>
            <input type="text" ng-model="other">
        </div>
        <!-- ‘@‘和‘=‘对应Attribute属性的值, ‘@‘是单向绑定父域的机制,需要使用{{}}表达式;‘&‘对应的属性名必须要以on开头 -->
        <dir-scope my-id="id" gender="gender" my-age="{{ age }}" on-speak="speak(‘demo‘)"></dir-scope>

app.js

.controller(‘parentScopeCtrl‘, [‘$scope‘, function ($scope){
            $scope.id = 12345;
            $scope.gender = "male";
            $scope.age = 24;
            $scope.demo = "haha";
            $scope.other = "Other";
            $scope.speak = function (msg){
                console.log(msg);
            }
        }])
.directive(‘dirScope‘, [function(){
            return {
                restrict : ‘EA‘,
                scope : {
                    myAge : ‘@‘, /* 子作用能感知到父作用域的变更,反之不行; 需要注意的是,属性赋值时需要使用{{}}表达式,而且该属性的值类型永远是String,也即是执行{{}}表达式返回的String */
                    myId : ‘=‘,  /* 父子作用域双向绑定 */
                    myGender : ‘=gender‘, /* 父子作用域双向绑定; 与‘=‘不同在于使用属性时的采用gender=‘‘ */
                    onSpeak : ‘&‘  // 通常&
                },
                template : [
                    ‘<div>‘,
                    ‘   <span>myID</span>‘,
                    ‘   <span>{{ myId }}</span>‘,
                    ‘   <input type="text" ng-model="myId" />‘,
                    ‘   <span>myGender</span>‘,
                    ‘   <span>{{ myGender }}</span>‘,
                    ‘   <input type="text" ng-model="myGender" />‘,
                    ‘   <span>myAge</span>‘,
                    ‘   <span>{{ myAge }}</span>‘,
                    ‘   <input type="text" ng-model="myAge" />‘,
                    ‘   <span>demo</span>‘,
                    ‘   <span>{{ demo }}</span>‘,
                    ‘   <input type="text" ng-model="demo" />‘,
                    ‘   <span>other</span>‘,
                    ‘   <span>{{ other }}</span>‘,
                    ‘   <input type="text" ng-model="other" />‘,
                    ‘   <button ng-click="log()">Show Different</button>‘,
                    ‘</div>‘
                ].join(‘‘),
                controller : [‘$scope‘, function ($scope){
                    $scope.log = function (){
                        console.log(typeof $scope.myId, $scope.myId);
                        console.log(typeof $scope.myGender, $scope.myGender);
                        console.log(typeof $scope.myAge, $scope.myAge);
                        console.log(typeof $scope.onSpeak, $scope.onSpeak);
                        $scope.onSpeak();
                    }   
                    $scope.demo = "scope-hahaha"; // 完全与父域中同名属性隔离
                    // 指令中使用other属性,但是由于scope取值为对象,所以也是与父域完全隔离的
                    // 在当前$scope中以已经生明的情况下,$scope.$watch监听的是自身的parentName
                    $scope.$watch(‘demo‘, function(newValue, oblValue) {
                        console.log(‘directive-scope -->> from watch scope demo = ‘ + newValue);
                    });
                    // 监听父作用域中的parentName
                    $scope.$parent.$watch(‘demo‘, function(newValue, oldValue) {
                        console.log(‘directive-scope -->> from watch parent scope demo = ‘ + newValue);
                    });
                }]
            };
        }]);

参数require

与scope参数一样,require参数也是指令与外界通信的媒介。scope参数主要负责的是指令与外界作用域间的通信,require参数主要负责指令与指令之间的通信。大部分自定义指令都很能独立完成某项复杂的任务,往往需要多个指令之间相互组合协作。

require参数接收一个String字符串或是一个字符串数组,String值为需要引入的外界指令的名称,实际传入的是外界指令对应的控制器(这个后续再讨论link参数时详细讨论)。require参数在寻找依赖指令时提供了两种策略’?’ 和 ‘^’。
‘?’ – 如果没有查找到相应的指令,则返回null
‘^’ – 从自身开始并向父级作用域链中搜索依赖指令,返回第一个匹配的值,如果没有’^’则仅仅在自身作用域查找。

行为参数link与controller

link与controller参数都是描述指令行为的参数,但他们两分别负责不同的行为描述。
controller关注的是指令自身内部作用域具备什么样的行为,其关注点在于指令作用域的行为上。
link关注的是指令中HTML模板的操作行为,其关注点在于DOM操作行为上。

link参数理解

从AngularJS官方教程上我们可以获知,Angular在刚从Server Response中获取得到静态网页时,首先回去扫描整个页面并收集HTML页面中包含哪些指令(Angular原生的还是用户自定义的),然后再去加载指令的template中的HTML模板或是下载templateUrl中指定的模板,如此类推,如果加载进来的HTML模板中包含其他指令,继续上诉操作,最终形成模板树,并返回相应的模板函数,提供给下一阶段进行数据绑定。简单的应用示例;

<body ng-app=‘app‘>
    <dir-demo></dir-demo>
    <script >
        angular.module(‘app‘ , [])
            .directive( ‘dirDemo‘ , function () {
                return {
                    restrict: ‘E‘ ,
                    template: ‘<p>dirDemo</p><dirDemo2></dirDemo2>‘,
                    link: function (scope) {
                        console.log( ‘dirDemo2‘ );
                    }
                };
            })  
            .directive( ‘dirDemo2‘ , function () {
                return {
                    restrict: ‘E‘ ,
                    template: ‘<p>dirDemo2</p><dirDemo3></dirDemo3>‘,
                    link: function (scope) {
                        console.log( ‘dirDemo2‘ );
                    }
                };
            })
            .directive( ‘dirDemo3‘ , function () {
                return {
                    restrict: ‘E‘ ,
                    template: ‘<p>dirDemo3</p>‘,
                    link: function (scope) {
                        console.log( ‘dirDemo3‘ );
                    }
                };
            });
    </script >
</ body>

运行上述程序观察输出: dirDemo, dirDemo2, dirDemo3
如果结合代码调试,在link中设置断点,我们可以发现整个执行顺序是:
1 加载模板,形成DOM树
2 执行link函数
3 数据绑定
为什么会是这个执行顺序呢?
其实:在刚形成DOM树的这个时间节点上,进行DOM操作的性能开销是最低的(这事应该是一个DOM片段),进行事件绑定等做操。

    angular.module(‘app‘ , [])
    .directive( ‘dirDemo‘ , function () {
        return {
            restrict: ‘E‘ ,
            require : ‘^ngModel‘, // 需要引用外界指令ngModel的控制器
            template: ‘<p>dirDemo</p><dirDemo2></dirDemo2>‘,
            link: function ($scope, $element, $attrs, ctrl) {
                $element.bind("click", function () {
                    console.log("绑定点击事件");
                });
                $element.append("<p>增加段落块</p>");
                //设置样式
                $element.css("background-color", "yellow");
                //最佳实践中,应该下面的方法转移到controller中
                $scope.hello = function () {
                    console.log("hello");
                };
            }
        };
    });  

link函数的参数是固定的(scope,element, $attrs, ctrl);
第四个参数即是require参数指定的外部指令的控制器,如果require是一个数组,那么第四个参数相应的也是一个数组,控制器的顺序同require参数中声明的顺序一样。

如果我们在上面的示例代码中添加上controller后,在此进行断点调试可发现,全局的执行顺序是:
1 执行controller,设置各个作用域的scope
2 加载模板,形成DOM模板树
3 执行link函数,设置各级DOM的行为
4 数据绑定,在各级scope中绑定DOM

compile参数

我们通过定义一个compile来取代link函数。可以说compile提供了一个更细粒度的link函数形式,在compile函数中我们可以使用pre-link和post-link函数来替代link函数。
html

<level-one>
    <level-two>
        <level-three>Hello </level-three>
    </level-two>
</level-one>

app.js

function createDirective(name){
    return function(){
        return {
          restrict: ‘E‘,
          compile: function(tElem, tAttrs){
            console.log(name + ‘: compile‘);
            return {
              pre: function(scope, iElem, iAttrs){
                console.log(name + ‘: pre-link‘);
              },
              post: function(scope, iElem, iAttrs){
                console.log(name + ‘: post-link‘);
              }
            }
          }
        }
    }
};

angular.module(‘app‘, [])
    .directive(‘levelOne‘, createDirective(‘levelOne‘))
    .directive(‘levelTwo‘, createDirective(‘levelTwo‘))
    .directive(‘levelThree‘, createDirective(‘levelThree‘));

运行上面的实例程序,我们能够简单的了解到AngularJS在处理指令时的内部流程。
程序控制台输出为:

levelOne: compile 
levelTwo: compile 
levelThree: compile 

levelOne: pre-link
levelTwo: pre-link 
levelThree: pre-link 

levelThree: post-link 
levelTwo: post-link 
levelOne: post-link 

从上面的示例中我们可以发现,link过程,其实还分为pre-link和post-link阶段。同时Angular在指令的link函数调用前先调用了指令的compile函数对指令进行了编译。

关于compile的更多内容将在下一篇文章中进行分享。

参考文献
[AngularJS Developer Guide - Directive][https://docs.angularjs.org/guide/directive]

AngularJS 学习笔记 -- 指令(Directive)

标签:

原文地址:http://blog.csdn.net/shenlei19911210/article/details/51173291

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!