AngularJS 自定义指令

来源:互联网 发布:地基承载力的简易算法 编辑:程序博客网 时间:2024/05/25 19:56

本篇文章参考自AngularJS权威教程。

1. 简介

  AngularJS中的指令,可以把它简单的理解成在特定DOM元素上运行的函数,指令可以拓展该元素的功能,例如ng-click可以让一个元素能够监听click事件,并且在接收到click事件的时候能够执行AngularJS表达式。

2. 自定义指令

  我们通过AngularJS模块的directive()方法来定义指令。该方法有两个参数,第一个参数是字符串,代表着指令的名字,第二个参数是函数,该函数返回一个对象,这个对象定义了指令的所有行为(也可以简单的返回一个函数,该函数相当于只定义了该对象的link属性,只是在构建简单指令的一个语法糖)。在DOM元素调用指令时,会使用$compile服务利用该对象来构造指令的行为。
  定义指令行为的对象有以下的属性可选。

1)restrict属性

  该属性值为一个字符串,该字符串中的字母代表着指令可以使用何种形式被声明。

可选值 含义 用法实例 E 元素 <my-directive></my-directive> A(默认) 属性 <div my-directive=”expression”></div> C 类名 <div class=”my-directive:expression;”></div> M 注释 <–direcctive:my-directive:expression–>

  例如”EA”代表着可以用元素和属性的方式声明指令,而类名和注释则不行。

2)priority属性

  该属性值为数值型,代表优先级,大多数指令会忽略该属性而使用默认值0。在同一个元素上,优先级更高的属性会被先调用,同一优先级的则是先声明的先调用。

3)terminal属性

  该属性值为布尔型,默认为false。这个属性是用来告诉AngularJS,当该指令值为false时是否停止运行当前元素上比本指令优先级低的指令(同优先级的仍然会运行)。
  一个例子是ngView和ngIf,ngIf的优先级略高于ngView,如果ngIf表达式为false,那么ngView就不会被执行。

4)template属性

  template属性值为字符串或函数。如果是字符串,那么它代表着一段HTML文本,如果是函数,它可以接收两个参数tElement和tAttrs,其中t代表template,该词是相对与instance而言的,也就所是所谓的模板和实例,该函数会返回一个代表模板的字符串。故无论是字符串还是函数,template代表的就是一个HTML模板。
  模板字符串中至少得有一个根DOM元素,即使它只有一个文本节点,另外由于字符串多行必须使用+号或者用\,使得维护起来非常麻烦以及不美观,所以强烈推荐使用templateUrl。

5)templateUrl属性

  templateUrl属性值可以是字符串或函数。如果是字符串,那么它代表了一个外部HTML文件的路径,如果是函数,那么它同样有tElement和tAttrs两个参数,函数返回一个外部HTML文件路径的字符串。

6)replace属性

  replace属性是个布尔值,默认为false。默认情况下,template或templateUrl提供的模板会被当做子元素插入到调用此指令的元素内部。而如果设置该属性为true,那么会替代该元素。

7)scope属性

可选值 含义 true 从当前作用域对象继承一个新的作用域对象 false(默认) 直接调用当前作用域对象 对象 创建一个与当前作用域隔离的作用域对象

  前两种情况相对较简单,重点分析最后一种情况。具有隔离作用域的指令最主要的使用场景是创建可复用的组件,组件可以在未知上下文使用,并且可以避免污染所处的外部作用域或不经意地污染内部作用域。
  直接为scope属性赋空对象可以使其数据完全与外部隔绝。但是,完全无数据的隔离作用域并不常见,我们可以在为对象添加属性与值以指定该隔离作用域内的变量如何与外部作用域联系。AngularJS提供了以下三种绑定策略,使我们能够将指令内部的隔离作用域同指令外部的作用域进行数据绑定。

  • @
    使用@符号可以将该隔离作用域的变量同使用指令的DOM元素属性值相绑定。
  • =
    使用=符号可以将该隔离作用域的变量同父级作用域的变量进行双向绑定。
  • &
    使用&符号可以将该隔离作用域的函数返回值同父级作用域的任何属性(包括数组,对象,函数)进行绑定。

