理解AngularJs指令


Posted in Javascript onDecember 10, 2015

对于指令,可以把它简单的理解成在特定DOM元素上运行的函数,指令可以扩展这个元素的功能。

首先来看个完整的参数示例再来详细的介绍各个参数的作用及用法:

angular.module('myApp', []) 
.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、restrict[string]

restrict是一个可选的参数。用于指定该指令在DOM中以何种形式被声明。默认值是A,即以属性的形式来进行声明。
可选值如下:
E(元素)

<my-directive></my-directive>
A(属性,默认值)

<div my-directive="expression"></div>
C(类名)

<div class="my-directive:expression;"></div>
M(注释)

<--directive:my-directive expression-->
一般考虑到浏览器的兼容性,强烈建议使用默认的属性就可以即即以属性的形式来进行声明。最后一种方式建议再不要求逼格指数的时候千万不要用。

Code:

angular.module('app',[])
  .directive('myDirective', function () {
      return { 
        restrict: 'E', 
        template: '<a href="http://www.baidu.com">百度</a>' 
      };
    })
HtmlCode:
 <my-directive></my-directive>

 效果:理解AngularJs指令

2、priority[int]

大多数指令会忽略这个参数,使用默认值0,但也有些场景设置高优先级是非常重要甚至是必须的。例如,ngRepeat将这个参数设置为1000,这样就可以保证在同一元素上,它总是在其他指令之前被调用。

3、terminal[bool]

这个参数用来停止运行当前元素上比本指令优先级低的指令。但同当前指令优先级相同的指令还是会被执行。
例如:ngIf的优先级略高于ngView(它们操控的实际就是terminal参数),如果ngIf的表达式值为true,ngView就可以被正常执行,但如果ngIf表达式的值为false,由于ngView的优先级较低就不会被执行。

4、template[string or function]

template参数是可选的,必须被设置为以下两种形式之一:

  • 一段HTML文本;
  • 一个可以接受两个参数的函数,参数为tElement和tAttrs,并返回一个代表模板的字符串。tElement和tAttrs中的t代表template,是相对于instance的。

首先演示下第二种用法:

angular.module('app',[])
  .directive('myDirective', function () {
      return { 
        restrict: 'EAC', 
        template: function (elem, attr) {
          return "<a href='" + attr.value + "'>" + attr.text + "</a>";
        }
    };
  })

HtmlCode:(效果同上,不做演示了)

<my-directive value="http://www.baidu.com" text="百度"></my-directive>
    <div my-directive
       value="http://www.baidu.com"
       text="百度"></div>

5、templateUrl[string or function]

templateUrl是可选的参数,可以是以下类型:

  • 一个代表外部HTML文件路径的字符串;
  • 一个可以接受两个参数的函数,参数为tElement和tAttrs,并返回一个外部HTML文件路径的字符串。

无论哪种方式,模板的URL都将通过ng内置的安全层,特别是$getTrustedResourceUrl,这样可以保护模板不会被不信任的源加载。 默认情况下,调用指令时会在后台通过Ajax来请求HTML模板文件。加载大量的模板将严重拖慢一个客户端应用的速度。为了避免延迟,可以在部署应用之前对HTML模板进行缓存。

Code:

angular.module('app',[])
  .directive('myDirective', function () {
      return { 
        restrict: 'AEC', 
        templateUrl: function (elem, attr) {
          return attr.value + ".html"; //当然这里我们可以直接指定路径,同时在模板中可以包含表达式
        }
    };
  })

6、replace[bool]

replace是一个可选参数,如果设置了这个参数,值必须为true,因为默认值为false。默认值意味着模板会被当作子元素插入到调用此指令的元素内部,
例如上面的示例默认值情况下,生成的html代码如下:

<my-directive value="http://www.baidu.com" text="百度"><a href="http://www.baidu.com">百度</a></my-directive>

如果设置replace=true

<a href="http://www.baidu.com" value="http://www.baidu.com" text="百度">百度</a>

据我观察,这种效果只有设置restrict="E"的情况下,才会表现出实际效果。

