AngularJS开发指南4:指令的详解

指令是我们用来扩展浏览器能力的技术之一。在DOM编译期间,和HTML元素关联着的指令会被检测到,并且被执行。这使得指令可以为DOM指定行为,或者改变它。

AngularJS有一套完整的、可扩展的、用来帮助web应用开发的指令集,它使得HTML可以转变成“特定领域语言(DSL)”。

指令可以做为HTML中的元素名,属性名,类名,或者注释。下面是一些等效调用myDir指令的例子:

<span my-dir="exp"></span>
<span class="my-dir: exp;"></span>
<my-dir></my-dir>
<!-- directive: my-dir exp -->

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);

你可能会疑惑为什么编译过程和链接过程要分离。要明白其中的原因,你可以先看下面这个带有“重复指令”的例子:

Hello {{user}}, you have these actions:
<ul>
  <li ng-repeat="action in user.actions">
    {{action.description}}
  </li>
</ul>

当上面的例子被编译后,编译器会遍历所有节点来寻找指令。例如{{user}}是一个替换式指令,ngRepeat是另一个指令。但是ngRepeat有一个难题。他需要为user.actions中的每一个action 构造一个li。这意味着它先要保存一个“干净”的li元素来用作克隆,然后等新的action插入进来时,克隆这个干净的li元素,把克隆出来的li元素插入到ul中。但是仅仅克隆li的话工作还没完。他还需要编译这个li才能把其中的{{action.descriptions}}的替换式替换成相应作用域下的值。我们可以用一个简单的方法来克隆和插入li元素,然后编译它。但是要编译每一个li的话,速度会很慢, 因为编译的工程需要我们遍历DOM树。如果我们在一个需要循环100次循环体内执行编译的话,性能问题就会马上凸现出来。

而我们的解决方案就是将编译工程分为两个阶段。编译阶段将指令识别出来并按优先级排序,链接阶段将作用域中的实例和li进行链接。

ngRepeat 会阻止li子元素{{action.description}}的编译,取而代之的是 ngRepeat指令会单独对li进行编译,编译时,会生成多个li元素组成的模板。这个编译结束后会生成一个链接函数,这个函数在执行时,会对整个模板进行编译,然后为每一个li元素创建一个新的作用域,并把它和对应的作用域链接上。这里,我们只需要编译一次(对模板进行一次统一的编译就行了),只是在链接的时候,需要链接多次,而链接操作并不消耗性能。

如何写一个指令

var myModule = angular.module(...);
myModule.directive(‘directiveName‘, function factory(injectables) {
  var directiveDefinitionObject = {
    priority: 0,
    template: ‘<div></div>‘,
    templateUrl: ‘directive.html‘,
    replace: false,
    transclude: false,
    restrict: ‘A‘,
    scope: false,
    compile: function compile(tElement, tAttrs, transclude) {
      return {
        pre: function preLink(scope, iElement, iAttrs, controller) { ... },
        post: function postLink(scope, iElement, iAttrs, controller) { ... }
      }
    },
    link: function postLink(scope, iElement, iAttrs) { ... }
  };
  return directiveDefinitionObject;
});

大部分情况下你不需要控制这么多细节,要简化上面的代码,我们首先需要依赖基本选项的默认值。如果使用默认值的话,上面的代码可以简化成:

var myModule = angular.module(...);
myModule.directive(‘directiveName‘, function factory(injectables) {
  var directiveDefinitionObject = {
    compile: function compile(tElement, tAttrs) {
      return function postLink(scope, iElement, iAttrs) { ... }
    }
  };
  return directiveDefinitionObject;
});

由于大部分的指令只关心实例,并不需要将模板进行变形,所以我们还可以简化成:

var myModule = angular.module(...);
myModule.directive(‘directiveName‘, function factory(injectables) {
  return function postLink(scope, iElement, iAttrs) { ... }
});

上面代码中的factory函数,我们叫工厂函数,它是用来创建指令的。它只会被调用一次:就是当编译器第一次匹配到相应指令的时候,你可以在其中进行任何初始化的工作。调用它时使用的是 $injector.invoke , 所以它遵循所有注入器的规则。

