AngularJS之禅

AngularJS是客户端MVC框架,它运行在web浏览器中,有助于我们写单页面、AJAX风格的web应用,是一个通用的框架。
 
AngularJS速成
实例:Hello World
首先引用AngularJS库angular.js。使用自定义的ng-app的HTML属性来运行。仔细观察<body>标签,有另一个非标准的HTML属性:ng-init,渲染模版前使用ng-init来初始化model。
AngularJS模版系统重要特性:
  使用自定义HTML标签和属性为静态html文档添加动态行为
  使用双大括号{{}}输出模型值
AngularJS中,框架理解和解释的所有特别的HTML标签和属性都可以称为指令。

双向数据绑定
不需要刷新模板,不需要使用框架API来更新模型,AngularJS可以智能检测模型变化且相应的更新DOM,任何视图的变化都会立刻反应到模型中,任何模型的变化也会反应到模版上。
 
AngularJS中的MVC模式
Scope
scope对象是提供模型给视图(模板)。通过将属性赋值给scope的实例,可以生成新值来渲染模板。对于给定视图,scope能够用数据和特定的功能来扩充。可以通过在scope实例上定义功能提供具体的UI逻辑给模板。
例如可以为name变量创建一个getter函数:
可以在模版中使用函数:
scope使我们能够精确控制domain模型的哪部分、哪些操作对视图层可用。
 
Controller
主要职责是初始化scope对象。具体包括:
  提供初始模型值
  通过UI交互(函数)来扩充scope
controller是正常的js功能函数。没有扩展任何具体框架类,不调用任何特定的AngularJS的api来执行操作,也不操作html模板和dom。
 
Model
仅是js的对象,不继承任何框架基类,不以任何特别方式构建模型对象。为了提供模型给AngularJS,仅需要将模型赋值给scope。
 
深入理解scope
每个$scope是Scope类的实例,Scope类有方法控制scope生命周期,提供事件传播机制,支持模版渲染过程。
 
scopes层级
ng-controller指令使用scope.$new()方法创建新的scope。angularjs有个根作用域$rootScope(所有其他作用域的父亲),新建应用时就有rootScope。
ng-controller指令是scope创建指令的例子。AngularJS碰到dom树中scope创建指令时就会创建scope类的一个新的实例。(ng-repeat可以生成多个scope实例)新创建的scope使用parent属性指向其父亲。
 
scope层级和继承
一个scope上定义的属性可以对所有子scope可见,只是子scope 不能使用相同的名字重定义这个属性。
scope的继承跟原型链继承一样。继承中读很容易理解,写有点复杂。
如下代码:
运行此代码,name变量在整个应用中可见,即使它只在最高层scope中定义。
跟js原型继承一样,子类的值不会影响父类的值。如果要改变父域的值,有几种方式:
1、使用$parent属性
通过直接指向父scope解决问题,麻烦的是,使用ng-model指令表达式作用在整个DOM结构上,在其他<input>标签上插入另一个scope-creating指令,$parent将会指向另一个完全不同的scope. 所以应该避免使用$parent属性。 
2、将属性绑定到一个对象上,并不直接指向一个scope属性。如下:
这个方法不会在DOM树结构上假定任何属性。避免直接绑定到scope的属性上。绑定到对象属性上是更好的方法。如ng-model="thing.name"
 
scope层级关系和事件系统
层级上的scope可用来作为事件总线。AngularJS让我们通过scope的层级关系传播命名事件。事件可以从任意一个scope上发出,向上($emit)或者向下($broadcast)传播。
 
AngularJS核心服务和指令使用这个事件总线标志应用状态的重要变化。例如,当location(URL)变化时可以监听$locationChangeSucess事件:
每个scope实例上的$on方法可以用来注册scope-event处理函数。类似于DOM事件,可以在event对象上调用preventDefault()和stopPropagation()方法。stopPropagation()方法将会阻止事件冒泡到scopes层级上,仅可以通过($emit)来向上发送事件。
在整个AngularJS框架中,只有三个emit的事件($includeContentRequested,$includeContentLoaded,$viewContentLoaded),和几个broadcast的事件($locationChangeStart,$loactionChangeSuccess,$routeUpdate,$routeChangeStart,$routeChangeSuccess,  $routeChangeError ,$destroy)。scope事件使用非常谨慎,在发出自定义事件之前需要评估其他选项(主要是双向数据绑定)。
 
scope生命周期
scope提供了独立的命名空间,避免变量名冲突。在层级上组织scopes有利于管理内存。当scopes中的一个scope不需要了,可以destroy。因此,这个scope上的model和functionality也会被垃圾回收。可以通过scope-creating指令创建新的scope,可以通过调用$new()和$destroy()方法手动创建和销毁scope.
 
View
声明式模板视图
AngularJS提倡声明式方法构建UI,即模版集中于描述想要的效果而不是如何实现。举例如下:
需要创建一个表单,用户输入一段message并发送。message的长短限制在100个字符,如果长度超出,Send button将会disable。用户需要知道还可以输入多少字符。如果剩下的字符小于10,显示字符数的style将会变化来提醒用户。如下所示:
初步实现如下所示:
 
加上显示的剩余字符数:
其次,如果message不符合长度限制,需要使Send按钮失效。
当仅有几个字符剩下时需要改变样式:
 
声明式指令表达想要的结果,避免操作DOM元素。
 
模块和依赖注入
模块化
如何将全局定义的controller简化为module。
使用modules变为:
 
module像是angularjs已组织的对象(controllers,services等)的容器。为了定义一个module,需要提供第一个参数name。第二个参数来表示依赖的其他模块。
调用angular.module函数返回一个新创建的module实例,进入这个实例,可以定义controllers。controller函数有以下参数:
     Controller名称
     Controller构造函数
