AngularJS自定义控件实例详解


Posted in Javascript onDecember 13, 2016

本文实例讲述了AngularJS自定义控件。分享给大家供大家参考,具体如下:

自定义指令介绍

AngularJS 指令作用是在 AngulaJS 应用中操作 Html 渲染。比如说,内插指令 ( {{ }} ), ng-repeat 指令以及 ng-if 指令。

当然你也可以实现自己的。这就是 AngularJS 所谓的"教会 HTML 玩新姿势”。本文将告诉你如何做到。

指令类型

可以自定义的指令类型如下:

元素
属性
CSS class
Comment directives

这里面,AngularJS 强烈建议你用元素和属性类型,而不用 CSS class 和 comment directives (除非迫不得已)。

指令类型决定了指令何时被激活。当 AngularJS 在 HTML 模板中找到一个 HTML 元素的时候,元素指令被激活。当 AngularJS 找到一个 HTML 元素属性时,属性指令被激活。当 AngularJS 找到一个 CSS class 时, CSS class 指令被激活,最后,当 AngularJS 找到 HTML comment 时,comment directive 被激活。

基础例子

你可以向模块注册一个指令,像这样:

<!-- lang: js -->
myapp = angular.module("myapp", []);
myapp.directive('div', function() {
  var directive = {};
  directive.restrict = 'E'; /* restrict this directive to elements */
  directive.template = "My first directive: {{textToInsert}}";
  return directive;
});

来看模块中调用的 directive() 函数。当你调用该函数时,意味着你要注册一个新指令。directive() 函数的第一个参数是新注册指令的名称。这是之后你在 HTML 模板中调用它的时候用的名称。例子中,我用了 'div' ,意思是说当 HTML 模板每次在找到 HTML 元素类型是 div 的时候,这个指令都会被激活。

传递给 directive 函数的第二个参数是一个工厂函数。调用该函数会返回一个指令的定义。 AngularJS 通过调用该函数来获取包含指令定义的 JavaScript 对象。如果你有仔细看上面的例子,你会知道它返回的确是是一个 Javascript 对象。

这个从工厂函数中返回的 Javascript 对象有两个属性: restrict 和 template 字段。

restrict 字段用来设置指令何时被激活,是匹配到 HTML 元素时,还是匹配到元素属性时。也就是指令类型。把restrict 设置为 E 时,只有名为 div 的 HTML 元素才会激活该指令。把 restrict 设置为 A 时,只有名为 div 的 HTML 元素属性才会激活该指令。你也可以用 AE ,这样会使得匹配到元素名和元素属性名时,都可以激活该指令。

template 字段是一个 HTML 模板,用来替换匹配的 div 元素。它会把匹配到的 div 当成一个占位符,然后用 HTML 模板在同一位置来替换掉它。也就是说,HTML 模板替换匹配的到 HTML 元素。

比如说你的 HTML 页面有这样一段 HTML:

<!-- lang: js -->
<div ng-controller="MyController" >
  <div>This div will be replaced</div>
</div>

当 AngularJS 找到内嵌的 div 的时候,会激活自定义的指令。然后把该 div 元素替换掉,替换后的 HTML 将变成这样:

<!-- lang: js -->
My first directive: {{textToInsert}}

然后你看到了,这段 HTML 里面包含了一个内插指令 ({{textToInsert}})。AngularJS 会再执行一次,让内插指令实际值显示出来。然后 $scope.textToInsert 属性将会在这个 HTML 点上替换掉内插指令占位符。

template 和 templateUrl 属性

创建自定义指令最简单的例子就是上面这样了。你的指令用来生成 HTML,然后你把 HTML 放到指令定义对象的 template 属性中。下面这个例子是上面那最简单的例子的重复,注意 template部分:

<!-- lang: js -->
myapp = angular.module("myapp", []);
myapp.directive('div', function() {
  var directive = {};
  directive.restrict = 'E'; /* restrict this directive to elements */
  directive.template = "My first directive: {{textToInsert}}";
  return directive;
});