指令定义对象,也就是上面代码中的directiveDefinitionObject对象,给编译器提供了生成指令需要的细节。这个对象的属性有:

  • 名称name - 当前作用域的名称。
  • 优先级priority - 当一个DOM上有多个指令时,就会需要指定指令执行的顺序。 这个优先级就是用来在执行指令的compile函数前,先排序的。高优先级的先执行。
  • terminal - 如果被设置为true,那么该指令就会在同一个DOM的指令集中最后被执行。
  • 作用域scope- 如果被定义成:
    • true - 那么就会为当前指令创建一个新的作用域。如果有多个在同一个DOM上的指令要求创建新作用域,那么只有一个新的会被创建。 这一创建新作用域的规则不适用于模板的根节点,因为模板的根节点总是会得到一个新的作用域。
    • {},对象哈希 - 那么一个新的“孤立的”作用域就会被创建。这个“孤立的”作用域区别于一般作用域的地方在于,它不会以原型继承的方式直接继承自父作用域。这对于创建可重用的组件是非常有用的,因为可重用的组件一般不应该读或写父作用域的数据。 这个“孤立的”作用域使用一个对象哈希来表示,这个哈希定义了一系列本地作用域属性,这些属性的值可以有以下几种方式。
      • @ 或 @attr - 将本地作用域成员和DOM属性绑定。绑定结果总是一个字符串,因为DOM的属性就是字符串。那@和@attr的区别是什么呢?举个例子:@的方式:<widget flater="hello {{name}}"> 和作用域对象: { flater:‘@‘ }。当DOM属性flater的name值改变的时候, 作用域中的flater也会改变,因为本地作用域成员flater绑定了此指令widget的DOM属性flater,同时DOM属性flater的name的值是从父作用域中读来的,也就是说父作用域有name属性。@attr的方式:<widget my-attr="hello {{name}}"> 和作用域对象: { localName:‘@myAttr‘ }。当name值改变的时候, 作用域中的LocalName也会改变。它的特点是:父作用域传递一个属性给子作用域。
      • = 或 =expression - 在本地作用域属性和父作用域属性间建立一个双向的绑定。 =的方式: <widget flater="parentModel"> 和作用域对象: { flater:‘=‘ }, 本地属性flater会反映父作用域中parentModel的值,flater和parentModel的任一方改变都会影响对方,原理就是:flater是子作用域的属性,parentModel是父作用域的属性,它们进行了双向绑定,一方改变,另一方也会改变,它们是通过DOM属性flater联系在一起的。=expression的方式: <widget my-attr="parentModel"> 和作用域对象: { localModel:‘=myAttr‘ }, 本地属性localModel会反映父作用域中parentModel的值。它的特点:父作用域下的属性跟子作用域下的属性进行双向绑定。
      • & 或 &attr - 比如:<widget flater="sayHello(name)">和作用域对象:{flater:‘&‘},这时本地属性flater绑定了父作用域下的sayHello方法。这时,你在子作用域下调用flater方法其实就是调用父作用域下的sayHello方法。如果需要传参的话,是通过flater({name:"chaojidan"})。它的特点:父作用域下传递一个函数给子作用域。
  • controller - 这个是指令内部的controller,跟angular中的controller不一样。它的作用是暴露此指令的一些方法给其他指令使用。这个控制器函数是在预编译阶段被执行的,并且它是共享的,这就使得指令间可以互相交流来扩大自己的能力。
  • require - 请求将另一个指令,假设为direct2,中的内部controller作为参数传入到当前指令的链接函数link中,这样在当前指令的link函数中,就可以调用direct2指令中的内部controller中定义的方法了。 这个请求需要传递被请求指令的名字。如果没有找到,就会触发一个错误。请求的名字可以加上下面两个前缀:
    • ? - 不要触发错误,这只是一个可选的请求。
    • ^ - 没找到的话,在父元素的作用域里面去查找有没有。
  • restrict - EACM中的任意一个字母。它是用来限制指令的声明格式的。如果没有这一项。那就只允许使用属性形式的指令。
    • E - 元素名称:<my-directive></my-directive>
    • A - 属性: <div my-directive="exp"> </div>
    • C - 类名:<div class="my-directive: exp;"></div>
    • M - 注释: <!-- directive: my-directive exp -->
  • 模板template - 将当前的元素替换掉。 这个替换过程会自动将元素的属性和css类名添加到新元素上。
  • 模板地址templateUrl - 和template属性一样,只不过这里指示的是一个模板的URL。因为模板加载是异步的,所有编译和链接都会等到加载完成后再执行。
  • 替换replace - 如果被设置成true,那么页面上指令内部里面的内容会被模板替换。比如:<hello><div>这是指令内部的内容</div></hello>,hello指令内部的div内容将会被模板替换掉。
  • transclude -  如果不想让指令内部的内容被模板替换,可以设置这个值为true。一般情况下需要和ngTransclude指令一起使用。 比如:template:"<div>hello every <div ng-transclude></div></div>",这时,指令内部的内容会嵌入到ng-transclude这个div中。也就是变成了<div>hello every <div>这是指令内部的内容</div></div>
  • 编译compile - 这就是后面将要讲到的编译函数。
  • 链接link - 这就是后面将要讲到的链接函数。

