Angular HTML 编译器


1)、概述

       Angular's HTML 编译器 允许你定义新的HTML语法。 通过 继承自 directives,编译器 允许你附加新的行为到HTML元素、属性甚至是新的标签。(关于directives后续文章会详细讲解)

       HTML有许多表述形的构造方法来格式化一些静态文档。例如想要某东西居中的话,只需要写 align="center" 即可,而不用关心怎么实现或者窗口有多宽等,这些细节都隐藏起来了,这就是表述形语言的强大之处。但 表述形语言也有局限,你没法教browser新的语法,比如你想让上述控件在窗口的1/3地方显示而不是1/2的话,就没有简单的内置方法属性了,因为这需要教browser一种新的规则。

        Angular自带了 很多对你构建应用很管用的预定义directives,你也可以自己创建一些特殊的directives,这样就有了一个动态特殊的语言来构建你的应用。

        这些所有的编译都是在web  browser中进行,不需要在服务器端进行,也不需要预编译。

2)、Compiler编译器

         Compiler是一个angular服务,它会遍历DOM从而找到相应属性,分为两个阶段。
                 编译:遍历 DOM并收集所有的 directives, 对应的结果是链接函数。
     链接:结合 directives和scope生产一个实时的view。scope模型中的任何变化都会反映到view上,任何用户与view的交互也会映射到scope模型中,保证了数据真实性。

        有些directives,比如 ng-repeat会为集合中的每子项复制DOM元素。这些复制的的模板只需要编译一次,然后为每个复制对象链接一次就行, 这样可以提高编译和链接过程的效率。


3)、Directive指令

        Directive是一种行为,在编译阶段遇到特殊的HTML构造时会触发。directive可以使用在元素、属性,类名和注释中。下面这些例子都可以调用 ng-bind directive。
<span ng-bind="exp"></span>
<span class="ng-bind: exp;"></span>
<ng-bind></ng-bind>
<!-- directive: ng-bind exp -->
        Directive是一个函数,当编译器在DOM中遇到时会执行,  directive API( https://code.angularjs.org/1.5.0-rc.0/docs/guide/compiler )里面有详解。

        下面是一个使得任意元素可以被拖拽的directive例子,注意一下 <span>元素的 draggable属性。
           script.js文件
angular.module('drag', []).
directive('draggable', function($document) {
  return function(scope, element, attr) {
    var startX = 0, startY = 0, x = 0, y = 0;
    element.css({
     position: 'relative',
     border: '1px solid red',
     backgroundColor: 'lightgrey',
     cursor: 'pointer',
     display: 'block',
     width: '65px'
    });
    element.on('mousedown', function(event) {
      // Prevent default dragging of selected content
      event.preventDefault();
      startX = event.screenX - x;
      startY = event.screenY - y;
      $document.on('mousemove', mousemove);
      $document.on('mouseup', mouseup);
    });
    function mousemove(event) {
      y = event.screenY - startY;
      x = event.screenX - startX;
      element.css({
        top: y + 'px',
        left:  x + 'px'
      });
    }
    function mouseup() {
      $document.off('mousemove', mousemove);
      $document.off('mouseup', mouseup);
    }
  };
});


index.html文件
<span draggable>Drag ME</span>

可拖拽属性的存在可以给任何元素有了新的行为。对于熟悉HTML原则的人来说,这是一种很自然的方式来拓展浏览器的 vocabulary

4)、理解View

        大多数其他模板系统都是通过使用静态字符串模板来连接数据并返回一个新的字符串。最后把新字符串通过 innerHTML插入HTML元素。

        任何数据变化都需要重新合并模板然后通过innerHTML插入DOM。这种方式存在几点问题:

  1. 需要读取用户输入并合并到数据中;
  2. 覆写的时候会把用户的输入擦除掉;
  3. 需要管理整个更新流程;
  4. 行为体验不佳。
        Angular不一样, Angular编译器直接处理DOM而不是字符串模板。通过directive中定义的链接函数会把scope模型结果与实时的视图显示结合起来。视图与scope模型绑定的是透明,意思就是不要你动手编码来刷新视图。并且这里没有使用 innerHTML方式,不会意外擦除用户输入。更妙的是, Angular directives不光包含文本绑定,还能定义行为构造。


         Angular方式创造了一个稳定的DOM,这个DOM元素绑定在一个模型细项实例,在绑定的生命周期中都不会改变。这意味着代码可以控制HTML元素并注册时间处理器,也知道引用不会因为模板数据合并而销毁。

5)、directives如何被编译

       重要的一点是Angular操作于DOM节点而不是字符串。通常,不会注意到这点因为一个页面加载时,web浏览器立即把HTML解析为DOM。
HTML编译包括三个阶段:

5.1)、$compile 遍历 DOM 并找出directives。
       如果编译器找到一个能匹配directive的元素,会把directive加入匹配DOM元素的directives列表,单个元素可能匹配多个directives。
5.2)、等所有的匹配DOM元素的directives定位到之后,编译器会把directives按优先级排序。
       每一个directive的编译函数都会被执行。每个编译函数有一次机会修改DOM,每个编译函数返回一个连接函数,这些函数会被组装成一个 "combined" 链接函数,他们会调用每个 directive的返回链接函数。
5.3)、$compile通过调用上一步的 "combined" 链接函数再用scope链接到模板这次会调用单个directive的链接函数,给元素注册监听器,使用scope设置好每个directive配置的 $watchs 。

        这样就有了scope与DOM之间实时的绑定。 所以在这一点上,编译过的scope模型上的变化都会投射到DOM上去。