如果 HTML 模板越来越大,把它写在一个 Javascript 字符串中肯定非常难维护。你可以把它放到一个单独的文件中,然后 AngularJS 可以通过这个文件把它加载进来。然后再把 HTML 模板文件的 URL 放到指令定义对象的 templateUrl 属性中。下面是例子:

<!-- lang: js -->
myapp = angular.module("myapp", []);
myapp.directive('div', function() {
  var directive = {};
  directive.restrict = 'E'; /* restrict this directive to elements */
  directive.templateUrl = "/myapp/html-templates/div-template.html";
  return directive;
});

然后 AngularJS 会从 templateUrl 属性中设置的 URL 将 HTML 模板加载进来。

用独立的 HTML 模板文件,设置 templateUrl 属性,在你要创建更多的通用指令时会显得更加有用,比如说用来显示用户信息的指令。例子:

<!-- lang: js -->
myapp = angular.module("myapp", []);
myapp.directive('userinfo', function() {
  var directive = {};
  directive.restrict = 'E'; /* restrict this directive to elements */
  directive.templateUrl = "/myapp/html-templates/userinfo-template.html";
  return directive;
});

例子创建了一个指令,当AngularJS 找到一个 <userinfo> 元素的时候就会激活它。AngularJS 加载指向 /myapp/html-templates/userinfo-template.html 的模板并解析它,它就像从一开始就被嵌在上一级 HTML 文件中一样。

从指令中隔离 $scope

在上面的例子中,把 userinfo 指令硬绑定到了 $scope ,因为 HTML 模板是直接引用 textToInsert 属性的。直接引用 $scope 让指令在同一个 controller 中的时候,非常难复用,因为 $scope在同一个 controller 中的值都是一样的。比如说,你在页面的 HTML 中这样写的时候:

<!-- lang: js -->
<userinfo></userinfo>
<userinfo></userinfo>

这两个 <userinfo> 元素会被同样的 HTML 模板取代,然后绑定到同样的 $scope 变量上。结果就是两个 <userinfo> 元素将会被一模一样的 HTML 代码给替换了。

为了把两个 <userinfo> 元素绑定到 $scope 中的不同的值,你需要给 HTML 模板一个 isolate scope。

所谓 isolate scope 是绑定在指令上的独立的 scope 对象。下面是定义的例子:

<!-- lang: js -->
myapp.directive('userinfo', function() {
  var directive = {};
  directive.restrict = 'E';
  directive.template = "User : {{user.firstName}} {{user.lastName}}";
  directive.scope = {
    user : "=user"
  }
  return directive;
})

请看 HTMl 模板中的两个内插指令 {{user.firstName}} 和 {{user.lastName}}。注意 user. 部分。以及directive.scope 属性。 directive.scope 是个带 user 属性的 Javascript 对象。于是directive.scope 就成为了 isolate scope 对象,现在 HTML 模板被绑到了 directive.scope.user 上(通过 {{user.firstName}} 和 {{user.lastName}} 内插指令)。

directive.scope.user 被设置为 “=user” 。意思是说 directive.scope.user 和 scope 中的属性绑在一起的(不是 isolate scope),scope 中的属性通过 user 属性传入 <userinfo> 元素。听起来挺费劲的,直接看例子:

<!-- lang: js -->
<userinfo user="jakob"></userinfo>
<userinfo user="john"></userinfo>

这两个 <userinfo> 元素都有 user 属性。user 的值指向了 $scope 中同名属性,被指定的 $scope中的属性将在 userinfo 的 isolate scope object 中被使用。

下面是完整的例子:

<!-- lang: js -->
<userinfo user="jakob"></userinfo>
<userinfo user="john"></userinfo>
<script>
myapp.directive('userinfo', function() {
  var directive = {};
  directive.restrict = 'E';
  directive.template = "User : <b>{{user.firstName}}</b> <b>{{user.lastName}}</b>";
  directive.scope = {
    user : "=user"
  }
  return directive;
});
myapp.controller("MyController", function($scope, $http) {
  $scope.jakob = {};
  $scope.jakob.firstName = "Jakob";
  $scope.jakob.lastName = "Jenkov";
  $scope.john = {};
  $scope.john.firstName = "John";
  $scope.john.lastName = "Doe";
});
</script>

