Angular自定义指令Directive详解

“控制器应该尽量保持短小精悍,而在控制器中进行DOM操做和数据操做则是一个很差的实践。设计良好的应用会将复杂的逻辑放到指令和服务中。经过使用指令和服务,咱们能够将控制器重构成一个轻量且更易维护的形式” ----《AngularJs权威教程》

一、Angular Directive说明

Directive中文翻译为指令,从《AngularJs权威教程》中对指令的描述,我理解的指令是对控制器的补充,主要功能是对Dom元素和数据的操做(通常指令主Dom操做、服务主数据操做,非强制规定),本文基于Angular1.4.6版本进行讲解和演示。css

在Angular中内置了一些指令,如常见的ng-app指令初始化一个 AngularJS 应用程序,ng-model指令把元素值(好比输入域的值)绑定到应用程序。同时Angular也支持建立自定义指令。html

二、Angular Directive使用

基础案例展现。网络

Html代码:app

<!DOCTYPE html>
<html ng-app="myApp">
<head>
    <meta charset="utf-8">
    <script src="http://cdn.static.runoob.com/libs/angular.js/1.4.6/angular.min.js"></script>
</head>
<body ng-controller="myCtrl">
    <!-- 引入指令 -->
    <my-dir></my-dir>
</body>
</html>

Js代码:函数

angular.module('myApp', [])
    .controller("myCtrl", function($scope) {})
    .directive("myDir", function() {  
        return {  
            template: "<h1>这是自定义指令</h1>",
            restrict: "E"  
        }  
    });

在线代码演示post

这是一个很简单的指令小案例,指令中的template模板内容“这是自定义指令”经过<my-dir>标签引入到页面中,涉及到指令两个很重要的参数templaterestrict,下面会具体讲解指令的参数。ui

三、Angular Directive参数

参数 类型
restrict String
priority Number
terminal Boolean
template String or Template Function
templateUrl String or Template Function
replace Boolean or String
transclude Boolean
scope Boolean or Object
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(...) { ... }
}

已上是指令的全部参数以及类型,重点介绍经常使用参数restrict、scope、replace、template、templateUrl、linkspa

参数1:restrict

restrict用于指定directive的使用形式。如上文例子指令代码中的restrict: "E"即指名使用Element元素形式,同时还有A、C、M共四种形式。默认值为EA翻译

参数值 形式 示例
E 元素 Element <my-dir></my-dir>
A 属性 Attribute <div my-dir></div>
C 样式 Class <div class="my-dir"></div>
M 注释 Comment <!-- directive:my-dir -->

Html代码:设计

<body>
    <!-- 元素形式引入指令 -->
    <my-dir></my-dir>
    <!-- 属性形式引入指令 -->
    <div my-dir></div>
    <!-- 样式形式引入指令 -->
    <div class="my-dir"></div>
    <!-- 指令形式引入指令 -->
    <!-- directive: my-dir -->
</body>

Js代码:

angular.module('myApp', [])
    .controller("myCtrl", function($scope) {})
    .directive("myDir", function() {  
        return {  
            template: "<h1>这是自定义指令</h1>",
            restrict: "ECMA"
        }  
    });

在线代码演示

指令命名需使用驼峰模式,Html引入指令需使用“-”链接每一个单词,如指令myDir在引入指令时改成my-dir写法。E、A、C、M四种形式可组合使用。

说明:
在线代码演示中注释引入<!-- directive: my-dir -->没有成功引入指令内容,这是因为在线codepen不支持,放到本身IDE中是没问题的。

参数2:scope

scope是指令的做用域,每一个指令建立后能够继承父做用域(外部controller提供的做用域或根做用域$rootScope),也能够拥有本身独立的做用域,scope参数有三种,分别是:false、true、{},默认为false

scope = false

scope设置为false时,指令模板中能够直接使用父做用域中得变量和函数,举个例子:

Html代码:

<div ng-app="MyApp">
    <div class="container" ng-controller="MyController">
        <div class="my-info">个人名字是:<span ng-bind="name"></span>
        <!-- 使用"ng-bind"防止网络状态不佳时出现没有被赋值表达式   -->
            <br/>个人年龄是:<span ng-bind="age"></span>

        </div>
        <!-- 使用属性声明指令 -->
        <div class="my-dir" my-dir></div>
    </div>
