angularjs v1.5 简明教程中文版 - v1.0


前言前言 本文是樊潇洁翻译的最新版本(v1.5版)的AngularJSAngularJS教程。官方英文版请见AngularJS官方教程和angularJS官方 站。 本人凭兴趣翻译有用的Web开发教程。如发现翻译得有误,请在新浪微博上发消息给我。 本人会在百度阅读和极客学院维基上陆续发布更多自己翻译的WEb开发教程。在百度阅读上收听我。这就开始学习 AngularJS吧! 2016年1月19日 目录目录 前言前言. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11 第 1 章第 1 章 快速入门快速入门 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 66 第 2 章第 2 章 必要准备工作必要准备工作 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1616 PhoneCat教程应用程序 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17 第 2 章第 2 章 起步起步 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1818 第 2 章第 2 章 操作代码操作代码 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2020 第 3 章第 3 章 静态模板静态模板 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2525 第 3 章第 3 章 实验实验 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2727 第 3 章第 3 章 总结总结 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2929 第 4 章第 4 章 Angular模板Angular模板 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3131 视图和模板 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33 模块和控制器 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35 测试 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 36 第 4 章第 4 章 实验实验 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2727 第 4 章第 4 章 总结总结 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2929 第 5 章第 5 章 筛选迭代器筛选迭代器 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4242 控制器 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 44 模板 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 45 测试 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 36 第 5 章第 5 章 实验实验 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2727 第 5 章第 5 章 总结总结 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2929 第 6 章第 6 章 双路数据绑定双路数据绑定 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5454 模板 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 45 控制器 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 44 测试 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 36 第 6 章第 6 章 实验实验 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2727 第 6 章第 6 章 总结总结 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2929 第 7 章第 7 章 XHR和依赖性注入XHR和依赖性注入 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6565 数据 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 67 控制器 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 44 测试 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 36 第 7 章第 7 章 实验实验 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2727 第 7 章第 7 章 总结总结 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2929 第 8 章第 8 章 模板连接和图像模板连接和图像 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7777 数据 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 67 模板 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 45 测试 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 36 第 8 章第 8 章 实验实验 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2727 第 8 章第 8 章 总结总结 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2929 第 9 章第 9 章 路由与多视图路由与多视图 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8686 依赖性 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 88 多个视图、路由和布局模板 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 89 模板 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 45 应用模块 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 93 控制器 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 44 测试 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 36 第 9 章第 9 章 实验实验 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2727 第 9 章第 9 章 总结总结 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2929 第 10 章第 10 章 更多模板更多模板 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 101101 数据 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 67 控制器 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 44 模板 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 45 测试 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 36 第 10 章第 10 章 实验实验 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2727 第 10 章第 10 章 总结总结 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2929 第 11 章第 11 章 筛选器筛选器 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 112112 自定义筛选器 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 114 模板 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 45 测试 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 36 第 11 章第 11 章 实验实验 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2727 第 11 章第 11 章 总结总结 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2929 控制器 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 44 模板 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 45 测试 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 36 第 12 章第 12 章 实验实验 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2727 第 12 章第 12 章 总结总结 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2929 第 13 章第 13 章 REST和自定义服务REST和自定义服务 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 131131 依赖性 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 88 模板 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 45 服务 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 135 控制器 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 44 测试 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 36 第 13 章第 13 章 总结总结 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2929 第 14 章第 14 章 应用动画应用动画 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 141141 依赖性 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 88 动画如何与 ngAnimate 协作 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 144 模板 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 45 模块和动画 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 146 用CSS过渡动画让ngRepeat动起来。. . . . . . . . . . . . . . . . . . . . . . . . . . 147 用CSS关键帧动画让 ngView 动起来 . . . . . . . . . . . . . . . . . . . . . . . . . . 149 用JavaScript让 ngClass 动起来 . . . . . . . . . . . . . . . . . . . . . . . . . . . 151 第 14 章第 14 章 总结总结 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2929 第 15 章第 15 章 完结篇完结篇 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 156156 11 快速入门快速入门 为什么要用AngularJS?为什么要用AngularJS? HTML非常适合于声明静态的文档,但是当我们试图使用它在web应用程序中声明动态视图时,它显得力不从心。An gularJS能为您的应用程序扩展HTML的词汇。由此产生的环境非常具有有表现力、可读性强、快速发展。 替代选择替代选择 其他处理HTML的缺点的框架要么是抽象出HTML、CSS、和/或JavaScript,要么为操纵DOM提供一个必要的方式。它 们都不能解决一个根本问题,即HTML不是为动态视图设计的。 可扩展性可扩展性 AngularJS是用来构建框架的工具集,很适全于你的应用程序开发。它完全可扩展,而且与别的库协作得很好。每 个功能可以被修改或替代,以适合你的独一无二的开发工作流以及功能需要。继续阅读以弄懂为何。 The BasicsThe Basics index.html

Hello {{yourName}}!

添加一些控件添加一些控件 数据绑定数据绑定 数据绑定是每当模型改变时更新视图的自动方法,当视图改变时,同样也会更新模型。这非常棒,因为从你需要 担心的列表中它减去了DOM操纵。 第 1 章 快速入门 | 7 控件控件 控件是DOM元素后面的行为。AngularJS让你能够用一个干净可读的形式表达行为,不需要更新DOM的通常样板、注 册回调或者观察模型变化。 扁平的JavaScript扁平的JavaScript 与别的框架不同,不需要为包装访问器方法中的模型,而继承私有类型。Angular模型是扁平的旧式JavaScript对 象。这使你的代码容易读取、容易维护、可重用,还不需要样板。 index.html

Todo

{{todoList.remaining()}} of {{todoList.todos.length}} remaining [ archive ]
  • {{todo.text}}
todo.js angular.module('todoApp', []) .controller('TodoListController', function() { var todoList = this; todoList.todos = [ {text:'learn angular', done:true}, {text:'build an angular app', done:false}]; todoList.addTodo = function() { todoList.todos.push({text:todoList.todoText, done:false}); todoList.todoText = ''; }; 第 1 章 快速入门 | 8 todoList.remaining = function() { var count = 0; angular.forEach(todoList.todos, function(todo) { count += todo.done ? 0 : 1; }); return count; }; todoList.archive = function() { var oldTodos = todoList.todos; todoList.todos = []; angular.forEach(oldTodos, function(todo) { if (!todo.done) todoList.todos.push(todo); }); }; }); todo.css .done-true { text-decoration: line-through; color: grey; } 后端连接后端连接 深链接深链接 一个深链接反应了用户在应用中的哪个位置,这很有用,所以用户可以把它存为书签以及电子邮件链接,以在应 用内部定位它。往返旅行的应用程序会自动获得这个功能,但Ajax应用程序按其性质不会。AngularJS结合了深链 接以及类似桌面应用程序的行为的优点。 表单验证表单验证 客户端表单验证是完美的用户体验的一个重要的部分。AngularJS使你能够声明表单的有效性规则,而不需要书写 JavaScript代码。从而事半功倍。 服务器通信服务器通信 AngularJS提供了内建的建在在XHR的顶层的服务,以及多种多样的使用第三方库的其它后端。通过处理异步返回 的数据,Promise进一步简化了您的代码。在这个示例中,我们使用AngularFire库以把一个Firebase后端接通到 一个简单的Angular应用上。 index.html 第 1 章 快速入门 | 9

JavaScript Projects