编译函数 Compile function

function compile(tElement, tAttrs, transclude) { ... }

编译函数是用来处理需要修改模板DOM的情况的。因为大部分指令都不需要修改模板,所以这个函数也不常用。需要用到的例子有ngTrepeat,这个是需要修改模板的,还有ngView这个是需要异步载入内容的。编译函数接受以下参数。

  • tElement - template element - 指令所在的元素。对这个元素及其子元素进行变形之类的操作是安全的。
  • tAttrs - template attributes - 这个元素上所有指令声明的属性,这些属性都是在编译函数里共享的。
  • transclude - 一个嵌入的链接函数function(scope, cloneLinkingFn)

注意:在编译函数里面不要进行任何DOM变形之外的操作。 更重要的,DOM监听事件的注册应该在链接函数中做,而不是编译函数中。

编译函数可以返回一个对象或者函数。

  • 返回函数 - 等效于在编译函数不存在时,使用配置对象的link属性注册的链接函数。
  • 返回对象 - 返回一个通过prepost属性注册了函数的对象。参考下面pre-linkingpost-liking函数的解释。

链接函数 Linking function

function link(scope, iElement, iAttrs, controller) { ... }

链接函数负责注册DOM事件和更新DOM。它是在模板被克隆之后执行的,它也是大部分指令逻辑代码编写的地方。

  • scope - 指令需要监听的作用域。
  • iElement - instance element - 指令所在的元素。只有在postLink函数中对元素的子元素进行操作才是安全的,因为那时它们才已经全部链接好。
  • iAttrs - instance attributes - 实例属性,一个标准化的、所有声明在当前元素上的属性列表,这些属性在所有链接函数间是共享的。
  • controller - 控制器实例,也就是当前指令通过require请求的指令direct2内部的controller。比如:direct2指令中的controller:function(){this.addStrength = function(){}},那么,在当前指令的link函数中,你就可以通过controller.addStrength进行调用了。

Pre-linking function 在子元素被链接前执行。不能用来进行DOM的变形,以防链接函数找不到正确的元素来链接。

Post-linking function 所有元素都被链接后执行。

属性 Attributes

The Attributes object属性对象 - 作为参数传递给链接函数和编译函数。这使得下列资源可以被使用。

  • 标准化的属性名: 因为指令的名称,如ngBind可以有很多种变形表示,如ng:bind,或者x-ng-bind,这个对象使得可以用标准的名称获取到相应的属性。
  • 指令间通信:所有指令间共享同一个属性对象的实例,这使得指令可以通过这个属性对象通信。
  • 支持替换式:属性中若包含替换式,那么其他指令能够读到替换式的值。
  • 监视替换式属性:使用$observe,能监视使用了替换式的属性(比如 src="{{bar}}")。这是一种高效的,也是唯一的方法来获取变量的值。因为在链接阶段替换式还没有被替换成值前,所有变量此时是undefined。
 function linkingFn(scope, elm, attrs, ctrl) {
   // get the attribute value
   console.log(attrs.ngModel);
   // change the attribute
   attrs.$set(‘ngModel‘, ‘new value‘);
   // observe changes to interpolated attribute
   attrs.$observe(‘ngModel‘, function(value) {
       console.log(‘ngModel has changed value to ‘ + value);
   });
 }

创建组件

通常需要使用更复杂的DOM结构替换单个指令。这允许指令成为一个可以生成应用程序可重用组件的短标志。

加油!

时间: 2024-11-07 13:28:26

AngularJS开发指南4:指令的详解的相关文章

AngularJS开发指南7:AngularJS本地化,国际化,以及兼容IE低版本浏览器

AngularJS本地化,国际化 国际化,简写为i18n,指的是使产品快速适应不同语言和文化. 本地化,简称l10n,是指使产品在特定文化和语言市场中可用. 对开发者来说,国际化一个应用意味着将所有的文字和其他因地区而异的数据从应用中抽离出来. 本地化意味着为这些抽离的数据和文字提供翻译和转变成本地的格式. 目前,AngularJS支持日期,数字和货币的国际化和本地化. 另外,AngularJS还通过ngPluralize指令支持本地多元化. 所有的AngularJS本地化组件都依赖于$loca

IA-32指令解析详解