下面演示@符号的用法,该例子将隔离作用域的name变量与for-name属性的值绑定在一块,如果for-name属性的值修改了(如果没修改只是重新赋值隔离作用域变量不会变),隔离作用域的变量也会修改。
  另外,有一个语法糖,当符号后面跟的属性名与变量名相同,可以略去。即name:”@”等同与name”@name”。

<body ng-app="myApp" ng-controller="myController">    <my-direct for-name="{{name}}"></my-direct>    <p>父级作用域:{{ name }} <input type="button" value="外部改变" ng-click="name='李四'"/></p>    <script>        var app = angular.module("myApp",['ionic']);        app.controller("myController",function($scope) {            $scope.name = "张三";        });        app.directive("myDirect",function() {            return {                restrict:"E",                template:"<div>隔离作用域:{{ name }} <input type='button' value='内部改变' ng-click='name=&quot;王五&quot;'/></div>",                scope:{                    name:"@forName"                }            }        });    </script></body>

这里写图片描述
  下面演示=符号的用法,该例子将隔离作用域的name变量与父作用域的name变量进行双向绑定,它们会同步变化。

<body ng-app="myApp" ng-controller="myController">    <my-direct for-name="name"></my-direct>    <p>父级作用域:{{ name }} <input type="button" value="外部改变" ng-click="name='李四'"/></p>    <script>        var app = angular.module("myApp",['ionic']);        app.controller("myController",function($scope) {            $scope.name = "张三";        });        app.directive("myDirect",function() {            return {                restrict:"E",                template:"<div>隔离作用域:{{ name }} <input type='button' value='内部改变' ng-click='name=&quot;王五&quot;'/></div>",                scope:{                    name:"=forName"                }            }        });    </script></body>

这里写图片描述
  最后演示&符号,我们包装了父级作用域的变量,数组,无参方法,及有参方法。注意在隔离作用域使用有参方法时,传进去的应该是一个对象,对象的属性就是所需要的参数,值就是该参数的值。

<body ng-app="myApp" ng-controller="myController">    <my-direct get-title="title" get-people="people" alert-without-parameter="alert_without_parameter()" alert-with-parameter="alert_with_parameter(str)"></my-direct>    <script>        var app = angular.module("myApp",['ionic']);        app.controller("myController",function($scope) {            $scope.title="名人";            $scope.people = [                {name:"习近平",age:63},                {name:"李克强",age:61}            ];            $scope.alert_without_parameter = function() {                alert("无参方法");            }            $scope.alert_with_parameter = function(str) {                alert(str);            }        });        app.directive("myDirect",function() {            return {                restrict:"E",                template:"<div>{{ getTitle() }}</div><ul><li ng-repeat='person in getPeople()'>{{ person.name + person.age }}</li></ul>" +                "<input type='button' value='测试' ng-click='alertWithoutParameter()'/>" +                "<p><input type='text' ng-model='display'/><input type='button' value='测试' ng-click='alertWithParameter({str:display})'> </p>",                scope:{                    getTitle:"&",                    getPeople:"&",                    alertWithoutParameter:'&',                    alertWithParameter:'&'                }            }        });    </script></body>

这里写图片描述

8) transclude属性

  该属性值为布尔值,默认为false。该值表示是否使用指令所在元素其中的内容的模板整个传进来,放到template中声明ng-transclude指令的地方。最常用的用法是用指令创建一个布局,然后使用指令把实际内容传进来,这样就可以复用布局了。
  下面是一个复用标题的例子。

