├── AngularJS入门教程00:引导程序.md ├── AngularJS入门教程01:静态模板.md ├── AngularJS入门教程02:AngularJS模板.md ├── AngularJS入门教程03:迭代器.md ├── AngularJS入门教程04:双向绑定.md ├── AngularJS入门教程05:XHR和依赖注入.md ├── AngularJS入门教程06:链接与图片模板.md ├── AngularJS入门教程07:路由与多视图.md ├── AngularJS入门教程08:更多模板.md ├── AngularJS入门教程09:过滤器.md ├── AngularJS入门教程10:事件处理器.md ├── AngularJS入门教程11:REST和定制服务.md ├── AngularJS入门教程12:完结篇.md ├── AngularJS入门教程目录.md ├── AngularJS入门教程:导言和准备.md ├── AngularJS快速开始.md └── README.md /AngularJS入门教程00:引导程序.md: -------------------------------------------------------------------------------- 1 | 我们现在开始准备编写AngularJS应用——**phonecat**。这一步骤(步骤0),您将会熟悉重要的源代码文件,学习启动包含AngularJS种子项目的开发环境,并在浏览器端运行应用。 2 | 3 | 1. 进入angular-phonecat目录,运行如下命令: 4 | 5 | git checkout -f step-0 6 | 7 | 该命令将重置phonecat项目的工作目录,建议您在每一学习步骤运行此命令,将命令中的数字改成您学习步骤对应的数字,该命令将清除您在工作目录内做的任何更改。 8 | 9 | 2. 运行以下命令: 10 | 11 | node scripts/web-server.js 12 | 13 | 来启动服务器,启动后命令行终端将会提示**`Http Server running at http://localhost:8000`**,请不要关闭该终端,关闭该终端即关闭了服务器。在浏览器中输入来访问我们的**phonecat**应用。 14 | 15 | 现在,在浏览器中您应该已经看到了我们的初始应用,很简单,但说明我们的项目已经可以运行了。 16 | 17 | 应用中显示的“Nothing here yet!”是由如下HTML代码构建而成,代码中包含了AngularJS的关键元素,正是我们需要学习的。 18 | 19 | **app/index.html** 20 | 21 | 22 | 23 | 24 | 25 | My HTML File 26 | 27 | 28 | 29 | 30 | 31 |

Nothing here {{'yet' + '!'}}