</div>

Js代码:

angular.module("MyApp", [])
    .controller("MyController", function ($scope) {
        // 这里咱们在做用域里初始化两个变量
    $scope.name = "echeverra";
    $scope.age = 20;
        // 建立一个方法,修改咱们建立的对象的年龄
    $scope.changeAge = function () {
        $scope.age = 22;
    }
})
    // 建立咱们的指令,指令名字为"myDir"
    .directive("myDir", function () {
    var obj = {
        // 指令的声明模式为 "AE" 属性和元素
        restrict: "AE",
        // 指令继承父做用域的属性和方法
        scope: false,
        replace: true,
        template: "<div class='my-directive'>" +
            "<h3>下面部分是咱们建立的指令生成的</h3>" +
            "个人名字是:<span ng-bind='name'></span><br/>" +
            "个人年龄是:<span ng-bind='age'></span>" +
            "<input type='text' ng-model='name'>"+
            " </div>"
    }
    return obj;
});

在线代码演示

从在线代码演示效果中咱们看到建立的指令继承了父做用域controller中的属性name、age,一样也继承了方法changeAge。当改变指令模块中inputname会发现父做用域controller中的name也会发生变化,这说明指令的做用域和父做用域在同一个做用域下。

scope = true

scope参数改成true时,咱们再来看:

在线代码演示

一样指令中继承了父做用域的属性和方法,不一样的是,当修改指令inputname时,指令的name对应改变,父做用域controller中的name并未发生改变,这说明指令的做用域和父做用域不在同一个做用域下。

scope = {}

scope设置为{}会使指令的做用域变得更加灵活,修改以前的代码:

Html代码:

<div ng-app="MyApp">
    <div class="container" ng-controller="MyController">
        <div class="my-info">个人名字是:<span ng-bind="name"></span>

            <br/>个人年龄是:<span ng-bind="age"></span>
            <br />
        </div>
        <div class="my-dir" my-dir my-name="{{name}}" age="age"  change-my-age="changeAge()"></div>
    </div>
</div>

Js代码:

angular.module("MyApp", [])
    .controller("MyController", function ($scope) {
    $scope.name = "echeverra";
    $scope.age = 20;
    $scope.male = 'man';
    $scope.changeAge = function(){
        $scope.age = 0;
    }
})
    .directive("myDir", function () {
    var obj = {
        restrict: "AE",
        scope: {
            name: '@myName',
            age: '=',
            changeAge: '&changeMyAge'
        },
        replace: true,
        template: "<div class='my-dir'>" +
            "<h3>下面部分是咱们建立的指令生成的</h3>" +
            "个人名字是:<span ng-bind='name'></span><br/>" +
            "个人性别是:<span ng-bind='male'></span><br/>" +
            "个人年龄是:<span ng-bind='age'></span><br/>" +
            "在这里修更名字:<input type='text' ng-model='name'><br/>" +
            "<button ng-click='changeAge()'>修改年龄</button>" +
            " </div>"
    }
    return obj;
});

在线代码演示

咱们使用了隔离的做用域,不表明咱们不可使用父做用域的属性和方法,咱们能够经过向scope{}中传入特殊的前缀标识符(即prefix),来进行数据绑定。

咱们能够经过前缀标识符@, &, =应用到指令中的属性,如:{name: '@myName', age: '=', changeAge: '&changeMyAge'}age: '='age: '=age'的简写),咱们能够在<div class="my-directive" my-directive my-name="{{name}}" age="age" change-my-age="changeAge()"></div>这个元素中,经过使用属性my-name,age,change-my-age来引用这些属性的值。

首先能够肯定的是scope={}不会继承父做用域,由于个人性别项male值是空的,在scope中未进行属性male绑定。

那么前缀标识符@, &, =分别有什么做用呢?

@

这是一个单向数据绑定的前缀标识符。在元素中使用属性,如:<div my-dir my-name="{{name}}"></div>,属性的名字要用“-”将两个单词链接,由于是数据的单向绑定因此要经过使用{{}}来绑定数据。在线代码演示结果中修改inputname值可看出,只指令的做用域生效name发生改变,父做用域name未改变,这点和scope参数为true效果是同样的。

