“控制器应该尽量保持短小精悍,而在控制器中进行DOM操做和数据操做则是一个很差的实践。设计良好的应用会将复杂的逻辑放到指令和服务中。经过使用指令和服务,咱们能够将控制器重构成一个轻量且更易维护的形式” ----《AngularJs权威教程》
Directive中文翻译为指令,从《AngularJs权威教程》中对指令的描述,我理解的指令是对控制器的补充,主要功能是对Dom元素和数据的操做(通常指令主Dom操做、服务主数据操做,非强制规定),本文基于Angular1.4.6版本进行讲解和演示。css
在Angular中内置了一些指令,如常见的ng-app
指令初始化一个 AngularJS 应用程序,ng-model
指令把元素值(好比输入域的值)绑定到应用程序。同时Angular也支持建立自定义指令。html
基础案例展现。网络
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>
标签引入到页面中,涉及到指令两个很重要的参数template
和restrict
,下面会具体讲解指令的参数。ui
参数 | 类型 |
---|---|
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、link
。spa
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中是没问题的。
scope是指令的做用域,每一个指令建立后能够继承父做用域(外部controller
提供的做用域或根做用域$rootScope
),也能够拥有本身独立的做用域,scope
参数有三种,分别是:false、true、{}
,默认为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
。当改变指令模块中input
的name
会发现父做用域controller
中的name
也会发生变化,这说明指令的做用域和父做用域在同一个做用域下。
当scope
参数改成true
时,咱们再来看:
一样指令中继承了父做用域的属性和方法,不一样的是,当修改指令input
的name
时,指令的name
对应改变,父做用域controller
中的name
并未发生改变,这说明指令的做用域和父做用域不在同一个做用域下。
当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>
,属性的名字要用“-”将两个单词链接,由于是数据的单向绑定因此要经过使用{{}}
来绑定数据。在线代码演示结果中修改input
的name
值可看出,只指令的做用域生效name
发生改变,父做用域name
未改变,这点和scope
参数为true
效果是同样的。
这是一个双向数据绑定的前缀标识符。在元素中使用属性,如:<div my-dir age="age"></div>
,由于是数据的双向绑定,因此使用=
前缀实现,而不是{{}}
。在线代码演示结果中修改input
的name
值可看出,指令的做用域和父做用域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 = false
,scope = true
初始化都会继承父做用域中的属性和方法,不一样的是scope = false
和父做用域在同一做用域下,scope = true
和父做用域不在同一做用域,而是建立了一个独立的做用域。scope = {}
不会继承父做用域,属性控制更加灵活,单向绑定@
,外部scope
可以影响内部scope
,但反过来不成立,双向绑定=
,外部scope
和内部scope
的model
可以相互改变,函数绑定&
把内部scope
的函数和外部scope
的函数绑定起来。
template
参数值能够是一个字符串也能够是一个函数,值为字符串比较好理解,上文一直在演示的就是值为字符串的形式。当值为函数时,可接收两个参数element
和attrs
。举例:
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: '指令元素' }
templateUrl
和上一个参数template
用法基本相同,只不过template
值为字符串是一段html模板,templateUrl
值为字符串是文件路径。如:
angular.module('myApp', []) .controller("myCtrl", function($scope) {}) .directive("myDir", function() { return { templateUrl: 'index.html'; } });
具体使用请参考参数template
。
replace
是一个可选参数,默认值为false
,表示指令中模板template
内容会当作子元素插入到指令元素中,若是设置为true
,表示指令中模板会直接替换掉指令元素。
举例replace
为false
:
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>
被替换掉了,这就是二者的区别。
当同一个元素声明两个指令,须要设定执行前后顺序,这时就须要用到参数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
。
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()
等。
TODO