32 | 33 | 34 | 35 | 36 | ## 代码在做什么呢? 37 | 38 | ### `ng-app`指令: 39 | 40 | 41 | 42 | `ng-app`指令标记了AngularJS脚本的作用域,在``中添加`ng-app`属性即说明整个``都是AngularJS脚本作用域。开发者也可以在局部使用`ng-app`指令,如`
`,则AngularJS脚本仅在该`
`中运行。 43 | 44 | ### AngularJS脚本标签: 45 | 46 | 47 | 48 | 49 | 这行代码载入angular.js脚本,当浏览器将整个HTML页面载入完毕后将会执行该angular.js脚本,angular.js脚本运行后将会寻找含有`ng-app`指令的HTML标签,该标签即定义了AngularJS应用的作用域。 50 | 51 | ### 双大括号绑定的表达式: 52 | 53 |

Nothing here {{'yet' + '!'}}

54 | 55 | 56 | 这行代码演示了AngularJS模板的核心功能——绑定,这个绑定由双大括号`{{}}`和表达式`'yet' + '!'`组成。 57 | 58 | 这个绑定告诉AngularJS需要运算其中的表达式并将结果插入DOM中,接下来的步骤我们将看到,DOM可以随着表达式运算结果的改变而实时更新。 59 | 60 | AngularJS表达式[Angular expression]是一种类似于JavaScript的代码片段,AngularJS表达式仅在AngularJS的作用域中运行,而不是在整个DOM中运行。 61 | 62 | ## 引导AngularJS应用 63 | 64 | 通过ngApp指令来自动引导AngularJS应用是一种简洁的方式,适合大多数情况。在高级开发中,例如使用脚本装载应用,您也可以使用[bootstrap][]手动引导AngularJS应用。 65 | 66 | **AngularJS应用引导过程有3个重要点:** 67 | 68 | 1. [注入器(injector)][$injector]将用于创建此应用程序的依赖注入(dependency injection); 69 | 2. 注入器将会创建[根作用域]($rootScope)作为我们应用模型的范围; 70 | 3. AngularJS将会链接根作用域中的DOM,从用ngApp标记的HTML标签开始,逐步处理DOM中指令和绑定。 71 | 72 | 一旦AngularJS应用引导完毕,它将继续侦听浏览器的HTML触发事件,如鼠标点击事件、按键事件、HTTP传入响应等改变DOM模型的事件。这类事件一旦发生,AngularJS将会自动检测变化,并作出相应的处理及更新。 73 | 74 | 上面这个应用的结构非常简单。该模板包仅含一个指令和一个静态绑定,其中的模型也是空的。下一步我们尝试稍复杂的应用! 75 | 76 | ![img_tutorial_00][] 77 | 78 | ## 我工作目录中这些文件是干什么的? 79 | 80 | 上面的应用来自于AngularJS种子项目,我们通常可以使用[AngularJS种子项目][angular-seed]来创建新项目。种子项目包括最新的AngularJS代码库、测试库、脚本和一个简单的应用程序示例,它包含了开发一个典型的web应用程序所需的基本配置。 81 | 82 | 对于本教程,我们对AngularJS种子项目进行了下列更改: 83 | 84 | 1. 删除示例应用程序; 85 | 2. 添加手机图像到app/img/phones/; 86 | 3. 添加手机数据文件(JSON)到app/phones/; 87 | 4. 添加[Twitter Bootstrap][Twitter Bootstrap]文件到app/css/ 和app/img/。 88 | 89 | ## 练习 90 | 91 | 试试把关于数学运算的新表达式添加到index.html: 92 | 93 |

1 + 2 = {{ 1 + 2 }}

94 | 95 | ## 总结 96 | 97 | 现在让我们转到[步骤1][step_01],将一些内容添加到web应用程序。 98 | 99 | [img_tutorial_00]: http://docs.angularjs.org/img/tutorial/tutorial_00.png 100 | [bootstrap]: http://angularjs.cn/A00o 101 | [Angular expression]: http://angularjs.cn/A00s 102 | [$injector]: http://docs.angularjs.org/api/AUTO.$injector 103 | [$rootScope]: http://docs.angularjs.org/api/ng.$rootScope 104 | [step_01]: http://angularjs.cn/A004 105 | [angular-seed]: https://github.com/angular/angular-seed 106 | [Twitter Bootstrap]: http://twitter.github.com/bootstrap/ 107 | -------------------------------------------------------------------------------- /AngularJS入门教程01:静态模板.md: -------------------------------------------------------------------------------- 1 | 为了说明angularJS如何增强了标准HTML,我们先将创建一个静态HTML页面模板,然后把这个静态HTML页面模板转换成能动态显示的AngularJS模板。 2 | 3 | 在本步骤中,我们往HTML页面中添加两个手机的基本信息,用以下命令将工作目录重置到步骤1。 4 | 5 | git checkout -f step-1 6 | 7 | 请编辑app/index.html文件,将下面的代码添加到index.html文件中,然后运行该应用查看效果。 8 | 9 | **app/index.html** 10 | 11 |
    12 |
  • 13 | Nexus S 14 |

    15 | Fast just got faster with Nexus S. 16 |

    17 |
  • 18 |
  • 19 | Motorola XOOM™ with Wi-Fi 20 |

    21 | The Next, Next Generation tablet. 22 |

    23 |
  • 24 |
25 | 26 | ## 练习 27 | 28 | 尝试添加多个静态HTML代码到index.html, 例如: 29 | 30 |

Total number of phones: 2

31 | 32 | ## 总结 33 | 34 | 本步骤往应用中添加了静态HTML手机列表, 现在让我们转到[步骤2][step_02]以了解如何使用AngularJS动态生成相同的列表。 35 | 36 | [step_02]: http://angularjs.cn/A005 37 | -------------------------------------------------------------------------------- /AngularJS入门教程02:AngularJS模板.md: -------------------------------------------------------------------------------- 1 | 是时候给这些网页来点动态特性了——用AngularJS!我们这里为后面要加入的控制器添加了一个测试。 2 | 3 | 一个应用的代码架构有很多种。对于AngularJS应用,我们鼓励使用[模型-视图-控制器(MVC)模式][]解耦代码和分离关注点。考虑到这一点,我们用AngularJS来为我们的应用添加一些模型、视图和控制器。 4 | 5 | 请重置工作目录: 6 | 7 | git checkout -f step-2 8 | 9 | 我们的应用现在有了一个包含三部手机的列表。 10 | 11 | 步骤1和步骤2之间最重要的不同在下面列出。,你可以到[GitHub][]去看完整的差别。 12 | 13 | ### 视图和模板 14 | 15 | 在AngularJS中,一个**视图**是模型通过HTML**模板**渲染之后的映射。这意味着,不论模型什么时候发生变化,AngularJS会实时更新结合点,随之更新视图。 16 | 17 | 比如,视图组件被AngularJS用下面这个模板构建出来: 18 | 19 | 20 | 21 | ... 22 | 23 | 24 | 25 | 26 | 27 |
    28 |
  • 29 | {{phone.name}} 30 |

    {{phone.snippet}}

    31 |
  • 32 |
33 | 34 | 35 | 36 | 我们刚刚把静态编码的手机列表替换掉了,因为这里我们使用[ngRepeat指令][]和两个用花括号包裹起来的AngularJS表达式——`{{phone.name}}`和`{{phone.snippet}}`——能达到同样的效果。 37 | 38 | - 在`
  • `标签里面的`ng-repeat="phone in phones"`语句是一个AngularJS迭代器。这个迭代器告诉AngularJS用第一个`
  • `标签作为模板为列表中的每一部手机创建一个`
  • `元素。 39 | - 正如我们在第0步时学到的,包裹在`phone.name`和`phone.snippet`周围的花括号标识着数据绑定。和常量计算不同的是,这里的表达式实际上是我们应用的一个数据模型引用,这些我们在`PhoneListCtrl`控制器里面都设置好了。 40 | 41 | ![tutorial_02.png][] 42 | 43 | ### 模型和控制器 44 | 在`PhoneListCtrl`**控制器**里面初始化了**数据模型**(这里只不过是一个包含了数组的函数,数组中存储的对象是手机数据列表): 45 | 46 | **app/js/controller.js**: 47 | 48 | function PhoneListCtrl($scope) { 49 | $scope.phones = [ 50 | {"name": "Nexus S", 51 | "snippet": "Fast just got faster with Nexus S."}, 52 | {"name": "Motorola XOOM™ with Wi-Fi", 53 | "snippet": "The Next, Next Generation tablet."}, 54 | {"name": "MOTOROLA XOOM™", 55 | "snippet": "The Next, Next Generation tablet."} 56 | ]; 57 | } 58 | 59 | 尽管控制器看起来并没有起到什么控制的作用,但是它在这里起到了至关重要的作用。通过给定我们数据模型的语境,控制器允许我们建立模型和视图之间的数据绑定。我们是这样把表现层,数据和逻辑部件联系在一起的: 60 | 61 | - `PhoneListCtrl`——控制器方法的名字(在JS文件`controllers.js`中)和``标签里面的[ngController][]指令的值相匹配。 62 | - 手机的数据此时与注入到我们控制器函数的**作用域**(`$scope`)相关联。当应用启动之后,会有一个根作用域被创建出来,而控制器的作用域是根作用域的一个典型后继。这个控制器的作用域对所有``标记内部的数据绑定有效。 63 | 64 | AngularJS的作用域理论非常重要:一个作用域可以视作模板、模型和控制器协同工作的粘接器。AngularJS使用作用域,同时还有模板中的信息,数据模型和控制器。这些可以帮助模型和视图分离,但是他们两者确实是同步的!任何对于模型的更改都会即时反映在视图上;任何在视图上的更改都会被立刻体现在模型中。 65 | 66 | 想要更加深入理解AngularJS的作用域,请参看[AngularJS作用域文档][]。 67 | 68 | ### 测试 69 | “AngularJS方式”让开发时代码测试变得十分简单。让我们来瞅一眼下面这个为控制器新添加的单元测试: 70 | 71 | **test/unit/controllersSpec.js:** 72 | 73 | describe('PhoneCat controllers', function() { 74 | 75 | describe('PhoneListCtrl', function(){ 76 | 77 | it('should create "phones" model with 3 phones', function() { 78 | var scope = {}, 79 | ctrl = new PhoneListCtrl(scope); 80 | 81 | expect(scope.phones.length).toBe(3); 82 | }); 83 | }); 84 | }); 85 | 86 | 这个测试验证了我们的手机数组里面有三条记录(暂时无需弄明白这个测试脚本)。这个例子显示出为AngularJS的代码创建一个单元测试是多么的容易。正因为测试在软件开发中是必不可少的环节,所以我们使得在AngularJS可以轻易地构建测试,来鼓励开发者多写它们。 87 | 88 | 在写测试的时候,AngularJS的开发者倾向于使用Jasmine行为驱动开发(BBD)框架中的语法。尽管AngularJS没有强迫你使用Jasmine,但是我们在教程里面所有的测试都使用Jasmine编写。你可以在Jasmine的[官方主页][]或者[Jasmine Wiki][]上获得相关知识。 89 | 90 | 基于AngularJS的项目被预先配置为使用[JsTestDriver][]来运行单元测试。你可以像下面这样运行测试: 91 | 92 | 1. 在一个单独的终端上,进入到`angular-phonecat`目录并且运行`./scripts/test-server.sh`来启动测试(Windows命令行下请输入`.\scripts\test-server.bat`来运行脚本,后面脚本命令运行方式类似); 93 | 2. 打开一个新的浏览器窗口,并且转到 ; 94 | 3. 选择“Capture this browser in strict mode”。 95 | 96 | 这个时候,你可以抛开你的窗口不管然后把这事忘了。JsTestDriver会自己把测试跑完并且把结果输出在你的终端里。 97 | 98 | 4. 运行`./scripts/test.sh`进行测试 。 99 | 100 | 你应当看到类似于如下的结果: 101 | 102 | Chrome: Runner reset. 103 | . 104 | Total 1 tests (Passed: 1; Fails: 0; Errors: 0) (2.00 ms) 105 | Chrome 19.0.1084.36 Mac OS: Run 1 tests (Passed: 1; Fails: 0; Errors 0) (2.00 ms) 106 | 107 | 耶!测试通过了!或者没有... 108 | 注意:如果在你运行测试之后发生了错误,关闭浏览器然后回到终端关了脚本,然后在重新来一边上面的步骤。 109 | 110 | ## 练习 111 | - 为`index.html`添加另一个数据绑定。例如: 112 | 113 |

    Total number of phones: {{phones.length}}

    114 | 115 | - 创建一个新的数据模型属性,并且把它绑定到模板上。例如: 116 | 117 | $scope.hello = "Hello, World!" 118 | 119 | 更新你的浏览器,确保显示出来“Hello, World!” 120 | 121 | - 用一个迭代器创建一个简单的表: 122 | 123 | 124 | 125 | 126 |
    row number
    {{i}}
    127 | 128 | 现在让数据模型表达式的`i`增加1: 129 | 130 | 131 | 132 | 133 |
    row number
    {{i+1}}
    134 | 135 | - 确定把`toBe(3)`改成`toBe(4)`之后单元测试失败,然后重新跑一遍`./scripts/test.sh`脚本 136 | 137 | ## 总结 138 | 你现在拥有一个模型,视图,控制器分离的动态应用了,并且你随时进行了测试。现在,你可以进入到[步骤3][step_03]来为应用加入全文检索功能了。 139 | 140 | [step_03]: http://angularjs.cn/A006 141 | [模型-视图-控制器(MVC)模式]: http://en.wikipedia.org/wiki/Model%E2%80%93View%E2%80%93Controller 142 | [GitHub]: https://github.com/angular/angular-phonecat/compare/step-1...step-2 143 | [ngRepeat指令]: http://docs.angularjs.org/api/ng.directive:ngRepeat 144 | [tutorial_02.png]: http://docs.angularjs.org/img/tutorial/tutorial_02.png 145 | [ngController]: http://docs.angularjs.org/api/ng.directive:ngController 146 | [AngularJS作用域文档]: http://docs.angularjs.org/api/ng.$rootScope.Scope 147 | [官方主页]: http://pivotal.github.com/jasmine/ 148 | [Jasmine Wiki]: https://github.com/pivotal/jasmine/wiki 149 | [JsTestDriver]: http://code.google.com/p/js-test-driver/ 150 | -------------------------------------------------------------------------------- /AngularJS入门教程03:迭代器.md: -------------------------------------------------------------------------------- 1 | 我们在上一步做了很多基础性的训练,所以现在我们可以来做一些简单的事情喽。我们要加入全文检索功能(没错,这个真的非常简单!)。同时,我们也会写一个端到端测试,因为一个好的端到端测试可以帮上很大忙。它监视着你的应用,并且在发生回归的时候迅速报告。 2 | 3 | 请重置工作目录: 4 | 5 | git checkout -f step-3 6 | 7 | 我们的应用现在有了一个搜索框。注意到页面上的手机列表随着用户在搜索框中的输入而变化。 8 | 9 | 步骤2和步骤3之间最重要的不同在下面列出。你可以在[GitHub][]里看到完整的差别。 10 | 11 | ### 控制器 12 | 13 | 我们对控制器不做任何修改。 14 | 15 | ### 模板 16 | 17 | **app/index.html** 18 | 19 |
    20 |
    21 |
    22 | 23 | 24 | Search: 25 | 26 |
    27 |
    28 | 29 | 30 |
      31 |
    • 32 | {{phone.name}} 33 |

      {{phone.snippet}}

      34 |
    • 35 |
    36 | 37 |
    38 |
    39 |
    40 | 41 | 我们现在添加了一个``标签,并且使用AngularJS的[$filter][filter]函数来处理[ngRepeat][]指令的输入。 42 | 43 | 这样允许用户输入一个搜索条件,立刻就能看到对电话列表的搜索结果。我们来解释一下新的代码: 44 | 45 | - 数据绑定: 这是AngularJS的一个核心特性。当页面加载的时候,AngularJS会根据输入框的属性值名字,将其与数据模型中相同名字的变量绑定在一起,以确保两者的同步性。 46 | 47 | 在这段代码中,用户在输入框中输入的数据名字称作`query`,会立刻作为列表迭代器(`phone in phones | filter:`query`)其过滤器的输入。当数据模型引起迭代器输入变化的时候,迭代器可以高效得更新DOM将数据模型最新的状态反映出来。 48 | 49 | ![img_tutorial_03][] 50 | 51 | - 使用`filter`过滤器:[filter][]函数使用`query`的值来创建一个只包含匹配`query`记录的新数组。 52 | 53 | `ngRepeat`会根据`filter`过滤器生成的手机记录数据数组来自动更新视图。整个过程对于开发者来说都是透明的。 54 | 55 | ### 测试 56 | 57 | 在步骤2,我们学习了编写和运行一个测试的方法。单元测试用来测试我们用js编写的控制器和其他组件都非常方便,但是不能方便的对DOM操作和应用集成进行测试。对于这些来说,端到端测试是一个更好的选择。 58 | 59 | 搜索特性是完全通过模板和数据绑定实现的,所以我们的第一个端到端测试就来验证这些特性是否符合我们的预期。 60 | 61 | **test/e2e/scenarios.js:** 62 | 63 | describe('PhoneCat App', function() { 64 | 65 | describe('Phone list view', function() { 66 | 67 | beforeEach(function() { 68 | browser().navigateTo('../../app/index.html'); 69 | }); 70 | 71 | 72 | it('should filter the phone list as user types into the search box', function() { 73 | expect(repeater('.phones li').count()).toBe(3); 74 | 75 | input('query').enter('nexus'); 76 | expect(repeater('.phones li').count()).toBe(1); 77 | 78 | input('query').enter('motorola'); 79 | expect(repeater('.phones li').count()).toBe(2); 80 | }); 81 | }); 82 | }); 83 | 84 | 尽管这段测试代码的语法看起来和我们之前用Jasmine写的单元测试非常像,但是端到端测试使用的是[AngularJS端到端测试器][]提供的接口。 85 | 86 | 运行一个端到端测试,在浏览器新标签页中打开下面任意一个: 87 | 88 | - node.js用户: 89 | - 使用其他http服务器的用户:`http://localhost:[port-number]/[context-path]/test/e2e/runner.html` 90 | - 访客: 91 | 92 | 这个测试验证了搜素框和迭代器被正确地集成起来。你可以发现,在AngularJS里写一个端到端测试多么的简单。尽管这个例子仅仅是一个简单的测试,但是用它来构建任何一个复杂、可读的端到端测试都很容易。 93 | 94 | ## 练习 95 | 96 | - 在`index.html`模板中添加一个`{{query}}`绑定来实时显示`query`模型的当前值,然后观察他们是如何根据输入框中的值而变化。 97 | - 现在我们来看一下我们怎么让`query`模型的值出现在HTML的页面标题上。 98 | 99 | 你或许认为像下面这样在`title`标签上加上一个绑定就行了: 100 | 101 | Google Phone Gallery: {{query}} 102 | 103 | 但是,当你重载页面的时候,你根本没办法得到期望的结果。这是因为`query`模型仅仅在`body`元素定义的作用域内才有效。 104 | 105 | 106 | 107 | 如果你想让``元素绑定上`query`模型,你必须把`ngController`声明**移动**到`HTML`元素上,因为它是`title`和`body`元素的共同祖先。 108 | 109 | <html ng-app ng-controller="PhoneListCtrl"> 110 | 111 | 一定要注意把`body`元素上的`ng-controller`声明给删了。 112 | 113 | 当绑定两个花括号在`title`元素上可以实现我们的目标,但是你或许发现了,页面正加载的时候它们已经显示给用户看了。一个更好的解决方案是使用[ngBind][]或者[ngBindTemplate][]指令,它们在页面加载时对用户是不可见的: 114 | 115 | <title ng-bind-template="Google Phone Gallery: {{query}}">Google Phone Gallery 116 | 117 | - 在`test/e2e/scenarios.js`的`describe`块中加入下面这些端到端测试代码: 118 | 119 | it('should display the current filter value within an element with id "status"', 120 | function() { 121 | expect(element('#status').text()).toMatch(/Current filter: \s*$/); 122 | input('query').enter('nexus'); 123 | expect(element('#status').text()).toMatch(/Current filter: nexus\s*$/); 124 | //alternative version of the last assertion that tests just the value of the binding 125 | using('#status').expect(binding('query')).toBe('nexus'); 126 | }); 127 | 128 | 刷新浏览器,端到端测试器会报告测试失败。为了让测试通过,编辑`index.html`,添加一个`id为“status”`的`div`或者`p`元素,内容是一个`query`绑定,再加上`Current filter:`前缀。例如: 129 | 130 |
    Current filter: {{query}}
    131 | 132 | - 在端到端测试里面加一条`pause();`语句,重新跑一遍。你将发现测试器暂停了!这样允许你有机会在测试运行过程中查看你应用的状态。测试应用是实时的!你可以更换搜索内容来证明。稍有经验你就会知道,这对于在端到端测试中迅速找到问题是多么的关键。 133 | 134 | ## 总结 135 | 136 | 我们现在添加了全文搜索功能,并且完成一个测试证明了搜索是对的!现在让我们继续到[步骤4][step_04]来看看给我们的手机应用增加排序功能。 137 | 138 | 139 | [GitHub]: https://github.com/angular/angular-phonecat/compare/step-2...step-3 140 | [AngularJS端到端测试器]: http://code.angularjs.org/1.1.0/docs/guide/dev_guide.e2e-testing 141 | [dev_guide.e2e-testing]: http://docs.angularjs.org/guide/dev_guide.e2e-testing 142 | [img_tutorial_03]: http://docs.angularjs.org/img/tutorial/tutorial_03.png 143 | [ngBind]: http://docs.angularjs.org/api/ng.directive:ngBind 144 | [ngBindTemplate]: http://docs.angularjs.org/api/ng.directive:ngBindTemplate 145 | [filter]: http://docs.angularjs.org/api/ng.filter:filter 146 | [ngRepeat]: http://docs.angularjs.org/api/ng.directive:ngRepeat 147 | [step_04]: http://angularjs.cn/A007 148 | -------------------------------------------------------------------------------- /AngularJS入门教程04:双向绑定.md: -------------------------------------------------------------------------------- 1 | 在这一步你会增加一个让用户控制手机列表显示顺序的特性。动态排序可以这样实现,添加一个新的模型属性,把它和迭代器集成起来,然后让数据绑定完成剩下的事情。 2 | 3 | 请重置工作目录: 4 | 5 | git checkout -f step-4 6 | 7 | 你应该发现除了搜索框之外,你的应用多了一个下来菜单,它可以允许控制电话排列的顺序。 8 | 9 | 步骤3和步骤4之间最重要的不同在下面列出。你可以在[GitHub][]里看到完整的差别。 10 | 11 | ### 模板 12 | 13 | **app/index.html** 14 | 15 | Search: 16 | Sort by: 17 | 21 | 22 | 23 |
      24 |
    • 25 | {{phone.name}} 26 |

      {{phone.snippet}}

      27 |
    • 28 |
    29 | 30 | 我们在**index.html**中做了如下更改: 31 | 32 | - 首先,我们增加了一个叫做`orderProp`的` 105 | Sort by: 106 | 110 | 111 |
  • 112 |
    113 | 114 | 115 |
      116 |
    • 117 | 118 | {{phone.name}} 119 |

      {{phone.snippet}}

      120 |
    • 121 |
    122 | 123 |
    124 |
    125 | 126 | 127 | 同时我们为手机详细信息视图添加一个占位模板。 128 | 129 | **app/partials/phone-detail.html** 130 | 131 | TBD: detail view for {{phoneId}} 132 | 133 | 注意到我们的布局模板中没再添加`PhoneListCtrl`或`PhoneDetailCtrl`控制器属性! 134 | 135 | ### 测试 136 | 137 | 为了自动验证所有的东西都良好地集成起来,我们需要写一些端到端测试,导航到不同的URL上然后验证正确地视图被渲染出来。 138 | 139 | ... 140 | it('should redirect index.html to index.html#/phones', function() { 141 | browser().navigateTo('../../app/index.html'); 142 | expect(browser().location().url()).toBe('/phones'); 143 | }); 144 | ... 145 | 146 | describe('Phone detail view', function() { 147 | 148 | beforeEach(function() { 149 | browser().navigateTo('../../app/index.html#/phones/nexus-s'); 150 | }); 151 | 152 | 153 | it('should display placeholder page with phoneId', function() { 154 | expect(binding('phoneId')).toBe('nexus-s'); 155 | }); 156 | }); 157 | 158 | 你现在可以刷新你的浏览器,然后重新跑一遍端到端测试,或者你可以在[AngularJS的服务器](http://angular.github.com/angular-phonecat/step-4/test/e2e/runner.html)上运行一下。 159 | 160 | ## 练习 161 | 162 | 试着在`index.html`上增加一个`{{orderProp}}`绑定,当你在手机列表视图上时什么也没变。这是因为`orderProp`模型仅仅在`PhoneListCtrl`管理的作用域下才是可见的,这与`
    `元素相关。如果你在`phone-list.html`模板中加入同样的绑定,那么这个绑定会按你设想的那样被渲染出来。 163 | 164 | ## 总结 165 | 166 | 设置路由并实现手机列表视图之后,我们已经可以进入[步骤8][step_08]来实现手机详细信息视图了。 167 | 168 | [GitHub]: https://github.com/angular/angular-phonecat/compare/step-6...step-7 169 | [ng.$routeProvider]: http://code.angularjs.org/1.1.0/docs/api/ng.$routeProvider 170 | [ng.$route]: http://code.angularjs.org/1.1.0/docs/api/ng.$route 171 | [guide/di]: http://code.angularjs.org/1.1.0/docs/guide/di 172 | [ng.$routeParams]: http://code.angularjs.org/1.1.0/docs/api/ng.$routeParams 173 | [ng.directive:ngApp]: http://code.angularjs.org/1.1.0/docs/api/ng.directive:ngApp 174 | [ng.directive:ngView]: http://code.angularjs.org/1.1.0/docs/api/ng.directive:ngView 175 | [step_08]: http://angularjs.cn/A00b 176 | -------------------------------------------------------------------------------- /AngularJS入门教程08:更多模板.md: -------------------------------------------------------------------------------- 1 | 在这一步,你将实现手机详细信息视图,这个视图会在用户点击手机列表中的一部手机时被显示出来。 2 | 3 | 请重置工作目录: 4 | 5 | git checkout -f step-8 6 | 7 | 现在当你点击列表中的一部手机之后,这部手机的详细信息页面就会被显示出来。 8 | 9 | 为了实现手机详细信息视图我们将会使用[$http][ng.$http]来获取数据,同时我们也要增添一个`phone-detail.html`视图模板。 10 | 11 | 步骤7和步骤8之间最重要的不同在下面列出。你可以在[GitHub](https://github.com/angular/angular-phonecat/compare/step-7...step-8)里看到完整的差别。 12 | 13 | ### 数据 14 | 15 | 除了`phones.json`,`app/phones/`目录也包含了每一部手机信息的json文件。 16 | 17 | **app/phones/nexus-s.json**(样例片段) 18 | 19 | { 20 | "additionalFeatures": "Contour Display, Near Field Communications (NFC),...", 21 | "android": { 22 | "os": "Android 2.3", 23 | "ui": "Android" 24 | }, 25 | ... 26 | "images": [ 27 | "img/phones/nexus-s.0.jpg", 28 | "img/phones/nexus-s.1.jpg", 29 | "img/phones/nexus-s.2.jpg", 30 | "img/phones/nexus-s.3.jpg" 31 | ], 32 | "storage": { 33 | "flash": "16384MB", 34 | "ram": "512MB" 35 | } 36 | } 37 | 38 | 这些文件中的每一个都用相同的数据结构描述了一部手机的不同属性。我们会在手机详细信息视图中显示这些数据。 39 | 40 | ### 控制器 41 | 42 | 我们使用`$http`服务获取数据,以此来拓展我们的`PhoneListCtrl`。这和之前的手机列表控制器的工作方式是一样的。 43 | 44 | **app/js/controllers.js** 45 | 46 | function PhoneDetailCtrl($scope, $routeParams, $http) { 47 | $http.get('phones/' + $routeParams.phoneId + '.json').success(function(data) { 48 | $scope.phone = data; 49 | }); 50 | } 51 | 52 | //PhoneDetailCtrl.$inject = ['$scope', '$routeParams', '$http']; 53 | 54 | 为了构造HTTP请求的URL,我们需要`$route`服务提供的当前路由中抽取`$routeParams.phoneId`。 55 | 56 | ### 模板 57 | 58 | **phone-detail.html**文件中原有的TBD占位行已经被列表和构成手机详细信息的绑定替换掉了。注意到,这里我们使用AngularJS的`{{表达式}}`标记和`ngRepeat`来在视图中渲染数据模型。 59 | 60 | **app/partials/phone-detail.html** 61 | 62 | 63 | 64 |

    {{phone.name}}

    65 | 66 |

    {{phone.description}}

    67 | 68 | 73 | 74 | 88 | 89 | ### 测试 90 | 91 | 我们来写一个新的单元测试,这个测试和我们在步骤5中为`PhoneListCtrl`写的那个很像。 92 | 93 | **test/unit/controllersSpec.js** 94 | 95 | ... 96 | describe('PhoneDetailCtrl', function(){ 97 | var scope, $httpBackend, ctrl; 98 | 99 | beforeEach(inject(function(_$httpBackend_, $rootScope, $routeParams, $controller) { 100 | $httpBackend = _$httpBackend_; 101 | $httpBackend.expectGET('phones/xyz.json').respond({name:'phone xyz'}); 102 | 103 | $routeParams.phoneId = 'xyz'; 104 | scope = $rootScope.$new(); 105 | ctrl = $controller(PhoneDetailCtrl, {$scope: scope}); 106 | })); 107 | 108 | 109 | it('should fetch phone detail', function() { 110 | expect(scope.phone).toBeUndefined(); 111 | $httpBackend.flush(); 112 | 113 | expect(scope.phone).toEqual({name:'phone xyz'}); 114 | }); 115 | }); 116 | ... 117 | 118 | 执行`./scripts/test.sh`脚本来执行测试,你应该会看到如下输出: 119 | 120 | Chrome: Runner reset. 121 | ... 122 | Total 3 tests (Passed: 3; Fails: 0; Errors: 0) (5.00 ms) 123 | Chrome 19.0.1084.36 Mac OS: Run 3 tests (Passed: 3; Fails: 0; Errors 0) (5.00 ms) 124 | 125 | 同时,我们也添加一个端到端测试,指向Nexus S手机详细信息页面并且验证页面的头部是“Nexus S”。 126 | 127 | **test/e2e/scenarios.js** 128 | 129 | ... 130 | describe('Phone detail view', function() { 131 | 132 | beforeEach(function() { 133 | browser().navigateTo('../../app/index.html#/phones/nexus-s'); 134 | }); 135 | 136 | 137 | it('should display nexus-s page', function() { 138 | expect(binding('phone.name')).toBe('Nexus S'); 139 | }); 140 | }); 141 | ... 142 | 143 | 你现在可以刷新你的浏览器,然后重新跑一遍端到端测试,或者你可以在[AngularJS的服务器](http://angular.github.com/angular-phonecat/step-4/test/e2e/runner.html)上运行一下。 144 | 145 | ## 练习 146 | 147 | 使用[AngularJS端到端测试API][dev_guide.e2e-testing]写一个测试,用它来验证我们在Nexus S详细信息页面显示的四个缩略图。 148 | 149 | ## 总结 150 | 现在手机详细页面已经就绪了,在[步骤9][step_09]中我们将学习如何写一个显示过滤器。 151 | 152 | [ng.$http]: http://code.angularjs.org/1.1.0/docs/api/ng.$http 153 | [dev_guide.e2e-testing]: http://code.angularjs.org/1.1.0/docs/guide/dev_guide.e2e-testing 154 | [step_09]: http://angularjs.cn/A00c 155 | -------------------------------------------------------------------------------- /AngularJS入门教程09:过滤器.md: -------------------------------------------------------------------------------- 1 | 在这一步你将学习到如何创建自己的显示过滤器。 2 | 3 | 请重置工作目录: 4 | 5 | git checkout -f step-9 6 | 7 | 现在转到一个手机详细信息页面。在上一步,手机详细页面显示“true”或者“false”来说明某个手机是否具有特定的特性。现在我们使用一个定制的过滤器来把那些文本串图形化:√作为“true”;以及×作为“false”。来让我们看看过滤器代码长什么样子。 8 | 9 | 步骤8和步骤9之间最重要的不同在下面列出。你可以在[GitHub][]里看到完整的差别。 10 | 11 | ### 定制过滤器 12 | 13 | 为了创建一个新的过滤器,先创建一个`phonecatFilters`模块,并且将定制的过滤器注册给这个模块。 14 | 15 | **app/js/filters.js** 16 | 17 | angular.module('phonecatFilters', []).filter('checkmark', function() { 18 | return function(input) { 19 | return input ? '\u2713' : '\u2718'; 20 | }; 21 | }); 22 | 23 | 我们的过滤器命名为**checkmark**。它的输入要么是`true`,要么是`false`,并且我们返回两个表示true或false的unicode字符(`\u2713和\u2718`)。 24 | 25 | 现在我们的过滤器准备好了,我们需要将我们的`phonecatFilters`模块作为一个依赖注册到我们的主模块`phonecat`上。 26 | 27 | **app/js/app/js** 28 | 29 | ... 30 | angular.module('phonecat', ['phonecatFilters']). 31 | ... 32 | 33 | ### 模板 34 | 35 | 由于我们的模板代码写在`app/js/filter.js`文件中,所以我们需要在布局模板中引入这个文件。 36 | 37 | **app/index.html** 38 | 39 | ... 40 | 41 | 42 | ... 43 | 44 | 在AngularJS模板中使用过滤器的语法是: 45 | 46 | {{ expression | filter }} 47 | 48 | 我们把过滤器应用到手机详细信息模板中: 49 | 50 | **app/partials/phone-detail.html** 51 | 52 | ... 53 |
    54 |
    Infrared
    55 |
    {{phone.connectivity.infrared | checkmark}}
    56 |
    GPS
    57 |
    {{phone.connectivity.gps | checkmark}}
    58 |
    59 | ... 60 | 61 | ### 测试 62 | 过滤器和其他组件一样,应该被测试,并且这些测试实际上很容易完成。 63 | 64 | **test/unit/filtersSpec.js** 65 | 66 | describe('filter', function() { 67 | 68 | beforeEach(module('phonecatFilters')); 69 | 70 | 71 | describe('checkmark', function() { 72 | 73 | it('should convert boolean values to unicode checkmark or cross', 74 | inject(function(checkmarkFilter) { 75 | expect(checkmarkFilter(true)).toBe('\u2713'); 76 | expect(checkmarkFilter(false)).toBe('\u2718'); 77 | })); 78 | }); 79 | }); 80 | 81 | 注意在执行任何过滤器测试之前,你需要为`phonecatFilters`模块配置我们的测试注入器。 82 | 83 | 执行`./scripts/test/sh`运行测试,你应该会看到如下的输出: 84 | 85 | Chrome: Runner reset. 86 | .... 87 | Total 4 tests (Passed: 4; Fails: 0; Errors: 0) (3.00 ms) 88 | Chrome 19.0.1084.36 Mac OS: Run 4 tests (Passed: 4; Fails: 0; Errors 0) (3.00 ms) 89 | 90 | ## 练习 91 | 92 | * 现在让我们来练习一下[AngularJS内置过滤器][ng.$filter],在**index.html**中加入如下绑定: 93 | * `{{ "lower cap string" | uppercase }}` 94 | * `{{ {foo: "bar", baz: 23} | json }}` 95 | * `{{ 1304375948024 | date }}` 96 | * `{{ 1304375948024 | date:"MM/dd/yyyy @ h:mma" }}` 97 | 98 | * 我们也可以用一个输入框来创建一个模型,并且将之与一个过滤后的绑定结合在一起。在**index.html**中加入如下代码: 99 | 100 | Uppercased: {{ userInput | uppercase }} 101 | 102 | ## 总结 103 | 104 | 现在你已经知道了如何编写和测试一个定制化插件,在[步骤10][step_10]中我们会学习如何用AngularJS继续丰富我们的手机详细信息页面。 105 | 106 | [GitHub]: https://github.com/angular/angular-phonecat/compare/step-8...step-9 107 | [ng.$filter]: http://code.angularjs.org/1.1.0/docs/api/ng.$filter 108 | [step_10]: http://angularjs.cn/A00d 109 | -------------------------------------------------------------------------------- /AngularJS入门教程10:事件处理器.md: -------------------------------------------------------------------------------- 1 | 在这一步,你会在手机详细信息页面让手机图片可以点击。 2 | 3 | 请重置工作目录: 4 | 5 | git checkout -f step-10 6 | 7 | 手机详细信息视图展示了一幅当前手机的大号图片,以及几个小一点的缩略图。如果用户点击缩略图就能把那张大的替换成自己那就更好了。现在我们来看看如何用AngularJS来实现它。 8 | 9 | 步骤9和步骤10之间最重要的不同在下面列出。你可以在[GitHub][]里看到完整的差别。 10 | 11 | ### 控制器 12 | 13 | **app/js/controllers.js** 14 | 15 | ... 16 | function PhoneDetailCtrl($scope, $routeParams, $http) { 17 | $http.get('phones/' + $routeParams.phoneId + '.json').success(function(data) { 18 | $scope.phone = data; 19 | $scope.mainImageUrl = data.images[0]; 20 | }); 21 | 22 | $scope.setImage = function(imageUrl) { 23 | $scope.mainImageUrl = imageUrl; 24 | } 25 | } 26 | 27 | //PhoneDetailCtrl.$inject = ['$scope', '$routeParams', '$http']; 28 | 29 | 在`PhoneDetailCtrl`控制器中,我们创建了`mainImageUrl`模型属性,并且把它的默认值设为第一个手机图片的URL。 30 | 31 | ### 模板 32 | 33 | **app/partials/phone-detail.html** 34 | 35 | 36 | 37 | ... 38 | 39 | 44 | ... 45 | 46 | 我们把大图片的`ngSrc`指令绑定到`mainImageUrl`属性上。 47 | 48 | 同时我们注册一个[ngClick][ng.directive:ngClick]处理器到缩略图上。当一个用户点击缩略图的任意一个时,这个处理器会使用`setImage`事件处理函数来把`mainImageUrl`属性设置成选定缩略图的URL。 49 | 50 | ### 测试 51 | 52 | 为了验证这个新特性,我们添加了两个端到端测试。一个验证主图片被默认设置成第一个手机图片。第二个测试点击几个缩略图并且验证主图片随之合理的变化。 53 | 54 | **test/e2e/scenarios.js** 55 | 56 | ... 57 | describe('Phone detail view', function() { 58 | 59 | ... 60 | 61 | it('should display the first phone image as the main phone image', function() { 62 | expect(element('img.phone').attr('src')).toBe('img/phones/nexus-s.0.jpg'); 63 | }); 64 | 65 | it('should swap main image if a thumbnail image is clicked on', function() { 66 | element('.phone-thumbs li:nth-child(3) img').click(); 67 | expect(element('img.phone').attr('src')).toBe('img/phones/nexus-s.2.jpg'); 68 | 69 | element('.phone-thumbs li:nth-child(1) img').click(); 70 | expect(element('img.phone').attr('src')).toBe('img/phones/nexus-s.0.jpg'); 71 | }); 72 | }); 73 | }); 74 | 75 | 你现在可以刷新你的浏览器,然后重新跑一遍端到端测试,或者你可以在[AngularJS的服务器](http://angular.github.com/angular-phonecat/step-4/test/e2e/runner.html)上运行一下。 76 | 77 | ## 练习 78 | 79 | 为`PhoneDetailCtrl`添加一个新的控制器方法: 80 | 81 | $scope.hello = function(name) { 82 | alert('Hello ' + (name || 'world') + '!'); 83 | } 84 | 85 | 并且添加: 86 | 87 | 88 | 89 | 到**phone-details.html**模板。 90 | 91 | ## 总结 92 | 93 | 现在图片浏览器已经做好了,我们已经为[步骤11][step_11](最后一步啦!)做好了准备,我们会学习用一种更加优雅的方式来获取数据。 94 | 95 | [GitHub]: https://github.com/angular/angular-phonecat/compare/step-9...step-10 96 | [ng.directive:ngClick]: http://code.angularjs.org/1.1.0/docs/api/ng.directive:ngClick 97 | [step_11]: http://angularjs.cn/A00e 98 | -------------------------------------------------------------------------------- /AngularJS入门教程11:REST和定制服务.md: -------------------------------------------------------------------------------- 1 | 在这一步中,我们会改进我们APP获取数据的方式。 2 | 3 | 请重置工作目录: 4 | 5 | git checkout -f step-11 6 | 7 | 对我们应用所做的最后一个改进就是定义一个代表[RESTful][]客户端的定制服务。有了这个客户端我们可以用一种更简单的方式来发送XHR请求,而不用去关心更底层的[$http][ng.$http]服务(API、HTTP方法和URL)。 8 | 9 | 步骤9和步骤10之间最重要的不同在下面列出。你可以在[GitHub][]里看到完整的差别。 10 | 11 | ### 模板 12 | 13 | 定制的服务被定义在**app/js/services**,所以我们需要在布局模板中引入这个文件。另外,我们也要加载**angularjs-resource.js**这个文件,它包含了`ngResource`模块以及其中的`$resource`服务,我们一会就会用到它们: 14 | 15 | **app/index.html** 16 | 17 | ... 18 | 19 | 20 | ... 21 | 22 | ### 服务 23 | 24 | **app/js/services.js** 25 | 26 | angular.module('phonecatServices', ['ngResource']). 27 | factory('Phone', function($resource){ 28 | return $resource('phones/:phoneId.json', {}, { 29 | query: {method:'GET', params:{phoneId:'phones'}, isArray:true} 30 | }); 31 | }); 32 | 33 | 我们使用模块API通过一个工厂方法注册了一个定制服务。我们传入服务的名字`Phone`和工厂函数。工厂函数和控制器构造函数差不多,它们都通过函数参数声明依赖服务。Phone服务声明了它依赖于`$resource`服务。 34 | 35 | [$resource][ngResource.$resource]服务使得用短短的几行代码就可以创建一个[RESTful][]客户端。我们的应用使用这个客户端来代替底层的[$http][ng.$http]服务。 36 | 37 | **app/js/app.js** 38 | 39 | ... 40 | angular.module('phonecat', ['phonecatFilters', 'phonecatServices']). 41 | ... 42 | 43 | 我们需要把`phonecatServices`添加到`phonecat`的依赖数组里。 44 | 45 | ### 控制器 46 | 47 | 通过重构掉底层的[$http][ng.$http]服务,把它放在一个新的服务`Phone`中,我们可以大大简化子控制器(`PhoneListCtrl`和`PhoneDetailCtrl`)。AngularJS的[$resource][ngResource.$resource]相比于`$http`更加适合于与RESTful数据源交互。而且现在我们更容易理解控制器这些代码在干什么了。 48 | 49 | **app/js/controllers.js** 50 | 51 | ... 52 | 53 | function PhoneListCtrl($scope, Phone) { 54 | $scope.phones = Phone.query(); 55 | $scope.orderProp = 'age'; 56 | } 57 | 58 | //PhoneListCtrl.$inject = ['$scope', 'Phone']; 59 | 60 | 61 | 62 | function PhoneDetailCtrl($scope, $routeParams, Phone) { 63 | $scope.phone = Phone.get({phoneId: $routeParams.phoneId}, function(phone) { 64 | $scope.mainImageUrl = phone.images[0]; 65 | }); 66 | 67 | $scope.setImage = function(imageUrl) { 68 | $scope.mainImageUrl = imageUrl; 69 | } 70 | } 71 | 72 | //PhoneDetailCtrl.$inject = ['$scope', '$routeParams', 'Phone']; 73 | 74 | 注意到,在`PhoneListCtrl`里我们把: 75 | 76 | $http.get('phones/phones.json').success(function(data) { 77 | $scope.phones = data; 78 | }); 79 | 80 | 换成了: 81 | 82 | $scope.phones = Phone.query(); 83 | 84 | 我们通过这条简单的语句来查询所有的手机。 85 | 86 | 另一个非常需要注意的是,在上面的代码里面,当调用Phone服务的方法是我们并没有传递任何回调函数。尽管这看起来结果是同步返回的,其实根本就不是。被同步返回的是一个“future”——一个对象,当XHR相应返回的时候会填充进数据。鉴于AngularJS的数据绑定,我们可以使用future并且把它绑定到我们的模板上。然后,当数据到达时,我们的视图会自动更新。 87 | 88 | 有的时候,单单依赖future对象和数据绑定不足以满足我们的需求,所以在这些情况下,我们需要添加一个回调函数来处理服务器的响应。`PhoneDetailCtrl`控制器通过在一个回调函数中设置`mainImageUrl`就是一个解释。 89 | 90 | ### 测试 91 | 92 | 修改我们的单元测试来验证我们新的服务会发起HTTP请求并且按照预期地处理它们。测试同时也检查了我们的控制器是否与服务正确协作。 93 | 94 | [$resource][ngResource.$resource]服务通过添加更新和删除资源的方法来增强响应得到的对象。如果我们打算使用`toEqual`匹配器,我们的测试会失败,因为测试值并不会和响应完全等同。为了解决这个问题,我们需要使用一个最近定义的`toEqualData`[Jasmine匹配器][]。当`toEqualData`匹配器比较两个对象的时候,它只考虑对象的属性而忽略掉所有的方法。 95 | 96 | **test/unit/controllersSpec.js:** 97 | 98 | describe('PhoneCat controllers', function() { 99 | 100 | beforeEach(function(){ 101 | this.addMatchers({ 102 | toEqualData: function(expected) { 103 | return angular.equals(this.actual, expected); 104 | } 105 | }); 106 | }); 107 | 108 | beforeEach(module('phonecatServices')); 109 | 110 | describe('PhoneListCtrl', function(){ 111 | var scope, ctrl, $httpBackend; 112 | 113 | beforeEach(inject(function(_$httpBackend_, $rootScope, $controller) { 114 | $httpBackend = _$httpBackend_; 115 | $httpBackend.expectGET('phones/phones.json'). 116 | respond([{name: 'Nexus S'}, {name: 'Motorola DROID'}]); 117 | 118 | scope = $rootScope.$new(); 119 | ctrl = $controller(PhoneListCtrl, {$scope: scope}); 120 | })); 121 | 122 | it('should create "phones" model with 2 phones fetched from xhr', function() { 123 | expect(scope.phones).toEqual([]); 124 | $httpBackend.flush(); 125 | 126 | expect(scope.phones).toEqualData( 127 | [{name: 'Nexus S'}, {name: 'Motorola DROID'}]); 128 | }); 129 | 130 | it('should set the default value of orderProp model', function() { 131 | expect(scope.orderProp).toBe('age'); 132 | }); 133 | }); 134 | 135 | describe('PhoneDetailCtrl', function(){ 136 | var scope, $httpBackend, ctrl, 137 | xyzPhoneData = function() { 138 | return { 139 | name: 'phone xyz', 140 | images: ['image/url1.png', 'image/url2.png'] 141 | } 142 | }; 143 | 144 | beforeEach(inject(function(_$httpBackend_, $rootScope, $routeParams, $controller) { 145 | $httpBackend = _$httpBackend_; 146 | $httpBackend.expectGET('phones/xyz.json').respond(xyzPhoneData()); 147 | 148 | $routeParams.phoneId = 'xyz'; 149 | scope = $rootScope.$new(); 150 | ctrl = $controller(PhoneDetailCtrl, {$scope: scope}); 151 | })); 152 | 153 | it('should fetch phone detail', function() { 154 | expect(scope.phone).toEqualData({}); 155 | $httpBackend.flush(); 156 | 157 | expect(scope.phone).toEqualData(xyzPhoneData()); 158 | }); 159 | }); 160 | }); 161 | 162 | 执行`./scripts/test.sh`运行测试,你应该会看到如下的输出: 163 | 164 | Chrome: Runner reset. 165 | .... 166 | Total 4 tests (Passed: 4; Fails: 0; Errors: 0) (3.00 ms) 167 | Chrome 19.0.1084.36 Mac OS: Run 4 tests (Passed: 4; Fails: 0; Errors 0) (3.00 ms) 168 | 169 | ## 总结 170 | 171 | 完工!你在相当短的时间内已经创建了一个Web应用。在[完结篇][the_end]里面我们会提起接下来应该干什么。 172 | 173 | [RESTful]: http://en.wikipedia.org/wiki/Representational_State_Transfer 174 | [ng.$http]: http://code.angularjs.org/1.1.0/docs/api/ng.$http 175 | [GitHub]: https://github.com/angular/angular-phonecat/compare/step-10...step-11 176 | [ngResource.$resource]: http://code.angularjs.org/1.1.0/docs/api/ngResource.$resource 177 | [Jasmine匹配器]: http://pivotal.github.com/jasmine/jsdoc/symbols/jasmine.Matchers.html 178 | [the_end]: http://angularjs.cn/A00f 179 | -------------------------------------------------------------------------------- /AngularJS入门教程12:完结篇.md: -------------------------------------------------------------------------------- 1 | 我们的应用现在完成了。你可以随意练习这些代码,用`git checkout`或者`goto_step.sh`命令切换到之前的步骤。 2 | 3 | 对于更多我们在教程部分提及的细节以及AngularJS理论的例子,你可以在[开发指南][guide]中找到。 4 | 5 | 一些更多的例子,请参照[Cookbook][]。 6 | 7 | 当你准备好使用AngularJS创建一个新项目时,我们推荐使用[AngularJS种子项目](https://github.com/angular/angular-seed)来引导你的开发。 8 | 9 | 我们希望这篇教程对你有用,让你对AngularJS有了足够的了解,并且愿意对其进行更加深入的学习。我们特别期待着你能够开发出自己的AngularJS应用,或者让你对AngularJS[贡献代码](http://docs.angularjs.org/misc/contribute)产生兴趣。 10 | 11 | 如果你有什么问题,反馈,或是想跟我们打个招呼,可在[社区论坛][]交流,或者直接向发消息吧! 12 | 13 | [guide]: http://angularjs.cn/tag/AngularJS_开发指南 14 | [cookbook]: http://docs.angularjs.org/cookbook/index 15 | [社区论坛]: http://angularjs.cn/tag/AngularJS 16 | -------------------------------------------------------------------------------- /AngularJS入门教程目录.md: -------------------------------------------------------------------------------- 1 | 1. [快速开始](http://angularjs.cn/A002 "AngularJS快速开始") 2 | 2. [导言和准备](http://angularjs.cn/A00g "AngularJS入门教程:导言和准备") 3 | 3. [引导程序](http://angularjs.cn/A003 "AngularJS入门教程00:引导程序") 4 | 4. [静态模板](http://angularjs.cn/A004 "AngularJS入门教程01:静态模板") 5 | 5. [AngularJS模板](http://angularjs.cn/A005 "AngularJS入门教程02:AngularJS模板") 6 | 6. [迭代器过滤](http://angularjs.cn/A006 "AngularJS入门教程03:迭代器过滤") 7 | 7. [双向绑定](http://angularjs.cn/A007 "AngularJS入门教程04:双向绑定") 8 | 8. [XHR和依赖注入](http://angularjs.cn/A008 "AngularJS入门教程05:XHR和依赖注入") 9 | 9. [链接与图片模板](http://angularjs.cn/A009 "AngularJS入门教程06:链接与图片模板") 10 | 10. [路由与多视图](http://angularjs.cn/A00a "AngularJS入门教程07:路由与多视图") 11 | 11. [更多模板](http://angularjs.cn/A00b "AngularJS入门教程08:更多模板") 12 | 12. [过滤器](http://angularjs.cn/A00c "AngularJS入门教程09:过滤器") 13 | 13. [事件处理器](http://angularjs.cn/A00d "AngularJS入门教程10:事件处理器") 14 | 14. [REST和定制服务](http://angularjs.cn/A00e "AngularJS入门教程11:REST和定制服务") 15 | 15. [完结篇](http://angularjs.cn/A00f "AngularJS入门教程12:完结篇") 16 | -------------------------------------------------------------------------------- /AngularJS入门教程:导言和准备.md: -------------------------------------------------------------------------------- 1 | 学习AngularJS的一个好方法是逐步完成本教程,它将引导您构建一个完整的AngularJS web应用程序。 该web应用是一个Android设备清单的目录列表,您可以筛选列表以便查看您感兴趣的设备,然后查看设备的详细信息。 2 | 3 | ![PhoneApp][PhoneApp] 4 | 5 | **本教程将向您展示AngularJS怎样使得web应用更智能更灵活,而且不需要各种扩展程序或插件。 通过本教程的学习,您将:** 6 | 7 | 1. 阅读示例学习怎样使用AngularJS的客户端数据绑定和依赖注入功能来建立可立即响应用户操作的动态数据视图。 8 | 2. 学习如何使用AngularJS创建数据侦听器,且不需要进行DOM操作。 9 | 3. 学习一种更好、更简单的方法来测试您的web应用程序。 10 | 4. 学习如何使用AngularJS创建常见的web任务,例如更方便的将数据引入应用程序。 11 | 12 | 而且这一切可在任何一个浏览器实现,无需配置浏览器! 13 | 14 | **当你完成了本教程后,您将学会:** 15 | 16 | 1. 创建一个可在任何浏览器中的工作的动态应用。 17 | 2. 了解AngularJS与其它JavaScript框架之间的区别。 18 | 3. 了解AngularJS如何实现数据绑定。 19 | 4. 利用AngularJS的种子项目快速创建自己的项目。 20 | 5. 创建和运行测试。 21 | 6. 学习更多AngularJS标识资源(API)。 22 | 23 | 本教程将指导您完成一个简单的应用程序创建过程,包括编写和运行单元测试、不断地测试应用。 教程的每个步骤为您提供建议以了解更多有关AngularJS和您创建的web应用程序。 24 | 您可能会在短时间内快速读完本教程,也可能需要花大量时间深入研究本教程。 如果想看一个简短的AngularJS介绍文档,请查看[快速开始][ Getting Started]文档。 25 | 26 | ## 搭建学习环境 27 | 28 | 无论是Mac、Linux或Windows环境中,您均可遵循本教程学习编程。您可以使用源代码管理版本控制系统Git获取本教程项目的源代码文件,或直接从网上下载本教程项目源代码文件的镜像归档压缩包。 29 | 30 | 1. 您需要安装Node.js和Testacular来运行本项目,请到[Node.js][Node.js]官方网站下载并安装最新版,然后把node可执行程序路径添加到系统环境变量`PATH`中,完成后在命令行中运行一下命令可以查看是否安装成功: 31 | 32 | node -version 33 | 34 | 然后安装[Testacular][Testacular]单元测试程序,请运行如下命令: 35 | 36 | npm install -g testacular 37 | 38 | 2. 安装[Git][Git]工具,然后用以下命令从Github复制本教程项目的源代码文件: 39 | 40 | git clone git://github.com/angular/angular-phonecat.git 41 | 42 | 您也可以直接从网上[下载][angular-phonecat]本教程项目源代码的镜像归档压缩包。这个命令会在您当前文件夹中建立新文件夹`angular-phonecat`。 43 | 44 | 3. 最后一件事要做的就是确保您的计算机安装了web浏览器和文本编辑器。 45 | 46 | 4. 进入教程源代码文件包angular-phonecat,运行服务器后台程序,开始[学习AngularJS][step_00]! 47 | 48 | cd angular-phonecat 49 | node scripts/web-server.js 50 | 51 | [step_00]: http://angularjs.cn/A003 52 | [PhoneApp]: http://docs.angularjs.org/img/tutorial/catalog_screen.png 53 | [Getting Started]: http://angularjs.cn/A002 54 | [Git]: http://git-scm.com/download 55 | [angular-phonecat]: https://github.com/angular/angular-phonecat 56 | [Node.js]: http://nodejs.org/ 57 | [Testacular]: http://vojtajina.github.com/testacular 58 | -------------------------------------------------------------------------------- /AngularJS快速开始.md: -------------------------------------------------------------------------------- 1 | ## Hello World! 2 | 3 | 开始学习AngularJS的一个好方法是创建经典应用程序“Hello World!”: 4 | 5 | 1. 使用您喜爱的文本编辑器,创建一个HTML文件,例如:helloworld.html。 6 | 2. 将下面的源代码复制到您的HTML文件。 7 | 3. 在web浏览器中打开这个HTML文件。 8 | 9 | **源代码** 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | Hello {{'World'}}! 18 | 19 | 20 | 21 | 请在您的浏览器中运行以上代码查看效果。 22 | 23 | 现在让我们仔细看看代码,看看到底怎么回事。 当加载该页时,标记`ng-app`告诉AngularJS处理整个HTML页并引导应用: 24 | 25 | 26 | 27 | 28 | 这行载入AngularJS脚本: 29 | 30 | 31 | 32 | 33 | (想了解AngularJS处理整个HTML页的细节,请看Bootstrap。) 34 | 35 | 最后,标签中的正文是应用的模板,在UI中显示我们的问候语: 36 | 37 | Hello {{'World'}}! 38 | 39 | 40 | 注意,使用双大括号标记`{{}}`的内容是问候语中绑定的表达式,这个表达式是一个简单的字符串‘World’。 41 | 42 | 下面,让我们看一个更有趣的例子:使用AngularJS对我们的问候语文本绑定一个动态表达式。 43 | 44 | ## Hello AngularJS World! 45 | 46 | 本示例演示AngularJS的双向数据绑定(bi-directional data binding): 47 | 48 | 1. 编辑前面创建的helloworld.html文档。 49 | 2. 将下面的源代码复制到您的HTML文件。 50 | 3. 刷新浏览器窗口。 51 | 52 | **源代码** 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | Your name: 61 |
    62 | Hello {{yourname || 'World'}}! 63 | 64 | 65 | 66 | 请在您的浏览器中运行以上代码查看效果。 67 | 68 | 该示例有一下几点重要的注意事项: 69 | 70 | * 文本输入指令``绑定到一个叫`yourname`的模型变量。 71 | * 双大括号标记将`yourname`模型变量添加到问候语文本。 72 | * 你不需要为该应用另外注册一个事件侦听器或添加事件处理程序! 73 | 74 | 现在试着在输入框中键入您的名称,您键入的名称将立即更新显示在问候语中。 这就是AngularJS双向数据绑定的概念。 输入框的任何更改会立即反映到模型变量(一个方向),模型变量的任何更改都会立即反映到问候语文本中(另一方向)。 75 | 76 | ## AngularJS应用的解析 77 | 78 | 本节描述AngularJS应用程序的三个组成部分,并解释它们如何映射到模型-视图-控制器设计模式: 79 | 80 | ### 模板(Templates) 81 | 82 | 模板是您用HTML和CSS编写的文件,展现应用的视图。 您可给HTML添加新的元素、属性标记,作为AngularJS编译器的指令。 AngularJS编译器是完全可扩展的,这意味着通过AngularJS您可以在HTML中构建您自己的HTML标记! 83 | 84 | ### 应用程序逻辑(Logic)和行为(Behavior) 85 | 86 | 应用程序逻辑和行为是您用JavaScript定义的控制器。AngularJS与标准AJAX应用程序不同,您不需要另外编写侦听器或DOM控制器,因为它们已经内置到AngularJS中了。这些功能使您的应用程序逻辑很容易编写、测试、维护和理解。 87 | 88 | ### 模型数据(Data) 89 | 90 | 模型是从AngularJS作用域对象的属性引申的。模型中的数据可能是Javascript对象、数组或基本类型,这都不重要,重要的是,他们都属于AngularJS作用域对象。 91 | 92 | AngularJS通过作用域来保持数据模型与视图界面UI的双向同步。一旦模型状态发生改变,AngularJS会立即刷新反映在视图界面中,反之亦然。 93 | 94 | ### 此外,AngularJS还提供了一些非常有用的服务特性: 95 | 96 | 1. 底层服务包括依赖注入,XHR、缓存、URL路由和浏览器抽象服务。 97 | 2. 您还可以扩展和添加自己特定的应用服务。 98 | 3. 这些服务可以让您非常方便的编写WEB应用。 99 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | AngularjsTutorial_cn 2 | ==================== 3 | 4 | AngularJS入门教程——[AngularJS中文社区](http://angularjs.cn/)提供 5 | 6 | **AngularJS中文社区**是一个专业的AngularJS中文开源技术社区,致力于AngularJS的技术学习、交流和研究。 7 | 8 | 我们非常欢迎更多对AngularJS感兴趣的朋友[加入](http://angularjs.cn/register)! 9 | 10 | ----- 11 | 12 | 1、 **angularjs** 13 | 14 | **新浪微博:**[@ZENSH严清][1] **任务:**管理AngularJS中文社区,初步校核翻译文档; 15 | 16 | 2、 **rainer_H** 17 | 18 | **新浪微博:**[@l4future][2] **任务:**翻译“Dveloper Guide”部分; 19 | 20 | 3、**furtherLee** 21 | 22 | **新浪微博:**[@速冻沙漠][3] **任务:**翻译“Angular Tutorial”部分。 23 | 24 | 25 | [1]: http://weibo.com/zensh 26 | [2]: http://weibo.com/u/1856964593 27 | [3]: http://weibo.com/u/1901891651 28 | 29 | ### AngularJS入门教程目录 30 | 31 | 1. [快速开始](http://angularjs.cn/A002 "AngularJS快速开始") 32 | 2. [导言和准备](http://angularjs.cn/A00g "AngularJS入门教程:导言和准备") 33 | 3. [引导程序](http://angularjs.cn/A003 "AngularJS入门教程00:引导程序") 34 | 4. [静态模板](http://angularjs.cn/A004 "AngularJS入门教程01:静态模板") 35 | 5. [AngularJS模板](http://angularjs.cn/A005 "AngularJS入门教程02:AngularJS模板") 36 | 6. [迭代器过滤](http://angularjs.cn/A006 "AngularJS入门教程03:迭代器") 37 | 7. [双向绑定](http://angularjs.cn/A007 "AngularJS入门教程04:双向绑定") 38 | 8. [XHR和依赖注入](http://angularjs.cn/A008 "AngularJS入门教程05:XHR和依赖注入") 39 | 9. [链接与图片模板](http://angularjs.cn/A009 "AngularJS入门教程06:链接与图片模板") 40 | 10. [路由与多视图](http://angularjs.cn/A00a "AngularJS入门教程07:路由与多视图") 41 | 11. [更多模板](http://angularjs.cn/A00b "AngularJS入门教程08:更多模板") 42 | 12. [过滤器](http://angularjs.cn/A00c "AngularJS入门教程09:过滤器") 43 | 13. [事件处理器](http://angularjs.cn/A00d "AngularJS入门教程10:事件处理器") 44 | 14. [REST和定制服务](http://angularjs.cn/A00e "AngularJS入门教程11:REST和定制服务") 45 | 15. [完结篇](http://angularjs.cn/A00f "AngularJS入门教程12:完结篇") 46 | --------------------------------------------------------------------------------