AngularJS自定义控件实例详解
本文实例讲述了AngularJS自定义控件。分享给大家供大家参考,具体如下:
自定义指令介绍
AngularJS指令作用是在AngulaJS应用中操作Html渲染。比如说,内插指令({{}}),ng-repeat指令以及ng-if指令。
当然你也可以实现自己的。这就是AngularJS所谓的"教会HTML玩新姿势”。本文将告诉你如何做到。
指令类型
可以自定义的指令类型如下:
元素
属性
CSSclass
Commentdirectives
这里面,AngularJS强烈建议你用元素和属性类型,而不用CSSclass和commentdirectives(除非迫不得已)。
指令类型决定了指令何时被激活。当AngularJS在HTML模板中找到一个HTML元素的时候,元素指令被激活。当AngularJS找到一个HTML元素属性时,属性指令被激活。当AngularJS找到一个CSSclass时,CSSclass指令被激活,最后,当AngularJS找到HTMLcomment时,commentdirective被激活。
基础例子
你可以向模块注册一个指令,像这样:
<!--lang:js--> myapp=angular.module("myapp",[]); myapp.directive('div',function(){ vardirective={}; directive.restrict='E';/*restrictthisdirectivetoelements*/ directive.template="Myfirstdirective:{{textToInsert}}"; returndirective; });
来看模块中调用的directive()函数。当你调用该函数时,意味着你要注册一个新指令。directive()函数的第一个参数是新注册指令的名称。这是之后你在HTML模板中调用它的时候用的名称。例子中,我用了'div',意思是说当HTML模板每次在找到HTML元素类型是div的时候,这个指令都会被激活。
传递给directive函数的第二个参数是一个工厂函数。调用该函数会返回一个指令的定义。AngularJS通过调用该函数来获取包含指令定义的JavaScript对象。如果你有仔细看上面的例子,你会知道它返回的确是是一个Javascript对象。
这个从工厂函数中返回的Javascript对象有两个属性:restrict和template字段。
restrict字段用来设置指令何时被激活,是匹配到HTML元素时,还是匹配到元素属性时。也就是指令类型。把restrict设置为E时,只有名为div的HTML元素才会激活该指令。把restrict设置为A时,只有名为div的HTML元素属性才会激活该指令。你也可以用AE,这样会使得匹配到元素名和元素属性名时,都可以激活该指令。
template字段是一个HTML模板,用来替换匹配的div元素。它会把匹配到的div当成一个占位符,然后用HTML模板在同一位置来替换掉它。也就是说,HTML模板替换匹配的到HTML元素。
比如说你的HTML页面有这样一段HTML:
<!--lang:js--> <divng-controller="MyController"> <div>Thisdivwillbereplaced</div> </div>
当AngularJS找到内嵌的div的时候,会激活自定义的指令。然后把该div元素替换掉,替换后的HTML将变成这样:
<!--lang:js--> Myfirstdirective:{{textToInsert}}
然后你看到了,这段HTML里面包含了一个内插指令({{textToInsert}})。AngularJS会再执行一次,让内插指令实际值显示出来。然后$scope.textToInsert属性将会在这个HTML点上替换掉内插指令占位符。
template和templateUrl属性
创建自定义指令最简单的例子就是上面这样了。你的指令用来生成HTML,然后你把HTML放到指令定义对象的template属性中。下面这个例子是上面那最简单的例子的重复,注意template部分:
<!--lang:js--> myapp=angular.module("myapp",[]); myapp.directive('div',function(){ vardirective={}; directive.restrict='E';/*restrictthisdirectivetoelements*/ directive.template="Myfirstdirective:{{textToInsert}}"; returndirective; });
如果HTML模板越来越大,把它写在一个Javascript字符串中肯定非常难维护。你可以把它放到一个单独的文件中,然后AngularJS可以通过这个文件把它加载进来。然后再把HTML模板文件的URL放到指令定义对象的templateUrl属性中。下面是例子:
<!--lang:js--> myapp=angular.module("myapp",[]); myapp.directive('div',function(){ vardirective={}; directive.restrict='E';/*restrictthisdirectivetoelements*/ directive.templateUrl="/myapp/html-templates/div-template.html"; returndirective; });
然后AngularJS会从templateUrl属性中设置的URL将HTML模板加载进来。
用独立的HTML模板文件,设置templateUrl属性,在你要创建更多的通用指令时会显得更加有用,比如说用来显示用户信息的指令。例子:
<!--lang:js--> myapp=angular.module("myapp",[]); myapp.directive('userinfo',function(){ vardirective={}; directive.restrict='E';/*restrictthisdirectivetoelements*/ directive.templateUrl="/myapp/html-templates/userinfo-template.html"; returndirective; });
例子创建了一个指令,当AngularJS找到一个<userinfo>元素的时候就会激活它。AngularJS加载指向/myapp/html-templates/userinfo-template.html的模板并解析它,它就像从一开始就被嵌在上一级HTML文件中一样。
从指令中隔离$scope
在上面的例子中,把userinfo指令硬绑定到了$scope,因为HTML模板是直接引用textToInsert属性的。直接引用$scope让指令在同一个controller中的时候,非常难复用,因为$scope在同一个controller中的值都是一样的。比如说,你在页面的HTML中这样写的时候:
<!--lang:js--> <userinfo></userinfo> <userinfo></userinfo>
这两个<userinfo>元素会被同样的HTML模板取代,然后绑定到同样的$scope变量上。结果就是两个<userinfo>元素将会被一模一样的HTML代码给替换了。
为了把两个<userinfo>元素绑定到$scope中的不同的值,你需要给HTML模板一个isolatescope。
所谓isolatescope是绑定在指令上的独立的scope对象。下面是定义的例子:
<!--lang:js--> myapp.directive('userinfo',function(){ vardirective={}; directive.restrict='E'; directive.template="User:{{user.firstName}}{{user.lastName}}"; directive.scope={ user:"=user" } returndirective; })
请看HTMl模板中的两个内插指令{{user.firstName}}和{{user.lastName}}。注意user.部分。以及directive.scope属性。directive.scope是个带user属性的Javascript对象。于是directive.scope就成为了isolatescope对象,现在HTML模板被绑到了directive.scope.user上(通过{{user.firstName}}和{{user.lastName}}内插指令)。
directive.scope.user被设置为“=user”。意思是说directive.scope.user和scope中的属性绑在一起的(不是isolatescope),scope中的属性通过user属性传入<userinfo>元素。听起来挺费劲的,直接看例子:
<!--lang:js--> <userinfouser="jakob"></userinfo> <userinfouser="john"></userinfo>
这两个<userinfo>元素都有user属性。user的值指向了$scope中同名属性,被指定的$scope中的属性将在userinfo的isolatescopeobject中被使用。
下面是完整的例子:
<!--lang:js--> <userinfouser="jakob"></userinfo> <userinfouser="john"></userinfo> <script> myapp.directive('userinfo',function(){ vardirective={}; directive.restrict='E'; directive.template="User:<b>{{user.firstName}}</b><b>{{user.lastName}}</b>"; directive.scope={ user:"=user" } returndirective; }); myapp.controller("MyController",function($scope,$http){ $scope.jakob={}; $scope.jakob.firstName="Jakob"; $scope.jakob.lastName="Jenkov"; $scope.john={}; $scope.john.firstName="John"; $scope.john.lastName="Doe"; }); </script>
compile()和link()函数
如果你需要在你的指令中做更高级的操作,单纯使用HTML模板做不到的时候,你可以考虑使用compile()和link()函数。
compile()和link()函数定义了指令如何修改匹配到的HTML。
当指令第一次被AngularJS编译的时候(在HTML中第一次被发现),compile()函数被调用。compile()函数将为该元素做一次性配置。
然后compile()函数以返回link()作为终结。link()函数将在每次元素被绑到$scope数据时,都被调用。
下面是例子:
<!--lang:js--> <script> myapp=angular.module("myapp",[]); myapp.directive('userinfo',function(){ vardirective={}; directive.restrict='E';/*restrictthisdirectivetoelements*/ directive.compile=function(element,attributes){ //doone-timeconfigurationofelement. varlinkFunction=function($scope,element,atttributes){ //bindelementtodatain$scope } returnlinkFunction; } returndirective; }); </script>
compile()函数带两个参数:element和attributes。
element参数是jqLite包装过的DOM元素。AngularJS有一个简化版jQuery可以用于操作DOM,所以对element的DOM操作方式和你在jQuery中所知的一样。
attributes参数是包含了DOM元素中的所有的属性集合的Javascript对象。因此,你要访问某个属性你可以这样写attributes.type。
link()函数带三个参数:$scope,element和attributes。element和attributes参数和传到compile()函数中的一样。$scope参数是正常的scope对象,或者当你在指令定义对象中定义了isolatescope的时候,它就是isolatescope。
compile()和link()的命名相当混乱。它们肯定是受编译队伍的启发而命名的。我可以看到相似点,不过一个编译器是传入一次,然后输出。而指令则是配置一次HTML元素,然后在之后的$scope对象改变时进行更新。
compile()函数如果叫做create(),init()或者configure()也许会更好。这样可以表达出这个函数只会被调用一次的意思。
而link()函数如果叫bind()或者render()也许会更好,能更好的表达出这样的意思,在指令绑定数据或者重绑定数据的时候,这个函数将会被调用。
下面是一个完整的例子,演示了指令使用compile()和link()函数的:
<!--lang:js--> <divng-controller="MyController"> <userinfo>Thiswillbereplaced</userinfo> </div> <script> myapp=angular.module("myapp",[]); myapp.directive('userinfo',function(){ vardirective={}; directive.restrict='E';/*restrictthisdirectivetoelements*/ directive.compile=function(element,attributes){ element.css("border","1pxsolid#cccccc"); varlinkFunction=function($scope,element,attributes){ element.html("Thisisthenewcontent:"+$scope.firstName); element.css("background-color","#ffff00"); } returnlinkFunction; } returndirective; }) myapp.controller("MyController",function($scope,$http){ $scope.cssClass="notificationDiv"; $scope.firstName="Jakob"; $scope.doClick=function(){ console.log("doClick()called"); } }); </script>
compile()函数设置HTML元素的border。它只执行一次,因为compile()函数只执行一次。
link()替换HTML元素内容,并且把背景颜色设置为黄色。
把设置border放在compile(),把背景颜色放在link()没啥特别的理由。你可以把所有的操作都丢到compile(),或者都放到link()。如果都放到compile()它们只会被设置一次(你需要它们是常量的话)。如果放到了link(),它们会在每次HTML元素被绑到$scope的时候都发生变化。当你希望根据$scope中的数据来设置boarder和背景颜色的时候这非常有用。
只设置link()函数
有时候你的指令可能不需要compile()。你只需要用到link()。这种情况下,你可以直接设置指令定义对象中的link()函数。下面是一个对上面例子的修改,只用link函数:
<!--lang:js--> <divng-controller="MyController"> <userinfo>Thiswillbereplaced</userinfo> </div> <script> myapp=angular.module("myapp",[]); myapp.directive('userinfo',function(){ vardirective={}; directive.restrict='E';/*restrictthisdirectivetoelements*/ directive.link=function($scope,element,attributes){ element.html("Thisisthenewcontent:"+$scope.firstName); element.css("background-color","#ffff00"); } returndirective; }) myapp.controller("MyController",function($scope,$http){ $scope.cssClass="notificationDiv"; $scope.firstName="Jakob"; $scope.doClick=function(){ console.log("doClick()called"); } }); </script>
注意link()方法和之前例子中返回的link()是完全一样的。
通过Transclusion封装元素的指令
到目前为止,我们看到的所有例子,都是把匹配到的元素内容给替换为指令的指定内容,不管是通过Javascript也好或者HTML模板也好。不过如果遇到内容中有部分是开发者可以指定的内容的时候呢?比如说:
<!--lang:js--> <mytransclude>Thisisatranscludeddirective{{firstName}}</mytransclude>
标记为<mytransclude>的元素,它的部分内容可以由开发者设置。因此,这部分HTML不应该被指令的HTML模板给替换。我们实际上是希望这部分HTML由AngularJS来处理的。这个处理叫做“transclusion”。1
为了能让AngularJS把这部分HTML放到指令内部处理,你必须设置指令定义对象的transclude属性为true。你还需要告诉AngularJS,指令的HTML模板中哪一部分需要把transcludedHTML包含进来。你可以通过插入ng-transclude属性(实际上,是一个指令)到HTML模板的HTML元素中,标记你想要添加transcludedHTML的元素。
下面是一个AngularJS指令,演示如何使用transclusion:
<!--lang:js--> <mytransclude>Thisisatranscludeddirective{{firstName}}</mytransclude> <script> myapp=angular.module("myapp",[]); myapp.directive('mytransclude',function(){ vardirective={}; directive.restrict='E';/*restrictthisdirectivetoelements*/ directive.transclude=true; directive.template="<divclass='myTransclude'ng-transclude></div>"; returndirective; }); myapp.controller("MyController",function($scope,$http){ $scope.firstName="Jakob"; }); </script>
注意<mytransclude>元素内的HTML。这部分HTML代码包含了内插指令{{firstName}}。我们希望AngularJS来为我们处理这部分HTML,让内插指令执行。为了实现这个目的,我在指令定义对象中把transclude设置为true。我还在HTML模板中用到了ng-transclude属性。这个属性告诉AngularJS什么元素需要插入到transcludedHTML。
1:说实话,我没看懂那个定义,说的太TM难懂了,而且我好不爽写代码没输出的教程。只好自己动手做做例子。我觉得其实应该是这样的,把目标元素内容作为一个整体,拿到HTML模板中来,添加到ng-transclude指定的标签下。这个处理,我觉得应该就是叫做transcluded。比如说刚才的例子(有些bug,自己修正一下),因为directive.transclude=true;,所以原来<mytransclude>元素内的HTML:
<!--lang:js--> Thisisatranscludeddirective{{firstName}}
在激活指令'mytransclude'的时候,会被拿到'mytransclude'指令的模板中来,放到被ng-transclude指定的
<!--lang:js--> "<divclass='myTransclude'ng-transclude></div>"
中。于是最终输出的结果应该是:
<!--lang:js--> <mytransclude> <divclass='myTransclude'ng-transclude> <spanclass="ng-scopeng-binding">ThisisatranscludeddirectiveJakob</span> </div> </mytransclude>
更多关于AngularJS相关内容感兴趣的读者可查看本站专题:《AngularJS入门与进阶教程》及《AngularJSMVC架构总结》
希望本文所述对大家AngularJS程序设计有所帮助。