<body ng-app="myApp" ng-controller="myController">     <sidebox title="Links">        <ul>            <li>First link</li>            <li>Second link</li>        </ul>    <sidebox/>    <sidebox title="TagCloud">            <a href="">Graphics</a>            <a href="">AngularJS</a>            <a href="">D3</a>            <a href="">Front-end</a>            <a href="">Startup</a>    </sidebox>    <script>        var app = angular.module("myApp",['ionic']);        app.directive('sidebox',function() {           return {               restrict:'EA',               scope:{                   title:'@'               },               transclude:true,               template:'<div class="sidebox">'+                       '<div class="content"><h2 class="header">{{title}}</h2>' +               '<span class="content" ng-transclude></span></div>'           };        });        app.controller("myController",function($scope) {        });    </script></body>

这里写图片描述

9)compile与link属性

  compile属性值与link属性值都是一个函数,link函数又返回一个具有pre(一般称为pre-link)和post(一般称为post-link)函数的对象(如果只返回一个函数,那么是post-link函数)。compile属性与link属性是互斥的,如果同时存在会忽略link属性。也就是说,如果有compile函数也有pre和post-函数,那么就不能有link属性,只能将包含pre函数和post函数的对象由compile函数来返回。

  • compile函数
    能够在指令和实时数据加到DOM中之前进行DOM操作,在这个函数进行增删节点等DOM操作是安全的。该函数没有scope参数的原因是此时scope还没生成。
/*** Compile function* @param tElem - template element* @param tAttrs - attributes of the template element*/function(tElem, tAttrs){    // ...};
  • pre-link函数
    pre-link函数在实际中运用较少,它发生在所有compile函数运行之后,在它所有子指令的post-link将要运行之前。
/*** Pre-link function* @param scope - scope associated with this istance* @param iElem - instance element* @param iAttrs - attributes of the instance element*/function(scope, iElem, iAttrs){    // ...};
  • post-link函数
    post-link函数通常被认为是执行业务逻辑最安全的地方。因为它运行的时候它的子指令的post-link和pre-link都已经执行完成。
/*** Post-link function* @param scope - scope associated with this istance* @param iElem - instance element* @param iAttrs - attributes of the instance element*/function(scope, iElem, iAttrs){    // ...};

  下面这个例子演示了这三个运行的顺序,以及iElem参数和tElem的参数的区别。

<body ng-app="myApp" ng-controller="myController">    <first>        <second>            <fourth>                Hello {{ name }}            </fourth>        </second>        <third>            <fifth>                Hello {{ name }}            </fifth>        </third>    </first>    <script>        var app = angular.module("myApp",[]);        function createDirective(name) {            return function() {                return {                    restrict:"E",                    compile:function(tElem,tArrs) {                        console.log(name + ': compile => ' + tElem.html());                        return {                            pre:function (scope,iElem,iAttrs) {                                console.log(name + ': pre-link => ' + iElem.html());                            },                            post:function(scope,iElem,iAttrs) {                                console.log(name + ': post-link => ' + iElem.html());                            }                        }                    }                };            }        }        app.controller('myController',function($scope) {            $scope.name="World";        });        app.directive('first',createDirective('first'));        app.directive('second',createDirective('second'));        app.directive('third',createDirective('third'));        app.directive('fourth',createDirective('fourth'));        app.directive('fifth',createDirective('fifth'));    </script></body>

这里写图片描述这里写图片描述
  首先是所有的compile函数按嵌套关系先序遍历运行。然后是pre-link函数pre-link函数之间的先后顺序仍按照先序遍历,但是当一个已经运行过pre-link元素的所有子元素都运行了pre-link函数时,会马上运行它的post-link函数,同时有多个这样的元素时,先运行pre-link的后运行post-link。
  这样的好处在于,post-link函数运行时,它的所有子元素肯定都已经运行过了pre-link,post-link,以及compile函数,所以一般我们的业务逻辑写在post-link中。
  另外,注意到了tElem的Html内容是不包含任何指令的,它只是一个模板,而iElem则是一个实例,它包含所有的指令以及作用域,这也是为什么增删节点在compile中进行更安全的原因。 

4 0