创建自定义 AngularJS 指令:Part I 指令基础知识

AngularJS提供了许多指令,用于操作DOM、处理事件回调函数的路由事件,数据绑定,关联控制器或作用域到视图上,等等许多功能。例如ng-clickng-show/ng-hideng-repeat ,以及许多其它能在 AngularJS 核心代码里找到的指令能让我们非常轻松地开始使用这一框架。

虽然内置的指令涵盖许多不同的情况,但有时候,这些情况并不能满足我们的需求,你发现自己需要自定义指令实现这些功能,但是不知道如何开始写自定义指令。在这一系列的帖子中,我将一步一步地向你展示指令的原理和怎样使用指令。总体目标是慢慢前进并提供简单、易消化的指令示例,且随着本系列的进展不断深入。

我假设你已经知道什么是指令,它们是怎样被使用的。如果你完全不知道指令是什么,看看我制作的60 分钟 AngularJS 视频教程 (或者获取 eBook 版本) 或我新写的 AngularJS JumpStart 课程。


编写自定义指令很容易吧?


在你第一次看到 AngularJS 指令的时候,它们可能会吓着你。他们提供了许多不同的选项,有一些神秘的功能(这里的神秘主要指“它们到底在想什么?”),一开始使用它确实具有挑战性。但是,一旦你理解了部分指令功能,并知道他们是如何结合在一起,你会发现,并没有那么困难。我把它比作学习演奏乐器,当你第一次演奏钢琴或吉,你的表现不会好,而且感觉完全无法胜任。然而,投入必要的练习时间后,最终你能相当不错地处理更复杂的音乐。对于指令的学习,同样如此 - 你需要花一些时间去练习使用它,而一旦你掌握了它的诀窍,你就可以做很多功能强大的东西。我期待着某一天 Polymer 项目和 Web Components spec 中的特性能取代我们今天编写的指令。如果你想一窥指令的未来发展方向,就看看这些链接。


开始自定义指令


你为什么要自定义指令呢?比如你要将一些客户数据集合以表格的形式展示在页面视图中。你可以通过添加DOM的方式实现,但这样做会很难测试,也打破了分离原则——你不应该在 AngularJS 里这样做。但是,可以通过自定义指令实现这一功能。又例如,你有一些数据绑定,用于几个视图中,而你想重用这些数据绑定表达式。使用 ng-include 加载的一个子视图就可以实现,指令同样能很好的工作在这样的情况下。还有其它许多情况可以使用自定义指令,这些例子还只是冰山一角而已。

让我们从一个非常基本的 AngularJS 指令的例子开始。假设我们在应用里定义了如下模块和控制器(我也会在后续的文章里提到这些模块和控制器):

var app = angular.module('directivesModule', []);

app.controller('CustomersController', ['$scope', function ($scope) {
    var counter = 0;
    $scope.customer = {
        name: 'David',
        street: '1234 Anywhere St.'
    };
    
    $scope.customers = [
        {
            name: 'David',
            street: '1234 Anywhere St.'
        },
        {
            name: 'Tina',
            street: '1800 Crest St.'
        },
        {
            name: 'Michelle',
            street: '890 Main St.'
        }
    ];

    $scope.addCustomer = function () {
        counter++;
        $scope.customers.push({
            name: 'New Customer' + counter,
            street: counter + ' Cedar Point St.'
        });
    };

    $scope.changeData = function () {
        counter++;
        $scope.customer = {
            name: 'James',
            street: counter + ' Cedar Point St.'
        };
    };
}]);

在应用的视图中,我们多次写下类似下面的数据绑定表达式:

Name: {{customer.name}} 
<br />
Street: {{customer.street}}

可用于促进重复使用的一种技术是将数据绑定模板放到子视图(我称之为myChildView.html),并从父视图中使用NG-include指令引用它。这允许myChildView.html被重复使用在整个应用任何需要它地方。

<div ng-include="'myChildView.html'"></div>

虽然这能搞定事情,但还有另外一个可用的技术是将数据绑定表达式放入一个指令里。要创建一个指令,你首先定位到指令会被放入的目标模块,并调用它的 directive() 函数。这个 directive() 函数参数是指令的名字和一个函数。下面是一个简单的嵌入数据绑定表达式的指令:

angular.module('directivesModule').directive('mySharedScope', function () {
    return {
        template: 'Name: {{customer.name}}<br /> Street: {{customer.street}}'
    };
});

这给我们带来了比使用 ng-include 指令加载子视图更多的东西吗?目前还没有。然而,指令能够通过添加一点点代码来做到更多事情,而且它们能够让元素轻易地附上功能。一个将 mySharedScope 指令附加到一个 <div> 的例子如下所示:

<div my-shared-scope></div>

当指令运行时,它会基于之前展示的控制器里的数据输出下面的内容:


Name: David 
Street: 1234 Anywhere St.


你首先注意到的是:mySharedScope指令在视图中以my-shared-scope形式引用。为什么会这样呢?这是因为指令必须以驼峰法命名。例如:当你使用ngRepeat指令时,你使用的是连字符版的ng-repeat

另一个有趣的事情是:这个指令默认从视图继承了“scope”(作用域)。如果前边展示的控制器被绑定到这个视图上,那么这个控制器中的$scopecustomer属性将会关联这个指令中。这被称作shared scope(共享作用域),并且非常适合于需要知道父作用域中信息的情况。但在你需要创建一个可复用的指令时,你不能依赖于知道父作用域中的属性,而是要使用一种称作isolate scope(独立作用域)的东西。我将在下节中详细讲解独立作用相关知识。


指令属性


