AngularJS Developer Guide 开发指南学习笔记
Table of Contents
1 Introduction & Conceptual Overview
HTML本身是静态的,对于现在的动态网页无能为力.为了弥合静态和动态之间
的鸿沟,有两种静态方式:库(jQuery
),框架(durandal
,
ember
). AngularJS
则提供了第三种方式,通过数据绑定等方式让浏览
器学会新的语法. AngularJS
是完全基于前端的,适用于CRUD应用.
2 DI
AngularJS中的模组类似于名字空间,在几乎所有东西都在名字空间下注册.这 些注册的东西是通过名字推断来进行绑定(引用)的.AngularJS自带的那功能,都 是可以直接引用的.自己定义的东西,也可以直接靠名字推定,但是要给模组 加上依赖关系.
service, directive, filter 都是通过 factory/directive/filter
方
法注册在模组中的,这些方法统称工厂(factory)方法.
我们可以通过 config/run
方法注册在模组配置和运行的时候需要运行的
函数,这些函数也同样的可以被以类似上面的方式来调用.这些方法被归类为
模组(module)方法.
控制器(controller),被解释为classes或者constructor function,同样需 要注册到模组中,通过名字来引用.
这些东西在引用的时候,可以直接当函数参数名,可以用 $inject 来显示 说明,也可以用数组.后两种基本上是在第一种的基础上多写些代码来说明.直 接用函数参数名是最简单的,但是不被官方所推荐.
3 Data Binding & Scopes & Template
传统模板是通过模板Tempalte和模型Model来建立视图View. AngularJS是通过模
板建立起View和Model的对应,让View跟Model直接互动.
AngularJS中,template是包含AngularJS元素和属性的HTML.template中可用
的AngularJS元素和属性为 Directive, Markup (), Filter,
Form controls.通过这些template,JS(model)和(view)被连接了起来(数
据绑定).
上下文(Scope)提供表达式的运行环境,它被组织成一个类似DOM的层级结构.上 下文监视表达式并传递事件.API($watch)监视模型的变更,($apply)将任意 由Angular之外的事件对模型的改变传递到View.这里所谓的Angular之外的 事件,指的是controllers,services,event handlers.
上下文之间可以嵌套,嵌套的上下文有两种 child scope 和 isolate scope, child scope 继承上层上下文的内容, isolate scope 不继承. 如果上下文中没有对应的属性,表达式是没有任何作用的.
上下文可以看做将应用控制器controller和视图view联系起来的胶水.
在template连接的时候,directive会设置上下文中的$watch,$watch会通知
directive任何属性的变更,从而使directive能根据更新的数据来渲染DOM
控制器controller和directive都能直接访问scope,但是不能直接访问对方.这
种设计孤立controller和directive. $rootScope
提供对root上下文的访
问,一般指向ng-app所在的范围.
如果用chrome的inspect element功能查看页面元素,会发现
class="ng-scope ng-binding"
之类的属
性.Anuglar会自动将 ng-scope 加入制定了上下文的元素, ng-binding
则说明这里面的内容是用绑定实现的.
在不同层级使用同一个controller,
<div ng-controller="EventController"> Root scope <tt>MyEvent</tt> count: <ul> <li ng-repeat="i in [1]" ng-controller="EventController"> <button ng-click="$emit('MyEvent')">$emit('MyEvent')</button> <button ng-click="$broadcast('MyEvent')">$broadcast('MyEvent')</button> <br> Middle scope <tt>MyEvent</tt> count: <ul> <li ng-repeat="item in [1, 2]" ng-controller="EventController"> Leaf scope <tt>MyEvent</tt> count: </li> </ul> </li> </ul> </div>
angular.module('eventExample', []) .controller('EventController', ['$scope', function($scope) { $scope.count = 0; $scope.$on('MyEvent', function() { $scope.count++; }); }]);
这个例子中,首先我们看JS的部分.这部分注册了一个EventController,这个 controller对于给定的上下文 $scope 设定属性 count,同时注册一个 名为 MyEvent 的事件,调用一个闭包函数对于当前上下文进行操作.
接下来看HTML,里面把同一个controller用了三次,也就意味着对于
eventExample,给予三个不同的 $scope.而这三个 $scope 各自拥有自
己的 MyEvent 函数副本.最后, $emit('MyEvent')
将调用这个函数的
动作传递给上层 $scope, $broadcast('MyEvent')
则传递给下层
$scope.
$scope 的生命周期中,模型的改变不会立即触发 $watch, 而是通过在 $apply 结束时的 $digest 来触发 $watch,这么做的好处在于将多个 改变合为一个 $watch 动作. 当子上下文不需要继续存在时,子上下文的 创建者负责调用 scope.$destroy() 负责停止事件传递,允许垃圾回收来 释放内存.
4 Module & Provider
angular.module会覆盖已存在的名字.模组提供 provider, factory, service, value, constant, animation, filter, controller, directive, config, run 这些方法.除了最后两个以 外,都是注册某一种功能. provider,factory,service,value,constant 都是JS内部用的, directive,filter,animation,controller 都是在html里面用 的(JS里面定义的).
- JS
- provider 提供一个在config阶段配置内容的机会,通过 $get 函数返 回绑定的对象.
- factory 注册一个名字空间,跟value类似,但是可以引用其它的服务
- service 注册一个类,用 new 来获得对象
- value 注册一个变量,也可以在变量下加入函数,但是不能引用其它的服务.
- html
- directive, 定义一个html元素,属性,类名,对相应的元素加入定义好的模块
- controller, 注册一个可在html里面引用的属性值,对相应元素中的变 量进行绑定和操作.
- animation, 动作效果
- html and JS
- filter, 过滤器. 返回一个过滤函数并绑定在这个名字上. 过滤函数接 受整个数组为第一个参数,还可以自定附加参数.
Provider是AngularJS的基础,Value,Factory,Service,Constant都建立在Provider之上.下
面的 myApp.XXX
方法实际上都是调用 $provider.XXX
.
- Value recipe
var myApp = angular.module('myApp', []); myApp.value('clientId', 'a12345654321x'); myApp.controller('DemoController', ['clientId', function DemoController(clientId) { this.clientId = clientId; }]);
<html ng-app="myApp"> <body ng-controller="DemoController as demo"> Client ID: </body> </html>
其实就是定义一个模组下的变量/对象,可以是简单值,也可以在这个变量 下加入函数.
- Factory recipe
相对Value,可使用其他服务,初始化服务,惰性初始化. 使用
myApp.factory
方法.返回的是一系列变量,函数. - Service recipe
使用
myApp.service()
方法,注册一个构造函数,得到一个类(class), 用 new 来得到新实例.返回的是构造函数. - Provider Recipe
比Factory更高一层,能够让用户在使用服务之前进行定制化,用config函
数来实现.Provider里面有个
$get
方法,DI会把这个方法返回的东西绑 定到注册的Provider名字上.myApp.provider('AAA', function AAAProvider(){ this.configFunc = function(bla){blabla}; this.$get = function(lab){ return BBB}; }); myApp.config(function(AAAProvider){ AAAProvider.configFunc(...); }); myApp.controller('CCC', function(AAA){ /*AAA is BBB*/});
provider 只能在config里面,而在run里面,只能用非 provider.
- Constant Recipe
Constant在config和run的过程中都存在(在他们之前).用
myApp.constant
来注册. - Special Purpose Objects 包括controller, directive, filter, animation.
4.1 Service
服务都是惰性加载和单例,通过 factory 注册.服务本身对于html是不可 见的.在JS里面引用其功能. 除了用工厂方法意外,还可以在模组的config方法中通过 $provie.factory 来 注册服务.这里的Service和前面Provider段落中的不是一个概念.
4.2 Controller
控制器是用于连接上下文和服务的,通过 controller 注册,对于html可见,通 过 ng-controller 来进行绑定.本身也可以定义一些行为.不需要返回什 么东西.
5 Directive
Directive提供自己定义的HTML标记,基于AngularJS HTML compiler
($compile
). ngApp, ngRepeat 等等就是AngularJS自带的
directive,这些directive通过 ng-app, ng-repeat 标记在HTML文件中
改变元素的行为模式.当然,这里的compile并不是真正意义上的编
译,AngularJS只是将事件监听器绑定到HTML元素上,使得这些元素可以互动.
在 $compile
的API中可以找到directive的各种option的详细说明.
命名方面, -,:,_
都可以作为分隔符, ng-model
和 ng:model
是等
效的.同时还可以在前面加上 x-
或者 data-
前缀, ng-model
和
data-ng:model
是等效的.
使用directive可以将它作为tag name, attribute name, comment 或者 class name.
<my-dir></my-dir> <span my-dir="exp"></span> <!-- directive:my-dir exp--> <span class="my-dir: exp"></span>
以上三种标记方式是等效的.当然,推荐使用tag name和attribute name.
ngAttr 能够绕过浏览器限制使用AngularJS的表达式设定attribute.
<circle cx=""></circle> <circle ng-attr-cx=""></circle> <svg viewBox=""></svg> <svg ng-attr-view-box=""></svg>
Directive返回固定的option.如果说Service(Factory)是注册名字和对应的属性,函数;Directive则是将值和函数绑定到预先定义好的选项
5.1 注册Directive
module.directive 函数注册directive,它接受一个名字和一个工厂方法.方 法返回template,用其填充directive元素的内容.
5.2 restrict option
值为 ‘A’ (match attribute), ‘E’ (match element), ‘C’ (match class).指定这个directive被用作元素的属性,元素名或类
5.3 link option
操作DOM.这个option需要指定一个函数,函数的参数为 scope (上下文),element (当前元素),attrs (当前元素的属性), ctrl (array of controllers required by the directive).
angular.module('docsTimeDirective', []) .controller('Controller', ['$scope', function($scope) { $scope.format = 'M/d/yy h:mm:ss a'; }]) .directive('myCurrentTime', ['$interval', 'dateFilter', function($interval, dateFilter) { function link(scope, element, attrs, ctrl) { var format, timeoutId; function updateTime() { element.text(dateFilter(new Date(), format)); } scope.$watch(attrs.myCurrentTime, function(value) { format = value; updateTime(); }); element.on('$destroy', function() { $interval.cancel(timeoutId); }); // start the UI update process; save the timeoutId for canceling timeoutId = $interval(function() { updateTime(); // update DOM }, 1000); } return { link: link }; }]);
设定函数 updateTime 直接操纵元素内容,通过 $watch 将元素中的属 性变动跟操作函数绑定在一起. 通过 $interval 来实现自动读秒更新时 间. 绑定 $destroy 来终止 $interval 读秒.
5.4 isolate scope
angular.module('docsIsolateScopeDirective', []) .controller('Controller', ['$scope', function($scope) { $scope.naomi = { name: 'Naomi', address: '1600 Amphitheatre' }; $scope.igor = { name: 'Igor', address: '123 Somewhere' }; }]) .directive('myCustomer', function() { return { restrict: 'E', scope: { customerInfo: '=info' }, templateUrl: 'my-customer-iso.html' }; });
<div ng-controller="Controller"> <my-customer info="naomi"></my-customer> <hr> <my-customer info="igor"></my-customer> </div>
controller里面绑定了 naomi 和 ingor 的值,html里面将directive
元素中的 info 绑定为 naomi 或 ingor. directive里面定义独立
上下文,将 customerInfo 绑定到元素里的
info. my-customer-iso.html 里面引用 customerInfo 的内容. 如果要
引用 <div bind-to-this="thing">
,
在directive里面应该用
=bindToThis
.如果isolate scope里的
属性名跟directive标签中的一样,则可以缩写为 XXX:
'='
.
注意:如同之前提到过的,isolate scope没有继承高级上下文的内容,在这
里面调用高级上下文中的变量,只能是空
template里面或者templateUrl引用的html里面的内容继承的是directive
的上下文
5.5 transclude
给予外部上下文访问能力.同时也意味着之前isolate scope 被遮盖了,无法访问.
angular.module('docsIsoFnBindExample', []) .controller('Controller', ['$scope', '$timeout', function($scope, $timeout) { $scope.name = 'Tobias'; $scope.hideDialog = function () { $scope.dialogIsHidden = true; $timeout(function () { $scope.dialogIsHidden = false; }, 2000); }; }]) .directive('myDialog', function() { return { restrict: 'E', transclude: true, scope: { 'close': '&onClose' }, templateUrl: '<div class="alert"><a href class="close" ng-click="close()">×</a><div ng-transclude></div></div>' }; });
<div ng-controller="Controller"> <my-dialog ng-hide="dialogIsHidden" on-close="hideDialog()"> Check out the contents, ! </my-dialog> </div>
点击动作为ng-click="close()"
,而
close
动作被绑定到 &onClose
这个directive. on-close
又在
html里面绑定到 hideDialog
函数上. 这个示例中显示了如何将函数表
达式传递给directive,让directive在一定时候选择执行这个函数.scope中
读取值用’@XX’,读取表达式结果用’=XX’,读取表达式但不执行用’&XX’.
5.6 scope
不管是template还是templateUrl都是在directive的上下文下.当没有
isolate scope的时候,直接访问外部上下文.有isolate scope的时候,只能
访问isolate scope.这个时候html里面元素的内容,比如
<AA>blabla</AA>
里面的 blabla 是被忽略/覆盖的.
当在template中加入一个含有transclude的元素的时候,html里面的
blabla 会被显示在这个元素之中,而这个含有transclude的元素的内容
却不会被显示,同时html里面的表达式无法访问isolate scope.
... return { template: <div>CONTENT IN JAVASCRIPT (DIRECTIVE SCOPE (OUTER/ISOLATE SCOPE))</div><div ng-transclude>CONTENT FROM HTML (OUTER SCOPE)</div> }
5.7 controller in directive.
6 Filters
在html里面用的时候
在JS里面使用一个叫做 filter 的过滤器
angular.module('FilterInControllerModule', []). controller('FilterController', ['filterFilter', function(filterFilter) { this.array = [ {name: 'Tobias'}, {name: 'Jeff'}, {name: 'Brian'}, {name: 'Igor'}, {name: 'James'}, {name: 'Brad'} ]; this.filteredArray = filterFilter(this.array, 'a'); }]);
创建过滤器
angular.module('myReverseFilterApp', []) .filter('reverse', function() { return function(input, uppercase) { input = input || ''; var out = ""; for (var i = 0; i < input.length; i++) { out = input.charAt(i) + out; } // conditional based on optional argument if (uppercase) { out = out.toUpperCase(); } return out; }; })
$filter
服务通过名字调用相应的过滤器.