Angular - 指令

前言

使用指令的优势在于,我们无需太多关心指令的内部实现(当给 Angular 应用添加所需指令后,Angular 内部会自行编译和运行所有指令),只需把重点放在如何使用指令上即可。

对于指令,我们需要了解一下几个问题:

  1. 什么是指令?
  2. 如何创建指令?
  3. 指令是如何编译和运行的?

什么是指令?

指令就是 AngularJS 扩展具有自定义功能的 HTML 元素的途径,指令又分为内置指令和自定义指令。

内置指令

AngularJS 提供了一系列的内置指令。其中一些指令重载了原生 HTML 元素,比如: <form><a> 标签,当在 HTML 中使用标签时,并不一定可以明确看出是否在使用指令。

基础 ng 属性指令
布尔属性

根据 HTML 标准的定义,布尔属性代表了 truefalse。当这个属性出现时即为 true,未出现为 false。AngularJS 提供了一组布尔属性,通过表达式的值来判断是否在元素上插入或移除该属性。

  1. ng-disabled
    使用 ng-disabled 可以对 disabled 属性进行绑定。
  2. ng-readonly
    使用 ng-readonly 可以对 readonly 属性进行绑定。
  3. ng-checked
    使用 ng-checked 可以对 checked 属性进行绑定。
  4. ng-selected
    使用 ng-selected 可以对 option 标签中的 selected 属性进行绑定。
  <div ng-controller='MyApp'>
        <p><button ng-disabled="isDisabled">ng-disabled = true</button></p>
        <p><button ng-disabled="!isDisabled">ng-disabled = false</button></p>
        <p><input type="text" ng-readonly="isReadonly" value="ng-readonly = true"></p>
        <p><input type="text" ng-readonly="!isReadonly" value="ng-readonly = false"></p>
        <p><input type="checkbox" ng-checked="isChecked">ng-checked = true</p>
        <p><input type="checkbox" ng-checked="!isChecked">ng-checked = false</p>
        <p>
            <select>
                <option>ng-selected = false</option>
                <option ng-selected="isSelected">ng-selected = true</option>
            </select>
        </p>
    </div>
angular.module('myApp', [])
        .controller('MyApp', ['$scope', function ($scope) {
            $scope.isDisabled = true;
            $scope.isReadonly = true;
            $scope.isChecked = true;
            $scope.isSelected = true;
        }])

注: 不是将属性值设为 true 或 false,而是该属性是否出现。

类布尔属性

ng-hrefng-src 等属性虽然不是标准的 HTML 布尔属性,但是由于行为相似,所以 AngularJS 是和布尔属性同等对待的。

  1. ng-href
    url 为动态绑定时,应使用 ng-href 代替 href,AngularJS 会在链接生效后再执行点击行为。
  2. ng-src
    src 地址为动态绑定时,应使用 ng-src 代替 src,AngularJS 会告诉浏览器,在 ng-src 对应的表达式生效之前不要加载图像。
在指令中使用子作用域

ng-appng-controller 是特殊的指令,因为他们会修改嵌套在它们内部的指令的作用域。

  1. ng-app
    任何具有 ng-app 属性的 DOM 元素将会被标记为 $rootScope$rootScope 是作用域链的起始点,任何嵌套在 ng-app 内的指令都会继承它)的起始点。
    注:如果需要在一个页面中放置多个 AngularJS应用,需要手动引导应用。
  2. ng-controller
    在 DOM 元素上放置一个控制器,为嵌套在其中的指令创建一个子作用域,避免将所有操作和模型都定义在 $rootScope 上。
    ng-controller 接收一个必备参数 expression(一个 AngularJS 表达式)。
    以下内置指令具有同样的特性:
  3. ng-include
    使用 ng-include 可以加载、编译并包含外部 HTML 片段到当前应用中。
  4. ng-switch
    ng-switchng-switch-whenon="propertyName" 一起使用,可以在 propertyName 发生变化时渲染不同指令到视图中。
<input type="text" ng-model='person.name'>
<div ng-switch on='person.name'>
    <p ng-switch-default>默认显示</p>
    <p ng-switch-when='show'>满足条件显示</p>