=

这是一个双向数据绑定的前缀标识符。在元素中使用属性,如:<div my-dir age="age"></div>,由于是数据的双向绑定,因此使用=前缀实现,而不是{{}}。在线代码演示结果中修改inputname值可看出,指令的做用域和父做用域name均发生改变,这点和scope参数为false效果是同样的。

&

这是一个绑定函数方法的前缀标识符。在元素中使用属性,如:<div my-dir change-my-age="changeAge()"></div>,属性的名字要用“-”将每一个单词链接。在线代码演示结果中点击“修改年龄”按钮,指令做用域和父做用域中的年龄都发生改变,这点和前缀标识符=效果相同。

注意:
在新建立指令的做用域对象中,使用属性的名字进行绑定时,要使用驼峰命名标准,以下面的代码:

scope: {
            // `myName` 就是原来元素中的`my-name`属性
            name: '@myName', 
            age: '=',
            // `changeMyAge`就是原来元素中的`change-my-age`属性
            changeAge: '&changeMyAge' 
        }

进一步说明,咱们建立的指令是如何利用这些前缀标识符来寻找咱们想要的属性或函数的呢?

使用@时当指令编译到模板的name,就会到scope中查找是否含有name的键值对(name:'@myName'),找到后发现前缀标识符@就知道这是一个单向数据绑定标识符,以后会去寻找元素上含有这个值的属性(my-name={{name}}),而后在父做用域中查找{{name}}的值,获得以后传递给属性my-name,经过指令参数scope中键值对name:'@myName'最后传给了模板中的name。(此处比较绕,不过真心值得研究一番,有助于深入理解)。=&@差很少,只不过=进行的是双向的数据绑定,不论模板仍是父做用域上的属性的值发生改变都会使另外一个值发生改变,而&是绑定函数而已。

总结:
scope = falsescope = true初始化都会继承父做用域中的属性和方法,不一样的是scope = false和父做用域在同一做用域下,scope = true和父做用域不在同一做用域,而是建立了一个独立的做用域。
scope = {}不会继承父做用域,属性控制更加灵活,单向绑定@,外部scope可以影响内部scope,但反过来不成立,双向绑定=,外部scope和内部scopemodel可以相互改变,函数绑定&把内部scope的函数和外部scope的函数绑定起来。

参数3:template

template参数值能够是一个字符串也能够是一个函数,值为字符串比较好理解,上文一直在演示的就是值为字符串的形式。当值为函数时,可接收两个参数elementattrs。举例:

Html代码:

<!-- 引入指令 -->
<my-dir name="echeverra" age="20" title="指令元素"></my-dir>

Js代码:

angular.module('myApp', [])
    .controller("myCtrl", function($scope) {})
    .directive("myDir", function() {  
        return {  
            template: function(element, attrs) {
              return '<h2>name:'+attrs.name+'</h2>'+
                   '<h2>age:'+attrs.age+'</h2>'+
                   '<h2>title:'+attrs.title+'</h2>'
            },
            restrict: "E"  
        }  
    });

在线代码演示

函数参数element是指使用此指令的元素<my-dir name="echeverra" age="20" title="指令元素"></my-dir>,而attrs是指令元素上全部属性的集合,形如:

{
  name: 'echeverra',
  age: '20',
  title: '指令元素'
}

参数4:templateUrl

templateUrl和上一个参数template用法基本相同,只不过template值为字符串是一段html模板,templateUrl值为字符串是文件路径。如:

angular.module('myApp', [])
    .controller("myCtrl", function($scope) {})
    .directive("myDir", function() {  
        return {  
            templateUrl: 'index.html';
        }  
    });

具体使用请参考参数template

参数5:replace

replace是一个可选参数,默认值为false,表示指令中模板template内容会当作子元素插入到指令元素中,若是设置为true,表示指令中模板会直接替换掉指令元素。

举例replacefalse:

Html代码:

<div id="dir">
    <!-- 引入指令 -->
    <my-dir></my-dir>
</div>
<button ng-click='getDirCont()'>获取引入指令元素</button>
<div ng-bind="dirCont"></div>

