示例:模板控制语句 if/else

最后更新于:2022-04-01 22:40:55

### 18.14. 示例:模板控制语句 if/else 这个示例是尝试实现:

判断为真, {{ name }}

判断为假, {{ name }}

a:

name:

考虑实现的思路: - _else_ 与 _if_ 是两个指令,它们是父子关系。通过 `scope` 可以联系起来。至于 `scope` 是在 `link` 中处理还是 `controller` 中处理并不重要。 - _true_ 属性的条件判断通过 _$parse_ 服务很容易实现。 - 如果最终效果要去掉 _if_ 节点,我们可以使用注释节点来“占位”。 JS 代码: 1 var app = angular.module('Demo', [], angular.noop); 2 3 app.directive('if', function($parse, $compile){ 4 var compile = function($element, $attrs){ 5 var cond = $parse($attrs.true); 6 7 var link = function($scope, $ielement, $iattrs, $controller){ 8 $scope.if_node = $compile($.trim($ielement.html()))($scope, angular.noop); 9 $ielement.empty(); 10 var mark = $(''); 11 $element.before(mark); 12 $element.remove(); 1314 $scope.$watch(function(scope){ 15 if(cond(scope)){ 16 mark.after($scope.if_node); 17 $scope.else_node.detach(); 18 } else { 19 if($scope.else_node !== undefined){ 20 mark.after($scope.else_node); 21 $scope.if_node.detach(); 22 } 23 } 24 }); 25 } 26 return link; 27 } 2829 return {compile: compile, 30 scope: true, 31 restrict: 'E'} 32 }); 3334 app.directive('else', function($compile){ 35 var compile = function($element, $attrs){ 3637 var link = function($scope, $ielement, $iattrs, $controller){ 38 $scope.else_node = $compile($.trim($ielement.html()))($scope, angular.noop); 39 $element.remove(); 40 } 41 return link; 42 } 4344 return {compile: compile, 45 restrict: 'E'} 46 }); 4748 app.controller('TestCtrl', function($scope){ 49 $scope.a = 1; 50 }); 5152 angular.bootstrap(document, ['Demo']); 代码中注意一点,就是 `if_node` 在得到之时,就已经是做了变量绑定的了。错误的思路是,在 `$watch` 中再去不断地得到新的 `if_node` 。
';

示例:模板控制语句 for

最后更新于:2022-04-01 22:40:53

### 18.13. 示例:模板控制语句 for 这个示例尝试实现一个重复语句,功能同官方的 `ngRepeat` ,但是使用方式类似于我们通常编程语言中的 _for_ 语句:
  • {{ o }}, {{ name }}
同样,我们从上面的使用方式去考虑这个指令的实现: - 这是一个完全的控制指令,所以单个节点应该只有它一个指令起作用就好了,于是权重要比较高,并且“到此为止”—— _priority_ 设置为 `1000` , _terminal_ 设置为 `true` 。 - 使用时的语法问题。事实上浏览器会把 `for` 节点补充成一个正确的 HTML 结构,即里面的属性都会变成类似 `o=""` 这样。我们通过节点的 `outerHTML` 属性取到字符串并解析取得需要的信息。 - 我们把 `for` 节点之间的内容作为一个模板,并且通过循环多次渲染该模板之后把结果填充到合适的位置。 - 在处理上面的那个模板时,需要不断地创建新 `scope` 的,并且 `o` 这个成员需要单独赋值。 注意:这里只是简单实现功能。官方的那个 _ngRepeat_ 比较复杂,是做了专门的算法优化的。当然,这里的实现也可以是简单把 DOM 结构变成使用 _ngRepeat_ 的形式 :) JS 部分代码: 1 var app = angular.module('Demo', [], angular.noop); 2 3 app.directive('for', function($compile){ 4 var compile = function($element, $attrs, $link){ 5 var match = $element[0].outerHTML.match(''); 6 if(!match || match.length != 3){throw Error('syntax: ')} 7 var iter = match[1]; 8 var list = match[2]; 9 var tpl = $compile($.trim($element.html())); 10 $element.empty(); 1112 var link = function($scope, $ielement, $iattrs, $controller){ 1314 var new_node = []; 1516 $scope.$watch(list, function(list){ 17 angular.forEach(new_node, function(n){n.remove()}); 18 var scp, inode; 19 for(var i = 0, ii = list.length; i < ii; i++){ 20 scp = $scope.$new(); 21 scp[iter] = list[i]; 22 inode = tpl(scp, angular.noop); 23 $ielement.before(inode); 24 new_node.push(inode); 25 } 2627 }); 28 } 2930 return link; 31 } 32 return {compile: compile, 33 priority: 1000, 34 terminal: true, 35 restrict: 'E'}; 36 }); 3738 app.controller('TestCtrl', angular.noop); 39 angular.bootstrap(document, ['Demo']);
';

示例:文本框

最后更新于:2022-04-01 22:40:51

### 18.12. 示例:文本框 这个例子与官网上的那个例子相似。最终是要显示一个文本框,这个文本框由标题和内容两部分组成。而且标题和内容则是引用 controller 中的变量值。 HTML 部分的代码:

标题:

内容:

从这个期望实现效果的 HTML 代码中,我们可以考虑设计指令的实现方式: - 这个指令的使用方式是“标签”, 即 _restrict_ 这个参数应该设置为 `E` 。 - 节点的属性值是对 controller 变量的引用,那么我们应该在指令的 _scope_ 中使用 `=` 的方式来指定成员值。 - 最终的效果显示需要进行 DOM 结构的重构,那直接使用 _template_ 就好了。 - 自定义的标签在最终效果中是多余的,所有 _replace_ 应该设置为 `true` 。 JS 部分的代码: var app = angular.module('Demo', [], angular.noop); app.directive('ysBlock', function(){ return {compile: angular.noop, template: '

{{ title }}

{{ text }}
', replace: true, scope: {title: '=title', text: '=text'}, restrict: 'E'}; }); app.controller('TestCtrl', function($scope){ $scope.title = '标题在这里'; $scope.text = '内容在这里'; }); angular.bootstrap(document, ['Demo']); 可以看到,这种简单的组件式指令,只需要作 DOM 结构的变换即可实现,连 `compile` 函数都不需要写。
';

预定义的 FormController

最后更新于:2022-04-01 22:40:49

### 18.11. 预定义的 FormController 前面的“表单控制”那章,实际上讲的就是 _FormController_ ,只是那里是从 `scope` 中获取到的引用。现在从指令定义的角度,来更清楚地了解 _FormController_ 及 _NgModelController_ 是如何配合工作的。 先说一下, _form_ 和 _ngForm_ 是官方定义的两个指令,但是它们其实是同一个东西。前者只允许以标签形式使用,而后者允许 `EAC` 的形式。DOM 结构中, `form` 标签不能嵌套,但是 ng 的指令没有这个限制。不管是 _form_ 还是 _ngForm_ ,它们的 `controller` 都被命名成了 `form` 。 所以 _require_ 这个参数不要写错了。 _FormController_ 的几个成员是很好理解的: _$pristine_ 表单是否被动过_$dirty_ 表单是否没被动过_$valid_ 表单是否检验通过_$invalid_ 表单是否检验未通过_$error_ 表单中的错误_$setDirty()_ 直接设置 _$dirty_ 及 _$pristine_
var app = angular.module('Demo', [], angular.noop); app.directive('test', function(){ var link = function($scope, $element, $attrs, $ctrl){ $scope.do = function(){ //$ctrl.$setDirty(); console.log($ctrl.$pristine); //form是否没被动过 console.log($ctrl.$dirty); //form是否被动过 console.log($ctrl.$valid); //form是否被检验通过 console.log($ctrl.$invalid); //form是否有错误 console.log($ctrl.$error); //form中有错误的字段 } } return {compile: function(){return link}, require: 'form', restrict: 'A'} }); app.controller('TestCtrl', function($scope){ }); _$error_ 这个属性,是一个对象, `key` 是错误名, `value` 部分是一个列表,其成员是对应的 _NgModelController_ 的实例。 _FormController_ 可以自由增减它包含的那些,类似于 _NgModelController_ 的实例。在 DOM 结构上,有 `ng-model` 的 `input` 节点的 _NgMoelController_ 会被自动添加。 _$addControl()_ 添加一个 conroller _$removeControl()_ 删除一个 controller 这两个手动使用机会应该不会很多。被添加的实例也可以手动实现所有的 _NgModelController_ 的方法
1 var app = angular.module('Demo', [], angular.noop); 2 3 app.directive('test', function(){ 4 var link = function($scope, $element, $attrs, $ctrl){ 5 $scope.add = function(){ 6 $ctrl.$addControl($scope.bb); 7 console.log($ctrl); 8 } 9 } 1011 return {compile: function(){return link}, 12 require: 'form', 13 restrict: 'A'} 14 }); 1516 app.directive('bb', function(){ 17 var controller = function($scope, $element, $attrs, $transclude){ 18 $scope.bb = this; 19 this.$name = 'bb'; 20 } 2122 return {compile: angular.noop, 23 restrict: 'E', 24 controller: controller} 25 }); 2627 app.controller('TestCtrl', function($scope){ 28 }); 整合 _FormController_ 和 _NgModelController_ 就很容易扩展各种类型的字段:
1 var app = angular.module('Demo', [], angular.noop); 2 3 app.directive('input', function(){ 4 var link = function($scope, $element, $attrs, $ctrl){ 5 console.log($attrs.type); 6 var validator = function(v){ 7 if(v == '123'){ 8 $ctrl.$setValidity('my', true); 9 return v; 10 } else { 11 $ctrl.$setValidity('my', false); 12 return undefined; 13 } 14 } 1516 $ctrl.$formatters.push(validator); 17 $ctrl.$parsers.push(validator); 18 } 1920 return {compile: function(){return link}, 21 require: 'ngModel', 22 restrict: 'E'} 23 }); 2425 app.controller('TestCtrl', function($scope){ 26 $scope.show = function(){ 27 console.log($scope.f); 28 } 29 }); 虽然官方原来定义了几种 `type` ,但这不妨碍我们继续扩展新的类型。如果新的 `type` 参数值不在官方的定义列表里,那会按 `text` 类型先做处理,这其实什么影响都没有。剩下的,就是写我们自己的验证逻辑就行了。 上面的代码是参见官方的做法,使用格式化的过程,同时在里面做有效性检查。
';

预定义的 NgModelController

最后更新于:2022-04-01 22:40:46

### 18.10. 预定义的 NgModelController 在前面讲 conroller 参数的时候,提到过可以为指令定义一个 conroller 。官方的实现中,有很多已定义的指令,这些指令当中,有两个已定义的 conroller ,它们是 _NgModelController_ 和 _FormController_ ,对应 _ng-model_ 和 _form_ 这两个指令(可以参照前面的“表单控件”一章)。 在使用中,除了可以通过 `$scope` 来取得它们的引用之外,也可以在自定义指令中通过 _require_ 参数直接引用,这样就可以在 `link` 函数中使用 controller 去实现一些功能。 先看 _NgModelController_ 。这东西的作用有两个,一是控制 `ViewValue` 与 `ModelValue` 之间的转换关系(你可以实现看到的是一个值,但是存到变量里变成了另外一个值),二是与 _FormController_ 配合做数据校验的相关逻辑。 先看两个应该是最有用的属性: _$formatters_ 是一个由函数组成的列表,串行执行,作用是把变量值变成显示的值。_$parsers_ 与上面的方向相反,把显示的值变成变量值。 假设我们在变量中要保存一个列表的类型,但是显示的东西只能是字符串,所以这两者之间需要一个转换:
1 var app = angular.module('Demo', [], angular.noop); 2 3 app.directive('test', function(){ 4 var link = function($scope, $element, $attrs, $ctrl){ 5 6 $ctrl.$formatters.push(function(value){ 7 return value.join(','); 8 }); 910 $ctrl.$parsers.push(function(value){ 11 return value.split(','); 12 }); 13 } 1415 return {compile: function(){return link}, 16 require: 'ngModel', 17 restrict: 'A'} 18 }); 1920 app.controller('TestCtrl', function($scope){ 21 $scope.a = []; 22 //$scope.a = [1,2,3]; 23 $scope.show = function(v){ 24 console.log(v); 25 } 26 }); 上面在定义 `test` 这个指令, _require_ 参数指定了 `ngModel` 。同时因为 DOM 结构, `ng-model` 是存在的。于是, `link` 函数中就可以获取到一个 _NgModelController_ 的实例,即代码中的 `$ctrl` 。 我们添加了需要的过滤函数: - 从变量( `ModelValue` )到显示值( `ViewValue` )的过程, `$formatters` 属性,把一个列表变成一个字符串。 - 从显示值到变量的过程, `$parsers` 属性,把一个字符串变成一个列表。 对于显示值和变量,还有其它的 API ,这里就不细说了。 另一部分,是关于数据校验的,放到下一章同 _FormController_ 一起讨论。
';

Attributes的细节

最后更新于:2022-04-01 22:40:44

### 18.9. Attributes的细节 节点属性被包装之后会传给 `compile` 和 `link` 函数。从这个操作中,我们可以得到节点的引用,可以操作节点属性,也可以为节点属性注册侦听事件。 var app = angular.module('Demo', [], angular.noop); app.directive('test', function(){ var func = function($element, $attrs){ console.log($attrs); } return {compile: func, restrict: 'E'} 整个 _Attributes_ 对象是比较简单的,它的成员包括了: _$$element_ 属性所在的节点。_$attr_ 所有的属性值(类型是对象)。_$normalize_ 一个名字标准化的工具函数,可以把 `ng-click` 变成 `ngClick` 。_$observe_ 为属性注册侦听器的函数。_$set_ 设置对象属性,及节点属性的工具。 除了上面这些成员,对象的成员还包括所有属性的名字。 先看 _$observe_ 的使用,基本上相当于 _$scope_ 中的 _$watch_ :
var app = angular.module('Demo', [], angular.noop); app.directive('test', function(){ var func = function($element, $attrs){ console.log($attrs); $attrs.$observe('a', function(new_v){ console.log(new_v); }); } return {compile: func, restrict: 'E'} }); app.controller('TestCtrl', function($scope){ $scope.a = 123; }); _$set_ 方法的定义是: `function(key, value, writeAttr, attrName) { ... }` 。 - _key_ 对象的成员名。 - _value_ 需要设置的值。 - _writeAttr_ 是否同时修改 DOM 节点的属性(注意区别“节点”与“对象”),默认为 `true` 。 - _attrName_ 实际的属性名,与“标准化”之后的属性名有区别。
这里
var app = angular.module('Demo', [], angular.noop); app.directive('test', function(){ var func = function($element, $attrs){ $attrs.$set('b', 'ooo'); $attrs.$set('a-b', '11'); $attrs.$set('c-d', '11', true, 'c_d'); console.log($attrs); } return {compile: func, restrict: 'E'} }); app.controller('TestCtrl', function($scope){ $scope.show = function(v){console.log(v);} }); 从例子中可以看到,原始的节点属性值对,放到对象中之后,名字一定是“标准化”之后的。但是手动 `$set` 的新属性,不会自动做标准化处理。
';

指令定义时的参数

最后更新于:2022-04-01 22:40:42

### 18.8. 指令定义时的参数 指令定义时的参数如下: - name - priority - terminal - scope - controller - require - restrict - template - templateUrl - replace - transclude - compile - link 现在我们开始一个一个地吃掉它们……,但是并不是按顺序讲的。 _priority_ 这个值设置指令的权重,默认是 `0` 。当一个节点中有多个指令存在时,就按着权限从大到小的顺序依次执行它们的 `compile` 函数。相同权重顺序不定。_terminal_ 是否以当前指令的权重为结束界限。如果这值设置为 `true` ,则节点中权重小于当前指令的其它指令不会被执行。相同权重的会执行。_restrict_ 指令可以以哪些方式被使用,可以同时定义多种方式。 - E 元素方式 `` - A 属性方式 `
` - C 类方式 `
` - M 注释方式 `` _transclude_ 前面已经讲过基本的用法了。可以是 `'element'` 或 `true` 两种值。_compile_ 基本的定义函数。 `function compile(tElement, tAttrs, transclude) { ... }`_link_ 前面介绍过了。大多数时候我们不需要单独定义它。只有 `compile` 未定义时 `link` 才会被尝试。 `function link(scope, iElement, iAttrs, controller) { ... }`_scope_ scope 的形式。 `false` 节点的 scope , `true` 继承创建一个新的 scope , `{}` 不继承创建一个新的隔离 scope 。 `{@attr: '引用节点属性', =attr: '把节点属性值引用成scope属性值', &attr: '把节点属性值包装成函数'}`_controller_ 为指令定义一个 controller , `function controller($scope, $element, $attrs, $transclude) { ... }`_name_ 指令的 controller 的名字,方便其它指令引用。_require_ 要引用的其它指令 conroller 的名字, `?name` 忽略不存在的错误, `^name` 在父级查找。_template_ 模板内容。_templateUrl_ 从指定地址获取模板内容。_replace_ 是否使用模板内容替换掉整个节点, `true` 替换整个节点, `false` 替换节点内容。 var app = angular.module('Demo', [], angular.noop); app.directive('a', function(){ var func = function(element, attrs, link){ console.log('a'); } return {compile: func, priority: 1, restrict: 'EA'}; }); app.directive('b', function(){ var func = function(element, attrs, link){ console.log('b'); } return {compile: func, priority: 2, //terminal: true, restrict: 'A'}; }); 上面几个参数值都是比较简单且容易理想的。 再看 _scope_ 这个参数:
1 var app = angular.module('Demo', [], angular.noop); 2 3 app.directive('a', function(){ 4 var func = function(element, attrs, link){ 5 return function(scope){ 6 console.log(scope); 7 } 8 } 910 return {compile: func, 11 scope: true, 12 restrict: 'A'}; 13 }); 1415 app.directive('b', function(){ 16 var func = function(element, attrs, link){ 17 return function(scope){ 18 console.log(scope); 19 } 20 } 2122 return {compile: func, 23 restrict: 'A'}; 24 }); 2526 app.controller('TestCtrl', function($scope){ 27 $scope.a = '123'; 28 console.log($scope); 29 }); 对于 _scope_ : - 默认为 `false` , `link` 函数接受的 `scope` 为节点所在的 `scope` 。 - 为 `true` 时,则 `link` 函数中第一个参数(还有 `controller` 参数中的 `$scope` ), `scope` 是节点所在的 `scope` 的 `child scope` ,并且如果节点中有多个指令,则只要其中一个指令是 `true` 的设置,其它所有指令都会受影响。 这个参数还有其它取值。当其为 `{}` 时,则 `link` 接受一个完全隔离(isolate)的 `scope` ,于 `true` 的区别就是不会继承其它 `scope` 的属性。但是这时,这个 `scope` 的属性却可以有很灵活的定义方式: _@attr_ 引用节点的属性。
var app = angular.module('Demo', [], angular.noop); app.directive('a', function(){ var func = function(element, attrs, link){ return function(scope){ console.log(scope); } } return {compile: func, scope: {a: '@abc', b: '@xx', c: '@'}, restrict: 'A'}; }); app.controller('TestCtrl', function($scope){ $scope.a = '123'; }); - _@abc_ 引用 div 节点的 abc 属性。 - _@xx_ 引用 div 节点的 xx 属性,而 xx 属性又是一个变量绑定,于是 `scope` 中 `b` 属性值就和 `TestCtrl` 的 `a` 变量绑定在一起了。 - _@_ 没有写 attr name ,则默认取自己的值,这里是取 div 的 c 属性。 _=attr_ 相似,只是它把节点的属性值当成节点 `scope` 的属性名来使用,作用相当于上面例子中的 _@xx_ :
var app = angular.module('Demo', [], angular.noop); app.directive('a', function(){ var func = function(element, attrs, link){ return function(scope){ console.log(scope); } } return {compile: func, scope: {a: '=abc'}, restrict: 'A'}; }); app.controller('TestCtrl', function($scope){ $scope.here = '123'; }); _&attr_ 是包装一个函数出来,这个函数以节点所在的 `scope` 为上下文。来看一个很爽的例子:
这里
{{ here }}
1 var app = angular.module('Demo', [], angular.noop); 2 3 app.directive('a', function(){ 4 var func = function(element, attrs, link){ 5 return function llink(scope){ 6 console.log(scope); 7 scope.a(); 8 scope.b(); 910 scope.show = function(here){ 11 console.log('Inner, ' + here); 12 scope.a({here: 5}); 13 } 14 } 15 } 1617 return {compile: func, 18 scope: {a: '&abc', b: '&ngClick'}, 19 restrict: 'A'}; 20 }); 2122 app.controller('TestCtrl', function($scope){ 23 $scope.here = 123; 24 console.log($scope); 2526 $scope.show = function(here){ 27 console.log(here); 28 } 29 }); scope.a 是 _&abc_ ,即: scope.a = function(){here = here + 1} 只是其中的 `here` 是 `TestCtrl` 的。 scope.b 是 _&ngClick_ ,即: scope.b = function(){show(here)} 这里的 `show()` 和 `here` 都是 `TestCtrl` 的,于是上面的代码最开始会在终端输出一个 `124` 。 当点击“这里”时,这时执行的 `show(here)` 就是 `llink` 中定义的那个函数了,与 `TestCtrl` 无关。但是,其间的 `scope.a({here:5})` ,因为 `a` 执行时是 `TestCtrl` 的上下文,于是向 `a` 传递的一个对象,里面的所有属性 `TestCtrl` 就全收下了,接着执行 `here=here+1` ,于是我们会在屏幕上看到 `6` 。 这里是一个上下文交错的环境,通过 _&_ 这种机制,让指令的 `scope` 与节点的 `scope` 发生了互动。真是鬼斧神工的设计。而实现它,只用了几行代码: case '&': { parentGet = $parse(attrs[attrName]); scope[scopeName] = function(locals) { return parentGet(parentScope, locals); } break; } 再看 _controller_ 这个参数。这个参数的作用是提供一个 controller 的构造函数,它会在 `compile` 函数之后, `link` 函数之前被执行。 haha 1 var app = angular.module('Demo', [], angular.noop); 2 3 app.directive('a', function(){ 4 var func = function(){ 5 console.log('compile'); 6 return function(){ 7 console.log('link'); 8 } 9 } 1011 var controller = function($scope, $element, $attrs, $transclude){ 12 console.log('controller'); 13 console.log($scope); 1415 var node = $transclude(function(clone_element, scope){ 16 console.log(clone_element); 17 console.log('--'); 18 console.log(scope); 19 }); 20 console.log(node); 21 } 2223 return {compile: func, 24 controller: controller, 25 transclude: true, 26 restrict: 'E'} 27 }); `controller` 的最后一个参数, `$transclude` ,是一个只接受 `cloneAttachFn` 作为参数的一个函数。 按官方的说法,这个机制的设计目的是为了让各个指令之间可以互相通信。参考普通节点的处理方式,这里也是处理指令 `scope` 的合适位置。 kk 1 var app = angular.module('Demo', [], angular.noop); 2 3 app.directive('a', function(){ 4 var func = function(){ 5 } 6 7 var controller = function($scope, $element, $attrs, $transclude){ 8 console.log('a'); 9 this.a = 'xx'; 10 } 1112 return {compile: func, 13 name: 'not_a', 14 controller: controller, 15 restrict: 'E'} 16 }); 1718 app.directive('b', function(){ 19 var func = function(){ 20 return function($scope, $element, $attrs, $controller){ 21 console.log($controller); 22 } 23 } 2425 var controller = function($scope, $element, $attrs, $transclude){ 26 console.log('b'); 27 } 2829 return {compile: func, 30 controller: controller, 31 require: 'not_a', 32 restrict: 'EA'} 33 }); _name_ 参数在这里可以用以为 `controller` 重起一个名字,以方便在 _require_ 参数中引用。 _require_ 参数可以带两种前缀(可以同时使用): - _?_ ,如果指定的 controller 不存在,则忽略错误。即: require: '?not_b' 如果名为 `not_b` 的 controller 不存在时,不会直接抛出错误, `link` 函数中对应的 `$controller` 为 `undefined` 。 - _^_ ,同时在父级节点中寻找指定的 controller ,把上面的例子小改一下: kk 把 `a` 的 _require_ 改成(否则就找不到 `not_a` 这个 controller ): require: '?^not_a' 还剩下几个模板参数: _template_ 模板内容,这个内容会根据 _replace_ 参数的设置替换节点或只替换节点内容。_templateUrl_ 模板内容,获取方式是异步请求。_replace_ 设置如何处理模板内容。为 `true` 时为替换掉指令节点,否则只替换到节点内容。

原始内容

var app = angular.module('Demo', [], angular.noop); app.directive('a', function(){ var func = function(){ } return {compile: func, template: '

标题 {{ name }}

', //replace: true, //controller: function($scope){$scope.name = 'xxx'}, //scope: {}, scope: true , controller: function($scope){console.log($scope)}, restrict: 'A'} }); app.controller('TestCtrl', function($scope){ $scope.name = '123'; console.log($scope); }); _template_ 中可以包括变量引用的表达式,其 `scope` 遵寻 _scope_ 参数的作用(可能受继承关系影响)。 _templateUrl_ 是异步请求模板内容,并且是获取到内容之后才开始执行指令的 `compile` 函数。 最后说一个 _compile_ 这个参数。它除了可以返回一个函数用为 `link` 函数之外,还可以返回一个对象,这个对象能包括两个成员,一个 _pre_ ,一个 _post_ 。实际上, `link` 函数是由两部分组成,所谓的 _preLink_ 和 _postLink_ 。区别在于执行顺序,特别是在指令层级嵌套的结构之下, _postLink_ 是在所有的子级指令 `link` 完成之后才最后执行的。 _compile_ 如果只返回一个函数,则这个函数被作为 _postLink_ 使用: 1 var app = angular.module('Demo', [], angular.noop); 2 3 app.directive('a', function(){ 4 var func = function(){ 5 console.log('a compile'); 6 return { 7 pre: function(){console.log('a link pre')}, 8 post: function(){console.log('a link post')}, 9 } 10 } 1112 return {compile: func, 13 restrict: 'E'} 14 }); 1516 app.directive('b', function(){ 17 var func = function(){ 18 console.log('b compile'); 19 return { 20 pre: function(){console.log('b link pre')}, 21 post: function(){console.log('b link post')}, 22 } 23 } 2425 return {compile: func, 26 restrict: 'E'} 27 });
';

把节点内容作为变量处理的类型

最后更新于:2022-04-01 22:40:40

### 18.7. 把节点内容作为变量处理的类型 回顾最开始的那个代码显示的例子,那个例子只能处理一次节点内容。如果节点的内容是一个变量的话,需要用另外的思路来考虑。这里我们假设的例子是,定义一个指令 `showLenght` ,它的作用是在一段文本的开头显示出这段节点文本的长度,节点文本是一个变量。指令使用的形式是:
{{ text }}
从上面的 HTML 代码中,大概清楚 ng 解析它的过程(只看 `show-length` 那一行): - 解析 `div` 时发现了一个 `show-length` 的指令。 - 如果 `show-length` 指令设置了 `transclude` 属性,则 `div` 的节点内容被重新编译,得到的 `link` 函数作为指令 `compile` 函数的参数传入。 - 如果 `show-length` 指令没有设置 `transclude` 属性,则继续处理它的子节点( `TextNode` )。 - 不管是上面的哪种情况,都会继续处理到 `{{ text }}` 这段文本。 - 发现 `{{ text }}` 是一个 `Interpolate` ,于是自动在此节点中添加了一个指令,这个指令的 `link` 函数就是为 `scope` 添加了一个 `$watch` ,实现的功能是是当 `scope` 作 `$digest` 的时候,就更新节点文本。 与处理 `{{ text }}` 时添加的指令相同,我们实现 `showLength` 的思路,也就是: - 修改原来的 DOM 结构 - 为 `scope` 添加 `$watch` ,当 `$digest` 时修改指定节点的文本,其值为指定节点文本的长度。 代码如下: app.directive('showLength', function($rootScope, $document){ var func = function(element, attrs, link){ return function(scope, ielement, iattrs, controller){ var node = link(scope); ielement.append(node); var lnode = $(''); ielement.prepend(lnode); scope.$watch(function(scope){ lnode.text(node.text().length); }); }; } return {compile: func, transclude: true, // element是节点没,其它值是节点的内容没 restrict: 'A'}; }); 上面代码中,因为设置了 `transclude` 属性,我们在 `showLength` 的 `link` 函数(就是 `return` 的那个函数)中,使用 `func` 的第三个函数来重塑了原来的文本节点,并放在我们需要的位置上。然后,我们添加自己的节点来显示长度值。最后给当前的 `scope` 添加 `$watch` ,以更新这个长度值。
';

transclude的细节

最后更新于:2022-04-01 22:40:37

### 18.6. transclude的细节 `transclude` 有两方面的东西,一个是使用 `$compile` 时传入的函数,另一个是定义指令的 `compile` 函数时接受的一个参数。虽然这里的一出一进本来是相互对应的,但是实际使用中,因为大部分时候不会手动调用 `$compile` ,所以,在“默认”情况下,指令接受的 `transclude` 又会是一个比较特殊的函数。 看一个基本的例子: var app = angular.module('Demo', [], angular.noop); app.directive('more', function(){ var func = function(element, attrs, transclude){ var sum = transclude(1, 2); console.log(sum); console.log(element); } return {compile: func, restrict: 'E'}; }); app.controller('TestCtrl', function($scope, $compile, $element){ var s = '123'; var link = $compile(s, function(a, b){return a + b}); var node = link($scope); $element.append(node); }); angular.bootstrap(document, ['Demo']); 我们定义了一个 `more` 指令,它的 `compile` 函数的第三个参数,就是我们手工 `$compile` 时传入的。 如果不是手工 `$compile` ,而是 ng 初始化时找出的指令,则 `transclude` 是一个 `link` 函数(指令定义需要设置 `transclude` 选项):
123
app.directive('more', function($rootScope, $document){ var func = function(element, attrs, link){ var node = link($rootScope); node.removeAttr('more'); //不去掉就变死循环了 $('body', $document).append(node); } return {compile: func, transclude: 'element', // element是节点没,其它值是节点的内容没 restrict: 'A'}; });
';

Compile的细节

最后更新于:2022-04-01 22:40:35

### 18.5. Compile的细节 指令的处理过程,是 ng 的 _Compile_ 过程的一部分,它们也是紧密联系的。继续深入指令的定义方法,首先就要对 Compile 的过程做更细致的了解。 前面说过, ng 对页面的处理过程: - 浏览器把 HTML 字符串解析成 DOM 结构。 - ng 把 DOM 结构给 `$compile` ,返回一个 `link` 函数。 - 传入具体的 `scope` 调用这个 `link` 函数。 - 得到处理后的 DOM ,这个 DOM 处理了指令,连接了数据。 `$compile` 最基本的使用方式: var link = $compile('

{{ text }}

'); var node = link($scope); console.log(node); 上面的 `$compile` 和 `link` 调用时都有额外参数来实现其它功能。先看 `link` 函数,它形如: function(scope[, cloneAttachFn] 第二个参数 `cloneAttachFn` 的作用是,表明是否复制原始节点,及对复制节点需要做的处理,下面这个例子说明了它的作用:
A {{ text }}
B
app.controller('TestCtrl', function($scope, $compile){ var link = $compile($('#a')); //true参数表示新建一个完全隔离的scope,而不是继承的child scope var scope = $scope.$new(true); scope.text = '12345'; //var node = link(scope, function(){}); var node = link(scope); $('#b').append(node); }); `cloneAttachFn` 对节点的处理是有限制的,你可以添加 `class` ,但是不能做与数据绑定有关的其它修改(修改了也无效): app.controller('TestCtrl', function($scope, $compile){ var link = $compile($('#a')); var scope = $scope.$new(true); scope.text = '12345'; var node = link(scope, function(clone_element, scope){ clone_element.text(clone_element.text() + ' ...'); //无效 clone_element.text('{{ text2 }}'); //无效 clone_element.addClass('new_class'); }); $('#b').append(node); }); 修改无效的原因是,像 `{{ text }}` 这种所谓的 _Interpolate_ 在 `$compile` 中已经被处理过了,生成了相关函数(这里起作用的是 `directive` 中的一个 `postLink` 函数),后面执行 `link` 就是执行了 `$compile` 生成的这些函数。当然,如果你的文本没有数据变量的引用,那修改是会有效果的。 前面在说自定义指令时说过, `link` 函数是由 `compile` 函数返回的,也就像前面说的,应该把改变 DOM 结构的逻辑放在 `compile` 函数中做。 `$compile` 还有两个额外的参数: $compile(element, transclude, maxPriority); `maxPriority` 是指令的权重限制,这个容易理解,后面再说。 `transclude` 是一个函数,这个函数会传递给 `compile` 期间找到的 `directive` 的 `compile` 函数(编译节点的过程中找到了指令,指令的 `compile` 函数会接受编译时传递的 `transclude` 函数作为其参数)。 但是在实际使用中,除我们手工在调用 `$compile` 之外,初始化时的根节点 `compile` 是不会传递这个参数的。 在我们定义指令时,它的 `compile` 函数是这个样子的: function compile(tElement, tAttrs, transclude) { ... } 事实上, `transclude` 的值,就是 `directive` 所在的 **原始** 节点,把原始节点重新做了编译之后得到的 `link` 函数(需要 `directive` 定义时使用 `transclude` 选项),后面会专门演示这个过程。所以,官方文档上也把 `transclude` 函数描述成 `link` 函数的样子(如果自定义的指令只用在自己手动 `$compile` 的环境中,那这个函数的形式是可以随意的): {function(angular.Scope[, cloneAttachFn]} 所以记住,定义指令时, `compile` 函数的第三个参数 `transclude` ,就是一个 `link` ,装入 `scope` 执行它你就得到了一个节点。
';

属性值类型的自定义

最后更新于:2022-04-01 22:40:33

### 18.4. 属性值类型的自定义 官方代码中的 `ng-show` 等算是我说的这种类型。使用时主要是在节点加添加一个属性值以附加额外的功能。看一个简单的例子:

有颜色的文本

有颜色的文本 我们定义了一个叫 `color` 的指令,可以指定节点文本的颜色。但是这个例子还无法像 `ng-show` 那样工作的,这个例子只能渲染一次,然后就无法根据变量来重新改变显示了。要响应变化,我们需要手工使用 `scope` 的 `$watch` 来处理: 1 2
3

有颜色的文本

4

有颜色的文本

5
6 7
';

基本的自定义方法

最后更新于:2022-04-01 22:40:30

### 18.3. 基本的自定义方法 自定义一个指令可以非常非常的复杂,但是其基本的调用形式,同自定义服务大概是相同的:

如果在 `directive` 中直接返回一个函数,则这个函数会作为 `compile` 的返回值,也即是作为 `link` 函数使用。这里说的 `compile` 和 `link` 都是一个指令的组成部分,一个完整的定义应该返回一个对象,这个对象包括了多个属性: - name - priority - terminal - scope - controller - require - restrict - template - templateUrl - replace - transclude - compile - link 上面的每一个属性,都可以单独探讨的。 下面是一个完整的基本的指令定义例子: //失去焦点使用 jQuery 的扩展支持冒泡 app.directive('ngBlur', function($parse){ return function($scope, $element, $attr){ var fn = $parse($attr['ngBlur']); $element.on('focusout', function(event){ fn($scope, {$event: event}); }); } });
//失去焦点使用 jQuery 的扩展支持冒泡 app.directive('ngBlur', function($parse){ return function($scope, $element, $attr){ var fn = $parse($attr['ngBlur']); $element.on('focusout', function(event){ fn($scope, {$event: event}); }); } });
1 var app = angular.module('Demo', [], angular.noop); 2 3 app.directive('code', function(){ 4 var func = function($scope, $element, $attrs){ 5 6 var html = $element.text(); 7 var lines = html.split('\n'); 8 9 //处理首尾空白 10 if(lines[0] == ''){lines = lines.slice(1, lines.length - 1)} 11 if(lines[lines.length-1] == ''){lines = lines.slice(0, lines.length - 1)} 1213 $element.empty(); 1415 //处理外框 16 (function(){ 17 $element.css('clear', 'both'); 18 $element.css('display', 'block'); 19 $element.css('line-height', '20px'); 20 $element.css('height', '200px'); 21 })(); 2223 //是否显示行号的选项 24 if('lines' in $attrs){ 25 //处理行号 26 (function(){ 27 var div = $('
' 28 .replace('%s', String(lines.length).length * 10)); 29 var s = ''; 30 angular.forEach(lines, function(_, i){ 31 s += '
%s
\n'.replace('%s', i + 1); 32 }); 33 div.html(s); 34 $element.append(div); 35 })(); 36 } 3738 //处理内容 39 (function(){ 40 var div = $('
'); 41 var s = ''; 42 angular.forEach(lines, function(l){ 43 s += '%s
\n'.replace('%s', l.replace(/\s/g, ' ')); 44 }); 45 div.html(s); 46 $element.append(div); 47 })(); 48 } 4950 return {link: func, 51 restrict: 'AE'}; //以元素或属性的形式使用命令 52 }); 5354 angular.bootstrap(document, ['Demo']); 上面这个自定义的指令,做的事情就是解析节点中的文本内容,然后修改它,再把生成的新内容填充到节点当中去。其间还涉及了节点属性值 `lines` 的处理。这算是指令中最简单的一种形式。因为它是“一次性使用”,中间没有变量的处理。比如如果节点原来的文本内容是一个变量引用,类似于 `{{ code }}` ,那上面的代码就不行了。这种情况麻烦得多。后面会讨论。
';

指令的执行过程

最后更新于:2022-04-01 22:40:28

### 18.2. 指令的执行过程 ng 中对指令的解析与执行过程是这样的: - 浏览器得到 HTML 字符串内容,解析得到 DOM 结构。 - ng 引入,把 DOM 结构扔给 `$compile` 函数处理: - 找出 DOM 结构中有变量占位符 - 匹配找出 DOM 中包含的所有指令引用 - 把指令关联到 DOM - 关联到 DOM 的多个指令按权重排列 - 执行指令中的 `compile` 函数(改变 DOM 结构,返回 `link` 函数) - 得到的所有 `link` 函数组成一个列表作为 `$compile` 函数的返回 - 执行 `link` 函数(连接模板的 `scope`)。
';

指令的使用

最后更新于:2022-04-01 22:40:26

### 18.1. 指令的使用 使用指令时,它的名字可以有多种形式,把指令放在什么地方也有多种选择。 通常,指令的定义名是形如 `ngBind` 这样的 “camel cased” 形式。在使用时,它的引用名可以是: - ng:bind - ng_bind - ng-bind - x-ng-bind - data-ng-bind 你可以根据你自己是否有 “HTML validator” 洁癖来选择。 指令可以放在多个地方,它们的作用相同: - `` 作为标签的属性 - `` 作为标签类属性的值 - `` 作为标签 - `` 作为注释 这些方式可以使用指令定义中的 `restrict` 属性来控制。 可以看出,指令即可以作为标签使用,也可以作为属性使用。仔细考虑一下,这在类 XML 的结构当中真算得上是一种神奇的机制。
';

自定义指令directive

最后更新于:2022-04-01 22:40:24

# 18. 自定义指令directive 这是 ng 最强大的一部分,也是最复杂最让人头疼的部分。 目前我们看到的所谓“模板”系统,只不过是官方实现的几个指令而已。这意味着,通过自定义各种指令,我们不但可以完全定义一套“模板”系统,更可以把 HTML 页面直接打造成为一种 DSL (领域特定语言)。
';

自定义过滤器

最后更新于:2022-04-01 22:40:21

# 17. 自定义过滤器 先来回顾一下 ng 中的一些概念: - _module_ ,代码的组织单元,其它东西都是在定义在具体的模块中的。 - _app_ ,业务概念,可能会用到多个模块。 - _service_ ,仅在数据层面实现特定业务功能的代码封装。 - _controller_ ,与 DOM 结构相关联的东西,即是一种业务封装概念,又体现了项目组织的层级结构。 - _filter_ ,改变输入数据的一种机制。 - _directive_ ,与 DOM 结构相关联的,特定功能的封装形式。 上面的这几个概念基本上就是 ng 的全部。每一部分都可以自由定义,使用时通过各要素的相互配合来实现我们的业务需求。 我们从最开始一致打交道的东西基本上都是 _controller_ 层面的东西。在前面,也介绍了 _module_ 和 _service_ 的自定义。剩下的会介绍 _filter_ 和 _directive_ 的定义。基本上这几部分的定义形式都是一样的,原理上是通过 `provider` 来做注入形式的声明,在实际操作过程中,又有很多 shortcut 式的声明方式。 过滤器的自定义是最简单的,就是一个函数,接受输入,然后返回结果。在考虑过滤器时,我觉得很重要的一点: **无状态** 。 具体来说,过滤器就是一个函数,函数的本质含义就是确定的输入一定得到确定的输出。虽然 _filter_ 是定义在 _module_ 当中的,而且 _filter_ 又是在 _controller_ 的 DOM 范围内使用的,但是,它和具体的 _module_ , _controller_ , _scope_ 这些概念都没有关系(虽然在这里你可以使用 js 的闭包机制玩些花样),它仅仅是一个函数,而已。换句话说,它没有任何上下文关联的能力。 过滤器基本的定义方式: var app = angular.module('Demo', [], angular.noop); app.filter('map', function(){ var filter = function(input){ return input + '...'; }; return filter; }); 上面的代码定义了一个叫做 `map` 的过滤器。使用时:

示例数据: {{ a|map }}

过滤器也可以带参数,多个参数之间使用 `:` 分割,看一个完整的例子: 1
2

示例数据: {{ a|map:map_value:'>>':'(no)' }}

3

示例数据: {{ b|map:map_value:'>>':'(no)' }}

4
5 6 7
';

AngularJS与其它框架的混用(jQuery, Dojo)

最后更新于:2022-04-01 22:40:19

# 16. AngularJS与其它框架的混用(jQuery, Dojo) 这个问题似乎很多人都关心,但是事实是,如果了解了 ng 的工作方式,这本来就不是一个问题了。 在我自己使用 ng 的过程当中,一直是混用 jQuery 的,以前还要加上一个 Dojo 。只要了解每种框架的工作方式,在具体的代码中每个框架都做了什么事,那么整体上控制起来就不会有问题。 回到 ng 上来看,首先对于 jQuery 来说,最开始说提到过,在 DOM 操作部分, ng 与 jQuery 是兼容的,如果没有 jQuery , ng 自己也实现了兼容的部分 API 。 同时,最开始也提到过, ng 的使用最忌讳的一点就是修改 DOM 结构——你应该使用 ng 的模板机制进行数据绑定,以此来控制 DOM 结构,而不是直接操作。换句话来说,在不动 DOM 结构的这个前提之下,你的数据随便怎么改,随便使用哪个框架来控制都是没问题的,到时如有必要使用 `$scope.$digest()` 来通知 ng 一下即可。 下面这个例子,我们使用了 jQuery 中的 _Deferred_ ( `$.ajax` 就是返回一个 _Deferred_ ),还使用了 ng 的 _$timeout_ ,当然是在 ng 的结构之下: 1 2 3 4 5 AngularJS 6 7 8 9
10 {{ a }} 11
1213 41 42 再把 Dojo 加进来看与 DOM 结构相关的例子。之前说过,使用 ng 就最好不要手动修改 DOM 结构,但这里说两点: 1. 对于整个页面,你可以只在局部使用 ng ,不使用 ng 的地方你可以随意控制 DOM 。 1. 如果 DOM 结构有变动,你可以在 DOM 结构定下来之后再初始化 ng 。 下面这个例子使用了 _AngularJS_ , _jQuery_ , _Dojo_ : 1 2 3 4 5 AngularJS 6 8 9 1011
1213

14 15

1617

18 19

2021

22 23 24

2526

27 需要编辑的内容: 28 29

3031
32
33
3435
363738 94 95
';

实例

最后更新于:2022-04-01 22:40:17

### 15.5. 实例 _ngResource_ 要举一个实例是比较麻烦的事。因为它必须要一个后端来支持,这里如果我用 Python 写一个简单的后端,估计要让这个后端跑起来对很多人来说都是问题。所以,我在几套公共服务的 API 中纠结考察了一番,最后使用 [www.rememberthemilk.com](http://www.rememberthemilk.com) 的 API 来做了一个简单的,可用的例子。 例子见: [http://zouyesheng.com/demo/ng-resource-demo.html](http://zouyesheng.com/demo/ng-resource-demo.html) (可以直接下载看源码) 先说一下 API 的情况。这里的请求调用全是跨域的,所以交互上全部是使用了 JSONP 的形式。 API 的使用有使用签名认证机制,嗯, js 中直接算 md5 是可行的,我用了一个现成的库(但是好像不能处理中文吧)。 这个例子中的 `LoginCtrl` 大家就不用太关心了,参见官方的文档,走完流程拿到 _token_ 完事。与 _ngResource_ 相关的是 `MainCtrl` 中的东西。 其实从这个例子中就可以看出,目前 _ngResource_ 的机制对于服务端返回的数据的格式是严重依赖的,同时也可以反映出 _$http_ 对一些场景根本无法应对的局限。所以,我现在的想法是理解 _ngResource_ 的思想,真正需要的人自己使用 _jQuery_ 重新实现一遍也许更好。这应该也花不了多少时间, _ngResource_ 的代码本来不多。 我为什么说 _$http_ 在一些场景中有局限呢。在这个例子当中,所有的请求都需要带一个签名,签名值是由请求中带的参数根据规则使用 md5 方法计算出的值。我找不到一个 hook 可以让我在请求出去之前修改这个请求(添加上签名)。所以在这个例子当中,我的做法是根据 _ngResource_ 的请求最后会使用 _$httpBackend_ 这个底层服务,在 module 定义时我自己复制官方的相关代码,重新定义 _$httpBackend_ 服务,在需要的地方做我自己的修改: script.src = sign_url(url); 不错,我就改了这一句,但我不得不复制了 50 行官方源码到我的例子中。 另外一个需要说的是对返回数据的处理。因为 _ngResource_ 会使用返回的数据直接填充实例,所以这个数据格式就很重要。 首先,我们可以使用 `$http.defaults.transformResponse` 来统一处理一下返回的数据,但是这并不能解决所有问题,可目前 _ngResource_ 并不提供对每一个 _action_ 的单独的后处理回调函数项。除非你的服务端是经过专门的适应性设计的,否则你用 _ngResource_ 不可能爽。例子中,我为了获取当前列表的结果,我不得不自己去封装结果: var list_list = List.getList(function(){ var res = list_list[1]; while(list_list.length > 0){list_list.pop()}; angular.forEach(res.list, function(v){ list_list.push(new List({list: v})); }); $scope.list_list = list_list; $scope.show_add = true; return; });
';

定义和使用时的占位量

最后更新于:2022-04-01 22:40:15

### 15.4. 定义和使用时的占位量 两方面。一是在定义时,在其 URL 中可以使用变量引用的形式(类型于定义锚点路由时那样)。第二时定义默认 _params_ ,即 GET 参数时,可以定义为引用 _postData_ 中的某变量。比如我们这样改一下: var Book = $resource('/book/:id', {}, actions); var book = Book.read({id: '123'}, {}, function(response){ console.log(response); }); 在 URL 中有一个 `:id` ,表示对 _params_ 中 `id` 这个变量的引用。因为 `read` 是一个 POST 请求,根据调用形式,第一个参数是 _params_ ,第二个参数是 _postData_ 。这样的调用结果就是,我们会发一个 POST 请求到如下地址, _postData_ 为空: /book/123?_method=read 再看默认的 _params_ 中引用 _postData_ 变量的形式: var Book = $resource('/book', {id: '@id'}, actions); var book = Book.read({title: 'xx'}, {id: '123'}, function(response){ console.log(response); }); 这样会出一个 POST 请求, _postData_ 内容中有一个 `id` 数据,访问的 URL 是: /book?_method=read&id=123&title=xx 这两个机制也可以联合使用: var Book = $resource('/book/:id', {id: '@id'}, actions); var book = Book.read({title: 'xx'}, {id: '123'}, function(response){ console.log(response); }); 结果就是出一个 POST 请求, _postData_ 内容中有一个 `id` 数据,访问的 URL 是: /book/123?_method=read&title=xx
';

基本使用

最后更新于:2022-04-01 22:40:12

### 15.3. 基本使用 在定义了资源之后,我们看如果使用这些资源,发出请求: var book = Book.read({id: '123'}, function(response){ console.log(response); }); 这里我们进行 _Book_ 的“类”方法调用。在方法的使用上,根据官方文档: HTTP GET "class" actions: Resource.action([parameters], [success], [error]) non-GET "class" actions: Resource.action([parameters], postData, [success], [error]) non-GET instance actions: instance.$action([parameters], [success], [error]) 我们这里是第二种形式,即类方法的非 GET 请求。我们给的参数会作为 `postData` 传递。如果我们需要 GET 参数,并且还需要一个错误回调,那么: var book = Book.read({get: 'haha'}, {id: '123'}, function(response){ console.log(response); }, function(error){ console.log(error); } ); 调用之后,我们会立即得到的 `book` ,它是 Book 类的一个实例。这里所谓的实例,实际上就是先把所有的 _action_ 加一个 **$** 前缀放到一个空对象里,然后把发出的参数填充进去。等请求返回了,把除 _action_ 以外的成员删除掉,再把请求返回的数据填充到这个对象当中。所以,如果我们这样: var book = Book.read({id: '123'}, function(response){ console.log(book); }); console.log(book) 就能看到 `book` 实例的变化过程了。 现在我们得到一个真实的实例,看一下实例的调用过程: //响应的数据是 {result: 0, msg: '', obj: {id: 'xxx'}} var book = Book.create({title: '测试标题', author: '测试作者'}, function(response){ console.log(book); }); 可以看到,在请求回调之后, `book` 这个实例的成员已经被响应内容填充了。但是这里有一个问题,我们返回的数据,并不适合一个 book 实例。格式先不说,它把 `title` 和 `author` 这些信息都丢了(因为响应只返回了 `id` )。 如果仅仅是格式问题,我们可以通过配置 _$http_ 服务来解决( AJAX 请求都要使用 _$http_ 服务的): $http.defaults.transformResponse = function(data){return angular.fromJson(data).obj}; 当然,我们也可以自己来解决一下丢信息的问题: var p = {title: '测试标题', author: '测试作者'}; var book = Book.create(p, function(response){ angular.extend(book, p); console.log(book); }); 不过,始终会有一些不方便了。比较正统的方式应该是调节服务器端的响应,让服务器端也具有和前端一样的实例概念,返回的是完整的实例信息。即使这样,你也还要考虑格式的事。 现在我们得到了一个真实的 `book` 实例了,带有 `id` 信息。我们尝试一下实例的方法调用,先回过去头看一下那三种调用形式,对于实例只有第三种形式: non-GET instance actions: instance.$action([parameters], [success], [error]) 首先解决一个疑问,如果一个实例是进行一个 GET 的调用会怎么样?没有任何问题,这当然没有任何问题的,形式和上面一样。 如何实例是做 POST 请求的话,从形式上看,我们无法控制请求的 _postData_ ?是的,所有的 POST 请求,其 _postData_ 都会被实例数据自动填充,形式上我们只能控制 _params_ 。 所以,如果是在做修改调用的话: book.$update({title: '新标题', author: '测试作者'}, function(response){ console.log(book); }); 这样是没有意义的并且错误的。因为要修改的数据只是作为 GET 参数传递了,而 `postData` 传递的数据就是当前实例的数据,并没有任何修改。 正确的做法: book.title = '新标题' book.$update(function(response){ console.log(book); }); 显然,这种情况下,回调都可以省了: book.title = '新标题' book.$update();
';