在前边的mySharedScope指令中,函数的返回对象中,只有一个template属性。这个属性的作用是定义模板代码(在这里是一个数据绑定表达式),用于生成HTML。还有什么其它可用的属性呢?

Custom directives will typically return an object literal that is responsible for defining properties needed by the directive such as templates, a controller (if one is used), DOM manipulation code, and more. Several different properties can be used (you’ll find a complete list of them here). Here’s a look at a few of the key properties that you may come across and an example of using them:

自定义指令通常会返回一个对象,它负责定义指令所需的属性,例如:模板、控制器(如果使用了)、操作DOM的代码等等。有不同的几种属性可以使用(你可以在此找到它们的完整列表)。下面看看一些你可能遇到的关键属性,以及一个使用它们的实例:

angular.module('moduleName')
    .directive('myDirective', function () {
    return {
        restrict: 'EA', //E = element, A = attribute, C = class, M = comment         
        scope: {
            //@ reads the attribute value, = provides two-way binding, & works with functions
            title: '@'         },
        template: '<div>{{ myVal }}</div>',
        templateUrl: 'mytemplate.html',
        controller: controllerFunction, //Embed a custom controller in the directive
        link: function ($scope, element, attrs) { } //DOM manipulation
    }
});

下边是对这些属性的一个简单解释:

属性描述
restrict决定一个指令可如何被使用(例如:被用作元素、属性、CSS class 或 注释)
scope用于创建一个子 scope 或孤立的 scope。
template定义指令的输出内容。可以包含 HTML 、数据绑定表达式,甚至是其它指令。
templateUrl提供指令所用模版的路径。如果模版被定义在 <script> 内,那它可以包含一个 DOM 元素的 id 。
controller用于定义和指令模版关联的控制器。
link用于 DOM 操作任务的函数

操作DOM


除了在模版上执行数据绑定操作(我会在将来的文章里讲到更多相关的情况)外,指令同样可用于操作 DOM 。即通过之前提到的 link 函数来做。

link 函数通常接受 3 个参数(虽然某些情况下还可以传递其它参数),包括 scope 、与指令关联的元素,以及目标元素的属性(attribute)。下面是指令如何处理某个元素上 click、mouseenter,以及 mouseleave 事件的例子:

app.directive('myDomDirective', function () {
    return {
        link: function ($scope, element, attrs) {
            element.bind('click', function () {
                element.html('You clicked me!');
            });
            element.bind('mouseenter', function () {
                element.css('background-color', 'yellow');
            });
            element.bind('mouseleave', function () {
                element.css('background-color', 'white');
            });
        }
    };
});

要使用此指令,只需将如下代码添加到视图:

<div my-dom-directive>Click Me!</div>

当鼠标进入或离开这个<div>元素区域时,它的背景颜色将会在黄色和白色之间切换(这里使用了简单的嵌入样式,你也可以使用样式类)。当点击这个<div>元素时,元素内容变成了“You clicked me!”。当然了,还有很多种操作DOM的用法,但这个示例可以使你更容易上手。


构造AngularJS指令代码



虽然mySharedScopemyDomDirective指令能很好的工作,但我喜欢定义指令和其他AngularJS组件时遵循特定的模式。下面是一个例子:

(function () {

    var directive = function () {
        return {

        };
    };

    angular.module('moduleName')
        .directive('directiveName', directive);

}());

This code wraps everything with an immediately invoked function to pull everything out of the global scope. It then defines the directive functionality in a function assigned to the directive variable. Finally, a call is made to the module’s directive() function and the directive variable is passed in. There are several other techniques that can be used to structure code (see my previous post on the subject) but this is the general pattern I like to follow.

这 段代码将所有内容封装在一个立即执行函数中,以将所有内容拉到全局作用域之外。然后它在函数中定义了一个指令功能函数,并将其赋给directive变量。最后,调用模块的directive()函数,并传递directive变量给它。还有其它几种构造代码的技术(看看我关于本主题的旧文章),但这是我喜欢的模式。


总结


在这关于AngularJS的第一篇文章中,你学会了一些基础知识,并知道怎样创建两个简单指令。我们还只是略懂皮毛罢了。在下篇文章中我将介绍孤立作用域和其它被称作局部Scope的属性,这些属性可以被用于数据绑定和其它作用。



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
编译原理是计算机专业的一门核心课程,旨在介绍编译程序构造的一般原理和基本方法。编译原理不仅是计算机科学理论的重要组成部分,也是实现高效、可靠的计算机程序设计的关键。本文将对编译原理的基本概念、发展历程、主要内容和实际应用进行详细介绍编译原理是计算机专业的一门核心课程,旨在介绍编译程序构造的一般原理和基本方法。编译原理不仅是计算机科学理论的重要组成部分,也是实现高效、可靠的计算机程序设计的关键。本文将对编译原理的基本概念、发展历程、主要内容和实际应用进行详细介绍编译原理是计算机专业的一门核心课程,旨在介绍编译程序构造的一般原理和基本方法。编译原理不仅是计算机科学理论的重要组成部分,也是实现高效、可靠的计算机程序设计的关键。本文将对编译原理的基本概念、发展历程、主要内容和实际应用进行详细介绍编译原理是计算机专业的一门核心课程,旨在介绍编译程序构造的一般原理和基本方法。编译原理不仅是计算机科学理论的重要组成部分,也是实现高效、可靠的计算机程序设计的关键。本文将对编译原理的基本概念、发展历程、主要内容和实际应用进行详细介绍编译原理是计算机专业的一门核心课程,旨在介绍编译程序构造的一般原理和基本

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值