通过ng-app属性告知AngularJS module的存在
协作对象
module不仅可以用来注册angularjs框架直接调用的对象如controllers、filters等,也可以是任何应用开发人员定义的对象。还可以描述这些对象之间的依赖关系。
 
注入依赖
scope对象可以注入到控制器实例中。AngularJS可以找出一个controller需要的新的scope实例,并能创建一个新的scope实例将scope实例注入到controller中。
angularjs的注入依赖执行以下功能:
  理解需要用对象表示collaborator
  找到需要的collaborator
  将对象包装成全功能的应用
 
注册服务
AngularJS只能连接它知道的对象。第一步注入依赖注入的机制:注册一个module对象,不是直接注册对象实例,而是抛出对象创建方法给AngularJS的依赖注入系统。其次AngularJS解释这些方法将对象实例化后再连接它们。最后连接的对象集合组成了可运行的应用。
AngularJS中,有一个特定的$provide服务可以注册不同的方法来创建对象。注册的方法然后由$injector翻译来提供可以使用的对象实例(有所有的依赖resolved和injected)
$injector服务创建的对象指向了服务,在应用生命周期中,AngularJS将解释给定的方法仅一次,所以仅能创建单个对象实例。
被$injector服务创建的对象即是服务。angularjs仅仅解释给定的方法一次,所以仅仅会创建对象的一个单例。services是单例对象。
AngularJS仅仅是对象实例的集合,我们可以控制这些对象如何创建。
 
Services
不能注册NotificationsService服务为一个value对象,因为需要与存档服务表达依赖关系。依赖其他对象,为一个对象注册方法的最简单的方式是注册一个结构化函数。可使用以下service方法:
NotificationsService结构化函数可以写成如下:
通过使用AngularJS依赖注入,可以从NoificiationsService构造函数中消除新的关键字。这个服务与依赖的实例无关,可接受任何存档服务。应用更灵活!
实际上service方法不常使用,但对于注册预先存在的构造函数还是很方便的,这样AngularJS可以管理由这些构造函数创建的对象。
 
Factories
是另一种创建对象的注册方法,比service更加灵活,因为可以注册任何抽象的对象创建函数,如下:
AngularJS将使用提供的factory函数注册一个返回对象。可以是任意有效的JS对象,包括function对象。
factory方法是将对象放入AngularJS依赖注入系统的最常用方法。
 
Constants
constants可以在模块级别上定义并作为其他协作对象注入。
Constants对于创建services很有用,可以在多个不同应用中重用。有一个缺点是只要一个service依赖于一个constant,这个constant值必须提供。所以有时需要有默认的配置值并允许用户根据需要修改比较好。
 
Providers
所有注册方法只是Provider的特例。注册notificationsService服务为一个provider的例子如下:
首先provider是一个函数,返回包含$get属性的对象。$get属性是一个factory函数,返回一个service实例。可以将providers视为对象,将factory函数嵌入到$get属性中。
其次,从provider函数返回的对象有额外的方法和属性。所以在$get方法触发前可能设置配置选项,也可以设置maxLen属性。可能有更复杂的配置逻辑,因为服务还可以暴露配置方法。
 
模块的生命周期
为了支持providers,AngularJS将模块的生命周期分为两个阶段:
配置阶段:收集并配置所有方法
运行阶段:执行任何后期实例化逻辑
 
配置阶段
providers仅仅在配置阶段配置。providers可配置如下:
依赖有Provider后缀的notificationsServiceProvider对象表示方法准备执行。配置阶段可以对对象创建方法做最后调整。
 
执行阶段
执行阶段可以注册任何需要在application bootstrap上执行的功能,可以将运行阶段视为其他程序中主要方法。AngularJS模块最大的区别是有多个配置和运行块,不止一个入口。
如下:
模板:
不同阶段不同注册方法,总结下创建对象的不同方法以及这些方法对应的module的生命周期:
 
模块依赖
AngularJS不仅可以管理对象依赖,也可以管理模块间的依赖关系。可以很容易的将相关services组成一个module,创建service库。
例如,可以将notifications和archiving services移动到他们自己的modules中,如下:
这种方式每个service(或者相关services的集合)可以组合成一个可复用的实体(a module)。最高级别的模块可以声明所有模块的依赖关系。
 
AngularJS模块可以相互依赖,每个模块有几个services,但是各自的services可以依赖其他的services。这会引发几个有趣的问题:
  一个AngularJS模块上定义的service依赖于另一个模块的services?
  子模块中定义的service可以依赖父模块的service或者其他子模块中定义的services?
  可以仅对特定module中可见的私有模块services吗?
  在不同模块中可以有同样名字的services吗?
 
Services和模块间的可见性
car服务定义在app模块中。app模块声明了对engines模块的依赖,dieselEngine服务定义在这个模块上。car可以被engine的一个实例注入。
定义在相邻模块上的services相互之间都可见。可以将car服务移到单独的模块,然后改变模块依赖,依赖engines和cars模块的应用如下:
在之前的例子中,engine仍然可以注入到car中。
定义在应用中的modules中的service可以对其他所有模块可见。 
因为AngularJS结合所有模块中的所有服务成一个大的应用层级的services,仅有一个给定名字的service。这点可以在依赖一个模块但又想覆盖模块中的某些services情况下使用。可以直接在cars模块中重定义dieselEngine service如下:
 
当前AngularJS版本中,在一个模块上定义的所有services对所有其他模块都可见,无法限制一个模块或者模块子集上的服务的可见性。
 
初写文章,译的不合适的地方,还望大牛们不吝赐教!!!
英文原文:Mastering Web Application Development with AngularJS
原文地址:https://www.cnblogs.com/wwjuan/p/5015242.html