欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

AngularJS自定义指令详解(有分页插件代码)

程序员文章站 2022-07-05 20:46:15
前言 除了 angularjs 内置的指令外,我们还可以创建自定义指令。 通过 .directive() 函数来添加自定义的指令。 调用自定义指令时,需要...

前言

除了 angularjs 内置的指令外,我们还可以创建自定义指令。

通过 .directive() 函数来添加自定义的指令。

调用自定义指令时,需要在html 元素上添加自定义指令名。

自定义指令命名规则:使用驼峰命名法来命名,即除第一个单词外的首字母需大写。如: mydirective。

在html页面调用该指令时需要以 - 分割,如: my-directive。示例代码:

<body ng-app="myapp">

<my-directive></my-directive>

<script>
 var app = angular.module("myapp", []);
 app.directive("mydirective", function() {
  return {
   template : "<h1>模板:可以写自己的html页面代码</h1>"
  };
 });
</script>

</body>

html页面调用自定义指令的四种方式

通过在自定义指令里添加 restrict 属性,根据设置不同的值来决定html页面的调用方式,如:

var app = angular.module("myapp", []);
app.directive("mydirective", function() {
 return {
  restrict : "a",//只能通过属性调用
  template : "<h1>自定义指令!</h1>"
 };
});

restrict值的不同,决定了调用方式的不同

属性值 调用方式 示例
a (attribute首字母) 属性名 <div my-directive></div>
c (class 首字母) 类名 <div class='my-directive'></div>
e (element 首字母) 元素名 <my-directive></my-directive>
m 注释 <!-- 指令: my-directive>

restrict 默认值为 ea, 即在html页面可通过元素名和属性名来调用自定义指令。

自定义指令属性详解

属性 值类型 说明
restrict string 指令的调用方式,a、c、e、m
priority number 指令执行的优先级
template string 指令使用的模板,可将html页面代码写于此。只能与templateurl二选其一
templateurl string 从指定的url地址加载模板。只能与template二选其一
replace boolean 是否用模板替换当前元素。true : 将指令标签替换成temple中定义的内容,页面上不会再有<my-directive>标签;false :则append(追加)在当前元素上,即模板的内容包在<my-directive>标签内部。默认false。
transclude boolean 是否将当前元素的内容转移到模板中
scope boolean /object 指定指令的作用域。false(默认值): 使用父作用域作为自己的作用域(每个引用自定义指令的标签若其中一个标签改变某一变量值,则会影响其他标签的值 )。true: 新建一个作用域,该作用域继承父作用域(两个引用自定义指令的标签之间的变量互不影响)。javascript对象:与父作用域隔离,并指定可以从父作用域访问的变量
controller function 定义与其他指令进行交互的接口函数
require string 指定需要依赖的其他指令
link function 以编程的方式操作dom,包括添加监听器等
compile function 编程的方式修改dom模板的副本,可以返回链接函数

对表格里的知识进行延伸

1.templateurl

如果template里拼写的html页面代码十分的多页复杂,拼字符串的话就太麻烦啦,这里我们就可以选择templateurl。我们可以将要拼写的html页面代码独立到一个页面里,如template.html;然后再指定该html文件所在的路径即可,如templateurl:”template.html”。用到该自定义指令时,会自动发一个http请求来获取template.html对应的模板内容。这样做的缺点是,多了一个http请求。别急,可以改进的:

angularjs规定了模板还可以用<script>标签定义:

<script type="text/ng-template" id="template.html">
   <div>自定义指令模板用script标签定义的方式,须放在html页面ng-controller指令所在标签的内部</div>
</script>

上面代码写在html页面的ng-controller指令所在标签的里面,这样就不用再去请求它了。示例:

<!doctype html>
<html>
<head>
<meta charset="utf-8">
<script src="http://apps.bdimg.com/libs/angular.js/1.4.6/angular.min.js"></script> 
</head>
<body >
 <div ng-app="myapp" ng-controller="mycontroller">

  <!-- 引用自定义指令 -->
  <my-directive></my-directive>

  <!-- 模板代码:须放在mycontroller所在标签内部 -->
  <script type="text/ng-template" id="template.html">
   <div> 自定义指令模板的templateurl形式</div>
  </script>
 </div>

 <script>
  //创建模块
  var app = angular.module('myapp', []);
  //创建控制器
  app.controller('mycontroller', function($scope) { });
  //创建自定义指令
  app.directive("mydirective", function() {
   return {
    restrict:'e',
    templateurl : "template.html"
   };
  });
 </script>
