当前位置: 代码迷 >> .NET相关 >> angular源码分析:$compile服务——指令的编撰
  详细解决方案

angular源码分析:$compile服务——指令的编撰

热度:160   发布时间:2016-04-24 02:38:50.0
angular源码分析:$compile服务——指令的编写

这一期中,我不会分析源码,只是翻译一下"https://docs.angularjs.org/api/ng/service/$compile",当然不是逐字逐句翻译,讲解指令应该如何编写,下一期再接着讲$compile的源码。我觉得,懂得如何使用angular可能对童鞋们更有用。
先说点废话:上一期更新的时间是11月25日,一停就是相隔两周多了。1.是由于公司的网站上线(给公司打个广告(美好学院)[http://www.meihaoxueyuan.com]),2.是由于家里发生了一些事,3.是$compile做为angular的一个关键服务,tmd代码也太复杂了。

$compile

$compile的功能是,将一个html字符串或者一个DOM进行编译,最后返回一个链接函数,这个链接函数可以用于将作用域(Scope)和模版"链接"到一起.编译的过程,其实质是遍历DOM树,匹配和处理DOM上的各种指令的过程.
注意:这个篇文档将深入介绍指令的各种选项,如果只是想通过例子来简单了解指令,可以参考(directive guide)[https://docs.angularjs.org/guide/directive]

全面的指令接口(Comprehensive Directive API)

对于指令(directive)有很多的不同选项.
你可以通过directive定义的工厂函数返回一个"指令定义对象(Directive Definition Object)(用于定义指令的各种属性,如下)",或者仅仅返回一个postLink(所有其他属性将使用默认值);
推荐使用Directive Definition Object形式
这里有一个实例,用Directive Definition Object形式申明的指令

var myModule = angular.module(...);myModule.directive('directiveName', function factory(injectables) {  var directiveDefinitionObject = {    priority: 0,    template: '<div></div>', // or // function(tElement, tAttrs) { ... },    // or    // templateUrl: 'directive.html', // or // function(tElement, tAttrs) { ... },    transclude: false,    restrict: 'A',    templateNamespace: 'html',    scope: false,    controller: function($scope, $element, $attrs, $transclude, otherInjectables) { ... },    controllerAs: 'stringIdentifier',    bindToController: false,    require: 'siblingDirectiveName', // or // ['^parentDirectiveName', '?optionalDirectiveName', '?^optionalParent'],    compile: function compile(tElement, tAttrs, transclude) {      return {        pre: function preLink(scope, iElement, iAttrs, controller) { ... },        post: function postLink(scope, iElement, iAttrs, controller) { ... }      }      // or      // return function postLink( ... ) { ... }    },    // or    // link: {    //  pre: function preLink(scope, iElement, iAttrs, controller) { ... },    //  post: function postLink(scope, iElement, iAttrs, controller) { ... }    // }    // or    // link: function postLink( ... ) { ... }  };  return directiveDefinitionObject;});

注意:没有申明的属性选项将使用默认值,你可以在下面看到他们的默认值
下面是一个可能简单一点的例子:

var myModule = angular.module(...);myModule.directive('directiveName', function factory(injectables) {  var directiveDefinitionObject = {    link: function postLink(scope, iElement, iAttrs) { ... }  };  return directiveDefinitionObject;  // or  // return function postLink(scope, iElement, iAttrs) { ... }});

指令定义对象(Directive Definition Object)

指令定义对象将给编译器提供编译必要的说明。它包含这些属性:

multiElement (多元素)

当这个属性设置为true的时候,html编译器将会在含有directive-name-startdirective-name-end属性的Dom节点间收集Dom节点,被收集到的节点集合将作为一个整体被用作指令的元素(elements).推荐在没有严格行为(如ngClick)且不会操作或者替换子元素的的指令编写时,使用该属性.通过这个属性,我们可以给指令的操作元素制定一个范围,具体怎么用就看想象力了.

priority (优先级)

当大多数情况指令都被定义成只处理一个独立的DOM元素,有时候给指令排一个顺序是很有必要的.这个priority将用于编译函数执行前的指令排序.priority被定义为一个数值,这个数字越大,指令的优先级越高,就越是会被优先执行.Pre-link函数也会按照这个顺序来执行,但是post-link将会按照相反的顺序来执行.如果这个属性没有定义的话,默认值将是0.我们在查看angularjs的api文档中的指令时,可以注意一下每个指令的执行的优先级,比如ngIf的优先级是600,而ngShow的优先级是0,有的时候优先级会很重要.

terminal (终结者,请允许我这么翻译它)

如果这个属性设置为true,所有优先级小于该指令的指令都不会在执行了,注意和该指令优先级相同的元素依然会得到执行.

scope (作用域)

这个scope属性可以设置为true和对象或者falsy值(与false相等的值):

  • falsy:不会为指令建立新的作用域,指令将使用父作用域.
  • true :一个原型将继承父作用域的新的子作用域将会为这个指令建立.如果一个dom元素的多个指令都要求创建新的作用域,只有一个作用域会被创建.新建的作用规则将不再适用于模版的上层元素,因为模版总是新建一个作用域.
  • {...}(一个对象哈希):一个新的"孤立"作用域将会为指令创建."孤立"作用域不同与常规的作用,它不会在原型上继承父作用域.这对于创建一个可重用的组件很有帮助,因为它让指令不会意外的读到或修改到父作用域的数据.

"孤立"作用域对象哈希定义了一个本地作用域的属性集合,这些属性能够从使用指令的元素的属性上派生出.这些本地的属性值,对于模版中的别名很有用.在对象哈希映射的键将会被定义为"孤立作用域"的的属性;映射的值将定义怎么和父作用域绑定到一起,通过匹配在使用的指令的的元素的属性.(为什么,翻译出来就这么绕呢,还是自己英语太搓了?)

  • @ 或者 @attr :绑定一个本地作用域到一个DOM的属性值.这回导致所有得到的值都是一个字符串.如果attr没有,绑定的属性将和本地属性同名.比如,有这样的一个DOM节点:<widget my-attr="hello {{name}}">;并且widget指令中有如此定义scope:{localName:'@myAttr'},那么widget的作用scope的属性localName将是hello {{name}}计算后的值.当name属性的值改变后,localName的值也会相应的改变.name则是从父作用域中获取的.
  • = 或者 =attr :设置双向绑定在作用域和父作用域间.如果attr没有定义,那么绑定的作用域名和使用指令的元素的属性名相同.当绑定指令的元素是<widget my-attr="parentModel">并且widget指令作用域定义为scope: { localModel:'=myAttr' },那么widget作用域属性localModel将映射为父作用域的parentModel,当改变parentModel时localModel也会相应改变,同样localModel改变时parentModel也会改变.如果父作用域中的属性不存在,angular会抛出一个NON_ASSIGNABLE_MODEL_EXPRESSION的异常.我们可以通过=?或者=?attr,来避免异常的抛出.如果你希望对属性使用"shallow watch",你可以使用=*或者=*attr(=*?或者=*?attr,如果属性是只是可选的).对于"shallow watch"在https://docs.angularjs.org/api/ng/type/$rootScope.Scope#$watchCollection有说明."shallow watch"将监视对象上的所有属性,任何属性变化都会导致回调.
  • & 或者 &attr :提供了一种执行父作用域下的表达式的方式.如果不指明attr,子作用域属性和dom元素的属性的名字将一致.当有使用的指令的dom元素<widget my-attr="count = count + value">和指令定义的作用域scope: { localFn:'&myAttr' },那么孤立作用域属性localFn将指向一个函数,这个函数包装了count = count + value表达式.这种方式比较适合子作用域通过表达式的方式从父作用域中获取值

一般来说,可能多个指令同时作用于一个元素,但是在指令对作用域类型的要求上会存在一些限制.下面几点能有助于解释这些限制.为了简单起见,我们这里以两个指令为例,当然也适用于多个指令的情况:

  • no scope + no scope => 两个指令都没有要求自己的作用域,那么都会使用父作用域
  • child scope + no scope => 会共享使用独立的统一个子作用域
  • child scope + child scope => 会共享使用独立的统一个子作用域
  • isolated scope + no scope => 前者使用"孤立"作用域,后者使用父作用域
  • isolated scope + child scope =>不会正常工作
  • isolated scope + isolated scope => 不会正常工作

bindToController

当一个"孤立"作用域被用于一个组件,并且controllerAs也被使用了,bindToController: true将允许一个组件把他的属性绑定到控制器上,而不是scope上.当控制器被实例化,这些"孤立"作用域上的绑定的值就已经被初始化完成了.

controller

控制器构造函数.这个控制器将在pre-linking 阶段前被初始化,并且可以被其他的指令访问(参看require属性).这允许指令间的交互和协调相互的行为.控制器是可以使用下面本体变量进行"依赖注入的:

  • $scope 当前绑定到DOM元素的作用域
  • $element 当前的DOM元素
  • $attrs 当前的元素属性对象
  • $transclude 一个transclude链接函数预先绑定正确的transclusion 作用域:
    function([scope], cloneLinkingFn, futureParentElement, slotName):
  • scope: (可选)重载作用域
  • cloneLinkingFn:(可选)参数用于创建原始的transcluded 内容的克隆元素
  • futureParentElement (可选)
  • slotName:(可选)

require

要求另一个指令并且注入其控制器作为第4个参数传入链接函数.require这个属性要求传入的是一个指令名字的字符串或者字符串的数组.如果使用数组,注入的书序讲师数组中元素的顺序.如果相应的指令没有找到,指令没有控制器,一个错误将产生(除非链接函数没有定义,错误检测可以被忽略).指令名称可以使用的前缀:

  • 没有前缀 需要的指令在当前的DOM元素上.没有找到就抛出一个错误.
  • ? 尝试在当前的DOM元素上查找指令的controller,如果没有找到链接函数的第四个参数将被传入一个null
  • ^ 在当前的DOM元素和其父级元素上查找,如果没有找到报错
  • ^^ 只在父级元素上查找,没找到报错
  • ?^ 在当前DOM元素和父级元素上找,没有找到不报错,给link函数第四个参数传入一个null
  • ?^^ 在父级元素上找,没有找到不报错,给link函数第四个参数传入一个null

controllerAs

标识名用于引用指令作用域中的控制器.这允许控制器可以被在指令模版中被引用到.这尤其有用当指令作为一个组件来使用时,比如使用一个"孤立"作用域.这也是可能的,运用一个不使用"孤立"作用域的指令,但是你需要意识到,controllerAs引用可能重写父类的一些已经存在的属性.

restrict

要求一个字符串,这个字符串是 EACM的子集,这个用来限制指令使用样式.如果没有定义,默认是元素形式和元素属性形式

  • E Element name(default) :<my-directive></my-directive>
  • A Atrribute(default) :<div my-directive="exp"></div>
  • C Class:`
  • M Commment:<!-- directive:my-directive exp -->

    templateNamespace

    一个用于代表模版中标记语言使用的文档类型的字符.angular js 需要这些信息用于元素的创建和克隆的特殊处理,当他们被在外部定义为使用容器<svg><math>.
  • html 所有的根节点在模版中是html.根节点也可以是同样级别的元素比如<svg>或者<math>
  • svg 根节点在模版中是svg元素(不包括<math>)
  • math 根节点在模版中是MathML元素(不包括<svg>)

如果templateNamespace 没有定义,默认是html.

template

HTML 标记,可以这样:

  • 替代指令元素的内容(默认)
  • 替代指令元素自己(如果replace是true ,但是这个属性可能在新版中会被删除)
  • 包裹指令元素的内容(如果transclude是true)
    其形式可以如下:
  • 一个字符串:比如<div red-on-hover>{{delete_str}}</div>
  • 一个包含两个参数(tElement,tAttrs)的函数,但是必须返回一个字符串

templateUrl

这个参数和template的功能类似,但是是通过一个明确的URL地址异步加载模版.
因为模版加载是异步的,所以编译器将在模版被解析出来后才对元素上的执行进行编译.这意味着对于兄弟元素和父级元素的编译和链接想继续,就像这个元素上没有指令一样.

编译器不支持整个编译过程等待模版的加载,是因为这将导致整个应用"假死"直到模版被异步加载成功,特别是当只有一个深度嵌套的指令拥有一个templateUrl属性的情况.

模版的加载是异步的,即使模版被预先加载到了$templateCache中.

你可以给templateUrl指定一个代表URL的字符串,也可以指定一个携带两个参数(tElment和tAttrs)(在后面的compile函数中将解释)并且返回一个url字符串的函数.在两种情况中,模版的Url都会通过$sce.getTrustedResourceUrl的处理.

replace ([不赞成使用],将会在下一个主版本中移除,即v2.0)

指出模版将替换什么内容,默认为false

对于应用模块中,元素替换的需求只有很少的场景,主要的一中情况就是使用了svg上下文的可重用的定制组件(因为svg在DOM树中的定制元素是无法运行的).

transclude

  相关解决方案