AngularJS指令参数详解—

文章转自: http://blog.csdn.net/kongjiea/article/details/49840035javascript

指令,很重要

AngularJSjQuery最大的区别在哪里?我认为,表如今数据双向绑定,实质就是DOM的操做形式不同。php

  • jquery经过选择器找到DOM元素,再赋予元素的行为;css

  • angularjs则是,将指令与DOM绑定在一块儿,再扩展指令的行为。html

因此AngularJS开发最理想的结果就是,在页面HTML与CSS的设计时,设计工程师只须要关注指令的使用;而在背后的逻辑开发上,架构工程师则是不须要知道如何操做DOM,只须要关注指令背后的行为要如何实现就行;测试工程师也能够开发针对指令的单元测试。java

指令就是DOM与逻辑行为的媒介,本质就是DOM绑定的独立逻辑行为函数。jquery

指令难点在于参数

来看看都有哪些angularjs

 
angular.module('app', [])
.directive('myDirective', function() {
    return {
    restrict: String,                
    priority: Number,
    terminal: Boolean,
    template: String or Template Function:
    function(tElement, tAttrs) {...},
    templateUrl: String,
    replace: Boolean or String,
    scope: Boolean or Object,
    transclude: Boolean,
    controller: String or
    function(scope, element, attrs, transclude, otherInjectables) { ... },
    controllerAs: String,
    require: String,
    link: function(scope, iElement, iAttrs) { ... },
    compile: // 返回一个对象或链接函数,以下所示:
    function(tElement, tAttrs, transclude) {
        return {
            pre: function(scope, iElement, iAttrs, controller) { ... },
            post: function(scope, iElement, iAttrs, controller) { ... }
           }
        return function postLink(...) { ... }
        }
    };
 });
 

 

 

刚开始接触指令的时候,我简直就是蒙了,这堆参数究竟怎么用怎么理解啊。告诉你们个人一个理解方法。
把它们分红三类:express

  1. 描述指令或DOM自己特性的内部参数数组

  2. 链接指令外界、与其余指令或控制器沟通的对外参数架构

  3. 描述指令自己行为的行为参数

内部参数

  • restrict:String,E(元素)<div my-directive="expression"></div> C(类名) M(注释)<--directive:my-directive expression-->

  • priority: Number,指令执行优先级

  • template: String,指令连接DOM模板,例如“<h1>{{head}}</h1>”

  • templateUrl:String,DOM模板路径

  • replace: Boolean,指令连接模板是否替换原有元素,

对外参数——scope

scope参数很是重要,本应该是放到最后说明的,可是scope倒是理解其余参数的关键,因此务必先跟你们说清楚。

scope参数的做用是,隔离指令与所在控制器间的做用域、隔离指令与指令间的做用域。

scope参数是可选的,默认值为false,可选true、对象{};

  • false:共享父域

  • true:继承父域,且新建独立做用域

  • 对象{}:不继承父域,且新建独立做用域

false、true、{}三者对比

来看个例子

 
<body>
    <div ng-controller='parentCtrl'>
        <h3>指令scope参数——false、true、{}对比测试</h3>
        parent:
        <div>
            <span> {{parentName}}</span>
            <input type="text" ng-model="parentName" />
        </div>
        <br />
        <child-a></child-a>
        <br />
        <child-b></child-b>
        <br />
        <child-c parent-name="parentName"></child-c>
    </div>
    <!--t1指令模板-->
    <script type="text/html" id="t1">
        <div>
            <span>{{parentName}}</span>
            <input type="text" ng-model="parentName" />
        </div>
    </script>
    <script>
        var app = angular.module("app", []);

        app.controller('parentCtrl', function ($scope) {
            $scope.parentName = "parent";
        })

        //false:共享做用域
        app.directive('childA', function () {
            return {
                restrict: 'E',
                scope: false,
                template: function (elem, attr) {
                    return "false:" + document.getElementById('t1').innerHTML;
                }
            };
        });

        //true:继承父域,并创建独立做用域
        app.directive('childB', function () {
            return {
                restrict: 'E',
                scope: true,
                template: function (elem, attr) {
                    return "true:" + document.getElementById('t1').innerHTML;
                },
                controller: function ($scope) {
                    $scope.parentName = "parent";

                    //已声明的状况下,$scope.$watch监听的是本身的parentName
                    $scope.$watch('parentName', function (n, o) {
                        console.log("child watch" + n);
                    });

                    //$scope.$parent.$watch监听的是父域的parentName
                    $scope.$parent.$watch('parentName', function (n, o) {
                        console.log("parent watch" + n);
                    });
                }
            };
        });

        //{}:不继承父域,创建独立做用域
        app.directive('childC', function () {
            return {
                restrict: 'E',
                scope: {},
                template: function (elem, attr) {
                    return "{}:" + document.getElementById('t1').innerHTML;
                },
                controller: function ($scope) {
                    console.log($scope);
                }
            };
        });

    </script>