</body>

</html>

有多个模板时,我们可以将所有的模板集中在一个文件中,只需加载一次,然后根据id的不同调用不同的模板。

2.transclude

定义是否将当前元素(html页面的自定义指令)的内容转移到模板中。
模板中要接收当前元素内容的标签需要使用ng-transclude指令。

<body >
 <div ng-app="myapp" ng-controller="mycontroller">
  <!-- 引用自定义指令 -->
  <my-directive>自定义指定令内容</my-directive>

  <!-- 模板代码 -->
  <script type="text/ng-template" id="template.html">
   <div> 模板内容</div>
   <div ng-transclude></div>//模板接收上面自定义指令间的内容
  </script>
 </div>

 <script>
  //创建模块
  var app = angular.module('myapp', []);
  //创建控制器
  app.controller('mycontroller', function($scope) { });
  //创建自定义指令
  app.directive("mydirective", function() {
   return {
    templateurl : "template.html",
     transclude : true//转移到模板中
   };
  });
 </script>
</body>

3.什么是scope的父作用域

引用自定义指令的html页面的控制器所能控制的范围。下面代码的父作用域就是mycontroller所控制的范围

<body >
 <div ng-app="myapp" ng-controller="mycontroller">  
  <my-directive></my-directive><!-- 引用自定义指令 -->
 </div>

 <script>
  //创建模块
  var app = angular.module('myapp', []);
  //创建控制器
  app.controller('mycontroller', function($scope){ });
  //创建自定义指令
  app.directive("mydirective", function() {
   return {
    template : "<h1>自定义指令!</h1>"
   };
  });
 </script>
</body>

4.scope属性的值是对象(object)时的用法

angularjs内置指令的用法:ng-model=”obj”,通过obj这个变量双向绑定值,controller里变了,html页面也跟着变化。这说明,内置指令不仅可作为属性,还可动态改变值,这个要是不懂的,看看基础语法。如下代码:

<div ng-app="myapp" ng-controller="mycontroller">
 要动态变化的内容: <input ng-model="obj">
</div>

<script>
 var app = angular.module('myapp', []);
 app.controller('mycontroller', function($scope) {
  $scope.obj = "这个字符串值会同步到html里";
 });
</script>

自定义指令当然也需要实现这种功能啦。scope属性为对象时,可为自定义指令指定一个可以绑定值的属性。这里还得说明一下的是,这个scope属性与自定义指令里link属性里的scope参数是一个变量。

<!-- =符号的用法-->
<body >
 <div ng-app="myapp" ng-controller="mycontroller">
  <!-- 引用自定义指令:obj变量与控制器里的objc变量双向绑定了值 -->
  <my-directive speak="obj"></my-directive>
 </div>

 <script>
  //创建模块
  var app = angular.module('myapp', []);

  //创建控制器
  app.controller('mycontroller', function($scope) { 
   $scope.obj="父作用域";//父作用域给自定义指令属性赋的值
  });

  //创建自定义指令
  app.directive("mydirective", function() {
   return {
    template : "<p>模板内容</p>",
    scope:{
     title:"=speak"//定义一个speak属性供html页面的自定义指令用。如果写成title:"="格式,则自定义指令里的属性名就是title。
    },
    link: function postlink(scope, ielement, iattrs) {
     console.log(scope.title)//这里打印的值与控制器里的值一样
    }
   };
  });
 </script>
</body>

有了前面的一个示例,下面再来说说绑定策略:即用符号前缀给自定义指令传值。它是一个键值对,键是在自定义指令中使用的,值里符号后面的字符串是html页面自定义指令的属性名;如果值里只有符号,则html页面自定义指令的属性名就是键名。