介绍完基本的指令参数后,就要涉及到更重要的作用域参数了...

7、scope参数[bool or object]

 scope参数是可选的,可以被设置为true或一个对象。默认值是false。

如果一个元素上有多个指令使用了隔离作用域,其中只有一个可以生效。只有指令模板中的根元素可以获得一个新的作用域。因此,对于这些对象来说scope默认被设置为true。内置指令ng-controller的作用,就是从父级作用域继承并创建一个新的子作用域。它会创建一个新的从父作用域继承而来的子作用域。这里的继承就不在赘述,和面向对象中的继承基本是一直的。

 首先我们来分析一段代码:

<div ng-app="app" ng-init="name= '祖父'">
      <div ng-init="name='父亲'">
        第一代:{{ name }}
        <div ng-init="name= '儿子'" ng-controller="SomeController">
          第二代: {{ name }}
          <div ng-init="name='孙子'">
            第三代: {{ name }}
          </div>
        </div>
      </div>
    </div>

我们发现第一代,我们初始化name为父亲,但是第二代和第三代其实是一个作用域,那么他们的name其实是一个对象,因此出现的效果如下:

第一代:父亲
第二代: 孙子
第三代: 孙子
我们在修改一下代码,把第三代隔离开来再看看效果:

 

<div ng-app="app"ng-init="name= '祖父'">
      <div ng-init="name='父亲'">
        第一代:{{ name }}
        <div ng-init="name= '儿子'" ng-controller="SomeController">
          第二代: {{ name }}
          <div ng-init="name='孙子'" ng-controller="SecondController">
            第三代: {{ name }}
          </div>
        </div>
      </div>
    </div>

JsCode:

angular.module('app', [])
    .controller('SomeController',function($scope) {
      
    })
    .controller('SecondController', function ($scope) {
    
  })

效果如下:

第一代:父亲
第二代: 儿子
第三代: 孙子
在修改下代码来看看继承:

<div ng-app="app"ng-init="name= '祖父的吻'">
      <div>
        第一代:{{ name }}
        <div ng-controller="SomeController">
          第二代: {{ name }}
          <div ng-controller="SecondController">
            第三代: {{ name }}
          </div>
        </div>
      </div>
    </div>

效果如下:

第一代:祖父的吻
第二代: 祖父的吻
第三代: 祖父的吻

如果要创建一个能够从外部原型继承作用域的指令,将scope属性设置为true,简单来说就是可继承的隔离,即不能反向影响父作用域。

 再来看个例子:

angular.module('myApp', [])
    .controller('MainController', function ($scope) {
    })
    .directive('myDirective', function () {
      return {
        restrict: 'A',
        scope:false,//切换为{},true测试
        priority: 100,
        template: '<div>内部:{{ myProperty }}<input ng-model="myProperty"/></div>'
      };
    });

Html代码:

<div ng-controller='MainController' ng-init="myProperty='Hello World!'">
    外部: {{ myProperty}}
    <input ng-model="myProperty" />
    <div my-directive></div>
  </div>

当我们改变scope的值我们会发现

false:继承但不隔离

理解AngularJs指令

true:继承并隔离

理解AngularJs指令

{}:隔离且不继承

理解AngularJs指令

 8、transclude

transclude是一个可选的参数。默认值是false。嵌入通常用来创建可复用的组件,典型的例子是模态对话框或导航栏。我们可以将整个模板,包括其中的指令通过嵌入全部传入一个指令中。指令的内部可以访问外部指令的作用域,并且模板也可以访问外部的作用域对象。为了将作用域传递进去,scope参数的值必须通过{}或true设置成隔离作用域。如果没有设置scope参数,那么指令内部的作用域将被设置为传入模板的作用域。

只有当你希望创建一个可以包含任意内容的指令时,才使用transclude: true。

 我们来看两个例子-导航栏:

<div side-box title="TagCloud">
    <div class="tagcloud">
      <a href="">Graphics</a>
      <a href="">ng</a>
      <a href="">D3</a>
      <a href="">Front-end</a>
      <a href="">Startup</a>
    </div>
  </div>

