1 开发环境配置
1.1 mac下node js安装
一向不喜欢追求刚刚新鲜出炉的事物的我,终于在node js出来一年后开始迈出脚步。
废话少说,先把环境给配置好。google "mac node",出来几条结果,提供的方法基本一样,遂开始实践之。附google后的安装步骤:
1、安装xcode、git;
2、使用git将node源码拉到本地git clone git://github.com/ry/node.git
3、./configure
4、make
5、make install
安装过程中出来的几个问题,首先./configure的时候,有几个东西没有: openssl -> not found, library -> not found, fdatasync(2) with c -> no。
然后make的时候悲催的报错了:build failed: -> task failed(err #1):...。
之后,我又重复了好几遍,都是没有成功,开始怀疑是否是openssl,library rt缺失导致的,准备装openssl神马的,顺便群里面问了句,有朋友建议使用node0.6.1,然后跑到github上,download了最新版本的源码,重复google上的安装步骤,'build' finished successfully, 'install' finished successfully,搞定!
1.2 AngularJS开发环境配置
1.2.1 测试用server环境
无论是Mac、Linux或Windows环境中,您均可遵循本教程学习编程。您可以使用源代码管理版本控制系统Git获取本教 程项目的源代码文件,或直接从网上下载本教程项目源代码文件的镜像归档压缩包。
1. 您需要安装Node.js和Testacular来运行本项目,请到Node.js官方网站下载并安装最新版,然后把node可执 行程序路径添加到系统环境变量PATH中,完成后在命令行中运行一下命令可以查看是否安装成功:
node-version
2. 然后安装Testacular单元测试程序,请运行如下命令:
npm install -g
testacular
3. 安装Git工具,然后用以下命令从Github复制本教程项目的源代码文件:
git clone git://github.com/angular/angular-phonecat.git
您也可以直接从网上下载本教程项目源代码的镜像归档压缩包。这个命令会在您当前文件夹中建立新文件夹angular-phonecat。
4. 最后一件事要做的就是确保您的计算机安装了web浏览器和文本编辑器。
5. 进入教程源代码文件包angular-phonecat,运行服务器后台程序,开始学习AngularJS!
cd angular-phonecat
node scripts/web-server.js
1.3 WebStorm中配置AngularJS
AngularJS最理想开发工具WebStorm
http://blog.fens.me/angularjs-webstorm-ide/
2 AngularJS基础
2.1 语法
2.1.1 指令
ng-app -该指令启动一个AngularJS应用。
ng-init -该指令初始化应用程序数据。
ng-model -此指令定义的模型,该模型是变量在AngularJS使用。
ng-repeat -该指令将重复集合中的每个项目的HTML元素。
2.1.1.1 ng-app属性
ng-app指令:
<html lang="en" ng-app>
ng-app指令标记了AngularJS脚本的作用域,在<html>中添加ng-app属性即说明整个<html>都是AngularJS脚本作用域。开发者也可以在局部使用ng-app指令,如,则AngularJS脚本仅在该<div>中运行。
2.1.1.2 ng-repeat属性
在<li>标签里面的ng-repeat="phone in phones"语句是一个 AngularJS 迭代器。这个迭代器告诉 AngularJS 用第一个<li>标签作为模板为列表中的每一部手机创建一个<li>元素。
2.1.1.3 ng-init
2.1.1.4 ng-model
2.1.1.5 ng-class
ng-class用来给元素绑定类名,其表达式的返回值可以是以下三种:
1) 类名字符串,可以用空格分割多个类名,如’redtext boldtext’;
2) 类名数组,数组中的每一项都会层叠起来生效;
3) 一个名值对应的map,其键值为类名,值为boolean类型,当值为true时,该类会被加在元素上。
下面来看一个使用map的例子:
ng-class测试
红色 加粗 删除线
map:{redtext:{{red}},boldtext:{{bold}}, striketext:{{strike}}}
如果你想拼接一个类名出来,可以使用插值表达式,如:
<div class=”{{style}}text”>字体样式测试</div>
然后在controller中指定style的值:
注意我用了class而不是ng-class,这是不可以对换的,官方的文档也未做说明,姑且认为这是ng的语法规则吧。
与ng-class相近的,ng还提供了ng-class-odd、ng-class-even两个指令,用来配合ng-repeat分别在奇数列和偶数列使用对应的类。这个用来在表格中实现隔行换色再方便不过了。
2.1.1.6 ng-style
ng-style用来绑定元素的css样式,其表达式的返回值为一个js对象,键为css样式名,值为该样式对应的合法取值。用法比较简单:
<div ng-style="{color:'red'}">ng-style测试</div>
<div ng-style="style">ng-style测试</div>
$scope.style = {color:'red'};
2.1.1.7 ng-show,ng-hide
ng-show,ng-hide
对于比较常用的元素显隐控制,ng也做了封装,ng-show和ng-hide的值为boolean类型的表达式,当值为true时,对应的show或hide生效。框架会用display:block和display:none来控制元素的显隐。
2.1.1.8 表单控件功能相关的
三、表单控件功能相关的
对于常用的表单控件功能,ng也做了封装,方便灵活控制。
ng-checked 控制radio和checkbox的选中状态
ng-selected 控制下拉框的选中状态
ng-disabled 控制失效状态
ng-readonly 控制只读状态
以上指令的取值均为boolean类型,当值为true时相关状态生效,道理比较简单就不多做解释。 注意: 上面的这些只是单向绑定,即只是从数据到模板,不能反作用于数据。要双向绑定,还是要使用 ng-model 。
2.1.1.9 事件绑定相关指令
四、事件绑定相关
事件绑定是javascrpt中比较重要的一部分内容,ng对此也做了详细的封装,正如我们之前使用过的ng-click一样,其他事件的指令如下:
事件绑定指令的取值为函数,并且需要加上括号,例如:
<select ng-change=”change($event)”></select>
然后在controller中定义如下:
scope.change = function(event){
alert($event.target);
//……………………
}
在模板中可以用变量$event将事件对象传递到controller中。
对于ng的这种设计,一些人有所质疑,视图与事件绑定混在一起到底好不好?我们不是要讲究视图与逻辑分离吗?如此一来,把事件的绑定又变回了内联的,岂不是历史的倒退。我也一样对此表示不解,因为不写onclick已经很多年。。。但既然已经存在了,我们不妨往合理的方向上想一想,或许ng的设计者压根就不想让模板成为单纯的视图层,本来就是想增强HTML,让它有一点业务能力。
2.1.1.10 ng-include包含html指令
使用 AngularJS, 你可以使用ng-include指令来包含 HTML 内容:
实例:
ng-include用法分析以及多标签页面的简单实现方式
http://my.oschina.net/keysITer/blog/630621?p=1
AngularJS包含
http://www.runoob.com/angularjs/angularjs-include.html
2.1.2 AngularJS脚本标签
<script src="lib/angular/angular.js"></script>
这行代码载入angular.js脚本,当浏览器将整个HTML页面载入完毕后将会执行该angular.js脚本,angular.js脚本运 行后将会寻找含有ng-app指令的HTML标签,该标签即定义了AngularJS应用的作用域。
2.1.3 angular.module
Angular Module声明和获取重载
http://www.cnblogs.com/whitewolf/p/angular-module-declare-and-get.html
module是angular中重要的模块组织方式,它提供了将一组内聚的业务组件(controller、service、filter、directive…)封装在一起的能力。这样做可以将代码按照业务领域问题分module的封装,然后利用module的依赖注入其关联的模块内容,使得我们能够更好的”分离关注点“,达到更好的”高内聚低耦合“。”高内聚低耦合“是来自面向对象设计原则。内聚是指模块或者对象内部的完整性,一组紧密联系的逻辑应该被封装在同一模块、对象等代码单元中,而不是分散在各处;耦合则指模块、对象等代码单元之间的依赖程度,如果一个模块的修改,会影响到另一个模块,则说明这两模块之间是相互依赖紧耦合的。
同时module也是我们angular代码的入口,首先需要声明module,然后才能定义angular中的其他组件元素,如controller、service、filter、directive、config代码块、run代码块等。
关于module的定义为:angular.module(‘com.ngbook.demo’, [])。关于module函数可以传递3个参数,它们分别为:
name:模块定义的名称,它应该是一个唯一的必选参数,它会在后边被其他模块注入或者是在ngAPP指令中声明应用程序主模块;
requires:模块的依赖,它是声明本模块需要依赖的其他模块的参数。特别注意:如果在这里没有声明模块的依赖,则我们是无法在模块中使用依赖模块的任何组件的;它是个可选参数。
configFn: 模块的启动配置函数,在angular config阶段会调用该函数,对模块中的组件进行实例化对象实例之前的特定配置,如我们常见的对$routeProvider配置应用程序的路由信息。它等同于”module.config“函数,建议用”module.config“函数替换它。这也是个可选参数。
对于angular.module方法,我们常用的方式有有种,分别为angular.module(‘com.ngbook.demo’, [可选依赖])和angular.module(‘com.ngbook.demo’)。请注意它是完全不同的方式,一个是声明创建module,而另外一个则是获取已经声明了的module。在应用程序中,对module的声明应该有且只有一次;对于获取module,则可以有多次。推荐将angular组件独立分离在不同的文件中,module文件中声明module,其他组件则引入module,需要注意的是在打包或者script方式引入的时候,我们需要首先加载module声明文件,然后才能加载其他组件模块。
从moduleInstance的定义,我们能够看出,angular.module为我们公开的API有:invokeQueue、runBlocks、requires、name、provider、factory、servic、value、constant、animation、filter、controller、directive、config、run。其中invokeQueue和runBlocks是按名约定的私有属性,请不要随意使用,其他API都是我们常用的angular组件定义方法,从invokeLater代码中能看到这类angular组件定义的返回依然是moduleInstance实例,这就形成了流畅API,推荐使用链式定义这些组件,而不是声明一个全局的module变量。
最后,如果传入了第三个参数configFn,则会将它配置到config信息中,当angular进入config阶段时,它们将会依次执行,进行对angular应用或者angular组件如service等的实例化前的配置。
2.1.4 绑定{{ }}
<p>Nothinghere {{'yet' '!'}}</p>
这行代码演示了AngularJS模板的核心功能——绑定,这个绑定由双大括号{{}}和表达式'yet' '!'组成。 这个绑定告诉AngularJS需要运算其中的表达式并将结果插入DOM中,接下来的步骤我们将看到,DOM可以随着表达式运算结果的改变而实时更新。
AngularJS表达式Angular expression是一种类似于JavaScript的代码片段,AngularJS表达式仅在AngularJS的作用 域中运行,而不是在整个DOM中运行。
2.1.5 迭代器过滤
2.1.5.1 简单使用
控制器
我们对控制器不做任何修改。
模板
app/index.html
我们现在添加了一个标签,并且使用AngularJS的$filter函数来处理ngRepeat指令的输入。 这样允许用户输入一个搜索条件,立刻就能看到对电话列表的搜索结果。我们来解释一下新的代码:
数据绑定:这是AngularJS的一个核心特性。当页面加载的时候,AngularJS会根据输入框的属性值名字,将 其与数据模型中相同名字的变量绑定在一起,以确保两者的同步性。
在这段代码中,用户在输入框中输入的数据名字称作query,会立刻作为列表迭代器(phone in phones | filter:query`)其过滤器的输入。当数据模型引起迭代器输入变化的时候,迭代器可以高效得更新DOM将数据模型最新的状态反映出来。
使用filter过滤器:filter函数使用query的值来创建一个只包 匹配query记录的新数组。
ngRepeat会根据filter过滤器生成的手机记录数据数组来自动更新视图。整个过程对于开发者来说都是透明的。
2.1.5.2 定制过滤器
为了创建一个新的过滤器,先创建一个phonecatFilters模块,并且将定制的过滤器注册给这个模块。
app/js/filters.js
angular.module('phonecatFilters', []).filter('checkmark',function() {
returnfunction(input) {
return input ?'u2713' : 'u2718';
};
});
我们的过滤器命名为checkmark。它的输入要么是true,要么是false,并且我们返回两个表示true或false的unicode字符(u2713和u2718)。
现在我们的过滤器准备好了,我们需要将我们的phonecatFilters模块作为一个依赖注册到我们的主模块phonecat上。
app/js/app/js
...
angular.module('phonecat', ['phonecatFilters']).
...
模板
由于我们的模板代码写在app/js/filter.js文件中,所以我们需要在布局模板中引入这个文件。
app/index.html
...
<script src="js/controllers.js"></script>
<script src="js/filters.js"></script>
...
在AngularJS模板中使用过滤器的语法是:
{{ expression | filter }}
我们把过滤器应用到手机详细信息模板中:
app/partials/phone-detail.html
2.1.6 双向绑定
模板
app/index.html
控制器
app/js/controllers.js:
function PhoneListCtrl($scope) {
$scope.phones = [
{"name": "Nexus S",
"snippet":"Fast just got faster with Nexus S.",
"age": 0},
{"name": "Motorola XOOMTM with Wi-Fi",
"snippet": "The Next, Next Generation tablet.",
"age": 1},
{"name": "MOTOROLA XOOMTM",
"snippet": "The Next, Next Generation tablet.",
"age": 2}
];
$scope.orderProp = 'age';
}
2.1.7 $http请求网络服务
服务是通过AngularJS的依赖注入DI子系统来管理的。依赖注入服务可以使你的Web应用良好构建(比如分离表现层、 数据和控制三者的部件)并且松耦合(一个部件自己不需要解决部件之间的依赖问题,它们都被DI子系统所处理)。
$http向你的Web服务器发起一个HTTP请求
app/js/controllers.js
function PhoneListCtrl(scope, http) {
$http.get('phones/phones.json').success(function(data) {
$scope.phones =data;
});
$scope.orderProp= 'age';
}
//PhoneListCtrl.inject = ['scope', '
$http向Web服务器发起一个HTTPGET请求,索取phone/phones.json(注意,url是相对于我们的index.html文件的)。服务器用js on文件中的数据作为响应。(这个响应或许是实时从后端服务器动态产生的。但是对于浏览器来说,它们看起来都是 一样的。为了简单起见,我们在教程里面简单地使用了一个json文件。)
为了使用AngularJS的服务,你只需要在控制器的构造函数里面作为参数声明出所需服务的名字,就像这样:
function PhoneListCtrl(scope, http) {...}
当控制器构造的时候,AngularJS的依赖注入器会将这些服务注入到你的控制器中。当然,依赖注入器也会处理所需 服务可能存在的任何传递性依赖(一个服务通常会依赖于其他的服务)。
注意到参数名字非常重要,因为注入器会用他们去寻找相应的依赖。
2.1.8 '$'前缀命名习惯
你可以创建自己的服务,实际上我们在步骤11就会学习到它。作为一个命名习惯,AngularJS内建服务,作用域方法,以及一些其他的AngularJS API都在名字前面使用一个‘’前缀。不要使用‘’前缀来命名你自己的服务和模型,否则可能会产生名字冲突。
2.1.9 关于JS压缩
由于AngularJS是通过控制器构造函数的参数名字来推断依赖服务名称的。所以如果你要压缩PhoneListCtrl控制器的JS代 码,它所有的参数也同时会被压缩,这时候依赖注入系统就不能正确的识别出服务了。
为了克服压缩引起的问题,只要在控制器函数里面给$inject属性赋值一个依赖服务标识符的数组,就像被注释掉那段 最后一行那样:
PhoneListCtrl.inject =['scope', '
另一种方法也可以用来指定依赖列表并且避免压缩问题——使用Javascript数组方式构造控制器:把要注入的服务放 到一个字符串数组(代表依赖的名字)里,数组最后一个元素是控制器的方法函数:
var PhoneListCtrl = ['scope', 'http', function(scope,http) { /* constructor body */ }];
上面提到的两种方法都能和AngularJS可注入的任何函数完美协作,要选哪一种方式完全取决于你们项目的编程风格,建议使用数组方式。
2.1.10 链接与图片模板
数据
注意到现在phones.json文件包 了唯一标识符和每一部手机的图像链接。这些url现在指向app/img/phones/目录。
app/phones/phones.json(样例片段)
模板
app/index.html
这些链接将来会指向每一部电话的详细信息页。不过现在为了产生这些链接,我们在href属性里面使用我们早已熟悉 的双括号数据绑定。在步骤2,我们添加了{{phone.name}}绑定作为元素内容。在这一步,我们在元素属性中使用{{phone.id}}绑定。
我们同样为每条记录添加手机图片,只需要使用ngSrc指令代替<img>的src属性标签就可以了。如果我们仅仅用一个正常src属性来进行绑定(<img class='diagram' sr="{{phone.imageUrl}}">),浏览器会把AngularJS的{{表达式}}标记直接进行字面解释,并且发起一个向非法url(http://localhost:8000/app/{{phone.imageUrl}})的请求。因为浏览器载入页面时,同时也会请求载 入图片,AngularJS在页面载入完毕时才开始编译——浏览器请求载入图片时{{phone.imageUrl}}还没得到编译!有了这个ngSrc指令会避免产生这种情况,使用ngSrc指令防止浏览器产生一个指向非法地址的请求。
2.2 引导AngularJS应用
通过ngApp指令来自动引导AngularJS应用是一种简洁的方式,适合大多数情况。在高级开发中,例如使用脚本装载应用,您也可以使用bootstrap手动引导AngularJS应用。
AngularJS应用引导过程有3个重要点:
1. 注入器(injector)将用于创建此应用程序的依赖注入(dependency injection);
2. 注入器将会创建根作用域作为我们应用模型的范围;
3. AngularJS 将会链接根作用域中的DOM,从用ngApp 标记的 HTML 标签开始,逐步处理 DOM 中指令和绑定。
一旦AngularJS应用引导完毕,它将继续侦听浏览器的HTML触发事件,如鼠标点击事件、按键事件、HTTP传入响应等 改变DOM模型的事件。这类事件一旦发生,AngularJS将会自动检测变化,并作出相应的处理及更新。
2.3 视图和模板
在AngularJS中,一个视图是模型通过HTML**模板**渲染之后的映射。这意味着,不论模型什么时候发生变化,Angul arJS会实时更新结合点,随之更新视图。
比如,视图组件被AngularJS用下面这个模板构建出来:
我们刚刚把静态编码的手机列表替换掉了,因为这里我们使用ngRepeat指令和两个用花括号包裹起来的AngularJS表 达式——{{phone.name}}和{{phone.snippet}}——能达到同样的效果。
2.4 模型和控制器
在PhoneListCtrl控制器里面初始化了数据模型(这里只不过是一个包含了数组的函数,数组中存储的对象是手机数据列表):
app/js/controller.js:
function PhoneListCtrl($scope) {
$scope.phones = [
{"name": "Nexus S",
"snippet": "Fast just got faster with Nexus S."},
{"name":"Motorola XOOMTM with Wi-Fi",
"snippet": "The Next, Next Generation tablet."},
{"name": "MOTOROLA XOOMTM",
"snippet": "The Next, Next Generation tablet."}
];
}
尽管控制器看起来并没有起到什么控制的作用,但是它在这里起到了至关重要的作用。通过给定我们数据模型的语境, 控制器允许我们建立模型和视图之间的数据绑定。我们是这样把表现层,数据和逻辑部件联系在一起的:
· PhoneListCtrl——控制器方法的名字(在JS文件 controllers.js中)和<body>标签里面的 ngController 指令的值相匹配。
· 手机的数据此时与注入到我们控制器函数的作用域($scope)相关联。当应用启动之后,会有一个根作用域被创建出来,而控制器的作用域是根作用域的一个典型后继。这个控制器的作用域对所有<body
ng-controller="PhoneListCtrl">标记内部的数据绑定有效。
AngularJS的作用域理论非常重要:一个作用域可以视作模板、模型和控制器协同工作的粘接器。AngularJS使用作用 域,同时还有模板中的信息,数据模型和控制器。这些可以帮助模型和视图分离,但是他们两者确实是同步的!任何 对于模型的更改都会即时反映在视图上;任何在视图上的更改都会被立刻体现在模型中。
想要更加深入理解AngularJS的作用域,请参看AngularJS作用域文档。
2.5 测试
测试 “AngularJS方式”让开发时代码测试变得十分简单。让我们来瞅一眼下面这个为控制器新添加的单元测试:
test/unit/controllersSpec.js:
describe('PhoneCat controllers', function() {
describe('PhoneListCtrl', function(){
it('shouldcreate "phones" model with 3 phones', function() {
var scope ={},
ctrl = newPhoneListCtrl(scope);
expect(scope.phones.length).toBe(3);
});
});
});
在写测试的时候,AngularJS的开发者倾向于使用Jasmine行为驱动开发(BBD)框架中的语法。尽管AngularJS没有强迫你使用Jasmine,但是我们在教程里面所有的测试都使用Jasmine编写。你可以在Jasmine的官方主页或者Jasmine W iki上获得相关知识。
基于AngularJS的项目被预先配置为使用JsTestDriver来运行单元测试。你可以像下面这样运行测试:
1、在一个单独的终端上,进入到angular-phonechat目录并且运行./scripts/test-server.sh来启动测试 (Windows 命令行下请输入.scriptstest-server.bat来运行脚本,后面脚本命令运行方式类似);
2、打开一个新的浏览器窗口,并且转到 http://localhost:9876;
3、选择“Capture this browser in strict mode”。
这个时候,你可以抛开你的窗口不管然后把这事忘了。JsTestDriver会自己把测试跑完并且把结果输出在你的终端里。
4、运行./scripts/test.sh进行测试 。
你应当看到类似于如下的结果:
Chrome: Runner reset.
.
Total 1tests (Passed: 1; Fails: 0; Errors: 0) (2.00 ms)
Chrome19.0.1084.36 Mac OS: Run 1 tests (Passed: 1; Fails: 0; Errors 0) (2.00 ms)
2.6 依赖注入(DI)
当应用引导时,AngularJS会创建一个注入器,我们应用后面所有依赖注入的服务都会需要它。这个注入器自己并不知道http和route是干什么的,实际上除非它在模块定义的时候被配置过,否则它根 都不知道这些服务的存在。注入器唯一的职责是载入指定的服务模块,在这些模块中注册所有定义的服务提供者,并且当需要时给一个指定的函数注 入依赖(服务)。这些依赖通过它们的提供者“懒惰式”(需要时才加载)实例化。
提供者是提供(创建)服务实例并且对外提供API接口的对象,它可以被用来控制一个服务的创建和运行时行为。对于route服务来说,routeProvider对外提供了API接口,通过API接口允许你为你的应用定义路由规则。
AngularJS模块解决了从应用中删除全局状态和提供方法来配置注入器这两个问题。和AMD或者require.js这两个模块(非AngularJS的两个库)不同的是,AngularJS模块并没有试图去解决脚 加载顺序以及懒惰式脚 加载这样的问题。这些目标和AngularJS要解决的问题毫无关联,所以这些模块完全可以共存来实现各自的目标。
2.7 路由
深入理解ANGULARUI路由_UI-ROUTER
http://www.html5jq.com/fe/angular_node/20150417/133.html
AngularJS ui-router (嵌套路由)
http://www.oschina.net/translate/angularjs-ui-router-nested-routes
Angularjs中UI Router全攻略
http://www.jb51.net/article/78895.htm
AngularJS路由系列(四)-UI-Router的$state服务、路由事件、获取路由参数
http://www.myexception.cn/javascript/2041588.html
ui-router -路由控制$stateProvider
http://www.360doc.com/content/15/0517/13/203871_471206681.shtml
2.7.1 $routeProvider路由
AngularJS中应用的路由通过routeProvider来声明,它是route服务的提供者。这项服务使得控制器、视图模板与当前浏览器的URL可以轻易集成。应用这个特性我们就可以实现深链接,它允许我们使用浏览器的历史(回退或者前进 导航)和书签。
App模块
app/js/app.js
angular.module('phonecat', []).config(['routeProvider',function(routeProvider) {
$routeProvider.
when('/phones', {templateUrl:'partials/phone-list.html', controller: PhoneListCtrl}).
when('/phones/:phoneId', {templateUrl: 'partials/phone-detail.html',controller: PhoneDetailCtrl}).
otherwise({redirectTo: '/phones'});
}]);
为了给我们的应用配置路由,我们需要给应用创建一个模块。我们管这个模块叫做phonecat,并且通过使用configAPI,我们请求把routeProvider注入到我们的配置函数并且使用routeProvider.whenAPI来定义我们的路由规则。
注意到在注入器配置阶段,提供者也可以同时被注入,但是一旦注入器被创建并且开始创建服务实例的时候,他们就不再会被外界所获取到。
我们的路由规则定义如下
我们重用之前创造过的PhoneListCtrl控制器,同时我们为手机详细视图添加一个新的PhoneDetailCtrl控制器,把它存放在app/js/controllers.js文件里。$route.otherwise({redirectTo: '/phones'})语句使得当浏览器地址不能匹配我们任何一个路由规则时,触发重定向到/phones。
注意到在第二条路由声明中:phoneId参数的使用。route服务使用路由声明/phones/:phoneId作为一个匹配当前URL的模板。所有以:符号声明的变量(此处变量为phones)都会被提取,然后存放在routeParams对象中。
为了让我们的应用引导我们新创建的模块,我们同时需要在ngApp指令的值上指明模块的名字:
app/index.html
<!doctype html>
<html lang="en" ng-app="phonecat">
...
控制器
app/js/controllers.js
...
function PhoneDetailCtrl(scope,routeParams) {
scope.phoneId =routeParams.phoneId;
}
//PhoneDetailCtrl.inject = ['scope', '
模板
$route服务通常和ngView指令一起使用。ngView指令的角色是为当前路由把对应的视图模板载入到布局模板中。
app/index.html
注意,我们把index.html模板里面大部分代码移除,我们只放置了一个<div>容器,这个<div>具有ng-view属性。我们删除掉的代码现在被放置在phone-list.html模板中:
app/partials/phone-list.html
同时我们为手机详细信息视图添加一个占位模板。 app/partials/phone-detail.html
TBD: detail view for {{phoneId}}
注意到我们的布局模板中没再添加PhoneListCtrl或PhoneDetailCtrl控制器属性!
2.7.2 UI-Router
2.7.2.1 前端路由的基本原理
• 哈希#
• HTML5中新的history API
• 路由的核心是给应用定义"状态"
• 使用路由机制会影响到应用的整体编码方式(需预先定义好状态)
• 考虑兼容性问题与"优雅降级"
2.7.2.2 Angular-UI-Router使用技巧
ui-sref 指令链接到特定状态
<a ui-sref="home">Home</a>
<a ui-sref="about">About</a>
<a ui-sref="contacts.list">Contacts</a>
$state.includes 返回 true / false
以上方法为查看当前状态是否在某父状态内,比如 $state.includes('contacts') 返回 true / false
ui-sref-active 查看当前激活状态并设置 Class
包含模块
angular.module('uiRouter', ['ui.router']);
方便获得当前状态的方法,绑到根作用域
app.run(['rootScope', 'state', '
function(rootScope, state,
rootScope.state =
rootScope.stateParams =
}
]);
2.7.2.3 路由重定向 $urlRouterProvider
app.config(['stateProvider', 'urlRouterProvider',
function(stateProvider, urlRouterProvider) {
$urlRouterProvider
// 错误的路由重定向
.when('/c?id', '/contacts/:id')
.when('/user/:id', '/contacts/:id')
.otherwise('/');
}
]);
2.7.2.4 状态配置
$stateProvider.state('about', {
url: '/about',
template: '<h1>Welcome to UI-Router Demo</h1>',
// optional below
templateProvider: ['timeout', function(timeout) {
return $timeout(function() {
return '<p class="lead">UI-Router Resource</p>' '<p>The second line</p>'
},100);
}
],
templateUrl: 'about.html',
templateUrl: function() {
return 'about.html';
},
controller: 'UIRouterCtrl',
// below is a state controller picked from UI-Router official sample
controller: ['scope', 'state', 'contacts', 'utils',
function (scope, state, contacts, utils) {
$scope.contacts = contacts;
$scope.goToRandom = function () {
var randId = utils.newRandomKey(scope.contacts, 'id', state.params.contactId);
$state.go('contacts.details', { contactId: randId });
}
}
]
});
2.7.2.5 嵌套配置Configure
JavaScript Codes
Parent Router
$stateProvider.state('contacts', {
// abstract: true 表明此状态不能被显性激活,只能被子状态隐性激活
abstract: true,
url: '/contacts',
templateUrl: 'contacts.html',
// resolve 被使用来处理异步数据调用,以下是返回一个 promise ->
contacts.all()
resolve: {
contacts: ['contacts', function(contacts) {
// 以下方法被放在contacts.service.js 中,以 factory 存在
return contacts.all();
}
]
},
// below is a state controller picked from UI-Router official sample
controller: ['scope', 'state', 'contacts', 'utils',
function (scope, state, contacts, utils) {
$scope.contacts = contacts;
$scope.goToRandom = function () {
var randId = utils.newRandomKey(scope.contacts, 'id', state.params.contactId);
$state.go('contacts.details', { contactId: randId });
}
}
]
})
Children Router (NestedRouter)
$stateProvider.state('contacts.list', {
url: '',
templateUrl: 'contacts.list.html'
})
.state('contacts.detail', {
url: '/{contactId:[0-9]{1,4}}',
// view 用在该状态下有多个 ui-view 的情况,可以对不同的 ui-view 使用特定的 template, controller, resolve data
// 绝对 view 使用 '@' 符号来区别,比如 'foo@bar' 表明名为 'foo' 的 ui-view 使用了 'bar' 状态的模板(template),相对 view 则无
views: {
// 无名 view
'': {
templateUrl: 'contacts.detail.html',
controller: ['scope', 'stateParams', 'utils',
function (scope, stateParams, utils) {
scope.contact = utils.findById(scope.contacts,
}
]
},
// for "ui-view='hint'"
'hint@' : {
template: 'This is contacts.defail populating the "hint" ui-view'
},
// for "ui-view='menuTip'"
'menuTip': {
templateProvider: ['$stateParams',
function($stateParams) {
return '<hr><small class="muted">Contact ID: ' $stateParams.contactId '</small>';
}
]
}
}
})
HTML Codes
2.8 事件处理器
2.8.1 事件监听
2.8.1.1 内置事件
includeContentLoaded(emit事件)
ngInclude内容重新加载的时候,从ngInclude指令触发
includeContentRequested(emit事件)
从调用ngInclude的作用域上发送,每次ngInclude的内容被请求的时候,都会发布该事件
viewContentLoaded(emit事件)
当ngView内容被重新加载时,从ngView作用域上发布
locationChangeStart(broadcast事件)
通过location服务对浏览器的地址更新时会触发locationChangeStart事件
locationChangeSuccess(broadcast事件)
当浏览器的地址成功变更时触发
routeChangeStart(broadcast事件)
在路由变更发生之前,该事件从$rootScope发布
Angularjs的那些事 – 视图的生命周期
http://www.cnblogs.com/huangjacky/p/3693180.html
angular事件
http://blog.csdn.net/yangnianbing110/article/details/43244169
angularjs中实现window.onload()与$(document).ready()的方法
http://www.111cn.net/wy/js-ajax/58476.htm
判断AngularJS渲染页面完成
http://blog.csdn.net/xinshangshangxin/article/details/44700813
2.8.2 事件移除监听
$scope.locationListener = function (event){
hj.route.saveModuleData(window.location.href,{"backUrl": hj.route.getAbsoluteUrl("mpIndex.html#/getMpList")});
//要做事件移除动作,不然其他路由变化也能监听到
var listeners = scope.listeners['locationChangeSuccess'];
var index = listeners.indexOf($scope.locationListener);
listeners.splice(index,1);
};
scope.on('locationChangeSuccess', scope.locationListener);
2.8.3 事件绑定
控制器
app/js/controllers.js
...
function PhoneDetailCtrl(scope, routeParams,
http.get('phones/' routeParams.phoneId '.json').success(function(data) {
$scope.phone = data;
$scope.mainImageUrl = data.images[0];
});
$scope.setImage =function(imageUrl) {
$scope.mainImageUrl = imageUrl;
}
}
//PhoneDetailCtrl.inject = ['scope', 'routeParams','http'];
在PhoneDetailCtrl控制器中,我们创建了mainImageUrl模型属性,并且把它的默认值设为第一个手机图片的URL。
模板
app/partials/phone-detail.html
我们把大图片的ngSrc指令绑定到mainImageUrl属性上。 同时我们注册一个ngClick处理器到缩略图上。当一个用户点击缩略图的任意一个时,这个处理器会使用setImage事件处理函数来把mainImageUrl属性设置成选定缩略图的URL。
2.9 REST和定制服务
模板
定制的服务被定义在app/js/services,所以我们需要在布局模板中引入这个文件。另外,我们也要加载angularjs-resource.js这个文件,它包了ngResource模块以及其中的$resource服务,我们一会就会用到它们:
app/index.html
服务
app/js/services.js
angular.module('phonecatServices', ['ngResource']).factory('Phone', function($resource){
return $resource('phones/:phoneId.json', {}, {
query: {method:'GET', params:{phoneId:'phones'}, isArray:true}
});
});
我们使用模块API通过一个工厂方法注册了一个定制服务。我们传入服务的名字Phone和工厂函数。工厂函数和控制器构造函数差不多,它们都通过函数参数声明依赖服务。Phone服务声明了它依赖于$resource服务。
resource服务使得用短短的几行代码就可以创建一个RESTful客户端。我们的应用使用这个客户端来代替底层的http服务。
app/js/app.js
...
angular.module('phonecat', ['phonecatFilters', 'phonecatServices']).
...
我们需要把phonecatServices添加到phonecat的依赖数组里。
控制器
通过重构掉底层的http服务,把它放在一个新的服务Phone中,我们可以大大简化子控制器(PhoneListCtrl和PhoneDetailCtrl)。AngularJS的resource相比于
app/js/controllers.js
...
function PhoneListCtrl($scope, Phone) {
$scope.phones = Phone.query();
$scope.orderProp = 'age';
}
//PhoneListCtrl.inject = ['scope', 'Phone'];
function PhoneDetailCtrl(scope, routeParams, Phone) {
scope.phone = Phone.get({phoneId:routeParams.phoneId}, function(phone) {
$scope.mainImageUrl = phone.images[0];
});
$scope.setImage = function(imageUrl) {
$scope.mainImageUrl = imageUrl;
}
}
//PhoneDetailCtrl.inject = ['scope', '
注意到,在PhoneListCtrl里我们把:
$http.get('phones/phones.json').success(function(data) {
$scope.phones =data;
});
换成了:
$scope.phones = Phone.query();
我们通过这条简单的语句来查询所有的手机。
另一个非常需要注意的是,在上面的代码里面,当调用Phone服务的方法是我们并没有传递任何回调函数。尽管这看起来结果是同步返回的($scope.phones = Phone.query();),其实根本就不是。被同步返回的是一个“future”——一个对象,当XHR相应返回的时候会 填充进数据。鉴于AngularJS的数据绑定,我们可以使用future并且把它绑定到我们的模板上。然后,当数据到达时,我们的视图会自动更新。
有的时候,单单依赖future对象和数据绑定不足以满足我们的需求,所以在这些情况下,我们需要添加一个回调函数来处理服务器的响应。PhoneDetailCtrl控制器通过在一个回调函数中设置mainImageUrl就是一个解释。
3 参考链接
整理AngularJS中的一些常用指令
http://www.xker.com/page/e2015/06/198575.html
AngularJS移动开发中的坑汇总
http://blog.csdn.net/offbye/article/details/38490821?utm_source=tuicool&utm_medium=referral
25个超有用的AngularJS Web开发工具
http://www.chinaz.com/web/2015/0703/419434.shtml
AngularJS最理想开发工具WebStorm
http://blog.fens.me/angularjs-webstorm-ide/
angular通过$http与服务器通信
http://blog.csdn.net/yangnianbing110/article/details/43124679
AngularJS-常用服务
http://www.2cto.com/kf/201504/388774.html
第九讲Angularjs常用服务http location cacheFactory log
http://www.phonegap100.com/article-416-1.html
简介AngularJS中$http服务的用法
http://www.jb51.net/article/79243.htm
AngularJS中使用路由和$location切换视图
http://www.thinksaas.cn/group/topic/348590/
angularjs通过锚链接实现页面切换的问题
https://segmentfault.com/q/1010000002949626
走进AngularJs(二)ng模板中常用指令的使用方式-吕大豹
http://www.tuicool.com/articles/jIV7rm
React vs Angular 2:战争继续
http://ouvens.github.io/article-translation/2016/04/07/react-vs-angular2-fight-rages-on.html?utm_source=tuicool&utm_medium=referral
急急急!高手请帮忙!angule js中ng-view中使用了ng-include,如何实现ng-include的这个页面刷新,外部的ng-view不刷新
http://www.oschina.net/question/2356458_233962
ng-include用法分析以及多标签页面的简单实现方式
http://my.oschina.net/keysITer/blog/630621?p=1
深入理解ng里的scope
http://get.ftqq.com/462.get
angularJs前端的页面分解与组装
http://hudeyong926.iteye.com/blog/2111664