符号 说明 示例
@ 值传递,单向绑定。html页面自定义指令里的val属性的值可传给link的scope使用。第一种写法——str : “@”,这种写法html页面的指令属性名为str str : “@val”,属性名为val
= 双向绑定数据到指令的属性中,数据值可以是任意类型的。第一种写法:name : “=”,这种写法html页面的自定义指令属性名就是name name : “=username”,属性名是username
& 使用父作用域中的一个函数,可以在指令中调用。第一种写法:getname:”&”,这种写法html页面的自定义指令属性名就是gegname getname : “&getusername”,属性名是getusername

其余两种符号用法:

<!-- @符号的用法 -->
<body >
 <div ng-app="myapp" ng-controller="mycontroller">
  <!-- 引用自定义指令 -->
  <my-directive title="obj" str="abcd">自定义指定令的内容555</my-directive>
 </div>

 <script>
  //创建模块
  var app = angular.module('myapp', []);
  //创建控制器
  app.controller('mycontroller', function($scope) { 
   $scope.obj="父作用域";//父作用域给自定义指令属性赋的值
  });
  //创建自定义指令
  app.directive("mydirective", function() {
   return {
    template : "<p >模板内容</p>",
    scope:{
     title:"=",
     str:"@"
    },
    link: function postlink(scope, ielement, iattrs) {
     console.log(scope.str)
     console.log(scope.title)
    }
   };
  });
 </script>
</body>

<!-- &符号的用法 -->
<body >
 <div ng-app="myapp" ng-controller="mycontroller">
  <!-- 引用自定义指令 -->
  <my-directive fun="test()"></my-directive>
 </div>

 <script>
  //创建模块
  var app = angular.module('myapp', []);
  //创建控制器
  app.controller('mycontroller', function($scope) { 
   $scope.test = function(){
     console.log('自定义指令会调用该法,所以这句话会打印到控制台上')
   }
  });
  //创建自定义指令
  app.directive("mydirective", function() {
   return {
    template : "<p >模板内容</p>",
    scope:{
     fun:"&"//属性名直接是fun
    },
    link: function postlink(scope, ielement, iattrs) {
     scope.fun();//调用父作用域的方法,好似不能传参,未深究。
    }
   };
  });
 </script>
</body>

5.controller属性

controller属性用于提供对外的接口,即该自定义指令会被其他自定义指令调用。所谓的接口,就是this后的变量或方法。

controller可以使用的参数,作用域、节点、节点的属性、节点内容的迁移,这些都可以通过依赖注入被传进来,所以你可以根据需要只写要用的参数,有$scope,z$element, $attrs, $transclude。

调用该自定义指令的指令需要放在该指令之间。假定firstdirective指令是要被调用的自定义指令,expander是调用者指令。如下:

<first-directive>
  <expander ng-repeat="item in list" attribute="list">{{item.title}}:{{item.text}}</expander>
 </first-directive>

既然firstdirective内部还有指令,则firstdirective必须配置transclude属性为true。代码如下:

//用于被调用的自定义指令
 app.directive('firstdirective',function(){
  return {
   template : '<div ng-transclude></div>',
   replace : true,
   transclude : true,
   controller :function(){
    this.getdata = function(val){
      var data = 3 * val;
     return data;
    }
    this.a = "abc";
   }
  }
 });

调用其他指令的自定义指令必须配置require属性指定指令名。然后在link函数里就可注入要调用的指令。

//自定义指令
 app.directive('expander',function(){
  return {
   templateurl : 'template.html',
   replace : true,
   transclude : true,
   require : '^?firstdirective',//引用其他自定义指令,^表示从父节点开始找,?表示将告诉$compile服务,如果所需的指令没找到,不要抛出异常
   scope : {
    title : '=attribute'
   },
   link : function(scope,element,attris,firstdirct){//注入
    console.log(firstdirct.a)//调用其他指令的变量
    console.log(firstdirct.getdata(6)) //调用其他指令的方法    
   }
  };
 });

6.link属性用法

link后的方法在指令中负责执行dom 操作和注册事件监听器等。link函数有五个参数(scope,element,attrs,controller,linker)。link 方法的参数解释:

scope: 它与自定义指令里的scope属性是一个东西。它是指令scope的引用,所以可改名为sco等其他名字。scope 变量在初始化时是不被定义的,link 方法会注册监视器监视值变化事件。

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

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

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

7.link与compile的区别

compile函数有三个参数(celement,cattrs,clinker),使用compile函数可以在ng创建原始dom实例以及创建scope实例之前,改变原始的dom(template element);可以应用于当需要生成多个element实例但只有一个template element的情况,ng-repeat就是一个最好的例子。它就在是compile函数阶段改变原始的dom生成多个原始dom节点,然后每个又生成element实例。因为compile只会运行一次,所以当你需要生成多个element实例的时候是可以提高性能的。

link函数有五个参数(scope,element,attrs,ctrl,linker)。

link又分为pre-link和post-link,在代码里直接用pre和post表示,当我们直接使用link时,默认跟post一样。我在网上找了个例子来说明一下区别,代码如下:

<body>
 <div ng-app="myapp" ng-controller="mycontroller">
  <level-one>
   <level-two>
    <level-three> hello </level-three>
   </level-two>
  </level-one>
 </div>
 <script>
   //创建模块
  var app = angular.module('myapp', []);
  //创建控制器
  app.controller('mycontroller', function($scope) { 

  });
  //自定义指令
  function createdirective(name){
   return function(){
   return {
    restrict: 'e',
    compile: function(telem, tattrs){
    console.log(name + ': compile => ' + telem.html());
    return {
     pre: function(scope, ielem, iattrs){
     console.log(name + ': pre link => ' + ielem.html());
     },
     post: function(scope, ielem, iattrs){
     console.log(name + ': post link => ' + ielem.html());
     }
    }
    }
   }
   }
  }

  app.directive('levelone', createdirective('levelone'));
  app.directive('leveltwo', createdirective('leveltwo'));
  app.directive('levelthree', createdirective('levelthree'));
 </script>
</body>

注意打印结果:

levelone: compile => 
  <level-two>
   <level-three>
    hello 
   </level-three>
  </level-two>
 leveltwo: compile => 
   <level-three>
    hello 
   </level-three>
 levelthree: compile => 
    hello 
 levelone: pre link => 
  <level-two>
   <level-three>
    hello 
   </level-three>
  </level-two>

leveltwo: pre link => 
   <level-three>
    hello 
   </level-three>

levelthree: pre link => 
    hello 

levelthree: post link => 
    hello 
leveltwo: post link => 
   <level-three>
    hello 
   </level-three>

levelone: post link => 
  <level-two>
   <level-three>
    hello 
   </level-three>
  </level-two>

分析打印结果:

运行levelone指令中的compile函数,ng就会递归遍历它的dom节点,然后在level-two与level-three上面重复这些操作。所以会依次打印连续三个compile。

pre会在所有compile执行完后且在所有post之前执行。这样可以在执行post前执行一些其他代码,有些类似aop。

由上面结果可知,post的执行顺序却是先levelthree最后levelone,即反向调用相关联的post-link函数。这么做的好处是,当我们运行levelone时,保证leveltwo与levelthree都已经执行过了,这样就会更安全。所以默认的link就是post。

一个我做过的分页例子

之所以展示这个代码,主要是给一些朋友看看真实的项目,,多余的东西删掉了,具体的注入这里就不在讲了。

html页面代码:

<div class="wp-20" ng-controller="appstatisticcontroller" ng-cloak>
 <div class="panel-footer">
  <s-pagination conf="paginationconf"></s-pagination>
 </div>
</div>

控制器代码:

"use strict";//严格

