Angular的指令详解

指令,很重要

AngularJS与jQuery最大的区别在哪里?表现在数据双向绑定,实质就是DOM的操作形式不一样。

  • jquery通过选择器找到DOM元素,再赋予元素的行为;

  • 而angularjs则是,将指令与DOM绑定在一起,再扩展指令的行为。

所以AngularJS开发最理想的结果就是,在页面HTML与CSS的设计时,设计工程师只需要关注指令的使用;而在背后的逻辑开发上,架构工程师则是不需要知道如何操作DOM,只需要关注指令背后的行为要如何实现就行;测试工程师也可以开发针对指令的单元测试。

指令就是DOM与逻辑行为的媒介,本质就是DOM绑定的独立逻辑行为函数。

指令难点在于参数

来看看都有哪些

angular.module('app', [])
.directive('myDirective', function() {
    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(...) { ... }
        }
    };
 });

刚开始接触指令的时候,我简直就是蒙了,这堆参数究竟怎么用怎么理解啊。告诉大家我的一个理解方法。
把它们分成三类:

  1. 描述指令或DOM本身特性的内部参数

  2. 连接指令外界、与其他指令或控制器沟通的对外参数

  3. 描述指令本身行为的行为参数

除了 AngularJS 内置的63个指令外,我们还可以创建自定义指令。你可以使用 .directive 函数来添加自定义的指令。要调用自定义指令,HTML 元素上需要添加自定义指令名。使用驼峰法来命名一个指令, runoobDirective, 但在使用它时需要以 – 分割, runoob-directive;

angular在编译期间,编译器会用$interpolate服务去检查文本中是否嵌入了表达式。这个表达式会被当成一个监视器一样注册,并且作为$digest循环中的一部分,它会自动更新。

HTML的编译分为三个阶段:

  1. 首先浏览器会用它的标准API将HTML解析成DOM。 你需要认清这一点,因为我们的模板必须是可被解析的HTML。这是AngularJS和那些“以字符串为基础而非以DOM元素为基础的”模板系统的区别之处。

  2. DOM的编译是由 $compile 方法来执行的。 这个方法会遍历DOM并找到匹配的指令。一旦找到一个,它就会被加入一个指令列表中,这个列表是用来记录所有和当前DOM相关的指令的。 一旦所有的指令都被确定了,会按照优先级被排序,并且他们的 compile 方法会被调用。 指令的 $compile() 函数能修改DOM结构,并且要负责生成一个link函数(后面会提到)。$compile方法最后返回一个合并起来的链接函数,这时链接函数是每一个指令的compile函数返回的链接函数的集合。

  3. 通过调用上一步所说的链接函数来将模板与作用域链接起来。这会轮流调用每一个指令的链接函数,让每一个指令都能对DOM注册监听事件,和建立对作用域的的监听。这样最后就形成了作用域的DOM的动态绑定。任何一个作用域的改变都会在DOM上体现出来。

var html = '<div ng-bind='exp'></div>';

// Step 1: parse HTML into DOM element
var template = angular.element(html);

// Step 2: compile the template
var linkFn = $compile(template);

// Step 3: link the compiled template with the scope.
linkFn(scope);

~~~

1.angular中的指令 #

1.1 创建指令 #

<my-dire></my-dire>

var app = angular.module('appModule',[]);
app.directive('myDire',function () {
    return {

    }
});

1.2 模板 #

var app = angular.module('appModule',[]);
app.directive('myDire',function () {
    return {
+       template:'<div>Hello</div>'   
    }
});

1.3 transclude #

保留指令中的内容

<my-dire>world</my-dire>

return {
+    transclude:true,
-    template:'<div>Hello</div>'
+    template:'<div>Hello <span ng-transclude></span></div>'
}

1.3.1 面板 #

引入bootstrap

<link rel="stylesheet" href="https://cdn.bootcss.com/bootstrap/4.0.0-alpha.2/css/bootstrap.css">