bootstrap.css // Uncomment this in Plunker or JSFiddle: @import '//netdna.bootstrapcdn.com/twitter-bootstrap/2.0.4/css/bootstrap-combined.min.css'; project.js angular.module('project', ['ngRoute', 'firebase']) .value('fbURL', 'https://ng-projects-list.firebaseio.com/') .service('fbRef', function(fbURL) { return new Firebase(fbURL) }) .service('fbAuth', function($q, $firebase, $firebaseAuth, fbRef) { var auth; return function () { if (auth) return $q.when(auth); var authObj = $firebaseAuth(fbRef); if (authObj.$getAuth()) { return $q.when(auth = authObj.$getAuth()); } var deferred = $q.defer(); authObj.$authAnonymously().then(function(authData) { auth = authData; deferred.resolve(authData); }); return deferred.promise; } }) .service('Projects', function($q, $firebase, fbRef, fbAuth) { var self = this; this.fetch = function () { if (this.projects) return $q.when(this.projects); return fbAuth().then(function(auth) { var deferred = $q.defer(); var ref = fbRef.child('projects-fresh/' + auth.auth.uid); var $projects = $firebase(ref); ref.on('value', function(snapshot) { if (snapshot.val() === null) { $projects.$set(window.projectsArray); } self.projects = $projects.$asArray(); deferred.resolve(self.projects); }); 第 1 章 快速入门 | 10 //Remove projects list when no longer needed. ref.onDisconnect().remove(); return deferred.promise; }); }; }) .config(function($routeProvider) { var resolveProjects = { projects: function (Projects) { return Projects.fetch(); } }; $routeProvider .when('/', { controller:'ProjectListController as projectList', templateUrl:'list.html', resolve: resolveProjects }) .when('/edit/:projectId', { controller:'EditProjectController as editProject', templateUrl:'detail.html', resolve: resolveProjects }) .when('/new', { controller:'NewProjectController as editProject', templateUrl:'detail.html', resolve: resolveProjects }) .otherwise({ redirectTo:'/' }); }) .controller('ProjectListController', function(projects) { var projectList = this; projectList.projects = projects; }) .controller('NewProjectController', function($location, projects) { var editProject = this; editProject.save = function() { projects.$add(editProject.project).then(function(data) { $location.path('/'); }); }; }) .controller('EditProjectController', function($location, $routeParams, projects) { var editProject = this; var projectId = $routeParams.projectId, projectIndex; editProject.projects = projects; projectIndex = editProject.projects.$indexFor(projectId); editProject.project = editProject.projects[projectIndex]; editProject.destroy = function() { editProject.projects.$remove(editProject.project).then(function(data) { $location.path('/'); }); 第 1 章 快速入门 | 11 }; editProject.save = function() { editProject.projects.$save(editProject.project).then(function(data) { $location.path('/'); }); }; }); list.html
Project Description
{{project.name}} {{project.description}}
detail.html
Required {{myForm.name.$pristine}}
Required Not a URL