define(["application-configuration", "s-pagination", "tabledataservice"], function (app) {
 app.register.controller("appstatisticcontroller", ["$scope", "$rootscope", "$stateparams","$http", "tabledataservice",
  function($scope, $rootscope, $stateparams, $http, tabledataservice) {  

   var gettabledatasuccess = function(result) {
    if(result.c == 1) {
     $scope.title = result.title;
     $scope.lists = result.pagelist;
     $scope.total = result.data;
     $scope.paginationconf.totalitems = result.total;
    }else if(result.c == 2){
     //弹出框,没有查到数据 
    } else {
     alert(result.i);
    }
   };

   var gettabledataerror = function(result) {
    alert(result);
   };
   /*重要的代码,这个paginationconf与自定义指令双向绑定数据*/
   $scope.paginationconf = {
    currentpage: 1,
    itemsperpage: 10,
    pageslength: 9,
    search: false,
    onchange: function() {
     var param = {
      "pageno": this.currentpage,
      "pagesize": this.itemsperpage,
      "timetype": $scope.formdata.timetype,
      "adstyle":$scope.formdata.adstyle,
     };
     param.appid = $stateparams.appid;
     tabledataservice.gettabledata(
      param,
      "ims/appstat.do",
      gettabledatasuccess,
      gettabledataerror
     );
    }
   };

   $scope.$watch("formdata",function(newvalue,oldvalue, scope) {
    if(newvalue.keywords == oldvalue.keywords) {
      $scope.paginationconf.search = true;
    }
   }, true);

  }]);
});

自定义指令代码:也算是angularjs的分页插件

/**
 * 分页插件封装s-pagination.js
 * @date 2016-05-06
 * @author peter
 */