IA-32指令解析详解 0x00 前言 这段时间忙于考试,信息论和最优化,还有算法分析,有点让人头大.期间花了几天看SEH机制,能明白个大概,但是对于VC++对于SHE的包装似乎还是不是很明白,发现逆向工程核心原理对于这段写的太简单,至于加密与解密则是模棱两可,软件加密技术内幕倒是详解了,可是太老了,代码又是汇编写的总是编译不通过.真是让人难受!本来想写一篇SEH详解,但是基于以上原因暂时搁置.这两天看了逆向核心原理关于IA-32指令解析的内容,发现还是挺有意思,遂记录下来. 0x01 何谓IA

Angularjs开发指南

Angularjs开发指南:http://docs.ngnice.com/guide Angularjs中文网:http://www.apjs.net/

优佳贝开发优佳贝模式详解

优佳贝开发优佳贝模式详解(微or电 158.1500.1390 小凡团队)优佳贝系统开发,优佳贝模式定制,优佳贝软件开发,优佳贝app开发,优佳贝模式系统开发. 互联网颠覆了传统的商业模式,创造了庞大的商业机遇,更改变了世界财富的分配定律!互联网已经无国界,已经引申到世界每一个角落.互联网与电子商务的结合,必将创造一个又一个新奇迹!在历史发展的当今世界.21世纪,整个世界经济发生了巨大的变化!21世纪更是一个移动互联网经济占据鳌头的市场!谁抓住了互联网市场趋向谁就抓住了,在互联网一体化的今天,信

【10.2.3】ArcGIS Runtime for Android搭建开发环境过程中问题详解

一.Visual Studio Ultimate2012安装过程问题 1.问题描述 安装完成后,您将看到一条消息,指示安装程序已完成,但并不是所有的功能具有已正确安装,以及以下警告消息: Microsoft Web Deploy 3.0 所需的证书不在有效期内根据当前系统时钟或签名文件中的时间戳验证时. 2.解决方案 修改电脑系统时间为2013年7月,断网后重新安装,成功后再联网. Visual Studio Ultimate2012激活密钥:RBCXF-CVBGR-382MK-DFHJ4-C6

awk指令操作详解

记录与字段 awk一次从文件中读取一条记录,并将记录存储在字段变量$0中.记录被分隔为字段比功能存储在$1,$2,...$NF中,(默认使用空格或指标符为分隔符).内建参数NF为记录的字段数. #输出demo.txt 文件的第1个,第2个,第3个,第4个字段 awk '{print $1,$2,$3,$4}' demo.txt #输出demo.txt 文件整行记录 awk '{print $0}' demo.txt #输出demo.txt 该行的字段个数. awk '{print NF}' de

IOS开发学习笔记(1)-----UILabel 详解

1. [代码][C/C++]代码     //创建uilabelUILabel *label1 = [[UILabel alloc] initWithFrame:CGRectMake(20, 40, 280, 80)];//设置背景色label1.backgroundColor = [UIColor grayColor];//设置taglabel1.tag = 91;//设置标签文本label1.text = @"Hello world!";//设置标签文本字体和字体大小label1.

IOS开发学习笔记(2)-----UIButton 详解

1. [代码][C/C++]代码     //这里创建一个圆角矩形的按钮    UIButton *button1 = [UIButton buttonWithType:UIButtonTypeRoundedRect];    //    能够定义的button类型有以下6种,//    typedef enum {//        UIButtonTypeCustom = 0,          自定义风格//        UIButtonTypeRoundedRect,        

AngularJS开发指南:表达式

---恢复内容开始--- 表达式是类似Javascript的代码片段,通常在绑定中用到,写在双大括号中如{{表达式}}.表达式是用$parse方法来处理的. 下面是一些合法的AngularJS表达式 1+2 3*10 | currency user.name AngularJS表达式 与Javascript表达式的比较 你可能会认为AngularJS视图中的表达式就是Javascript表达式,这种认识不完全对,因为AngularJS不会用Javascript的eval()函数去执行表达式. 不

Android开发技巧之viewstub用法详解及实现延迟加载

这一篇是接着上面的include标签的例子来讲的,地址http://blog.csdn.net/jason0539/article/details/26131831 上一篇的布局中间就用了viewstub这个控件,现在来说一下其作用和用法 " ViewStub 是一个不可见的,大小为0的View,最佳用途就是实现View的延迟加载,避免资源浪费,在需要的时候才加载View " 需要注意的是,加载view之后,viewstub本身就会被新加载进来的view替换掉 上代码了,看完就理解了