AngularJS Phonecat(步骤0-步骤5)
2017-07-29 17:34
357 查看
导言
最近在学AngularJS的实例教程PhoneCat Tutorial App,发现网上的中文教程都比较久远,与英文版对应不上,而且缺少组件和文件重构两节。所以决定自己整理一个中文简明教程,内容较多,先整理0-5小节。教程展示一个Angular应用程序:
catalog_screen.png
涉及如下技术:
视图和模型的双向数据绑定;
Karma和Protractor测试;
组件化和模块化编程。
英文教程
配置
安装Git
下载Git,并安装。安装后可以使用git命令行工具git bash,在教程中只用到两个git命令:git clone ... 从远处版本仓库克隆代码到本地计算机
git checkout ...在本地计算机上查看(取出)特定版本(标签)的代码
拷贝源码
命令行中输入:git clone --depth=16 https://github.com/angular/angular-phonecat.git
然后打开项目文件:
cd angular-phonecat
注意:从现在开始,所有命令都是在angular-phonecat目录下执行。
安装Node
下载Node.js,并安装。所需Node的最低版本是 Node.js v4+,可以通过下面命令行确认node版本:node --version
安装工具
npm install该命令行会安装package.jason规定的工具到node_modules文件夹,并下载AngularJS框架到app/bower_components。
下载的工具有:
Bower - 客户端代码包管理工具
Http-Server - 简单的本地静态web服务器
Karma - 单元测试工具
Protractor - 端到端 (E2E) 测试工具
初步接触项目
npm start: 开启本地服务器npm test: 运行Karma单元测试工具
单元测试:npm test会自动打开谷歌浏览器和火狐,点击debug、再打开控制台可以查看报错信息。测试成功时,命令行窗口会返回success信息。
npm run update-webdriver: 安装Protractor所需驱动
npm run protractor: 运行Protractor端到端测试
端到端测试:npm run update-webdriver、npm start、npm run protractor。测试成功时,命令行窗口会返回success信息。
注意:输入 npm start 后,应该另外开一个命令行窗口(不能将服务器关闭,否则无法测试),再输入 npm run protractor命令。
0 准备
重置项目
git checkout -f step-0该命令将重置phonecat项目的工作目录,需要在每一学习步骤运行此命令,将step-0的0改成相应步骤的数字(如:2 AngularJS模板,则数字为2)。
启动服务器
npm start在浏览器中输入: http://localhost:8000/index.html,查看页面内容。
index.html
app/index.html:<!doctype html> <html lang="en" ng-app> <head> <meta charset="utf-8"> <title>My HTML File</title> <link rel="stylesheet" href="bower_components/bootstrap/dist/css/bootstrap.css" /> <script src="bower_components/angular/angular.js"></script> </head> <body> <p>Nothing here {{'yet' + '!'}}</p> </body> </html>
代码中,ng-app表示html元素会被Angular用作应用程序的根(root)元素。这就是说,ng-app规定以整个html页面还是部分元素作为Angular程序。
双大括号(Double-curly)绑定表达式:
Nothing here {{'yet'+'!'}}
这一行展示了Angular模板应用的两个核心功能:{{ }}进行绑定,简单表达式'yet'+'!'可以用于绑定。
程序结构如下:
tutorial_00.png
1 静态模板
重置项目
git checkout -f step-1跳到步骤1,后面不再讲这一步,每次都要重置,只需要改变数字。
index.html
app/index.html:<ul> <li> <span>Nexus S</span> <p> Fast just got faster with Nexus S. </p> </li> <li> <span>Motorola XOOM? with Wi-Fi</span> <p> The Next, Next Generation tablet. </p> </li> </ul> <p>Total number of phones: 2</p>
静态的HTML,这节没什么内容,直接进入下一部分。
2 AngularJS模板
视图和模板
视图是模型通过HTML模板渲染之后的映射。这意味着,不论模型什么时候发生变化,AngularJS会实时更新结合点,随之更新视图。app/index.html:
<html ng-app> <head> ... <script src="lib/angular/angular.js"></script> <script src="js/controllers.js"></script> </head> <body ng-controller="PhoneListCtrl"> <ul> <li ng-repeat="phone in phones"> {{phone.name}} <p>{{phone.snippet}}</p> </li> </ul> </body> </html>
ng-repeat="phone in phones"是一个AngularJS迭代器。这个迭代器告诉AngularJS用第一个
li标签作为模板为列表中的每一部手机创建一个
li元素。
{{phone.name}}和{{phone.snippet}}是我们应用的一个数据模型引用,这些我们在PhoneListCtrl控制器里面都设置好了。
tutorial_02.png
模型和控制器
在PhoneListCtrl控制器里面初始化了数据模型(这里只是一个包含了数组的函数,数组中存储的对象是手机数据列表)。app/js/controller.js:
function PhoneListCtrl($scope) { $scope.phones = [ {"name": "Nexus S", "snippet": "Fast just got faster with Nexus S."}, {"name": "Motorola XOOM? with Wi-Fi", "snippet": "The Next, Next Generation tablet."}, {"name": "MOTOROLA XOOM?", "snippet": "The Next, Next Generation tablet."} ]; }
单元测试
describe('PhoneListController', function() { beforeEach(module('phonecatApp')); it('should create a `phones` model with 3 phones', inject(function($controller) { var scope = {}; var ctrl = $controller('PhoneListController', {$scope: scope}); expect(scope.phones.length).toBe(3); })); });
向命令行输入
npm test
如果未装谷歌或火狐,要修改karma.conf.js文件,否则无法正常测试。
3 组件
什么是组件
控制器+模板-->组件一个简单的例子:
angular. module('myApp'). component('greetUser', { template: 'Hello, {{$ctrl.user}}!', controller: function GreetUserController() { this.user = 'world'; } });
可以在视图中引入
<<greet-user></greet-user>,Angular将它扩展为DOM子树,由模板生成结构,控制器进行管理。
默认情况下,组件使用$ CTRL作为控制器的别名。
在代码中使用组件
app/index.html:<html ng-app="phonecatApp"> <head> ... <script src="bower_components/angular/angular.js"></script> <script src="app.js"></script> <script src="phone-list.component.js"></script> </head> <body> <!-- 使用自定义组件渲染手机列表 --> <phone-list></phone-list> </body> </html>
app/app.js:
// 定义主模块 `phonecatApp` angular.module('phonecatApp', []);
app/phone-list.component.js:
// 注册组件 `phoneList`(模板+控制器) angular. module('phonecatApp'). component('phoneList', { template: '<ul>' + '<li ng-repeat="phone in $ctrl.phones">' + '<span>{{phone.name}}</span>' + '<p>{{phone.snippet}}</p>' + '</li>' + '</ul>', controller: function PhoneListController() { this.phones = [ { name: 'Nexus S', snippet: 'Fast just got faster with Nexus S.' }, { name: 'Motorola XOOM™ with Wi-Fi', snippet: 'The Next, Next Generation tablet.' }, { name: 'MOTOROLA XOOM™', snippet: 'The Next, Next Generation tablet.' } ]; } });
使用组件的好处:
让index.html更简洁;
更好地分离视图和模型,修改index.html时不会不小心破坏组件;
组件可以单独测试;
组件可以复用
tutorial_03.png
组件测试
app/phone-list.component.spec.js:describe('phoneList', function() { // 加载主模板 beforeEach(module('phonecatApp')); // 测试控制器 describe('PhoneListController', function() { it('should create a `phones` model with 3 phones', inject(function($componentController) { var ctrl = $componentController('phoneList'); expect(ctrl.phones.length).toBe(3); })); }); });
4 文件夹和文件管理
我们在这一节重构文件,让代码结构更清晰,方便开发者快速查找到所需功能或片段。让每个功能/实体拥有自己的文件。
1)为什么?为了简单起见,开发者可能把所有代码都在一个文件中,或者将同一类型的代码放入同一个文件(例如在一个文件中放所有控制器,在另一文件中放所有部件,在第三个文件中放所有服务)。
这似乎在一开始很好地工作,但随着应用程序代码的增长,这种结构会成为一种负担维护。随着添加越来越多的功能,文件将变得越来越大,我们将难以找到自己所需代码。
2)怎么做?
将每个功能/实体(比如一个独立的控制器、一个独立的组件)放到单独的文件中。
比如,phone-list功能,文件结构如下:
app/ phone-list/ phone-list.component.js phone-list.component.spec.js app.js
按功能模块组织代码,而不是按功能组织代码。
1)为什么?模块化结构的好处之一是代码重用 - 不仅在同一应用程序内,但在其他应用程序也可以重用。
代码重用的最后一个阻碍是:每个功能/部分需要声明自己、将自己注册到所有相关的模块。比如将组件注册到主模块,我们在新项目中复用该组件,就需要修改组件代码中的主模块名字。这样影响了功能的封装,复用需要修改组件内部代码。
以phoneList组件为例:
angular. module('phonecatApp'). //phoneList组件将自己注册到主模块phonecatApp(这样子,每次测试phonelist,spec文件会先加载phonecatApp模块。) component('phoneList', ...);//phoneList组件声明自己
假设我们需要开发另一个项目的手机列表。简单复制phoneList/目录到新项目,并在新项目index.html文件引入该脚本,就搞定了?
好吧,没那么简单。新项目中没有phonecatApp模块,我们需要把代码中所有的“phonecatApp”改为新项目主模块的名称。这样子既费力,而且容易出错。
2)怎么做?
更好的办法是新增一个phonelist功能模块,将phonelist组件注册到这个模块上,(英语原文:在每个功能/部分中声明自己和需要所有相关模块),在主模块(phonecatApp)中声明各功能模块的依赖关系。
改变后的phonelist目录:
app/ phone-list/ phone-list.module.js //增加phonelist模块 phone-list.component.js phone-list.component.spec.js app.module.js
app/phone-list/phone-list.module.js 模块文件:
angular.module('phoneList', []);// 定义 `phoneList` 模块
app/phone-list/phone-list.component.js 组件文件:
angular. module('phoneList').// 将 `phoneList`组件注册到 `phoneList` 模块上 component('phoneList', {...});
app/app.module.js 主模块文件(由于app/app.js 现在只包含主模块,我们给它一个 .module后缀):
// 定义主模块 `phonecatApp` angular.module('phonecatApp', [ 'phoneList' // 将`phoneList` 模块加入依赖关系数组,这样主模块就可以访问注册到`phoneList`模块上的组件 ]);
这样,在新项目中复用代码,只需要直接复制phonelist目录、在新项目主模块中添加phonelist模块的依赖关系。
外部HTML模板
1)为什么?组件的模板让我们了解数据布局并将HTML代码片段展示给用户。在步骤3中,我们使用字符串的来编写内联模板,但这种方式并不理想的,尤其是对于较大的模板。更好的方式是使用.html文件编写HTML代码,这样在编辑器写代码更顺畅(例如特定的HTML颜色突出显示和自动完成),也能让组件更简洁易读。
2)怎么做?
使用外部模板重构phoneList组件,在组件中用模板url属性指定需要加载的模板,并将模板放在phone-list/ 目录下。
增加外部模板:
将HTML代码复制到app/phone-list/phone-list.template.html中。
修改组件代码:
app/phone-list/phone-list.component.js: angular. module('phoneList'). component('phoneList', { // 注意:url关联到 `index.html` templateUrl: 'phone-list/phone-list.template.html', controller: ... });
当创建phoneList组件的一个实例时,phone-list.component.js会通过http请求得到app/phone-list/phone-list.template.html模板。
使用外部模板虽然好,但会导致http请求增加。所以,Angular还通过$templateRequest和$templateCache来管理外部模板。
文件目录最终布局
app/ phone-list/ phone-list.component.js phone-list.component.spec.js phone-list.module.js phone-list.template.html app.css app.module.js index.html
测试
之前phonelist组件进行单元测试时,需要加载主模块,主模块代码增长会影响测试效率。现在只需要加载phonelist模块,这样更加载的内容更少、测试更快。app/phone-list/phone-list.component.spec.js:
describe('phoneList', function() { // Load the module that contains the `phoneList` component before each test beforeEach(module('phoneList')); ... });
5 搜索框--过滤迭代器
phone-list模板
app/phone-list/phone-list.template.html:<div class="container-fluid"> <div class="row"> <div class="col-md-2"> <!--Sidebar content--> Search: <input ng-model="$ctrl.query" /> </div> <div class="col-md-10"> <!--Body content--> <ul class="phones"> <li ng-repeat="phone in $ctrl.phones | filter:$ctrl.query"> <span>{{phone.name}}</span> <p>{{phone.snippet}}</p> </li> </ul> </div> </div> </div>
添加了一个<input>标签,并且使用AngularJS的$filter函数来处理ngRepeat指令的输入。
数据绑定:输入框和过滤器绑定"$ctrl.query",当用户向输入框输入值时,过滤器可以马上获取该值。
搜索功能:filter函数使用query的值过滤数据,得到匹配query的手机数组。迭代器会根据filter生成的手机数组来自动更新视图。
tutorial_05.png
端到端测试
e2e-tests/scenarios.js:describe('PhoneCat Application', function() { describe('phoneList', function() { beforeEach(function() { browser.get('index.html'); }); it('should filter the phone list as a user types into the search box', function() { var phoneList = element.all(by.repeater('phone in $ctrl.phones')); var query = element(by.model('$ctrl.query')); expect(phoneList.count()).toBe(3); query.sendKeys('nexus'); expect(phoneList.count()).toBe(1); query.clear(); query.sendKeys('motorola'); expect(phoneList.count()).toBe(2); }); }); });
命令行输入:npm run protractor,自动进行测试。
6-7节:AngularJS Phonecat (步骤6-步骤7)
8-9节:AngularJS Phonecat (步骤8-步骤9)
10-12节:AngularJS Phonecat(步骤10-步骤12)
13-14节:AngularJS Phonecat(步骤13-步骤14)
作者:minxuan
链接:http://www.jianshu.com/p/85220c95f3eb
來源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
相关文章推荐
- AngularJS Phonecat (步骤6-步骤7)
- AngularJS Phonecat (步骤8-步骤9)
- angularjs学习笔记2—运行phonecat项目
- AngularJS-Phonecat的实现思路
- 新手学习AngularJS最佳项目:angular-phonecat官方案例
- AngularJS初探:搭建PhoneCat项目的开发与测试环境
- AngularJS——搭建PhoneCat项目的开发与测试环境
- AngularJS 系列教程 :PhoneCat 应用教程
- AngularJS 官方案例:angular-phonecat
- AngularJs专用测试工具Protractor 搭建PhoneCat项目的开发与测试环境
- AngularJS 配置和运行phonecat错误
- 20170419 AngularJs 官方phonecat实例学习笔记
- Angularjs学习---官方phonecat实例学习angularjs step0 step1
- AngularJS PhoneCat代码分析
- AngularJS PhoneCat代码分析
- AngularJS--PhoneCat
- AngularJS初探:搭建PhoneCat项目的开发与测试环境
- AngularJS Phonecat实例讲解
- angularjs 最新安装步骤-2017
- Error: [$injector:modulerr] Failed to instantiate module phonecatApp