angular.module('s.pagination', []).directive('spagination',[function(){//自定义指令
 return {
  restrict: 'e',//仅限元素名调用
  template: '<div class="page-list">' +
  '<ul class="pagination" ng-show="conf.totalitems > 0">' +
  '<li ng-class="{disabled: conf.currentpage == 1}" ng-click="prevpage()"><span>«</span></li>' +
  '<li ng-repeat="item in pagelist track by $index" ng-class="{active: item == conf.currentpage, separate: item == \'...\'}" ' +
  'ng-click="changecurrentpage(item)">' +
  '<span>{{ item }}</span>' +
  '</li>' +
  '<li ng-class="{disabled: conf.currentpage == conf.numberofpages}" ng-click="nextpage()"><span>»</span></li>' +
  '</ul>' +
  '<div class="page-total" ng-show="conf.totalitems > 0">' +
  '第<input type="text" ng-model="jumppagenum" ng-keyup="jumptopage($event)"/>页 ' +
  '每页<select ng-model="conf.itemsperpage" ng-options="option for option in conf.perpageoptions "></select>' +
  '/共<strong>{{ conf.totalitems }}</strong>条' +
  '</div>' +
  '<div class="no-items" ng-show="conf.totalitems <= 0">暂无数据</div>' +
  '</div>',
  replace: true,
  scope: {
   conf: '='//双向绑定数据
  },
  link: function(scope, element, attrs){
   // 变更当前页
   scope.changecurrentpage = function(item) {
    if(item == '...'){
     return;
    }else{
     scope.conf.currentpage = item;
    }
   };

   // 定义分页的长度必须为奇数 (default:5)
   scope.conf.pageslength = parseint(scope.conf.pageslength) ? parseint(scope.conf.pageslength) : 5 ;
   if(scope.conf.pageslength % 2 === 0){
    // 如果不是奇数的时候处理一下
    scope.conf.pageslength = scope.conf.pageslength -1;
   }

   // conf.erpageoptions
   if(!scope.conf.perpageoptions){
    scope.conf.perpageoptions = [10, 20, 30, 40, 50];
   }

   // pagelist数组
   function getpagination(newvalue, oldvalue) {
    //新增属性search 用于附加搜索条件改变时触发
    if(newvalue[1] != oldvalue[1] || newvalue[2] != oldvalue[2]) {
     scope.conf.search = true;
    }
    // conf.currentpage
    scope.conf.currentpage = parseint(scope.conf.currentpage) ? parseint(scope.conf.currentpage) : 1;

    // conf.totalitems
    scope.conf.totalitems = parseint(scope.conf.totalitems) ? parseint(scope.conf.totalitems) : 0;

    // conf.itemsperpage (default:15)
    scope.conf.itemsperpage = parseint(scope.conf.itemsperpage) ? parseint(scope.conf.itemsperpage) : 15;

    // numberofpages
    scope.conf.numberofpages = math.ceil(scope.conf.totalitems/scope.conf.itemsperpage);

    // judge currentpage > scope.numberofpages
    if(scope.conf.currentpage < 1){
     scope.conf.currentpage = 1;
    }

    // 如果分页总数>0,并且当前页大于分页总数
    if(scope.conf.numberofpages > 0 && scope.conf.currentpage > scope.conf.numberofpages){
     scope.conf.currentpage = scope.conf.numberofpages;
    }

    // jumppagenum
    scope.jumppagenum = scope.conf.currentpage;

    // 如果itemsperpage在不在perpageoptions数组中,就把itemsperpage加入这个数组中
    var perpageoptionslength = scope.conf.perpageoptions.length;
    // 定义状态
    var perpageoptionsstatus;
    for(var i = 0; i < perpageoptionslength; i++){
     if(scope.conf.perpageoptions[i] == scope.conf.itemsperpage){
      perpageoptionsstatus = true;
     }
    }
    // 如果itemsperpage在不在perpageoptions数组中,就把itemsperpage加入这个数组中
    if(!perpageoptionsstatus){
     scope.conf.perpageoptions.push(scope.conf.itemsperpage);
    }

    // 对选项进行sort
    scope.conf.perpageoptions.sort(function(a, b){return a-b});

    scope.pagelist = [];
    if(scope.conf.numberofpages <= scope.conf.pageslength){
     // 判断总页数如果小于等于分页的长度,若小于则直接显示
     for(i =1; i <= scope.conf.numberofpages; i++){
      scope.pagelist.push(i);
     }
    }else{
     // 总页数大于分页长度(此时分为三种情况:1.左边没有...2.右边没有...3.左右都有...)
     // 计算中心偏移量
     var offset = (scope.conf.pageslength - 1)/2;
     if(scope.conf.currentpage <= offset){
      // 左边没有...
      for(i =1; i <= offset +1; i++){
       scope.pagelist.push(i);
      }
      scope.pagelist.push('...');
      scope.pagelist.push(scope.conf.numberofpages);
     }else if(scope.conf.currentpage > scope.conf.numberofpages - offset){
      scope.pagelist.push(1);
      scope.pagelist.push('...');
      for(i = offset + 1; i >= 1; i--){
       scope.pagelist.push(scope.conf.numberofpages - i);
      }
      scope.pagelist.push(scope.conf.numberofpages);
     }else{
      // 最后一种情况,两边都有...
      scope.pagelist.push(1);
      scope.pagelist.push('...');

      for(i = math.ceil(offset/2) ; i >= 1; i--){
       scope.pagelist.push(scope.conf.currentpage - i);
      }
      scope.pagelist.push(scope.conf.currentpage);
      for(i = 1; i <= offset/2; i++){
       scope.pagelist.push(scope.conf.currentpage + i);
      }

      scope.pagelist.push('...');
      scope.pagelist.push(scope.conf.numberofpages);
     }
    }

    if(scope.conf.onchange){
     //请求数据
     if(scope.conf.search) {
      scope.conf.onchange();
      scope.conf.search = false;
     }

    }
    scope.$parent.conf = scope.conf;
   }

   // prevpage
   scope.prevpage = function(){
    if(scope.conf.currentpage > 1){
     scope.conf.currentpage -= 1;
    }
   };
   // nextpage
   scope.nextpage = function(){
    if(scope.conf.currentpage < scope.conf.numberofpages){
     scope.conf.currentpage += 1;
    }
   };

   // 跳转页
   scope.jumptopage = function(){
    scope.jumppagenum = scope.jumppagenum.replace(/[^0-9]/g,'');
    if(scope.jumppagenum !== ''){
     scope.conf.currentpage = scope.jumppagenum;
    }
   };

   scope.$watch(function() {

    if(!scope.conf.totalitems) {
     scope.conf.totalitems = 0;
    }
    if(angular.isundefined(scope.conf.search)) {
     scope.conf.search = false;
    }

    var newvalue = [scope.conf.totalitems, scope.conf.currentpage, scope.conf.itemsperpage, scope.conf.search];
    return newvalue;
   }, getpagination, true);
  }
 };
}]);

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。