compile() 和 link() 函数

如果你需要在你的指令中做更高级的操作,单纯使用 HTML 模板做不到的时候,你可以考虑使用compile() 和 link() 函数。

compile() 和 link() 函数定义了指令如何修改匹配到的 HTML。

当指令第一次被 AngularJS 编译的时候 (在 HTML 中第一次被发现),compile() 函数被调用。compile() 函数将为该元素做一次性配置。

然后 compile() 函数以返回 link() 作为终结。link() 函数将在每次元素被绑到 $scope 数据时,都被调用。

下面是例子:

<!-- lang: js -->
<script>
myapp = angular.module("myapp", []);
myapp.directive('userinfo', function() {
  var directive = {};
  directive.restrict = 'E'; /* restrict this directive to elements */
  directive.compile = function(element, attributes) {
    // do one-time configuration of element.
    var linkFunction = function($scope, element, atttributes) {
      // bind element to data in $scope
    }
    return linkFunction;
  }
  return directive;
});
</script>

compile() 函数带两个参数: element 和 attributes。

element 参数是 jqLite 包装过的 DOM 元素。AngularJS 有一个简化版 jQuery 可以用于操作 DOM,所以对 element 的 DOM 操作方式和你在 jQuery 中所知的一样。

attributes 参数是包含了 DOM 元素中的所有的属性集合的 Javascript 对象。因此,你要访问某个属性你可以这样写 attributes.type。

link() 函数带三个参数: $scope,element 和 attributes。element 和attributes 参数和传到 compile() 函数中的一样。$scope 参数是正常的 scope 对象,或者当你在指令定义对象中定义了 isolate scope 的时候,它就是 isolate scope。

compile() 和 link() 的命名相当混乱。它们肯定是受编译队伍的启发而命名的。我可以看到相似点,不过一个编译器是传入一次,然后输出。而指令则是配置一次 HTML 元素,然后在之后的 $scope 对象改变时进行更新。

compile() 函数如果叫做 create(), init() 或者 configure()也许会更好。这样可以表达出这个函数只会被调用一次的意思。

而 link() 函数如果叫 bind() 或者 render() 也许会更好,能更好的表达出这样的意思,在指令绑定数据或者重绑定数据的时候,这个函数将会被调用。

下面是一个完整的例子,演示了指令使用 compile() 和 link() 函数的:

<!-- lang: js -->
<div ng-controller="MyController" >
  <userinfo >This will be replaced</userinfo>
</div>
<script>
  myapp = angular.module("myapp", []);
  myapp.directive('userinfo', function() {
    var directive = {};
    directive.restrict = 'E'; /* restrict this directive to elements */
    directive.compile = function(element, attributes) {
      element.css("border", "1px solid #cccccc");
      var linkFunction = function($scope, element, attributes) {
        element.html("This is the new content: " + $scope.firstName);
        element.css("background-color", "#ffff00");
      }
      return linkFunction;
    }
    return directive;
  })
  myapp.controller("MyController", function($scope, $http) {
    $scope.cssClass = "notificationDiv";
    $scope.firstName = "Jakob";
    $scope.doClick = function() {
      console.log("doClick() called");
    }
  });
</script>

compile() 函数设置 HTML 元素的 border 。它只执行一次,因为 compile() 函数只执行一次。

link() 替换 HTML 元素内容,并且把背景颜色设置为黄色。

把设置 border 放在 compile(), 把背景颜色放在 link() 没啥特别的理由。你可以把所有的操作都丢到 compile(),或者都放到 link()。如果都放到 compile() 它们只会被设置一次(你需要它们是常量的话)。如果放到了 link(),它们会在每次 HTML 元素被绑到 $scope 的时候都发生变化。当你希望根据 $scope 中的数据来设置 boarder 和背景颜色的时候这非常有用。

只设置 link() 函数

有时候你的指令可能不需要 compile() 。你只需要用到 link()。这种情况下,你可以直接设置指令定义对象中的 link() 函数。下面是一个对上面例子的修改,只用 link 函数:

<!-- lang: js -->
<div ng-controller="MyController" >
  <userinfo >This will be replaced</userinfo>