Js代码:

angular.module('myApp', [])

        .controller("myCtrl", function($scope) {
            $scope.getDirCont = function() {
                $scope.dirCont = document.getElementById('dir').innerHTML;
            }
        })
        .directive("myDir", function() {
            return {
                template:"<h1>这是自定义指令</h1>",
                restrict: "E",
                replace: false,
            }
        });

在线代码演示

点击获取引入指令元素按钮后显示<my-dir><h1>这是自定义指令</h1></my-dir>,能够看到指令元素<my-dir></my-dir>还在,若是将replace设置为true,咱们在来看:

在线代码演示

此次获取只显示元素<h1>这是自定义指令</h1>,说明指令元素<my-dir></my-dir>被替换掉了,这就是二者的区别。

参数6:priority

当同一个元素声明两个指令,须要设定执行前后顺序,这时就须要用到参数priority,值为数字,默认值为0,咱们来看下不使用priority的状况会有什么麻烦。

Html代码:

<!-- 引入指令 -->
<div ng-init="greeting='Hello '" d1 d2>{{greeting}}!</div>

Js代码:

angular.module('myApp', [])
        .controller("myCtrl", function($scope) {})
        .directive("d1", function() {
            return {
                link: function (scope) {
                    scope.greeting += 'World ';
                }
            }
        })
        .directive("d2", function() {
            return {
                link: function (scope) {
                    scope.greeting += 'Angular ';
                }
            }
        });

在线代码演示

输出:Hello Angular World !,可见先执行了ng-init="greeting='Hello '",而后执行了指令d2,最后是d1。实际咱们想预期输出的是Hello World Angular !,咱们将指令元素中的d一、d2顺序对调,再将指令中建立的d一、d2两个指令顺序对调,再试发现输出的仍是Hello Angular World !,这是由于Angular是使用字母顺序来肯定连接函数谁先被调用,将d1改成e1就会获得预期的结果,有兴趣的能够尝试一下。如何设定指令执行优先级?这时候priority就派上用场了。

Js代码:

angular.module('myApp', [])
        .controller("myCtrl", function($scope) {})
        .directive("d1", function() {
            return {
                priority: 1,
                link: function (scope) {
                    scope.greeting += 'World ';
                }
            }
        })
        .directive("d2", function() {
            return {
              priority: 2,
                link: function (scope) {
                    scope.greeting += 'Angular ';
                }
            }
        });

在线代码演示

输出:Hello World Angular

参数7:link

link函数主要用于操做Dom元素,给Dom元素绑定事件和监听,link参数要求声明一个函数,称之为链式函数。

写法:

link: function(scope, element, attrs) {
  // 在这里操做DOM
}
  • scope:指令所在的做用域
  • element:指令元素的封装,能够调用angular封装的简装jq方法和属性
  • attr:指令元素的属性的集合

若是指令使用了require选项,那么连接函数会有第四个参数,表明控制器或者所依赖的指令的控制器。

require 'SomeController',
link: function(scope, element, attrs, SomeController) {
  // 在这里操做DOM,能够访问require指定的控制器
}

下面举例操做Dom元素,改变元素样式。

Html代码:

<!-- 引入指令 -->
<input ng-model="color" placeholder="请输入颜色值"/>
<br/>
<my-dir name="my-dir-name"></my-dir>

Js代码:

angular.module('myApp', [])
        .controller("myCtrl", function($scope) {})
        .directive("myDir", function() {
            return {
                template:'<h3 style="background-color:{{color}}">code</h3>',
                replace:true,
                link:function(scope, ele, attrs, ctrl) {
                    ele.bind('click', function () {
                        scope.$apply(function () {
                            scope.color = 'red';
                            ele.text(attrs.name);
                        })
                    });
                    ele.bind('mouseover', function () {
                        ele.css({'cursor': 'pointer'})
                    });
                }
            }
        });

在线代码演示

点击code,获取指令元素的属性name值,ele调用text()展现,同时ele绑定bind鼠标悬浮事件,调用css()方法,改变鼠标样式。ele是指令元素的jqLite包装,因此能够基本的jq方法,如例子中的bind()css()等。

参数8:transclude

TODO

相关文章
相关标签/搜索