Cancel
第 1 章 快速入门 | 12 创建组件创建组件 指令指令 指令是一个独有而且强大的功能,只在Angular中可用。指令使你能够发明新的HTML句法、专针对于你的应用程 序。 可重用的组件可重用的组件 我们使用指令以创建可重复使用的组件。组件允许你隐藏复杂的DOM结构、CSS以及行为。这使你能够专注于应用 程序要做什么,或者单独的应用程序看起来如何。 本地化本地化 严肃的应用程序的一个重要组成部分是本地化。AngularJS的本地探知筛选器以及阻塞指令使你能够建立屏蔽,使 你的应用程序在所有的地方都可用。 index.html Date: {{ '2012-04-01' | date:'fullDate' }}
Currency: {{ 123456 | currency }}
Number: {{ 98765.4321 | number }}
components.js 第 1 章 快速入门 | 13 angular.module('components', []) .directive('tabs', function() { return { restrict: 'E', transclude: true, scope: {}, controller: function($scope, $element) { var panes = $scope.panes = []; $scope.select = function(pane) { angular.forEach(panes, function(pane) { pane.selected = false; }); pane.selected = true; } this.addPane = function(pane) { if (panes.length == 0) $scope.select(pane); panes.push(pane); } }, template: '
' + '' + '
' + '
', replace: true }; }) .directive('pane', function() { return { require: '^tabs', restrict: 'E', transclude: true, scope: { title: '@' }, link: function(scope, element, attrs, tabsController) { tabsController.addPane(scope); }, template: '
' + '
', replace: true }; }) app.js angular.module('app', ['components']) .controller('BeerCounter', function($scope, $locale) { $scope.beers = [0, 1, 2, 3, 4, 5, 6]; if ($locale.id == 'en-us') { $scope.beerForms = { 0: 'no beers', one: '{} beer', other: '{} beers' }; 第 1 章 快速入门 | 14 } else { $scope.beerForms = { 0: '?iadne pivo', one: '{} pivo', few: '{} pivá', other: '{} pív' }; } }); bootstrap.css // Uncomment this in Plunker or JSFiddle: @import '//netdna.bootstrapcdn.com/twitter-bootstrap/2.0.4/css/bootstrap-combined.min.css'; 可测性内置可测性内置 可注入可注入 在AngularJS依赖性注入允许你声明式地描述你的应用程序是如何连线的。这意味着你的应用程序不需要main()方 法,该方法通常是一个难以维护的大堆杂。依赖性的注入也是AngularJS的一个核心。这意味着任何不适合你的需 要的组件可以轻松替换掉。 可测试可测试 AngularJS设计为从根基开始都是可测试的。它鼓励行为与视图分离、用预绑定来模拟、充分利用依赖性注入。它 还配备了端到端的场景分流道,它通过理解AngularJS的内部运作机制消除了测试片层分享。 第 1 章 快速入门 | 15 22 必要准备工作必要准备工作 PhoneCat教程应用程序PhoneCat教程应用程序 AngularJS最好的入方法是跟着教程操作,它带领你经历了一个AngularJS网页应用程序的构建。你将建立的这个 应用是一个目录,显示了一个安卓设备的列表,让你能够筛选列表,以只察看你感兴趣的设备,然后查看任何设 备的详情。 运行在浏览器上的演示应用 图片 2.1图片 2.1 运行在浏览器上的演示应用 跟随着这个教程以看到Angular如何让浏览器变得更聪明——不需要使用原生的扩展或者插件: • 查看如何使用客户端数据绑定的示例,以建立数据的动态视图,它会响应用户的操作立即改变自己。 • 查看Angular如何在与你的数据同步的同时,保持你的视图不变,不需要DOM操纵。 • 学一个更好的、更容易的方法,以测试你的网页应用,利用Karma以及Protractor。 • 学会如何使用依赖性注入和服务,以制作常见的网页任务,比如说更容易地在应用中获得数据。 当你看完该教程时你将能够: • 创建工作在现代浏览器中的动态的应用程序。 • 使用数据绑定以把你的数据模块连接到你的视图中。 • 利用Karma创建并运行单元测试。 • 利用Protractor创建并运行端到端测试。 • 从模板中移出应用逻辑,移到控件中。 • 使用Angular服务从服务器端获得数据。 • 使用ngAnimate把动画应用到你的应用程序中。 • 识别资源以学习更多关于AngularJS。 本教程将指导你完成建立一个应用程序的整个过程,包括编写并运行单元测试和端到端测试。每一步骤的末尾的 实验向你提供了学习更多关于AngularJS的建议,以及你正在建立的应用程序的建议。 你可以在几个时内看完整个教程,或者你可能会想愉快地花一天时间真正深入挖掘它。如果你寻求更短的Angular JS的入门,请仔细阅读起步文档。 第 2 章 必要准备工作 | 17 第 2 章 起步第 2 章 起步 第 2 章 起步 | 18 本页的剩余部分解释了你可以如何设置你的本地机器用于开发。如果你只是想阅读教程,则你可以直接查看第一 步:第一步。 第 2 章 起步 | 19 第 2 章 操作代码第 2 章 操作代码 第 2 章 操作代码 | 20 你可以在你自己的电脑上跟随着这个教程、摆弄代码。用这种方法,你可以得到真正书写AngularJS代码的亲手实 践,还使用了推荐的测试工具。 该教程取决于为源代码管理器使用了哪个版本的Git。除了安装并运行几段git代码,你不需要知道关于Git的任何 东西,只要跟着这个教程。 安装Git安装Git 你可以从http://git-scm.com/download下载并安装Git。一旦安装好了,你应该能够访问到 git 命令行工具。你 将需要用到的主要命令是: • git clone ... : 把一个远程的知识库克隆到你的本地机器上 • git checkout ... : 检查一个特定的分支或一个代码的标记版本以破解 下载angular-phonecat下载angular-phonecat 运行以下命令以克隆放置在GitHub上的 angular-phonecat repository: git clone --depth=14 https://github.com/angular/angular-phonecat.git 该命令在你当前的目录中创建了 angular-phonecat 目录。 该`--depth=14`的选项仅仅是告诉Git只拉下来最后的14次提交。这样使下载更小更快。 把你当前的目录变成 angular-phonecat 。 cd angular-phonecat 从现在开始,本教程指令,假定你从 angular-phonecat 目录上运行所有的命令。 安装Node.js安装Node.js 如果你想运行预配置的本地web服务器以及测试工具,则你还需要Node.js v0.10.27+。 你可以针对你的操作系统从http://nodejs.org/download/下载一个Node.js安装包。 运行以下的命令行,检查你已经安装的Node.js的版本: node --version 第 2 章 操作代码 | 21 在基于Debian的发行版中,与别的实用工具有一个名称冲突,它称为 node 。建议的解决方案是再安装 nodejs-leg acy apt 安装包,它会把 node 重命名为 nodejs 。 apt-get install nodejs-legacy npm nodejs --version npm --version 如果你需要在你的本地环境中运行Node.js的不同版本,请考虑安装Node版本管理器(nvm)。 一旦你已经在你的机器上安装了Node.js,你可以依靠运行以下代码下载该工具。 npm install 这个命令读取了angular-phonecat的 package.json 文件,并把以下工具下载到 node_modules 目录中: • Bower - 客户端代码包管理器 • Http-Server - 简单的本地静态web服务器 • Karma - 单元测试运行器 • Protractor - 端到端测试运行器 运行 npm install 还将自动使用bower以把该Angular框架下载到 app/bower_component 目录。 注意angular-phonecat项目被设置为通过npm脚本安装并运行这些实用工具。这意味着要想跟随这个教程,你并非 一定要让实用工具中的一个全局安装在你的系统中。参见下面的**安装助手工具**以了解更多信息。 该项目用一些npm助手脚本预配置,以使它容易运行你在开发时需要用到的常见的任务: • npm start : 启动一个本地开发Web服务器 • npm test : 启动Karma单元测试运行器 • npm run protractor : 运行Protractor端到端(E2E)测试 • npm run update-webdriver : 安装Protractor所需要的驱动程序 安装助手工具(可选的)安装助手工具(可选的) Bower、Http-Server、Karma和Protractor模块也都中可执行的,它们可以全局安装,也可从终端/命令提示符中 直接运行。跟随着这个教程,你不需要安装它,但是如果你决定你确实想要直接运行它们,你可以使用 sudo npm install -g ... 来全局安装这些模块。 作为实例,要想安装可执行的Bower命令行,你只需要输入以下指令: 第 2 章 操作代码 | 22 sudo npm install -g bower (Omit the sudo if running on Windows) 然后你可以直接运行该bower工具了,如下: bower install 运行开发Web服务器运行开发Web服务器 虽然Angular应用程序是纯客户端代码,而且能够直接从文件系统中,在web浏览器中打开它们,但是最好从一个H TTP web服务器中供应它们。特别是,为了安全原因,如果网页直接从文件系统中加载,很多现代浏览器不允许Ja vaScript发起服务器请求。 为了在开发期间托管应用程序,用一个简单的静态的web服务器配置angular-phonecat项目。运行以下指令以开启 web服务器。 npm start 这将创建一个本地web服务器,鉴听你的本地机器上的端口8000。现在你可以在这个地址上浏览该应用程序了: http://localhost:8000/app/index.html 要想在不同的IP地址或端口上供应该web应用程序,可以编辑package.json内部的“start”脚本。你可以使用`- a`以设置地址,使用`-p`以设置端口。 运行单元测试运行单元测试 我们使用单元测试以确保我们的应用程序中的JavaScript代码正确运行。单元测试关注于应用程序的小型的隔离 部分。单元测试保存在 test/unit 目录中。 angular-phonecat项目被配置为使用Karma以针对本应用程序运行该单元测试。运行以下指令以开始Karma。 npm test 这将开始Karma单元测试运行器。Karma将读取在 test/karma.conf.js 中的配置文件。 这个配置文件告诉Karma 要: • 打开一个Chrome浏览器,把它连接到Karma。 • 在该浏览器中执行所有的单元测试 • 报告在终端/命令行窗口中的那些测试的结果 第 2 章 操作代码 | 23 • 观察所有项目的JavaScript文件,每当有变化时重新运行测试 最后让它一直在后台运行,因为北会给你即时的回调,关于当你在操作代码时,你的改变是否通过了单元测试的 回调。 运行端到端测试运行端到端测试 我们使用端到端测试以确保应用程序作为一个整体运行。端到端测试被设计为测试整个应用客户端应用程序,特 别是测试视图是否正确显示并有正确的行为。它在浏览器中运行,通过模拟真实用户与真实应用程序的交互。 端到端测试保存在 test/e2e 目录中。 该angular-phonecat项目被配置为使用Protractor以针对应用程序运行端到端测试。Protractor依赖于一组允许 它与浏览器交互的驱动程序。你可以通过运行以下代码以安装这些驱动程序: npm run update-webdriver 你只需要运行它一次。你只需要运行它一次。 因为Protactor通过与正在运行的应用程序交互来起作用,我们需要开启我们的web服务器: npm start 然后在一个单独的终端/命令行窗口中,通过运行以下指令,我们可以针对该应用程序运行Protractor测试脚本: npm run protractor Protractor将读取在 test/protractor-conf.js 中的配置文件。该配置文件要求Protractor做: • 打开一个Chrome浏览器,把它连接到应用程序上 • 在浏览器中执行所有的端到端测试 • 报告在终端/命令行窗口中的那些测试结果 • 关闭浏览器并退出 最好在每当你对HTML视图作了改变的时候运行端到端测试,或者当你想检查该应用程序作为一个整体是否正确执 行时,运行端到端测试。通常在把一个新的改变提交到远程知识库之前运行端到端测试。 现在你已经测试好了你的本地机器,让我们开始这个教程吧:第一步 引导程序。 第 2 章 操作代码 | 24 33 静态模板静态模板 为了演示Angular如何增强静态HTML,你可以创建一个纯静态静态HTML网页,然后仔细观察我们可以如何把这些HTML代 码变成一个模板,从而Angular可以用来动态显示同样的结果、以任何数据集显示结果。 在这一步中,你将在一个HTML页面中添加关于两款手机的基本的信息。 • 网页现在包含了一个列表,带有两款手机的信息。 把工作空间重置到第一步 git checkout -f step-1 刷新你的浏览器或在线检查这一步:Step 1 Live Demo 下面列出了第零步和第一步之间的最重要的区别。你可以在GitHub里看到完整的差异。 app/index.html:app/index.html:
  • Nexus S

    Fast just got faster with Nexus S.

  • Motorola XOOM? with Wi-Fi

    The Next, Next Generation tablet.

第 3 章 静态模板 | 26 第 3 章 实验第 3 章 实验 第 3 章 实验 | 27 • 尝试向 index.html 添加更多的静态HTML。比如:

Total number of phones: 2

第 3 章 实验 | 28 第 3 章 总结第 3 章 总结 第 3 章 总结 | 29 额外对你的应用使用静态HTML以显示这个列表。现在,让我们前往第二步 Angular模板以学习如何使用AngularJS 以动态生成同一个列表。 第 3 章 总结 | 30 44 Angular模板Angular模板 现在是时候用AngularJS制作动态网页了。我们将添加一个测试,验证用于控制器的代码,我们将添加这个控制 器。 为应用程序构造代码有很多方式。针对Angular应用,我们鼓励使用模块-视图-控制器(MVC)设计模式以解耦代 码、分离关注点。考虑到这一点,我们使用小的Angular以及JavaScript为我们的应用添加模块、视图和控制器组 件。 • 现在下面的数据中动态生成了三款手机的列表: 把工作空间重置到第二步 git checkout -f step-2 刷新你的浏览器或在线检查这一步:Step 2 Live Demo 下面列出了第一步和第二步之间的最重要的区别。你可以在GitHub里看到完整的差异。 第 4 章 Angular模板 | 32 视图和模板视图和模板 在Angular中,视图视图是模块透过HTML模板模板的映射。这意味着每当模块有变化时,Angular会刷新适当的绑定点,随 之更新视图。 以下面代码为模板,Angular结构化了视图组件: app/index.htmlapp/index.html :: ...
  • {{phone.name}}

    {{phone.snippet}}

我们用ngRepeat指令和两个Angular表达式替代硬编码的手机列表: • 在
  • 元素标签上的元素属性 ng-repeat="phone in phones" 是一个Angular转发器指令。该转发器告诉Angu lar为列表中的每款使用元素标签
  • 作为模板的手机创建一个
  • 元素。 • 用花括号包围的表达式( {{phone.name}} 和 {{phone.snippet}} )将被替换成表达式的值。 我们已经添加了一个新指令,称为 ng-controller ,它给元素标签附加了一个 PhoneListCtrl 控制器控制器。在这 个点上: • 在花括号中的表达式( {{phone.name}} 和 {{phone.snippet}} )表示绑定,在我们的应用程序模块中参引它 们,它们被设置在我们的 PhoneListCtrl 控制器上。 注意:我们已经指定了一个[Angular模块](https://docs.angularjs.org/api/ng/type/angular.Module)以载入 使用`ng-app="phonecatApp"`,在那里,`phonecatApp`是我们的模块名。该模块将包含`PhoneListCtrl`。 第 4 章 Angular模板 | 33 第 4 章 Angular模板 | 34 模块和控制器模块和控制器 数据模块模块(一个简单的手机数列,以对象字面记号法表达)现在在 PhoneListCtrl 控制器控制器中实例化了。该控制器控制器 只是一个构造器函数,需要一个 $scope 参数: app/js/controllers.jsapp/js/controllers.js :: var phonecatApp = angular.module('phonecatApp', []); phonecatApp.controller('PhoneListCtrl', function ($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.'} ]; }); 在这里,我们声明了一个控制器,称为 PhoneListCtrl ,并把它注册到一个AngularJS模块 PhonecatApp 中。注 意,我们的 ng-app 指令(在元素标签 上)现在指定了 phonecatApp 模块名作为载入的模块,在引导应用A ngular应用程序时载入该模块。 虽然控制器没有做太多的事情,但是它扮演了一个至关重要的角色。通过为我们的上下文提供数据模块,控制器 允许我们在模块和视图之间建立数据绑定。我们在展示、数据和逻辑组件之间添加点状虚线,如下所示: • 该ngController指令,定位在 元素标签上,引用了我们的控制器的名称, PhoneListCtrl (放置在Ja vaScript文件 controllers.js 上)。 • PhoneListCtrl 控件在 $scope 上附加了手机数据,把它注入到我们的控制器函数中。该作用域作用域是根作用域根作用域的 原型化的后代,在定义应用程序的时候创建了该根作用域。该控制器作用域可以在元素标签 内部的所有绑定位置上可用。 作用域作用域 一个作用域的概念在Angular中是至关重要的。作用域可以被视为胶合剂,允许模板、模块和控制器一起工作。An gular使用作用域,以及模板、数据模块和控制器中包含的信息,以保持模块和视图分离,但是同步。任何对模块 的改变会影响视图;任何在视图中发生的改变反应在模块中。 要想学习更多关于Angular作用域的知识,请参阅angular作用域文档。 第 4 章 Angular模板 | 35 测试测试 从视图中分离控制器的“Angular方法”,使测试代码变得容易,就像是它在被开发那样。如果你的控制器在全局 命名空间中可用,则我们可以用一个模拟的 scope 对象简单把它实例化: test/e2e/scenarios.jstest/e2e/scenarios.js :: describe('PhoneListCtrl', function(){ it('should create "phones" model with 3 phones', function() { var scope = {}, ctrl = new PhoneListCtrl(scope); expect(scope.phones.length).toBe(3); }); }); 测试实例化的 PhoneListCtrl 并在包含三个记录的作用域上核查手机数列属性。这个示例演示了为Angular中的代 码创建一个单元测试是多么容易。因为测试是软件开发的如此至关重要的部分,我们让在Angular中创建测试变得 容易,从而可以鼓励开发员编写它们。 测试非全局控制器测试非全局控制器 在实践中,你应该不想让你的控制器函数在全局命名空间内。取而代之的是,你可以看到我们已经利用一个 phone catApp 模块上的匿名构造器函数注册了控制器。 在这种情况下,Angular提供了一个服务, $controller ,它可以以名称接收你的控制器。这里有使用 $controlle r 同样的测试: test/unit/controllersSpec.jstest/unit/controllersSpec.js :: describe('PhoneListCtrl', function(){ beforeEach(module('phonecatApp')); it('should create "phones" model with 3 phones', inject(function($controller) { var scope = {}, ctrl = $controller('PhoneListCtrl', {$scope:scope}); expect(scope.phones.length).toBe(3); })); }); • 在每个测试开始之前,我们会告诉Angular要载入 phonecatApp 模块。 第 4 章 Angular模板 | 36 • 我们要求Angular把该 $controller 服务 inject 到我们的测试函数中。 • 我们使用 $controller 以创建一个 PhoneListCtrl 的实例。 • 利用这个实例,我们在包含三个记录的作用域上核查了手机数列属性。 编写并运行测试编写并运行测试 Angular喜欢使用Jasmine的行为-驱动开发(BCC)的句法。虽然Angular没有要求你使用Jasmine,但是在这个教 程中,我们用Jasmine v1.3编写所有的测试。你可以在Jasmine官方首页和Jasmine文档中学习Jasmine。 angular-seed项目是预处理的,以使用Karma运行单元测试,但是你将需要确保已经安装了Karma和它的必要的插 件。你可以通过运行 rpm install 来做到这。 要想运行测试,请运行 rpm test ,然后观察文件有什么改变。 • Karma将自动开始一个Chrome和Firefox浏览器的新实例。只需要忽略它们,让它们在后台运行。Karma将为测 试执行使用这些浏览器。 • 如果你已经在你的机器上安装了这些浏览器中的一个,确保在运行测试之前更新Karma的配置文件。本地配置 文件在 test/karma.conf.js ,然后更新 browsers 属性。 例如,如果你只安装了Chrome: ... browsers: ['Chrome'], ... • 你将在终端看到以下或者类似的输出: info: Karma server started at http://localhost:9876/ info (launcher): Starting browser "Chrome" info (Chrome 22.0): Connected on socket id tPUm9DXcLHtZTKbAEO-n Chrome 22.0: Executed 1 of 1 SUCCESS (0.093 secs / 0.004 secs) 耶!测试通过了!或者没有通过…… • 要想重新运行测试,只需要改变任何源或者test.js文件。Karma将注意到这些改变,并将为你重新运行测 试。现在是不是甜? 确保你没有把Karma打开的浏览器最小化了。在一些操作系统中,分配到一个最小化的浏览器上的内存是有限 的,导致你的karma测试运行变得极其缓慢。 第 4 章 Angular模板 | 37 第 4 章 实验第 4 章 实验 第 4 章 实验 | 38 • 添加对 index.html 的另一个绑定。例如: html

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

    • 在控制器中创建一个新模块属性,然后从模板中把它绑定到模块上。例如: $scope.name = "World"; 然后向 index.html 添加一个新的绑定:

    Hello, {{name}}!

    刷新你的浏览器,核实它是否说了"Hello, World!"。 • 为 ./test/unit/controllersSpec.js 中的控制器更新单元测试,以反映以前的变化。例如添加: expect(scope.name).toBe('World'); • 在 index.html 中创建一个重复器,它结构化了一个简单的表格:
    row number
    {{i}}
    现在,让这个基于1的列表的 i 在绑定中增值1。
    row number
    {{i+1}}
    另需指出:尝试并使一个8x8的表格使用一个额外的 ng-repeat 。 • 通过把 expect(scope.phones.length).toBe(3) 变成 toBe(4) ,使单元测试失败。 第 4 章 实验 | 39 第 4 章 总结第 4 章 总结 第 4 章 总结 | 40 现在你有了一个动态的应用,功能分离开模块、视力和控制器组件,而且你测试了它们。现在,让我们前往第三 步 筛选迭代器以学习如何为应用添加全文搜索。 第 4 章 总结 | 41 55 筛选迭代器筛选迭代器 我们在上一步中为开发应用打基础做了很多工作,现在我们将做一些简单的事情;我们将添加全文搜索(是 的,它很简单!)。我们还将编写一个端到端测试,因为一个好的端到端测试可以帮上大忙。它监视着你的应 用,并在发生回归时迅速报告。 • 现在应用有了一个搜索框。注意页面上的手机列表的变化取决于用户在搜索框中打了什么字。 把工作空间重置到第三步 git checkout -f step-3 刷新你的浏览器或在线检查这一步:Step 3 Live Demo 下面列出了第二步和第三步之间最重要的区别。你可以在GitHub里看到完整的差异。 第 5 章 筛选迭代器 | 43 控制器控制器 我们对控制器不作修改。 第 5 章 筛选迭代器 | 44 模板模板 app/index.html:app/index.html:
    Search:
    • {{phone.name}}

      {{phone.snippet}}

    我们添加了一个标准HTML 元素标记,并使用Angular的filter函数来处理repeat指令的输入。 这使用户输入搜索条件,并在手机列表中快速看到搜索结果。新的代码演示如下: • 数据绑定:这是Angular的一个核心功能。当网页载入时,Angular把输入框的名称绑定到数据模块的同名的 变量上,并保持两者同步。 在代码中,用户打字到输入框的数据(命名为 queryquery )很快可以作为一个筛选器输入到列表迭代器( phone in phones | filter: queryquery )中。在改变数据模块的时候,导致迭代器的输入发生变化,迭代器有效地更新 了DOM,以反映模块的当前状态。 第 5 章 筛选迭代器 | 45 • 使用 filter 筛选器:filter函数使用了 query 值发创建一个新的数列,只包含匹配 query 的记录。 ngRepeat 自动更新了视力,以响应 filter 筛选器返回的手机数字的变化。该处理对开发者来说是完全透明 的。 第 5 章 筛选迭代器 | 46 测试测试 在第二步中,我们学会了如何编写并运行单元测试。对于测试我们的用JavaScript编写的应用程序的控制器和其 它组件,单元测试是完美的,但是测试DOM操作或测试我们的应用程序的接通不太方便。针对这些,一个端到端的 测试是一个更好的选择。 该搜索功能完全是通过模板和数据绑定来实现的,我们将编写我们第一个端到端的测试,以验证该功能起了什么 作用。 test/e2e/scenarios.jstest/e2e/scenarios.js :: describe('PhoneCat App', function() { describe('Phone list view', function() { beforeEach(function() { browser.get('app/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 phones')); var query = element(by.model('query')); expect(phoneList.count()).toBe(3); query.sendKeys('nexus'); expect(phoneList.count()).toBe(1); query.clear(); query.sendKeys('motorola'); expect(phoneList.count()).toBe(2); }); }); }); 这个测试验证了搜索框以及迭代器是否正确地接通了。注意,在Angular中,编写端到端测试是如此地容易。虽然 这个示例只针对一个简单的测试,但是它确实很容易测试任何功能化的、可读的、端到端的测试。 利用Protractor运行端到端的测试利用Protractor运行端到端的测试 甚至虽然测试的句法看起来很像我们的用Jasmine编写的控制器单元测试,但是端到端测试使用Protractor的AP I。在http://angular.github.io/protractor/#/api可以读到Protractor的API。 第 5 章 筛选迭代器 | 47 与Karma很像的是针对单元测试的测试运行者,我们使用Protractor以运行端到端测试。用 npm run protractor 来 尝试它。端到端测试很慢,所以与单元测试不同,在运行测试之后Protractor将退出,不会自动在每次文件更改 时重新运行测试套装。要想重新运行测试套装,需要再次执行 npm run protractor 。 注意,你必须确保你的应用通过一个web服务器提供服务,从而用Protractor测试。你可以使用`npm start`来做 到这。你还需要确保你在运行`npm run protractor`之前已经安装了Protractor,并更新了web驱动器。You can do this by issuing `npm install` and `npm run update-webdriver` into your terminal. 第 5 章 筛选迭代器 | 48 第 5 章 实验第 5 章 实验 第 5 章 实验 | 49 显示当前查询显示当前查询 通过添加一个绑定到 index.html 模板的 {{query}} 来显示 query 模块当前的值,并看到当你在输入框中打字 时,它如何变化。 在标题中显示查询在标题中显示查询 让我们看到我们可以取得 query 模板的当前值,模块出现在HTML网页的标题上。 • 把一个端到端测试添加到 describe 块中, test/e2e/scenarios.js 看起来将如这: ```js describe('PhoneCat App', function() { describe('Phone list view', function() { beforeEach(function() { browser.get('app/index.html'); }); var phoneList = element.all(by.repeater('phone in phones')); var query = element(by.model('query')); it('should filter the phone list as a user types into the search box', function() { expect(phoneList.count()).toBe(3); query.sendKeys('nexus'); expect(phoneList.count()).toBe(1); query.clear(); query.sendKeys('motorola'); expect(phoneList.count()).toBe(2); }); it('should display the current filter value in the title bar', function() { query.clear(); expect(browser.getTitle()).toMatch(/Google Phone Gallery:\s*$/); query.sendKeys('nexus'); expect(browser.getTitle()).toMatch(/Google Phone Gallery: nexus$/); }); }); }); ``` 第 5 章 实验 | 50 运行protractor( npm run protractor ),看到测试失败了。 • 你可能认为你只需要用以下方式向标题标签添加 {{query}} : Google Phone Gallery: {{query}} 然而,当你重载入这个网页的时候,你不会看到想要的结果。这是因为“查询”模块驻留在作用域内,由 n g-controller="PhoneListCtrl" 指令在body元素上定义。 如果你想要从 元素上绑定查询模块,你必须把 ngController 声明移动到HTML元素上,因为它是body 元素和title元素常用的父元素。 <html ng-app="phonecatApp" ng-controller="PhoneListCtrl"> 确保从body元素中移除从body元素中移除 ng-controller 声明。 • 重新运行 rpm run protractor ,看到现在测试已经看通过了。 • 在title元素内部使用双花工作得很好,与此同时,你可能会注意到页面加载的一瞬间它们确实显示给用户 了。一个更好的解决方案是使用ngBind指令或ngBindTemplate指令,当页面加载时用户能看到它们。 <title ng-bind-template="Google Phone Gallery: {{query}}">Google Phone Gallery 第 5 章 实验 | 51 第 5 章 总结第 5 章 总结 第 5 章 总结 | 52 我们现在已经把全文搜索添加上去了,还包含了一个用来验证搜索是否起作用的测试!现在让我们前往第四步 双 路数据绑定以学会如何向手机应用添加排序功能。 第 5 章 总结 | 53 66 双路数据绑定双路数据绑定 在这一步中,你将添加一个功能,让你的用户控制手机列表中的项目的排序。这个动态排序由创建一个新模块属 性来实现,用迭代器接通它们,并且让数据绑定来完成剩余工作。 • 除了搜索框,应用显示了一个下拉菜单,允许用户 控制列出的手机的排序。 把工作空间重置到第四步 git checkout -f step-4 刷新你的浏览器或在线检查这一步:Step 4 Live Demo 下面列出了第四步和第五步之间的最重要的区别。你可以在GitHub里看到完整的差异。 第 6 章 双路数据绑定 | 55 模板模板 app/index.htmlapp/index.html :: Search: Sort by:
    • {{phone.name}}

      {{phone.snippet}}

    我们制作以下对 index.html 模板的改变: • 首先,我们添加了一个 Uppercased: {{ userInput | uppercase }} 第 11 章 实验 | 118 第 11 章 总结第 11 章 总结 第 11 章 总结 | 119 现在你已经学会了如何编写并测试一个自定义筛选器,前往第十步 事件处理函数以学习我们可以如何用Angular 继续丰富手机详情页面。 第 11 章 总结 | 120 #事件处理函数 在这一步中,你将添加一个可点击的手机图像交换器,指向手机详情页面。 • 手机详情视图显示了当前手机的一张大图像以及若干张小的缩略图。如果我们可以通过在想要的缩略图中点 吉,从而把大图像与任何小缩略图作替换,这会很棒。让我们看一看我们可以如何用Angular做到这。 把工作空间重置到第十步 git checkout -f step-10 刷新你的浏览器或在线检查这一步:Step 10 Live Demo 下面列出了第九步和第十步之间最重要的区别。你可以在GitHub上看到完整的差异。 第 章 | 122 控制器控制器 app/js/controllers.jsapp/js/controllers.js :: ... var phonecatControllers = angular.module('phonecatControllers',[]); phonecatControllers.controller('PhoneDetailCtrl', ['$scope', '$routeParams', '$http', function($scope, $routeParams, $http) { $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 控制器中,我们创建了 mainImageUrl 模块属性,并把它的默认值设置为第一个手机图像URL。 我们还创建了一个 setImage 事件处理函数,它将改变 mainImageUrl 的值。 第 章 | 123 模板模板 app/partials/phone-detail.htmlapp/partials/phone-detail.html :: ...
    ... 我们把大图像的 ngSrc 指令绑定到 mainImageUrl 属性上。 我们还将利用缩略图注册一个 ngClick 处理函数。当用户在缩图略之一上点击时,处理函数将使用 setImage 事件 处理函数以改变 mainImageUrl 属性的值,把它变成缩略图的URL。 第 章 | 124 测试测试 要想验证这个功能,我们添加了两个端到端测试。一个验证了主图像被默认设置为每一个手机图像。另一个测试 了在一些缩略图上的点击,并验证了相应的主图像改变。 test/e2e/scenarios.jstest/e2e/scenarios.js :: ... describe('Phone detail view', function() { ... it('should display the first phone image as the main phone image', function() { expect(element(by.css('img.phone')).getAttribute('src')).toMatch(/img\/phones\/nexus-s.0.jpg/); }); it('should swap main image if a thumbnail image is clicked on', function() { element(by.css('.phone-thumbs li:nth-child(3) img')).click(); expect(element(by.css('img.phone')).getAttribute('src')).toMatch(/img\/phones\/nexus-s.2.jpg/); element(by.css('.phone-thumbs li:nth-child(1) img')).click(); expect(element(by.css('img.phone')).getAttribute('src')).toMatch(/img\/phones\/nexus-s.0.jpg/); }); }); 现在你可以再次运行 rpn run protractor 以看到测试运行。 你还必须重构你的单元测试之一,因为又有一个 mainImageUrl 模块属性添加到了 PhoneDetailCtrl 控制器上 了。下面,我们创建了函数 xyzPhoneData ,该函数会返回相应的带有 image 元素属性的json,从而使测试通过。 test/unit/controllersSpec.jstest/unit/controllersSpec.js :: ... beforeEach(module('phonecatApp')); ... describe('PhoneDetailCtrl', function(){ var scope, $httpBackend, ctrl, xyzPhoneData = function() { return { name: 'phone xyz', images: ['image/url1.png', 'image/url2.png'] } }; beforeEach(inject(function(_$httpBackend_, $rootScope, $routeParams, $controller) { $httpBackend = _$httpBackend_; $httpBackend.expectGET('phones/xyz.json').respond(xyzPhoneData()); $routeParams.phoneId = 'xyz'; scope = $rootScope.$new(); 第 章 | 125 ctrl = $controller('PhoneDetailCtrl', {$scope: scope}); })); it('should fetch phone detail', function() { expect(scope.phone).toBeUndefined(); $httpBackend.flush(); expect(scope.phone).toEqual(xyzPhoneData()); }); }); 你的单元测试现在应该通过了。 第 章 | 126 第 12 章 实验第 12 章 实验 第 12 章 实验 | 127 • 让我们给 PhoneDetailCtrl 添加一个新的控制器方法: $scope.hello = function(name) { alert('Hello ' + (name || 'world') + '!'); } 再添加 to the phone-detail.html template. 第 12 章 实验 | 128 第 12 章 总结第 12 章 总结 第 12 章 总结 | 129 随着手机图像交换器到位,我们准备前往第十一步 REST和自定义服务以学习取得数据的一个更好方法。 第 12 章 总结 | 130 1313 REST和自定义服务REST和自定义服务 在这一步中,你将改变我们获取数据的方法。 • 我们定义了一个自定义服务,它代表了一个RESTful客户端。利用该客户端,我们可以用更容易的方式制作一 个向服务器索取数据的请求,不需要去处理底层?$http API、HTTP方法以及URL。 把工作空间重置到第十一步 git checkout -f step-11 刷新你的浏览器或在线检查这一步:Step 8 Live Demo 下面列出了第十步和第十一步之间最重要的区别。你可以在GitHub上看到完整的差异。 第 13 章 REST和自定义服务 | 132 依赖性依赖性 Angular在 ngResource 模块中提供了安静的功能,它是与核心Angular框架分开分布的。 我们正在使用Bower以安装客户端依赖性。这一步更新的 bower.json 配置文件,以包含新的依赖性: { "name": "angular-seed", "description": "A starter project for AngularJS", "version": "0.0.0", "homepage": "https://github.com/angular/angular-seed", "license": "MIT", "private": true, "dependencies": { "angular": "1.4.x", "angular-mocks": "1.4.x", "jquery": "~2.1.1", "bootstrap": "~3.1.1", "angular-route": "1.4.x", "angular-resource": "1.4.x" } } 新的依赖性 "angular-resource": "1.4.x" 告诉bower安装一个以angular为源的组件的版本,它与v1.4x版兼 容。我们必须要求bower下载并安装这个依赖性。我们可以通过运行下面的指令来做到它: npm install **警告:**如果自从你上一次运行`npm install`以后,Angular又发布了一个新版本,则你用`bower install`可 能遇到问题,因为你安装的angular.js的版本与它有冲突。如果你想通过它,则需要在运行`npm install`之前先 删除你的`app/bower_components`文件夹。 **注意:**如果你已经全局安装了bower,则你可以运行`bower install`,但是为了我们已经预配置的项目,`npm install`为我们运行了bower。 第 13 章 REST和自定义服务 | 133 模板模板 我们的自定义源服务将被定义在 app/js/services.js 中,因此我们需要在我们的布局模板中包含这个文件。另 外,我们还需要载入 angular-resouces.js 文件,它包含了ngResource模块: app/index.htmlapp/index.html .. ... ... 第 13 章 REST和自定义服务 | 134 服务服务 我们创建了自己的服务,以提供对服务器上的手机数据的访问: app/js/services.jsapp/js/services.js .. var phonecatServices = angular.module('phonecatServices', ['ngResource']); phonecatServices.factory('Phone', ['$resource', 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.jsapp/js/app.js .. ... angular.module('phonecatApp', ['ngRoute', 'phonecatControllers','phonecatFilters', 'phonecatServices']). ... 我们需要把 phonecatServices 模块依赖性添加到 phonecatApp 模块的需要数列中。 第 13 章 REST和自定义服务 | 135 控制器控制器 通过重构掉底层的$http服务,我们简化了我们的子控制器( PhoneListCtrl 和 PhoneDetailCtrl ),用称为 Phon e 的服务替代它。Angular的 $resource 服务比 $http 更容易使用,用来与作为REST的源对外提供的数据源交 互。现在我们更容易理解控制器中的这些代码是干什么的了。 app/js/controllers.jsapp/js/controllers.js .. var phonecatControllers = angular.module('phonecatControllers', []); ... phonecatControllers.controller('PhoneListCtrl', ['$scope', 'Phone', function($scope, Phone) { $scope.phones = Phone.query(); $scope.orderProp = 'age'; }]); phonecatControllers.controller('PhoneDetailCtrl', ['$scope', '$routeParams', 'Phone', function($scope, $routeParams, Phone) { $scope.phone = Phone.get({phoneId: $routeParams.phoneId}, function(phone) { $scope.mainImageUrl = phone.images[0]; }); $scope.setImage = function(imageUrl) { $scope.mainImageUrl = imageUrl; } }]); 注意我们把 PhoneList 内部替换成了什么: $http.get('phones/phones.json').success(function(data) { $scope.phones = data; }); 换成: $scope.phones = Phone.query(); 我们通过这条简单语句来查询所有手机。 一个需要注意的重要事情是,在上面的代码中,在引用手机服务的方法的时候,我们没有传递任何回调函数。虽 然它看起来就像结果是同步返回的,但其实根本不是。同步返回的是一个“future”——一个对象,当XHR响应返 回的时候,将填入数据。因为Angular中的数据绑定,我们可以使用这个future并且把它绑定到我们的模板上。然 后,当数据到达的时候,视图将自动更新。 有些时候,单凭future对象和数据绑定不足以满足我们所有的需求,在那种情况下,我们可以添加一个回调函 数,以处理服务器响应。 PhoneDetailCtrl 控制器通过设置回调函数中的 mainImageUrl 来演示它。 第 13 章 REST和自定义服务 | 136 测试测试 因为我们现在使用了ngResource模块,为了用以angular为源更新Karma配置单文件,它是必要的,这样新测试才 能通过。 test/karma.conf.jstest/karma.conf.js :: files : [ 'app/bower_components/angular/angular.js', 'app/bower_components/angular-route/angular-route.js', 'app/bower_components/angular-resource/angular-resource.js', 'app/bower_components/angular-mocks/angular-mocks.js', 'app/js/**/*.js', 'test/unit/**/*.js' ], 我们已经修改了我们的单元测试,以验证我们的新服务会发起HTTP请求,并像预期那样处理它们。测试还检查了 我们的控制器正确地与服务交互。 $resource服务参增加了带有用来更新和删除源的方法的响应对象。如果我们打算使用标准的 toEqual 匹配器,我 们的测试将失败,因为测试值不能与响应严格匹配。要想解决这个问题,我们使用了一个新定义的 toEqualDat a [Jasmine matcher][jasmine匹配器]。当 toEqualData 匹配器对比两个对象的时候,它考虑对象属性属性而忽 略对象方法。 test/unit/controllersSpec.jstest/unit/controllersSpec.js :: describe('PhoneCat controllers', function() { beforeEach(function(){ this.addMatchers({ toEqualData: function(expected) { return angular.equals(this.actual, expected); } }); }); beforeEach(module('phonecatApp')); beforeEach(module('phonecatServices')); describe('PhoneListCtrl', function(){ var scope, ctrl, $httpBackend; beforeEach(inject(function(_$httpBackend_, $rootScope, $controller) { $httpBackend = _$httpBackend_; $httpBackend.expectGET('phones/phones.json'). respond([{name: 'Nexus S'}, {name: 'Motorola DROID'}]); scope = $rootScope.$new(); ctrl = $controller('PhoneListCtrl', {$scope: scope}); })); 第 13 章 REST和自定义服务 | 137 it('should create "phones" model with 2 phones fetched from xhr', function() { expect(scope.phones).toEqualData([]); $httpBackend.flush(); expect(scope.phones).toEqualData( [{name: 'Nexus S'}, {name: 'Motorola DROID'}]); }); it('should set the default value of orderProp model', function() { expect(scope.orderProp).toBe('age'); }); }); describe('PhoneDetailCtrl', function(){ var scope, $httpBackend, ctrl, xyzPhoneData = function() { return { name: 'phone xyz', images: ['image/url1.png', 'image/url2.png'] } }; beforeEach(inject(function(_$httpBackend_, $rootScope, $routeParams, $controller) { $httpBackend = _$httpBackend_; $httpBackend.expectGET('phones/xyz.json').respond(xyzPhoneData()); $routeParams.phoneId = 'xyz'; scope = $rootScope.$new(); ctrl = $controller('PhoneDetailCtrl', {$scope: scope}); })); it('should fetch phone detail', function() { expect(scope.phone).toEqualData({}); $httpBackend.flush(); expect(scope.phone).toEqualData(xyzPhoneData()); }); }); }); 你现在可以在Karma选项卡中看到如下的输出: Chrome 22.0: Executed 5 of 5 SUCCESS (0.038 secs / 0.01 secs) 第 13 章 REST和自定义服务 | 138 第 13 章 总结第 13 章 总结 第 13 章 总结 | 139 现在我们已经看到了如何建立一个自定义的服务,作为REST的客户端,我们已经准备好前往第十二步 应用动 画(最后一步)以学会如何用动画提高应用程序。 第 13 章 总结 | 140 1414 应用动画应用动画 在这最后一步中,我们将通过在我们之前创建的模板代码的顶部添加CSS和JavaScript动画丰富我们的手机分类网 站应用。 • 我们现在使用 ngAnimate 模拟以启用动画,以贯穿这个应用。 • 我们还使用常用的 ng 指令以自动触发使动画接入的钩子。 • 发现一个应用之后,动画将在标准DOM操作之间运行,该标准DOM操作在给定的时间内发布在元素上(例 如,在 ngRepeat 上插入和移除节点,或在 ngClass 上添加和移除类)。 把工作空间重置到第十二步 git checkout -f step-12 刷新你的浏览器或在线检查这一步:Step 12 Live Demo 下面列出了第十一步和第十二步之间最重要的区别。你可以在GitHub上看到完整的差异。 第 14 章 应用动画 | 142 依赖性依赖性 Angular在 ngAnimate 模块中提供动画功能,它与核心Angular框架分开发布。另外,我们将在项目中使用 jquery 以实现额外的JavaScript动画。 我们正在使用Bower以安装客户侧依赖性。这一步更新了 bower.json 配置文件,以包含新的依赖性: { "name": "angular-seed", "description": "A starter project for AngularJS", "version": "0.0.0", "homepage": "https://github.com/angular/angular-seed", "license": "MIT", "private": true, "dependencies": { "angular": "1.4.x", "angular-mocks": "1.4.x", "jquery": "~2.1.1", "bootstrap": "~3.1.1", "angular-route": "1.4.x", "angular-resource": "1.4.x", "angular-animate": "1.4.x" } } • "angular-animate": "1.4.x" 告诉bower安装一个angular-animate组件的版本,与v1.4x版兼容。 • "jquery": "~2.1.1" 告诉bower安装jQuery的v2.1.1版。注意这不是一个Angular库,它是标准jQuery库。我 们可以使用bower来安装一个大作用域的第三方库。 我们必须要求bower以下载并安装依赖性运行以下指令实现它: npm install **警告:**如果在你上一次运行`npm install`之后已经发布了Angular的一个新版本,然后因为需要安装的angul ar.js版本之间的冲突,你可能在运行`bower install`指令时遇到问题。如果你遇到了这种问题,只需要在运 行`npm install`之前先删除你的`app/bower_components`文件夹。 **注意:**如果你已经全局安全了bower,你可以运行`bower install`,但是对于这个项目,我们已经预配置了 运行`npm install`来运行bower。 第 14 章 应用动画 | 143 动画如何与动画如何与 ngAnimatengAnimate 协作协作 要想知道动画如何与AngularJS协作,请先阅读?AngularJS动画指南。 第 14 章 应用动画 | 144 模板模板 在HTML模板代码内部需要修改,以链接asset文件,它定义了动画以及 angular-animate.js 文件。该动画模块,即 ngAnimate ,被定义在 angular-animate.js 内部,并包含了必要的代码,以使你的应用程序变得动感。 这里是在索引文件中需要修改的地方: app/index.htmlapp/index.html .. ... ... ... ... **重要:**确保在使用Augular 1.4的时候,使用jQuery v2.1版或更新的版本;官方不支持jQuery v1.x版。确保 在所有的AngularJS脚本之前载入jQuery,否则AugularJS不能侦测jQuery,而且动画将不会如预期那样起作用。 可以在CSS代码( animations.css )内中创建动画,也可以在JavaScript代码( animations.js )内部创建动 画。但是在开始之前,让我们创建一个新模块,它使用ngAnimate模块,作为依赖性,就像我们之前用 ngResourc e 所作的。 第 14 章 应用动画 | 145 模块和动画模块和动画 app/js/animations.jsapp/js/animations.js .. angular.module('phonecatAnimations', ['ngAnimate']); // ... // this module will later be used to define animations // ... 现在让我们把这个模块附加到我们的应用程序模块上…… app/js/app.jsapp/js/app.js .. // ... angular.module('phonecatApp', [ 'ngRoute', 'phonecatAnimations', 'phonecatControllers', 'phonecatFilters', 'phonecatServices', ]); // ... 现在,手机分类模块已经有动感了。让我们制作更多更多动画吧! 第 14 章 应用动画 | 146 用CSS过渡动画让ngRepeat动起来。用CSS过渡动画让ngRepeat动起来。 我们将从这一步开始,把CSS过渡动画添加到出现在 phone-list.html 网页上的 ngRepeat 指令。首先让我们把一 个额外的CSS类添加到我们的重复元素上,因此我们可以把它与我们的CSS动画代码连接。 app/partials/phone-list.htmlapp/partials/phone-list.html .. 注意我们将如何添加 phone-listing CSS类?这是我们让动画起作用,在HTML代码中所需要做的。 以下是实际的CSS过渡动画代码: app/css/animations.cssapp/css/animations.css .phone-listing.ng-enter, .phone-listing.ng-leave, .phone-listing.ng-move { -webkit-transition: 0.5s linear all; -moz-transition: 0.5s linear all; -o-transition: 0.5s linear all; transition: 0.5s linear all; } .phone-listing.ng-enter, .phone-listing.ng-move { opacity: 0; height: 0; overflow: hidden; } .phone-listing.ng-move.ng-move-active, .phone-listing.ng-enter.ng-enter-active { opacity: 1; height: 120px; } .phone-listing.ng-leave { opacity: 1; overflow: hidden; } .phone-listing.ng-leave.ng-leave-active { opacity: 0; height: 0; 第 14 章 应用动画 | 147 padding-top: 0; padding-bottom: 0; } 如你所见,我们的 phone-listing CSS类与动画钩子相结合,当列表中插入项目或移除项目时,就会出现动画钩 子。 • 当列表中添加了一款新手机并呈现在网页上时,元素上应用了 ng-enter 类。 • 当项目绕着列表移动时,元素上应用了 ng-move 类。 • 当项目从列表中移除时,元素上应用了 ng-leave 类。 添加或删除手机列表项目取决于传递给元素属性 ng-repeat 的数据。比如,如果过滤器数据改变了,项目动画地 加入或退出重复列表。 有些很重要的事情需要注意,当动画发生时,元素上添加了CSS类的两个集合: 1. “开始”类代表动画开始时的样式。 2. “激活”类代表动画结束时的样式。 开始类的名称是被激发的事件(就像 enter 、 move 或 leave )的名称带上前缀 ng- 。所以一个 enter 事件将导 致一个称为 ng-enter 类。 激活类名与开始类名相同,但是带了一个后缀 -active 。这两类CSS命名公约允许开发员精心制作一个动画,自始 至终。 在我们上面的示例中,当元素添加到列表中时,该元素从00高度伸展到120像素120像素高度;在从列表中移除之前,又收 缩到0像素0像素。同时还发生了一个渐现和渐消的效果。这里都是由CSS过渡动画处理的,CSS过渡动画声明在上面示例 代码的顶部。 虽然大多数现代浏览器能很好地支持CSS过渡和CSS动画。但是如果你想让动画与老旧的浏览器后向兼容,请考虑 使用基于JavaScript的动画,将在下面详细讲解它。 第 14 章 应用动画 | 148 用CSS关键帧动画让用CSS关键帧动画让 ngViewngView 动起来动起来 接下来,让我们为在 ngView 内部、路由之间的过渡添加一个动画。 首先,让我们给HTML添加一个新的CSS类,就像我们在上面的示例中所作的。这一次,不是使用 ng-repeat 元 素,而是把它添加到包含了 ng-view 指令的元素上。为了做到这,我们需要对HTML代码做一些小的改变,从而我 们可以对我们的动画,在视图改变之间的动画有更多的控制。 app/index.htmlapp/index.html ..
    利用这个改变, ng-view 指令被嵌套在一个带有 view-container CSS类的父元素内部。这个类添加了一个 positio n: relative 样式,因此动画过程中, ng-view 的定位相对于这个父元素。 在这里,让我们为过渡动画添加CSS,添加到 animations.css 文件上: app/css/animations.cssapp/css/animations.css .. .view-container { position: relative; } .view-frame.ng-enter, .view-frame.ng-leave { background: white; position: absolute; top: 0; left: 0; right: 0; } .view-frame.ng-enter { -webkit-animation: 0.5s fade-in; -moz-animation: 0.5s fade-in; -o-animation: 0.5s fade-in; animation: 0.5s fade-in; z-index: 100; } .view-frame.ng-leave { -webkit-animation: 0.5s fade-out; -moz-animation: 0.5s fade-out; -o-animation: 0.5s fade-out; animation: 0.5s fade-out; z-index:99; } @keyframes fade-in { from { opacity: 0; } to { opacity: 1; } 第 14 章 应用动画 | 149 } @-moz-keyframes fade-in { from { opacity: 0; } to { opacity: 1; } } @-webkit-keyframes fade-in { from { opacity: 0; } to { opacity: 1; } } @keyframes fade-out { from { opacity: 1; } to { opacity: 0; } } @-moz-keyframes fade-out { from { opacity: 1; } to { opacity: 0; } } @-webkit-keyframes fade-out { from { opacity: 1; } to { opacity: 0; } } /* 别忘了供应商的前缀! */ 没什么惊人的!仅仅是两页之间的一个简单的渐显和渐消效果。这里唯不寻常的东西是,在页面之间实现软切换 动画的时候,我们在前一页(具有 ng-leave 类的页面)的上方使用绝对定位来定位下一页(通过 ng-enter 来指 定)。因此前一页即将被删除时,它是渐消淡出的,与此同时新页渐显现在它上面。 一旦离开动画结束,元素会被移除;一旦进入动画结束 ,元素上的 ng-enter 和 ng-enter-active CSS类会被移 除,导致它用它的默认CSS代码重新呈现、重新定位(因此没有一旦动画结束就没有绝对定位了)。这动作起来非 常流畅,因此页面在路由变化时流动自然,不会有任何跳动感。 应用的CSS类(开始和结束类)与 ng-repeat 很相像。每当一个新页面载入到 ng-view 指令中时,将创建它自己的 一个副本,下载模板并追加内容。这确保了所有的视图都包含在一个单独的HTML元素中,该元素允许简单的动画 控制。 要想了解更多关于CSS动画的信息,请参阅Web 平台文档。 第 14 章 应用动画 | 150 用JavaScript让用JavaScript让 ngClassngClass 动起来动起来 让我们向应用程序添加另一个动画。切换到 phone-detail.html 网页,我们看到已经有一个很棒的缩略图交换 器。通过点击在网页列列中的缩略图,资料手机图像就变了。但是我们可以如何在改变它的同时添加动画呢? 让我们先考虑一下。基本上,当你在一个资料图上点击时,你正在改变图像的状态,以反映新选中的缩略图。在H TML中指定状态改变的最佳方法是使用样式类。和以前很相像,我们使用的CSS样式类以指定指定一个动画,当CSS 类本身变化时动画将发生。 当选中了一个新的手机缩略图时,状态改变了, .active CSS类添加到匹配的资料图像上,并播放了动画。 让我们开始,在 phone-detail.html 网贾上微调HTML代码。注意我们已经改变了显示大图像的方式: app/partials/phone-detail.htmlapp/partials/phone-detail.html ..

    {{phone.name}}

    {{phone.description}}

    就像缩略图,我们使用迭代器来显示所有的所有的资料图像作为一个列表,然而我们没有变动任何迭代相关的动画。而 是,我们在 ng-class 指令上保持关注,因为每当 active 类变成true,则它将应用到元素上,将呈现为可见。否 则,资料图像将隐藏。在我们的案例中,总是有一个元素具有 active 类,因此,任何时候总会有一款手机的资料 图像在屏幕上可见。 当元素上添加了激活类的时候,先添加了 active-add 类和 adtive-add-active 类,以指示Angular引发一个动 画。当元素上移除了激活类的时候,元素上应用了 active-remove 类和 active-remove-active ,它们反过来又会 触发别的动画。 要想确保手机图像在页面第一次加载时正确地显示,我们还要微调详情页的CSS样式: app/css/app.cssapp/css/app.css 第 14 章 应用动画 | 151 .phone-images { background-color: white; width: 450px; height: 450px; overflow: hidden; position: relative; float: left; } ... img.phone { float: left; margin-right: 3em; margin-bottom: 2em; background-color: white; padding: 2em; height: 400px; width: 400px; display: none; } img.phone:first-child { display: block; } 你可能认为我们将创建另一个CSS可用的动画。虽然我们可以那么做,但是还是让我们抓住机会学习如何用 abnima te 模块方法创建JavaScript可用的动画吧。 app/js/animations.jsapp/js/animations.js .. var phonecatAnimations = angular.module('phonecatAnimations', ['ngAnimate']); phonecatAnimations.animation('.phone', function() { var animateUp = function(element, className, done) { if(className != 'active') { return; } element.css({ position: 'absolute', top: 500, left: 0, display: 'block' }); jQuery(element).animate({ top: 0 }, done); return function(cancel) { if(cancel) { element.stop(); } }; } var animateDown = function(element, className, done) { if(className != 'active') { return; } 第 14 章 应用动画 | 152 element.css({ position: 'absolute', left: 0, top: 0 }); jQuery(element).animate({ top: -500 }, done); return function(cancel) { if(cancel) { element.stop(); } }; } return { addClass: animateUp, removeClass: animateDown }; }); 注意,我们正在使用jQuery以实现这个动画。jQuery并不要求JavaScript动画与AngularJS协作,但是我们将使用 它,因为编写你自己的JavaScript动画库超过了这个教程的范围。想要了解更多关于 jQuery.animate 的信息,请 参阅jQuery文档。 包含我们注册过的类的元素,无论元素上添加了一个类还是移除了一个类,都会调用 addClass 回调函数和 remove Class 回调函数;在本案例中,注册过的类是 .phone 。当元素上添加了 .active 类(通过 ng-class 指令),将 引发 addClass JavaScript回调函数,该回调函数带有一个参数 element 。最后传入的参数是 done 回调函数。 do ne 回调函数的目的是,通过调用该函数,当JavaScript动画结束时,可以让Angular知道。 removeClass 回调函数以同样的方式起作用,但是是在一个类从元素上移除时触发它。 在JavaScript回调函数中,你通过操纵DOM创建了该动画。在上面的代码中,这就是 element.css() 和 element.an imate() 所做的事情。回调函数用 500px 的偏移定位了下一个元素,把前一个项目和新的项目往上移 500px ,使 两个项目一起动起来。这导致了一个仿传送带的动画。当 animate 函数完成它的工作,它会调用 done 。 注意 addClass 和 removeClass 两者都返回了一个函数。这是一个可选的可选的函数,当动画被取消时(同一个元素上发 生了别的动画)时或者动画完成时,会调用这个函数。向这个函数传递一个布尔参数,该参数让开发者知道动画 是否被取消了。当动画完成时,这个函数可以用来做一些扫尾工作。 第 14 章 应用动画 | 153 第 14 章 总结第 14 章 总结 第 14 章 总结 | 154 现在你学会了!我们在相对短的时间里创建了一个web应用。在完结篇中我们将讨论接下来何去何从。 第 14 章 总结 | 155 1515 完结篇完结篇 我们的应用程序现在完成了。请随意练习这些代码,用 git checkout 命令跳回到前面的步骤。 要想获得我们在本教程中涉及的更多的Angular概念的细节,以及Angular概念的示例,参见开发指导 当你准备好开始用Angular开发一个项目的时候,我们推荐你用angular种子项目引导你的开发。 我们希望这篇教程对你有用,使你对Angular有了足够的了解,激起你更大的学习愿望。我们特别希望你能够开发 出自己的Angular Web应用,你可能对为Angular贡献产生兴趣。 如果你有什么问题、反馈,或者想和我们打招呼,请在https://groups.google.com/forum/#!forum/angular上发 消息吧。 第 15 章 完结篇 | 157 更多信息请访问 http://wiki.jikexueyuan.com/project/angularjs/
  • 还剩158页未读

    继续阅读

    下载pdf到电脑,查找使用更方便

    pdf的实际排版效果,会与网站的显示效果略有不同!!

    需要 10 金币 [ 分享pdf获得金币 ] 1 人已下载

    下载pdf

    pdf贡献者

    文杰天下

    贡献于2016-10-25

    下载需要 10 金币 [金币充值 ]
    亲,您也可以通过 分享原创pdf 来获得金币奖励!
    下载pdf