JsCode:

angular.module('myApp', []) 
 .directive('sideBox', function() { 
   return { 
     restrict: 'EA', 
     scope: { 
       title: '@' 
     }, 
     transclude: true, 
     template: '<div class="sidebox"><div class="content"><h2 class="header">' +
       '{{ title }}</h2><span class="content" ng-transclude></span></div></div>' 
     }; 
  });

这段代码告诉ng编译器,将它从DOM元素中获取的内容放到它发现ng-transclude指令的地方。

再来你看个官网的例子:

angular.module('docsIsoFnBindExample', [])
 .controller('Controller', ['$scope', '$timeout', function($scope, $timeout) {
  $scope.name = 'Tobias';
  $scope.hideDialog = function () {
   $scope.dialogIsHidden = true;
   $timeout(function () {
    $scope.dialogIsHidden = false;
   }, 2000);
  };
 }])
 .directive('myDialog', function() {
  return {
   restrict: 'E',
   transclude: true,
   scope: {
    'close': '&onClose'
   },
   templateUrl: 'my-dialog-close.html'
  };
 });

my-dialog-close.html

<div class="alert">
 <a href class="close" ng-click="close()">×</a>
 <div ng-transclude></div>
</div>

index.html

<div ng-controller="Controller">
 <my-dialog ng-hide="dialogIsHidden" on-close="hideDialog()">
  Check out the contents, {{name}}!
 </my-dialog>
</div>

如果指令使用了transclude参数,那么在控制器无法正常监听数据模型的变化了。建议在链接函数里使用$watch服务。

 9、controller[string or function]

 controller参数可以是一个字符串或一个函数。当设置为字符串时,会以字符串的值为名字,来查找注册在应用中的控制器的构造函数.

angular.module('myApp', []) 
.directive('myDirective', function() { 
restrict: 'A', 
controller: 'SomeController' 
})

可以在指令内部通过匿名构造函数的方式来定义一个内联的控制器

angular.module('myApp',[]) 
.directive('myDirective', function() { 
restrict: 'A', 
controller: 
function($scope, $element, $attrs, $transclude) { 
// 控制器逻辑放在这里
} 
});

我们可以将任意可以被注入的ng服务注入到控制器中,便可以在指令中使用它了。控制器中也有一些特殊的服务可以被注入到指令当中。这些服务有:

1. $scope

与指令元素相关联的当前作用域。
2. $element
当前指令对应的元素。
3. $attrs
由当前元素的属性组成的对象。

<div id="aDiv"class="box"></div>
具有如下的属性对象:
{ 
id: "aDiv", 
class: "box" 
}

4. $transclude
嵌入链接函数会与对应的嵌入作用域进行预绑定。transclude链接函数是实际被执行用来克隆元素和操作DOM的函数。

angular.module('myApp',[])
 .directive('myLink', function () {
   return {
     restrict: 'EA',
     transclude: true,
     controller:
     function ($scope, $element,$attrs,$transclude) {
       $transclude(function (clone) {       
         var a = angular.element('<a>');
         a.attr('href', $attrs.value);
         a.text(clone.text());
         $element.append(a);
       });
     }
   };
 });

html 

<my-link value="http://www.baidu.com">百度</my-link>
<div my-link value="http://www.google.com">谷歌</div>

仅在compile参数中使用transcludeFn是推荐的做法。link函数可以将指令互相隔离开来,而controller则定义可复用的行为。如果我们希望将当前指令的API暴露给其他指令使用,可以使用controller参数,否则可以使用link来构造当前指令元素的功能性(即内部功能)。如果我们使用了scope.$watch()或者想要与DOM元素做实时的交互,使用链接会是更好的选择。使用了嵌入,控制器中的作用域所反映的作用域可能与我们所期望的不一样,这种情况下,$scope对象无法保证可以被正常更新。当想要同当前屏幕上的作用域交互时,可以使用传入到link函数中的scope参数。

10、controllerAs[string]

controllerAs参数用来设置控制器的别名,这样就可以在视图中引用控制器甚至无需注入$scope。