增加panel.html

<div class="panel panel-default">
  <div class="panel-heading">这是一个面板</div>
  <div class="panel-body" ng-transclude>
  </div>
</div>

修改指令代码

return {
    transclude:true,
+   templateUrl:'panel.html',
}

1.4 link函数 #

link函数是用来链接视图和scope

  1. scope:当前作用域
  2. element:jq对象
  3. attrs:当前指令上的属性
return {
       transclude:true,
       templateUrl:'panel.html',
+      link: function (scope,element,attrs) {
+                      
+      }
}

1.4.1 给面板增加传入的标题 #

增加title属性

<my-dire title="这是头部">这是一个面板,helloAngular</my-dire>

增加属性值

<div class="panel panel-default">
+    <div class="panel-heading">{{title}}</div>
     <div class="panel-body" ng-transclude></div>
</div>

声明标题

return{
    link: function (scope,element,attrs) {
+      scope.title = attrs["title"];
    }
}

1.5 独立作用域 #

可以为当前指令设置独立作用域

return {
+  scope:true,
}

1.6 动态引用数据 #

1.6.1 “@”引用字符串 #

指令和作用域间的交互
声明控制器

app.controller('appCtrl',function ($scope) {
    $scope.title = [
        {name:'第一个面板'},
        {name:'第二个面板'}
    ]
});

挂载scope上的属性

<my-dire title="{{title[0].name}}">这是一个面板,helloAngular</my-dire>
<my-dire title="{{title[1].name}}">这是二个面板,helloAngular</my-dire>

在当前作用域下声明title属性,引用对应的值

return {
    transclude:true,
    templateUrl:'panel.html',
    scope:{
        title:'@'
    }
}

1.6.2 “=”引用scope上的属性 #

修改html

<my-dire title="title1">这是一个面板,helloAngular</my-dire>
<my-dire title="title2">这是一个面板,helloAngular</my-dire>

增加动态属性

app.controller('appCtrl',function ($scope) {
    $scope.title1 = 'hello1';
    $scope.title2 = 'hello2'
});

修改指令

scope:{
    title:'='
}

1.6.3 “=”与”@”区别 #

@单向绑定
html页面

控制器:<input type="text" ng-model="home">
<my-dire home="{{home}}"></my-dire>

控制器

app.controller('appCtrl',function ($scope) {
    $scope.home = 'home'
});
app.directive('myDire',function () {
    return {
        transclude:true,
        template:'指令内部<input type="text" ng-model="home">',
        scope:{
            home:'@'
        }
    }
});

=双向绑定
html页面

控制器:<input type="text" ng-model="hobby">
app.controller('appCtrl',function ($scope) {
     $scope.home = 'home';
+    $scope.hobby = 'hobby';
});
return {
+   template:'指令内部<input type="text" ng-model="hobby">',
    scope:{
         home:'@',
+        hobby:'='
    }
}

1.6.4 &绑定函数 #

html绑定方法

<my-dire home ="home(person)" name="{{name}}"></my-dire>

指令中调用函数

app.controller('appCtrl',function ($scope) {
    $scope.name = 'zfpx';
    $scope.home = function (who) {
        alert(who);
    }
});
app.directive('myDire',function () {
    return {
        template:'{{name}}<div ng-click="home({person:name})">谁的家</div>',
        scope:{
            home:'&',name:'@'
        }
    }
});

1.7 replace替换指令 #

增加replace:true替换原有指令

return {
    replace:true
}

1.8 compile函数 #

在link函数前执行,编译模板,返回的函数为link函数

<my-dire time="3"></my-dire>

指令中增加compile函数

return {
    transclude:true,
    template:'<div>Hello,zfpx {{title}}</div>',
    compile: function (element,attrs) {
        var tmpl = element.children();
        for(var i = 0; i < attrs.time-1;i++){
            element.append(tmpl.clone());
        }
        return function (scope,element,attrs) {
            scope.title = '123';
        }
    },
}