</body>
 

 

 

false参数

本质:子域与父域共享做用域。
特色:父域修改parentName的同时,指令绑定的parentName的元素会被刷新。

反之,指令内部parentName被修改时,父域的parentName一样会被刷新。

true参数

本质:子域继承父域,并创建独立做用域。
特色:

一、在指令已声明parentName的状况下,父域parentName变动,指令中parentName不会发生变化。
指令在true参数下,创建了的scope,独立并隔离与父控制器的scope。

controller: function ($scope) { $scope.parentName = "parent"; } 

反之,指令中parentName变动,父域也不会发生变化。

二、在指令未声明parentName的状况下,父域的parentName变动,指令中parentName也会刷新
这种状况不少时候会被忽略,指令的scope没有声明对象时,其元素绑定的仍然是父域的对象。但,一旦指令中Input变动了,对应的独立scope也会自动声明该绑定对象,这就回到了第1种状况。

controller: function ($scope) { //$scope.parentName = "parent"; } 

然而,指令中parentName变动,父域是不会变化的;

三、在指令已声明parentName的状况下 ,在指令中监听父域parentName 的变化无效。但监听子域parentName的变化有效
独立子域scope,只能监听本身的,不能监听父域的。但经过 $scope.$parent能够监听父域。

 
controller: function ($scope) {
    $scope.parentName = "parent" ;

    //已声明的状况下,$scope.$watch监听的是本身的parentName
    $scope.$watch( 'parentName' , function (n, o) {
        console.log("child watch" + n);
    });

    //$scope.$parent.$watch监听的是父域的parentName
    $scope.$parent.$watch( 'parentName' , function (n, o) {
        console.log("parent watch" + n);
    });
}
 

 

 

四、在指令未声明parentName的状况下 ,在指令中监听父域parentName的变化有效。
这里就不解释了,参考第2点,你们能够动手试一下。

 
controller: function ($scope) {
    //$scope.parentName = "parent";

    //未声明的状况下,$scope.$watch监听的是父域的parentName
    $scope.$watch('parentName' , function (n, o) {
        console.log("child watch" + n);
    });
}
 

 

 

对象{}参数

本质:子域不继承父域,并创建独立做用域。
特色:

一、当scope对象为空对象时,不管是父域parentName,仍是指令子域parentName发生变动,都不会影响到对方。
原理很清楚,就是指令创建的独立做用域,与父域是彻底隔离的。

scope: {} 

二、当scope对象为非空对象时,指令会将该对象处理成子域scope的扩展属性。而父域与子域之间传递数据的任务,就是能够经过这块扩展属性完成。

 
<div ng-controller='parentCtrl'>
    parent:
    <p><span>{{name}}</span><input type="text" ng-model="name" /></p>
    <p><span>{{sexy}}</span><input type="text" ng-model="sexy" /></p>
    <p><span>{{age}}</span><input type="text" ng-model="age" /></p>
    <br />

    <!--特别注意:@与=对应的attr,@是单向绑定父域的机制,记得加上{{}};&对应的attrName必须以on-开头-->
    <child-c my-name="name" my-sexy-attr="sexy" my-age="{{age}}" on-say="say('i m ' + name)"></child-c>
</div>

<!--t1指令模板-->
<script type="text/html" id="t1">
    <div>
        <span>{{myName}}</span>
        <input type="text" ng-model="myName" />
    </div>
    <div>
        <span>{{mySexy}}</span>
        <input type="text" ng-model="mySexy" />
    </div>
    <div>
        <span>{{myAge}}</span>
        <input type="text" ng-model="myAge" />
    </div>
</script>

<script>
    var app = angular.module("app", []);

    app.controller('parentCtrl', function ($scope) {
        $scope.name = "mark";
        $scope.sexy = "male";
        $scope.age = "30";
        $scope.say = function (sth) {
            alert(sth);
        };
    })

    app.directive('childC', function () {
        return {
            restrict: 'E',
            scope: {
                myName: '=',
                mySexy: '=mySexyAttr',
                myAge: '@',
                onSay: '&'
            },
            template: function (elem, attr) {
                return "{}:" + document.getElementById('t1').innerHTML;
            },
            controller: function ($scope) {
                console.log($scope.myName);
                console.log($scope.mySexy);
                console.log($scope.myAge);
                $scope.onSay();
            }
        };
    });

</script>
 

 

 

@(or @Attr)绑定策略——本地做用域属性,使用@符号将本地做用域同DOM属性的值进行绑定。指令内部做用域可使用外部做用域的变量。(单向引用父域对象)

<child-c my-age="{{age}}"></child-c> 

ps:@ 是单向绑定本地做用域,记得加上{{}}

scope: { myAge: '@', } 

= (or =Attr)绑定策略——双向绑定:经过=能够将本地做用域上的属性同父级做用域上的属性进行双向的数据绑定。就像普通的数据绑定同样,本地属性会反映出父数据模型中所发生的改变。(双向引用父域对象)

 
<child-c onSay="name"></child-c>
 

 

 

ps:=策略不须要加上{{}}进行绑定

 
scope: {          
     myName: '=',
}
 

 

 

& (or &Attr)绑定策略——经过&符号能够对父级做用域进行绑定,以便在其中运行函数。(调用父域函数)

 
<child-c on-say="say('i m ' + name)"></child-c>
 

 

 

ps:&对应的attrName必须以on-开头

scope: { onSay: '&', } 

父域绑定调用函数及传参

 
app.controller('parentCtrl', function ($scope) {
     $scope.say = function (sth) {
         alert(sth);
     };
})
 

 

 

ps特别注意:@与=对应的attr,;

 
<child-c my-name="name" my-sexy-attr="sexy" my-age="{{age}}" on-say="say('i m ' + name)"></child-c>
 

 

 

总结下来,scope扩展对象,既可以解耦父域与子域共域的问题,也可以实现指令与外界通信的问题,是Angular开发指令化模块化的重要基础。在日后的章节,我会向你们介绍指令化开发的更多实例。

对外参数——require

scope是指令与外界做用域通信的桥梁,而require是指令与指令之间通信的桥梁。这个参数最大的做用在于,当要开发单指令没法完成,须要一些组合型指令的控件或功能,例如日期控件,经过require参数,指令能够得到外部其余指令的控制器,从而达到交换数据、事件分发的目的。

使用方法: 在link函数第4个参数ctrl中获取注入外部指令的控制器,若是require为String,ctrl为对象,若是require是数组,ctrl为数组。

 
require: '^teacher1',
link: function ($scope, $element, $attrs, ctrl) {
    //ctrl指向teacher1指令的控制器
}
 

 

 

?策略——寻找指令名称,若是没有找到,link函数第4个参数为null;若是没有?,则报错。

^ 策略——在自身指令寻找指令名称的同时,向上父元素寻找;若是没有^,则仅在自身寻找。
以下例子,指令teacher及自身,可是不能找到相邻兄弟的<div teacher> <student-a></student-a> <student-b></student-b> </div>

完整例子

 
<body>
    <div teacher>
        {{name}}
        <student-a></student-a>
        <student-b></student-b>
    </div>
    <script>
        var app = angular.module("app", []);

        //studentA——require指向父级指令teacher
        app.directive('studentA', function () {
            return {
                require: '?^teacher',
                scope: {},
                template: '<div>A`s teacher name: <span>{{teacherName}}</span></div>',
                link: function ($scope, $element, $attrs, ctrl) {
                    //获取teacher指令控制器,并调用其方法sayName()
                    $scope.teacherName = ctrl.sayName();
                }
            };
        });

        //studentB——require指向父级指令teacher,及指令studentA
        //可是,因为不能得到兄弟,也没有采起?策略,致使报错
        app.directive('studentB', function () {
            return {
                require: ['?^teacher', 'studentA'],
                scope: {},
                template: '<div>B`s teacher name: <span>{{teacherName}}</span></div>',
                link: function ($scope, $element, $attrs, ctrl) {
                    $scope.teacherName = ctrl.sayName();
                }
            };
        });

        app.directive('teacher', function () {
            return {
                restrict: 'A',
                controller: function ($scope) {
                    $scope.name = "Miss wang";

                    //扩展控制器的方法sayName,目的是让外部内获取控制器内部数据
                    this.sayName = function () {
                        return $scope.name;
                    };
                }
            };
        });
    </script>
</body>
 

 

 

既然require能够获取外部指令,那Angular原生指令应该也是可以获取。其中最普遍应用的就是 行为参数——link与controller

为何要把link与controller两个参数放到一块儿?
由于不少童鞋会把它们错误地混淆使用,包括我本身。

link与controller都是描述指令行为的参数,但它们是要描述的行为是彻底不一样的类型。

controller语法  controller自己的意义就是赋予指令控制器,而控制器就是定义其内部做用域的行为的。

因此controller要描述的是:指令的做用域的行为。

//指向匿名控制器 controller: function ($scope) { }, //指向控制器mainCtrl controller: "mainCtrl" 

link语法  link名称是连接函数,啥意思,好像挺难理解。因此在解释连接函数以前,先要说一下Angular的初始化对于指令究竟作了什么。

Angular在刚从HTTP Response接收静态素材之初,会首先去分析母页HTML中有哪些原生指令或自定义指令,而后再去加载指令的template模板HTML,而template模板中又去加载本身的指令模板,如此类推,直到Angular找到了全部的指令及模板,造成模板树,并返回模板函数,提供给下一阶段进行数据绑定。

<body>
    <stu1 ></ stu1>
    <script >
        var app = angular.module("app" , []);

        app.directive( 'stu1' , function () {
            return {
                restrict: 'E' ,
                template: "<p>1</p><stu2></stu2>" ,
                link: function (scope) {
                    console.log( 'stu1 running' );
                }
            };
        });

        app.directive( 'stu2' , function () {
            return {
                restrict: 'E' ,
                template: "<p>2</p><stu3></stu3>" ,
                link: function (scope) {
                    console.log( 'stu2 running' );
                }
            };
        });

        app.directive( 'stu3' , function () {
            return {
                restrict: 'E' ,
                template: "<p>3</p>" ,
                link: function (scope) {
                    console.log( 'stu3 running' );
                }
            };
        });

    </script >
</ body>

console output

stu3 running
stu2 running
stu1 running

注意以上例子,在第一个断点 简单来讲就是:

    加载模板,造成DOM模板树

    @@@@

    数据绑定

@@@@是啥?没错,就是link连接函数,它会在造成模板树以后,在数据绑定以前,从最底部指令开始,逐个指令执行它们的link函数。

在这个时间节点的link函数,操做DOM的性能开销是最低,很是适合在这个时机执行DOM的操做,例如鼠标操做或触控事件分发绑定、样式Class设置、增删改元素等等。

因此link就是描述指令元素操做行为。

link: function (scope, element, attr, ctrl) {

    element.bind("click", function () {
        console.log("绑定点击事件");
    });

    element.append("<p>增长段落块</p>");

    //设置样式
    element.css("background-color", "yellow");

    //不推荐,在link中赋予scope行为
    scope.hello = function () {
        console.log("hello");
    };
}

同理,在link中定义$scope行为是不推荐的。

这样想一想,对于controller与link,就明白了。但还有一个问题,它们俩的执行顺序是?答案是先controller,后link。

放到全局顺序就是:

    执行controller,设置各个做用域scope

    加载模板,造成DOM模板树

    执行link,设置DOM各个行为

    数据绑定,最后scope绑上DOM

例子

    <div student>
        {{name }}
    </div>
    <script>
        var app = angular.module("app", []);

        app.directive('student', function () {
            return {
                restrict: 'A',
                controller: function ($scope) {
                    $scope.name = "tgor";

                    console.log('controller running');
                },
                link: function (scope, el) {
                    el.append("<p>hello</p>");

                    console.log('link running');
                }
            };
        });

    </script>

 

总结

总结就是不想总结,还有compile、controllerAs……没有讲。写得累死,但愿能给你们一些帮助,同时也能够将本身的错误暴露出来,望你们指正。
下一节,会跟你们分享一下指令在实战中的一些实例。

end