Angular 指令详解

2016/05/04 angulardirective

# 为什么使用AngularJS 指令?

使用过 AngularJS 的朋友应该最感兴趣的是它的指令。现今市场上的前端框架也只有AngularJS 拥有自定义指令的功能,并且AngularJS 是目前唯一提供Web应用可复用能力的框架。

目前有很多 JavaScript 产品提供插件给Web开发人员。例如,Bootstrap 就是当前比较流行的提供样式和 JavaScript 插件的前端开发工具包。但是开发人员在使用 Booostrap 中的插件时, 必须切换到 JavaScript 模式来写 jQuery 代码来激活插件虽然 jQuery 代码写起来十分简单,但是必须和 HTML 进行同步,这是一个单调乏味且容易出错的过程。

AngularJS 主页展示了一个简单的例子,用于实现 Bootstrap 中的 Tab 功能,可以在页面中轻松添加 Tab 功能,并且使用方法和 ul 标签一样简单。HTML 代码如下:

<body ng-app="components">
    <h3>BootStrap Tab Component</h3>
    <tabs>
        <pane title="First Tab">
            <div>This is the content of the first tab.</div>
        </pane>
        <pane title="Second Tab">
            <div>This is the content of the second tab.</div>
        </pane>
    </tabs>
</body>

JavaScript代码如下:

angular.module('components', []).directive('tabs', function() {
    return {
        restrict: 'E',
        transclude: true,
        scope: {},
        controller: ["$scope", function($scope) {
            var panes = $scope.panes = [];

            $scope.select = function(pane) {
                angular.forEach(panes, function(pane) {
                    pane.selected = false;
                });
                pane.selected = true;
            }

            this.addPane = function(pane) {
                if (panes.length == 0) $scope.select(pane);
                panes.push(pane);
            }
        }],
        template:
            '<div class="tabbable">' +
                '<ul class="nav nav-tabs">' +
                    '<li ng-repeat="pane in panes" ng-class="{active:pane.selected}">' +
                        '<a href="" ng-click="select(pane)">{{pane.title}}</a>' +
                    '</li>' +
                '</ul>' +
                '<div class="tab-content" ng-transclude></div>' +
            '</div>',
        replace: true
    };
})
.directive('pane', function() {
    return {
        require: '^tabs',
        restrict: 'E',
        transclude: true,
        scope: {
            title: '@'
        },
        link: function(scope, element, attrs, tabsCtrl) {
            tabsCtrl.addPane(scope);
        },
        template:
            '<div class="tab-pane" ng-class="{active: selected}" ng-transclude>' +
            '</div>',
        replace: true
    };
})

# 创建自定义 AngularJS 指令

文章开头的自定义指令十分的简单。它仅仅实现了同步的功能。一般指令是包含更多元素的:

//创建指令模块 (或者检索现有模块)
var m = angular.module("myApp");

// 创建"my-dir"指令
myApp.directive("myDir", function() {
    return {
        restrict: "E", // 指令是一个元素 (并非属性)
        scope: { // 设置指令对应的scope
            name: "@",      // name 值传递(字符串,单向绑定)
            amount: "=",    // amount 引用传递(双向绑定)
            save: "&"       // 保存操作
        },
        template: // 替换HTML (使用scope中的变量)
            "<div>" +
                "  {{name}}: <input ng-model='amount' />" +
                "  <button ng-click='save()'>Save</button>" +
            "</div>",

        replace: true, // 使用模板替换原始标记
        transclude: false, // 不复制原始HTML内容
        controller: ["$scope", function($scope) {
            // ...
        }],
        link: function(scope, element, attrs, controller) {
            // ...
        }
    }
});

注意这个自定义指令遵循一种格式:以 my 为前缀,类似于命名空间,因此如果你在应用中引用了多个模块指令,你可以通过前缀很容易的判断出它是在哪定义的。这不是硬性要求,但是这样做可以带来很多便利。

指令的构造函数会返回带有属性的 JavaScript 对象。这些内容在 AngularJS 主页中都有清晰说明。以下是我对一些属性的理解:

  1. restrict: 说明指令在 HTML 中的应用形式,备选项有"A"、"E" 和 "C","M",分别代表 attribute、element、class 和 comment(默认值为"A")。

  2. scope: 创建指令的作用范围,scope 在指令中作为属性标签传递。Scope 是创建可以复用指令的必要条件,每个指令(不论是处于嵌套指令的哪一级)都有其唯一的作用域,它不依赖于父 scope。scope 对象定义 names 和 types 变量。上面的例子即创建了3个 scope 变量:

    • name: "@"(值传递,单向绑定):"@"符号表示变量是值传递。指令会检索从父级 scope 中传递而来字符串中的值。指令可以使用该值但无法修改,是最常用的变量。

    • amount: "="(引用,双向绑定):"="符号表示变量是引用传递。指令检索主 Scope 中的引用取值。值可以是任意类型的,包括复合对象和数组。指令可以更改父级 Scope 中的值,所以当指令需要修改父级 Scope 中的值时我们就需要使用这种类型。

    • save: "&"(表达式):"&"符号表示变量是在父级 Scope 中启作用的表达式。它允许指令实现比修改值更高级的操作。

  3. template: 替代原始模板中的标记的字符串。替换功能将替换所有旧元素为新值。注意 template 是如何使用 Scope 中定义的变量的。这允许你无需写任何额外的代码即可创建 macro-style 风格指令。

  4. replace: 说明是否替换原始标记中的值或是追加原始标记中的值。默认值是 false,这时原始标记将被保留。

  5. transclude: 说明自定义指令是否复制原始标记中的内容。例如,之前展示的 "tab" 指令设置了 transclude 为 true,因为 tab 元素包含其他 HTML 元素。 "dateInput" 指令则需要在初始化时为空,所以需要设置 transclude 为 false。

  6. link: 该方法在指令中扮演着重要的角色。它负责执行DOM 操作和注册事件监听器等。link 方法包含以下参数:

    • scope: 指令 Scope 的引用。scope 变量在初始化时是不被定义的,link 方法会注册监视器监视值变化事件。

    • element: 包含指令的 DOM 元素的引用,link 方法一般通过 jQuery 操作实例(如果没有加载 jQuery,还可以使用 Angular's jqLite)。

    • controller: 在有嵌套指令的情况下使用。这个参数作用在于把子指令的引用提供给父指令,允许指令之间进行交互。

    注意:当调用 link 方法时, 通过值传递("@")的 scope 变量将不会被初始化,它们将会在指令的生命周期中另一个时间点进行初始化,如果你需要监听这个事件,可以使用 scope.$watch 方法。

上次更新: 2021/5/6 下午4:08:15