下面是使用 $compile 服务的代码,可以让你对Angular内部工作有个概念。

var $compile = ...; // injected into your code
var scope = ...;
var parent = ...; // DOM element where the compiled template can be appended

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.
var element = linkFn(scope);

// Step 4: Append to DOM (optional)
parent.appendChild(element);

6)、编译与链接的不同

       到这里你肯定想知道为何要有编译和链接阶段吧。简要的回答是当模型的改变能够引起DOM结构的变化时要编译与链接分离 directives 很少有编译函数,因为大多数directives跟特定的DOM元素对象一起作用而不是改变它们的整体结构。
       Directives通常都有链接函数,链接函数允许directive注册监听器到特殊的拷贝DOM元素对象,也会从scope把内容拷贝到DOM。 


7)、编译与链接例子

         看一个真是的ngRepeat的例子:

Hello {{user.name}}, you have these actions:
<ul>
  <li ng-repeat="action in user.actions">
    {{action.description}}
  </li>
</ul><span style="line-height: 1.5; font-family: 微软雅黑; font-size: 14px; background-color: inherit;"> </span>

        当上面的例子编译过后,编译器访问每一个节点来查询directives。{{user.name}}匹配插补 directive  ng-repeat 匹配ngRepeat directive但  ngRepeat 有点麻烦。它需要为user.actions中的每个action拷贝新的<li>元素。这看起来是似乎不足道,但是当你要考虑user.actions可能后面还要加子项的话就复杂多了。这意味着它需要保存一份干净的 <li>元素copy用来进行copy操作。

        因为新的actions插入, <li>元素模板需要拷贝并插入到ul。然而拷贝<li> 元素并不够, 还需要编译<li> 才能让它的directives(例如{{action.description}})评估正确的scope
         一个幼稚的解决这个问题的方法是直接插入 <li>元素的copy然后编译它。这个方法的问题是编译每个 拷贝的每个<li> 元素会有大量工作重复。还有就是,在拷贝前要遍历<li>来找directives。这回导致编译过程变慢,进而在插入新元素的时候减弱应用的响应。
      解决办法是分离成两个过程:
       编译过程,所有的directives 都被定位到并按优先级排序;链接过程, 执行 任何 "links" scope特定对象和 <li>对象

    ngRepeat 阻止编译过程沉降到<li>元素,因而它可以有一个原始拷贝并亲自处理DOM节点的插入和移除。

  ngRepeat directive 不单独编译<li> <li>元素编译结果是<li>种中所有的directives 连接一起一个链接函数,可以加入到<li>元素的特定拷贝。

  在ngRepeat运行时会监听表达式并把加入到 <li>元素拷贝数组,为拷贝的<li> 元素创建新的 scope 并调用拷贝<li>链接函数。

8)、理解Scopes怎么嵌入Directives

        directives最常用的用例是创建复用组件。下面是伪代码展示简单dialog 怎么使用。

<div>
  <button ng-click="show=true">show</button>

  <dialog title="Hello {{username}}."
          visible="show"
          on-cancel="show = false"
          on-ok="show = false; doSomething()">
     Body goes here: {{username}} is {{title}}.
  </dialog>
</div>

       点击"show" 按钮会打开dialog.。dialog 标题会绑定 username 数据, 主体会嵌入我们想要的东西。下面是一个dialog 模板定义的例子。

<div ng-show="visible">
  <h3>{{title}}</h3>
  <div class="body" ng-transclude></div>
  <div class="footer">
    <button ng-click="onOk()">Save changes</button>
    <button ng-click="onCancel()">Close</button>
  </div>
</div>

这并不会渲染正确,直到我们做一下scope处理。但是我们想要模板的scopetitle 属性是插入<dialog>元素的title属性的结果(例如,"Hello {{username}}")。此外,按钮希望 onOk 和onCancel存在于scope。这会限制这个控件的使用。为了解决映射问题,我们用scope创建模板希望的本地变量如下:

scope: {
  title: '@',             // the title uses the data-binding from the parent scope
  onOk: '&',              // create a delegate onOk function
  onCancel: '&',          // create a delegate onCancel function
  visible: '='            // set up visible to accept data-binding
}

        创建控件scope的本地属性带来两个问题:

  1. 隔离- i如果用户忘了设置 dialog 控件的的title 属性,dialog 模板会绑定其父scope 属性。这不可预测也不可取。

  2. 嵌入嵌入的DOM可以看控件的私有, 可能导致覆写嵌入需要的数据绑定属性。我们例子中,控件的title 属性毁掉了嵌入属性。

       为了解决缺少隔离的问题, directive申明一个新的隔离的scope 。一个隔离的scope不会继承自父scope。因此我们不需要担心毁掉任何属性。然而隔离的scope带来新问题,如果嵌入的DOM是控件scope 的子scope,它就不能绑定任何东西。因而嵌入的scope是原始scope的子scope before 在控件之前为它的私有变量创建一个隔离的scope。使得嵌入的和控件隔离scope并列。

        看起来比预期的复杂,但会给控件用户和开发者更少意外。

         因而最终的directive 定义看起来如此:

transclude: true,
scope: {
    title: '@',             // the title uses the data-binding from the parent scope
    onOk: '&',              // create a delegate onOk function
    onCancel: '&',          // create a delegate onCancel function
    visible: '='            // set up visible to accept data-binding
},
restrict: 'E',
replace: true


 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值