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转换字符串为dom对象(字符串动态创建dom)
May 10 Javascript
Jquery遍历节点的方法小集
Jan 22 Javascript
把字符串按照特定的字母顺序进行排序的js代码
Jan 28 Javascript
使用AngularJS创建自定义的过滤器的方法
Jun 18 Javascript
2016年最热门的15 款代码语法高亮工具,美化你的代码
Jan 06 Javascript
详解使用Vue Router导航钩子与Vuex来实现后退状态保存
Sep 11 Javascript
node.js使用fs读取文件出错的解决方案
Oct 23 Javascript
js刷新页面location.reload()用法详解
Dec 09 Javascript
JS实现旋转木马轮播图
Jan 01 Javascript
JavaScript实现横版菜单栏
Mar 17 Javascript
jQuery实现雪花飘落效果
Aug 02 jQuery
Vue+Element ui 根据后台返回数据设置动态表头操作
Sep 21 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
40年前的这部特摄片恐龙特级克塞号80后的共同回忆
2020/03/08 日漫
yii操作cookie实例简介
2014/07/09 PHP
PHP处理Oracle的CLOB实例
2014/11/03 PHP
thinkphp多表查询两表有重复相同字段的完美解决方法
2016/09/22 PHP
PHP jpgraph库的配置及生成统计图表:折线图、柱状图、饼状图
2017/05/15 PHP
thinkPHP3.2使用RBAC实现权限管理的实现
2019/08/27 PHP
php 多进程编程父进程的阻塞与非阻塞实例分析
2020/02/22 PHP
jquery新的绑定事件机制on方法的使用方法
2014/04/15 Javascript
JavaScript两种跨域技术全面介绍
2014/04/16 Javascript
用Jquery.load载入页面后样式没了页面混乱的解决方法
2014/10/20 Javascript
JavaScript获得表单target属性的方法
2015/04/02 Javascript
浅谈javascript的分号的使用
2015/05/12 Javascript
jquery实现Slide Out Navigation滑出式菜单效果代码
2015/09/07 Javascript
Vue.js第四天学习笔记(组件)
2016/12/02 Javascript
详解angular2采用自定义指令(Directive)方式加载jquery插件
2017/02/09 Javascript
jQuery使用bind函数实现绑定多个事件的方法
2017/10/11 jQuery
vue进入页面时滚动条始终在底部代码实例
2019/03/26 Javascript
微信小程序实现带放大效果的轮播图
2020/05/26 Javascript
vue使用vue-quill-editor富文本编辑器且将图片上传到服务器的功能
2021/01/13 Vue.js
Python多线程同步Lock、RLock、Semaphore、Event实例
2014/11/21 Python
python基础教程之Filter使用方法
2017/01/17 Python
树莓派实现移动拍照
2019/06/22 Python
python将邻接矩阵输出成图的实现
2019/11/21 Python
django-csrf使用和禁用方式
2020/03/13 Python
Python爬虫requests库多种用法实例
2020/05/28 Python
用Python 执行cmd命令
2020/12/18 Python
关于多种方式完美解决Python pip命令下载第三方库的问题
2020/12/21 Python
全球领先的鞋类零售商:The Walking Company
2016/07/21 全球购物
党员年终民主评议的自我评价
2013/11/05 职场文书
拓展培训心得体会
2014/01/04 职场文书
毕业自我鉴定怎么写
2014/03/25 职场文书
倡议书范文
2014/04/16 职场文书
心得体会的写法
2014/09/05 职场文书
我的职业生涯规划:打造自己的运动帝国
2014/09/18 职场文书
工作作风懒散检讨书
2014/10/29 职场文书
餐饮店长岗位职责
2015/04/14 职场文书