</div>
<script>
  myapp = angular.module("myapp", []);
  myapp.directive('userinfo', function() {
    var directive = {};
    directive.restrict = 'E'; /* restrict this directive to elements */
    directive.link = function($scope, element, attributes) {
        element.html("This is the new content: " + $scope.firstName);
        element.css("background-color", "#ffff00");
    }
    return directive;
  })
  myapp.controller("MyController", function($scope, $http) {
    $scope.cssClass = "notificationDiv";
    $scope.firstName = "Jakob";
    $scope.doClick = function() {
      console.log("doClick() called");
    }
  });
</script>

注意 link() 方法和之前例子中返回的 link() 是完全一样的。

通过 Transclusion 封装元素的指令

到目前为止,我们看到的所有例子,都是把匹配到的元素内容给替换为指令的指定内容,不管是通过 Javascript 也好或者 HTML 模板也好。不过如果遇到内容中有部分是开发者可以指定的内容的时候呢?比如说:

<!-- lang: js -->
<mytransclude>This is a transcluded directive {{firstName}}</mytransclude>

标记为 <mytransclude> 的元素,它的部分内容可以由开发者设置。因此,这部分 HTML 不应该被指令的 HTML 模板给替换。我们实际上是希望这部分 HTML 由 AngularJS 来处理的。这个处理叫做 “transclusion”。 1

为了能让 AngularJS 把这部分 HTML 放到指令内部处理,你必须设置指令定义对象的 transclude 属性为 true。你还需要告诉 AngularJS,指令的 HTML 模板中哪一部分需要把 transcluded HTML 包含进来。你可以通过插入 ng-transclude 属性 (实际上,是一个指令) 到 HTML 模板的 HTML 元素中,标记你想要添加 transcluded HTML 的元素。

下面是一个 AngularJS 指令,演示如何使用 transclusion:

<!-- lang: js -->
<mytransclude>This is a transcluded directive {{firstName}}</mytransclude>
<script>
  myapp = angular.module("myapp", []);
  myapp.directive('mytransclude', function() {
    var directive = {};
    directive.restrict = 'E'; /* restrict this directive to elements */
    directive.transclude = true;
    directive.template = "<div class='myTransclude' ng-transclude></div>";
    return directive;
  });
  myapp.controller("MyController", function($scope, $http) {
    $scope.firstName = "Jakob";
  });
</script>

注意 <mytransclude> 元素内的 HTML。这部分 HTML 代码包含了内插指令 {{firstName}}。我们希望 AngularJS 来为我们处理这部分 HTML,让内插指令执行。为了实现这个目的,我在指令定义对象中把 transclude 设置为 true。我还在 HTML 模板中用到了 ng-transclude 属性。这个属性告诉 AngularJS 什么元素需要插入到 transcluded HTML。

1: 说实话,我没看懂那个定义,说的太TM难懂了,而且我好不爽写代码没输出的教程。只好自己动手做做例子。我觉得其实应该是这样的,把目标元素内容作为一个整体,拿到 HTML 模板中来,添加到 ng-transclude 指定的标签下。这个处理,我觉得应该就是叫做 transcluded。比如说刚才的例子(有些 bug,自己修正一下),因为 directive.transclude = true; ,所以原来 <mytransclude> 元素内的 HTML:

<!-- lang: js -->
This is a transcluded directive {{firstName}}

在激活指令 'mytransclude' 的时候,会被拿到 'mytransclude' 指令的模板中来,放到被 ng-transclude 指定的

<!-- lang: js -->
"<div class='myTransclude' ng-transclude></div>"

中。于是最终输出的结果应该是:

<!-- lang: js -->
<mytransclude>
  <div class='myTransclude' ng-transclude>
    <span class="ng-scope ng-binding">This is a transcluded directive Jakob</span>
  </div>
</mytransclude>

希望本文所述对大家AngularJS程序设计有所帮助。

