美文网首页
Angular - 指令

Angular - 指令

作者: direwolf_ | 来源:发表于2019-04-26 10:32 被阅读0次

    前言

    使用指令的优势在于,我们无需太多关心指令的内部实现(当给 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 权威教程》

    相关文章

      网友评论

          本文标题:Angular - 指令

          本文链接:https://www.haomeiwen.com/subject/tfwqfqtx.html