AngularJS自定义控件实例详解
本文实例讲述了angularjs自定义控件。分享给大家供大家参考,具体如下:
自定义指令介绍
angularjs 指令作用是在 angulajs 应用中操作 html 渲染。比如说,内插指令 ( {{ }} ), ng-repeat 指令以及 ng-if 指令。
当然你也可以实现自己的。这就是 angularjs 所谓的"教会 html 玩新姿势”。本文将告诉你如何做到。
指令类型
可以自定义的指令类型如下:
元素
属性
css class
comment directives
这里面,angularjs 强烈建议你用元素和属性类型,而不用 css class 和 comment directives (除非迫不得已)。
指令类型决定了指令何时被激活。当 angularjs 在 html 模板中找到一个 html 元素的时候,元素指令被激活。当 angularjs 找到一个 html 元素属性时,属性指令被激活。当 angularjs 找到一个 css class 时, css class 指令被激活,最后,当 angularjs 找到 html comment 时,comment directive 被激活。
基础例子
你可以向模块注册一个指令,像这样:
<!-- lang: js --> myapp = angular.module("myapp", []); myapp.directive('div', function() { var directive = {}; directive.restrict = 'e'; /* restrict this directive to elements */ directive.template = "my first directive: {{texttoinsert}}"; return directive; });
来看模块中调用的 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 --> <div ng-controller="mycontroller" > <div>this div will be replaced</div> </div>
当 angularjs 找到内嵌的 div 的时候,会激活自定义的指令。然后把该 div 元素替换掉,替换后的 html 将变成这样:
<!-- lang: js --> my first directive: {{texttoinsert}}
然后你看到了,这段 html 里面包含了一个内插指令 ({{texttoinsert}})。angularjs 会再执行一次,让内插指令实际值显示出来。然后 $scope.texttoinsert 属性将会在这个 html 点上替换掉内插指令占位符。
template 和 templateurl 属性
创建自定义指令最简单的例子就是上面这样了。你的指令用来生成 html,然后你把 html 放到指令定义对象的 template 属性中。下面这个例子是上面那最简单的例子的重复,注意 template部分:
<!-- lang: js --> myapp = angular.module("myapp", []); myapp.directive('div', function() { var directive = {}; directive.restrict = 'e'; /* restrict this directive to elements */ directive.template = "my first directive: {{texttoinsert}}"; return directive; });
如果 html 模板越来越大,把它写在一个 javascript 字符串中肯定非常难维护。你可以把它放到一个单独的文件中,然后 angularjs 可以通过这个文件把它加载进来。然后再把 html 模板文件的 url 放到指令定义对象的 templateurl 属性中。下面是例子:
<!-- lang: js --> myapp = angular.module("myapp", []); myapp.directive('div', function() { var directive = {}; directive.restrict = 'e'; /* restrict this directive to elements */ directive.templateurl = "/myapp/html-templates/div-template.html"; return directive; });
然后 angularjs 会从 templateurl 属性中设置的 url 将 html 模板加载进来。
用独立的 html 模板文件,设置 templateurl 属性,在你要创建更多的通用指令时会显得更加有用,比如说用来显示用户信息的指令。例子:
<!-- lang: js --> myapp = angular.module("myapp", []); myapp.directive('userinfo', function() { var directive = {}; directive.restrict = 'e'; /* restrict this directive to elements */ directive.templateurl = "/myapp/html-templates/userinfo-template.html"; return directive; });
例子创建了一个指令,当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 模板一个 isolate scope。
所谓 isolate scope 是绑定在指令上的独立的 scope 对象。下面是定义的例子:
<!-- lang: js --> myapp.directive('userinfo', function() { var directive = {}; directive.restrict = 'e'; directive.template = "user : {{user.firstname}} {{user.lastname}}"; directive.scope = { user : "=user" } return directive; })
请看 html 模板中的两个内插指令 {{user.firstname}} 和 {{user.lastname}}。注意 user. 部分。以及directive.scope 属性。 directive.scope 是个带 user 属性的 javascript 对象。于是directive.scope 就成为了 isolate scope 对象,现在 html 模板被绑到了 directive.scope.user 上(通过 {{user.firstname}} 和 {{user.lastname}} 内插指令)。
directive.scope.user 被设置为 “=user” 。意思是说 directive.scope.user 和 scope 中的属性绑在一起的(不是 isolate scope),scope 中的属性通过 user 属性传入 <userinfo> 元素。听起来挺费劲的,直接看例子:
<!-- lang: js --> <userinfo user="jakob"></userinfo> <userinfo user="john"></userinfo>
这两个 <userinfo> 元素都有 user 属性。user 的值指向了 $scope 中同名属性,被指定的 $scope中的属性将在 userinfo 的 isolate scope object 中被使用。
下面是完整的例子:
<!-- lang: js --> <userinfo user="jakob"></userinfo> <userinfo user="john"></userinfo> <script> myapp.directive('userinfo', function() { var directive = {}; directive.restrict = 'e'; directive.template = "user : <b>{{user.firstname}}</b> <b>{{user.lastname}}</b>"; directive.scope = { user : "=user" } return directive; }); 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() { var directive = {}; directive.restrict = 'e'; /* restrict this directive to elements */ directive.compile = function(element, attributes) { // do one-time configuration of element. var linkfunction = function($scope, element, atttributes) { // bind element to data in $scope } return linkfunction; } return directive; }); </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 对象,或者当你在指令定义对象中定义了 isolate scope 的时候,它就是 isolate scope。
compile() 和 link() 的命名相当混乱。它们肯定是受编译队伍的启发而命名的。我可以看到相似点,不过一个编译器是传入一次,然后输出。而指令则是配置一次 html 元素,然后在之后的 $scope 对象改变时进行更新。
compile() 函数如果叫做 create(), init() 或者 configure()也许会更好。这样可以表达出这个函数只会被调用一次的意思。
而 link() 函数如果叫 bind() 或者 render() 也许会更好,能更好的表达出这样的意思,在指令绑定数据或者重绑定数据的时候,这个函数将会被调用。
下面是一个完整的例子,演示了指令使用 compile() 和 link() 函数的:
<!-- lang: js --> <div ng-controller="mycontroller" > <userinfo >this will be replaced</userinfo> </div> <script> myapp = angular.module("myapp", []); myapp.directive('userinfo', function() { var directive = {}; directive.restrict = 'e'; /* restrict this directive to elements */ directive.compile = function(element, attributes) { element.css("border", "1px solid #cccccc"); var linkfunction = function($scope, element, attributes) { element.html("this is the new content: " + $scope.firstname); element.css("background-color", "#ffff00"); } return linkfunction; } return directive; }) 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 --> <div ng-controller="mycontroller" > <userinfo >this will be replaced</userinfo> </div> <script> myapp = angular.module("myapp", []); myapp.directive('userinfo', function() { var directive = {}; directive.restrict = 'e'; /* restrict this directive to elements */ directive.link = function($scope, element, attributes) { element.html("this is the new content: " + $scope.firstname); element.css("background-color", "#ffff00"); } return directive; }) 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>this is a transcluded directive {{firstname}}</mytransclude>
标记为 <mytransclude> 的元素,它的部分内容可以由开发者设置。因此,这部分 html 不应该被指令的 html 模板给替换。我们实际上是希望这部分 html 由 angularjs 来处理的。这个处理叫做 “transclusion”。 1
为了能让 angularjs 把这部分 html 放到指令内部处理,你必须设置指令定义对象的 transclude 属性为 true。你还需要告诉 angularjs,指令的 html 模板中哪一部分需要把 transcluded html 包含进来。你可以通过插入 ng-transclude 属性 (实际上,是一个指令) 到 html 模板的 html 元素中,标记你想要添加 transcluded html 的元素。
下面是一个 angularjs 指令,演示如何使用 transclusion:
<!-- lang: js --> <mytransclude>this is a transcluded directive {{firstname}}</mytransclude> <script> myapp = angular.module("myapp", []); myapp.directive('mytransclude', function() { var directive = {}; directive.restrict = 'e'; /* restrict this directive to elements */ directive.transclude = true; directive.template = "<div class='mytransclude' ng-transclude></div>"; return directive; }); myapp.controller("mycontroller", function($scope, $http) { $scope.firstname = "jakob"; }); </script>
注意 <mytransclude> 元素内的 html。这部分 html 代码包含了内插指令 {{firstname}}。我们希望 angularjs 来为我们处理这部分 html,让内插指令执行。为了实现这个目的,我在指令定义对象中把 transclude 设置为 true。我还在 html 模板中用到了 ng-transclude 属性。这个属性告诉 angularjs 什么元素需要插入到 transcluded html。
1: 说实话,我没看懂那个定义,说的太tm难懂了,而且我好不爽写代码没输出的教程。只好自己动手做做例子。我觉得其实应该是这样的,把目标元素内容作为一个整体,拿到 html 模板中来,添加到 ng-transclude 指定的标签下。这个处理,我觉得应该就是叫做 transcluded。比如说刚才的例子(有些 bug,自己修正一下),因为 directive.transclude = true; ,所以原来 <mytransclude> 元素内的 html:
<!-- lang: js --> this is a transcluded directive {{firstname}}
在激活指令 'mytransclude' 的时候,会被拿到 'mytransclude' 指令的模板中来,放到被 ng-transclude 指定的
<!-- lang: js --> "<div class='mytransclude' ng-transclude></div>"
中。于是最终输出的结果应该是:
<!-- lang: js --> <mytransclude> <div class='mytransclude' ng-transclude> <span class="ng-scope ng-binding">this is a transcluded directive jakob</span> </div> </mytransclude>
更多关于angularjs相关内容感兴趣的读者可查看本站专题:《angularjs入门与进阶教程》及《angularjs mvc架构总结》
希望本文所述对大家angularjs程序设计有所帮助。