</div>
  1. ng-view
    ng-view 用来设置将被路由管理和放置在 HTML 中的视图的位置。
  2. ng-if
    ng-if 可以根据表达式的值在 DOM 中生成或移除一个元素。
  3. ng-repeat
    ng-repeat 用来遍历一个集合或为集合中的每个元素生成一个模板实例。
  • $index:遍历索引(0 ~ length - 1)。
  • $first:当元素为遍历的第一个时值为 true
  • $middle:当元素处于第一个和最后一个之间时值为 true
  • $last:当元素为遍历的最后一个时值为 true
  • $even:当 $index 的值为偶数是值为 true
  • $odd:当 $index 的值为奇数是值为 true
.even {
    background: #abcdef;
}
.odd {
    background: #eee;
}
.first {
    background: #ff0;
}
.last {
    background: #f00;
}
<p  ng-repeat="val in list" 
    ng-class="{even: $even, odd: $odd, first: $first, last: $last}">{{$index}}</p>
  1. ng-init
    ng-init 用来设置指令被调用时的内部作用域的初始状态。
  2. {{ }}
    {{ }} 是 AngularJS 内置的模板语法,在 $scope 和视图之间创建绑定,$scope 变化时,视图就会随之自动更新。
    注: {{ }} 实际上是 ng-bind 的简略形式,但在屏幕可视区内使用 {{ }} 会导致页面加载时未渲染的元素发生闪烁,用 ng-bind 可以避免这个问题。
  3. ng-bind
    {{ }}
  4. ng-cloak
    除了使用 ng-bind 来避免闪烁外,还可以在含有 {{ }} 的元素上使用 ng-cloak 指令。
  5. ng-bind-template
    ng-bind 类似,ng-bind-template 用来在视图中绑定多个表达式。
<p ng-bind-template='{{name}}  {{position}}'></p>
  1. ng-model
    ng-model 用来将表单控件同包含它们的作用域中的属性进行绑定。
  2. ng-show / ng-hide
    ng-showng-hide 是通过 CSS 控制元素的显隐(ng-if 则是通过添加或移除 dom)。
  3. ng-change
    在表单输入发生变化时触发。需要与 ng-model 一起使用。
  4. ng-form
    ng-form 用来在一个表单内部嵌套另一个表单。普通的 HTML <form> 标签不允许嵌套,但是 ng-form 可以(所以,只有内部所有子表单都合法时,外部表单才合法)。
    下列 CSS 类会根据表单的验证状态自动设置:
  • 表单合法时设置 ng-valid
  • 表单不合法时设置 ng-invalid
  • 表单未进行修改时设置 ng-pristion
  • 表单进行过修改时设置 ng-dirty
input {
    margin-bottom: 10px;
}
input.ng-invalid {
    border: 1px solid red;
}
input.ng-valid {
    border: 1px solid green;
}
<form name="signup_form" ng-controller="FormCtrl" ng-submit="submitForm()" novalidate>
    <div ng-repeat="field in fields" ng-form="signup_form_input">
        <input type="text"
                name="dynamic_input"
                ng-required="field.isRequired"
                ng-model="field.name"
                placeholder="{{field.placeholder}}" />
        <div ng-show="signup_form_input.dynamic_input.$dirty && signup_form_input.dynamic_input.$invalid">
            <span class="error" ng-show="signup_form_input.dynamic_input.$error.required">必填项</span>
        </div>
    </div>
    <button type="submit" ng-disabled="signup_form.$invalid">提交</button>
</form>
angular.module('myApp', [])
    .controller('FormCtrl', function($scope) {
    $scope.fields = [
        {placeholder: '请输入用户名', isRequired: true},
        {placeholder: '请输入密码', isRequired: true},
        {placeholder: '请输入邮箱(选填)', isRequired: false}
    ];
    $scope.submitForm = function() {
        alert("it works!");
    };
});
  1. ng-click
    ng-click 用来指定元素被点击是所触发的事件。
  2. ng-select
    ng-select 用来将数据同 HTML 的 <select> 元素进行绑定。可以和 ng-modelng-options 一同使用。
    ng-options 的值可以使一个内涵表达式(comprehension expression),简单来说就是它可以接受一个数组或对象,并对它们进行循环,将内部内容提供给 select 标签内部的选项。