Javascript 相关文章推荐
基于javascript实现单选及多选的向右和向左移动实例
Jul 25 Javascript
JavaScript的jQuery库中ready方法的学习教程
Aug 14 Javascript
使用CDN和AJAX加速WordPress中jQuery的加载
Dec 05 Javascript
浅析Javascript中bind()方法的使用与实现
Apr 29 Javascript
基于JS组件实现拖动滑块验证功能(代码分享)
Nov 18 Javascript
微信小程序 radio单选框组件详解及实例代码
Jan 10 Javascript
javascript设计模式之模块模式学习笔记
Feb 15 Javascript
手把手搭建安装基于windows的Vue.js运行环境
Jun 12 Javascript
vue实现点击选中,其他的不选中方法
Sep 05 Javascript
js实现继承的方法及优缺点总结
May 08 Javascript
Angular8 简单表单验证的实现示例
Jun 03 Javascript
解决ant design vue中树形控件defaultExpandAll设置无效的问题
Oct 26 Javascript
Node.js中process模块常用的属性和方法
Dec 13 #Javascript
Angular ng-repeat遍历渲染完页面后执行其他操作详细介绍
Dec 13 #Javascript
node.js 和HTML5开发本地桌面应用程序
Dec 13 #Javascript
AngularJS中$apply方法和$watch方法用法总结
Dec 13 #Javascript
vue.js学习之递归组件
Dec 13 #Javascript
AngularJS过滤器filter用法总结
Dec 13 #Javascript
ES6通过babel转码使用webpack使用import关键字
Dec 13 #Javascript
You might like
PHP实现的迪科斯彻(Dijkstra)最短路径算法实例
2017/09/16 PHP
php大小写转换函数(strtolower、strtoupper)用法介绍
2017/11/17 PHP
php实现获取农历(阴历)、节日、节气的类与用法示例
2017/11/20 PHP
Laravel框架模板继承操作示例
2018/06/11 PHP
Apply an AutoFormat to an Excel Spreadsheet
2007/06/12 Javascript
js 数组实现一个类似ruby的迭代器
2009/10/27 Javascript
js确定对象类型方法
2012/03/30 Javascript
JavaScript制作windows经典扫雷小游戏
2015/03/31 Javascript
js实现获取div坐标的方法
2015/11/16 Javascript
jQuery CSS3相结合实现时钟插件
2016/01/08 Javascript
js 递归和定时器的实例解析
2017/02/03 Javascript
利用vue组件自定义v-model实现一个Tab组件方法示例
2017/12/06 Javascript
nodejs简单访问及操作mysql数据库的方法示例
2018/03/15 NodeJs
详解Vue2.0组件的继承与扩展
2018/11/23 Javascript
浅谈redux, koa, express 中间件实现对比解析
2019/05/23 Javascript
Vue 权限控制的两种方法(路由验证)
2019/08/16 Javascript
Javascript异步编程async实现过程详解
2020/04/02 Javascript
详解Vue中的watch和computed
2020/11/09 Javascript
[03:16]DOTA2完美大师赛主赛事首日集锦
2017/11/23 DOTA
Python 随机生成中文验证码的实例代码
2013/03/20 Python
Pandas探索之高性能函数eval和query解析
2017/10/28 Python
django上传图片并生成缩略图方法示例
2017/12/11 Python
Python数据分析之双色球统计单个红和蓝球哪个比例高的方法
2018/02/03 Python
对Python中gensim库word2vec的使用详解
2018/05/08 Python
Python实现截取PDF文件中的几页代码实例
2019/03/11 Python
记录一下scrapy中settings的一些配置小结
2020/09/28 Python
MoviePy常用剪辑类及Python视频剪辑自动化
2020/12/18 Python
纯CSS3+DIV实现小三角形边框效果的示例代码
2020/08/03 HTML / CSS
美国创意之家:BulbHead
2017/07/12 全球购物
车间组长岗位职责
2013/12/20 职场文书
年终晚会主持词
2014/03/25 职场文书
爱的奉献演讲稿
2014/09/10 职场文书
领导班子在批评与自我批评座谈会上的发言
2014/09/28 职场文书
教师党员自我评议不足范文
2014/10/19 职场文书
Nginx工作模式及代理配置的使用细节
2022/03/21 Servers
Python通过loop.run_in_executor执行同步代码 同步变为异步
2022/04/11 Python