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 对象有两个属性:restricttemplate字段。

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部分:

var directive = {};

    directive.restrict = 'E'; /* restrict this directive to elements */
    directive. 如果 HTML 模板越来越大,把它写在一个 Javascript 字符串中肯定非常难维护。你可以把它放到一个单独的文件中,然后 AngularJS 可以通过这个文件把它加载进来。然后再把 HTML 模板文件的 URL 放到指令定义对象的templateUrl属性中。下面是例子:

'div',function() {
    var directive = {};

    directive.restrict = 'E'; /* restrict this directive to elements */
    directive.templateUrl = "/myapp/html-templates/div-template.html";

     然后 AngularJS 会从templateUrl属性中设置的 URL 将 HTML 模板加载进来。

用独立的 HTML 模板文件,设置templateUrl属性,在你要创建更多的通用指令时会显得更加有用,比如说用来显示用户信息的指令。例子:

'userinfo',152);">"/myapp/html-templates/userinfo-template.html";

     例子创建了一个指令,当AngularJS 找到一个<userinfo>元素的时候就会激活它。AngularJS 加载指向/myapp/html-templates/userinfo-template.html的模板并解析它,它就像从一开始就被嵌在上一级 HTML 文件中一样。

从指令中隔离 $scope

在上面的例子中,把userinfo指令硬绑定到了$scope,因为 HTML 模板是直接引用textToInsert属性的。直接引用$scope让指令在同一个controller中的时候,非常难复用,因为$scope在同一个controller中的值都是一样的。比如说,你在页面的 HTML 中这样写的时候:

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.userscope中的属性绑在一起的(不是 isolate scope),scope中的属性通过user属性传入<userinfo>元素。听起来挺费劲的,直接看例子:

userinfo user="jakob">"john"> 这两个<userinfo>元素都有user属性user的值指向了$scope中同名属性,被指定的$scope中的属性将在userinfoisolate scope object中被使用。

下面是完整的例子:

<!-- lang: js --> userinfo> script> myapp.directive(function() { var directive = {}; directive.restrict = 'E'; directive."User : <b>{{user.firstName}}b> b>{{user.lastName}}</b>"; directive.scope = { user : "=user" }  return directive; }); myapp.controller("MyController",0);">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数据时,都被调用

下面是例子:

script> myapp = angular.module(function() { var directive = {}; directive.restrict = /* restrict this directive to elements */ directive.compile = function(element,attributes) { // do one-time configuration of element. var linkFunction = // bind element to data in $scope } return linkFunction; } return directive; }); script>

compile()函数带两个参数:elementattributes

element参数是 jqLite 包装过的 DOM 元素。AngularJS 有一个简化版 jQuery 可以用于操作 DOM,所以对element的 DOM 操作方式和你在 jQuery 中所知的一样。

attributes参数是包含了 DOM 元素中的所有的属性集合的 Javascript 对象。因此,你要访问某个属性你可以这样写attributes.type

link()函数带三个参数:$scopeelementattributeselementattributes参数和传到compile()函数中的一样。$scope参数是正常的 scope 对象,或者当你在指令定义对象中定义了 isolate scope 的时候,它就是 isolate scope。

compile()link()的命名相当混乱。它们肯定是受编译队伍的启发而命名的。我可以看到相似点,不过一个编译器是传入一次,然后输出。而指令则是配置一次 HTML 元素,然后在之后的$scope对象改变时进行更新。

compile()函数如果叫做create(),init()或者configure()也许会更好。这样可以表达出这个函数只会被调用一次的意思。

link()函数如果叫bind()或者render()也许会更好,能更好的表达出这样的意思,在指令绑定数据或者重绑定数据的时候,这个函数将会被调用

下面是一个完整的例子,演示了指令使用compile()link()函数的:

userinfo >This will be replaceddiv>

"border","1px solid #cccccc"); "This is the new content: " + $scope.firstName); element.css("background-color",152);">"#ffff00"); } return directive; }) myapp.controller($http) { $scope.cssClass = "notificationDiv"; $scope.firstName = $scope.doClick = function() { console.log("doClick() called"); } });  compile()函数设置 HTML 元素的 border 。它只执行一次,因为compile()函数只执行一次。

link()替换 HTML 元素内容,并且把背景颜色设置为黄色。

把设置 border 放在compile(), 把背景颜色放在link()没啥特别的理由。你可以把所有的操作都丢到compile(),或者都放到link()。如果都放到compile()它们只会被设置一次(你需要它们是常量的话)。如果放到了link(),它们会在每次 HTML 元素被绑到$scope的时候都发生变化。当你希望根据$scope中的数据来设置 boarder 和背景颜色的时候这非常有用。

只设置 link() 函数

有时候你的指令可能不需要compile()。你只需要用到link()。这种情况下,你可以直接设置指令定义对象中的link()函数。下面是一个对上面例子的修改,只用 link 函数:

/* restrict this directive to elements */ directive.link =  注意link()方法和之前例子中返回的link()是完全一样的。

通过 Transclusion 封装元素的指令

到目前为止,我们看到的所有例子,都是把匹配到的元素内容给替换为指令的指定内容,不管是通过 Javascript 也好或者 HTML 模板也好。不过如果遇到内容中有部分是开发者可以指定的内容的时候呢?比如说:

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:

firstName}}mytransclude> script> myapp = angular.module('mytransclude',0);">directive = {}; directive.restrict = 'E'; /* restrict this directive to elements */ directive.transclude = true; directive."<div class='myTransclude' ng-transclude></div>"; { $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 -->
"class='myTransclude' ng-transclude>div>"

中。于是最终输出的结果应该是:

mytransclude>
    ng-transclude>
        span "ng-scope ng-binding">This is a transcluded directive Jakobspan>
    mytransclude>

猜你在找的Angularjs相关文章