(1). 数组作为数据源:

  • 用数组中的值做标签
  • 用数组中的值作为选中的标签
  • 用数组中的值做标签组
  • 用数组中的值作为选中的标签组

(2). 对象作为数据源:

  • 用对象的键 - 值(key - value)做标签
  • 用对象的键 - 值作为选中的标签
  • 用对象的键 - 值做标签组
  • 用对象的键 - 值作为选中的标签组
<div ng-controller="Directive">
    <select ng-model="item" ng-options="item.name for item in list">
        <option value="">who</option>
    </select>
    <p>Winner:{{item.name}}</p>
</div>
angular.module('directive', [])
        .controller('Directive', Directive);
    Directive.$inject = ['$scope'];
    function Directive ($scope) {
        $scope.list = [
            {
                id: 1,
                name: 'player1'
            }, {
                id: 2,
                name: 'player2'
            }, {
                id: 3,
                name: 'player3'
            }
        ];
    }
  1. ng-submit
    ng-submit 用来将表达式同 onsubmit 时间进行绑定。这个指令会阻止默认行为(发送请求并重新加载页面),但前提是表单不含有 action 属性。
  2. ng-class
    ng-class 可以动态设置元素的类,给要动态添加的类绑定一个表达式,当表达式为 true 时,这个类会被添加,反之会被移除。
  3. ng-attr-(suffix)
    当 AngularJS 编译 DOM 时会查找花括号 {{ some expression }} 内的表达式。这些表达式会被自动注册到 $watch 服务中并更新到 $digest 循环中,成为他的一部分:
<p>{{title}}</p>

有时候浏览器会对属性进行严格限制,如 SVG

<svg>
    <circle cx="{{ cx }}"></circle>
</svg>

此时会报错,指出有一个非法属性。这时可以用 ng-attr-(suffix) 解决。

<svg>
    <circle ng-attr-cx="{{ cx }}"></circle>
</svg>
  1. ng-style
    ng-style 为 HTML 元素添加 style 属性,且属性值必须是对象。
<p ng-style="{{style}}"></p>
$scope.style = {
    width: '100px',
    height: '100px',
    background: '#000'
}

自定义指令

定义一个指令
angular.module('directive', [])
    .directive('myDirective', myDirective);

function myDirective () {
    // 一个指令定义对象
    return {
        // 通过设置项定义指令
    }
}

directive() 方法接收两个参数:

  1. name (字符串)
    指令的名字,用来在视图中引用该指令。
  2. factory_function (函数)
    该函数返回一对象,在其中定义这个指令的全部行为。$compile 服务利用这个方法返回的对象,在 DOM 调用指令时来构造指令的行为。
指令的配置项
angular.module('directive', [])
    .directive('myDirective', myDirective);

function myDirective () {
    return {
        restrict: String,
        priority: Number,
        terminal: Boolean,
        template: String or Template Function: function (tElement, tAttrs) {},
        templateUrl: String,
        replace: Boolean or String,
        scope: Boolean or Object,
        transclude: Boolean,
        controller: String or function (scope, element, attrs, transclude, otherInjectables) {},
        controllerAs: String,
        require: String,
        link: function (scope, iElement, iAttrs) {},
        compile: // 返回一个对象或连接函数
            function (tElement, tAttrs, transclude) {
                return {
                    pre: function (scope, iElement, iAttrs, controller) {},
                    post: function (scope, iElement, iAttrs, controller) {}
                }
                // 或者
                return function postLink () {}
            }
    }
}
  • restrict (String)
    restrict 告诉 AngularJS 该指令以何种形式在 DOM 中被声明(默认 A)。
    E - 以元素形式声明
    <my-directive></my-directive>
    C - 以类名形式声明
    <div class="my-directive"></div>
    M - 以注释形式声明
    < !-- directive:my-directive -- >
    A - 以属性形式声明
    <div my-directive></div>

  • priority (Number)
    priority 可以用来设置指令的优先级,默认 0,通常情况下会忽略该属性,使用默认值(但也有场景需要设置高优先级,如 ng-repeat 的该参数为 1000,所以它总是在其他指令之前被调用;若优先级一直,则先声明先调用)。

  • terminal (Boolean)
    terminal 可以用来阻止当前元素上比自己优先级低的指令。
    例:ngIf 的优先级略高于 ngView, 如果 ngIf 表达式值为 true, 则 ngView 会正常执行,反之,由于 ngView 优先级较低就不会被执行。

  • template (String or Function)
    template 为可选参数,可以为:

    1. 一段 HTML 文本;
    2. 一个可以接受两个参数(tElementtAttrs)的函数,并返回一个代表模板的字符串。tElementtAttrs 中的 t 代表 template
      注:模板字符串中必须只存在一个根 DOM 元素