2.指令和指令间的交互 #

创建多个指令,依赖于girl指令

<girl love-money  ng-click="show()">Angular MM</girl>

创建指令公有部分

app.directive('girl',function () {
    return {
        controller: function ($scope) {
            var arr = [];
            this.add = function (attrs) {
                arr.push(attrs);
            };
            $scope.show = function () {
                alert(arr);
            }
        }
    }
});

创建指令间的依赖

app.directive('loveMoney',function () {
    return {
        require:'^girl',
        link: function (scope,element,attrs,girlCtrl) {
            girlCtrl.add('loveMoney');
        }
    }
});

3.opener指令demo #

增加css样式

.title{
    width: 100px;
    height: 30px;
    line-height: 30px;
    background: yellow;
}
.content{
    width: 100px;
    height: 100px;
    background: red;
}

增加指令

<opener title="标题1">这是内容1</opener>

增加引用模板

<div class="title" ng-click="show()">{{title}}</div>
<div class="content" ng-show="flag" ng-transclude></div>

增加指令

app.directive('opener',function () {
    return {
        templateUrl:'open.html',
        transclude:true,
        scope:{
            title:'@'
        },
        link:function(scope,element,attrs){
            scope.flag = true;
            scope.show = function () {
                scope.flag = !scope.flag;
            }
        }
    }
});

4.opener组 #

增加组

<group>
    <opener title="标题1">这是内容1</opener>
    <opener title="标题2">这是内容2</opener>
</group>

设置指令

app.directive('group', function () {
    return {
        controller: function ($scope) {
            var arr = [];
            this.add = function (scope) {
                arr.push(scope);
            }
            this.close = function (scope) {
                for(var i = 0; i<arr.length;i++){
                    if(arr[i]!=scope){
                        arr[i].flag = false;
                    }
                }
            }
        }
    }
});
app.directive('opener',function () {
    return {
        templateUrl:'open.html',
        transclude:true,
        require:'^group',
        scope:{
            title:'@'
        },
        link:function(scope,element,attrs,groupCtrl){
            scope.flag = false;
            scope.show = function () {
                scope.flag = !scope.flag;
                groupCtrl.close(scope);
            };
            groupCtrl.add(scope);
        }
    }
});

