AngularJS 自定义控件

AngularJS Custom Directivescss

好讨厌不带日期的博客,并且说得好啰嗦html


#自定义指令介绍angularjs

AngularJS 指令做用是在 AngulaJS 应用中操做 Html 渲染。好比说,内插指令 ( {{ }} ), ng-repeat 指令以及 ng-if 指令。app

固然你也能够实现本身的。这就是 AngularJS 所谓的"教会 HTML 玩新姿式"。本文将告诉你如何作到。函数

#指令类型this

能够自定义的指令类型以下:spa

  • 元素
  • 属性
  • CSS class
  • Comment directives

这里面,AngularJS 强烈建议你用元素和属性类型,而不用 CSS class 和 comment directives (除非无可奈何)。翻译

指令类型决定了指令什么时候被激活。当 AngularJS 在 HTML 模板中找到一个 HTML 元素的时候,元素指令被激活。当 AngularJS 找到一个 HTML 元素属性时,属性指令被激活。当 AngularJS 找到一个 CSS class 时, CSS class 指令被激活,最后,当 AngularJS 找到 HTML comment 时,comment directive 被激活。rest

#基础例子code

你能够向模块注册一个指令,像这样:

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

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

<!-- lang: js -->
<userinfo user="jakob"></userinfo>
<userinfo user="john"></userinfo>

这两个 <userinfo> 元素都有 user 属性。user 的值指向了 $scope 中同名属性,被指定的 $scope 中的属性将在 userinfoisolate 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() 函数带两个参数: 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() 函数的:

<!-- 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>

而后我又回去把翻译改了。如今感受念起来顺口一点了。

相关文章
相关标签/搜索