template: '<div>template</div>',

折行时需在末尾加上反斜线,这样才能正确解析多行字符串。

template: '<div>\
               template\
               <p>wrap</p>\
           </div>',
  • templateUrl (String or Function)
    templateUrl 为可选参数,可以为:

    1. 一个代表外部 HTML 文件路径的字符串;
    2. 一个可以接受两个参数的函数,参数为 TElementtAttrs,并返回一个外部 HTML 文件路径的字符串。
  • replace (Boolean)
    replace 为可选参数,如果设置这个参数,值必须为 true(默认为 false)。
    若值为 false 则意味着模板会被当做子元素插入到调用此指令的元素内部:

若值为 true 则意味着模板会替换调用此指令的元素:

  • scope (Boolean or Object)
    scope 为可选参数,可以设置为 true 或一个对象(默认为 false)。
    若值为 true,会从父作用域继承并创建一个新的作用域对象。
    每个指令被调用时可能会:
    (1). 默认值 false,继承父作用域不创建新作用域(互相影响);
<div ng-app='myApp'>
  <div ng-controller='MyCtrl'>
    父:{{value}}
    <directive-dom></directive-dom>
  </div>
</div>
<script>
  angular.module('myApp', [])
    .controller('MyCtrl', ['$scope', function ($scope) {
      $scope.value = 1;
    }])
    .directive('directiveDom', function () {
      return {
        restrict: 'ECMA',
        template: '<div><input type="type" ng-model="value" />子:{{value}}</div>',
        scope: false
      }
    });
</script>
默认值 `false`

(2). scope: true,创建一个新的继承父作用域的新的独立作用域(互不影响);

<div ng-app='myApp'>
  <div ng-controller='MyCtrl'>
    父:{{value}}
    <directive-dom></directive-dom>
  </div>
</div>
<script>
  angular.module('myApp', [])
    .controller('MyCtrl', ['$scope', function ($scope) {
      $scope.value = 1;
    }])
    .directive('directiveDom', function () {
      return {
        restrict: 'ECMA',
        template: '<div><input type="type" ng-model="value" />子:{{value}}</div>',
        scope: true
      }
    });
</script>
scope: true

(3). scope: {} / {...}
a.{}:创建一个隔离父作用域的新作用域

<div ng-app='myApp'>
  <div ng-controller='MyCtrl'>
    父:{{value}}
    <directive-dom></directive-dom>
  </div>
</div>
<script>
  angular.module('myApp', [])
    .controller('MyCtrl', ['$scope', function ($scope) {
      $scope.value = 1;
    }])
    .directive('directiveDom', function () {
      return {
        restrict: 'ECMA',
        template: '<div><input type="type" ng-model="value" />子:{{value}}</div>',
        scope: {}
      }
    });
</script>
scope: {}

b. {...}:可以与父作用域的属性方法进行绑定
绑定的三种形式:
@ (or @attr):绑定字符串
= (or =attr):双向绑定
& (or &attr):绑定方法

<div ng-app='myApp'>
  <div ng-controller="MyCtrl">
    选手 {{obj.id}} 的成绩为: {{obj.value}}
    <directive-dom obj="obj" str="绑定字符串" fn="fn(num)"></directive-dom>
  </div>