`~

指令(Directives)是所有AngularJS应用最重要的部分。尽管AngularJS已经提供了非常丰富的指令,但还是经常需要创建应用特定的指令。这篇教程会为你讲述如何自定义指令,以及介绍如何在实际项目中使用。在这篇文章的最后(第二部分),我会指导你如何使用Angular指令来创建一个简单的记事本应用。

概述

一个指令用来引入新的HTML语法。指令是DOM元素上的标记,使元素拥有特定的行为。举例来说,静态的HTML不知道如何来创建和展现一个日期选择器控件。让HTML能识别这个语法,我们需要使用指令。指令通过某种方法来创建一个能够支持日期选择的元素。我们会循序渐进地介绍这是如何实现的。 如果你写过AngularJS的应用,那么你一定已经使用过指令,不管你有没有意识到。你肯定已经用过简单的指令,比如 ng-mode, ng-repeat, ng-show等。这些指令都赋予DOM元素特定的行为。例如,ng-repeat 重复特定的元素,ng-show 有条件地显示一个元素。如果你想让一个元素支持拖拽,你也需要创建一个指令来实现它。指令背后基本的想法很简单。它通过对元素绑定事件监听或者改变DOM而使HTML拥有真实的交互性。

jQuery视角

想象一下使用jQuery如何创建一个日期选择器。首先,我们在HTML中添加一个普通的输入框,然后通过jQuery调用 $(element).dataPicker() 来将它转变成一个日期选择器。但是,仔细想一下。当一个设计人员过来检查HTML标记的时候,他/她能否立刻猜到这个字段实际上表示的内容?这只是一个简单的输入框,或者一个日期选择器?你需要查看jQuery代码来确定这些。而Angular的方法是使用一个指令来扩展HTML。

参考来自  AngularJS权威指南 ,

文章中主要介绍指令定义的选项配置

 废话不多说,下面就直接上代码


//angular指令的定义,myDirective ,使用驼峰命名法
angular.module('myApp', [])
.directive('myDirective', function ($timeout, UserDefinedService) {
// 指令操作代码放在这里
});

//angular自定义指令 的使用,使用 “-” 来代替驼峰命名法
<div my-directive></div>
注意:为了避免与未来的HTML标准冲突,给自定义的指令加入前缀来代表自定义的
命名空间。AngularJS本身已经使用了 ng- 前缀,所以可以选择除此以外的名字。
在例子中我们使用 my- 前缀(比如 my-derictive ) 。

指令的生命周期开始于 $compile 方法并
结束于 link 方法

//自定义指令的全部可设置的属性大致如下
指令的选项如下所示,每个键的值说明了可以将这个属性设置为何种类型或者什么样的
函数:
angular.module('myApp', [])
.directive('myDirective', function() {               //指令名称myDirective
return {                //返回一个对象
restrict: String,      //可选字符串参数,可以设置这个指令在DOM中可以何种形式被声明,
            //默认为A(attr(当做标签属性来使用))<div my-directive></div> 
            // 设置为“E”(ele,(直接当做标签来使用)) <my-directive></my-directive>
            //C(类名)
            //<div class="my-directive:expression;"></div>
            //M(注释)
            //<--directive:my-directive expression-->
            //这些选项可以单独使用,也可以混合在一起使用:
            //angular.module('myDirective', function(){
            //    return {
            //        restrict: 'EA' // 输入元素或属性
            //    };
            //})

priority: Number, //优先级,可忽略,默认为0, ngRepeat的优先级为1000,这样就可以保证在同一元素上,它总是在其他指令之前被调用。
terminal: Boolean,//(布尔型),true或false,如果为false,则这个参数用来告诉AngularJS停止运行当前元素上比本指令优先级低的指令。优先级相同的指令还是会被执行。 ngIf 的优先级略高于 ngView ,
template: String or Template Function: //(字符串或函数)指令中的一个重要的一个属性,必须被设置其中一种
                    //1,  一段HTML文本;
                    //2,可以接收两个参数的函数,参数为 tElement 和 tAttrs 
                    //在html模板中必须只有一个根html标签,且如果有换行则需要使用“\”
                    //例如template: '\
                    //    <div> <-- single root element -->\
                    //        <a href="http://google.com">Click me</a>\
                    //        <h1>When using two elements, wrap them in a parent element</h1>\
                    //    </div>\
                    //function(tElement, tAttrs) (...},
                    //更好的选择是使用 templateUrl 参数引用外部模板,参考下面的参数
templateUrl: String,        //(字符串或函数)1,外部路径的字符串,2,接受两个参数的函数,参数为 tElement 和 tAttrs ,并返回一个外部HTML文件路径的字符串
                //模板加载后,AngularJS会将它默认缓存到 $templateCache 服务中。(可以提前加载模块到缓存中,提高加载速度)
replace: Boolean or String,  //(布尔型)默认为false(模板内容会加载到标签内部),true(模板内容会替换当前标签)
scope: Boolean or Object,  //(布尔型或对象),默认为false, 设置为true 时,会从父作用域继承并创建一个新的作用域对象。
            // ng-controller 的作用,就是从父级作用域继承并创建一个新的子作用域。
            //如果要创建一个能够从外部原型继承作用域的指令,将 scope 属性设置为 true 
            //设置为一个对象,则能设置 隔离作用域, scope 属性设置为一个空对象 {} 。如果这样做了,指令的模板就无法访问外部作用域了:
            //例如.directive('myDirective', function() {
            //        return {
            //            restrict: 'A',
            //            scope: {},
            //            priority: 100,
            //            template: '<div>Inside myDirective {{ myProperty }}</div>'
            //        };
            //    });

            //在scope对象中,还可以使用“@” “=” “&”,来设置模板中数据的作用域和绑定规则
            //"@"  本地作用域属性:使用当前指令中的数据和DOM属性的值进行绑定
            //“=” 双向绑定:本地作用域上的属性同父级作用域上的属性进行双向的数据绑定。
            //“&” 父级作用域绑定:通过 & 符号可以对父级作用域进行绑定
            //例如
            //scope: {
            //    ngModel: '=', // 将ngModel同指定对象绑定
            //    onSend: '&', // 将引用传递给这个方法
            //    fromName: '@' // 储存与fromName相关联的字符串
            //}

transclude: Boolean,  //默认为false.只有当你希望创建一个可以包含任意内容的指令时, 才使用 transclude: true 。
            //如果指令使用了 transclude 参数,那么在控制器(下面马上会介绍)中就无法正常监听数
            //据模型的变化了。
controller: String or function(scope, element, attrs, transclude, otherInjectables) { ... }, //(字符串或函数)注册在应用中的控制器的构造函数
            //使用函数创建内联控制器,例如
            //angular.module('myApp',[])
            //    .directive('myDirective', function() {
            //    restrict: 'A',
            //    controller:
            /    function($scope, $element, $attrs, $transclude) {
            //    // 控制器逻辑放在这里
            //    }
            //})

controllerAs: String,   //可以在指令中创建匿名控制器,例如
            //.directive('myDirective', function() {
            //    return {
            //    restrict: 'A',
            //    template: '<h4>{{ myController.msg }}</h4>',
            //    controllerAs: 'myController',
            //    controller: function() {
            //        this.msg = "Hello World"
            //        }
            //    };
            //});    


require: String, //(字符串或数组)字符串代表另外一个指令的名字,如果没有前缀,指令将会在自身所提供的控制器中进行查找,如果没有找到任何控制器(或
        //具有指定名字的指令)就抛出一个错误。
        //例如
        //如果不使用 ^ 前缀,指令只会在自身的元素上查找控制器。
        //require: 'ngModel'
        // 使用 ?   如果在当前指令中没有找到所需要的控制器,会将 null 作为传给 link 函数的第四个参数
        //require: '?ngModel'
        //使用  ^  如果添加了 ^ 前缀,指令会在上游的指令链中查找 require 参数所指定的控制器。
        //require: '^ngModel'
        // 使用 ^?  将前面两个选项的行为组合起来,我们可选择地加载需要的指令并在父指令链中进行查找。
        //require: '^?ngModel',

link: function(scope, iElement, iAttrs) { ... }, //compile 选项本身并不会被频繁使用,但是 link 函数则会被经常使用。
                        //当我们设置了 link 选项, 实际上是创建了一个 postLink() 链接函数, 以便 compile() 函数可以定义链接函数。
                        //compile 和 link 选项是互斥的。如果同时设置了这两个选项,那么会把 compile
                        //所返回的函数当作链接函数,而 link 选项本身则会被忽略。
                        //通常情况下,如果设置了 compile 函数,说明我们希望在指令和实时数据被放到DOM中之前
                        //进行DOM操作,在这个函数中进行诸如添加和删除节点等DOM操作是安全的。
        //用 link 函数创建可以操作DOM的指令。
        //require 'SomeController',
        //link: function(scope, element, attrs, SomeController) {
            // 在这里操作DOM,可以访问required指定的控制器
        //}
compile: function(tElement, tAttrs, transclude) {  
        return {
            pre: function(scope, iElement, iAttrs, controller) { ... },
            post: function(scope, iElement, iAttrs, controller) { ... }
        }
        // 或者
        return function postLink(...) { ... }
    }
};
});

angular指令的详解就到这,如果想了解跟多请移步  angular官网https://docs.angularjs.org/

~~

未经允许不得转载:WEB前端开发 » Angular的指令详解

赞 (0)