<div ng-controller="MainController as main">
    <input type="text" ng-model="main.name" />
    <span>{{ main.name }}</span>
  </div>

JsCode:

angular.module('myApp',[])
  .controller('MainController', function () {
    this.name = "Halower";
  });

控制器的别名使路由和指令具有创建匿名控制器的强大能力。这种能力可以将动态的对象创建成为控制器,并且这个对象是隔离的、易于测试。

11、 require[string or string[]]

 require为字符串代表另外一个指令的名字。require会将控制器注入到其所指定的指令中,并作为当前指令的链接函数的第四个参数。字符串或数组元素的值是会在当前指令的作用域中使用的指令名称。在任何情况下,ng编译器在查找子控制器时都会参考当前指令的模板。

如果不使用^前缀,指令只会在自身的元素上查找控制器。指令定义只会查找定义在指令作当前用域中的ng-model=""
如果使用?前缀,在当前指令中没有找到所需要的控制器,会将null作为传给link函数的第四个参数。
如果添加了^前缀,指令会在上游的指令链中查找require参数所指定的控制器。
 如果添加了?^ 将前面两个选项的行为组合起来,我们可选择地加载需要的指令并在父指令链中进行查找
如果没有任何前缀,指令将会在自身所提供的控制器中进行查找,如果没有找到任何控制器(或具有指定名字的指令)就抛出一个错误
12、compile【object or function】

compile选项本身并不会被频繁使用,但是link函数则会被经常使用。本质上,当我们设置了link选项,实际上是创建了一个postLink() 链接函数,以便compile() 函数可以定义链接函数。通常情况下,如果设置了compile函数,说明我们希望在指令和实时数据被放到DOM中之前进行DOM操作,在这个函数中进行诸如添加和删除节点等DOM操作是安全的。

compile和link选项是互斥的。如果同时设置了这两个选项,那么会把compile所返回的函数当作链接函数,而link选项本身则会被忽略。

编译函数负责对模板DOM进行转换。链接函数负责将作用域和DOM进行链接。 在作用域同DOM链接之前可以手动操作DOM。在实践中,编写自定义指令时这种操作是非常罕见的,但有几个内置指令提供了这样的功能。

13、link

compile: function(tEle, tAttrs, transcludeFn) {
 //todo:
 return function(scope, ele, attrs) {
 // 链接函数
 };

链接函数是可选的。如果定义了编译函数,它会返回链接函数,因此当两个函数都定义时,编译函数会重载链接函数。如果我们的指令很简单,并且不需要额外的设置,可以从工厂函数(回调函数)返回一个函数来代替对象。如果这样做了,这个函数就是链接函数。

14、ngModel

它提供更底层的API来处理控制器内的数据,这个API用来处理数据绑定、验证、 CSS更新等不实际操作DOM的事情,ngModel 控制器会随 ngModel 被一直注入到指令中,其中包含了一些方法。为了访问ngModelController必须使用require设置.

ngModelController常用的元素如下:

 1).为了设置作用域中的视图值,需要调用 ngModel.$setViewValue() 函数。
$setViewValue() 方法适合于在自定义指令中监听自定义事件(比如使用具有回调函数的jQuery插件),我们会希望在回调时设置$viewValue并执行digest循环。

angular.module('myApp')
    .directive('myDirective', function() {
      return {
        require: '?ngModel',
        link: function(scope, ele, attrs, ngModel) {
          if (!ngModel) return;
          $(function() {
            ele.datepicker({
               //回调函数
              onSelect: function(date) {
                // 设置视图和调用 apply
                scope.$apply(function() {
                  ngModel.$setViewValue(date);
                });
              }
            });
          });
        }
      };
    });

2).$render方法可以定义视图具体的渲染方式
3).属性(这里属性可以参考前一篇文章末尾进行学习)

以上就是关于AngularJs指令的全部内容,希望对大家的学习有所帮助。