</div>
<script>
    (function () {
        'use strict';
        angular
            .module('myApp', [])
            .controller('MyCtrl', MyCtrl)
            .directive('directiveDom', directiveDom);
        MyCtrl.$inject = ['$scope'];
        function MyCtrl($scope) {
            $scope.obj = {
                value: 85,
                id: 1
            };
            $scope.fn = function (num) {
                console.log('我被点击了' + num + '次');
            };
        }
        function directiveDom() {
            return {
                restrivt: 'ECMA',
                template: '<div><button ng-click="nextHandle()">下一位</button><p>{{str}}</p></div>',
                scope: {
                    str: '@',
                    obj: '=',
                    fn: '&'
                },
                link: function ($scope) {
                    var n = 0;
                    $scope.nextHandle = function () {
                        n = n + 1;
                        $scope.obj.id = $scope.obj.id < 10 ? $scope.obj.id + 1 : 1;
                        $scope.obj.value = $scope.obj.id < 10 ? Math.floor(Math.random() * 100 + 1) : 85;
                        $scope.fn({num: n});
                    }
                }
            }
        }
    })();
</script>
scope: {...}
  • transclude (Boolean or Object)
    transclude 设置是否将指令内部的元素嵌入到模板中,可选参数,如果设置这个参数,默认为 false
  • controller (String or function)
    controller 为字符串时,会以字符串的值为名字,查找注册在应用中的控制器构造函数,也可以通过匿名构造函数的方式定义一个内联控制器(可以向其中注入任意 AngularJS 服务)。
    (1). $scope:与指令元素相关联的作用域;
    (2). $element:当前指令对应的元素;
    (3). $attrs:由当前元素的属性组成的对象;
    (4). $transclude:嵌入链接函数会与对应的嵌入作用域进行预绑定(transclude 链接函数是实际被执行用来克隆元素和操作 DOM 的函数)。
angular.module('myApp', [])
  .directive('directiveDom', function () {
    return {
      restrivt: 'ECMA',
      controller: function ($scope, $element, $attrs, $transclude) {

      }
    }
  });

注:controller 主要是用来提供可以在指令间复用的行为,link 只能在当前内部指令中定义行为,无法在指令间复用

  • controllerAs (String)
    controllerAs 用来设置控制器别名
  • require (String or Array)
    require 可以将 controller 注入到其值所指定的指令中,并作为当前指令 link 的第四个参数,设置为字符串时,代表另外一个指令的名字。
    require 参数值的修饰前缀:
    (1).?:在当前指令进行搜索(如果没有会传入 null);
    (2).^:在上游指令链中进行搜索;
    (3).?^:可以选择地加载需要的指令并在父指令链中进行搜索;
    (4).无:在自身所提供的控制器中进行搜索(如果没有会抛出一个错误)。
  • compile (Object or Function)
    通常情况下,设置 compile 函数是希望在指令和实时数据被放到 DOM 中之前进行 DOM 操作(编译函数负责对模板 DOM 进行转换)。
    注:compilelink 是互斥的,如果同时设置,则 link 会被忽略。
  • link (Function)
    如果指令中有 require 选项,则第四个参数代表控制器或所依赖的指令的控制器,若 require 选项为一个指令数组,则第四个参数代表由每个指令所对应的控制器所组成的数组(链接函数负责将作用域和 DOM 进行链接)。
    (1). scope:作用域;
    (2). iElement:实例元素,即使用此指令的元素;
    (3). iAttrs:实例属性(可以在所有指令的链接函数间共享);
    (4). controller:指向 require 选项所定义的控制器(可以在所有指令间共享)。

参考资料:
《Angular JS 权威教程》

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,793评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,567评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,342评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,825评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,814评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,680评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,033评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,687评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,175评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,668评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,775评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,419评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,020评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,206评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,092评论 2 351
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,510评论 2 343

推荐阅读更多精彩内容

  • 读中学其实是在很远的地方,若是走路需要两个小时的路程。母亲把家里的自行车推去修理铺修理一番,那时我高兴的不知所措。...
    陪月亮摘星星阅读 545评论 1 12
  • 总有一些人会与你渐渐远行 但你的心还是要坚定前行 你从不孤单 却终究要学会一个人陪伴 天虽时常下雨 但也有落日与你相伴
    午后的鱼小姐阅读 240评论 0 0
  • M:Model 模型 V:View 视图 C:Controller 控制器 Spring MVC是基于模型-视图-...
    命运_fda3阅读 434评论 0 0
  • “在遇见你之前,我爬过了世上最高的山,潜过最奇异的海,吹过了城堡上的风,坐在巴黎街头见过最美的姑娘,凭借自己的能力...
    Ireneeeee_阅读 251评论 0 0