1)、概述
2)、Compiler编译器
链接:结合 directives和scope生产一个实时的view。scope模型中的任何变化都会反映到view上,任何用户与view的交互也会映射到scope模型中,保证了数据真实性。
3)、Directive指令
<span ng-bind="exp"></span> <span class="ng-bind: exp;"></span> <ng-bind></ng-bind> <!-- directive: ng-bind exp -->
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。这种方式存在几点问题:
- 需要读取用户输入并合并到数据中;
- 覆写的时候会把用户的输入擦除掉;
- 需要管理整个更新流程;
- 行为体验不佳。
Angular方式创造了一个稳定的DOM,这个DOM元素绑定在一个模型细项实例,在绑定的生命周期中都不会改变。这意味着代码可以控制HTML元素并注册时间处理器,也知道引用不会因为模板数据合并而销毁。
5)、directives如何被编译
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配置的
$watch
s 。
这样就有了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)、编译与链接的不同
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操作。
action
s插入, <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运行时会监听表达式并把加入到
scope 并调用拷贝<li>元素拷贝
数组,为拷贝的<li>
元素创建新的<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处理。但是我们想要模板的scope的title
属性是插入<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的本地属性带来两个问题:
-
隔离- i如果用户忘了设置 dialog 控件的的
title
属性,dialog 模板会绑定其父scope 属性。这不可预测也不可取。 -
嵌入- 嵌入的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