Javascript 相关文章推荐
Javacript实现颜色梯度变化和渐变的效果代码
May 31 Javascript
js 页面元素的几个用法总结
Nov 18 Javascript
深入解析jQuery中Deferred的deferred.promise()方法
May 03 Javascript
浅谈js构造函数的方法与原型prototype
Jul 04 Javascript
基于js里调用函数时,函数名带括号和不带括号的区别
Jul 28 Javascript
浅谈JavaScript 函数参数传递到底是值传递还是引用传递
Aug 23 Javascript
jQuery中table数据的值拷贝和拆分
Mar 19 Javascript
Spring shiro + bootstrap + jquery.validate 实现登录、注册功能
Jun 02 jQuery
vue小图标favicon不显示的解决方案
Sep 19 Javascript
Angular中使用MathJax遇到的一些问题
Dec 15 Javascript
JS 获取文件后缀,判断文件类型(比如是否为图片格式)
May 09 Javascript
Vue通过懒加载提升页面响应速度
May 10 Vue.js
详解AngularJS实现表单验证
Dec 10 #Javascript
jquery实现鼠标悬浮停止轮播特效
Aug 20 #Javascript
JavaScript动态创建form表单并提交的实现方法
Dec 10 #Javascript
jquery实现定时自动轮播特效
Dec 10 #Javascript
jQuery手动点击实现图片轮播特效
Apr 20 #Javascript
javascript实现unicode与ASCII相互转换的方法
Dec 10 #Javascript
解决angular的post请求后SpringMVC后台接收不到参数值问题的方法
Dec 10 #Javascript
You might like
用Php实现链结人气统计
2006/10/09 PHP
php中配置文件操作 如config.php文件的读取修改等操作
2012/07/07 PHP
提高PHP性能的编码技巧以及性能优化详细解析
2013/08/24 PHP
destoon实现调用热门关键字的方法
2014/07/15 PHP
Linux系统下PHP-FPM的安装和配置教程
2015/08/17 PHP
浅谈PHP中try{}catch{}的使用方法
2016/12/09 PHP
laravel框架如何设置公共头和公共尾
2019/10/22 PHP
自适应图片大小的弹出窗口
2006/07/27 Javascript
js 禁用只读文本框获得焦点时的退格键
2010/04/25 Javascript
利用jQuery接受和处理xml数据的代码(.net)
2011/03/28 Javascript
js查错流程归纳
2012/05/04 Javascript
jquery特效 幻灯片效果示例代码
2013/07/16 Javascript
javascript上传图片前预览图片兼容大多数浏览器
2013/10/25 Javascript
ff chrome和ie下全局动态定位的异同及全局高度的取法
2014/06/30 Javascript
nodejs中模块定义实例详解
2017/03/18 NodeJs
xmlplus组件设计系列之按钮(2)
2017/04/26 Javascript
使用 Node.js 对文本内容分词和关键词抽取
2017/05/27 Javascript
javascript 封装Date日期类实例详解
2017/05/28 Javascript
微信小程序日历组件calendar详解及实例
2017/06/08 Javascript
jQuery EasyUI开发技巧总结
2017/09/26 jQuery
js实现列表向上无限滚动
2020/01/13 Javascript
python实现的登录和操作开心网脚本分享
2014/07/09 Python
python任务调度实例分析
2015/05/19 Python
python中subprocess批量执行linux命令
2018/04/27 Python
对python的unittest架构公共参数token提取方法详解
2018/12/17 Python
html5记忆翻牌游戏实现思路及代码
2013/07/25 HTML / CSS
调用HTML5的Canvas API绘制图形的快速入门指南
2016/06/17 HTML / CSS
旺仔牛奶广告词
2014/03/20 职场文书
马丁路德金演讲稿
2014/05/19 职场文书
科长个人四风问题整改措施思想汇报
2014/10/13 职场文书
学校机关党总支领导班子整改工作方案
2014/10/26 职场文书
2015元旦晚会主持人开场白+结束语
2014/12/14 职场文书
合理化建议书范文
2015/09/14 职场文书
2016年春节慰问信息大全
2015/11/30 职场文书
餐饮行业关注的9大营销策略
2019/08/26 职场文书
python 开心网和豆瓣日记爬取的小爬虫
2021/05/29 Python