AngularJS 教程



AngularJS 是一款优秀的前端 JS 框架。

AngularJS 通过新的属性和表达式扩展了 HTML。

AngularJS 可以构建一个单一页面应用程序(SPAs:Single Page Applications)。

AngularJS 学习起来非常简单。

现在开始学习 AngularJS!



每个章节都有相应的实例

在每个章节中,您可以在线编辑实例,然后点击按钮查看结果。

AngularJS 实例

<!DOCTYPE html>
<html>

<body>

<div ng-app="">
  <p>在输入框中尝试输入:</p>
  <p>姓名:<input type="text" ng-model="name"></p>
  <p ng-bind="name"></p>
</div>

<script src="//www.51coolma.cn/try/angularjs/1.2.5/angular.min.js"></script>

</body>
</html>

尝试一下 »


阅读本教程前,您需要了解的知识:

在开始学习 AngularJS 之前,您需要具备以下基础知识:


AngularJS 历史

AngularJS 是比较新的技术,版本 1.0 是在 2012 年发布的。

AngularJS 是由 Google 的员工 Miško Hevery 从 2009 年开始着手开发。

这是一个非常好的构想,该项目目前已由 Google 正式支持,有一个全职的开发团队继续开发和维护这个库。


AngularJS 实例

本教程包含了大量的 AngularJS 实例!

AngularJS 实例


AngularJS 参考手册

参考手册包含了本教程中使用到的所有指令和过滤器。

AngularJS 参考手册

AngularJS 简介


AngularJS 是一个 JavaScript 框架。它可通过 <script> 标签添加到 HTML 页面。

AngularJS 通过 指令 扩展了 HTML,且通过 表达式 绑定数据到 HTML。


AngularJS 是一个 JavaScript 框架

AngularJS 是一个 JavaScript 框架。它是一个以 JavaScript 编写的库。

AngularJS 是以一个 JavaScript 文件形式发布的,可通过 script 标签添加到网页中:

<script src="http://apps.bdimg.com/libs/angular.js/1.4.6/angular.min.js" rel="external nofollow" rel="external nofollow" rel="external nofollow" ></script>
Note 我们建议把脚本放在 <body> 元素的底部。
这会提高网页加载速度,因为 HTML 加载不受制于脚本加载。

AngularJS 扩展了 HTML

AngularJS 通过 ng-directives 扩展了 HTML。

ng-app 指令定义一个 AngularJS 应用程序。

ng-model 指令把元素值(比如输入域的值)绑定到应用程序。

ng-bind 指令把应用程序数据绑定到 HTML 视图。

AngularJS 实例

<!DOCTYPE html>
<html>
<body>

<div ng-app="">
  <p>在输入框中尝试输入:</p>
  <p>姓名:<input type="text" ng-model="name"></p>
  <p ng-bind="name"></p>
</div>

<script src="http://apps.bdimg.com/libs/angular.js/1.4.6/angular.min.js" rel="external nofollow" rel="external nofollow" rel="external nofollow" ></script>

</body>
</html>

尝试一下 »

实例讲解:

当网页加载完毕,AngularJS 自动开启。

ng-app 指令告诉 AngularJS,<div> 元素是 AngularJS 应用程序 的"所有者"。

ng-model 指令把输入域的值绑定到应用程序变量 name

ng-bind 指令把应用程序变量 name 绑定到某个段落的 innerHTML。

Note 如果您移除了 ng-app 指令,HTML 将直接把表达式显示出来,不会去计算表达式的结果。

什么是 AngularJS?

"AngularJS 是专门为应用程序设计的 HTML。"

AngularJS 使得开发现代的单一页面应用程序(SPAs:Single Page Applications)变得更加容易。

  • AngularJS 把应用程序数据绑定到 HTML 元素。
  • AngularJS 可以克隆和重复 HTML 元素。
  • AngularJS 可以隐藏和显示 HTML 元素。
  • AngularJS 可以在 HTML 元素"背后"添加代码。
  • AngularJS 支持输入验证。

AngularJS 指令

正如您所看到的,AngularJS 指令是以 ng 作为前缀的 HTML 属性。

ng-init 指令初始化 AngularJS 应用程序变量。

AngularJS 实例

<div ng-app="" ng-init="firstName='John'">

<p>姓名为 <span ng-bind="firstName"></span></p>

</div>

尝试一下 »
Note HTML5 允许扩展的(自制的)属性,以 data- 开头。
AngularJS 属性以 ng- 开头,但是您可以使用 data-ng- 来让网页对 HTML5 有效。

带有有效的 HTML5:

AngularJS 实例

<div data-ng-app="" data-ng-init="firstName='John'">

<p>姓名为 <span data-ng-bind="firstName"></span></p>

</div>

尝试一下 »


AngularJS 表达式

AngularJS 表达式写在双大括号内:{{ expression }}

AngularJS 表达式把数据绑定到 HTML,这与 ng-bind 指令有异曲同工之妙。

AngularJS 将在表达式书写的位置"输出"数据。

AngularJS 表达式 很像 JavaScript 表达式:它们可以包含文字、运算符和变量。

实例 {{ 5 + 5 }} 或 {{ firstName + " " + lastName }}

AngularJS 实例

<!DOCTYPE html>
<html>
<body>

<div ng-app="">
  <p>我的第一个表达式: {{ 5 + 5 }}</p>
</div>

<script src="http://apps.bdimg.com/libs/angular.js/1.4.6/angular.min.js" rel="external nofollow" rel="external nofollow" rel="external nofollow" ></script>

</body>
</html>

尝试一下 »

AngularJS 表达式

本节介绍了 AngularJS 表达式的作用与使用方法。

AngularJS 使用 表达式 把数据绑定到 HTML。


AngularJS 表达式

AngularJS 表达式写在双大括号内:{{ expression }}

AngularJS 表达式把数据绑定到 HTML,这与 ng-bind 指令有异曲同工之妙。

AngularJS 将在表达式书写的位置"输出"数据。

AngularJS 表达式 很像 JavaScript 表达式:它们可以包含文字、运算符和变量。

实例 {{ 5 + 5 }} 或 {{ firstName + " " + lastName }}


AngularJS 数字

AngularJS 数据就像 JavaScript 数字:

AngularJS 实例

<div ng-app="" ng-init="quantity=1;cost=5">

<p>总价: {{ quantity * cost }}</p>

</div>

尝试一下 »

使用 ng-bind 的相同实例:

AngularJS 实例

<div ng-app="" ng-init="quantity=1;cost=5">

<p>总价: <span ng-bind="quantity * cost"></span></p>

</div>

尝试一下 »
Note 使用 ng-init 不是很常见。您将在控制器一章中学习到一个更好的初始化数据的方式。

AngularJS 字符串

AngularJS 字符串就像 JavaScript 字符串

AngularJS 实例

<div ng-app="" ng-init="firstName='John';lastName='Doe'">

<p>姓名: {{ firstName + " " + lastName }}</p>

</div>

尝试一下 »

使用 ng-bind 的相同实例:

AngularJS 实例

<div ng-app="" ng-init="firstName='John';lastName='Doe'">

<p>姓名: <span ng-bind="firstName + ' ' + lastName"></span></p>

</div>

尝试一下 »


AngularJS 对象

AngularJS 对象就像 JavaScript 对象

AngularJS 实例

<div ng-app="" ng-init="person={firstName:'John',lastName:'Doe'}">

<p>姓为 {{ person.lastName }}</p>

</div>

尝试一下 »

使用 ng-bind 的相同实例:

AngularJS 实例

<div ng-app="" ng-init="person={firstName:'John',lastName:'Doe'}">

<p>姓为 <span ng-bind="person.lastName"></span></p>

</div>

尝试一下 »


AngularJS 数组

AngularJS 数组就像 JavaScript 数组:

AngularJS 实例

<div ng-app="" ng-init="points=[1,15,19,2,40]">

<p>第三个值为 {{ points[2] }}</p>

</div>

尝试一下 »

使用 ng-bind 的相同实例:

AngularJS 实例

<div ng-app="" ng-init="points=[1,15,19,2,40]">

<p>第三个值为 <span ng-bind="points[2]"></span></p>

</div>

尝试一下 »

AngularJS 指令

本节为你介绍 AngularJS 使用的一些指令。

AngularJS 通过被称为 指令 的新属性来扩展 HTML。


AngularJS 指令

AngularJS 指令是扩展的 HTML 属性,带有前缀 ng-

ng-app 指令初始化一个 AngularJS 应用程序。

ng-init 指令初始化应用程序数据。

ng-model 指令把元素值(比如输入域的值)绑定到应用程序。

AngularJS 实例

<div ng-app="" ng-init="firstName='John'">

  <p>在输入框中尝试输入:</p>
  <p>姓名:<input type="text" ng-model="firstName"></p>
  <p>你输入的为: {{ firstName }}</p>

</div>

尝试一下 »

ng-app 指令告诉 AngularJS,<div> 元素是 AngularJS 应用程序 的"所有者"。

Note一个网页可以包含多个运行在不同元素中的 AngularJS 应用程序。

数据绑定

上面实例中的 {{ firstName }} 表达式是一个 AngularJS 数据绑定表达式。

AngularJS 中的数据绑定,同步了 AngularJS 表达式与 AngularJS 数据。

{{ firstName }} 是通过 ng-model="firstName" 进行同步。

在下一个实例中,两个文本域是通过两个 ng-model 指令同步的:

AngularJS 实例

<div ng-app="" ng-init="quantity=1;price=5">

<h2>价格计算器</h2>

数量: <input type="number" ng-model="quantity">
价格: <input type="number" ng-model="price">

<p><b>总价:</b> {{ quantity * price }}</p>

</div>

尝试一下 »
Note使用 ng-init 不是很常见。您将在控制器一章中学习到一个更好的初始化数据的方式。

重复 HTML 元素

ng-repeat 指令会重复一个 HTML 元素:

AngularJS 实例

<div ng-app="" ng-init="names=['Jani','Hege','Kai']">
  <p>使用 ng-repeat 来循环数组</p>
  <ul>
    <li ng-repeat="x in names">
      {{ x }}
    </li>
  </ul>
<div>

尝试一下 »

ng-repeat 指令用在一个对象数组上:

AngularJS 实例

<div ng-app="" ng-init="names=[
{name:'Jani',country:'Norway'},
{name:'Hege',country:'Sweden'},
{name:'Kai',country:'Denmark'}]">

<p>循环对象:</p>
<ul>
  <li ng-repeat="x in names">
    {{ x.name + ', ' + x.country }}
  </li>
</ul>

</div>

尝试一下 »
NoteAngularJS 完美支持数据库的 CRUD(增加Create、读取Read、更新Update、删除Delete)应用程序。
把实例中的对象想象成数据库中的记录。

ng-app 指令

ng-app 指令定义了 AngularJS 应用程序的 根元素

ng-app 指令在网页加载完毕时会自动引导(自动初始化)应用程序。

稍后您将学习到 ng-app 如何通过一个值(比如 ng-app="myModule")连接到代码模块。


ng-init 指令

ng-init 指令为 AngularJS 应用程序定义了 初始值

通常情况下,不使用 ng-init。您将使用一个控制器或模块来代替它。

稍后您将学习更多有关控制器和模块的知识。


ng-model 指令

ng-model 指令 绑定 HTML 元素 到应用程序数据。

ng-model 指令也可以:

  • 为应用程序数据提供类型验证(number、email、required)。
  • 为应用程序数据提供状态(invalid、dirty、touched、error)。
  • 为 HTML 元素提供 CSS 类。
  • 绑定 HTML 元素到 HTML 表单。

ng-repeat 指令

ng-repeat 指令对于集合中(数组中)的每个项会 克隆一次 HTML 元素

AngularJS Scope(作用域)

本节为你介绍了什么是 AngularJS Scope(作用域)以及 Scope 在 AngularJS 应该如何使用。

Scope(作用域) 是应用在 HTML (视图) 和 JavaScript (控制器)之间的纽带。

Scope 是一个对象,有可用的方法和属性。

Scope 可应用在视图和控制器上。


如何使用 Scope

当你在 AngularJS 创建控制器时,你可以将 $scope 对象当作一个参数传递:

AngularJS 实例

控制器中的属性对应了视图上的属性:

<div ng-app="myApp" ng-controller="myCtrl">

<h1>{{carname}}</h1>

</div>

<script>
var app = angular.module('myApp', []);

app.controller('myCtrl', function($scope) {
    $scope.carname = "Volvo";
});
</script>

尝试一下 »

当在控制器中添加 $scope 对象时,视图 (HTML) 可以获取了这些属性。

视图中,你不需要添加 $scope 前缀, 只需要添加属性名即可,如: {{carname}}


Scope 概述

AngularJS 应用组成如下:

  • View(视图), 即 HTML。
  • Model(模型), 当前视图中可用的数据。
  • Controller(控制器), 即 JavaScript 函数,可以添加或修改属性。

scope 是模型。

scope 是一个 JavaScript 对象,带有属性和方法,这些属性和方法可以在视图和控制器中使用。

AngularJS 实例

如果你修改了视图,模型和控制器也会相应更新:

<div ng-app="myApp" ng-controller="myCtrl">

<input ng-model="name">

<h1>我的名字是 {{name}}</h1>

</div>

<script>
var app = angular.module('myApp', []);

app.controller('myCtrl', function($scope) {
    $scope.name = "John Dow";
});
</script>

尝试一下 »

Scope 作用范围

了解你当前使用的 scope 是非常重要的。

在以上两个实例中,只有一个作用域 scope,所以处理起来比较简单,但在大型项目中, HTML DOM 中有多个作用域,这时你就需要知道你使用的 scope 对应的作用域是哪一个。

AngularJS 实例

当我们使用 ng-repeat 指令时,每个重复项都访问了当前的重复对象:

<div ng-app="myApp" ng-controller="myCtrl">

<ul>
    <li ng-repeat="x in names">{{x}}</li>
</ul>

</div>

<script>
var app = angular.module('myApp', []);

app.controller('myCtrl', function($scope) {
    $scope.names = ["Emil", "Tobias", "Linus"];
});
</script>

尝试一下 »

每个 <li> 元素可以访问当前的重复对象,这里对应的是一个字符串, 并使用变量 x 表示。


根作用域

所有的应用都有一个 $rootScope,它可以作用在 ng-app 指令包含的所有 HTML 元素中。

$rootScope 可作用于整个应用中。是各个 controller 中 scope 的桥梁。用 rootscope 定义的值,可以在各个 controller 中使用。

AngularJS 实例

创建控制器时,将 $rootScope 作为参数传递,可在应用中使用:

<div ng-app="myApp" ng-controller="myCtrl"><h1>{{lastname}} 家族成员:</h1><ul>    <li ng-repeat="x in names">{{x}} {{lastname}}</li></ul></div><script>var app = angular.module('myApp', []);app.controller('myCtrl', function($scope, $rootScope) {    $scope.names = ["Emil", "Tobias", "Linus"];    $rootScope.lastname = "Refsnes";});</script>
尝试一下 »

AngularJS 控制器

AngularJS 控制器在<div>内由 ng-controller 指令定义。

 AngularJS 控制器 控制 AngularJS 应用程序的数据。

 AngularJS 控制器是常规的 JavaScript 对象


AngularJS 控制器

AngularJS 应用程序被控制器控制。

ng-controller 指令定义了应用程序控制器。

控制器是 JavaScript 对象,由标准的 JavaScript 对象的构造函数 创建。

AngularJS 实例

<div ng-app="myApp" ng-controller="myCtrl">

名: <input type="text" ng-model="firstName"><br>
姓: <input type="text" ng-model="lastName"><br>
<br>
姓名: {{firstName + " " + lastName}}

</div>

<script>
var app = angular.module('myApp', []);
app.controller('myCtrl', function($scope) {
    $scope.firstName = "John";
    $scope.lastName = "Doe";
});
</script>

尝试一下 »

应用解析:

AngularJS 应用程序由 ng-app 定义。应用程序在 <div> 内运行。

ng-controller="myCtrl" 属性是一个 AngularJS 指令。用于定义一个控制器。

myCtrl 函数是一个 JavaScript 函数。

AngularJS 使用$scope 对象来调用控制器。

在 AngularJS 中, $scope 是一个应用象(属于应用变量和函数)。

控制器的 $scope (相当于作用域、控制范围)用来保存AngularJS Model(模型)的对象。

控制器在作用域中创建了两个属性(firstNamelastName)。

ng-model 指令绑定输入域到控制器的属性(firstName 和 lastName)。


控制器方法

上面的实例演示了一个带有 lastName 和 firstName 这两个属性的控制器对象。

控制器也可以有方法(变量和函数):

AngularJS 实例

<div ng-app="myApp" ng-controller="personCtrl">

名: <input type="text" ng-model="firstName"><br>
姓: <input type="text" ng-model="lastName"><br>
<br>
姓名: {{fullName()}}

</div>

<script>
var app = angular.module('myApp', []);
app.controller('personCtrl', function($scope) {
    $scope.firstName = "John";
    $scope.lastName = "Doe";
    $scope.fullName = function() {
        return $scope.firstName + " " + $scope.lastName;
    }
});
</script>

尝试一下 »


外部文件中的控制器

在大型的应用程序中,通常是把控制器存储在外部文件中。

只需要把 <script> 标签中的代码复制到名为 personController.js 的外部文件中即可:

AngularJS 实例

<div ng-app="myApp" ng-controller="personController">

名: <input type="text" ng-model="person.firstName"><br>
姓: <input type="text" ng-model="person.lastName"><br>
<br>
姓名: {{person.firstName + " " + person.lastName}}

</div>

<script src="personController.js"></script>

尝试一下 »


其他实例

以下实例创建一个新的控制器文件:

angular.module('myApp', []).controller('namesCtrl', function($scope) {
    $scope.names = [
        {name:'Jani',country:'Norway'},
        {name:'Hege',country:'Sweden'},
        {name:'Kai',country:'Denmark'}
    ];
}); 

保存文件为  namesController.js:

然后,在应用中使用控制器文件:

AngularJS 实例

<div ng-app="myApp" ng-controller="namesCtrl">

<ul>
 <li ng-repeat="x in names">
    {{ x.name + ', ' + x.country }}
  </li>
</ul>

</div>

<script src="namesController.js"></script>

尝试一下 »

AngularJS 过滤器

AngularJS 过滤器可以用来格式化数据,过滤器能够用在表达式和指令中。

过滤器可以使用一个管道字符(|)添加到表达式和指令中。


AngularJS 过滤器

AngularJS 过滤器可用于转换数据:

过滤器描述
currency格式化数字为货币格式。
filter从数组项中选择一个子集。
lowercase格式化字符串为小写。
orderBy根据某个表达式排列数组。
uppercase格式化字符串为大写。


向表达式添加过滤器

过滤器可以通过一个管道字符(|)和一个过滤器添加到表达式中。

(下面的两个实例,我们将使用前面章节中提到的 person 控制器)

uppercase 过滤器格式化字符串为大写:

AngularJS 实例

<div ng-app="myApp" ng-controller="personCtrl">

<p>姓名为 {{ lastName | uppercase }}</p>

</div>

尝试一下 »

lowercase 过滤器格式化字符串为小写:

AngularJS 实例

<div ng-app="myApp" ng-controller="personCtrl">

<p>姓名为 {{ lastName | lowercase }}</p>

</div>

尝试一下 »


currency 过滤器

currency 过滤器格式化数字为货币格式:

AngularJS 实例

<div ng-app="myApp" ng-controller="costCtrl">

<input type="number" ng-model="quantity">
<input type="number" ng-model="price">

<p>总价 = {{ (quantity * price) | currency }}</p>

</div>

尝试一下 »


向指令添加过滤器

过滤器可以通过一个管道字符(|)和一个过滤器添加到指令中。

orderBy 过滤器根据某个表达式排列数组:

AngularJS 实例

<div ng-app="myApp" ng-controller="namesCtrl">

<ul>
  <li ng-repeat="x in names | orderBy:'country'">
    {{ x.name + ', ' + x.country }}
  </li>
</ul>

<div>

尝试一下 »


过滤输入

输入过滤器可以通过一个管道字符(|)和一个过滤器添加到指令中,该过滤器后跟一个冒号和一个模型名称。

filter 过滤器从数组中选择一个子集:

AngularJS 实例

<div ng-app="myApp" ng-controller="namesCtrl">

<p><input type="text" ng-model="test"></p>

<ul>
  <li ng-repeat="x in names | filter:test | orderBy:'country'">
    {{ (x.name | uppercase) + ', ' + x.country }}
  </li>
</ul>

</div>

尝试一下 »


AngularJS 服务(Service)

AngularJS 中的服务是一个函数或对象。

AngularJS 中你可以创建自己的服务,或使用内建服务。


什么是服务?

在 AngularJS 中,服务是一个函数或对象,可在你的 AngularJS 应用中使用。

AngularJS 内建了30 多个服务。

有个 $location 服务,它可以返回当前页面的 URL 地址。

实例

var app = angular.module('myApp', []);
app.controller('customersCtrl', function($scope, $location) {
    $scope.myUrl = $location.absUrl();
});

尝试一下 »

注意 $location 服务是作为一个参数传递到 controller 中。如果要使用它,需要在 controller 中定义。


为什么使用服务?

$http 是 AngularJS 应用中最常用的服务。服务向服务器发送请求,应用响应服务器传送过来的数据。

AngularJS 会一直监控应用,处理事件变化, AngularJS 使用 $location 服务比使用 window.location 对象更好。


$http 服务

$http 是 AngularJS 应用中最常用的服务。 服务向服务器发送请求,应用响应服务器传送过来的数据。

实例

使用 $http 服务向服务器请求数据:

var app = angular.module('myApp', []);
app.controller('myCtrl', function($scope, $http) {
    $http.get("welcome.htm").then(function (response) {
        $scope.myWelcome = response.data;
    });
});

尝试一下 »

以上是一个非常简单的 $http 服务实例,更多 $http 服务应用请查看 AngularJS Http 教程


$timeout 服务

AngularJS $timeout 服务对应了 JS window.setTimeout 函数。

实例

两秒后显示信息:

var app = angular.module('myApp', []);
app.controller('myCtrl', function($scope, $timeout) {
    $scope.myHeader = "Hello World!";
    $timeout(function () {
        $scope.myHeader = "How are you today?";
    }, 2000);
});

尝试一下 »

$interval 服务

AngularJS $interval 服务对应了 JS window.setInterval 函数。

实例

每一秒显示信息:

var app = angular.module('myApp', []);
app.controller('myCtrl', function($scope, $interval) {
    $scope.theTime = new Date().toLocaleTimeString();
    $interval(function () {
        $scope.theTime = new Date().toLocaleTimeString();
    }, 1000);
});

尝试一下 »

创建自定义服务

你可以创建自定义的访问,链接到你的模块中:

创建名为hexafy 的访问:

app.service('hexafy', function() {
    this.myFunc = function (x) {
        return x.toString(16);
    }
});

要使用自定义的访问,需要在定义过滤器的时候独立添加:

实例

使用自定义的的服务 hexafy 将一个数字转换为16进制数:

app.controller('myCtrl', function($scope, hexafy) {
    $scope.hex = hexafy.myFunc(255);
});

尝试一下 »

过滤器中,使用自定义服务

当你创建了自定义服务,并连接到你的应用上后,你可以在控制器,指令,过滤器或其他服务中使用它。

在过滤器 myFormat 中使用服务 hexafy:

app.filter('myFormat',['hexify', function(hexify) {
    return function(x) {
        returnhexify.myFunc(x);
    };
}]);

尝试一下 »

在对象数组中获取值时你可以使用过滤器:

创建服务 hexafy:

<ul>
<li ng-repeat="x in counts">{{x | myFormat}}</li>
</ul>

尝试一下 »

相关文章

AngularJS 过滤器

AngularJS XMLHttpRequest

我们可以使用 AngularJS 内置的 $http 服务直接同外部进行通信。

$http 服务只是简单的封装了浏览器原生的 XMLHttpRequest 对象。


$http 是 AngularJS 中的一个核心服务,用于读取远程服务器的数据。


读取 JSON 文件

以下是存储在web服务器上的 JSON 文件:

Customers_JSON.php

{"records":[
{
"Name" : "Alfreds Futterkiste",
"City" : "Berlin",
"Country" : "Germany"
},
{
"Name" : "Berglunds snabbköp",
"City" : "Luleå",
"Country" : "Sweden"
},
{
"Name" : "Centro comercial Moctezuma",
"City" : "México D.F.",
"Country" : "Mexico"
},
{
"Name" : "Ernst Handel",
"City" : "Graz",
"Country" : "Austria"
},
{
"Name" : "FISSA Fabrica Inter. Salchichas S.A.",
"City" : "Madrid",
"Country" : "Spain"
},
{
"Name" : "Galería del gastrónomo",
"City" : "Barcelona",
"Country" : "Spain"
},
{
"Name" : "Island Trading",
"City" : "Cowes",
"Country" : "UK"
},
{
"Name" : "Königlich Essen",
"City" : "Brandenburg",
"Country" : "Germany"
},
{
"Name" : "Laughing Bacchus Wine Cellars",
"City" : "Vancouver",
"Country" : "Canada"
},
{
"Name" : "Magazzini Alimentari Riuniti",
"City" : "Bergamo",
"Country" : "Italy"
},
{
"Name" : "North/South",
"City" : "London",
"Country" : "UK"
},
{
"Name" : "Paris spécialités",
"City" : "Paris",
"Country" : "France"
},
{
"Name" : "Rattlesnake Canyon Grocery",
"City" : "Albuquerque",
"Country" : "USA"
},
{
"Name" : "Simons bistro",
"City" : "København",
"Country" : "Denmark"
},
{
"Name" : "The Big Cheese",
"City" : "Portland",
"Country" : "USA"
},
{
"Name" : "Vaffeljernet",
"City" : "Århus",
"Country" : "Denmark"
},
{
"Name" : "Wolski Zajazd",
"City" : "Warszawa",
"Country" : "Poland"
}
]}


AngularJS $http

AngularJS $http 是一个用于读取web服务器上数据的服务。

$http.get(url) 是用于读取服务器数据的函数。

AngularJS 实例

<div ng-app="" ng-controller="customersController">

<ul>
  <li ng-repeat="x in names">
    {{ x.Name + ', ' + x.Country }}
  </li>
</ul>

</div>

<script>
function customersController($scope,$http) {
    $http.get("/statics/demosource/Customers_JSON.php")
    .success(function(response) {$scope.names = response;});
}
</script>

尝试一下 »

应用解析:

AngularJS 应用通过 ng-app 定义。应用在 <div> 中执行。

ng-controller 指令设置了 controller 对象 名。

函数 customersController 是一个标准的 JavaScript 对象构造器

控制器对象有一个属性: $scope.names

$http.get() 从web服务器上读取静态 JSON 数据

服务器数据文件为:  /statics/demosource/Customers_JSON.php

当从服务端载入 JSON 数据时,$scope.names 变为一个数组。

Note 以上代码也可以用于读取数据库数据。

AngularJS Select(选择框)

本节介绍 AngularJS Select(选择框)如何使用。

AngularJS 可以使用数组或对象创建一个下拉列表选项。


使用 ng-options 创建选择框

在 AngularJS 中我们可以使用 ng-options 指令来创建一个下拉列表,列表项通过对象和数组循环输出,如下实例:

实例

<div ng-app="myApp" ng-controller="myCtrl">

<select ng-model="selectedName" ng-options="x for x in names">
</select>

</div>

<script>
var app = angular.module('myApp', []);
app.controller('myCtrl', function($scope) {
    $scope.names = ["Google", "W3Cschool", "Taobao"];
});
</script>

尝试一下 »

ng-options 与 ng-repeat

我们也可以使用ng-repeat 指令来创建下拉列表:

实例

<select>
<option ng-repeat="x in names">{{x}}</option>
</select>

尝试一下 »

ng-repeat 指令是通过数组来循环 HTML 代码来创建下拉列表,但 ng-options 指令更适合创建下拉列表,它有以下优势:

使用 ng-options 的选项的一个对象, ng-repeat 是一个字符串。


应该用哪个更好?

假设我们使用以下对象:

$scope.sites = [    {site : "Google", url : "http://www.google.com"},    {site : "W3CSchool", url : "http://www.51coolma.cn"},    {site : "Taobao", url : "http://www.taobao.com"}];

ng-repeat 有局限性,选择的值是一个字符串:

实例

使用 ng-repeat:

<select ng-model="selectedSite">
<option ng-repeat="x in sites" value="{{x.url}}">{{x.site}}</option>
</select>

<h1>你选择的是: {{selectedSite}}</h1>

尝试一下 »

使用 ng-options 指令,选择的值是一个对象:

实例

使用 ng-options:

<select ng-model="selectedSite" ng-options="x.site for x in sites">
</select>

<h1>你选择的是: {{selectedSite.site}}</h1>
<p>网址为: {{selectedSite.url}}</p>

尝试一下 »

当选择值是一个对象时,我们就可以获取更多信息,应用也更灵活。


数据源为对象

前面实例我们使用了数组作为数据源,以下我们将数据对象作为数据源。

$scope.sites = {    site01 : "Google",    site02 : "W3CSchool",    site03 : "Taobao"};

ng-options 使用对象有很大的不同,如下所示:

实例

使用对象作为数据源, x 为键(key),y 为值(value):

<select ng-model="selectedSite" ng-options="x for (x, y) in sites">
</select>

<h1>你选择的值是: {{selectedSite}}</h1>

尝试一下 »

你选择的值为在 key-value 对中的 value

value 在 key-value 对中也可以是个对象:

实例

选择的值在 key-value 对的 value 中, 这是它是一个对象:

$scope.cars = {
car01 : {brand : "Ford", model : "Mustang", color : "red"},
car02 : {brand : "Fiat", model : "500", color : "white"},
car03 : {brand : "Volvo", model : "XC90", color : "black"}
};

尝试一下 »

在下拉菜单也可以不使用key-value 对中的 key , 直接使用对象的属性:

实例

<select ng-model="selectedCar" ng-options="y.brand for (x, y) in sites">
</select>

尝试一下 »

AngularJS 表格

本节介绍了显示 AngularJS 表格的方法。

ng-repeat 指令可以完美的显示表格。


在表格中显示数据

使用 angular 显示表格是非常简单的:

AngularJS 实例

<div ng-app="" ng-controller="customersController">

<table>
  <tr ng-repeat="x in names">
    <td>{{ x.Name }}</td>
    <td>{{ x.Country }}</td>
  </tr>
</table>

</div>

<script>
function customersController($scope,$http) {
  $http.get("/statics/demosource/Customers_JSON.php")
  .success(function(response) {$scope.names = response;});
}
</script>

尝试一下 »

废弃声明 (v1.5)v1.5 中$http 的 success 和 error 方法已废弃。使用 then 方法替代。如果你使用的是 v1.5 以下版本,可以使用以下代码:var app = angular.module('myApp', []);app.controller('customersCtrl', function($scope, $http) {   $http.get("/try/angularjs/data/Customers_JSON.php")   .success(function (response) {$scope.names = response.records;});});

Customers_JSON.php 文件代码:

实例

<?php

echo <<<EOT

{

"records":[

{"Name":"Alfreds Futterkiste","City":"Berlin","Country":"Germany"},

{"Name":"Ana Trujillo Emparedados y helados","City":"México D.F.","Country":"Mexico"},

{"Name":"Antonio Moreno Taquería","City":"México D.F.","Country":"Mexico"},

{"Name":"Around the Horn","City":"London","Country":"UK"},

{"Name":"B's Beverages","City":"London","Country":"UK"},

{"Name":"Berglunds snabbköp","City":"Luleå","Country":"Sweden"},

{"Name":"Blauer See Delikatessen","City":"Mannheim","Country":"Germany"},

{"Name":"Blondel père et fils","City":"Strasbourg","Country":"France"},

{"Name":"Bólido Comidas preparadas","City":"Madrid","Country":"Spain"},

{"Name":"Bon app'","City":"Marseille","Country":"France"},

{"Name":"Bottom-Dollar Marketse","City":"Tsawassen","Country":"Canada"},

{"Name":"Cactus Comidas para llevar","City":"Buenos Aires","Country":"Argentina"},

{"Name":"Centro comercial Moctezuma","City":"México D.F.","Country":"Mexico"},

{"Name":"Chop-suey Chinese","City":"Bern","Country":"Switzerland"},

{"Name":"Comércio Mineiro","City":"São Paulo","Country":"Brazil"}

]

}

EOT;

?>


使用 CSS 样式

为了让页面更加美观,我们可以在页面中使用CSS:

CSS 样式

<style>
table, th , td {
  border: 1px solid grey;
  border-collapse: collapse;
  padding: 5px;
}
table tr:nth-child(odd) {
  background-color: #f1f1f1;
}
table tr:nth-child(even) {
  background-color: #ffffff;
}
</style>

尝试一下 »


使用 orderBy 过滤器

排序显示,我们可以使用 orderBy 过滤器: 

AngularJS 实例

<table>
  <tr ng-repeat="x in names | orderBy : 'Country'">
    <td>{{ x.Name }}</td>
    <td>{{ x.Country }}</td>
  </tr>
</table>

尝试一下 »


使用 uppercase 过滤器

如果字母要转换为大写,可以添加 uppercase 过滤器: 

AngularJS 实例

<table>
  <tr ng-repeat="x in names">
    <td>{{ x.Name }}</td>
    <td>{{ x.Country | uppercase}}</td>
  </tr>
</table>

尝试一下 »

显示序号 ($index)

表格显示序号可以在 <td> 中添加 $index:

实例

<table>

  <tr ng-repeat="x in names">

    <td>{{ $index + 1 }}</td>

    <td>{{ x.Name }}</td>

    <td>{{ x.Country }}</td>

  </tr>

</table>


AngularJS SQL


在前面章节中的代码也可以用于读取数据库中的数据。


使用 PHP 从 MySQL 中获取数据

AngularJS 实例

<div ng-app="" ng-controller="customersController">

<table>
  <tr ng-repeat="x in names">
    <td>{{ x.Name }}</td>
    <td>{{ x.Country }}</td>
  </tr>
</table>

</div>

<script>
function customersController($scope,$http) {
    var site = "http://www.51coolma.cn";
    var page = "/statics/demosource/Customers_SQL.php";
    $http.get(site + page)
    .success(function(response) {$scope.names = response;});
}
</script>

尝试一下 »


ASP.NET 中执行 SQL 获取数据

AngularJS 实例

<div ng-app="" ng-controller="customersController">

<table>
  <tr ng-repeat="x in names">
    <td>{{ x.Name }}</td>
    <td>{{ x.Country }}</td>
  </tr>
</table>

</div>

<script>
function customersController($scope,$http) {
    var site = "http://www.51coolma.cn";
    var page = "/try/angularjs/data/Customers_SQL.aspx";
    $http.get(site + page)
    .success(function(response) {$scope.names = response;});
}
</script>

尝试一下 »


PHP 读取 MySQL 数据代码

<?php
header("Access-Control-Allow-Origin: *");
header("Content-Type: text/html; charset=UTF-8");

$conn = new mysqli("myServer", "myUser", "myPassword", "Northwind");

$result = $conn->query("SELECT CompanyName, City, Country FROM Customers");

$outp "[";
while($rs = $result->fetch_array(MYSQLI_ASSOC)) {
    if ($outp != "[") {$outp .= ",";}
    $outp .= '{"Name":"'  . $rs["CompanyName"] . '",';
    $outp .= '"City":"'   . $rs["City"]        . '",';
    $outp .= '"Country":"'. $rs["Country"]     . '"}';
}
$outp .="]";

$conn->close();

echo($outp);
?>


PHP 读取 MS Access 代码

<?php
header("Access-Control-Allow-Origin: *");
header("Content-Type: text/html; charset=ISO-8859-1");

$conn = new COM("ADODB.Connection");
$conn->open("PROVIDER=Microsoft.Jet.OLEDB.4.0;Data Source=Northwind.mdb");

$rs = $conn->execute("SELECT CompanyName, City, Country FROM Customers");

$outp = "[";
while (!$rs->EOF) {
    if ($outp != "[") {$outp .= ",";}
    $outp .= '{"Name":"'  . $rs["CompanyName"] . '",';
    $outp .= '"City":"'   . $rs["City"]        . '",';
    $outp .= '"Country":"'. $rs["Country"]     . '"}';
    $rs->MoveNext();
}
$outp .= "]";

$conn->close();

echo ($outp);
?>


服务端 ASP.NET, VB 和 MS Access 代码

<%@ Import Namespace="System.IO"%>
<%@ Import Namespace="System.Data"%>
<%@ Import Namespace="System.Data.OleDb"%>
<%
Response.AppendHeader("Access-Control-Allow-Origin", "*")
Dim conn As OleDbConnection
Dim objAdapter As OleDbDataAdapter
Dim objTable As DataTable
Dim objRow As DataRow
Dim objDataSet As New DataSet()
Dim outp
Dim c
conn = New OledbConnection("Provider=Microsoft.Jet.OLEDB.4.0;data source=Northwind.mdb")
objAdapter = New OledbDataAdapter("SELECT CompanyName, City, Country FROM Customers", conn)
objAdapter.Fill(objDataSet, "myTable")
objTable=objDataSet.Tables("myTable")

outp = "["
c = chr(34)
for each x in objTable.Rows
if outp <> "[" then outp = outp & ","
outp = outp & "{" & c & "Name"    & c & ":" & c & x("CompanyName") & c & ","
outp = outp &       c & "City"    & c & ":" & c & x("City")        & c & ","
outp = outp &       c & "Country" & c & ":" & c & x("Country")     & c & "}"
next

outp = outp & "]"
response.write(outp)
conn.close
%>


服务端 ASP.NET, VB Razor 和 SQL Lite 代码

@{
Response.AppendHeader("Access-Control-Allow-Origin", "*")
var db = Database.Open("Northwind");
var query = db.Query("SELECT CompanyName, City, Country FROM Customers");
var outp ="["
}
@foreach(var row in query)
{
if outp <> "[" then outp = outp + ","
outp = outp + "{" + c + "Name"    + c + ":" + c + @row.CompanyName + c + ","
outp = outp +       c + "City"    + c + ":" + c + @row.City        + c + ","
outp = outp +       c + "Country" + c + ":" + c + @row.Country     + c + "}"
}
outp = outp + "]"
@outp

AngularJS HTML DOM

AngularJS 为 HTML DOM 元素的属性提供了绑定应用数据的指令。

AngularJS 有自己的 HTML 属性指令。


ng-disabled 指令

ng-disabled 指令设置表单输入字段的 disabled 属性(input, select, 或 textarea)。

如果 ng-disabled 中的表达式返回 true 则表单字段将被禁用

AngularJS 实例

<div ng-app="">

<p>
<button ng-disabled="mySwitch">点我!</button>
</p>

<p>
<input type="checkbox" ng-model="mySwitch">按钮
</p>

</div>

尝试一下 »

实例讲解:

ng-disabled 指令绑定应用程序数据 "mySwitch" 到 HTML 的 disabled 属性。

ng-model 指令绑定 "mySwitch" 到 HTML input checkbox 元素的内容(value)。


ng-show 指令

ng-show 指令隐藏或显示一个 HTML 元素。

AngularJS 实例

<div ng-app="">

<p ng-show="true">我是可见的。</p>

<p ng-show="false">我是不可见的。</p>

</div>

尝试一下 »

您可以使用一个评估为 true or false 的表达式(比如 ng-show="hour < 12")来隐藏和显示 HTML 元素。

在下一章中,有另一个实例,通过单击一个按钮来隐藏一个 HTML 元素。

相关文章

HTML DOM 元素


AngularJS HTML 事件

本节介绍了 AngularJS 的 HTML 事件的使用。

AngularJS 有自己的 HTML 事件指令。


ng-click 指令

ng-click 指令定义了一个 AngularJS 单击事件。

AngularJS 实例

<div ng-app="myApp" ng-controller="myController">

<button ng-click="count = count + 1">点我!</button>

<p>{{ count }}</p>

</div>

尝试一下 »


隐藏 HTML 元素

ng-hide 指令用于设置应用的一部分 不可见

ng-hide="true" 让 HTML 元素 不可见

ng-hide="false" 让元素可见。

AngularJS 实例

<div ng-app="myApp" ng-controller="personController">

<button ng-click="toggle()">隐藏/显示</button>

<p ng-hide="myVar">
名: <input type="text" ng-model="person.firstName"><br>
姓: <input type="text" ng-model="person.lastName"><br>
<br>
姓名: {{person.firstName + " " + person.lastName}}
</p>

</div>

<script>
function personController($scope) {
    $scope.person = {
        firstName: "John",
        lastName: "Doe"
    };
    $scope.myVar = false;
    $scope.toggle = function() {
        $scope.myVar = !$scope.myVar;
    };
}
</script>

尝试一下 »

应用解析:

personController的第一部分与控制器章节类似。

应用有一个默认属性: $scope.myVar = false;

ng-hide 指令设置应用中的元素不可见。

toggle() 函数用于切换 myVar 变量的值(true 和 false)。

ng-hide="true" 让元素 不可见


显示 HTML 元素

ng-show 指令可用于设置应用中的一部分可见

ng-show="false" 可以设置 HTML 元素 不可见

ng-show="true" 可以以设置 HTML 元素可见。

以下实例使用了 ng-show 指令:

AngularJS 实例

<div ng-app="myApp" ng-controller="personController">

<button ng-click="toggle()">隐藏/显示</button>

<p ng-show="myVar">
名: <input type="text" ng-model="person.firstName"><br>
姓: <input type="text" ng-model="person.lastName"><br>
<br>
姓名: {{person.firstName + " " + person.lastName}}
</p>

</div>

<script>
function personController($scope) {
    $scope.person = {
        firstName: "John",
        lastName: "Doe"
    };
    $scope.myVar = true;
    $scope.toggle = function() {
        $scope.myVar = !$scope.myVar;
    };
}
</script>

尝试一下 »

AngularJS 模块

本节介绍了如何创建 AngularJS 模块以及 AngularJS 模块的相关运用。

模块定义了一个应用程序。

模块是应用程序中不同部分的容器。

模块是应用控制器的容器。

控制器通常属于一个模块。


创建模块

你可以通过 AngularJS 的 angular.module 函数来创建模块:

<div ng-app="myApp">...</div>

<script>

var app = angular.module("myApp", []);

</script>

"myApp" 参数对应执行应用的 HTML 元素

现在你可以在 AngularJS 应用中添加控制器,指令,过滤器等。


添加控制器

你可以使用 ng-controller 指令来添加应用的控制器:

AngularJS 实例

<div ng-app="myApp" ng-controller="myCtrl">
{{ firstName + " " + lastName }}
</div>

<script>

var app = angular.module("myApp", []);

app.controller("myCtrl", function($scope) {
    $scope.firstName = "John";
    $scope.lastName = "Doe";
});

</script>

尝试一下 »

你可以在 AngularJS 控制器 章节学到更多关于控制器的知识。


添加指令

AngularJS 提供了很多内置的指令,你可以使用它们来为你的应用添加功能。

完整的指令内容可以参阅 AngularJS 参考手册

此外,你可以使用模块来为你应用添加自己的指令:

AngularJS 实例

<div ng-app="myApp" 51coolma-directive></div>

<script>

var app = angular.module("myApp", []);

app.directive("51coolmaDirective", function() {
    return {
        template : "我在指令构造器中创建!"
    };
});
</script>

尝试一下 »

你可以在 AngularJS 指令 章节学到更多关于指令的知识。


模块和控制器包含在 JS 文件中

通常 AngularJS 应用程序将模块和控制器包含在 JavaScript 文件中。

在以下实例中, "myApp.js" 包含了应用模块的定义程序, "myCtrl.js" 文件包含了控制器:

AngularJS 实例

<!DOCTYPE html>
<html>
<script src="http://apps.bdimg.com/libs/angular.js/1.4.6/angular.min.js"></script>
<body>

<div ng-app="myApp" ng-controller="myCtrl">
{{ firstName + " " + lastName }}
</div>

<script src="myApp.js"></script>
<script src="myCtrl.js"></script>

</body>
</html>

尝试一下 »

myApp.js

var app = angular.module("myApp", []);

在模块定义中 [] 参数用于定义模块的依赖关系。
中括号[]表示该模块没有依赖,如果有依赖的话会在中括号写上依赖的模块名字。

myCtrl.js

app.controller("myCtrl", function($scope) {
    $scope.firstName = "John";
    $scope.lastName= "Doe";
});

函数会影响到全局命名空间

JavaScript 中应避免使用全局函数。因为他们很容易被其他脚本文件覆盖。

AngularJS 模块让所有函数的作用域在该模块下,避免了该问题。


什么时候载入库?

在我们的实例中,所有 AngularJS 库都在 HTML 文档的头部载入。

对于 HTML 应用程序,通常建议把所有的脚本都放置在 <body> 元素的最底部。

这会提高网页加载速度,因为 HTML 加载不受制于脚本加载。

在我们的多个 AngularJS 实例中,您将看到 AngularJS 库是在文档的 <head> 区域被加载。

在我们的实例中,AngularJS 在 <head> 元素中被加载,因为对 angular.module 的调用只能在库加载完成后才能进行。

另一个解决方案是在 <body> 元素中加载 AngularJS 库,但是必须放置在您的 AngularJS 脚本前面:

AngularJS 实例

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<script src="http://apps.bdimg.com/libs/angular.js/1.4.6/angular.min.js"></script>
</head>
<body>

<div ng-app="myApp" ng-controller="myCtrl">
{{ firstName + " " + lastName }}
</div>
<script>
var app = angular.module("myApp", []);
app.controller("myCtrl", function($scope) {
    $scope.firstName = "John";
    $scope.lastName = "Doe";
});
</script>

</body>
</html>

尝试一下 »

AngularJS 表单


AngularJS 表单是输入控件的集合。


HTML 控件

以下 HTML input 元素被称为 HTML 控件:

  • input 元素
  • select 元素
  • button 元素
  • textarea 元素

HTML 表单

HTML 表单通常与 HTML 控件同时存在。


AngularJS 表单实例

function ExampleController($scope) { $scope.master = {"firstName":"John","lastName":"Doe"}; $scope.reset = function() { $scope.user = angular.copy($scope.master); }; $scope.reset();};

First Name:

Last Name:


form = {{user}}

master = {{master}}


应用程序代码

<div ng-app="" ng-controller="formController">
  <form novalidate>
    First Name:<br>
    <input type="text" ng-model="user.firstName"><br>
    Last Name:<br>
    <input type="text" ng-model="user.lastName">
    <br><br>
    <button ng-click="reset()">RESET</button>
  </form>
  <p>form = {{user}}</p>
  <p>master = {{master}}</p>
</div>

<script>
function formController ($scope) {
    $scope.master = {firstName: "John", lastName: "Doe"};
    $scope.reset = function() {
        $scope.user = angular.copy($scope.master);
    };
    $scope.reset();
};
</script>

尝试一下 »

Note HTML 属性 novalidate 用于禁用浏览器的默认验证。

实例解析

AngularJS ng-model 指令用于绑定 input 元素到模型中。

模型对象 master 的值为 {"firstName" : "John", "lastName" : "Doe"}。

模型函数 reset 设置了模型对象 user 等于 master。

相关文章

HTML 表单

AngularJS 输入验证

AngularJS 表单和控件提供了验证功能,对与用户的输入进行判断,以辨别输入是否合法,如果不合法则进行警告。

AngularJS 表单和控件可以验证输入的数据。


输入验证

在前面的几个章节中,你已经学到关于 AngularJS 表单和控件的知识。

AngularJS 表单和控件可以提供验证功能,并对用户输入的非法数据进行警告。

Note 客户端的验证不能确保用户输入数据的安全,所以服务端的数据验证也是必须的。

应用代码

<!DOCTYPE html>
<html>

<body>
<h2>Validation Example</h2>

<form  ng-app=""  ng-controller="validateCtrl"
name="myForm" novalidate>

<p>Username:<br>
  <input type="text" name="user" ng-model="user" required>
  <span style="color:red" ng-show="myForm.user.$dirty && myForm.user.$invalid">
  <span ng-show="myForm.user.$error.required">Username is required.</span>
  </span>
</p>

<p>Email:<br>
  <input type="email" name="email" ng-model="email" required>
  <span style="color:red" ng-show="myForm.email.$dirty && myForm.email.$invalid">
  <span ng-show="myForm.email.$error.required">Email is required.</span>
  <span ng-show="myForm.email.$error.email">Invalid email address.</span>
  </span>
</p>

<p>
  <input type="submit"
  ng-disabled="myForm.user.$dirty && myForm.user.$invalid ||
  myForm.email.$dirty && myForm.email.$invalid">
</p>

</form>

<script src="//apps.bdimg.com/libs/angular.js/1.2.15/angular.min.js" rel="external nofollow" ></script>
<script>
function validateCtrl($scope) {
    $scope.user = 'John Doe';
    $scope.email = 'john.doe@gmail.com';
}
</script>

</body>
</html>

尝试一下 »
Note HTML 表单属性 novalidate 用于禁用浏览器默认的验证。

实例解析

AngularJS ng-model 指令用于绑定输入元素到模型中。

模型对象有两个属性: useremail

我们使用了 ng-show指令, color:red 在邮件是 $dirty$invalid 才显示。

相关阅读

HTML 表单验证

AngularJS API

本节为你介绍了 AngularJS 中一些通用的 API。

API 意为 Application Programming Interface(应用程序编程接口)。


AngularJS 全局 API

AngularJS 全局 API 用于执行常见任务的 JavaScript 函数集合,如:

  • 比较对象
  • 迭代对象
  • 转换对象

全局 API 函数使用 angular 对象进行访问。

以下列出了一些通用的 API 函数:

API描述
angular.lowercase()转换字符串为小写
angular.uppercase()转换字符串为大写
angular.isString()判断给定的对象是否为字符串,如果是返回 true。
angular.isNumber()判断给定的对象是否为数字,如果是返回 true。

angular.lowercase()

实例

<div ng-app="myApp" ng-controller="myCtrl">
<p>{{ x1 }}</p>
<p>{{ x2 }}</p>
</div>

<script>
var app = angular.module('myApp', []);
app.controller('myCtrl', function($scope) {
$scope.x1 = "JOHN";
$scope.x2 = angular.lowercase($scope.x1);
});
</script>

尝试一下 »

angular.uppercase()

实例

<div ng-app="myApp" ng-controller="myCtrl">
<p>{{ x1 }}</p>
<p>{{ x2 }}</p>
</div>

<script>
var app = angular.module('myApp', []);
app.controller('myCtrl', function($scope) {
$scope.x1 = "John";
$scope.x2 = angular.uppercase($scope.x1);
});
</script>

尝试一下 »

angular.isString()

实例

<div ng-app="myApp" ng-controller="myCtrl">
<p>{{ x1 }}</p>
<p>{{ x2 }}</p>
</div>

<script>
var app = angular.module('myApp', []);
app.controller('myCtrl', function($scope) {
$scope.x1 = "JOHN";
$scope.x2 = angular.isString($scope.x1);
});
</script>

尝试一下 »

angular.isNumber()

实例

<div ng-app="myApp" ng-controller="myCtrl">
<p>{{ x1 }}</p>
<p>{{ x2 }}</p>
</div>

<script>
var app = angular.module('myApp', []);
app.controller('myCtrl', function($scope) {
$scope.x1 = "JOHN";
$scope.x2 = angular.isNumber($scope.x1);
});
</script>

尝试一下 »

AngularJS Bootstrap

本节介绍了如何在你的 AngularJS 中使用 Bootstrap。

Bootstrap 中包含了大量的 Web 组件,根据这些组件,可以快速的搭建一个漂亮、功能完备的网站。


AngularJS 的首选样式表是 Twitter Bootstrap, Twitter Bootstrap 是目前最受欢迎的前端框架。

查看 Bootstrap教程


Bootstrap

你可以在你的 AngularJS 应用中加入 Twitter Bootstrap,你可以在你的 <head> 元素中添加如下代码:

<link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap.min.css" rel="external nofollow" target="_blank" >

如果站点在国内,建议使用百度静态资源库的Bootstrap,代码如下:

<link rel="stylesheet" href="//apps.bdimg.com/libs/bootstrap/3.2.0/css/bootstrap.min.css" rel="external nofollow" target="_blank" >

以下是一个完整的 HTML 实例, 使用了 AngularJS 指令和 Bootstrap 类。


HTML 代码

<!DOCTYPE html>
<html ang-app="">
<head>
<link rel="stylesheet" href="http://apps.bdimg.com/libs/bootstrap/3.2.0/css/bootstrap.min.css" rel="external nofollow" target="_blank" >
</head>

<body ng-controller="userController">
<div class="container">

<h3>Users</h3>

<table class="table table-striped">
  <thead><tr>
    <th>Edit</th>
    <th>First Name</th>
    <th>Last Name</th>
  </tr></thead>
  <tbody><tr ng-repeat="user in users">
    <td>
      <button class="btn" ng-click="editUser(user.id)">
      <span class="glyphicon glyphicon-pencil"></span>  Edit
      </button>
    </td>
    <td>{{ user.fName }}</td>
    <td>{{ user.lName }}</td>
  </tr></tbody>
</table>

<hr>
<button class="btn btn-success" ng-click="editUser('new')">
  <span class="glyphicon glyphicon-user"></span> Create New User
</button>
<hr>

<h3 ng-show="edit">Create New User:</h3>
<h3 ng-hide="edit">Edit User:</h3>

<form class="form-horizontal">
<div class="form-group">
  <label class="col-sm-2 control-label">First Name:</label>
  <div class="col-sm-10">
    <input type="text" ng-model="fName" ng-disabled="!edit" placeholder="First Name">
  </div>
</div>
<div class="form-group">
  <label class="col-sm-2 control-label">Last Name:</label>
  <div class="col-sm-10">
    <input type="text" ng-model="lName" ng-disabled="!edit" placeholder="Last Name">
  </div>
</div>
<div class="form-group">
  <label class="col-sm-2 control-label">Password:</label>
  <div class="col-sm-10">
    <input type="password" ng-model="passw1" placeholder="Password">
  </div>
</div>
<div class="form-group">
  <label class="col-sm-2 control-label">Repeat:</label>
  <div class="col-sm-10">
    <input type="password" ng-model="passw2" placeholder="Repeat Password">
  </div>
</div>
</form>

<hr>
<button class="btn btn-success" ng-disabled="error || incomplete">
  <span class="glyphicon glyphicon-save"></span> Save Changes
</button>
</div>

<script src = "http://apps.bdimg.com/libs/angular.js/1.2.15/angular.min.js"></script>
<script src = "myUsers.js"></script>
</body>
</html>>

尝试一下 »


指令解析

AngularJS 指令 描述
<html ng-app 为 <html> 元素定义一个应用(未命名)
<body ng-controller 为 <body> 元素定义一个控制器
<tr ng-repeat 循环 users 对象数组,每个 user 对象放在 <tr> 元素中。
<button ng-click 当点击 <button> 元素时调用函数 editUser()
<h3 ng-show 如果 edit = true 显示 <h3> 元素
<h3 ng-hide 如果 edit = true 隐藏 <h3> 元素
<input ng-model 为应用程序绑定 <input> 元素
<button ng-disabled 如果发生错误或者 ncomplete = true 禁用 <button> 元素


Bootstrap 类解析

元素 Bootstrap 类 定义
<div> container 内容容器
<table> table 表格
<table> table-striped 带条纹背景的表格
<button> btn 按钮
<button> btn-success 成功按钮
<span> glyphicon 字形图标
<span> glyphicon-pencil 铅笔图标
<span> glyphicon-user 用户图标
<span> glyphicon-save 保存图标
<form> form-horizontal 水平表格
<div> form-group 表单组
<label> control-label 控制器标签
<label> col-sm-2 跨越 2 列
<div> col-sm-10 跨越 10 列


JavaScript 代码

function userController($scope) {
$scope.fName = '';
$scope.lName = '';
$scope.passw1 = '';
$scope.passw2 = '';
$scope.users = [
{id:1, fName:'Hege',  lName:"Pege" },
{id:2, fName:'Kim',   lName:"Pim" },
{id:3, fName:'Sal',   lName:"Smith" },
{id:4, fName:'Jack',  lName:"Jones" },
{id:5, fName:'John',  lName:"Doe" },
{id:6, fName:'Peter', lName:"Pan" }
];
$scope.edit = true;
$scope.error = false;
$scope.incomplete = false;

$scope.editUser = function(id) {
  if (id == 'new') {
    $scope.edit = true;
    $scope.incomplete = true;
    $scope.fName = '';
    $scope.lName = '';
    } else {
    $scope.edit = false;
    $scope.fName = $scope.users[id-1].fName;
    $scope.lName = $scope.users[id-1].lName;
  }
};

$scope.$watch('passw1',function() {$scope.test();});
$scope.$watch('passw2',function() {$scope.test();});
$scope.$watch('fName', function() {$scope.test();});
$scope.$watch('lName', function() {$scope.test();});

$scope.test = function() {
  if ($scope.passw1 !== $scope.passw2) {
    $scope.error = true;
    } else {
    $scope.error = false;
  }
  $scope.incomplete = false;
  if ($scope.edit && (!$scope.fName.length ||
  !$scope.lName.length ||
  !$scope.passw1.length || !$scope.passw2.length)) {
       $scope.incomplete = true;
  }
};

}


JavaScript 代码解析

Scope 属性 用途
$scope.fName 模型变量 (用户名)
$scope.lName 模型变量 (用户姓)
$scope.passw1 模型变量 (用户密码 1)
$scope.passw2 模型变量 (用户密码 2)
$scope.users 模型变量 (用户的数组)
$scope.edit 当用户点击创建用户时设置为true。
$scope.error 如果 passw1 不等于 passw2 设置为 true
$scope.incomplete 如果每个字段都为空(length = 0)设置为 true
$scope.editUser 设置模型变量
$scope.watch 监控模型变量
$scope.test 验证模型变量的错误和完整性

AngularJS Include(包含)

本节介绍了 AngularJS Include(包含) 的知识,你将学习如何使用 ng-include 指令。

使用 AngularJS, 你可以在 HTML 中包含 HTML 文件。


在未来的HTML中包含 HTML 文件

在 HTML 中,目前还不支持包含 HTML 文件的功能。

W3C 已经建议 http://www.w3.org 在未来的 HTML 中支持包含HTML的功能,格式如下:

<link rel="import" href="/path/navigation.html">


服务端包含

大部分web服务器支持服务端脚本的包含 (SSI:Server Side Includes)。

使用 SSI, 你可以在HTML页面发送至浏览器前包含 HTML。

PHP 实例

<?php require("navigation.php"); ?>


客户端包含

客户端在 HTML 中使用 JavaScript 有多种方式可以包含 HTML 文件。

通常我们使用 http 请求 (AJAX) 从服务端获取数据,返回的数据我们可以通过 使用 innerHTML 写入到 HTML 元素中。


AngularJS 包含

使用 AngularJS, 你可以使用 ng-include 指令来包含 HTML 内容:

实例

<body>

<div class="container">
  <div ng-include="'myUsers_List.htm'"></div>
  <div ng-include="'myUsers_Form.htm'"></div>
</div>

</body>

尝试一下 »

步骤如下。


步骤 1: 创建 HTML 列表

myUsers_List.html

<table class="table table-striped">
  <thead><tr>
    <th>Edit</th>
    <th>First Name</th>
    <th>Last Name</th>
  </tr></thead>
  <tbody><tr ng-repeat="user in users">
    <td>
      <button class="btn" ng-click="editUser(user.id)">
        <span class="glyphicon glyphicon-pencil"></span>  Edit
      </button>
    </td>
    <td>{{ user.fName }}</td>
    <td>{{ user.lName }}</td>
  </tr></tbody>
</table>

尝试一下 »


步骤 2: 创建 HTML 表单

myUsers_List.html

<button class="btn btn-success" ng-click="editUser('new')">
  <span class="glyphicon glyphicon-user"></span> Create New User
</button>
<hr>

<h3 ng-show="edit">Create New User:</h3>
<h3 ng-hide="edit">Edit User:</h3>

<form class="form-horizontal">
<div class="form-group">
  <label class="col-sm-2 control-label">First Name:</label>
  <div class="col-sm-10">
    <input type="text" ng-model="fName" ng-disabled="!edit" placeholder="First Name">
  </div>
</div>
<div class="form-group">
  <label class="col-sm-2 control-label">Last Name:</label>
  <div class="col-sm-10">
    <input type="text" ng-model="lName" ng-disabled="!edit" placeholder="Last Name">
  </div>
</div>
<div class="form-group">
  <label class="col-sm-2 control-label">Password:</label>
  <div class="col-sm-10">
    <input type="password" ng-model="passw1" placeholder="Password">
  </div>
</div>
<div class="form-group">
  <label class="col-sm-2 control-label">Repeat:</label>
  <div class="col-sm-10">
    <input type="password" ng-model="passw2" placeholder="Repeat Password">
  </div>
</div>
</form>

<hr>
<button class="btn btn-success" ng-disabled="error || incomplete">
  <span class="glyphicon glyphicon-save"></span> Save Changes
</button>

尝试一下 »


步骤 3: 创建主页

myUsers.html

<!DOCTYPE html>
<html ng-app="">
<head>
<link rel="stylesheet" href = "http://apps.bdimg.com/libs/bootstrap/3.2.0/css/bootstrap.min.css">
</head>

<body ng-controller="userController">

<div class="container">
<div ng-include="'myUsers_List.htm'"></div>
<div ng-include="'myUsers_Form.htm'"></div>
</div>

<script src= "http://apps.bdimg.com/libs/angular.js/1.2.15/angular.min.js"></script>
<script src= "myUsers.js"></script>

</body>
</html>

尝试一下 »

AngularJS 动画

AngularJS 提供了动画效果,可以配合 CSS 使用。

AngularJS 使用动画需要引入 angular-animate.min.js 库。

<script src="http://apps.bdimg.com/libs/angular.js/1.4.6/angular-animate.min.js" rel="external nofollow" ></script>

还需在应用中使用模型 ngAnimate:

<body ng-app="ngAnimate">

什么是动画?

动画是通过改变 HTML 元素产生的动态变化效果。

实例

勾选复选框隐藏 DIV:

<body ng-app="ngAnimate">

隐藏 DIV: <input type="checkbox" ng-model="myCheck">

<div ng-hide="myCheck"></div>

</body>

尝试一下 »
Note应用中动画不宜太多,但合适的使用动画可以增加页面的丰富性,也可以更易让用户理解。

如果我们应用已经设置了应用名,可以把 ngAnimate 直接添加在模型中:

实例

<body ng-app="myApp">

<h1>隐藏 DIV: <input type="checkbox" ng-model="myCheck"></h1>

<div ng-hide="myCheck"></div>

<script>
var app = angular.module('myApp', ['ngAnimate']);
</script>

尝试一下 »

ngAnimate 做了什么?

ngAnimate 模型可以添加或移除 class 。

ngAnimate 模型并不能使 HTML 元素产生动画,但是 ngAnimate 会监测事件,类似隐藏显示 HTML 元素 ,如果事件发生 ngAnimate 就会使用预定义的 class 来设置 HTML 元素的动画。

AngularJS 添加/移除 class 的指令:

  • ng-show
  • ng-hide
  • ng-class
  • ng-view
  • ng-include
  • ng-repeat
  • ng-if
  • ng-switch

ng-showng-hide 指令用于添加或移除 ng-hide class 的值。

其他指令会在进入 DOM 会添加 ng-enter 类,移除 DOM 会添加 ng-leave 属性。

当 HTML 元素位置改变时,ng-repeat 指令同样可以添加 ng-move 类 。

此外, 在动画完成后,HTML 元素的类集合将被移除。例如:ng-hide 指令会添加一下类:

  • ng-animate
  • ng-hide-animate
  • ng-hide-add (如果元素将被隐藏)
  • ng-hide-remove (如果元素将显示)
  • ng-hide-add-active (如果元素将隐藏)
  • ng-hide-remove-active (如果元素将显示)

使用 CSS 动画

我们可以使用 CSS transition(过渡) 或 CSS 动画让 HTML 元素产生动画效果,该部分内容你可以参阅我们的CSS 过渡教程CSS 动画教程


CSS 过渡

CSS 过渡可以让我们平滑的将一个 CSS 属性值修改为另外一个:

实例

在 DIV 元素设置了 .ng-hide 类时,过渡需要花费 0.5 秒,高度从 100px 变为 0:

<style>
div {
    transition: all linear 0.5s;
    background-color: lightblue;
    height: 100px;
}
.ng-hide {
    height: 0;
}
</style>

尝试一下 »

CSS 动画

CSS 动画允许你平滑的修改 CSS 属性值:

实例

在 DIV 元素设置了 .ng-hide 类时, myChange 动画将执行,它会平滑的将高度从 100px 变为 0:

<style>
@keyframes myChange {
    from {
        height: 100px;
    } to {
        height: 0;
    }
}
div {
    height: 100px;
    background-color: lightblue;
}
div.ng-hide {
    animation: 0.5s myChange;
}
</style>

尝试一下 »

相关教程

CSS教程

AngularJS 依赖注入

依赖注入是 AngularJS 的重要特性之一,它简化了 Angular 解析模块/组件之间依赖的过程。


什么是依赖注入

wiki 上的解释是:依赖注入(Dependency Injection,简称DI)是一种软件设计模式,在这种模式下,一个或更多的依赖(或服务)被注入(或者通过引用传递)到一个独立的对象(或客户端)中,然后成为了该客户端状态的一部分。

该模式分离了客户端依赖本身行为的创建,这使得程序设计变得松耦合,并遵循了依赖反转和单一职责原则。与服务定位器模式形成直接对比的是,它允许客户端了解客户端如何使用该系统找到依赖

一句话 --- 没事你不要来找我,有事我会去找你。

AngularJS 提供很好的依赖注入机制。以下5个核心组件用来作为依赖注入:

  • value
  • factory
  • service
  • provider
  • constant

value

Value 是一个简单的 javascript 对象,用于向控制器传递值(配置阶段):

// 定义一个模块var mainApp = angular.module("mainApp", []);// 创建 value 对象 "defaultInput" 并传递数据mainApp.value("defaultInput", 5);...// 将 "defaultInput" 注入到控制器mainApp.controller('CalcController', function($scope, CalcService, defaultInput) {   $scope.number = defaultInput;   $scope.result = CalcService.square($scope.number);      $scope.square = function() {      $scope.result = CalcService.square($scope.number);   }});

factory

factory 是一个函数用于返回值。在 service 和 controller 需要时创建。

通常我们使用 factory 函数来计算或返回值。

// 定义一个模块var mainApp = angular.module("mainApp", []);// 创建 factory "MathService" 用于两数的乘积 provides a method multiply to return multiplication of two numbersmainApp.factory('MathService', function() {   var factory = {};      factory.multiply = function(a, b) {      return a * b   }   return factory;}); // 在 service 中注入 factory "MathService"mainApp.service('CalcService', function(MathService){   this.square = function(a) {      return MathService.multiply(a,a);   }});...

provider

AngularJS 中通过 provider 创建一个 service、factory等(配置阶段)。

Provider 中提供了一个 factory 方法 get(),它用于返回 value/service/factory。

// 定义一个模块var mainApp = angular.module("mainApp", []);...// 使用 provider 创建 service 定义一个方法用于计算两数乘积mainApp.config(function($provide) {   $provide.provider('MathService', function() {      this.$get = function() {         var factory = {};                    factory.multiply = function(a, b) {            return a * b;          }         return factory;      };   });});

constant

constant(常量)用来在配置阶段传递数值,注意这个常量在配置阶段是不可用的。

mainApp.constant("configParam", "constant value");

实例

以下实例提供了以上几个依赖注入机制的演示。

<html>      <head>      <meta charset="utf-8">      <title>AngularJS  依赖注入</title>   </head>      <body>      <h2>AngularJS 简单应用</h2>            <div ng-app = "mainApp" ng-controller = "CalcController">         <p>输入一个数字: <input type = "number" ng-model = "number" /></p>         <button ng-click = "square()">X<sup>2</sup></button>         <p>结果: {{result}}</p>      </div>            <script src="http://apps.bdimg.com/libs/angular.js/1.4.6/angular.min.js" rel="external nofollow" ></script>            <script>         var mainApp = angular.module("mainApp", []);                  mainApp.config(function($provide) {            $provide.provider('MathService', function() {               this.$get = function() {                  var factory = {};                                    factory.multiply = function(a, b) {                     return a * b;                  }                  return factory;               };            });         });			         mainApp.value("defaultInput", 5);                  mainApp.factory('MathService', function() {            var factory = {};                        factory.multiply = function(a, b) {               return a * b;            }            return factory;         });                  mainApp.service('CalcService', function(MathService){            this.square = function(a) {               return MathService.multiply(a,a);            }         });                  mainApp.controller('CalcController', function($scope, CalcService, defaultInput) {            $scope.number = defaultInput;            $scope.result = CalcService.square($scope.number);            $scope.square = function() {               $scope.result = CalcService.square($scope.number);            }         });			      </script>         </body></html>

尝试一下 »

AngularJS 路由

本章节我们将为大家介绍 AngularJS 路由。

AngularJS 路由允许我们通过不同的 URL 访问不同的内容。

通过 AngularJS 可以实现多视图的单页Web应用(single page web application,SPA)。

通常我们的URL形式为 http://51coolma.cn/first/page,但在单页Web应用中 AngularJS 通过 # + 标记 实现,例如:

http://51coolma.cn/#/firsthttp://51coolma.cn/#/secondhttp://51coolma.cn/#/third

当我们点击以上的任意一个链接时,向服务端请的地址都是一样的 (http://51coolma.cn/)。 因为 # 号之后的内容在向服务端请求时会被浏览器忽略掉。 所以我们就需要在客户端实现 # 号后面内容的功能实现。 AngularJS 路由 就通过 # + 标记 帮助我们区分不同的逻辑页面并将不同的页面绑定到对应的控制器上。


在以上图形中,我们可以看到创建了两个 URL: /ShowOrders 和 /AddNewOrder。每个 URL 都有对应的视图和控制器。

接下来我们来看一个简单的实例:

<html>    <head>    	<meta charset="utf-8">        <title>AngularJS 路由实例 - W3Cschool教程</title>    </head>    <body ng-app='routingDemoApp'>             <h2>AngularJS 路由应用</h2>        <ul>            <li><a href="#/">首页</a></li>            <li><a href="#/computers">电脑</a></li>            <li><a href="#/printers">打印机</a></li>            <li><a href="#/blabla">其他</a></li>        </ul>                 <div ng-view></div>        <script src="http://apps.bdimg.com/libs/angular.js/1.4.6/angular.min.js" rel="external nofollow"  rel="external nofollow" ></script>        <script src="http://apps.bdimg.com/libs/angular-route/1.3.13/angular-route.js" rel="external nofollow"  rel="external nofollow" ></script>        <script>            angular.module('routingDemoApp',['ngRoute'])            .config(['$routeProvider', function($routeProvider){                $routeProvider                .when('/',{template:'这是首页页面'})                .when('/computers',{template:'这是电脑分类页面'})                .when('/printers',{template:'这是打印机页面'})                .otherwise({redirectTo:'/'});            }]);        </script>         </body></html>

尝试一下 »

实例解析:

  • 1、载入了实现路由的 js 文件:angular-route.js。

  • 2、包含了 ngRoute 模块作为主应用模块的依赖模块。

    angular.module('routingDemoApp',['ngRoute'])
  • 3、使用 ngView 指令。

    <div ng-view></div>

    该 div 内的 HTML 内容会根据路由的变化而变化。

  • 配置 $routeProvider,AngularJS $routeProvider 用来定义路由规则。

    module.config(['$routeProvider', function($routeProvider){    $routeProvider        .when('/',{template:'这是首页页面'})        .when('/computers',{template:'这是电脑分类页面'})        .when('/printers',{template:'这是打印机页面'})        .otherwise({redirectTo:'/'});}]);

    AngularJS 模块的 config 函数用于配置路由规则。通过使用 configAPI,我们请求把$routeProvider注入到我们的配置函数并且使用$routeProvider.whenAPI来定义我们的路由规则。

    $routeProvider 为我们提供了 when(path,object) & otherwise(object) 函数按顺序定义所有路由,函数包含两个参数:

    • 第一个参数是 URL 或者 URL 正则规则。
    • 第二个参数是路由配置对象。

  • 路由设置对象

    AngularJS 路由也可以通过不同的模板来实现。

    $routeProvider.when 函数的第一个参数是 URL 或者 URL 正则规则,第二个参数为路由配置对象。

    路由配置对象语法规则如下:

$routeProvider.when(url, {    template: string,    templateUrl: string,    controller: string, function 或 array,    controllerAs: string,    redirectTo: string, function,    resolve: object<key, function>});

参数说明:

  • template:

    如果我们只需要在 ng-view 中插入简单的 HTML 内容,则使用该参数:

    .when('/computers',{template:'这是电脑分类页面'})
  • templateUrl:

    如果我们只需要在 ng-view 中插入 HTML 模板文件,则使用该参数:

    $routeProvider.when('/computers', {    templateUrl: 'views/computers.html',});

    以上代码会从服务端获取 views/computers.html 文件内容插入到 ng-view 中。

  • controller:

    function、string或数组类型,在当前模板上执行的controller函数,生成新的scope。

  • controllerAs:

    string类型,为controller指定别名。

  • redirectTo:

    重定向的地址。

  • resolve:

    指定当前controller所依赖的其他模块。

  • 实例

    <html><head><meta http-equiv="content-type" content="text/html; charset=UTF-8"><script src="http://apps.bdimg.com/libs/angular.js/1.4.6/angular.min.js" rel="external nofollow"  rel="external nofollow" ></script><script src="http://apps.bdimg.com/libs/angular-route/1.3.13/angular-route.js" rel="external nofollow"  rel="external nofollow" ></script><script type="text/javascript">angular.module('ngRouteExample', ['ngRoute']).controller('HomeController', function ($scope) { $scope.$route = $route;}).controller('AboutController', function ($scope) { $scope.$route = $route;}).config(function ($routeProvider) {    $routeProvider.    when('/home', {        templateUrl: 'embedded.home.html',        controller: 'HomeController'    }).    when('/about', {        templateUrl: 'embedded.about.html',        controller: 'AboutController'    }).    otherwise({        redirectTo: '/home'    });});</script>  </head><body ng-app="ngRouteExample" class="ng-scope">  <script type="text/ng-template" id="embedded.home.html">      <h1> Home </h1>  </script>  <script type="text/ng-template" id="embedded.about.html">      <h1> About </h1>  </script>  <div>     <div id="navigation">        <a href="#/home">Home</a>      <a href="#/about">About</a>    </div>          <div ng-view="">    </div>  </div></body></html>

    尝试一下 »

AngularJS 应用程序


现在是时候创建一个真正的 AngularJS 应用程序了。

你可以通过本节的 AngularJS 应用程序来熟悉 AngularJS 的使用。


AngularJS 应用程序

您已经学习了足够多关于 AngularJS 的知识,现在可以开始创建您的第一个 AngularJS 应用程序:

我的笔记



剩下的字符数:



应用程序讲解

AngularJS 实例

<html ng-app="myNoteApp">
<head>
<meta charset="utf-8">
<script src="http://apps.bdimg.com/libs/angular.js/1.4.6/angular.min.js"></script>
</head>
<body>

<div ng-controller="myNoteCtrl">

<h2>我的笔记</h2>

<p><textarea ng-model="message" cols="40" rows="10"></textarea></p>

<p>
<button ng-click="save()">保存</button>
<button ng-click="clear()">清除</button>
</p>

<p>Number of characters left: <span ng-bind="left()"></span></p>

</div>

<script src="myNoteApp.js"></script>
<script src="myNoteCtrl.js"></script>

</body>
</html>

尝试一下 »

应用程序文件 "myTodoApp.js":

var app = angular.module("myTodoApp", []);

控制器文件 "myTodoCtrl.js":

app.controller("myTodoCtrl", function($scope) {
    $scope.message = "";
    $scope.left  = function() {return 100 - $scope.message.length;};
    $scope.clear = function() {$scope.message="";};
    $scope.save  = function() {$scope.message="";};
});

HTML 页面中的一个 <div>,指向 ng-app="myTodoApp" 和 ng-controller="myTodoCtrl":

<div ng-app="myTodoApp" ng-controller="myTodoCtrl">

一个 ng-model 指令,绑定一个 <textarea> 到控制器变量 message

<textarea ng-model="message" cols="40" rows="10"></textarea>

两个 ng-click 事件,调用控制器函数 clear()save()

<button ng-click="save()">保存</button>
<button ng-click="clear()">清除</button>

一个 ng-bind 指令,绑定控制器函数 left() 到一个 <span>,字符会向左对齐显示:

剩下的字符数:<span ng-bind="left()"></span>

两个应用程序库被添加到 HTML 页面:

<script src="myTodoApp.js"></script>
<script src="myTodoCtrl.js"></script>

以上就是相关的 AngularJS 应用程序解析了。

AngularJS 实例

本节为你提供了一些 AngularJS 实例,包括基础的 AngularJS 的使用、AngularJS 表达式的使用、AngularJS 指令的使用等等。

尝试一下

您可以在线编辑实例,然后点击按钮查看结果。

AngularJS 实例

<div ng-app="">

<p>Name: <input type="text" ng-model="name"></p>
<p>You wrote: {{ name }}</p>

</div>

尝试一下 »


AngularJS 基础

我的第一个 AngularJS 表达式
我的第一个 AngularJS 指令
我的第一个 AngularJS 指令(带有有效的 HTML5)

基础的 AngularJS 讲解


AngularJS 表达式

带有数字的表达式
使用带有数字的 ng-bind
带有字符串的表达式
使用带有字符串的 ng-bind
带有对象的表达式
使用带有对象的 ng-bind
带有数组的表达式
使用带有数组的 ng-bind

表达式讲解


AngularJS 指令

AngularJS 指令
ng-model 指令
ng-repeat 指令(带有数组)
ng-repeat 指令(带有对象)

指令讲解


AngularJS 控制器

AngularJS 控制器
控制器属性
控制器函数
JavaScript 文件中的控制器 I
JavaScript 文件中的控制器 II

控制器讲解


AngularJS 过滤器

表达式过滤器 uppercase
表达式过滤器 lowercase
指令过滤器 orderBy
输入过滤器

过滤器讲解


AngularJS HTML DOM

The ng-disabled Directive

HTML DOM 讲解


AngularJS HTML 事件

ng-click 指令
ng-show 指令

HTML 事件讲解


AngularJS 模块

body 中的 AngularJS 模块
文件中的 AngularJS 模块

模块讲解


AngularJS 应用程序

AngularJS 应用程序

应用程序讲解

AngularJS 参考手册

AngularJS 参考手册总结了本教程中所用到的一些 AngularJS 指令以及 AngularJS 过滤器。


AngularJS 指令

本教程中使用的 AngularJS 指令:

指令描述讲解
ng_app定义应用程序的根元素。指令
ng_bind绑定 HTML 元素到应用程序数据。简介
ng_click定义元素被单击时的行为。HTML 事件
ng_controller为应用程序定义控制器对象。控制器
ng_disabled绑定应用程序数据到 HTML 的 disabled 属性。HTML DOM
ng_init为应用程序定义初始值。指令
ng_model绑定应用程序数据到 HTML 元素。指令
ng_repeat为控制器中的每个数据定义一个模板。指令
ng_show显示或隐藏 HTML 元素。HTML DOM


AngularJS 过滤器

本教程中使用的 AngularJS 过滤器:

过滤器描述
currency格式化数字为货币格式。
filter从数组项中选择一个子集。
lowercase格式化字符串为小写。
orderBy根据某个表达式排列数组。
uppercase格式化字符串为大写。

有关过滤器的具体知识在 AngularJS 过滤器 一章中进行讲解。


AngularJS v1.5 简明教程中文版

本文是 樊潇洁 翻译的最新版本(v1.5版)的AngularJS 教程。官方英文版请见AngularJS 官方教程AngularJS 官方站

本人凭兴趣翻译有用的Web开发教程。如发现翻译得有误,请在新浪微博上发消息给我。

本人会在百度阅读和W3Cschool教程维基上陆续发布更多自己翻译的WEb开发教程。在百度阅读上收听我。这就开始学习AngularJS吧!

2016年1月19日

快速入门

为什么要用AngularJS?

HTML非常适合于声明静态的文档,但是当我们试图使用它在web应用程序中声明动态视图时,它显得力不从心。AngularJS能为您的应用程序扩展HTML的词汇。由此产生的环境非常具有有表现力、可读性强、快速发展。

替代选择

其他处理HTML的缺点的框架要么是抽象出HTML、CSS、和/或JavaScript,要么为操纵DOM提供一个必要的方式。它们都不能解决一个根本问题,即HTML不是为动态视图设计的。

可扩展性

AngularJS是用来构建框架的工具集,很适全于你的应用程序开发。它完全可扩展,而且与别的库协作得很好。每个功能可以被修改或替代,以适合你的独一无二的开发工作流以及功能需要。继续阅读以弄懂为何。

The Basics

index.html

<!doctype html><html ng-app>  <head>    <script src="https://atts.51coolma.cn/attachments/image/wk/angularjs/angular.min.js"></script>  </head>  <body>    <div>      <label>Name:</label>      <input type="text" ng-model="yourName" placeholder="Enter a name here">      <hr>      <h1>Hello {{yourName}}!</h1>    </div>  </body></html>

添加一些控件

数据绑定

数据绑定是每当模型改变时更新视图的自动方法,当视图改变时,同样也会更新模型。这非常棒,因为从你需要担心的列表中它减去了DOM操纵。

控件

控件是DOM元素后面的行为。AngularJS让你能够用一个干净可读的形式表达行为,不需要更新DOM的通常样板、注册回调或者观察模型变化。

扁平的JavaScript

与别的框架不同,不需要为包装访问器方法中的模型,而继承私有类型。Angular模型是扁平的旧式JavaScript对象。这使你的代码容易读取、容易维护、可重用,还不需要样板。

index.html

<!doctype html><html ng-app="todoApp">  <head>    <script src="https://atts.51coolma.cn/attachments/image/wk/angularjs/angular.min.js"></script>    <script src="todo.js"></script>    <link rel="stylesheet" href="todo.css">  </head>  <body>    <h2>Todo</h2>    <div ng-controller="TodoListController as todoList">      <span>{{todoList.remaining()}} of {{todoList.todos.length}} remaining</span>      [ <a href="" ng-click="todoList.archive()">archive</a> ]      <ul class="unstyled">        <li ng-repeat="todo in todoList.todos">          <input type="checkbox" ng-model="todo.done">          <span class="done-{{todo.done}}">{{todo.text}}</span>        </li>      </ul>      <form ng-submit="todoList.addTodo()">        <input type="text" ng-model="todoList.todoText"  size="30"               placeholder="add new todo here">        <input class="btn-primary" type="submit" value="add">      </form>    </div>  </body></html>

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 = '';    };    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

<!doctype html><html ng-app="project">  <head>    <script src="/attachments/image/wk/angularjs/angular.min.js"></script>    <script src="/attachments/image/wk/angularjs/angular-resource.min.js">    </script>    <script src="/attachments/image/wk/angularjs/angular-route.min.js">   </script>    <script src="/attachments/image/wk/angularjs/firebase.js"></script>    <script src="/attachments/image/wk/angularjs/angularfire.min.js"></script>    <link rel="stylesheet" href="bootstrap.css">    <script src="project.js"></script>  </head>  <body>    <h2>JavaScript Projects</h2>    <div ng-view></div>  </body></html>

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);      });      //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('/');        });    };    editProject.save = function() {        editProject.projects.$save(editProject.project).then(function(data) {           $location.path('/');        });    };});

list.html

<input type="text" ng-model="projectList.search" class="search-query" id="projects_search"       placeholder="Search"><table>  <thead>  <tr>    <th>Project</th>    <th>Description</th>    <th><a href="#/new"><i class="icon-plus-sign"></i></a></th>  </tr>  </thead>  <tbody>  <tr ng-repeat="project in projectList.projects | filter:projectList.search | orderBy:'name'">    <td><a ng-href="{{project.site}}" target="_blank">{{project.name}}</a></td>    <td>{{project.description}}</td>    <td>      <a ng-href="#/edit/{{project.$id}}"><i class="icon-pencil"></i></a>    </td>  </tr>  </tbody></table>

detail.html

<form name="myForm">  <div class="control-group" ng-class="{error: myForm.name.$invalid && !myForm.name.$pristine}">    <label>Name</label>    <input type="text" name="name" ng-model="editProject.project.name" required>    <span ng-show="myForm.name.$error.required && !myForm.name.$pristine" class="help-inline">        Required {{myForm.name.$pristine}}</span>  </div>  <div class="control-group" ng-class="{error: myForm.site.$invalid && !myForm.site.$pristine}">    <label>Website</label>    <input type="url" name="site" ng-model="editProject.project.site" required>    <span ng-show="myForm.site.$error.required && !myForm.site.$pristine" class="help-inline">        Required</span>    <span ng-show="myForm.site.$error.url" class="help-inline">        Not a URL</span>  </div>  <label>Description</label>  <textarea name="description" ng-model="editProject.project.description"></textarea>  <br>  <a href="#/" class="btn">Cancel</a>  <button ng-click="editProject.save()" ng-disabled="myForm.$invalid"          class="btn btn-primary">Save</button>  <button ng-click="editProject.destroy()"          ng-show="editProject.project.$id" class="btn btn-danger">Delete</button></form>

创建组件

指令

指令是一个独有而且强大的功能,只在Angular中可用。指令使你能够发明新的HTML句法、专针对于你的应用程序。

可重用的组件

我们使用指令以创建可重复使用的组件。组件允许你隐藏复杂的DOM结构、CSS以及行为。这使你能够专注于应用程序要做什么,或者单独的应用程序看起来如何。

本地化

严肃的应用程序的一个重要组成部分是本地化。AngularJS的本地探知筛选器以及阻塞指令使你能够建立屏蔽,使你的应用程序在所有的地方都可用。

index.html

<!doctype html><html ng-app="app">  <head>    <script src="https://atts.51coolma.cn/attachments/image/wk/angularjs/angular.min.js"></script>    <script src="components.js"></script>    <script src="app.js"></script>    <link rel="stylesheet" href="bootstrap.css">  </head>  <body>    <tabs>      <pane title="Localization">        Date: {{ '2012-04-01' | date:'fullDate' }} <br>        Currency: {{ 123456 | currency }} <br>        Number: {{ 98765.4321 | number }} <br>      </pane>      <pane title="Pluralization">        <div ng-controller="BeerCounter">          <div ng-repeat="beerCount in beers">            <ng-pluralize count="beerCount" when="beerForms"></ng-pluralize>          </div>        </div>      </pane>    </tabs>  </body></html>

components.js

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:        '<div class="tabbable">' +          '<ul class="nav nav-tabs">' +            '<li ng-repeat="pane in panes" ng-class="{active:pane.selected}">'+              '<a href="" ng-click="select(pane)">{{pane.title}}</a>' +            '</li>' +          '</ul>' +          '<div class="tab-content" ng-transclude></div>' +        '</div>',      replace: true    };  })  .directive('pane', function() {    return {      require: '^tabs',      restrict: 'E',      transclude: true,      scope: { title: '@' },      link: function(scope, element, attrs, tabsController) {        tabsController.addPane(scope);      },      template:        '<div class="tab-pane" ng-class="{active: selected}" ng-transclude>' +        '</div>',      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'    };  } 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的内部运作机制消除了测试片层分享。

必要准备工作

PhoneCat教程应用程序

AngularJS最好的入方法是跟着教程操作,它带领你经历了一个AngularJS网页应用程序的构建。你将建立的这个应用是一个目录,显示了一个安卓设备的列表,让你能够筛选列表,可以只查看你感兴趣的设备,然后查看任何设备的详情。

运行在浏览器上的演示应用

跟随着这个教程以看到Angular如何让浏览器变得更聪明——不需要使用原生的扩展或者插件:

  • 查看如何使用客户端数据绑定的示例,以建立数据的动态视图,它会响应用户的操作立即改变自己。
  • 查看Angular如何在与你的数据同步的同时,保持你的视图不变,不需要DOM操纵。
  • 学一个更好的、更容易的方法,以测试你的网页应用,利用Karma以及Protractor。
  • 学会如何使用依赖性注入和服务,以制作常见的网页任务,比如说更容易地在应用中获得数据。

当你看完该教程时你将能够:

  • 创建工作在现代浏览器中的动态的应用程序。
  • 使用数据绑定以把你的数据模块连接到你的视图中。
  • 利用Karma创建并运行单元测试。
  • 利用Protractor创建并运行端到端测试。
  • 从模板中移出应用逻辑,移到控件中。
  • 使用Angular服务从服务器端获得数据。
  • 使用ngAnimate把动画应用到你的应用程序中。
  • 识别资源以学习更多关于AngularJS。

本教程将指导你完成建立一个应用程序的整个过程,包括编写并运行单元测试和端到端测试。每一步骤的末尾的实验向你提供了学习更多关于AngularJS的建议,以及你正在建立的应用程序的建议。

你可以在几个时内看完整个教程,或者你可能会想愉快地花一天时间真正深入挖掘它。如果你寻求更短的AngularJS的入门,请仔细阅读起步文档。

起步

本页的剩余部分解释了你可以如何设置你的本地机器用于开发。如果你只是想阅读教程,则你可以直接查看第一步:第一步 引导程序

操作代码

你可以在你自己的电脑上跟随着这个教程、摆弄代码。用这种方法,你可以得到真正书写AngularJS代码的亲手实践,还使用了推荐的测试工具。

该教程取决于为源代码管理器使用了哪个版本的Git。除了安装并运行几段git代码,你不需要知道关于Git的任何东西,只要跟着这个教程。

安装Git

你可以从http://git-scm.com/download下载并安装Git。一旦安装好了,你应该能够访问到git命令行工具。你将需要用到的主要命令是:

  • git clone ... : 把一个远程的知识库克隆到你的本地机器上
  • git checkout ... : 检查一个特定的分支或一个代码的标记版本以破解

下载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

如果你想运行预配置的本地web服务器以及测试工具,则你还需要Node.js v0.10.27+

你可以针对你的操作系统从http://nodejs.org/download/下载一个Node.js安装包。

运行以下的命令行,检查你已经安装的Node.js的版本:

node --version

在基于Debian的发行版中,与别的实用工具有一个名称冲突,它称为node。建议的解决方案是再安装nodejs-legacy apt 安装包,它会把node重命名为nodejs

apt-get install nodejs-legacy npmnodejs --versionnpm --version
如果你需要在你的本地环境中运行Node.js的不同版本,请考虑安装Node版本管理器(nvm)

一旦你已经在你的机器上安装了Node.js,你可以依靠运行以下代码下载该工具。

npm install

这个命令读取了angular-phonecat的package.json文件,并把以下工具下载到node_modules目录中:

运行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命令行,你只需要输入以下指令:

sudo npm install -g bower

(Omit the sudo if running on Windows)

然后你可以直接运行该bower工具了,如下:

bower install

运行开发Web服务器

虽然Angular应用程序是纯客户端代码,而且能够直接从文件系统中,在web浏览器中打开它们,但是最好从一个HTTP web服务器中供应它们。特别是,为了安全原因,如果网页直接从文件系统中加载,很多现代浏览器不允许JavaScript发起服务器请求。

为了在开发期间托管应用程序,用一个简单的静态的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。
  • 在该浏览器中执行所有的单元测试
  • 报告在终端/命令行窗口中的那些测试的结果
  • 观察所有项目的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视图作了改变的时候运行端到端测试,或者当你想检查该应用程序作为一个整体是否正确执行时,运行端到端测试。通常在把一个新的改变提交到远程知识库之前运行端到端测试。

现在你已经测试好了你的本地机器,让我们开始这个教程吧:第一步 引导程序

静态模板

为了演示Angular如何增强静态HTML,你可以创建一个纯静态HTML网页,然后仔细观察我们可以如何把这些HTML代码变成一个模板,从而Angular可以用来动态显示同样的结果、以任何数据集显示结果。

在这一步中,你将在一个HTML页面中添加关于两款手机的基本的信息。

  • 网页现在包含了一个列表,带有两款手机的信息。

把工作空间重置到第一步

git checkout -f step-1

刷新你的浏览器或在线检查这一步:Step 1 Live Demo

下面列出了第零步和第一步之间的最重要的区别。你可以在GitHub里看到完整的差异。

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>

实验

  • 尝试向index.html添加更多的静态HTML。比如:
<p>Total number of phones: 2</p>

总结

额外对你的应用使用静态HTML以显示这个列表。现在,让我们前往第二步 Angular模板以学习如何使用AngularJS以动态生成同一个列表。

在下一节内容中,我们将介绍 AngularJS 静态模板

Angular模板

现在是时候用AngularJS制作动态网页了。我们将添加一个测试,验证用于控制器的代码,我们将添加这个控制器。

为应用程序构造代码有很多方式。针对Angular应用,我们鼓励使用模块-视图-控制器(MVC)设计模式以解耦代码、分离关注点。考虑到这一点,我们使用小的Angular以及JavaScript为我们的应用添加模块、视图和控制器组件。

  • 现在下面的数据中动态生成了三款手机的列表:

把工作空间重置到第二步

git checkout -f step-2

刷新你的浏览器或在线检查这一步:Step 2 Live Demo

下面列出了第一步和第二步之间的最重要的区别。你可以在GitHub里看到完整的差异。

视图和模板

在Angular中,视图是模块透过HTML模板的映射。这意味着每当模块有变化时,Angular会刷新适当的绑定点,随之更新视图。

以下面代码为模板,Angular结构化了视图组件:

app/index.html:

<html ng-app="phonecatApp"><head>  ...  <script src="/attachments/image/wk/angularjs/angular.js"></script>  <script src="/attachments/image/wk/angularjs/controllers.js"></script></head><body ng-controller="PhoneListCtrl">  <ul>    <li ng-repeat="phone in phones">      <span>{{phone.name}}</span>      <p>{{phone.snippet}}</p>    </li>  </ul></body></html>

我们用ngRepeat指令和两个Angular表达式替代硬编码的手机列表:

  • <li>元素标签上的元素属性ng-repeat="phone in phones"是一个Angular转发器指令。该转发器告诉Angular为列表中的每款使用元素标签<li>作为模板的手机创建一个<li>元素。
  • 用花括号包围的表达式({{phone.name}}{{phone.snippet}})将被替换成表达式的值。

我们已经添加了一个新指令,称为ng-controller,它给元素标签<body>附加了一个PhoneListCtrl控制器。在这个点上:

  • 在花括号中的表达式({{phone.name}}{{phone.snippet}})表示绑定,在我们的应用程序模块中参引它们,它们被设置在我们的PhoneListCtrl控制器上。
注意:我们已经指定了一个[Angular模块](https://docs.angularjs.org/api/ng/type/angular.Module)以载入使用`ng-app="phonecatApp"`,在那里,`phonecatApp`是我们的模块名。该模块将包含`PhoneListCtrl`。

模块和控制器

数据模块(一个简单的手机数列,以对象字面记号法表达)现在在PhoneListCtrl控制器中实例化了。该控制器只是一个构造器函数,需要一个$scope参数:

app/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指令(在元素标签<html>上)现在指定了phonecatApp模块名作为载入的模块,在引导应用Angular应用程序时载入该模块。

虽然控制器没有做太多的事情,但是它扮演了一个至关重要的角色。通过为我们的上下文提供数据模块,控制器允许我们在模块和视图之间建立数据绑定。我们在展示、数据和逻辑组件之间添加点状虚线,如下所示:

  • ngController指令,定位在<body>元素标签上,引用了我们的控制器的名称,PhoneListCtrl(放置在JavaScript文件controllers.js上)。
  • PhoneListCtrl控件在$scope上附加了手机数据,把它注入到我们的控制器函数中。该作用域根作用域的原型化的后代,在定义应用程序的时候创建了该根作用域。该控制器作用域可以在元素标签<body ng-controller="PhoneListCtrl">内部的所有绑定位置上可用。

作用域

一个作用域的概念在Angular中是至关重要的。作用域可以被视为胶合剂,允许模板、模块和控制器一起工作。Angular使用作用域,以及模板、数据模块和控制器中包含的信息,以保持模块和视图分离,但是同步。任何对模块的改变会影响视图;任何在视图中发生的改变反应在模块中。

要想学习更多关于Angular作用域的知识,请参阅angular作用域文档

测试

从视图中分离控制器的“Angular方法”,使测试代码变得容易,就像是它在被开发那样。如果你的控制器在全局命名空间中可用,则我们可以用一个模拟的scope对象简单把它实例化:

test/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中创建测试变得容易,从而可以鼓励开发员编写它们。

测试非全局控制器

在实践中,你应该不想让你的控制器函数在全局命名空间内。取而代之的是,你可以看到我们已经利用一个phonecatApp模块上的匿名构造器函数注册了控制器。

在这种情况下,Angular提供了一个服务,$controller,它可以以名称接收你的控制器。这里有使用$controller同样的测试:

test/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模块。
  • 我们要求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测试运行变得极其缓慢。

实验

  • 添加对index.html的另一个绑定。例如:

    <p>Total number of phones: {{phones.length}}</p>
  • 在控制器中创建一个新模块属性,然后从模板中把它绑定到模块上。例如:

    $scope.name = "World";

    然后向index.html添加一个新的绑定:

    <p>Hello, {{name}}!</p>

    刷新你的浏览器,核实它是否说了"Hello, World!"。

  • ./test/unit/controllersSpec.js中的控制器更新单元测试,以反映以前的变化。例如添加:

    expect(scope.name).toBe('World');
  • index.html中创建一个重复器,它结构化了一个简单的表格:

    <table>  <tr><th>row number</th></tr>  <tr ng-repeat="i in [0, 1, 2, 3, 4, 5, 6, 7]"><td>{{i}}</td></tr></table>

    现在,让这个基于1的列表的i在绑定中增值1。

    <table>  <tr><th>row number</th></tr>  <tr ng-repeat="i in [0, 1, 2, 3, 4, 5, 6, 7]"><td>{{i+1}}</td></tr></table>

    另需指出:尝试并使一个8x8的表格使用一个额外的ng-repeat

  • 通过把expect(scope.phones.length).toBe(3)变成toBe(4),使单元测试失败。

总结

现在你有了一个动态的应用,功能分离开模块、视力和控制器组件,而且你测试了它们。现在,让我们前往第三步 筛选迭代器以学习如何为应用添加全文搜索。

    筛选迭代器

    我们在上一步中为开发应用打基础做了很多工作,现在我们将做一些简单的事情;我们将添加全文搜索(是的,它很简单!)。我们还将编写一个端到端测试,因为一个好的端到端测试可以帮上大忙。它监视着你的应用,并在发生回归时迅速报告。

    • 现在应用有了一个搜索框。注意页面上的手机列表的变化取决于用户在搜索框中打了什么字。

    把工作空间重置到第三步

    git checkout -f step-3

    刷新你的浏览器或在线检查这一步:Step 3 Live Demo

    下面列出了第二步和第三步之间最重要的区别。你可以在GitHub里看到完整的差异。

    控制器

    我们对控制器不作修改。

    模板

    app/index.html:

      <div class="container-fluid">    <div class="row">      <div class="col-md-2">        <!--Sidebar content-->        Search: <input ng-model="query">      </div>      <div class="col-md-10">        <!--Body content-->        <ul class="phones">          <li ng-repeat="phone in phones | filter:query">            {{phone.name}}            <p>{{phone.snippet}}</p>          </li>        </ul>      </div>    </div>  </div>

    我们添加了一个标准HTML<input>元素标记,并使用Angular的filter函数来处理repeat指令的输入。

    这使用户输入搜索条件,并在手机列表中快速看到搜索结果。新的代码演示如下:

    • 数据绑定:这是Angular的一个核心功能。当网页载入时,Angular把输入框的名称绑定到数据模块的同名的变量上,并保持两者同步。

      在代码中,用户打字到输入框的数据(命名为query)很快可以作为一个筛选器输入到列表迭代器(phone in phones | filter:query)中。在改变数据模块的时候,导致迭代器的输入发生变化,迭代器有效地更新了DOM,以反映模块的当前状态。

    • 使用filter筛选器:filter函数使用了query值发创建一个新的数列,只包含匹配query的记录。

      ngRepeat自动更新了视力,以响应filter筛选器返回的手机数字的变化。该处理对开发者来说是完全透明的。

    测试

    在第二步中,我们学会了如何编写并运行单元测试。对于测试我们的用JavaScript编写的应用程序的控制器和其它组件,单元测试是完美的,但是测试DOM操作或测试我们的应用程序的接通不太方便。针对这些,一个端到端的测试是一个更好的选择。

    该搜索功能完全是通过模板和数据绑定来实现的,我们将编写我们第一个端到端的测试,以验证该功能起了什么作用。

    test/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运行端到端的测试

    甚至虽然测试的句法看起来很像我们的用Jasmine编写的控制器单元测试,但是端到端测试使用Protractor的API。在http://angular.github.io/protractor/#/api可以读到Protractor的API。

    与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.

    实验

    显示当前查询

    通过添加一个绑定到index.html模板的{{query}}来显示query模块当前的值,并看到当你在输入框中打字时,它如何变化。

    在标题中显示查询

    让我们看到我们可以取得query模板的当前值,模块出现在HTML网页的标题上。

    • 把一个端到端测试添加到describe块中,test/e2e/scenarios.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$/);      });    });  });

      运行protractor(npm run protractor),看到测试失败了。

    • 你可能认为你只需要用以下方式向标题标签添加{{query}}

      <title>Google Phone Gallery: {{query}}</title>

      然而,当你重载入这个网页的时候,你不会看到想要的结果。这是因为“查询”模块驻留在作用域内,由ng-controller="PhoneListCtrl"指令在body元素上定义。

      <body ng-controller="PhoneListCtrl">

      如果你想要从<title>元素上绑定查询模块,你必须把ngController声明移动到HTML元素上,因为它是body元素和title元素常用的父元素。

      <html ng-app="phonecatApp" ng-controller="PhoneListCtrl">

      确保从body元素中移除ng-controller声明。

    • 重新运行rpm run protractor,看到现在测试已经看通过了。

    • 在title元素内部使用双花工作得很好,与此同时,你可能会注意到页面加载的一瞬间它们确实显示给用户了。一个更好的解决方案是使用ngBind指令ngBindTemplate指令,当页面加载时用户能看到它们。

      <title ng-bind-template="Google Phone Gallery: {{query}}">Google Phone Gallery</title>

    总结

    我们现在已经把全文搜索添加上去了,还包含了一个用来验证搜索是否起作用的测试!现在让我们前往第四步 双路数据绑定以学会如何向手机应用添加排序功能。

    双路数据绑定

    在这一步中,你将添加一个功能,让你的用户控制手机列表中的项目的排序。这个动态排序由创建一个新模块属性来实现,用迭代器接通它们,并且让数据绑定来完成剩余工作。

    • 除了搜索框,应用显示了一个下拉菜单,允许用户 控制列出的手机的排序。

    把工作空间重置到第四步

    git checkout -f step-4

    刷新你的浏览器或在线检查这一步:Step 4 Live Demo

    下面列出了第四步和第五步之间的最重要的区别。你可以在GitHub里看到完整的差异。

    模板

    app/index.html:

      Search: <input ng-model="query">  Sort by:  <select ng-model="orderProp">    <option value="name">Alphabetical</option>    <option value="age">Newest</option>  </select>  <ul class="phones">    <li ng-repeat="phone in phones | filter:query | orderBy:orderProp">      <span>{{phone.name}}</span>      <p>{{phone.snippet}}</p>    </li>  </ul>

    我们制作以下对index.html模板的改变:

    • 首先,我们添加了一个<select> html元素,命名为orderProp,因此我们的用户可以从两个提供的排序选择中选一个。
    • 然后,我们把filter筛选器连与orderBy?筛选器连缀,以更进一步处理输入到迭代器的处理。orderBy是一个筛选器,取用一个输入数组,复制它,之后返回一个副本,重排序这个副本。

    Angular在select元素以及orderProp模块之间创建了双路数据绑定。然后orderProp被用作针对orderBy筛选器的输入。

    正如我们在这一节第三步中讨论的,关于数据绑定和迭代器,每当模块变化时(比如说因为用户通过选择下拉菜单改变了顺序),Angular的数据绑定将导致该视图自动更新。不臃肿的DOM操作代码是必要的!

    控制器

    app/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.',     'age': 1},    {'name': 'Motorola XOOM? with Wi-Fi',     'snippet': 'The Next, Next Generation tablet.',     'age': 2},    {'name': 'MOTOROLA XOOM?',     'snippet': 'The Next, Next Generation tablet.',     'age': 3}  ];  $scope.orderProp = 'age';});
    • 我们修改了phones模块——手机的数组——并把一个age属性添加到每个手机记录中。属性被用于根据年代排序手机。

    • 我们给控制器添加了一行,把orderProp的默认值设置为age。如果我们还没有在这里设置一个默认值,orderBy筛选器会保持未初始化,直到我们的用户从下拉菜单中挑了一个选项。

      这是讲解双路数据绑定的好时候。注意,当应用在浏览器中载入的时候,下拉菜单中的“最新”被选中。这是因为我们在控制器中把orderProp设置为'age'。从我们的模块到UI的方向中的绑定工作也同样。现在,如果你选择了下拉菜单中的“Alphabetically(字母表排序)”,模块也将被更新,而且重排序了手机。这是数据绑定在反方向中所做的工作——从UI到模块。

    测试

    我们所做的变化将在单元测试和端到端测试中被验证。让我们先看一看单元测试。

    test/unit/controllersSpec.js:

    describe('PhoneCat controllers', function() {  describe('PhoneListCtrl', function(){    var scope, ctrl;    beforeEach(module('phonecatApp'));    beforeEach(inject(function($controller) {      scope = {};      ctrl = $controller('PhoneListCtrl', {$scope:scope});    }));    it('should create "phones" model with 3 phones', function() {      expect(scope.phones.length).toBe(3);    });    it('should set the default value of orderProp model', function() {      expect(scope.orderProp).toBe('age');    });  });});

    单元测试现在核实了默认的排序属性设置。

    我们使用Jasmins的API,把控制器架构抽出到beforeEach块,它由所有的父describe块中的测试共享。

    现在你应该在Karma选项卡中看到了以下输出:

    Chrome 22.0: Executed 2 of 2 SUCCESS (0.021 secs / 0.001 secs)

    让我们把注意力带回到端到端测试。

    test/e2e/scenarios.js:

    ...    it('should be possible to control phone order via the drop down select box', function() {      var phoneNameColumn = element.all(by.repeater('phone in phones').column('phone.name'));      var query = element(by.model('query'));      function getNames() {        return phoneNameColumn.map(function(elm) {          return elm.getText();        });      }      query.sendKeys('tablet'); //let's narrow the dataset to make the test assertions shorter      expect(getNames()).toEqual([        "Motorola XOOMu2122 with Wi-Fi",        "MOTOROLA XOOMu2122"      ]);      element(by.model('orderProp')).element(by.css('option[value="name"]')).click();      expect(getNames()).toEqual([        "MOTOROLA XOOMu2122",        "Motorola XOOMu2122 with Wi-Fi"      ]);    });...

    端到端测试核实了选择框的排序机制正在正常工作。

    现在你可以重新运行npm run protractor以查看测试运行。

    实验

    • PhoneListCtrl控制器中,移除设置orderProp值的状态,你将看到Angular给下拉列表临时地添加了一个新的空白("unknown")选项,而且排序将默认为无序/自然排序。

    • 把一个{{orderProp}}绑定到index.html模板上,从而把它的当前值显示为文本。

    • 在排序值前面添加一个-符号来逆转排序顺序:<option value="-age">Oldest</option>

    总结

    现在我们已经添加了列表排序,并测试了应用,前往第五步 XHR和依赖注入以学习关于Angular服务,以及Angular如何使用依赖性注入。

    XHR和依赖性注入

    本文主要为你介绍了 AngularJS XHR 和依赖注入,这里整理了详细资料和示例代码

    在硬编码的数据集中有三款手机的数据,建立一个应用程序足够了!让我们使用Angular内建的服务之一,$http从服务器上取得更大的数据集我们将使用Angular的依赖性注入(DI)来为PhoneListCtrl控制器提供服务。

    • 现在有一个20个电话的列表,从服务器载入。

    把工作空间重置到第五步

    git checkout -f step-5

    刷新你的浏览器或在线检查这一步:Step 5 Live Demo

    下面列出了第四步和第五步之间的最重要的区别。你可以在GitHub里看到完整的差异。

    数据

    在你的项目中,app/phones/phones.json文件是一个数据集,包含了一个更大的手机列表,以JSON格式存储。

    遵照以下文件示例:

    [ {  "age": 13,  "id": "motorola-defy-with-motoblur",  "name": "Motorola DEFYu2122 with MOTOBLURu2122",  "snippet": "Are you ready for everything life throws your way?"  ... },...]

    控制器

    我们将在控制器中使用Angular的$http服务向你的Web服务器发出HTTP请求,取回app/phones/phones.json文件中的数据。$http是几个用Web应用中来处理常见的操作的内建Angular服务之一。Angular在你需要的地方为你注入了这些服务。

    Angular的DI子系统负责管理这些服务。依赖性注入有用助于你的web应用既结构完好(例如,分离表现层、数据和控制三者)以及松弛的耦合(不能由组件自身解决的组件之间的依赖性问题,由DI子系统解决)。

    app/js/controllers.js:

    var phonecatApp = angular.module('phonecatApp', []);phonecatApp.controller('PhoneListCtrl', function ($scope, $http) {  $http.get('phones/phones.json').success(function(data) {    $scope.phones = data;  });  $scope.orderProp = 'age';});

    $http向你的Web服务器发出一个HTTP GET请求,要求phones/phones.json(该url相对于我们的index.html文件)。服务器在json文件中提供该数据,以响应该请求。(响应可能是由后端服务器动态生成的。但是在浏览器和我们的应用看来,它们没什么不同。为了简单起见,我们在本教程中使用了一个json文件。)

    $http服务返回了一个promise对象?,带有success方法。我们调用这个方法以处理异步响应,并假定该作用域的手机数据由该控制器控制,作为一个模块,称为phones。注意Angular侦测了该json响应,并为我们解析了它。

    要想在Angular中使用一个服务,你只要声明你所需要的依赖性的名字,作为控制器的构造函数的参数,如下所示:

    phonecatApp.controller('PhoneListCtrl', function ($scope, $http) {...}

    在构造控制器时,Angular的依赖性注入器会把这些服务注入到你的控制器中。这些依赖性控制器还负责创建该服务可能需要的任何传递依赖性(一个服务通常会依赖于其它服务)。

    注意,参数的名称非常重要,因为注入器会用这些名称去查阅依赖性。

    $前缀名称约定

    你可以创建你自己的服务,而且实际上我们将在第十一步 AngularJS REST和自定义服务做这个。作为一个命名约定,Angular的内建服务,作用域方法以及一些别的Angular API在命名前面使用一个$前缀。

    Angular提供的服务的命名空间有$前缀。要想避免冲突,最好避免把你的服务和模块命名成带有$前缀。

    如果你检查一个作用域,你可能还会注意到一些属性以$$开头。这些属性被视为是私有属性,不能访问或者修改。

    在极简化上的一个注记

    因为Angular从参数的名称调用控制器的依赖性到控制器构造器的函数,如果你打算为PhoneListCtrl控制器缩小JavaScript代码,所有的函数参数都会被压缩,而且依赖性注入器将不能正确的识别服务。

    我们可以克服这个问题,通过用依赖性的名称注释这个函数,作为字符串提供,它不会被压缩。提供这种注入注释有两种方法:

    • 在控制器函数中创建一个$inject属性,它可携带一个字符串数组。在数组中的每个字符串都是要注入到对应的参数上的服务的名称。我们可以在自己的示例中这样写:

          function PhoneListCtrl($scope, $http) {...}    PhoneListCtrl.$inject = ['$scope', '$http'];    phonecatApp.controller('PhoneListCtrl', PhoneListCtrl);
    • 在那里使用一个内联注释,并非是只提供这个函数,你还提供了一个数组。这个数组包含了一系列服务名称,后跟着函数本身。

          function PhoneListCtrl($scope, $http) {...}    phonecatApp.controller('PhoneListCtrl', ['$scope', '$http', PhoneListCtrl]);

    两种方法都能与Angular注入的任何函数完美协作,因此要选用哪种方法完全取决于你的项目的编程风格。

    如果使用第二种方法,在注册控制器时,通常以匿名函数的形式提供内联的构造器函数。

        phonecatApp.controller('PhoneListCtrl', ['$scope', '$http', function($scope, $http) {...}]);

    从此刻开始,我们将在本教程中使用内联方法。考虑到这一点,让我们把注释加到PhoneListCtrl上:

    app/js/controllers.js:

    var phonecatApp = angular.module('phonecatApp', []);phonecatApp.controller('PhoneListCtrl', ['$scope', '$http',  function ($scope, $http) {    $http.get('phones/phones.json').success(function(data) {      $scope.phones = data;    });    $scope.orderProp = 'age';  }]);

    测试

    test/unit/controllersSpec.js:

    因为我们开始使用依赖性注入,而且我们的控制器包含了依赖性,在我们的测试中构造控制器就变得有点复杂了。我们可以使用new操作符,并提供带有某种假的$http实现的构造器。然而,Angular提供了一个模拟$http服务,我们可以用在单元测试中。我们通过调用一个称为$httpBackend服务上的方法,为服务器请求配置了“假的”响应。

    describe('PhoneCat controllers', function() {  describe('PhoneListCtrl', function(){    var scope, ctrl, $httpBackend;    // 在每次测试之前载入我们的应用模块定义    beforeEach(module('phonecatApp'));    // 注入器会忽略前面和后面的下划线(例如_$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});    }));

    注意:因为我们在测试环境中载入了Jasmine以及angular-mocks.js,我们得到了两个辅助方法moduleinject,用来访问和配置注入器。

    我们在测试环境中创建控制器,如下所示:

    • 我们使用inject辅助方法,向Jasmine的beforeEach函数注入$rootScope$controller$httpBackend服务的实例,这些实例来自于一个注入器,在每一个测试内部都会被重新创建这个注入器。这保证了每次测试都从一个众所周知的起点开始,每次测试与其它测试相互独立。
    • 通过调用$rootScope.$new()来为我们的控制器创建一个新的作用域。
    • 调用了已注入的$controller函数,以参数的形式传入PhoneListCtrl控制器的名称和创建范围。

    因为我们的代码现在使用$http服务以取回我们的控制器中的手机列表数据,在我们创建PhoneListCtrl子作用域之前,我们需要告诉测试套件等待一个后面的请求,来自控制器。我们可以这样做:

    • 请求把$httpBackend服务注入到我们的beforeEach函数中。这是一个在产品环境中的服务的模拟版本,可以响应各种XHR和JSONP请求。该服务的模拟版本允许你编写测试,不需要处理原生的API和与它相关的全局状态——本来这两者都会使测试变成一个噩梦。

    • 使用$httpBackend.expectGET方法规定$httpBackend服务等待之后的HTTP请求,并告诉它如何响应它。注意,直到我们调用$httpBackend.flush方法,才会返回响应。

    现在我们作了断言以核实在响应到达之前,作用域上不存在手机模块:

        it('should create "phones" model with 2 phones fetched from xhr', function() {      expect(scope.phones).toBeUndefined();      $httpBackend.flush();      expect(scope.phones).toEqual([{name: 'Nexus S'},                                   {name: 'Motorola DROID'}]);    });
    • 通过调用$httpBackend.flush(),我们清空了浏览器中的请求队列。这导致$http服务返回的promise对象由规范的应答来处理。可以在模拟$httpBackend文档中了解为什么必须“清空HTTP请求”的完整解释。

    • 我们制作了断言,核实作用域上已经有手机模块了。

    最后,我们核实已经正确设置了orderProp的默认值。

        it('should set the default value of orderProp model', function() {      expect(scope.orderProp).toBe('age');    });

    现在在Karma标签卡中,你应该看到以下的输出:

    Chrome 22.0: Executed 2 of 2 SUCCESS (0.028 secs / 0.007 secs)

    实验

    • index.html的底部,添加一个<pre>{{phones | filter:query | orderBy:orderProp | json}}</pre>绑定以查看以json格式显示的手机列表。
    • PhoneListCtrl控制器中,通过限制手机的数量为列表的前五个来预处理http响应。在$http回调中使用以下的代码:
    $scope.phones = data.splice(0, 5);

    总结

    现在你已经知道了使用Angular服务是多么容易(幸亏Angular的依赖性注入),前往第六步 模板连接和图像,在那里你将添加一些手机的缩略图以及一些链接。

    模板连接和图像

    在这一步中,你将为手机列表中的手机添加一个缩略图,并链接到想去的地方。在后续的步骤中,你将使用这个链接显示关于目录中的这款手机的额外的信息。

    • 现在列表中的手机已经有链接和图像了。

    把工作空间重置到第六步

    git checkout -f step-6

    刷新你的浏览器或在线检查这一步:Step 6 Live Demo

    下面列出了第五步和第六步之间的最重要的区别。你可以在GitHub里看到完整的差异。

    数据

    注意,phones.json文件包含了针对每款手机的独一无二的ID和图像URL。URL指向app/img/phones目录。

    app/phones/phones.json(示例片段):

    [  {    ...    "id": "motorola-defy-with-motoblur",    "imageUrl": "img/phones/motorola-defy-with-motoblur.0.jpg",    "name": "Motorola DEFYu2122 with MOTOBLURu2122",    ...  },  ...]

    模板

    app/index.html:

    ...        <ul class="phones">          <li ng-repeat="phone in phones | filter:query | orderBy:orderProp" class="thumbnail">            <a href="#/phones/{{phone.id}}" class="thumb"><img ng-src="{{phone.imageUrl}}"></a>            <a href="#/phones/{{phone.id}}">{{phone.name}}</a>            <p>{{phone.snippet}}</p>          </li>        </ul>...

    要想未来动态生成指向手机详情页的链接,我们在元素属性href的值上使用了广为人知的双花括号绑定。在第二步中,我们添加了{{phone.name}}绑定,作为元素内容。在这一步中,{{phone.id}}绑定用在元素属性上。

    我们还在每条记录后面添加了手机图像,用一个带ngSrc指令的图像标签。这个指令防止浏览器字面理解Anglar标签{{ expression }}、向无效的URLhttp://localhost:8000/app/{{phone.imageUrl}}初始化一个请求,如果我们只在一个常规的元素属性src上绑定这个双花括号值(<img src="{{phone.imageUrl}}">),这样的事情真的会发生。使用ngSrc指令以防止浏览器对一个无效的位置发起http请求。

    测试

    test/e2e/scenarios.js:

    ...    it('should render phone specific links', function() {      var query = element(by.model('query'));      query.sendKeys('nexus');      element.all(by.css('.phones li a')).first().click();      browser.getLocationAbsUrl().then(function(url) {        expect(url).toBe('/phones/nexus-s');      });    });...

    我们添加了一个新的端到端测试,以核查应用是否生成了正确的链接,正确的链接要链到手机视图中,我们将在下一步是实现这个视图。

    现在你可以重新运行npm run protractor以查看测试运行。

    实验

    • 用扁平的旧元素属性src代替ng-src指令。使用一些工具,比如说Firebug,或者Chrome的Web Inspector,或者检查web服务器的访问日志,确保应用确实发起过了一个外部请求,指向/app/%7B%7Bphone.imageUrl%7D%7D(或者/app/{{phone.imageUrl}})。

      有个话题是,在Angular已经有机会评估表达式、并注入有效地址之前,当浏览器读取img标签时,浏览器将向非法的图像地址发起一个请求。

    总结

    现在你已经添加了一个手机图像和链接,前往第七步 路由与多视图以学习关于Angular布局模板、以及Angular如何使创建具有多个视图的应用变容易。

    路由与多视图

    在这一步中,你将学会如何通过使用被称为'ngRoute'的Angular模块添加路由,创建一个布局模板,以及如何绑定一个具有多视图的应用。

    • 当你导航到app/index.html上时,你将跳车到app/index.html/#/phones,而且手机列表出现在浏览器中。
    • 当你在手机链接上点击时,url变成特定的手机,出现了手机详情页。

    把工作空间重置到第七步

    git checkout -f step-7

    刷新你的浏览器或在线检查这一步:Step 7 Live Demo

    下面列出了第六步和第七步之间的区别。你可以在GitHub里看到完整的差异。

    依赖性

    这一步中添加路由功能是由 ngRoute模块中的angular提供的,它与核心的Angular框架分离分布。

    我们使用Bower以安装客户端依赖性。这一步更新了bower.json配置文件,以包含新的依赖性:

    {  "name": "angular-phonecat",  "description": "A starter project for AngularJS",  "version": "0.0.0",  "homepage": "https://github.com/angular/angular-phonecat",  "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-route": "1.4.x"告诉bower要安装与v1.4x兼容的angular-router组件版本。我们将告诉bower以下载并安装该依赖性。

    如果你已经全局安装了bower,则你可以只对该项目运行bower install,我们已经预配置了npm,从而为我们运行bower安装:

    npm install

    多个视图、路由和布局模板

    我们的应用渐渐地完善,变得越来越复杂。在第七步之前,应用向我们用户提供了单一视图(手机的列表),而且所有的模板代码都位于index.html文件中。构建应用的下一步是添加一个视图,这个视图将显示我们的列表中每款设备的详细信息。

    要想添加详情视图,我们可以扩展index.html以包含两套视图的模板代码,但是那将很快变得混乱。因此我们不用这种方法,而是把index.html变成“布局模板”。这是一个模板,常用于我们应用中的所有视图。然后别的“局部布局模板”根据当前的“路由”包含到这个布局模板中,从而形成一个完整视图展示给用户。

    通过$routeProvider来声明Angular中的应用程序路由,它是$route服务的提供者。这个服务使接通控制器、视图模板以及浏览器中的当前位置变得容易。利用这个功能,我们可以实现深链接,深链接让我们可以使用浏览器的历史(回退和前进导航)以及书签。

    一条关于DI、注入器和提供者的提醒

    如你已注意到的依赖性注入(DI)是AngularJS的核心,所以对它的工作原理略知一二是很重要的。

    在应用程序引导中,Angular创建了一个注入器,注入器用来寻找并注入你的应用所需要的所有的服务。注入器本身对$http$route服务是做什么的一无所知。实际上,注入器甚至不知道这些服务是否存在,除非用适当的模板定义对它进行配置。

    注入器只在以下步骤中出场:

    • 载入你在你的应用中指定的模块定义。
    • 注册所有的在模块定义中定义的提供者。
    • 当被要求做这的时候,注入一个指定的函数以及一些必要的依赖性(服务),它通过它们的提供者来惰性实例化。

    提供者是提供(创建)服务实例并且对外提供配置API的对象,API可以用来控制一个服务的创建和运行时行为。对于$route来说,$routeProvider对外提供API,API允许你定义针对你的应用程序的路由。

    **注意:**只能够把提供者注入到`config`函数中。因此你不能够把`$routeProvider`注入到`PhoneListCtrl`中。

    Angular模块解决了从应用程序中移除全局状态的问题,并提供配置注入器的方法。相对于AMD或require.js模块,Angular模块并不试图解决脚本载入次序问题或者懒惰式脚本取得问题。这些目标是完全独立的,两个模块系统可以并立存在,并实现他们的目标。

    要想加深你对Angular上的DI的理解,请参看理解依赖性注入

    模板

    $route服务常与ngView指令结合使用。ngView指令的角色是在布局模板中包含用于当前路由的视图模板。这使它完美恰合我们的index.html模板。

    **注意:**从AngularJS v1.2版开始,`ngRoute`在它自己的模块中,必须通过载入额外的`angular-route.js`文件来载入它,我们通过上面的Bower来下载`angular-route.js`文件。

    app/index.html:

    <!doctype html><html lang="en" ng-app="phonecatApp"><head>...  <script src="/attachments/image/wk/angularjs/angular.js"></script>  <script src="/attachments/image/wk/angularjs/angular-route.js"></script>  <script src="/attachments/image/wk/angularjs/app.js"></script>  <script src="/attachments/image/wk/angularjs/controllers.js"></script></head><body>  <div ng-view></div></body></html>

    我们已经在我们的索引文件添加了两个新的<script>标记,从而把外部JavaScript文件载入到我们的应用程序中:

    • angular-route.js : 定义Angular ngRoute模块,ngRoute模块向我们提供了路由。
    • app.js : 现在这个文件控住了我们的应用程序的根模块。

    注意:我们删除了index.html模板中的大部分代码,把它替换成一行代码,包含了一个带有元素属性ng-view的div。我们已经移除的这个代码被放到了phone-list.html模板中:

    app/partials/phone-list.html:

    <div class="container-fluid">  <div class="row">    <div class="col-md-2">      <!--Sidebar content-->      Search: <input ng-model="query">      Sort by:      <select ng-model="orderProp">        <option value="name">Alphabetical</option>        <option value="age">Newest</option>      </select>    </div>    <div class="col-md-10">      <!--Body content-->      <ul class="phones">        <li ng-repeat="phone in phones | filter:query | orderBy:orderProp" class="thumbnail">          <a href="#/phones/{{phone.id}}" class="thumb"><img ng-src="{{phone.imageUrl}}"></a>          <a href="#/phones/{{phone.id}}">{{phone.name}}</a>          <p>{{phone.snippet}}</p>        </li>      </ul>    </div>  </div></div>
    TODO!

    我们还为手机详情视图添加了一个占位符模板:

    app/partials/phone-detail.html:

    TBD: detail view for <span>{{phoneId}}</span>

    注意,我们正在使用的phoneId表达式将在PhoneDetailCtrl控制器中定义。

    应用模块

    要想增强应用的组织,我们动用了Angular的ngRoute模块,我们已经把控制器移到它们自己的模块phonecatControllers中(如下所示)。

    我们给index.html添加angular-route.js,并在controllers.js中创建一个新的phonecatControllers模块。然而,要想使用它们的代码,我们需要做的不止于此。我们还需要添加模块,作为我们的应用的依赖性。通过把两个应用作为phonecatApp的依赖性列表,我们可以使用这些指令以及它们提供的服务。

    app/js/app.js:

    var phonecatApp = angular.module('phonecatApp', [  'ngRoute',  'phonecatControllers']);...

    注意第二个参数传递到angular.module,['ngRoute','phonecatControllers']。这个数组列出了phonecatApp所依赖的模块。

    ...phonecatApp.config(['$routeProvider',  function($routeProvider) {    $routeProvider.      when('/phones', {        templateUrl: 'partials/phone-list.html',        controller: 'PhoneListCtrl'      }).      when('/phones/:phoneId', {        templateUrl: 'partials/phone-detail.html',        controller: 'PhoneDetailCtrl'      }).      otherwise({        redirectTo: '/phones'      });  }]);

    使用phonecatApp.config()方法,我们请求了$routeProvider,它会被注入到我们的配置函数中,并使用?$routeProvider.when()方法以定义我们的路由。

    我们的应用程序路由定义如下:

    • when('/phones'):当URL映射段为/phones的时候。将展示这个手机列表视图。要想构造这个视图,Angular将使用phone-list.html模板,以及PhoneListCtrl控制器。
    • when('/phones/:phoneId'):当URL映射段匹配/phones/:phoneId的时候(其中:phoneId是URL的变量部分),将展示手机详情视图。要想构造手机详情视图,Angular将使用phone-detail.html模板以及PhoneDetailCtrl控制器。
    • otherwise({redirectTo: '/phones'}):当浏览器的地址不匹配我们别的路由的时候,触发一个重定向到/phones

    我们再次使用我们在上一步中构造的PhoneListCtrl控制器,并为手机详情视图向app/js/controllers.js文件添加了一个新的、空的PhoneDetailCtrl控制器。

    注意在第二个路由声明中:phoneId参数的使用。$route服务使用route声明'/phones/:phoneId'作为匹配当前URL的模板。所有用:记号法定义的变量都会提取出来,放到?$routeParams对象上。

    控制器

    app/js/controllers.js:

    var phonecatControllers = angular.module('phonecatControllers', []);phonecatControllers.controller('PhoneListCtrl', ['$scope', '$http',  function ($scope, $http) {    $http.get('phones/phones.json').success(function(data) {      $scope.phones = data;    });    $scope.orderProp = 'age';  }]);phonecatControllers.controller('PhoneDetailCtrl', ['$scope', '$routeParams',  function($scope, $routeParams) {    $scope.phoneId = $routeParams.phoneId;  }]);

    再次提醒注意,我们创建了一个新的模块,称为phonecatControllers。对于小型的AngularJS应用,通常针对所有的控制器只创建一个模板,如果控制器只有为数不多的几个。随着你的应用程序扩大,常常要把你的代码重构到额外的模块中。为了更大的应用,你可能将会想要为你的应用的所有的主要功能创建独立的模块。

    因为我们的应用比较小,我们将把我们所有的控制器添加到phonecatControllers模块中。

    测试

    要想自动核查所有东西都正确连通了,我们编写了一个端到端的测试,导航到不同的URL上,并核查是否呈现了正确的视图。

    ...   it('should redirect index.html to index.html#/phones', function() {    browser.get('app/index.html');    browser.getLocationAbsUrl().then(function(url) {        expect(url).toEqual('/phones');      });  });  describe('Phone list view', function() {    beforeEach(function() {      browser.get('app/index.html#/phones');    });...  describe('Phone detail view', function() {    beforeEach(function() {      browser.get('app/index.html#/phones/nexus-s');    });    it('should display placeholder page with phoneId', function() {      expect(element(by.binding('phoneId')).getText()).toBe('nexus-s');    });  });

    你现在可以再次运行npm run protractor来查看测试的运行。

    实验

    • 尝试添加一个绑定到index.html{{orderProp}},而且你将看到什么事也没有发生,哪怕你正在手机列表视图中。这是因为orderProp模块只有在PhoneListCtrl管理的作用域内是可见的,PhoneListCtrl<div ng-view>元素关联。如果你在phone-list.html模板上添加同样的绑定,绑定将如你的预期运作起来。
    * 在`PhoneCatCtrl`中,创建一个带有`this.hero='Zoro'`的新模块,称为"hero"。在`PhoneListCtrl`中,让我们用`this.hero='Batman'`来遮蔽它。在`PhoneDetailCtrl`中,我们将使用`this.hero = "Captain Proton"`。然后 把`

    hero = {{hero}}

    `添加到全部三个模板`index.html`、`phone-list.html`和`phone-detail.html`上。打开应用,你将看到作用域继承以及模板属性遮蔽做了一些奇观。

    总结

    随着路由设置成功以及手机列表视力的实现,我们已经准备好前往第八步 更多模板,以实现手机详情视图。

    更多模板

    在这一步中,你将实现手机详情视图,当用户在手机列表中点击了一款手机,就会显示这个视图。

    • 当你在列表中点击了一款手机,将显示带手机专有信息的手机详情视图。

    要想实现手机详情视力,我们将使用$http以取回我们的数据,然后具体化phone-detail.html视图模板。

    把工作空间重置到第八步

    git checkout -f step-8

    刷新你的浏览器或在线检查这一步:Step 8 Live Demo

    下面列出了第七步和第八步之间最重要的区别。你可以在GitHub上看到完整的差异。

    数据

    除了phones.jsonapp/phones/目录还包括一个针对每款手机的JSON文件:

    app/phones/nexus-s.json: (sample snippet)

    {  "additionalFeatures": "Contour Display, Near Field Communications (NFC),...",  "android": {      "os": "Android 2.3",      "ui": "Android"  },  ...  "images": [      "img/phones/nexus-s.0.jpg",      "img/phones/nexus-s.1.jpg",      "img/phones/nexus-s.2.jpg",      "img/phones/nexus-s.3.jpg"  ],  "storage": {      "flash": "16384MB",      "ram": "512MB"  }}

    每个文件用同样的数据结构描述了手机的多种属性。我们将在手机详情视图中展示这些数据。

    控制器

    我们将使用$http服务以扩展PhoneDetailCtrl,从而取回JSON文件。这以手机列表控制器中同样的方式起作用。

    app/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;    });  }]);

    要想为HTTP请求构造URL,我们使用从$route服务生成的当前路由中提取到的$routeParames.phoneId

    模板

    该TBD占位符行已经被替换成列表,绑定包含了手机详情。注意我们使用Angular的{{expression}}标签以及ngRepeat的地方,用来从我们的模块把投射手机数据到视图中。

    app/partials/phone-detail.html:

    <img ng-src="{{phone.images[0]}}" class="phone"><h1>{{phone.name}}</h1><p>{{phone.description}}</p><ul class="phone-thumbs">  <li ng-repeat="img in phone.images">    <img ng-src="{{img}}">  </li></ul><ul class="specs">  <li>    <span>Availability and Networks</span>    <dl>      <dt>Availability</dt>      <dd ng-repeat="availability in phone.availability">{{availability}}</dd>    </dl>  </li>    ...  <li>    <span>Additional Features</span>    <dd>{{phone.additionalFeatures}}</dd>  </li></ul>
    TODO!

    测试

    我们写了一个新的单元测试,类似于我们之前在第五步中为PhoneListCtrl控制器所写的步骤。

    test/unit/controllersSpec.js:

      beforeEach(module('phonecatApp'));  ...  describe('PhoneDetailCtrl', function(){    var scope, $httpBackend, ctrl;    beforeEach(inject(function(_$httpBackend_, $rootScope, $routeParams, $controller) {      $httpBackend = _$httpBackend_;      $httpBackend.expectGET('phones/xyz.json').respond({name:'phone xyz'});      $routeParams.phoneId = 'xyz';      scope = $rootScope.$new();      ctrl = $controller('PhoneDetailCtrl', {$scope: scope});    }));    it('should fetch phone detail', function() {      expect(scope.phone).toBeUndefined();      $httpBackend.flush();      expect(scope.phone).toEqual({name:'phone xyz'});    });  });...

    你现在可以看到后来在Karma标签中的输出:

    Chrome 22.0: Executed 3 of 3 SUCCESS (0.039 secs / 0.012 secs)

    我们还添加了一个新的端到端的测试,导航到Nexus S详情页面,并核查了页面上的标题是“Nexus S”。

    test/e2e/scenarios.js:

    ...  describe('Phone detail view', function() {    beforeEach(function() {      browser.get('app/index.html#/phones/nexus-s');    });    it('should display nexus-s page', function() {      expect(element(by.binding('phone.name')).getText()).toBe('Nexus S');    });  });...

    你可以再次运行npm run protractor以看到测试运行。

    实验

    • 使用Protractor API,写一个测试核查到我们在Nexus S详情页面中显示了四个缩略图。

    总结

    现在手机详情视力已经到位了,继续前往第九步 筛选器以学习如何编写你自己的自定义显示过滤器。

    筛选器

    在使用 AngularJS 的过程中会遇到一些特殊的数据处理,你可以通过采用 AngularJS 筛选器的方式来对不同的数据处理实现想要达到的效果。

    在这一步中,你将学会如何创建你自己的自定义显示筛选器。

    • 在上一步中,详情页要么显示“true”,要么显示“false”以指示某个手机功能是否存在。我们已经使用了一个自定义筛选器以将那些文本字符串转换成另一种字型:“true”变成?,“false”变成?。让我们看看筛选器代码看起来如何。

    把工作空间重置到第九步

    git checkout -f step-9

    刷新你的浏览器或在线检查这一步:Step 9 Live Demo

    下面列出了第八步和第九步之间最重要的区别。你可以在GitHub上看到完整的差异。

    自定义筛选器

    为了创建一个新筛选器,你即将创建一个phonecatFilters模块,并用这个模块注册你的自定义滤镜:

    app/js/filters.js:

    angular.module('phonecatFilters', []).filter('checkmark', function() {  return function(input) {    return input ? 'u2713' : 'u2718';  };});

    我们的筛选器的名字是“checkmark”。input要么估值为true,要么估值为false,而且会返回我们选中用来代表true和false的两个unicode字符之一(u2713->?代表true,u2718 -> ?代表false)。

    现在我们的筛选器已经准备好了,我们需要注册phonecatFilters模块作为我们的主phonecatApp模块的依赖性。

    app/js/app.js:

    ...angular.module('phonecatApp', ['ngRoute','phonecatControllers','phonecatFilters']);...

    模板

    因为筛选器生存在app/js/filters.js文件夹中,我们需要在我们的布局模板中包含这个文件。

    app/index.html:

    ... <script src="/attachments/image/wk/angularjs/controllers.js"></script> <script src="/attachments/image/wk/angularjs/filters.js"></script>...

    在Angular模板中使用筛选器的句法如下所示:

    {{ expression | filter }}

    让我们在手机详情模板中采用这个筛选器:

    app/partials/phone-detail.html:

    ...    <dl>      <dt>Infrared</dt>      <dd>{{phone.connectivity.infrared | checkmark}}</dd>      <dt>GPS</dt>      <dd>{{phone.connectivity.gps | checkmark}}</dd>    </dl>...

    测试

    筛选器,就像任何别的组件,必须被测试,而且写这些测试很容易。

    test/unit/filtersSpec.js:

    describe('filter', function() {  beforeEach(module('phonecatFilters'));  describe('checkmark', function() {    it('should convert boolean values to unicode checkmark or cross',        inject(function(checkmarkFilter) {      expect(checkmarkFilter(true)).toBe('u2713');      expect(checkmarkFilter(false)).toBe('u2718');    }));  });});

    我们必须在执行任何筛选器测试之前调用beforeEach(module('phonecatFilters'))。这种调用把我们的phonecatFilter模块载入到注入器,以测试运行。

    注意我们将调用助手函数inject(function(checkmarkFilter) { ... }),从而获得访问我们想要测试的文件。参见angular.mock.inject()

    注意在注入的时候,后缀Filter会追加到你的筛选器名称中。参见筛选器指南?部分,在那里是概述。

    你现在必须在Karma选项卡中看到以下的输出:

    Chrome 22.0: Executed 4 of 4 SUCCESS (0.034 secs / 0.012 secs)

    实验

    • 让我们用一些内建的Angular筛选器来做实验,并把以下绑定添加到index.html

      • {{ "lower cap string" | uppercase }}
      • {{ {foo: "bar", baz: 23} | json }}
      • {{ 1304375948024 | date }}
      • {{ 1304375948024 | date:"MM/dd/yyyy @ h:mma" }}
    • 我们可以创建一个模块,带有一个输入元素,并把它与一个筛选绑定结合起来。向index.html添加以下代码:

      <input ng-model="userInput"> Uppercased: {{ userInput | uppercase }}

    总结

    现在你已经学会了如何编写并测试一个自定义筛选器,前往第十步 事件处理函数以学习我们可以如何用Angular继续丰富手机详情页面。

    事件处理函数

    在这一步中,你将添加一个可点击的手机图像交换器,指向手机详情页面。

    • 手机详情视图显示了当前手机的一张大图像以及若干张小的缩略图。如果我们可以通过在想要的缩略图中点吉,从而把大图像与任何小缩略图作替换,这会很棒。让我们看一看我们可以如何用Angular做到这。

    把工作空间重置到第十步

    git checkout -f step-10

    刷新你的浏览器或在线检查这一步:Step 10 Live Demo

    下面列出了第九步和第十步之间最重要的区别。你可以在GitHub上看到完整的差异。

    控制器

    app/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的值。

    模板

    app/partials/phone-detail.html:

    <img ng-src="{{mainImageUrl}}" class="phone">...<ul class="phone-thumbs">  <li ng-repeat="img in phone.images">    <img ng-src="{{img}}" ng-click="setImage(img)">  </li></ul>...

    我们把大图像的ngSrc指令绑定到mainImageUrl属性上。

    我们还将利用缩略图注册一个ngClick处理函数。当用户在缩图略之一上点击时,处理函数将使用setImage事件处理函数以改变mainImageUrl属性的值,把它变成缩略图的URL。

    TODO!

    测试

    要想验证这个功能,我们添加了两个端到端测试。一个验证了主图像被默认设置为每一个手机图像。另一个测试了在一些缩略图上的点击,并验证了相应的主图像改变。

    test/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.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();      ctrl = $controller('PhoneDetailCtrl', {$scope: scope});    }));    it('should fetch phone detail', function() {      expect(scope.phone).toBeUndefined();      $httpBackend.flush();      expect(scope.phone).toEqual(xyzPhoneData());    });  });

    你的单元测试现在应该通过了。

    实验

    • 让我们给PhoneDetailCtrl添加一个新的控制器方法:

      $scope.hello = function(name) {    alert('Hello ' + (name || 'world') + '!');}

      再添加

      <button ng-click="hello('Elmo')">Hello</button>

      to the phone-detail.html template.

    TODO! 控制器方法在控制器/作用域之间继承,因此你可以在`phone-list.html`模板内使用同样的片段。* 把`hello`方法从`PhoneCatCtrl`移到`PhoneListCtrl`,而且你将看到在`index.html`中声明的按钮将停止工作,与此同时在`phone-list.html`模板中声明的那个按钮依然在运作。

    总结

    随着手机图像交换器到位,我们准备前往第十一步 REST和自定义服务以学习取得数据的一个更好方法。

    REST和自定义服务

    在这一步中,你将改变我们获取数据的方法。

    • 我们定义了一个自定义服务,它代表了一个RESTful客户端。利用该客户端,我们可以用更容易的方式制作一个向服务器索取数据的请求,不需要去处理底层?$http API、HTTP方法以及URL。

    把工作空间重置到第十一步

    git checkout -f step-11

    刷新你的浏览器或在线检查这一步:Step 8 Live Demo

    下面列出了第十步和第十一步之间最重要的区别。你可以在GitHub上看到完整的差异。

    依赖性

    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。

    模板

    我们的自定义源服务将被定义在app/js/services.js中,因此我们需要在我们的布局模板中包含这个文件。另外,我们还需要载入angular-resouces.js文件,它包含了ngResource模块:

    app/index.html.

    ...  <script src="/attachments/image/wk/angularjs/angular-resource.js"></script>  <script src="/attachments/image/wk/angularjs/services.js"></script>...

    服务

    我们创建了自己的服务,以提供对服务器上的手机数据的访问:

    app/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.js.

    ...angular.module('phonecatApp', ['ngRoute', 'phonecatControllers','phonecatFilters', 'phonecatServices'])....

    我们需要把phonecatServices模块依赖性添加到phonecatApp模块的需要数列中。

    控制器

    通过重构掉底层的$http服务,我们简化了我们的子控制器(PhoneListCtrlPhoneDetailCtrl),用称为Phone的服务替代它。Angular的$resource服务比$http更容易使用,用来与作为REST的源对外提供的数据源交互。现在我们更容易理解控制器中的这些代码是干什么的了。

    app/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来演示它。

    测试

    因为我们现在使用了ngResource模块,为了用以angular为源更新Karma配置单文件,它是必要的,这样新测试才能通过。

    test/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匹配器,我们的测试将失败,因为测试值不能与响应严格匹配。要想解决这个问题,我们使用了一个新定义的toEqualData[Jasmine matcher][jasmine匹配器]。当toEqualData匹配器对比两个对象的时候,它考虑对象属性属性而忽略对象方法。

    test/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});    }));    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)

    总结

    现在我们已经看到了如何建立一个自定义的服务,作为REST的客户端,我们已经准备好前往第十二步 应用动画(最后一步)以学会如何用动画提高应用程序。

    应用动画

    在这最后一步中,我们将通过在我们之前创建的模板代码的顶部添加CSS和JavaScript动画丰富我们的手机分类网站应用。

    • 我们现在使用ngAnimate模拟以启用动画,以贯穿这个应用。
    • 我们还使用常用的ng指令以自动触发使动画接入的钩子。
    • 发现一个应用之后,动画将在标准DOM操作之间运行,该标准DOM操作在给定的时间内发布在元素上(例如,在ngRepeat上插入和移除节点,或在ngClass上添加和移除类)。

    把工作空间重置到第十二步

    git checkout -f step-12

    刷新你的浏览器或在线检查这一步:Step 12 Live Demo

    下面列出了第十一步和第十二步之间最重要的区别。你可以在GitHub上看到完整的差异。

    依赖性

    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的一个新版本,然后因为需要安装的angular.js版本之间的冲突,你可能在运行`bower install`指令时遇到问题。如果你遇到了这种问题,只需要在运行`npm install`之前先删除你的`app/bower_components`文件夹。
    **注意:**如果你已经全局安全了bower,你可以运行`bower install`,但是对于这个项目,我们已经预配置了运行`npm install`来运行bower。

    动画如何与ngAnimate协作

    要想知道动画如何与AngularJS协作,请先阅读?AngularJS动画指南

    模板

    在HTML模板代码内部需要修改,以链接asset文件,它定义了动画以及angular-animate.js文件。该动画模块,即ngAnimate,被定义在angular-animate.js内部,并包含了必要的代码,以使你的应用程序变得动感。

    这里是在索引文件中需要修改的地方:

    app/index.html.

    ...  <!-- for CSS Transitions and/or Keyframe Animations -->  <link rel="stylesheet" href="css/animations.css">  ...  <!-- jQuery is used for JavaScript animations (include this before angular.js) -->  <script src="/attachments/image/wk/angularjs/jquery.js"></script>  ...  <!-- required module to enable animation support in AngularJS -->  <script src="/attachments/image/wk/angularjs/angular-animate.js"></script>  <!-- for JavaScript Animations -->  <script src="/attachments/image/wk/angularjs/animations.js"></script>...
    **重要:**确保在使用Augular 1.4的时候,使用jQuery v2.1版或更新的版本;官方不支持jQuery v1.x版。确保在所有的AngularJS脚本之前载入jQuery,否则AugularJS不能侦测jQuery,而且动画将不会如预期那样起作用。

    可以在CSS代码(animations.css)内中创建动画,也可以在JavaScript代码(animations.js)内部创建动画。但是在开始之前,让我们创建一个新模块,它使用ngAnimate模块,作为依赖性,就像我们之前用ngResource所作的。

    模块和动画

    app/js/animations.js.

    angular.module('phonecatAnimations', ['ngAnimate']);  // ...  // this module will later be used to define animations  // ...

    现在让我们把这个模块附加到我们的应用程序模块上……

    app/js/app.js.

    // ...angular.module('phonecatApp', [  'ngRoute',  'phonecatAnimations',  'phonecatControllers',  'phonecatFilters',  'phonecatServices',]);// ...

    现在,手机分类模块已经有动感了。让我们制作更多更多动画吧!

    用CSS过渡动画让ngRepeat动起来。

    我们将从这一步开始,把CSS过渡动画添加到出现在phone-list.html网页上的ngRepeat指令。首先让我们把一个额外的CSS类添加到我们的重复元素上,因此我们可以把它与我们的CSS动画代码连接。

    app/partials/phone-list.html.

    <!--  让我们改变重复器HTML,以包含一个新的CSS类,之后我们将用它实现动画:--><ul class="phones">  <li ng-repeat="phone in phones | filter:query | orderBy:orderProp"      class="thumbnail phone-listing">    <a href="#/phones/{{phone.id}}" class="thumb"><img ng-src="{{phone.imageUrl}}"></a>    <a href="#/phones/{{phone.id}}">{{phone.name}}</a>    <p>{{phone.snippet}}</p>  </li></ul>

    注意我们将如何添加phone-listingCSS类?这是我们让动画起作用,在HTML代码中所需要做的。

    以下是实际的CSS过渡动画代码:

    app/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;  padding-top: 0;  padding-bottom: 0;}

    如你所见,我们的phone-listing CSS类与动画钩子相结合,当列表中插入项目或移除项目时,就会出现动画钩子。

    • 当列表中添加了一款新手机并呈现在网页上时,元素上应用了ng-enter类。
    • 当项目绕着列表移动时,元素上应用了ng-move类。
    • 当项目从列表中移除时,元素上应用了ng-leave类。

    添加或删除手机列表项目取决于传递给元素属性ng-repeat的数据。比如,如果过滤器数据改变了,项目动画地加入或退出重复列表。

    有些很重要的事情需要注意,当动画发生时,元素上添加了CSS类的两个集合:

    1. “开始”类代表动画开始时的样式。
    2. “激活”类代表动画结束时的样式。

    开始类的名称是被激发的事件(就像entermoveleave)的名称带上前缀ng-。所以一个enter事件将导致一个称为ng-enter类。

    激活类名与开始类名相同,但是带了一个后缀-active。这两类CSS命名公约允许开发员精心制作一个动画,自始至终。

    在我们上面的示例中,当元素添加到列表中时,该元素从0高度伸展到120像素高度;在从列表中移除之前,又收缩到0像素。同时还发生了一个渐现和渐消的效果。这里都是由CSS过渡动画处理的,CSS过渡动画声明在上面示例代码的顶部。

    虽然大多数现代浏览器能很好地支持CSS过渡CSS动画。但是如果你想让动画与老旧的浏览器后向兼容,请考虑使用基于JavaScript的动画,将在下面详细讲解它。

    用CSS关键帧动画让ngView动起来

    接下来,让我们为在ngView内部、路由之间的过渡添加一个动画。

    首先,让我们给HTML添加一个新的CSS类,就像我们在上面的示例中所作的。这一次,不是使用ng-repeat元素,而是把它添加到包含了ng-view指令的元素上。为了做到这,我们需要对HTML代码做一些小的改变,从而我们可以对我们的动画,在视图改变之间的动画有更多的控制。

    app/index.html.

    <div class="view-container">  <div ng-view class="view-frame"></div></div>

    利用这个改变,ng-view指令被嵌套在一个带有view-containerCSS类的父元素内部。这个类添加了一个position: relative样式,因此动画过程中,ng-view的定位相对于这个父元素。

    在这里,让我们为过渡动画添加CSS,添加到animations.css文件上:

    app/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; }}@-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-enterng-enter-active CSS类会被移除,导致它用它的默认CSS代码重新呈现、重新定位(因此没有一旦动画结束就没有绝对定位了)。这动作起来非常流畅,因此页面在路由变化时流动自然,不会有任何跳动感。

    应用的CSS类(开始和结束类)与ng-repeat很相像。每当一个新页面载入到ng-view指令中时,将创建它自己的一个副本,下载模板并追加内容。这确保了所有的视图都包含在一个单独的HTML元素中,该元素允许简单的动画控制。

    要想了解更多关于CSS动画的信息,请参阅Web 平台文档

    用JavaScript让ngClass动起来

    让我们向应用程序添加另一个动画。切换到phone-detail.html网页,我们看到已经有一个很棒的缩略图交换器。通过点击在网页列列中的缩略图,资料手机图像就变了。但是我们可以如何在改变它的同时添加动画呢?

    让我们先考虑一下。基本上,当你在一个资料图上点击时,你正在改变图像的状态,以反映新选中的缩略图。在HTML中指定状态改变的最佳方法是使用样式类。和以前很相像,我们使用的CSS样式类以指定指定一个动画,当CSS类本身变化时动画将发生。

    当选中了一个新的手机缩略图时,状态改变了,.activeCSS类添加到匹配的资料图像上,并播放了动画。

    让我们开始,在phone-detail.html网贾上微调HTML代码。注意我们已经改变了显示大图像的方式:

    app/partials/phone-detail.html.

    <!-- We're only changing the top of the file --><div class="phone-images">  <img ng-src="{{img}}"       class="phone"       ng-repeat="img in phone.images"       ng-class="{active:mainImageUrl==img}"></div><h1>{{phone.name}}</h1><p>{{phone.description}}</p><ul class="phone-thumbs">  <li ng-repeat="img in phone.images">    <img ng-src="{{img}}" ng-mouseenter="setImage(img)">  </li></ul>

    就像缩略图,我们使用迭代器来显示所有的资料图像作为一个列表,然而我们没有变动任何迭代相关的动画。而是,我们在ng-class指令上保持关注,因为每当active类变成true,则它将应用到元素上,将呈现为可见。否则,资料图像将隐藏。在我们的案例中,总是有一个元素具有active类,因此,任何时候总会有一款手机的资料图像在屏幕上可见。

    当元素上添加了激活类的时候,先添加了active-add类和adtive-add-active类,以指示Angular引发一个动画。当元素上移除了激活类的时候,元素上应用了active-remove类和active-remove-active,它们反过来又会触发别的动画。

    要想确保手机图像在页面第一次加载时正确地显示,我们还要微调详情页的CSS样式:

    app/css/app.css

    .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可用的动画。虽然我们可以那么做,但是还是让我们抓住机会学习如何用abnimate模块方法创建JavaScript可用的动画吧。

    app/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;    }    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回调函数和removeClass回调函数;在本案例中,注册过的类是.phone。当元素上添加了.active类(通过ng-class指令),将引发addClassJavaScript回调函数,该回调函数带有一个参数element。最后传入的参数是done回调函数。done回调函数的目的是,通过调用该函数,当JavaScript动画结束时,可以让Angular知道。

    removeClass回调函数以同样的方式起作用,但是是在一个类从元素上移除时触发它。

    在JavaScript回调函数中,你通过操纵DOM创建了该动画。在上面的代码中,这就是element.css()element.animate()所做的事情。回调函数用500px的偏移定位了下一个元素,把前一个项目和新的项目往上移500px,使两个项目一起动起来。这导致了一个仿传送带的动画。当animate函数完成它的工作,它会调用 done

    注意addClassremoveClass两者都返回了一个函数。这是一个可选的函数,当动画被取消时(同一个元素上发生了别的动画)时或者动画完成时,会调用这个函数。向这个函数传递一个布尔参数,该参数让开发者知道动画是否被取消了。当动画完成时,这个函数可以用来做一些扫尾工作。

    总结

    现在你学会了!我们在相对短的时间里创建了一个web应用。在完结篇中我们将讨论接下来何去何从。

    相关教程

    CSS教程

    完结篇

    我们的应用程序现在完成了。请随意练习这些代码,用git checkout命令跳回到前面的步骤。

    要想获得我们在本教程中涉及的更多的Angular概念的细节,以及Angular概念的示例,参见开发指导

    当你准备好开始用Angular开发一个项目的时候,我们推荐你用angular种子项目引导你的开发。

    我们希望这篇教程对你有用,使你对Angular有了足够的了解,激起你更大的学习愿望。我们特别希望你能够开发出自己的Angular Web应用,你可能对为Angular贡献产生兴趣。

    如果你有什么问题、反馈,或者想和我们打招呼,请在https://groups.google.com/forum/#!forum/angular上发消息吧。

    Angular是当下非常流行的前端框架,受到了广大前端开发者的喜爱。下面,51coolma将为大家列出一些经典的Angular面试题以及答案,供大家参考。

    timg (1)

    Q1、ng-if跟ng-show/hide的区别有哪些?

    A:区别主要有两点:

    1、ng-if 在后面表达式为 true 的时候才创建dom 节点,而ng-show 是在初始时就创建了,可以用 display:block 和 display:none 来控制显示和不显示。

    2、ng-if 会(隐式地)产生新作用域,ng-switch 、ng-include 等会动态创建一块界面的也是如此。

    这样会导致,在 ng-if 中用基本变量绑定 ng-model,同事在外层的 div 中将 model 绑定给另一个显示区域后,在内层改变时,外层并不会随着内层改变,因为这已经两个不同的变量了。

    <p>{{name}}</p><div ng-if="true">    <input type="text" ng-model="name"></div>

    而在 ng-show 中,却不存在此问题,因为它不自带一级作用域。

    为了避免这类问题的出现,我们可以始终将页面中的元素绑定到对象的属性(data.x),而不是直接绑定到基本变量(x)上。


    Q2、ng-repeat迭代数组的时候,如果数组中有相同值,会有什么问题,如何解决?

    A:会提示 Duplicates in a repeater are not allowed. 出现这种情况的时候,我们可以通过加 track by $index 来解决。当然,也可以 trace by 任何一个普通的值,只要能唯一性标识数组中的每一项即可(建立 dom 和数据之间的关联)。


    Q3、ng-click 中写的表达式,能使用 JS 原生对象上的方法吗?

    A:不止是 ng-click 中,只要是在页面中,都是无法直接调用原生的 JS 方法的,因为这些并不存在于与页面对应的 Controller 的 $scope 中。

    举个例子:

    <p>{{parseInt(55.66)}}<p>

    我们会发现,什么也没有显示。

    但如果在 $scope 中添加了下面这个函数:

    $scope.parseInt = function(x){    return parseInt(x);}

    运行就自然是没什么问题了。

    对于这种需求,使用一个 filter 或许是不错的选择:

    <p>{{13.14 | parseIntFilter}}</p>app.filter('parseIntFilter', function(){    return function(item){        return parseInt(item);    }})


    Q4、{{now | 'yyyy-MM-dd'}} 这种表达式里面,竖线和后面的参数通过什么方式可以自定义?

    A:filter,格式化数据,接收一个输入,按某规则处理,返回处理结果。


    ng 内置的filter主要有九种:

    1:date(日期)

    2:currency(货币)

    3:limitTo(限制数组或字符串长度)

    4:orderBy(排序)

    5:lowercase(小写)

    6:uppercase(大写)

    7:number(格式化数字,加上千位分隔符,并接收参数限定小数点位数)

    8:filter(处理一个数组,过滤出含有某个子串的元素)

    9:json(格式化 json 对象)


    filter 有两种使用方法:

    一种是直接在页面里:

    <p>{{now | date : 'yyyy-MM-dd'}}</p>

    另一种是在 js 里面用:

    // $filter('过滤器名称')(需要过滤的对象, 参数1, 参数2,...)$filter('date')(now, 'yyyy-MM-dd hh:mm:ss');

    自定义 filter

    // 形式app.filter('过滤器名称',function(){    return function(需要过滤的对象,过滤器参数1,过滤器参数2,...){        //...做一些事情          return 处理后的对象;    }});  // 栗子app.filter('timesFilter', function(){    return function(item, times){        var result = '';        for(var i = 0; i < times; i++){            result += item;        }        return result;    }})


    Q5、factory、service 和 provider 是什么关系?

    factory

    把 service 的方法和数据放在一个对象里,并返回这个对象。

    app.factory('FooService', function(){  return {    target: 'factory',    sayHello: function(){      return 'hello ' + this.target;    }  }});

    service

    通过构造函数的方式创建 service,然后返回一个实例化对象。

    app.service('FooService', function(){  var self = this;  this.target = 'service';  this.sayHello = function(){    return 'hello ' + self.target;  }});

    provider

    创建一个可通过 config 配置的 service,$get 中返回的,就是用 factory 创建 service 的内容。

    app.provider('FooService', function(){  this.configData = 'init data';  this.setConfigData = function(data){    if(data){      this.configData = data;    }  }  this.$get = function(){    var self = this;    return {      target: 'provider',      sayHello: function(){        return self.configData + ' hello ' + this.target;      }    }  }}); // 此处注入的是 FooService 的 providerapp.config(function(FooServiceProvider){  FooServiceProvider.setConfigData('config data');});

    从底层实现上来看,三者的关系是:service 调用了 factory,返回其实例;factory 调用了 provider,返回其 $get 中定义的内容。factory 和 service 的功能类似,但是 factory 是普通 function,可以返回任何东西;service 是构造器,可以不返回(绑定到 this 的都可以被访问);provider 是加强版 factory,返回一个可配置的 factory。


    Q6、angular 的数据绑定采用什么机制?详述原理

    A:采用了脏检查机制。

    双向数据绑定是 AngularJS 的核心机制之一。当 view 中有任何一个数据变化时,都会更新到 model 中。如果 model 中的数据有变化时,view 也会同步更新,显然,这需要一个监控。

    原理

    Angular 在 scope 模型上设置了一个监听队列,这个监听队列可以用来监听数据变化并更新 view 。每次绑定一个东西到 view 上时, AngularJS 就会往 $watch 队列里插入一条 $watch ,用来检测它监视的 model 里是否有变化的东西。当浏览器接收到可以被 angular context 处理的事件时, $digest 循环就会触发,遍历所有的 $watch ,最后更新 dom。


    Q7、两个平级界面块 a 和 b,如果 a 中触发一个事件,有哪些方式能让 b 知道?详述原理

    A:这个问题换一种说法就是,如何在平级界面模块间进行通信。有两种方法,一种是共用服务,一种是基于事件。

    共用服务

    在 Angular 中,通过 factory 可以生成一个单例对象,在需要通信的模块 a 和 b 中注入这个对象即可。

    基于事件

    这个又分两种方式

    第一种是借助父 controller。在子 controller 中向父 controller 触发( $emit )一个事件,然后在父 controller 中监听( $on )事件,再广播( $broadcast )给子 controller ,这样通过事件携带的参数,实现了数据经过父 controller,在同级 controller 之间传播。

    第二种是借助 $rootScope 。每个 Angular 应用默认有一个根作用域 $rootScope , 根作用域位于最顶层,从它往下挂着各级作用域。所以,如果子控制器直接使用 $rootScope 广播和接收事件,那么就可实现同级之间的通信。


    Q8、一个 angular 应用应当如何良好地分层?

    目录结构的划分

    对于小型项目,可以按照文件类型组织,比如:

    cssjs controllers models services filterstemplates 

    但是对于规模较大的项目,最好按业务模块划分,比如:

    cssmodules account  controllers  models  services  filters  templates disk  controllers  models  services  filters  templates

    modules 下最好再有一个 common 目录来存放公共的东西。

    逻辑代码的拆分

    作为一个 MVVM 框架,Angular 应用本身就应该按照 模型,视图模型(控制器),视图来划分。

    这里逻辑代码的拆分,主要是指尽量让 controller 这一层很薄。提取共用的逻辑到 service 中 (比如后台数据的请求,数据的共享和缓存,基于事件的模块间通信等),提取共用的界面操作到 directive 中(比如将日期选择、分页等封装成组件等),提取共用的格式化操作到 filter 中等等。

    在复杂的应用中,也可以为实体建立对应的构造函数,比如硬盘(Disk)模块,可能有列表、新建、详情这样几个视图,并分别对应的有 controller,那么可以建一个 Disk 构造函数,里面完成数据的增删改查和验证操作,有跟 Disk 相关的 controller,就注入 Disk 构造器并生成一个实例,这个实例就具备了增删改查和验证方法。这样既层次分明,又实现了复用(让 controller 层更薄了)。


    Q9、angular 应用常用哪些路由库,各自的区别是什么?

    A:Angular1.x 中常用 ngRoute 和 ui.router。

    区别

    ngRoute 模块是 Angular 自带的路由模块,而 ui.router 模块是基于 ngRoute模块开发的第三方模块。

    ui.router 是基于 state (状态)的, ngRoute 是基于 url 的,ui.router模块具有更强大的功能,主要体现在视图的嵌套方面。

    使用 ui.router 能够定义有明确父子关系的路由,并通过 ui-view 指令将子路由模版插入到父路由模板的 <div ui-view></div> 中去,从而实现视图嵌套。而在 ngRoute 中不能这样定义,如果同时在父子视图中 使用了 <div ng-view></div> 会陷入死循环。

    示例

    ngRoute

    var app = angular.module('ngRouteApp', ['ngRoute']);app.config(function($routeProvider){  $routeProvider    .when('/main', {      templateUrl: "main.html",      controller: 'MainCtrl'    })    .otherwise({ redirectTo: '/tabs' });

    ui.router

    var app = angular.module("uiRouteApp", ["ui.router"]);app.config(function($urlRouterProvider, $stateProvider){  $urlRouterProvider.otherwise("/index");  $stateProvider    .state("Main", {      url: "/main",      templateUrl: "main.html",      controller: 'MainCtrl'    })


    Q10、分属不同团队进行开发的 angular 应用,如果要做整合,可能会遇到哪些问题,如何解决?

    A:可能会遇到不同模块之间的冲突。

    比如一个团队所有的开发在 moduleA 下进行,另一团队开发的代码在 moduleB 下

    angular.module('myApp.moduleA', [])  .factory('serviceA', function(){    ...  })   angular.module('myApp.moduleB', [])  .factory('serviceA', function(){    ...  })     angular.module('myApp', ['myApp.moduleA', 'myApp.moduleB'])  

    会导致两个 module 下面的 serviceA 发生了覆盖。

    貌似在 Angular1.x 中并没有很好的解决办法,所以最好在前期进行统一规划,做好约定,严格按照约定开发,每个开发人员只写特定区块代码。


    Q11、angular 的缺点有哪些?

    强约束

    导致学习成本较高,对前端不友好。

    但遵守 AngularJS 的约定时,生产力会很高,对 Java 程序员友好。

    不利于SEO

    因为所有内容都是动态获取并渲染生成的,搜索引擎没法爬取。

    一种解决办法是,对于正常用户的访问,服务器响应 AngularJS 应用的内容;对于搜索引擎的访问,则响应专门针对 SEO 的HTML页面。

    性能问题

    作为 MVVM 框架,因为实现了数据的双向绑定,对于大数组、复杂对象会存在性能问题。

    可以用来 优化 Angular 应用的性能 的办法:

    减少监控项(比如对不会变化的数据采用单向绑定)

    主动设置索引(指定 track by ,简单类型默认用自身当索引,对象默认使用 $$hashKey ,比如改为 track by item.id )

    降低渲染数据量(比如分页,或者每次取一小部分数据,根据需要再取)

    数据扁平化(比如对于树状结构,使用扁平化结构,构建一个 map 和树状数据,对树操作时,由于跟扁平数据同一引用,树状数据变更会同步到原始的扁平数据)

    另外,对于Angular1.x ,存在 脏检查 和 模块机制 的问题。


    Q12、如何看待 angular 1.2 中引入的 controller as 语法?

    A:在 angular 1.2 以前,在 view 上的任何绑定都是直接绑定在 $scope 上的

    function myCtrl($scope){  $scope.a = 'aaa';  $scope.foo = function(){    ...  }}

    使用 controllerAs,不需要再注入 $scope ,controller 变成了一个很简单的 javascript 对象(POJO),一个更纯粹的 ViewModel。

    function myCtrl(){  // 使用 vm 捕获 this 可避免内部的函数在使用 this 时导致上下文改变  var vm = this;  vm.a = 'aaa';}


    Angularjs和jQuery是当前两个比较好用的前端开发工具,下面就简单来说说这两者之间的区别吧。


    概念不同:

    从概念上看,Angularjs属于前端框架,我们写的代码都被其调用;而jQuery则是一个封装了很多功能的库,我们需要调用jQuery中的API去实现一些功能。


    开发模式存在差异

    AngularJS与jQuery在web应用开发模式上有着完全不同的思路:

    jQuery:通过显示操作浏览器中的DOM来创建应用程序(适合回合式应用),对于比较大型且复杂项目,jQuery不利于全面的单元测试;

    AngularJS:将浏览器吸收为应用程序的基础(适合单页应用),比较适合大型及复杂项目,对于全面的单元测试也相对比较容易。


    什么是会和应用和单页应用?


    回合式应用

    在Web应用发展刚起步时,很多开发者都用的是回合式应用。

    相较于单页应用,它就相当于多页应用吧。

    优点:对浏览器的要求少,对客户端的支持比较大;

    缺点:加载等待时间较长,在每次请求加载时,它需要后端服务器来处理所有请求并管理所有的应用程序状态,这就占用了许多带宽。(这时因为每个HTML文档必须是自包含的,也就是每次更新页面,都要把HTML文档中更新的和未更新的内容完整的发送给服务器端,这就导致从服务器产生的每个响应中包含许多相同的内容)。


    单页应用

    该模式下只有一个初始的HTML文档被发送给浏览器,用户交互所产生的Ajax(异步)请求只会请求较小的HTML片段,或者要插入到已有的显示给用户元素中的数据。

    而初始的HTML文档不会被再次加载或者替换,这就造成了Ajax请求被异步执行的同时,用户还可以继续与已有的HTML进行交互,而无需等待。

    优点: 减少向后端请求的数据量,减少了带宽压力,提升了整体web的性能。

    缺点:对浏览器的性能的要求变高,某些浏览器可能会存在不兼容。


    AngularJS与jQuery的适用领域

    1

    上图很明确地阐述了AngularJS与jQuery的区别:

    在项目复杂度比较低时,比较适合使用jQuery框架,而随着项目复杂度的提升,jQuery框架会被逐渐被以单页应用和复杂的回合式应用见长的AngularJS框架所取代。


    推荐阅读;

    jQuery微课——理论实践一把抓

    AngularJS教程

    jQuery教程


    angularjs和vuejs都是MVVM框架,那么这两者有什么区别呢?


    QQ截图20180424103103


    angularjs和vue的区别:

    1、vueJS简单易学,而angularJS的上手较高;

    2、vue专注于View层,主打的是快速便捷,而angularJS功能则比较全面,当然体量也较大,相对没有vue那么便捷;

    3、angularJS的指令都是ng-xxx,而vueJS的指令都是v-xxx;

    4、angularJS的所有指令和方法都是绑定在$scope上的,而vueJS是new出来一个实例,所有的方法和指令都在这个实例上,一个页面上可以有多个vue实例,但是angularJS的对象只能有一个;

    5、angularJS是由google开发和维护的,vueJS是由个人维护的;

    6、vueJS一般用于移动端的开发,而angularJS一般应用于大型的项目


    推荐阅读:

    angularJS教程

    vueJS教程


    AngularJS 教程



    AngularJS 是一款优秀的前端 JS 框架。

    AngularJS 通过新的属性和表达式扩展了 HTML。

    AngularJS 可以构建一个单一页面应用程序(SPAs:Single Page Applications)。

    AngularJS 学习起来非常简单。

    现在开始学习 AngularJS!



    每个章节都有相应的实例

    在每个章节中,您可以在线编辑实例,然后点击按钮查看结果。

    AngularJS 实例

    <!DOCTYPE html>
    <html>

    <body>

    <div ng-app="">
      <p>在输入框中尝试输入:</p>
      <p>姓名:<input type="text" ng-model="name"></p>
      <p ng-bind="name"></p>
    </div>

    <script src="//www.51coolma.cn/try/angularjs/1.2.5/angular.min.js"></script>

    </body>
    </html>

    尝试一下 »


    阅读本教程前,您需要了解的知识:

    在开始学习 AngularJS 之前,您需要具备以下基础知识:


    AngularJS 历史

    AngularJS 是比较新的技术,版本 1.0 是在 2012 年发布的。

    AngularJS 是由 Google 的员工 Miško Hevery 从 2009 年开始着手开发。

    这是一个非常好的构想,该项目目前已由 Google 正式支持,有一个全职的开发团队继续开发和维护这个库。


    AngularJS 实例

    本教程包含了大量的 AngularJS 实例!

    AngularJS 实例


    AngularJS 参考手册

    参考手册包含了本教程中使用到的所有指令和过滤器。

    AngularJS 参考手册

    AngularJS 简介


    AngularJS 是一个 JavaScript 框架。它可通过 <script> 标签添加到 HTML 页面。

    AngularJS 通过 指令 扩展了 HTML,且通过 表达式 绑定数据到 HTML。


    AngularJS 是一个 JavaScript 框架

    AngularJS 是一个 JavaScript 框架。它是一个以 JavaScript 编写的库。

    AngularJS 是以一个 JavaScript 文件形式发布的,可通过 script 标签添加到网页中:

    <script src="http://apps.bdimg.com/libs/angular.js/1.4.6/angular.min.js" rel="external nofollow" rel="external nofollow" rel="external nofollow" ></script>
    Note 我们建议把脚本放在 <body> 元素的底部。
    这会提高网页加载速度,因为 HTML 加载不受制于脚本加载。

    AngularJS 扩展了 HTML

    AngularJS 通过 ng-directives 扩展了 HTML。

    ng-app 指令定义一个 AngularJS 应用程序。

    ng-model 指令把元素值(比如输入域的值)绑定到应用程序。

    ng-bind 指令把应用程序数据绑定到 HTML 视图。

    AngularJS 实例

    <!DOCTYPE html>
    <html>
    <body>

    <div ng-app="">
      <p>在输入框中尝试输入:</p>
      <p>姓名:<input type="text" ng-model="name"></p>
      <p ng-bind="name"></p>
    </div>

    <script src="http://apps.bdimg.com/libs/angular.js/1.4.6/angular.min.js" rel="external nofollow" rel="external nofollow" rel="external nofollow" ></script>

    </body>
    </html>

    尝试一下 »

    实例讲解:

    当网页加载完毕,AngularJS 自动开启。

    ng-app 指令告诉 AngularJS,<div> 元素是 AngularJS 应用程序 的"所有者"。

    ng-model 指令把输入域的值绑定到应用程序变量 name

    ng-bind 指令把应用程序变量 name 绑定到某个段落的 innerHTML。

    Note 如果您移除了 ng-app 指令,HTML 将直接把表达式显示出来,不会去计算表达式的结果。

    什么是 AngularJS?

    "AngularJS 是专门为应用程序设计的 HTML。"

    AngularJS 使得开发现代的单一页面应用程序(SPAs:Single Page Applications)变得更加容易。

    • AngularJS 把应用程序数据绑定到 HTML 元素。
    • AngularJS 可以克隆和重复 HTML 元素。
    • AngularJS 可以隐藏和显示 HTML 元素。
    • AngularJS 可以在 HTML 元素"背后"添加代码。
    • AngularJS 支持输入验证。

    AngularJS 指令

    正如您所看到的,AngularJS 指令是以 ng 作为前缀的 HTML 属性。

    ng-init 指令初始化 AngularJS 应用程序变量。

    AngularJS 实例

    <div ng-app="" ng-init="firstName='John'">

    <p>姓名为 <span ng-bind="firstName"></span></p>

    </div>

    尝试一下 »
    Note HTML5 允许扩展的(自制的)属性,以 data- 开头。
    AngularJS 属性以 ng- 开头,但是您可以使用 data-ng- 来让网页对 HTML5 有效。

    带有有效的 HTML5:

    AngularJS 实例

    <div data-ng-app="" data-ng-init="firstName='John'">

    <p>姓名为 <span data-ng-bind="firstName"></span></p>

    </div>

    尝试一下 »


    AngularJS 表达式

    AngularJS 表达式写在双大括号内:{{ expression }}

    AngularJS 表达式把数据绑定到 HTML,这与 ng-bind 指令有异曲同工之妙。

    AngularJS 将在表达式书写的位置"输出"数据。

    AngularJS 表达式 很像 JavaScript 表达式:它们可以包含文字、运算符和变量。

    实例 {{ 5 + 5 }} 或 {{ firstName + " " + lastName }}

    AngularJS 实例

    <!DOCTYPE html>
    <html>
    <body>

    <div ng-app="">
      <p>我的第一个表达式: {{ 5 + 5 }}</p>
    </div>

    <script src="http://apps.bdimg.com/libs/angular.js/1.4.6/angular.min.js" rel="external nofollow" rel="external nofollow" rel="external nofollow" ></script>

    </body>
    </html>

    尝试一下 »

    AngularJS 表达式

    本节介绍了 AngularJS 表达式的作用与使用方法。

    AngularJS 使用 表达式 把数据绑定到 HTML。


    AngularJS 表达式

    AngularJS 表达式写在双大括号内:{{ expression }}

    AngularJS 表达式把数据绑定到 HTML,这与 ng-bind 指令有异曲同工之妙。

    AngularJS 将在表达式书写的位置"输出"数据。

    AngularJS 表达式 很像 JavaScript 表达式:它们可以包含文字、运算符和变量。

    实例 {{ 5 + 5 }} 或 {{ firstName + " " + lastName }}


    AngularJS 数字

    AngularJS 数据就像 JavaScript 数字:

    AngularJS 实例

    <div ng-app="" ng-init="quantity=1;cost=5">

    <p>总价: {{ quantity * cost }}</p>

    </div>

    尝试一下 »

    使用 ng-bind 的相同实例:

    AngularJS 实例

    <div ng-app="" ng-init="quantity=1;cost=5">

    <p>总价: <span ng-bind="quantity * cost"></span></p>

    </div>

    尝试一下 »
    Note 使用 ng-init 不是很常见。您将在控制器一章中学习到一个更好的初始化数据的方式。

    AngularJS 字符串

    AngularJS 字符串就像 JavaScript 字符串

    AngularJS 实例

    <div ng-app="" ng-init="firstName='John';lastName='Doe'">

    <p>姓名: {{ firstName + " " + lastName }}</p>

    </div>

    尝试一下 »

    使用 ng-bind 的相同实例:

    AngularJS 实例

    <div ng-app="" ng-init="firstName='John';lastName='Doe'">

    <p>姓名: <span ng-bind="firstName + ' ' + lastName"></span></p>

    </div>

    尝试一下 »


    AngularJS 对象

    AngularJS 对象就像 JavaScript 对象

    AngularJS 实例

    <div ng-app="" ng-init="person={firstName:'John',lastName:'Doe'}">

    <p>姓为 {{ person.lastName }}</p>

    </div>

    尝试一下 »

    使用 ng-bind 的相同实例:

    AngularJS 实例

    <div ng-app="" ng-init="person={firstName:'John',lastName:'Doe'}">

    <p>姓为 <span ng-bind="person.lastName"></span></p>

    </div>

    尝试一下 »


    AngularJS 数组

    AngularJS 数组就像 JavaScript 数组:

    AngularJS 实例

    <div ng-app="" ng-init="points=[1,15,19,2,40]">

    <p>第三个值为 {{ points[2] }}</p>

    </div>

    尝试一下 »

    使用 ng-bind 的相同实例:

    AngularJS 实例

    <div ng-app="" ng-init="points=[1,15,19,2,40]">

    <p>第三个值为 <span ng-bind="points[2]"></span></p>

    </div>

    尝试一下 »

    AngularJS 指令

    本节为你介绍 AngularJS 使用的一些指令。

    AngularJS 通过被称为 指令 的新属性来扩展 HTML。


    AngularJS 指令

    AngularJS 指令是扩展的 HTML 属性,带有前缀 ng-

    ng-app 指令初始化一个 AngularJS 应用程序。

    ng-init 指令初始化应用程序数据。

    ng-model 指令把元素值(比如输入域的值)绑定到应用程序。

    AngularJS 实例

    <div ng-app="" ng-init="firstName='John'">

      <p>在输入框中尝试输入:</p>
      <p>姓名:<input type="text" ng-model="firstName"></p>
      <p>你输入的为: {{ firstName }}</p>

    </div>

    尝试一下 »

    ng-app 指令告诉 AngularJS,<div> 元素是 AngularJS 应用程序 的"所有者"。

    Note一个网页可以包含多个运行在不同元素中的 AngularJS 应用程序。

    数据绑定

    上面实例中的 {{ firstName }} 表达式是一个 AngularJS 数据绑定表达式。

    AngularJS 中的数据绑定,同步了 AngularJS 表达式与 AngularJS 数据。

    {{ firstName }} 是通过 ng-model="firstName" 进行同步。

    在下一个实例中,两个文本域是通过两个 ng-model 指令同步的:

    AngularJS 实例

    <div ng-app="" ng-init="quantity=1;price=5">

    <h2>价格计算器</h2>

    数量: <input type="number" ng-model="quantity">
    价格: <input type="number" ng-model="price">

    <p><b>总价:</b> {{ quantity * price }}</p>

    </div>

    尝试一下 »
    Note使用 ng-init 不是很常见。您将在控制器一章中学习到一个更好的初始化数据的方式。

    重复 HTML 元素

    ng-repeat 指令会重复一个 HTML 元素:

    AngularJS 实例

    <div ng-app="" ng-init="names=['Jani','Hege','Kai']">
      <p>使用 ng-repeat 来循环数组</p>
      <ul>
        <li ng-repeat="x in names">
          {{ x }}
        </li>
      </ul>
    <div>

    尝试一下 »

    ng-repeat 指令用在一个对象数组上:

    AngularJS 实例

    <div ng-app="" ng-init="names=[
    {name:'Jani',country:'Norway'},
    {name:'Hege',country:'Sweden'},
    {name:'Kai',country:'Denmark'}]">

    <p>循环对象:</p>
    <ul>
      <li ng-repeat="x in names">
        {{ x.name + ', ' + x.country }}
      </li>
    </ul>

    </div>

    尝试一下 »
    NoteAngularJS 完美支持数据库的 CRUD(增加Create、读取Read、更新Update、删除Delete)应用程序。
    把实例中的对象想象成数据库中的记录。

    ng-app 指令

    ng-app 指令定义了 AngularJS 应用程序的 根元素

    ng-app 指令在网页加载完毕时会自动引导(自动初始化)应用程序。

    稍后您将学习到 ng-app 如何通过一个值(比如 ng-app="myModule")连接到代码模块。


    ng-init 指令

    ng-init 指令为 AngularJS 应用程序定义了 初始值

    通常情况下,不使用 ng-init。您将使用一个控制器或模块来代替它。

    稍后您将学习更多有关控制器和模块的知识。


    ng-model 指令

    ng-model 指令 绑定 HTML 元素 到应用程序数据。

    ng-model 指令也可以:

    • 为应用程序数据提供类型验证(number、email、required)。
    • 为应用程序数据提供状态(invalid、dirty、touched、error)。
    • 为 HTML 元素提供 CSS 类。
    • 绑定 HTML 元素到 HTML 表单。

    ng-repeat 指令

    ng-repeat 指令对于集合中(数组中)的每个项会 克隆一次 HTML 元素

    AngularJS Scope(作用域)

    本节为你介绍了什么是 AngularJS Scope(作用域)以及 Scope 在 AngularJS 应该如何使用。

    Scope(作用域) 是应用在 HTML (视图) 和 JavaScript (控制器)之间的纽带。

    Scope 是一个对象,有可用的方法和属性。

    Scope 可应用在视图和控制器上。


    如何使用 Scope

    当你在 AngularJS 创建控制器时,你可以将 $scope 对象当作一个参数传递:

    AngularJS 实例

    控制器中的属性对应了视图上的属性:

    <div ng-app="myApp" ng-controller="myCtrl">

    <h1>{{carname}}</h1>

    </div>

    <script>
    var app = angular.module('myApp', []);

    app.controller('myCtrl', function($scope) {
        $scope.carname = "Volvo";
    });
    </script>

    尝试一下 »

    当在控制器中添加 $scope 对象时,视图 (HTML) 可以获取了这些属性。

    视图中,你不需要添加 $scope 前缀, 只需要添加属性名即可,如: {{carname}}


    Scope 概述

    AngularJS 应用组成如下:

    • View(视图), 即 HTML。
    • Model(模型), 当前视图中可用的数据。
    • Controller(控制器), 即 JavaScript 函数,可以添加或修改属性。

    scope 是模型。

    scope 是一个 JavaScript 对象,带有属性和方法,这些属性和方法可以在视图和控制器中使用。

    AngularJS 实例

    如果你修改了视图,模型和控制器也会相应更新:

    <div ng-app="myApp" ng-controller="myCtrl">

    <input ng-model="name">

    <h1>我的名字是 {{name}}</h1>

    </div>

    <script>
    var app = angular.module('myApp', []);

    app.controller('myCtrl', function($scope) {
        $scope.name = "John Dow";
    });
    </script>

    尝试一下 »

    Scope 作用范围

    了解你当前使用的 scope 是非常重要的。

    在以上两个实例中,只有一个作用域 scope,所以处理起来比较简单,但在大型项目中, HTML DOM 中有多个作用域,这时你就需要知道你使用的 scope 对应的作用域是哪一个。

    AngularJS 实例

    当我们使用 ng-repeat 指令时,每个重复项都访问了当前的重复对象:

    <div ng-app="myApp" ng-controller="myCtrl">

    <ul>
        <li ng-repeat="x in names">{{x}}</li>
    </ul>

    </div>

    <script>
    var app = angular.module('myApp', []);

    app.controller('myCtrl', function($scope) {
        $scope.names = ["Emil", "Tobias", "Linus"];
    });
    </script>

    尝试一下 »

    每个 <li> 元素可以访问当前的重复对象,这里对应的是一个字符串, 并使用变量 x 表示。


    根作用域

    所有的应用都有一个 $rootScope,它可以作用在 ng-app 指令包含的所有 HTML 元素中。

    $rootScope 可作用于整个应用中。是各个 controller 中 scope 的桥梁。用 rootscope 定义的值,可以在各个 controller 中使用。

    AngularJS 实例

    创建控制器时,将 $rootScope 作为参数传递,可在应用中使用:

    <div ng-app="myApp" ng-controller="myCtrl"><h1>{{lastname}} 家族成员:</h1><ul>    <li ng-repeat="x in names">{{x}} {{lastname}}</li></ul></div><script>var app = angular.module('myApp', []);app.controller('myCtrl', function($scope, $rootScope) {    $scope.names = ["Emil", "Tobias", "Linus"];    $rootScope.lastname = "Refsnes";});</script>
    尝试一下 »

    AngularJS 控制器

    AngularJS 控制器在<div>内由 ng-controller 指令定义。

     AngularJS 控制器 控制 AngularJS 应用程序的数据。

     AngularJS 控制器是常规的 JavaScript 对象


    AngularJS 控制器

    AngularJS 应用程序被控制器控制。

    ng-controller 指令定义了应用程序控制器。

    控制器是 JavaScript 对象,由标准的 JavaScript 对象的构造函数 创建。

    AngularJS 实例

    <div ng-app="myApp" ng-controller="myCtrl">

    名: <input type="text" ng-model="firstName"><br>
    姓: <input type="text" ng-model="lastName"><br>
    <br>
    姓名: {{firstName + " " + lastName}}

    </div>

    <script>
    var app = angular.module('myApp', []);
    app.controller('myCtrl', function($scope) {
        $scope.firstName = "John";
        $scope.lastName = "Doe";
    });
    </script>

    尝试一下 »

    应用解析:

    AngularJS 应用程序由 ng-app 定义。应用程序在 <div> 内运行。

    ng-controller="myCtrl" 属性是一个 AngularJS 指令。用于定义一个控制器。

    myCtrl 函数是一个 JavaScript 函数。

    AngularJS 使用$scope 对象来调用控制器。

    在 AngularJS 中, $scope 是一个应用象(属于应用变量和函数)。

    控制器的 $scope (相当于作用域、控制范围)用来保存AngularJS Model(模型)的对象。

    控制器在作用域中创建了两个属性(firstNamelastName)。

    ng-model 指令绑定输入域到控制器的属性(firstName 和 lastName)。


    控制器方法

    上面的实例演示了一个带有 lastName 和 firstName 这两个属性的控制器对象。

    控制器也可以有方法(变量和函数):

    AngularJS 实例

    <div ng-app="myApp" ng-controller="personCtrl">

    名: <input type="text" ng-model="firstName"><br>
    姓: <input type="text" ng-model="lastName"><br>
    <br>
    姓名: {{fullName()}}

    </div>

    <script>
    var app = angular.module('myApp', []);
    app.controller('personCtrl', function($scope) {
        $scope.firstName = "John";
        $scope.lastName = "Doe";
        $scope.fullName = function() {
            return $scope.firstName + " " + $scope.lastName;
        }
    });
    </script>

    尝试一下 »


    外部文件中的控制器

    在大型的应用程序中,通常是把控制器存储在外部文件中。

    只需要把 <script> 标签中的代码复制到名为 personController.js 的外部文件中即可:

    AngularJS 实例

    <div ng-app="myApp" ng-controller="personController">

    名: <input type="text" ng-model="person.firstName"><br>
    姓: <input type="text" ng-model="person.lastName"><br>
    <br>
    姓名: {{person.firstName + " " + person.lastName}}

    </div>

    <script src="personController.js"></script>

    尝试一下 »


    其他实例

    以下实例创建一个新的控制器文件:

    angular.module('myApp', []).controller('namesCtrl', function($scope) {
        $scope.names = [
            {name:'Jani',country:'Norway'},
            {name:'Hege',country:'Sweden'},
            {name:'Kai',country:'Denmark'}
        ];
    }); 

    保存文件为  namesController.js:

    然后,在应用中使用控制器文件:

    AngularJS 实例

    <div ng-app="myApp" ng-controller="namesCtrl">

    <ul>
     <li ng-repeat="x in names">
        {{ x.name + ', ' + x.country }}
      </li>
    </ul>

    </div>

    <script src="namesController.js"></script>

    尝试一下 »

    AngularJS 过滤器

    AngularJS 过滤器可以用来格式化数据,过滤器能够用在表达式和指令中。

    过滤器可以使用一个管道字符(|)添加到表达式和指令中。


    AngularJS 过滤器

    AngularJS 过滤器可用于转换数据:

    过滤器描述
    currency格式化数字为货币格式。
    filter从数组项中选择一个子集。
    lowercase格式化字符串为小写。
    orderBy根据某个表达式排列数组。
    uppercase格式化字符串为大写。


    向表达式添加过滤器

    过滤器可以通过一个管道字符(|)和一个过滤器添加到表达式中。

    (下面的两个实例,我们将使用前面章节中提到的 person 控制器)

    uppercase 过滤器格式化字符串为大写:

    AngularJS 实例

    <div ng-app="myApp" ng-controller="personCtrl">

    <p>姓名为 {{ lastName | uppercase }}</p>

    </div>

    尝试一下 »

    lowercase 过滤器格式化字符串为小写:

    AngularJS 实例

    <div ng-app="myApp" ng-controller="personCtrl">

    <p>姓名为 {{ lastName | lowercase }}</p>

    </div>

    尝试一下 »


    currency 过滤器

    currency 过滤器格式化数字为货币格式:

    AngularJS 实例

    <div ng-app="myApp" ng-controller="costCtrl">

    <input type="number" ng-model="quantity">
    <input type="number" ng-model="price">

    <p>总价 = {{ (quantity * price) | currency }}</p>

    </div>

    尝试一下 »


    向指令添加过滤器

    过滤器可以通过一个管道字符(|)和一个过滤器添加到指令中。

    orderBy 过滤器根据某个表达式排列数组:

    AngularJS 实例

    <div ng-app="myApp" ng-controller="namesCtrl">

    <ul>
      <li ng-repeat="x in names | orderBy:'country'">
        {{ x.name + ', ' + x.country }}
      </li>
    </ul>

    <div>

    尝试一下 »


    过滤输入

    输入过滤器可以通过一个管道字符(|)和一个过滤器添加到指令中,该过滤器后跟一个冒号和一个模型名称。

    filter 过滤器从数组中选择一个子集:

    AngularJS 实例

    <div ng-app="myApp" ng-controller="namesCtrl">

    <p><input type="text" ng-model="test"></p>

    <ul>
      <li ng-repeat="x in names | filter:test | orderBy:'country'">
        {{ (x.name | uppercase) + ', ' + x.country }}
      </li>
    </ul>

    </div>

    尝试一下 »


    AngularJS 服务(Service)

    AngularJS 中的服务是一个函数或对象。

    AngularJS 中你可以创建自己的服务,或使用内建服务。


    什么是服务?

    在 AngularJS 中,服务是一个函数或对象,可在你的 AngularJS 应用中使用。

    AngularJS 内建了30 多个服务。

    有个 $location 服务,它可以返回当前页面的 URL 地址。

    实例

    var app = angular.module('myApp', []);
    app.controller('customersCtrl', function($scope, $location) {
        $scope.myUrl = $location.absUrl();
    });

    尝试一下 »

    注意 $location 服务是作为一个参数传递到 controller 中。如果要使用它,需要在 controller 中定义。


    为什么使用服务?

    $http 是 AngularJS 应用中最常用的服务。服务向服务器发送请求,应用响应服务器传送过来的数据。

    AngularJS 会一直监控应用,处理事件变化, AngularJS 使用 $location 服务比使用 window.location 对象更好。


    $http 服务

    $http 是 AngularJS 应用中最常用的服务。 服务向服务器发送请求,应用响应服务器传送过来的数据。

    实例

    使用 $http 服务向服务器请求数据:

    var app = angular.module('myApp', []);
    app.controller('myCtrl', function($scope, $http) {
        $http.get("welcome.htm").then(function (response) {
            $scope.myWelcome = response.data;
        });
    });

    尝试一下 »

    以上是一个非常简单的 $http 服务实例,更多 $http 服务应用请查看 AngularJS Http 教程


    $timeout 服务

    AngularJS $timeout 服务对应了 JS window.setTimeout 函数。

    实例

    两秒后显示信息:

    var app = angular.module('myApp', []);
    app.controller('myCtrl', function($scope, $timeout) {
        $scope.myHeader = "Hello World!";
        $timeout(function () {
            $scope.myHeader = "How are you today?";
        }, 2000);
    });

    尝试一下 »

    $interval 服务

    AngularJS $interval 服务对应了 JS window.setInterval 函数。

    实例

    每一秒显示信息:

    var app = angular.module('myApp', []);
    app.controller('myCtrl', function($scope, $interval) {
        $scope.theTime = new Date().toLocaleTimeString();
        $interval(function () {
            $scope.theTime = new Date().toLocaleTimeString();
        }, 1000);
    });

    尝试一下 »

    创建自定义服务

    你可以创建自定义的访问,链接到你的模块中:

    创建名为hexafy 的访问:

    app.service('hexafy', function() {
        this.myFunc = function (x) {
            return x.toString(16);
        }
    });

    要使用自定义的访问,需要在定义过滤器的时候独立添加:

    实例

    使用自定义的的服务 hexafy 将一个数字转换为16进制数:

    app.controller('myCtrl', function($scope, hexafy) {
        $scope.hex = hexafy.myFunc(255);
    });

    尝试一下 »

    过滤器中,使用自定义服务

    当你创建了自定义服务,并连接到你的应用上后,你可以在控制器,指令,过滤器或其他服务中使用它。

    在过滤器 myFormat 中使用服务 hexafy:

    app.filter('myFormat',['hexify', function(hexify) {
        return function(x) {
            returnhexify.myFunc(x);
        };
    }]);

    尝试一下 »

    在对象数组中获取值时你可以使用过滤器:

    创建服务 hexafy:

    <ul>
    <li ng-repeat="x in counts">{{x | myFormat}}</li>
    </ul>

    尝试一下 »

    相关文章

    AngularJS 过滤器

    AngularJS XMLHttpRequest

    我们可以使用 AngularJS 内置的 $http 服务直接同外部进行通信。

    $http 服务只是简单的封装了浏览器原生的 XMLHttpRequest 对象。


    $http 是 AngularJS 中的一个核心服务,用于读取远程服务器的数据。


    读取 JSON 文件

    以下是存储在web服务器上的 JSON 文件:

    Customers_JSON.php

    {"records":[
    {
    "Name" : "Alfreds Futterkiste",
    "City" : "Berlin",
    "Country" : "Germany"
    },
    {
    "Name" : "Berglunds snabbköp",
    "City" : "Luleå",
    "Country" : "Sweden"
    },
    {
    "Name" : "Centro comercial Moctezuma",
    "City" : "México D.F.",
    "Country" : "Mexico"
    },
    {
    "Name" : "Ernst Handel",
    "City" : "Graz",
    "Country" : "Austria"
    },
    {
    "Name" : "FISSA Fabrica Inter. Salchichas S.A.",
    "City" : "Madrid",
    "Country" : "Spain"
    },
    {
    "Name" : "Galería del gastrónomo",
    "City" : "Barcelona",
    "Country" : "Spain"
    },
    {
    "Name" : "Island Trading",
    "City" : "Cowes",
    "Country" : "UK"
    },
    {
    "Name" : "Königlich Essen",
    "City" : "Brandenburg",
    "Country" : "Germany"
    },
    {
    "Name" : "Laughing Bacchus Wine Cellars",
    "City" : "Vancouver",
    "Country" : "Canada"
    },
    {
    "Name" : "Magazzini Alimentari Riuniti",
    "City" : "Bergamo",
    "Country" : "Italy"
    },
    {
    "Name" : "North/South",
    "City" : "London",
    "Country" : "UK"
    },
    {
    "Name" : "Paris spécialités",
    "City" : "Paris",
    "Country" : "France"
    },
    {
    "Name" : "Rattlesnake Canyon Grocery",
    "City" : "Albuquerque",
    "Country" : "USA"
    },
    {
    "Name" : "Simons bistro",
    "City" : "København",
    "Country" : "Denmark"
    },
    {
    "Name" : "The Big Cheese",
    "City" : "Portland",
    "Country" : "USA"
    },
    {
    "Name" : "Vaffeljernet",
    "City" : "Århus",
    "Country" : "Denmark"
    },
    {
    "Name" : "Wolski Zajazd",
    "City" : "Warszawa",
    "Country" : "Poland"
    }
    ]}


    AngularJS $http

    AngularJS $http 是一个用于读取web服务器上数据的服务。

    $http.get(url) 是用于读取服务器数据的函数。

    AngularJS 实例

    <div ng-app="" ng-controller="customersController">

    <ul>
      <li ng-repeat="x in names">
        {{ x.Name + ', ' + x.Country }}
      </li>
    </ul>

    </div>

    <script>
    function customersController($scope,$http) {
        $http.get("/statics/demosource/Customers_JSON.php")
        .success(function(response) {$scope.names = response;});
    }
    </script>

    尝试一下 »

    应用解析:

    AngularJS 应用通过 ng-app 定义。应用在 <div> 中执行。

    ng-controller 指令设置了 controller 对象 名。

    函数 customersController 是一个标准的 JavaScript 对象构造器

    控制器对象有一个属性: $scope.names

    $http.get() 从web服务器上读取静态 JSON 数据

    服务器数据文件为:  /statics/demosource/Customers_JSON.php

    当从服务端载入 JSON 数据时,$scope.names 变为一个数组。

    Note 以上代码也可以用于读取数据库数据。

    AngularJS Select(选择框)

    本节介绍 AngularJS Select(选择框)如何使用。

    AngularJS 可以使用数组或对象创建一个下拉列表选项。


    使用 ng-options 创建选择框

    在 AngularJS 中我们可以使用 ng-options 指令来创建一个下拉列表,列表项通过对象和数组循环输出,如下实例:

    实例

    <div ng-app="myApp" ng-controller="myCtrl">

    <select ng-model="selectedName" ng-options="x for x in names">
    </select>

    </div>

    <script>
    var app = angular.module('myApp', []);
    app.controller('myCtrl', function($scope) {
        $scope.names = ["Google", "W3Cschool", "Taobao"];
    });
    </script>

    尝试一下 »

    ng-options 与 ng-repeat

    我们也可以使用ng-repeat 指令来创建下拉列表:

    实例

    <select>
    <option ng-repeat="x in names">{{x}}</option>
    </select>

    尝试一下 »

    ng-repeat 指令是通过数组来循环 HTML 代码来创建下拉列表,但 ng-options 指令更适合创建下拉列表,它有以下优势:

    使用 ng-options 的选项的一个对象, ng-repeat 是一个字符串。


    应该用哪个更好?

    假设我们使用以下对象:

    $scope.sites = [    {site : "Google", url : "http://www.google.com"},    {site : "W3CSchool", url : "http://www.51coolma.cn"},    {site : "Taobao", url : "http://www.taobao.com"}];

    ng-repeat 有局限性,选择的值是一个字符串:

    实例

    使用 ng-repeat:

    <select ng-model="selectedSite">
    <option ng-repeat="x in sites" value="{{x.url}}">{{x.site}}</option>
    </select>

    <h1>你选择的是: {{selectedSite}}</h1>

    尝试一下 »

    使用 ng-options 指令,选择的值是一个对象:

    实例

    使用 ng-options:

    <select ng-model="selectedSite" ng-options="x.site for x in sites">
    </select>

    <h1>你选择的是: {{selectedSite.site}}</h1>
    <p>网址为: {{selectedSite.url}}</p>

    尝试一下 »

    当选择值是一个对象时,我们就可以获取更多信息,应用也更灵活。


    数据源为对象

    前面实例我们使用了数组作为数据源,以下我们将数据对象作为数据源。

    $scope.sites = {    site01 : "Google",    site02 : "W3CSchool",    site03 : "Taobao"};

    ng-options 使用对象有很大的不同,如下所示:

    实例

    使用对象作为数据源, x 为键(key),y 为值(value):

    <select ng-model="selectedSite" ng-options="x for (x, y) in sites">
    </select>

    <h1>你选择的值是: {{selectedSite}}</h1>

    尝试一下 »

    你选择的值为在 key-value 对中的 value

    value 在 key-value 对中也可以是个对象:

    实例

    选择的值在 key-value 对的 value 中, 这是它是一个对象:

    $scope.cars = {
    car01 : {brand : "Ford", model : "Mustang", color : "red"},
    car02 : {brand : "Fiat", model : "500", color : "white"},
    car03 : {brand : "Volvo", model : "XC90", color : "black"}
    };

    尝试一下 »

    在下拉菜单也可以不使用key-value 对中的 key , 直接使用对象的属性:

    实例

    <select ng-model="selectedCar" ng-options="y.brand for (x, y) in sites">
    </select>

    尝试一下 »

    AngularJS 表格

    本节介绍了显示 AngularJS 表格的方法。

    ng-repeat 指令可以完美的显示表格。


    在表格中显示数据

    使用 angular 显示表格是非常简单的:

    AngularJS 实例

    <div ng-app="" ng-controller="customersController">

    <table>
      <tr ng-repeat="x in names">
        <td>{{ x.Name }}</td>
        <td>{{ x.Country }}</td>
      </tr>
    </table>

    </div>

    <script>
    function customersController($scope,$http) {
      $http.get("/statics/demosource/Customers_JSON.php")
      .success(function(response) {$scope.names = response;});
    }
    </script>

    尝试一下 »

    废弃声明 (v1.5)v1.5 中$http 的 success 和 error 方法已废弃。使用 then 方法替代。如果你使用的是 v1.5 以下版本,可以使用以下代码:var app = angular.module('myApp', []);app.controller('customersCtrl', function($scope, $http) {   $http.get("/try/angularjs/data/Customers_JSON.php")   .success(function (response) {$scope.names = response.records;});});

    Customers_JSON.php 文件代码:

    实例

    <?php

    echo <<<EOT

    {

    "records":[

    {"Name":"Alfreds Futterkiste","City":"Berlin","Country":"Germany"},

    {"Name":"Ana Trujillo Emparedados y helados","City":"México D.F.","Country":"Mexico"},

    {"Name":"Antonio Moreno Taquería","City":"México D.F.","Country":"Mexico"},

    {"Name":"Around the Horn","City":"London","Country":"UK"},

    {"Name":"B's Beverages","City":"London","Country":"UK"},

    {"Name":"Berglunds snabbköp","City":"Luleå","Country":"Sweden"},

    {"Name":"Blauer See Delikatessen","City":"Mannheim","Country":"Germany"},

    {"Name":"Blondel père et fils","City":"Strasbourg","Country":"France"},

    {"Name":"Bólido Comidas preparadas","City":"Madrid","Country":"Spain"},

    {"Name":"Bon app'","City":"Marseille","Country":"France"},

    {"Name":"Bottom-Dollar Marketse","City":"Tsawassen","Country":"Canada"},

    {"Name":"Cactus Comidas para llevar","City":"Buenos Aires","Country":"Argentina"},

    {"Name":"Centro comercial Moctezuma","City":"México D.F.","Country":"Mexico"},

    {"Name":"Chop-suey Chinese","City":"Bern","Country":"Switzerland"},

    {"Name":"Comércio Mineiro","City":"São Paulo","Country":"Brazil"}

    ]

    }

    EOT;

    ?>


    使用 CSS 样式

    为了让页面更加美观,我们可以在页面中使用CSS:

    CSS 样式

    <style>
    table, th , td {
      border: 1px solid grey;
      border-collapse: collapse;
      padding: 5px;
    }
    table tr:nth-child(odd) {
      background-color: #f1f1f1;
    }
    table tr:nth-child(even) {
      background-color: #ffffff;
    }
    </style>

    尝试一下 »


    使用 orderBy 过滤器

    排序显示,我们可以使用 orderBy 过滤器: 

    AngularJS 实例

    <table>
      <tr ng-repeat="x in names | orderBy : 'Country'">
        <td>{{ x.Name }}</td>
        <td>{{ x.Country }}</td>
      </tr>
    </table>

    尝试一下 »


    使用 uppercase 过滤器

    如果字母要转换为大写,可以添加 uppercase 过滤器: 

    AngularJS 实例

    <table>
      <tr ng-repeat="x in names">
        <td>{{ x.Name }}</td>
        <td>{{ x.Country | uppercase}}</td>
      </tr>
    </table>

    尝试一下 »

    显示序号 ($index)

    表格显示序号可以在 <td> 中添加 $index:

    实例

    <table>

      <tr ng-repeat="x in names">

        <td>{{ $index + 1 }}</td>

        <td>{{ x.Name }}</td>

        <td>{{ x.Country }}</td>

      </tr>

    </table>


    AngularJS SQL


    在前面章节中的代码也可以用于读取数据库中的数据。


    使用 PHP 从 MySQL 中获取数据

    AngularJS 实例

    <div ng-app="" ng-controller="customersController">

    <table>
      <tr ng-repeat="x in names">
        <td>{{ x.Name }}</td>
        <td>{{ x.Country }}</td>
      </tr>
    </table>

    </div>

    <script>
    function customersController($scope,$http) {
        var site = "http://www.51coolma.cn";
        var page = "/statics/demosource/Customers_SQL.php";
        $http.get(site + page)
        .success(function(response) {$scope.names = response;});
    }
    </script>

    尝试一下 »


    ASP.NET 中执行 SQL 获取数据

    AngularJS 实例

    <div ng-app="" ng-controller="customersController">

    <table>
      <tr ng-repeat="x in names">
        <td>{{ x.Name }}</td>
        <td>{{ x.Country }}</td>
      </tr>
    </table>

    </div>

    <script>
    function customersController($scope,$http) {
        var site = "http://www.51coolma.cn";
        var page = "/try/angularjs/data/Customers_SQL.aspx";
        $http.get(site + page)
        .success(function(response) {$scope.names = response;});
    }
    </script>

    尝试一下 »


    PHP 读取 MySQL 数据代码

    <?php
    header("Access-Control-Allow-Origin: *");
    header("Content-Type: text/html; charset=UTF-8");

    $conn = new mysqli("myServer", "myUser", "myPassword", "Northwind");

    $result = $conn->query("SELECT CompanyName, City, Country FROM Customers");

    $outp "[";
    while($rs = $result->fetch_array(MYSQLI_ASSOC)) {
        if ($outp != "[") {$outp .= ",";}
        $outp .= '{"Name":"'  . $rs["CompanyName"] . '",';
        $outp .= '"City":"'   . $rs["City"]        . '",';
        $outp .= '"Country":"'. $rs["Country"]     . '"}';
    }
    $outp .="]";

    $conn->close();

    echo($outp);
    ?>


    PHP 读取 MS Access 代码

    <?php
    header("Access-Control-Allow-Origin: *");
    header("Content-Type: text/html; charset=ISO-8859-1");

    $conn = new COM("ADODB.Connection");
    $conn->open("PROVIDER=Microsoft.Jet.OLEDB.4.0;Data Source=Northwind.mdb");

    $rs = $conn->execute("SELECT CompanyName, City, Country FROM Customers");

    $outp = "[";
    while (!$rs->EOF) {
        if ($outp != "[") {$outp .= ",";}
        $outp .= '{"Name":"'  . $rs["CompanyName"] . '",';
        $outp .= '"City":"'   . $rs["City"]        . '",';
        $outp .= '"Country":"'. $rs["Country"]     . '"}';
        $rs->MoveNext();
    }
    $outp .= "]";

    $conn->close();

    echo ($outp);
    ?>


    服务端 ASP.NET, VB 和 MS Access 代码

    <%@ Import Namespace="System.IO"%>
    <%@ Import Namespace="System.Data"%>
    <%@ Import Namespace="System.Data.OleDb"%>
    <%
    Response.AppendHeader("Access-Control-Allow-Origin", "*")
    Dim conn As OleDbConnection
    Dim objAdapter As OleDbDataAdapter
    Dim objTable As DataTable
    Dim objRow As DataRow
    Dim objDataSet As New DataSet()
    Dim outp
    Dim c
    conn = New OledbConnection("Provider=Microsoft.Jet.OLEDB.4.0;data source=Northwind.mdb")
    objAdapter = New OledbDataAdapter("SELECT CompanyName, City, Country FROM Customers", conn)
    objAdapter.Fill(objDataSet, "myTable")
    objTable=objDataSet.Tables("myTable")

    outp = "["
    c = chr(34)
    for each x in objTable.Rows
    if outp <> "[" then outp = outp & ","
    outp = outp & "{" & c & "Name"    & c & ":" & c & x("CompanyName") & c & ","
    outp = outp &       c & "City"    & c & ":" & c & x("City")        & c & ","
    outp = outp &       c & "Country" & c & ":" & c & x("Country")     & c & "}"
    next

    outp = outp & "]"
    response.write(outp)
    conn.close
    %>


    服务端 ASP.NET, VB Razor 和 SQL Lite 代码

    @{
    Response.AppendHeader("Access-Control-Allow-Origin", "*")
    var db = Database.Open("Northwind");
    var query = db.Query("SELECT CompanyName, City, Country FROM Customers");
    var outp ="["
    }
    @foreach(var row in query)
    {
    if outp <> "[" then outp = outp + ","
    outp = outp + "{" + c + "Name"    + c + ":" + c + @row.CompanyName + c + ","
    outp = outp +       c + "City"    + c + ":" + c + @row.City        + c + ","
    outp = outp +       c + "Country" + c + ":" + c + @row.Country     + c + "}"
    }
    outp = outp + "]"
    @outp

    AngularJS HTML DOM

    AngularJS 为 HTML DOM 元素的属性提供了绑定应用数据的指令。

    AngularJS 有自己的 HTML 属性指令。


    ng-disabled 指令

    ng-disabled 指令设置表单输入字段的 disabled 属性(input, select, 或 textarea)。

    如果 ng-disabled 中的表达式返回 true 则表单字段将被禁用

    AngularJS 实例

    <div ng-app="">

    <p>
    <button ng-disabled="mySwitch">点我!</button>
    </p>

    <p>
    <input type="checkbox" ng-model="mySwitch">按钮
    </p>

    </div>

    尝试一下 »

    实例讲解:

    ng-disabled 指令绑定应用程序数据 "mySwitch" 到 HTML 的 disabled 属性。

    ng-model 指令绑定 "mySwitch" 到 HTML input checkbox 元素的内容(value)。


    ng-show 指令

    ng-show 指令隐藏或显示一个 HTML 元素。

    AngularJS 实例

    <div ng-app="">

    <p ng-show="true">我是可见的。</p>

    <p ng-show="false">我是不可见的。</p>

    </div>

    尝试一下 »

    您可以使用一个评估为 true or false 的表达式(比如 ng-show="hour < 12")来隐藏和显示 HTML 元素。

    在下一章中,有另一个实例,通过单击一个按钮来隐藏一个 HTML 元素。

    相关文章

    HTML DOM 元素


    AngularJS HTML 事件

    本节介绍了 AngularJS 的 HTML 事件的使用。

    AngularJS 有自己的 HTML 事件指令。


    ng-click 指令

    ng-click 指令定义了一个 AngularJS 单击事件。

    AngularJS 实例

    <div ng-app="myApp" ng-controller="myController">

    <button ng-click="count = count + 1">点我!</button>

    <p>{{ count }}</p>

    </div>

    尝试一下 »


    隐藏 HTML 元素

    ng-hide 指令用于设置应用的一部分 不可见

    ng-hide="true" 让 HTML 元素 不可见

    ng-hide="false" 让元素可见。

    AngularJS 实例

    <div ng-app="myApp" ng-controller="personController">

    <button ng-click="toggle()">隐藏/显示</button>

    <p ng-hide="myVar">
    名: <input type="text" ng-model="person.firstName"><br>
    姓: <input type="text" ng-model="person.lastName"><br>
    <br>
    姓名: {{person.firstName + " " + person.lastName}}
    </p>

    </div>

    <script>
    function personController($scope) {
        $scope.person = {
            firstName: "John",
            lastName: "Doe"
        };
        $scope.myVar = false;
        $scope.toggle = function() {
            $scope.myVar = !$scope.myVar;
        };
    }
    </script>

    尝试一下 »

    应用解析:

    personController的第一部分与控制器章节类似。

    应用有一个默认属性: $scope.myVar = false;

    ng-hide 指令设置应用中的元素不可见。

    toggle() 函数用于切换 myVar 变量的值(true 和 false)。

    ng-hide="true" 让元素 不可见


    显示 HTML 元素

    ng-show 指令可用于设置应用中的一部分可见

    ng-show="false" 可以设置 HTML 元素 不可见

    ng-show="true" 可以以设置 HTML 元素可见。

    以下实例使用了 ng-show 指令:

    AngularJS 实例

    <div ng-app="myApp" ng-controller="personController">

    <button ng-click="toggle()">隐藏/显示</button>

    <p ng-show="myVar">
    名: <input type="text" ng-model="person.firstName"><br>
    姓: <input type="text" ng-model="person.lastName"><br>
    <br>
    姓名: {{person.firstName + " " + person.lastName}}
    </p>

    </div>

    <script>
    function personController($scope) {
        $scope.person = {
            firstName: "John",
            lastName: "Doe"
        };
        $scope.myVar = true;
        $scope.toggle = function() {
            $scope.myVar = !$scope.myVar;
        };
    }
    </script>

    尝试一下 »

    AngularJS 模块

    本节介绍了如何创建 AngularJS 模块以及 AngularJS 模块的相关运用。

    模块定义了一个应用程序。

    模块是应用程序中不同部分的容器。

    模块是应用控制器的容器。

    控制器通常属于一个模块。


    创建模块

    你可以通过 AngularJS 的 angular.module 函数来创建模块:

    <div ng-app="myApp">...</div>

    <script>

    var app = angular.module("myApp", []);

    </script>

    "myApp" 参数对应执行应用的 HTML 元素

    现在你可以在 AngularJS 应用中添加控制器,指令,过滤器等。


    添加控制器

    你可以使用 ng-controller 指令来添加应用的控制器:

    AngularJS 实例

    <div ng-app="myApp" ng-controller="myCtrl">
    {{ firstName + " " + lastName }}
    </div>

    <script>

    var app = angular.module("myApp", []);

    app.controller("myCtrl", function($scope) {
        $scope.firstName = "John";
        $scope.lastName = "Doe";
    });

    </script>

    尝试一下 »

    你可以在 AngularJS 控制器 章节学到更多关于控制器的知识。


    添加指令

    AngularJS 提供了很多内置的指令,你可以使用它们来为你的应用添加功能。

    完整的指令内容可以参阅 AngularJS 参考手册

    此外,你可以使用模块来为你应用添加自己的指令:

    AngularJS 实例

    <div ng-app="myApp" 51coolma-directive></div>

    <script>

    var app = angular.module("myApp", []);

    app.directive("51coolmaDirective", function() {
        return {
            template : "我在指令构造器中创建!"
        };
    });
    </script>

    尝试一下 »

    你可以在 AngularJS 指令 章节学到更多关于指令的知识。


    模块和控制器包含在 JS 文件中

    通常 AngularJS 应用程序将模块和控制器包含在 JavaScript 文件中。

    在以下实例中, "myApp.js" 包含了应用模块的定义程序, "myCtrl.js" 文件包含了控制器:

    AngularJS 实例

    <!DOCTYPE html>
    <html>
    <script src="http://apps.bdimg.com/libs/angular.js/1.4.6/angular.min.js"></script>
    <body>

    <div ng-app="myApp" ng-controller="myCtrl">
    {{ firstName + " " + lastName }}
    </div>

    <script src="myApp.js"></script>
    <script src="myCtrl.js"></script>

    </body>
    </html>

    尝试一下 »

    myApp.js

    var app = angular.module("myApp", []);

    在模块定义中 [] 参数用于定义模块的依赖关系。
    中括号[]表示该模块没有依赖,如果有依赖的话会在中括号写上依赖的模块名字。

    myCtrl.js

    app.controller("myCtrl", function($scope) {
        $scope.firstName = "John";
        $scope.lastName= "Doe";
    });

    函数会影响到全局命名空间

    JavaScript 中应避免使用全局函数。因为他们很容易被其他脚本文件覆盖。

    AngularJS 模块让所有函数的作用域在该模块下,避免了该问题。


    什么时候载入库?

    在我们的实例中,所有 AngularJS 库都在 HTML 文档的头部载入。

    对于 HTML 应用程序,通常建议把所有的脚本都放置在 <body> 元素的最底部。

    这会提高网页加载速度,因为 HTML 加载不受制于脚本加载。

    在我们的多个 AngularJS 实例中,您将看到 AngularJS 库是在文档的 <head> 区域被加载。

    在我们的实例中,AngularJS 在 <head> 元素中被加载,因为对 angular.module 的调用只能在库加载完成后才能进行。

    另一个解决方案是在 <body> 元素中加载 AngularJS 库,但是必须放置在您的 AngularJS 脚本前面:

    AngularJS 实例

    <!DOCTYPE html>
    <html>
    <head>
    <meta charset="utf-8">
    <script src="http://apps.bdimg.com/libs/angular.js/1.4.6/angular.min.js"></script>
    </head>
    <body>

    <div ng-app="myApp" ng-controller="myCtrl">
    {{ firstName + " " + lastName }}
    </div>
    <script>
    var app = angular.module("myApp", []);
    app.controller("myCtrl", function($scope) {
        $scope.firstName = "John";
        $scope.lastName = "Doe";
    });
    </script>

    </body>
    </html>

    尝试一下 »

    AngularJS 表单


    AngularJS 表单是输入控件的集合。


    HTML 控件

    以下 HTML input 元素被称为 HTML 控件:

    • input 元素
    • select 元素
    • button 元素
    • textarea 元素

    HTML 表单

    HTML 表单通常与 HTML 控件同时存在。


    AngularJS 表单实例

    function ExampleController($scope) { $scope.master = {"firstName":"John","lastName":"Doe"}; $scope.reset = function() { $scope.user = angular.copy($scope.master); }; $scope.reset();};

    First Name:

    Last Name:


    form = {{user}}

    master = {{master}}


    应用程序代码

    <div ng-app="" ng-controller="formController">
      <form novalidate>
        First Name:<br>
        <input type="text" ng-model="user.firstName"><br>
        Last Name:<br>
        <input type="text" ng-model="user.lastName">
        <br><br>
        <button ng-click="reset()">RESET</button>
      </form>
      <p>form = {{user}}</p>
      <p>master = {{master}}</p>
    </div>

    <script>
    function formController ($scope) {
        $scope.master = {firstName: "John", lastName: "Doe"};
        $scope.reset = function() {
            $scope.user = angular.copy($scope.master);
        };
        $scope.reset();
    };
    </script>

    尝试一下 »

    Note HTML 属性 novalidate 用于禁用浏览器的默认验证。

    实例解析

    AngularJS ng-model 指令用于绑定 input 元素到模型中。

    模型对象 master 的值为 {"firstName" : "John", "lastName" : "Doe"}。

    模型函数 reset 设置了模型对象 user 等于 master。

    相关文章

    HTML 表单

    AngularJS 输入验证

    AngularJS 表单和控件提供了验证功能,对与用户的输入进行判断,以辨别输入是否合法,如果不合法则进行警告。

    AngularJS 表单和控件可以验证输入的数据。


    输入验证

    在前面的几个章节中,你已经学到关于 AngularJS 表单和控件的知识。

    AngularJS 表单和控件可以提供验证功能,并对用户输入的非法数据进行警告。

    Note 客户端的验证不能确保用户输入数据的安全,所以服务端的数据验证也是必须的。

    应用代码

    <!DOCTYPE html>
    <html>

    <body>
    <h2>Validation Example</h2>

    <form  ng-app=""  ng-controller="validateCtrl"
    name="myForm" novalidate>

    <p>Username:<br>
      <input type="text" name="user" ng-model="user" required>
      <span style="color:red" ng-show="myForm.user.$dirty && myForm.user.$invalid">
      <span ng-show="myForm.user.$error.required">Username is required.</span>
      </span>
    </p>

    <p>Email:<br>
      <input type="email" name="email" ng-model="email" required>
      <span style="color:red" ng-show="myForm.email.$dirty && myForm.email.$invalid">
      <span ng-show="myForm.email.$error.required">Email is required.</span>
      <span ng-show="myForm.email.$error.email">Invalid email address.</span>
      </span>
    </p>

    <p>
      <input type="submit"
      ng-disabled="myForm.user.$dirty && myForm.user.$invalid ||
      myForm.email.$dirty && myForm.email.$invalid">
    </p>

    </form>

    <script src="//apps.bdimg.com/libs/angular.js/1.2.15/angular.min.js" rel="external nofollow" ></script>
    <script>
    function validateCtrl($scope) {
        $scope.user = 'John Doe';
        $scope.email = 'john.doe@gmail.com';
    }
    </script>

    </body>
    </html>

    尝试一下 »
    Note HTML 表单属性 novalidate 用于禁用浏览器默认的验证。

    实例解析

    AngularJS ng-model 指令用于绑定输入元素到模型中。

    模型对象有两个属性: useremail

    我们使用了 ng-show指令, color:red 在邮件是 $dirty$invalid 才显示。

    相关阅读

    HTML 表单验证

    AngularJS API

    本节为你介绍了 AngularJS 中一些通用的 API。

    API 意为 Application Programming Interface(应用程序编程接口)。


    AngularJS 全局 API

    AngularJS 全局 API 用于执行常见任务的 JavaScript 函数集合,如:

    • 比较对象
    • 迭代对象
    • 转换对象

    全局 API 函数使用 angular 对象进行访问。

    以下列出了一些通用的 API 函数:

    API描述
    angular.lowercase()转换字符串为小写
    angular.uppercase()转换字符串为大写
    angular.isString()判断给定的对象是否为字符串,如果是返回 true。
    angular.isNumber()判断给定的对象是否为数字,如果是返回 true。

    angular.lowercase()

    实例

    <div ng-app="myApp" ng-controller="myCtrl">
    <p>{{ x1 }}</p>
    <p>{{ x2 }}</p>
    </div>

    <script>
    var app = angular.module('myApp', []);
    app.controller('myCtrl', function($scope) {
    $scope.x1 = "JOHN";
    $scope.x2 = angular.lowercase($scope.x1);
    });
    </script>

    尝试一下 »

    angular.uppercase()

    实例

    <div ng-app="myApp" ng-controller="myCtrl">
    <p>{{ x1 }}</p>
    <p>{{ x2 }}</p>
    </div>

    <script>
    var app = angular.module('myApp', []);
    app.controller('myCtrl', function($scope) {
    $scope.x1 = "John";
    $scope.x2 = angular.uppercase($scope.x1);
    });
    </script>

    尝试一下 »

    angular.isString()

    实例

    <div ng-app="myApp" ng-controller="myCtrl">
    <p>{{ x1 }}</p>
    <p>{{ x2 }}</p>
    </div>

    <script>
    var app = angular.module('myApp', []);
    app.controller('myCtrl', function($scope) {
    $scope.x1 = "JOHN";
    $scope.x2 = angular.isString($scope.x1);
    });
    </script>

    尝试一下 »

    angular.isNumber()

    实例

    <div ng-app="myApp" ng-controller="myCtrl">
    <p>{{ x1 }}</p>
    <p>{{ x2 }}</p>
    </div>

    <script>
    var app = angular.module('myApp', []);
    app.controller('myCtrl', function($scope) {
    $scope.x1 = "JOHN";
    $scope.x2 = angular.isNumber($scope.x1);
    });
    </script>

    尝试一下 »

    AngularJS Bootstrap

    本节介绍了如何在你的 AngularJS 中使用 Bootstrap。

    Bootstrap 中包含了大量的 Web 组件,根据这些组件,可以快速的搭建一个漂亮、功能完备的网站。


    AngularJS 的首选样式表是 Twitter Bootstrap, Twitter Bootstrap 是目前最受欢迎的前端框架。

    查看 Bootstrap教程


    Bootstrap

    你可以在你的 AngularJS 应用中加入 Twitter Bootstrap,你可以在你的 <head> 元素中添加如下代码:

    <link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap.min.css" rel="external nofollow" target="_blank" >

    如果站点在国内,建议使用百度静态资源库的Bootstrap,代码如下:

    <link rel="stylesheet" href="//apps.bdimg.com/libs/bootstrap/3.2.0/css/bootstrap.min.css" rel="external nofollow" target="_blank" >

    以下是一个完整的 HTML 实例, 使用了 AngularJS 指令和 Bootstrap 类。


    HTML 代码

    <!DOCTYPE html>
    <html ang-app="">
    <head>
    <link rel="stylesheet" href="http://apps.bdimg.com/libs/bootstrap/3.2.0/css/bootstrap.min.css" rel="external nofollow" target="_blank" >
    </head>

    <body ng-controller="userController">
    <div class="container">

    <h3>Users</h3>

    <table class="table table-striped">
      <thead><tr>
        <th>Edit</th>
        <th>First Name</th>
        <th>Last Name</th>
      </tr></thead>
      <tbody><tr ng-repeat="user in users">
        <td>
          <button class="btn" ng-click="editUser(user.id)">
          <span class="glyphicon glyphicon-pencil"></span>  Edit
          </button>
        </td>
        <td>{{ user.fName }}</td>
        <td>{{ user.lName }}</td>
      </tr></tbody>
    </table>

    <hr>
    <button class="btn btn-success" ng-click="editUser('new')">
      <span class="glyphicon glyphicon-user"></span> Create New User
    </button>
    <hr>

    <h3 ng-show="edit">Create New User:</h3>
    <h3 ng-hide="edit">Edit User:</h3>

    <form class="form-horizontal">
    <div class="form-group">
      <label class="col-sm-2 control-label">First Name:</label>
      <div class="col-sm-10">
        <input type="text" ng-model="fName" ng-disabled="!edit" placeholder="First Name">
      </div>
    </div>
    <div class="form-group">
      <label class="col-sm-2 control-label">Last Name:</label>
      <div class="col-sm-10">
        <input type="text" ng-model="lName" ng-disabled="!edit" placeholder="Last Name">
      </div>
    </div>
    <div class="form-group">
      <label class="col-sm-2 control-label">Password:</label>
      <div class="col-sm-10">
        <input type="password" ng-model="passw1" placeholder="Password">
      </div>
    </div>
    <div class="form-group">
      <label class="col-sm-2 control-label">Repeat:</label>
      <div class="col-sm-10">
        <input type="password" ng-model="passw2" placeholder="Repeat Password">
      </div>
    </div>
    </form>

    <hr>
    <button class="btn btn-success" ng-disabled="error || incomplete">
      <span class="glyphicon glyphicon-save"></span> Save Changes
    </button>
    </div>

    <script src = "http://apps.bdimg.com/libs/angular.js/1.2.15/angular.min.js"></script>
    <script src = "myUsers.js"></script>
    </body>
    </html>>

    尝试一下 »


    指令解析

    AngularJS 指令 描述
    <html ng-app 为 <html> 元素定义一个应用(未命名)
    <body ng-controller 为 <body> 元素定义一个控制器
    <tr ng-repeat 循环 users 对象数组,每个 user 对象放在 <tr> 元素中。
    <button ng-click 当点击 <button> 元素时调用函数 editUser()
    <h3 ng-show 如果 edit = true 显示 <h3> 元素
    <h3 ng-hide 如果 edit = true 隐藏 <h3> 元素
    <input ng-model 为应用程序绑定 <input> 元素
    <button ng-disabled 如果发生错误或者 ncomplete = true 禁用 <button> 元素


    Bootstrap 类解析

    元素 Bootstrap 类 定义
    <div> container 内容容器
    <table> table 表格
    <table> table-striped 带条纹背景的表格
    <button> btn 按钮
    <button> btn-success 成功按钮
    <span> glyphicon 字形图标
    <span> glyphicon-pencil 铅笔图标
    <span> glyphicon-user 用户图标
    <span> glyphicon-save 保存图标
    <form> form-horizontal 水平表格
    <div> form-group 表单组
    <label> control-label 控制器标签
    <label> col-sm-2 跨越 2 列
    <div> col-sm-10 跨越 10 列


    JavaScript 代码

    function userController($scope) {
    $scope.fName = '';
    $scope.lName = '';
    $scope.passw1 = '';
    $scope.passw2 = '';
    $scope.users = [
    {id:1, fName:'Hege',  lName:"Pege" },
    {id:2, fName:'Kim',   lName:"Pim" },
    {id:3, fName:'Sal',   lName:"Smith" },
    {id:4, fName:'Jack',  lName:"Jones" },
    {id:5, fName:'John',  lName:"Doe" },
    {id:6, fName:'Peter', lName:"Pan" }
    ];
    $scope.edit = true;
    $scope.error = false;
    $scope.incomplete = false;

    $scope.editUser = function(id) {
      if (id == 'new') {
        $scope.edit = true;
        $scope.incomplete = true;
        $scope.fName = '';
        $scope.lName = '';
        } else {
        $scope.edit = false;
        $scope.fName = $scope.users[id-1].fName;
        $scope.lName = $scope.users[id-1].lName;
      }
    };

    $scope.$watch('passw1',function() {$scope.test();});
    $scope.$watch('passw2',function() {$scope.test();});
    $scope.$watch('fName', function() {$scope.test();});
    $scope.$watch('lName', function() {$scope.test();});

    $scope.test = function() {
      if ($scope.passw1 !== $scope.passw2) {
        $scope.error = true;
        } else {
        $scope.error = false;
      }
      $scope.incomplete = false;
      if ($scope.edit && (!$scope.fName.length ||
      !$scope.lName.length ||
      !$scope.passw1.length || !$scope.passw2.length)) {
           $scope.incomplete = true;
      }
    };

    }


    JavaScript 代码解析

    Scope 属性 用途
    $scope.fName 模型变量 (用户名)
    $scope.lName 模型变量 (用户姓)
    $scope.passw1 模型变量 (用户密码 1)
    $scope.passw2 模型变量 (用户密码 2)
    $scope.users 模型变量 (用户的数组)
    $scope.edit 当用户点击创建用户时设置为true。
    $scope.error 如果 passw1 不等于 passw2 设置为 true
    $scope.incomplete 如果每个字段都为空(length = 0)设置为 true
    $scope.editUser 设置模型变量
    $scope.watch 监控模型变量
    $scope.test 验证模型变量的错误和完整性

    AngularJS Include(包含)

    本节介绍了 AngularJS Include(包含) 的知识,你将学习如何使用 ng-include 指令。

    使用 AngularJS, 你可以在 HTML 中包含 HTML 文件。


    在未来的HTML中包含 HTML 文件

    在 HTML 中,目前还不支持包含 HTML 文件的功能。

    W3C 已经建议 http://www.w3.org 在未来的 HTML 中支持包含HTML的功能,格式如下:

    <link rel="import" href="/path/navigation.html">


    服务端包含

    大部分web服务器支持服务端脚本的包含 (SSI:Server Side Includes)。

    使用 SSI, 你可以在HTML页面发送至浏览器前包含 HTML。

    PHP 实例

    <?php require("navigation.php"); ?>


    客户端包含

    客户端在 HTML 中使用 JavaScript 有多种方式可以包含 HTML 文件。

    通常我们使用 http 请求 (AJAX) 从服务端获取数据,返回的数据我们可以通过 使用 innerHTML 写入到 HTML 元素中。


    AngularJS 包含

    使用 AngularJS, 你可以使用 ng-include 指令来包含 HTML 内容:

    实例

    <body>

    <div class="container">
      <div ng-include="'myUsers_List.htm'"></div>
      <div ng-include="'myUsers_Form.htm'"></div>
    </div>

    </body>

    尝试一下 »

    步骤如下。


    步骤 1: 创建 HTML 列表

    myUsers_List.html

    <table class="table table-striped">
      <thead><tr>
        <th>Edit</th>
        <th>First Name</th>
        <th>Last Name</th>
      </tr></thead>
      <tbody><tr ng-repeat="user in users">
        <td>
          <button class="btn" ng-click="editUser(user.id)">
            <span class="glyphicon glyphicon-pencil"></span>  Edit
          </button>
        </td>
        <td>{{ user.fName }}</td>
        <td>{{ user.lName }}</td>
      </tr></tbody>
    </table>

    尝试一下 »


    步骤 2: 创建 HTML 表单

    myUsers_List.html

    <button class="btn btn-success" ng-click="editUser('new')">
      <span class="glyphicon glyphicon-user"></span> Create New User
    </button>
    <hr>

    <h3 ng-show="edit">Create New User:</h3>
    <h3 ng-hide="edit">Edit User:</h3>

    <form class="form-horizontal">
    <div class="form-group">
      <label class="col-sm-2 control-label">First Name:</label>
      <div class="col-sm-10">
        <input type="text" ng-model="fName" ng-disabled="!edit" placeholder="First Name">
      </div>
    </div>
    <div class="form-group">
      <label class="col-sm-2 control-label">Last Name:</label>
      <div class="col-sm-10">
        <input type="text" ng-model="lName" ng-disabled="!edit" placeholder="Last Name">
      </div>
    </div>
    <div class="form-group">
      <label class="col-sm-2 control-label">Password:</label>
      <div class="col-sm-10">
        <input type="password" ng-model="passw1" placeholder="Password">
      </div>
    </div>
    <div class="form-group">
      <label class="col-sm-2 control-label">Repeat:</label>
      <div class="col-sm-10">
        <input type="password" ng-model="passw2" placeholder="Repeat Password">
      </div>
    </div>
    </form>

    <hr>
    <button class="btn btn-success" ng-disabled="error || incomplete">
      <span class="glyphicon glyphicon-save"></span> Save Changes
    </button>

    尝试一下 »


    步骤 3: 创建主页

    myUsers.html

    <!DOCTYPE html>
    <html ng-app="">
    <head>
    <link rel="stylesheet" href = "http://apps.bdimg.com/libs/bootstrap/3.2.0/css/bootstrap.min.css">
    </head>

    <body ng-controller="userController">

    <div class="container">
    <div ng-include="'myUsers_List.htm'"></div>
    <div ng-include="'myUsers_Form.htm'"></div>
    </div>

    <script src= "http://apps.bdimg.com/libs/angular.js/1.2.15/angular.min.js"></script>
    <script src= "myUsers.js"></script>

    </body>
    </html>

    尝试一下 »

    AngularJS 动画

    AngularJS 提供了动画效果,可以配合 CSS 使用。

    AngularJS 使用动画需要引入 angular-animate.min.js 库。

    <script src="http://apps.bdimg.com/libs/angular.js/1.4.6/angular-animate.min.js" rel="external nofollow" ></script>

    还需在应用中使用模型 ngAnimate:

    <body ng-app="ngAnimate">

    什么是动画?

    动画是通过改变 HTML 元素产生的动态变化效果。

    实例

    勾选复选框隐藏 DIV:

    <body ng-app="ngAnimate">

    隐藏 DIV: <input type="checkbox" ng-model="myCheck">

    <div ng-hide="myCheck"></div>

    </body>

    尝试一下 »
    Note应用中动画不宜太多,但合适的使用动画可以增加页面的丰富性,也可以更易让用户理解。

    如果我们应用已经设置了应用名,可以把 ngAnimate 直接添加在模型中:

    实例

    <body ng-app="myApp">

    <h1>隐藏 DIV: <input type="checkbox" ng-model="myCheck"></h1>

    <div ng-hide="myCheck"></div>

    <script>
    var app = angular.module('myApp', ['ngAnimate']);
    </script>

    尝试一下 »

    ngAnimate 做了什么?

    ngAnimate 模型可以添加或移除 class 。

    ngAnimate 模型并不能使 HTML 元素产生动画,但是 ngAnimate 会监测事件,类似隐藏显示 HTML 元素 ,如果事件发生 ngAnimate 就会使用预定义的 class 来设置 HTML 元素的动画。

    AngularJS 添加/移除 class 的指令:

    • ng-show
    • ng-hide
    • ng-class
    • ng-view
    • ng-include
    • ng-repeat
    • ng-if
    • ng-switch

    ng-showng-hide 指令用于添加或移除 ng-hide class 的值。

    其他指令会在进入 DOM 会添加 ng-enter 类,移除 DOM 会添加 ng-leave 属性。

    当 HTML 元素位置改变时,ng-repeat 指令同样可以添加 ng-move 类 。

    此外, 在动画完成后,HTML 元素的类集合将被移除。例如:ng-hide 指令会添加一下类:

    • ng-animate
    • ng-hide-animate
    • ng-hide-add (如果元素将被隐藏)
    • ng-hide-remove (如果元素将显示)
    • ng-hide-add-active (如果元素将隐藏)
    • ng-hide-remove-active (如果元素将显示)

    使用 CSS 动画

    我们可以使用 CSS transition(过渡) 或 CSS 动画让 HTML 元素产生动画效果,该部分内容你可以参阅我们的CSS 过渡教程CSS 动画教程


    CSS 过渡

    CSS 过渡可以让我们平滑的将一个 CSS 属性值修改为另外一个:

    实例

    在 DIV 元素设置了 .ng-hide 类时,过渡需要花费 0.5 秒,高度从 100px 变为 0:

    <style>
    div {
        transition: all linear 0.5s;
        background-color: lightblue;
        height: 100px;
    }
    .ng-hide {
        height: 0;
    }
    </style>

    尝试一下 »

    CSS 动画

    CSS 动画允许你平滑的修改 CSS 属性值:

    实例

    在 DIV 元素设置了 .ng-hide 类时, myChange 动画将执行,它会平滑的将高度从 100px 变为 0:

    <style>
    @keyframes myChange {
        from {
            height: 100px;
        } to {
            height: 0;
        }
    }
    div {
        height: 100px;
        background-color: lightblue;
    }
    div.ng-hide {
        animation: 0.5s myChange;
    }
    </style>

    尝试一下 »

    相关教程

    CSS教程

    AngularJS 依赖注入

    依赖注入是 AngularJS 的重要特性之一,它简化了 Angular 解析模块/组件之间依赖的过程。


    什么是依赖注入

    wiki 上的解释是:依赖注入(Dependency Injection,简称DI)是一种软件设计模式,在这种模式下,一个或更多的依赖(或服务)被注入(或者通过引用传递)到一个独立的对象(或客户端)中,然后成为了该客户端状态的一部分。

    该模式分离了客户端依赖本身行为的创建,这使得程序设计变得松耦合,并遵循了依赖反转和单一职责原则。与服务定位器模式形成直接对比的是,它允许客户端了解客户端如何使用该系统找到依赖

    一句话 --- 没事你不要来找我,有事我会去找你。

    AngularJS 提供很好的依赖注入机制。以下5个核心组件用来作为依赖注入:

    • value
    • factory
    • service
    • provider
    • constant

    value

    Value 是一个简单的 javascript 对象,用于向控制器传递值(配置阶段):

    // 定义一个模块var mainApp = angular.module("mainApp", []);// 创建 value 对象 "defaultInput" 并传递数据mainApp.value("defaultInput", 5);...// 将 "defaultInput" 注入到控制器mainApp.controller('CalcController', function($scope, CalcService, defaultInput) {   $scope.number = defaultInput;   $scope.result = CalcService.square($scope.number);      $scope.square = function() {      $scope.result = CalcService.square($scope.number);   }});

    factory

    factory 是一个函数用于返回值。在 service 和 controller 需要时创建。

    通常我们使用 factory 函数来计算或返回值。

    // 定义一个模块var mainApp = angular.module("mainApp", []);// 创建 factory "MathService" 用于两数的乘积 provides a method multiply to return multiplication of two numbersmainApp.factory('MathService', function() {   var factory = {};      factory.multiply = function(a, b) {      return a * b   }   return factory;}); // 在 service 中注入 factory "MathService"mainApp.service('CalcService', function(MathService){   this.square = function(a) {      return MathService.multiply(a,a);   }});...

    provider

    AngularJS 中通过 provider 创建一个 service、factory等(配置阶段)。

    Provider 中提供了一个 factory 方法 get(),它用于返回 value/service/factory。

    // 定义一个模块var mainApp = angular.module("mainApp", []);...// 使用 provider 创建 service 定义一个方法用于计算两数乘积mainApp.config(function($provide) {   $provide.provider('MathService', function() {      this.$get = function() {         var factory = {};                    factory.multiply = function(a, b) {            return a * b;          }         return factory;      };   });});

    constant

    constant(常量)用来在配置阶段传递数值,注意这个常量在配置阶段是不可用的。

    mainApp.constant("configParam", "constant value");

    实例

    以下实例提供了以上几个依赖注入机制的演示。

    <html>      <head>      <meta charset="utf-8">      <title>AngularJS  依赖注入</title>   </head>      <body>      <h2>AngularJS 简单应用</h2>            <div ng-app = "mainApp" ng-controller = "CalcController">         <p>输入一个数字: <input type = "number" ng-model = "number" /></p>         <button ng-click = "square()">X<sup>2</sup></button>         <p>结果: {{result}}</p>      </div>            <script src="http://apps.bdimg.com/libs/angular.js/1.4.6/angular.min.js" rel="external nofollow" ></script>            <script>         var mainApp = angular.module("mainApp", []);                  mainApp.config(function($provide) {            $provide.provider('MathService', function() {               this.$get = function() {                  var factory = {};                                    factory.multiply = function(a, b) {                     return a * b;                  }                  return factory;               };            });         });			         mainApp.value("defaultInput", 5);                  mainApp.factory('MathService', function() {            var factory = {};                        factory.multiply = function(a, b) {               return a * b;            }            return factory;         });                  mainApp.service('CalcService', function(MathService){            this.square = function(a) {               return MathService.multiply(a,a);            }         });                  mainApp.controller('CalcController', function($scope, CalcService, defaultInput) {            $scope.number = defaultInput;            $scope.result = CalcService.square($scope.number);            $scope.square = function() {               $scope.result = CalcService.square($scope.number);            }         });			      </script>         </body></html>

    尝试一下 »

    AngularJS 路由

    本章节我们将为大家介绍 AngularJS 路由。

    AngularJS 路由允许我们通过不同的 URL 访问不同的内容。

    通过 AngularJS 可以实现多视图的单页Web应用(single page web application,SPA)。

    通常我们的URL形式为 http://51coolma.cn/first/page,但在单页Web应用中 AngularJS 通过 # + 标记 实现,例如:

    http://51coolma.cn/#/firsthttp://51coolma.cn/#/secondhttp://51coolma.cn/#/third

    当我们点击以上的任意一个链接时,向服务端请的地址都是一样的 (http://51coolma.cn/)。 因为 # 号之后的内容在向服务端请求时会被浏览器忽略掉。 所以我们就需要在客户端实现 # 号后面内容的功能实现。 AngularJS 路由 就通过 # + 标记 帮助我们区分不同的逻辑页面并将不同的页面绑定到对应的控制器上。


    在以上图形中,我们可以看到创建了两个 URL: /ShowOrders 和 /AddNewOrder。每个 URL 都有对应的视图和控制器。

    接下来我们来看一个简单的实例:

    <html>    <head>    	<meta charset="utf-8">        <title>AngularJS 路由实例 - W3Cschool教程</title>    </head>    <body ng-app='routingDemoApp'>             <h2>AngularJS 路由应用</h2>        <ul>            <li><a href="#/">首页</a></li>            <li><a href="#/computers">电脑</a></li>            <li><a href="#/printers">打印机</a></li>            <li><a href="#/blabla">其他</a></li>        </ul>                 <div ng-view></div>        <script src="http://apps.bdimg.com/libs/angular.js/1.4.6/angular.min.js" rel="external nofollow"  rel="external nofollow" ></script>        <script src="http://apps.bdimg.com/libs/angular-route/1.3.13/angular-route.js" rel="external nofollow"  rel="external nofollow" ></script>        <script>            angular.module('routingDemoApp',['ngRoute'])            .config(['$routeProvider', function($routeProvider){                $routeProvider                .when('/',{template:'这是首页页面'})                .when('/computers',{template:'这是电脑分类页面'})                .when('/printers',{template:'这是打印机页面'})                .otherwise({redirectTo:'/'});            }]);        </script>         </body></html>

    尝试一下 »

    实例解析:

    • 1、载入了实现路由的 js 文件:angular-route.js。

    • 2、包含了 ngRoute 模块作为主应用模块的依赖模块。

      angular.module('routingDemoApp',['ngRoute'])
    • 3、使用 ngView 指令。

      <div ng-view></div>

      该 div 内的 HTML 内容会根据路由的变化而变化。

    • 配置 $routeProvider,AngularJS $routeProvider 用来定义路由规则。

      module.config(['$routeProvider', function($routeProvider){    $routeProvider        .when('/',{template:'这是首页页面'})        .when('/computers',{template:'这是电脑分类页面'})        .when('/printers',{template:'这是打印机页面'})        .otherwise({redirectTo:'/'});}]);

      AngularJS 模块的 config 函数用于配置路由规则。通过使用 configAPI,我们请求把$routeProvider注入到我们的配置函数并且使用$routeProvider.whenAPI来定义我们的路由规则。

      $routeProvider 为我们提供了 when(path,object) & otherwise(object) 函数按顺序定义所有路由,函数包含两个参数:

      • 第一个参数是 URL 或者 URL 正则规则。
      • 第二个参数是路由配置对象。

    • 路由设置对象

      AngularJS 路由也可以通过不同的模板来实现。

      $routeProvider.when 函数的第一个参数是 URL 或者 URL 正则规则,第二个参数为路由配置对象。

      路由配置对象语法规则如下:

    $routeProvider.when(url, {    template: string,    templateUrl: string,    controller: string, function 或 array,    controllerAs: string,    redirectTo: string, function,    resolve: object<key, function>});

    参数说明:

    • template:

      如果我们只需要在 ng-view 中插入简单的 HTML 内容,则使用该参数:

      .when('/computers',{template:'这是电脑分类页面'})
    • templateUrl:

      如果我们只需要在 ng-view 中插入 HTML 模板文件,则使用该参数:

      $routeProvider.when('/computers', {    templateUrl: 'views/computers.html',});

      以上代码会从服务端获取 views/computers.html 文件内容插入到 ng-view 中。

    • controller:

      function、string或数组类型,在当前模板上执行的controller函数,生成新的scope。

    • controllerAs:

      string类型,为controller指定别名。

    • redirectTo:

      重定向的地址。

    • resolve:

      指定当前controller所依赖的其他模块。

    • 实例

      <html><head><meta http-equiv="content-type" content="text/html; charset=UTF-8"><script src="http://apps.bdimg.com/libs/angular.js/1.4.6/angular.min.js" rel="external nofollow"  rel="external nofollow" ></script><script src="http://apps.bdimg.com/libs/angular-route/1.3.13/angular-route.js" rel="external nofollow"  rel="external nofollow" ></script><script type="text/javascript">angular.module('ngRouteExample', ['ngRoute']).controller('HomeController', function ($scope) { $scope.$route = $route;}).controller('AboutController', function ($scope) { $scope.$route = $route;}).config(function ($routeProvider) {    $routeProvider.    when('/home', {        templateUrl: 'embedded.home.html',        controller: 'HomeController'    }).    when('/about', {        templateUrl: 'embedded.about.html',        controller: 'AboutController'    }).    otherwise({        redirectTo: '/home'    });});</script>  </head><body ng-app="ngRouteExample" class="ng-scope">  <script type="text/ng-template" id="embedded.home.html">      <h1> Home </h1>  </script>  <script type="text/ng-template" id="embedded.about.html">      <h1> About </h1>  </script>  <div>     <div id="navigation">        <a href="#/home">Home</a>      <a href="#/about">About</a>    </div>          <div ng-view="">    </div>  </div></body></html>

      尝试一下 »

    AngularJS 应用程序


    现在是时候创建一个真正的 AngularJS 应用程序了。

    你可以通过本节的 AngularJS 应用程序来熟悉 AngularJS 的使用。


    AngularJS 应用程序

    您已经学习了足够多关于 AngularJS 的知识,现在可以开始创建您的第一个 AngularJS 应用程序:

    我的笔记



    剩下的字符数:



    应用程序讲解

    AngularJS 实例

    <html ng-app="myNoteApp">
    <head>
    <meta charset="utf-8">
    <script src="http://apps.bdimg.com/libs/angular.js/1.4.6/angular.min.js"></script>
    </head>
    <body>

    <div ng-controller="myNoteCtrl">

    <h2>我的笔记</h2>

    <p><textarea ng-model="message" cols="40" rows="10"></textarea></p>

    <p>
    <button ng-click="save()">保存</button>
    <button ng-click="clear()">清除</button>
    </p>

    <p>Number of characters left: <span ng-bind="left()"></span></p>

    </div>

    <script src="myNoteApp.js"></script>
    <script src="myNoteCtrl.js"></script>

    </body>
    </html>

    尝试一下 »

    应用程序文件 "myTodoApp.js":

    var app = angular.module("myTodoApp", []);

    控制器文件 "myTodoCtrl.js":

    app.controller("myTodoCtrl", function($scope) {
        $scope.message = "";
        $scope.left  = function() {return 100 - $scope.message.length;};
        $scope.clear = function() {$scope.message="";};
        $scope.save  = function() {$scope.message="";};
    });

    HTML 页面中的一个 <div>,指向 ng-app="myTodoApp" 和 ng-controller="myTodoCtrl":

    <div ng-app="myTodoApp" ng-controller="myTodoCtrl">

    一个 ng-model 指令,绑定一个 <textarea> 到控制器变量 message

    <textarea ng-model="message" cols="40" rows="10"></textarea>

    两个 ng-click 事件,调用控制器函数 clear()save()

    <button ng-click="save()">保存</button>
    <button ng-click="clear()">清除</button>

    一个 ng-bind 指令,绑定控制器函数 left() 到一个 <span>,字符会向左对齐显示:

    剩下的字符数:<span ng-bind="left()"></span>

    两个应用程序库被添加到 HTML 页面:

    <script src="myTodoApp.js"></script>
    <script src="myTodoCtrl.js"></script>

    以上就是相关的 AngularJS 应用程序解析了。

    AngularJS 实例

    本节为你提供了一些 AngularJS 实例,包括基础的 AngularJS 的使用、AngularJS 表达式的使用、AngularJS 指令的使用等等。

    尝试一下

    您可以在线编辑实例,然后点击按钮查看结果。

    AngularJS 实例

    <div ng-app="">

    <p>Name: <input type="text" ng-model="name"></p>
    <p>You wrote: {{ name }}</p>

    </div>

    尝试一下 »


    AngularJS 基础

    我的第一个 AngularJS 表达式
    我的第一个 AngularJS 指令
    我的第一个 AngularJS 指令(带有有效的 HTML5)

    基础的 AngularJS 讲解


    AngularJS 表达式

    带有数字的表达式
    使用带有数字的 ng-bind
    带有字符串的表达式
    使用带有字符串的 ng-bind
    带有对象的表达式
    使用带有对象的 ng-bind
    带有数组的表达式
    使用带有数组的 ng-bind

    表达式讲解


    AngularJS 指令

    AngularJS 指令
    ng-model 指令
    ng-repeat 指令(带有数组)
    ng-repeat 指令(带有对象)

    指令讲解


    AngularJS 控制器

    AngularJS 控制器
    控制器属性
    控制器函数
    JavaScript 文件中的控制器 I
    JavaScript 文件中的控制器 II

    控制器讲解


    AngularJS 过滤器

    表达式过滤器 uppercase
    表达式过滤器 lowercase
    指令过滤器 orderBy
    输入过滤器

    过滤器讲解


    AngularJS HTML DOM

    The ng-disabled Directive

    HTML DOM 讲解


    AngularJS HTML 事件

    ng-click 指令
    ng-show 指令

    HTML 事件讲解


    AngularJS 模块

    body 中的 AngularJS 模块
    文件中的 AngularJS 模块

    模块讲解


    AngularJS 应用程序

    AngularJS 应用程序

    应用程序讲解

    AngularJS 参考手册

    AngularJS 参考手册总结了本教程中所用到的一些 AngularJS 指令以及 AngularJS 过滤器。


    AngularJS 指令

    本教程中使用的 AngularJS 指令:

    指令描述讲解
    ng_app定义应用程序的根元素。指令
    ng_bind绑定 HTML 元素到应用程序数据。简介
    ng_click定义元素被单击时的行为。HTML 事件
    ng_controller为应用程序定义控制器对象。控制器
    ng_disabled绑定应用程序数据到 HTML 的 disabled 属性。HTML DOM
    ng_init为应用程序定义初始值。指令
    ng_model绑定应用程序数据到 HTML 元素。指令
    ng_repeat为控制器中的每个数据定义一个模板。指令
    ng_show显示或隐藏 HTML 元素。HTML DOM


    AngularJS 过滤器

    本教程中使用的 AngularJS 过滤器:

    过滤器描述
    currency格式化数字为货币格式。
    filter从数组项中选择一个子集。
    lowercase格式化字符串为小写。
    orderBy根据某个表达式排列数组。
    uppercase格式化字符串为大写。

    有关过滤器的具体知识在 AngularJS 过滤器 一章中进行讲解。


    AngularJS v1.5 简明教程中文版

    本文是 樊潇洁 翻译的最新版本(v1.5版)的AngularJS 教程。官方英文版请见AngularJS 官方教程AngularJS 官方站

    本人凭兴趣翻译有用的Web开发教程。如发现翻译得有误,请在新浪微博上发消息给我。

    本人会在百度阅读和W3Cschool教程维基上陆续发布更多自己翻译的WEb开发教程。在百度阅读上收听我。这就开始学习AngularJS吧!

    2016年1月19日

    快速入门

    为什么要用AngularJS?

    HTML非常适合于声明静态的文档,但是当我们试图使用它在web应用程序中声明动态视图时,它显得力不从心。AngularJS能为您的应用程序扩展HTML的词汇。由此产生的环境非常具有有表现力、可读性强、快速发展。

    替代选择

    其他处理HTML的缺点的框架要么是抽象出HTML、CSS、和/或JavaScript,要么为操纵DOM提供一个必要的方式。它们都不能解决一个根本问题,即HTML不是为动态视图设计的。

    可扩展性

    AngularJS是用来构建框架的工具集,很适全于你的应用程序开发。它完全可扩展,而且与别的库协作得很好。每个功能可以被修改或替代,以适合你的独一无二的开发工作流以及功能需要。继续阅读以弄懂为何。

    The Basics

    index.html

    <!doctype html><html ng-app>  <head>    <script src="https://atts.51coolma.cn/attachments/image/wk/angularjs/angular.min.js"></script>  </head>  <body>    <div>      <label>Name:</label>      <input type="text" ng-model="yourName" placeholder="Enter a name here">      <hr>      <h1>Hello {{yourName}}!</h1>    </div>  </body></html>

    添加一些控件

    数据绑定

    数据绑定是每当模型改变时更新视图的自动方法,当视图改变时,同样也会更新模型。这非常棒,因为从你需要担心的列表中它减去了DOM操纵。

    控件

    控件是DOM元素后面的行为。AngularJS让你能够用一个干净可读的形式表达行为,不需要更新DOM的通常样板、注册回调或者观察模型变化。

    扁平的JavaScript

    与别的框架不同,不需要为包装访问器方法中的模型,而继承私有类型。Angular模型是扁平的旧式JavaScript对象。这使你的代码容易读取、容易维护、可重用,还不需要样板。

    index.html

    <!doctype html><html ng-app="todoApp">  <head>    <script src="https://atts.51coolma.cn/attachments/image/wk/angularjs/angular.min.js"></script>    <script src="todo.js"></script>    <link rel="stylesheet" href="todo.css">  </head>  <body>    <h2>Todo</h2>    <div ng-controller="TodoListController as todoList">      <span>{{todoList.remaining()}} of {{todoList.todos.length}} remaining</span>      [ <a href="" ng-click="todoList.archive()">archive</a> ]      <ul class="unstyled">        <li ng-repeat="todo in todoList.todos">          <input type="checkbox" ng-model="todo.done">          <span class="done-{{todo.done}}">{{todo.text}}</span>        </li>      </ul>      <form ng-submit="todoList.addTodo()">        <input type="text" ng-model="todoList.todoText"  size="30"               placeholder="add new todo here">        <input class="btn-primary" type="submit" value="add">      </form>    </div>  </body></html>

    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 = '';    };    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

    <!doctype html><html ng-app="project">  <head>    <script src="/attachments/image/wk/angularjs/angular.min.js"></script>    <script src="/attachments/image/wk/angularjs/angular-resource.min.js">    </script>    <script src="/attachments/image/wk/angularjs/angular-route.min.js">   </script>    <script src="/attachments/image/wk/angularjs/firebase.js"></script>    <script src="/attachments/image/wk/angularjs/angularfire.min.js"></script>    <link rel="stylesheet" href="bootstrap.css">    <script src="project.js"></script>  </head>  <body>    <h2>JavaScript Projects</h2>    <div ng-view></div>  </body></html>

    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);      });      //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('/');        });    };    editProject.save = function() {        editProject.projects.$save(editProject.project).then(function(data) {           $location.path('/');        });    };});

    list.html

    <input type="text" ng-model="projectList.search" class="search-query" id="projects_search"       placeholder="Search"><table>  <thead>  <tr>    <th>Project</th>    <th>Description</th>    <th><a href="#/new"><i class="icon-plus-sign"></i></a></th>  </tr>  </thead>  <tbody>  <tr ng-repeat="project in projectList.projects | filter:projectList.search | orderBy:'name'">    <td><a ng-href="{{project.site}}" target="_blank">{{project.name}}</a></td>    <td>{{project.description}}</td>    <td>      <a ng-href="#/edit/{{project.$id}}"><i class="icon-pencil"></i></a>    </td>  </tr>  </tbody></table>

    detail.html

    <form name="myForm">  <div class="control-group" ng-class="{error: myForm.name.$invalid && !myForm.name.$pristine}">    <label>Name</label>    <input type="text" name="name" ng-model="editProject.project.name" required>    <span ng-show="myForm.name.$error.required && !myForm.name.$pristine" class="help-inline">        Required {{myForm.name.$pristine}}</span>  </div>  <div class="control-group" ng-class="{error: myForm.site.$invalid && !myForm.site.$pristine}">    <label>Website</label>    <input type="url" name="site" ng-model="editProject.project.site" required>    <span ng-show="myForm.site.$error.required && !myForm.site.$pristine" class="help-inline">        Required</span>    <span ng-show="myForm.site.$error.url" class="help-inline">        Not a URL</span>  </div>  <label>Description</label>  <textarea name="description" ng-model="editProject.project.description"></textarea>  <br>  <a href="#/" class="btn">Cancel</a>  <button ng-click="editProject.save()" ng-disabled="myForm.$invalid"          class="btn btn-primary">Save</button>  <button ng-click="editProject.destroy()"          ng-show="editProject.project.$id" class="btn btn-danger">Delete</button></form>

    创建组件

    指令

    指令是一个独有而且强大的功能,只在Angular中可用。指令使你能够发明新的HTML句法、专针对于你的应用程序。

    可重用的组件

    我们使用指令以创建可重复使用的组件。组件允许你隐藏复杂的DOM结构、CSS以及行为。这使你能够专注于应用程序要做什么,或者单独的应用程序看起来如何。

    本地化

    严肃的应用程序的一个重要组成部分是本地化。AngularJS的本地探知筛选器以及阻塞指令使你能够建立屏蔽,使你的应用程序在所有的地方都可用。

    index.html

    <!doctype html><html ng-app="app">  <head>    <script src="https://atts.51coolma.cn/attachments/image/wk/angularjs/angular.min.js"></script>    <script src="components.js"></script>    <script src="app.js"></script>    <link rel="stylesheet" href="bootstrap.css">  </head>  <body>    <tabs>      <pane title="Localization">        Date: {{ '2012-04-01' | date:'fullDate' }} <br>        Currency: {{ 123456 | currency }} <br>        Number: {{ 98765.4321 | number }} <br>      </pane>      <pane title="Pluralization">        <div ng-controller="BeerCounter">          <div ng-repeat="beerCount in beers">            <ng-pluralize count="beerCount" when="beerForms"></ng-pluralize>          </div>        </div>      </pane>    </tabs>  </body></html>

    components.js

    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:        '<div class="tabbable">' +          '<ul class="nav nav-tabs">' +            '<li ng-repeat="pane in panes" ng-class="{active:pane.selected}">'+              '<a href="" ng-click="select(pane)">{{pane.title}}</a>' +            '</li>' +          '</ul>' +          '<div class="tab-content" ng-transclude></div>' +        '</div>',      replace: true    };  })  .directive('pane', function() {    return {      require: '^tabs',      restrict: 'E',      transclude: true,      scope: { title: '@' },      link: function(scope, element, attrs, tabsController) {        tabsController.addPane(scope);      },      template:        '<div class="tab-pane" ng-class="{active: selected}" ng-transclude>' +        '</div>',      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'    };  } 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的内部运作机制消除了测试片层分享。

    必要准备工作

    PhoneCat教程应用程序

    AngularJS最好的入方法是跟着教程操作,它带领你经历了一个AngularJS网页应用程序的构建。你将建立的这个应用是一个目录,显示了一个安卓设备的列表,让你能够筛选列表,可以只查看你感兴趣的设备,然后查看任何设备的详情。

    运行在浏览器上的演示应用

    跟随着这个教程以看到Angular如何让浏览器变得更聪明——不需要使用原生的扩展或者插件:

    • 查看如何使用客户端数据绑定的示例,以建立数据的动态视图,它会响应用户的操作立即改变自己。
    • 查看Angular如何在与你的数据同步的同时,保持你的视图不变,不需要DOM操纵。
    • 学一个更好的、更容易的方法,以测试你的网页应用,利用Karma以及Protractor。
    • 学会如何使用依赖性注入和服务,以制作常见的网页任务,比如说更容易地在应用中获得数据。

    当你看完该教程时你将能够:

    • 创建工作在现代浏览器中的动态的应用程序。
    • 使用数据绑定以把你的数据模块连接到你的视图中。
    • 利用Karma创建并运行单元测试。
    • 利用Protractor创建并运行端到端测试。
    • 从模板中移出应用逻辑,移到控件中。
    • 使用Angular服务从服务器端获得数据。
    • 使用ngAnimate把动画应用到你的应用程序中。
    • 识别资源以学习更多关于AngularJS。

    本教程将指导你完成建立一个应用程序的整个过程,包括编写并运行单元测试和端到端测试。每一步骤的末尾的实验向你提供了学习更多关于AngularJS的建议,以及你正在建立的应用程序的建议。

    你可以在几个时内看完整个教程,或者你可能会想愉快地花一天时间真正深入挖掘它。如果你寻求更短的AngularJS的入门,请仔细阅读起步文档。

    起步

    本页的剩余部分解释了你可以如何设置你的本地机器用于开发。如果你只是想阅读教程,则你可以直接查看第一步:第一步 引导程序

    操作代码

    你可以在你自己的电脑上跟随着这个教程、摆弄代码。用这种方法,你可以得到真正书写AngularJS代码的亲手实践,还使用了推荐的测试工具。

    该教程取决于为源代码管理器使用了哪个版本的Git。除了安装并运行几段git代码,你不需要知道关于Git的任何东西,只要跟着这个教程。

    安装Git

    你可以从http://git-scm.com/download下载并安装Git。一旦安装好了,你应该能够访问到git命令行工具。你将需要用到的主要命令是:

    • git clone ... : 把一个远程的知识库克隆到你的本地机器上
    • git checkout ... : 检查一个特定的分支或一个代码的标记版本以破解

    下载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

    如果你想运行预配置的本地web服务器以及测试工具,则你还需要Node.js v0.10.27+

    你可以针对你的操作系统从http://nodejs.org/download/下载一个Node.js安装包。

    运行以下的命令行,检查你已经安装的Node.js的版本:

    node --version

    在基于Debian的发行版中,与别的实用工具有一个名称冲突,它称为node。建议的解决方案是再安装nodejs-legacy apt 安装包,它会把node重命名为nodejs

    apt-get install nodejs-legacy npmnodejs --versionnpm --version
    如果你需要在你的本地环境中运行Node.js的不同版本,请考虑安装Node版本管理器(nvm)

    一旦你已经在你的机器上安装了Node.js,你可以依靠运行以下代码下载该工具。

    npm install

    这个命令读取了angular-phonecat的package.json文件,并把以下工具下载到node_modules目录中:

    运行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命令行,你只需要输入以下指令:

    sudo npm install -g bower

    (Omit the sudo if running on Windows)

    然后你可以直接运行该bower工具了,如下:

    bower install

    运行开发Web服务器

    虽然Angular应用程序是纯客户端代码,而且能够直接从文件系统中,在web浏览器中打开它们,但是最好从一个HTTP web服务器中供应它们。特别是,为了安全原因,如果网页直接从文件系统中加载,很多现代浏览器不允许JavaScript发起服务器请求。

    为了在开发期间托管应用程序,用一个简单的静态的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。
    • 在该浏览器中执行所有的单元测试
    • 报告在终端/命令行窗口中的那些测试的结果
    • 观察所有项目的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视图作了改变的时候运行端到端测试,或者当你想检查该应用程序作为一个整体是否正确执行时,运行端到端测试。通常在把一个新的改变提交到远程知识库之前运行端到端测试。

    现在你已经测试好了你的本地机器,让我们开始这个教程吧:第一步 引导程序

    静态模板

    为了演示Angular如何增强静态HTML,你可以创建一个纯静态HTML网页,然后仔细观察我们可以如何把这些HTML代码变成一个模板,从而Angular可以用来动态显示同样的结果、以任何数据集显示结果。

    在这一步中,你将在一个HTML页面中添加关于两款手机的基本的信息。

    • 网页现在包含了一个列表,带有两款手机的信息。

    把工作空间重置到第一步

    git checkout -f step-1

    刷新你的浏览器或在线检查这一步:Step 1 Live Demo

    下面列出了第零步和第一步之间的最重要的区别。你可以在GitHub里看到完整的差异。

    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>

    实验

    • 尝试向index.html添加更多的静态HTML。比如:
    <p>Total number of phones: 2</p>

    总结

    额外对你的应用使用静态HTML以显示这个列表。现在,让我们前往第二步 Angular模板以学习如何使用AngularJS以动态生成同一个列表。

    在下一节内容中,我们将介绍 AngularJS 静态模板

    Angular模板

    现在是时候用AngularJS制作动态网页了。我们将添加一个测试,验证用于控制器的代码,我们将添加这个控制器。

    为应用程序构造代码有很多方式。针对Angular应用,我们鼓励使用模块-视图-控制器(MVC)设计模式以解耦代码、分离关注点。考虑到这一点,我们使用小的Angular以及JavaScript为我们的应用添加模块、视图和控制器组件。

    • 现在下面的数据中动态生成了三款手机的列表:

    把工作空间重置到第二步

    git checkout -f step-2

    刷新你的浏览器或在线检查这一步:Step 2 Live Demo

    下面列出了第一步和第二步之间的最重要的区别。你可以在GitHub里看到完整的差异。

    视图和模板

    在Angular中,视图是模块透过HTML模板的映射。这意味着每当模块有变化时,Angular会刷新适当的绑定点,随之更新视图。

    以下面代码为模板,Angular结构化了视图组件:

    app/index.html:

    <html ng-app="phonecatApp"><head>  ...  <script src="/attachments/image/wk/angularjs/angular.js"></script>  <script src="/attachments/image/wk/angularjs/controllers.js"></script></head><body ng-controller="PhoneListCtrl">  <ul>    <li ng-repeat="phone in phones">      <span>{{phone.name}}</span>      <p>{{phone.snippet}}</p>    </li>  </ul></body></html>

    我们用ngRepeat指令和两个Angular表达式替代硬编码的手机列表:

    • <li>元素标签上的元素属性ng-repeat="phone in phones"是一个Angular转发器指令。该转发器告诉Angular为列表中的每款使用元素标签<li>作为模板的手机创建一个<li>元素。
    • 用花括号包围的表达式({{phone.name}}{{phone.snippet}})将被替换成表达式的值。

    我们已经添加了一个新指令,称为ng-controller,它给元素标签<body>附加了一个PhoneListCtrl控制器。在这个点上:

    • 在花括号中的表达式({{phone.name}}{{phone.snippet}})表示绑定,在我们的应用程序模块中参引它们,它们被设置在我们的PhoneListCtrl控制器上。
    注意:我们已经指定了一个[Angular模块](https://docs.angularjs.org/api/ng/type/angular.Module)以载入使用`ng-app="phonecatApp"`,在那里,`phonecatApp`是我们的模块名。该模块将包含`PhoneListCtrl`。

    模块和控制器

    数据模块(一个简单的手机数列,以对象字面记号法表达)现在在PhoneListCtrl控制器中实例化了。该控制器只是一个构造器函数,需要一个$scope参数:

    app/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指令(在元素标签<html>上)现在指定了phonecatApp模块名作为载入的模块,在引导应用Angular应用程序时载入该模块。

    虽然控制器没有做太多的事情,但是它扮演了一个至关重要的角色。通过为我们的上下文提供数据模块,控制器允许我们在模块和视图之间建立数据绑定。我们在展示、数据和逻辑组件之间添加点状虚线,如下所示:

    • ngController指令,定位在<body>元素标签上,引用了我们的控制器的名称,PhoneListCtrl(放置在JavaScript文件controllers.js上)。
    • PhoneListCtrl控件在$scope上附加了手机数据,把它注入到我们的控制器函数中。该作用域根作用域的原型化的后代,在定义应用程序的时候创建了该根作用域。该控制器作用域可以在元素标签<body ng-controller="PhoneListCtrl">内部的所有绑定位置上可用。

    作用域

    一个作用域的概念在Angular中是至关重要的。作用域可以被视为胶合剂,允许模板、模块和控制器一起工作。Angular使用作用域,以及模板、数据模块和控制器中包含的信息,以保持模块和视图分离,但是同步。任何对模块的改变会影响视图;任何在视图中发生的改变反应在模块中。

    要想学习更多关于Angular作用域的知识,请参阅angular作用域文档

    测试

    从视图中分离控制器的“Angular方法”,使测试代码变得容易,就像是它在被开发那样。如果你的控制器在全局命名空间中可用,则我们可以用一个模拟的scope对象简单把它实例化:

    test/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中创建测试变得容易,从而可以鼓励开发员编写它们。

    测试非全局控制器

    在实践中,你应该不想让你的控制器函数在全局命名空间内。取而代之的是,你可以看到我们已经利用一个phonecatApp模块上的匿名构造器函数注册了控制器。

    在这种情况下,Angular提供了一个服务,$controller,它可以以名称接收你的控制器。这里有使用$controller同样的测试:

    test/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模块。
    • 我们要求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测试运行变得极其缓慢。

    实验

    • 添加对index.html的另一个绑定。例如:

      <p>Total number of phones: {{phones.length}}</p>
    • 在控制器中创建一个新模块属性,然后从模板中把它绑定到模块上。例如:

      $scope.name = "World";

      然后向index.html添加一个新的绑定:

      <p>Hello, {{name}}!</p>

      刷新你的浏览器,核实它是否说了"Hello, World!"。

    • ./test/unit/controllersSpec.js中的控制器更新单元测试,以反映以前的变化。例如添加:

      expect(scope.name).toBe('World');
    • index.html中创建一个重复器,它结构化了一个简单的表格:

      <table>  <tr><th>row number</th></tr>  <tr ng-repeat="i in [0, 1, 2, 3, 4, 5, 6, 7]"><td>{{i}}</td></tr></table>

      现在,让这个基于1的列表的i在绑定中增值1。

      <table>  <tr><th>row number</th></tr>  <tr ng-repeat="i in [0, 1, 2, 3, 4, 5, 6, 7]"><td>{{i+1}}</td></tr></table>

      另需指出:尝试并使一个8x8的表格使用一个额外的ng-repeat

    • 通过把expect(scope.phones.length).toBe(3)变成toBe(4),使单元测试失败。

    总结

    现在你有了一个动态的应用,功能分离开模块、视力和控制器组件,而且你测试了它们。现在,让我们前往第三步 筛选迭代器以学习如何为应用添加全文搜索。

      筛选迭代器

      我们在上一步中为开发应用打基础做了很多工作,现在我们将做一些简单的事情;我们将添加全文搜索(是的,它很简单!)。我们还将编写一个端到端测试,因为一个好的端到端测试可以帮上大忙。它监视着你的应用,并在发生回归时迅速报告。

      • 现在应用有了一个搜索框。注意页面上的手机列表的变化取决于用户在搜索框中打了什么字。

      把工作空间重置到第三步

      git checkout -f step-3

      刷新你的浏览器或在线检查这一步:Step 3 Live Demo

      下面列出了第二步和第三步之间最重要的区别。你可以在GitHub里看到完整的差异。

      控制器

      我们对控制器不作修改。

      模板

      app/index.html:

        <div class="container-fluid">    <div class="row">      <div class="col-md-2">        <!--Sidebar content-->        Search: <input ng-model="query">      </div>      <div class="col-md-10">        <!--Body content-->        <ul class="phones">          <li ng-repeat="phone in phones | filter:query">            {{phone.name}}            <p>{{phone.snippet}}</p>          </li>        </ul>      </div>    </div>  </div>

      我们添加了一个标准HTML<input>元素标记,并使用Angular的filter函数来处理repeat指令的输入。

      这使用户输入搜索条件,并在手机列表中快速看到搜索结果。新的代码演示如下:

      • 数据绑定:这是Angular的一个核心功能。当网页载入时,Angular把输入框的名称绑定到数据模块的同名的变量上,并保持两者同步。

        在代码中,用户打字到输入框的数据(命名为query)很快可以作为一个筛选器输入到列表迭代器(phone in phones | filter:query)中。在改变数据模块的时候,导致迭代器的输入发生变化,迭代器有效地更新了DOM,以反映模块的当前状态。

      • 使用filter筛选器:filter函数使用了query值发创建一个新的数列,只包含匹配query的记录。

        ngRepeat自动更新了视力,以响应filter筛选器返回的手机数字的变化。该处理对开发者来说是完全透明的。

      测试

      在第二步中,我们学会了如何编写并运行单元测试。对于测试我们的用JavaScript编写的应用程序的控制器和其它组件,单元测试是完美的,但是测试DOM操作或测试我们的应用程序的接通不太方便。针对这些,一个端到端的测试是一个更好的选择。

      该搜索功能完全是通过模板和数据绑定来实现的,我们将编写我们第一个端到端的测试,以验证该功能起了什么作用。

      test/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运行端到端的测试

      甚至虽然测试的句法看起来很像我们的用Jasmine编写的控制器单元测试,但是端到端测试使用Protractor的API。在http://angular.github.io/protractor/#/api可以读到Protractor的API。

      与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.

      实验

      显示当前查询

      通过添加一个绑定到index.html模板的{{query}}来显示query模块当前的值,并看到当你在输入框中打字时,它如何变化。

      在标题中显示查询

      让我们看到我们可以取得query模板的当前值,模块出现在HTML网页的标题上。

      • 把一个端到端测试添加到describe块中,test/e2e/scenarios.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$/);      });    });  });

        运行protractor(npm run protractor),看到测试失败了。

      • 你可能认为你只需要用以下方式向标题标签添加{{query}}

        <title>Google Phone Gallery: {{query}}</title>

        然而,当你重载入这个网页的时候,你不会看到想要的结果。这是因为“查询”模块驻留在作用域内,由ng-controller="PhoneListCtrl"指令在body元素上定义。

        <body ng-controller="PhoneListCtrl">

        如果你想要从<title>元素上绑定查询模块,你必须把ngController声明移动到HTML元素上,因为它是body元素和title元素常用的父元素。

        <html ng-app="phonecatApp" ng-controller="PhoneListCtrl">

        确保从body元素中移除ng-controller声明。

      • 重新运行rpm run protractor,看到现在测试已经看通过了。

      • 在title元素内部使用双花工作得很好,与此同时,你可能会注意到页面加载的一瞬间它们确实显示给用户了。一个更好的解决方案是使用ngBind指令ngBindTemplate指令,当页面加载时用户能看到它们。

        <title ng-bind-template="Google Phone Gallery: {{query}}">Google Phone Gallery</title>

      总结

      我们现在已经把全文搜索添加上去了,还包含了一个用来验证搜索是否起作用的测试!现在让我们前往第四步 双路数据绑定以学会如何向手机应用添加排序功能。

      双路数据绑定

      在这一步中,你将添加一个功能,让你的用户控制手机列表中的项目的排序。这个动态排序由创建一个新模块属性来实现,用迭代器接通它们,并且让数据绑定来完成剩余工作。

      • 除了搜索框,应用显示了一个下拉菜单,允许用户 控制列出的手机的排序。

      把工作空间重置到第四步

      git checkout -f step-4

      刷新你的浏览器或在线检查这一步:Step 4 Live Demo

      下面列出了第四步和第五步之间的最重要的区别。你可以在GitHub里看到完整的差异。

      模板

      app/index.html:

        Search: <input ng-model="query">  Sort by:  <select ng-model="orderProp">    <option value="name">Alphabetical</option>    <option value="age">Newest</option>  </select>  <ul class="phones">    <li ng-repeat="phone in phones | filter:query | orderBy:orderProp">      <span>{{phone.name}}</span>      <p>{{phone.snippet}}</p>    </li>  </ul>

      我们制作以下对index.html模板的改变:

      • 首先,我们添加了一个<select> html元素,命名为orderProp,因此我们的用户可以从两个提供的排序选择中选一个。
      • 然后,我们把filter筛选器连与orderBy?筛选器连缀,以更进一步处理输入到迭代器的处理。orderBy是一个筛选器,取用一个输入数组,复制它,之后返回一个副本,重排序这个副本。

      Angular在select元素以及orderProp模块之间创建了双路数据绑定。然后orderProp被用作针对orderBy筛选器的输入。

      正如我们在这一节第三步中讨论的,关于数据绑定和迭代器,每当模块变化时(比如说因为用户通过选择下拉菜单改变了顺序),Angular的数据绑定将导致该视图自动更新。不臃肿的DOM操作代码是必要的!

      控制器

      app/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.',     'age': 1},    {'name': 'Motorola XOOM? with Wi-Fi',     'snippet': 'The Next, Next Generation tablet.',     'age': 2},    {'name': 'MOTOROLA XOOM?',     'snippet': 'The Next, Next Generation tablet.',     'age': 3}  ];  $scope.orderProp = 'age';});
      • 我们修改了phones模块——手机的数组——并把一个age属性添加到每个手机记录中。属性被用于根据年代排序手机。

      • 我们给控制器添加了一行,把orderProp的默认值设置为age。如果我们还没有在这里设置一个默认值,orderBy筛选器会保持未初始化,直到我们的用户从下拉菜单中挑了一个选项。

        这是讲解双路数据绑定的好时候。注意,当应用在浏览器中载入的时候,下拉菜单中的“最新”被选中。这是因为我们在控制器中把orderProp设置为'age'。从我们的模块到UI的方向中的绑定工作也同样。现在,如果你选择了下拉菜单中的“Alphabetically(字母表排序)”,模块也将被更新,而且重排序了手机。这是数据绑定在反方向中所做的工作——从UI到模块。

      测试

      我们所做的变化将在单元测试和端到端测试中被验证。让我们先看一看单元测试。

      test/unit/controllersSpec.js:

      describe('PhoneCat controllers', function() {  describe('PhoneListCtrl', function(){    var scope, ctrl;    beforeEach(module('phonecatApp'));    beforeEach(inject(function($controller) {      scope = {};      ctrl = $controller('PhoneListCtrl', {$scope:scope});    }));    it('should create "phones" model with 3 phones', function() {      expect(scope.phones.length).toBe(3);    });    it('should set the default value of orderProp model', function() {      expect(scope.orderProp).toBe('age');    });  });});

      单元测试现在核实了默认的排序属性设置。

      我们使用Jasmins的API,把控制器架构抽出到beforeEach块,它由所有的父describe块中的测试共享。

      现在你应该在Karma选项卡中看到了以下输出:

      Chrome 22.0: Executed 2 of 2 SUCCESS (0.021 secs / 0.001 secs)

      让我们把注意力带回到端到端测试。

      test/e2e/scenarios.js:

      ...    it('should be possible to control phone order via the drop down select box', function() {      var phoneNameColumn = element.all(by.repeater('phone in phones').column('phone.name'));      var query = element(by.model('query'));      function getNames() {        return phoneNameColumn.map(function(elm) {          return elm.getText();        });      }      query.sendKeys('tablet'); //let's narrow the dataset to make the test assertions shorter      expect(getNames()).toEqual([        "Motorola XOOMu2122 with Wi-Fi",        "MOTOROLA XOOMu2122"      ]);      element(by.model('orderProp')).element(by.css('option[value="name"]')).click();      expect(getNames()).toEqual([        "MOTOROLA XOOMu2122",        "Motorola XOOMu2122 with Wi-Fi"      ]);    });...

      端到端测试核实了选择框的排序机制正在正常工作。

      现在你可以重新运行npm run protractor以查看测试运行。

      实验

      • PhoneListCtrl控制器中,移除设置orderProp值的状态,你将看到Angular给下拉列表临时地添加了一个新的空白("unknown")选项,而且排序将默认为无序/自然排序。

      • 把一个{{orderProp}}绑定到index.html模板上,从而把它的当前值显示为文本。

      • 在排序值前面添加一个-符号来逆转排序顺序:<option value="-age">Oldest</option>

      总结

      现在我们已经添加了列表排序,并测试了应用,前往第五步 XHR和依赖注入以学习关于Angular服务,以及Angular如何使用依赖性注入。

      XHR和依赖性注入

      本文主要为你介绍了 AngularJS XHR 和依赖注入,这里整理了详细资料和示例代码

      在硬编码的数据集中有三款手机的数据,建立一个应用程序足够了!让我们使用Angular内建的服务之一,$http从服务器上取得更大的数据集我们将使用Angular的依赖性注入(DI)来为PhoneListCtrl控制器提供服务。

      • 现在有一个20个电话的列表,从服务器载入。

      把工作空间重置到第五步

      git checkout -f step-5

      刷新你的浏览器或在线检查这一步:Step 5 Live Demo

      下面列出了第四步和第五步之间的最重要的区别。你可以在GitHub里看到完整的差异。

      数据

      在你的项目中,app/phones/phones.json文件是一个数据集,包含了一个更大的手机列表,以JSON格式存储。

      遵照以下文件示例:

      [ {  "age": 13,  "id": "motorola-defy-with-motoblur",  "name": "Motorola DEFYu2122 with MOTOBLURu2122",  "snippet": "Are you ready for everything life throws your way?"  ... },...]

      控制器

      我们将在控制器中使用Angular的$http服务向你的Web服务器发出HTTP请求,取回app/phones/phones.json文件中的数据。$http是几个用Web应用中来处理常见的操作的内建Angular服务之一。Angular在你需要的地方为你注入了这些服务。

      Angular的DI子系统负责管理这些服务。依赖性注入有用助于你的web应用既结构完好(例如,分离表现层、数据和控制三者)以及松弛的耦合(不能由组件自身解决的组件之间的依赖性问题,由DI子系统解决)。

      app/js/controllers.js:

      var phonecatApp = angular.module('phonecatApp', []);phonecatApp.controller('PhoneListCtrl', function ($scope, $http) {  $http.get('phones/phones.json').success(function(data) {    $scope.phones = data;  });  $scope.orderProp = 'age';});

      $http向你的Web服务器发出一个HTTP GET请求,要求phones/phones.json(该url相对于我们的index.html文件)。服务器在json文件中提供该数据,以响应该请求。(响应可能是由后端服务器动态生成的。但是在浏览器和我们的应用看来,它们没什么不同。为了简单起见,我们在本教程中使用了一个json文件。)

      $http服务返回了一个promise对象?,带有success方法。我们调用这个方法以处理异步响应,并假定该作用域的手机数据由该控制器控制,作为一个模块,称为phones。注意Angular侦测了该json响应,并为我们解析了它。

      要想在Angular中使用一个服务,你只要声明你所需要的依赖性的名字,作为控制器的构造函数的参数,如下所示:

      phonecatApp.controller('PhoneListCtrl', function ($scope, $http) {...}

      在构造控制器时,Angular的依赖性注入器会把这些服务注入到你的控制器中。这些依赖性控制器还负责创建该服务可能需要的任何传递依赖性(一个服务通常会依赖于其它服务)。

      注意,参数的名称非常重要,因为注入器会用这些名称去查阅依赖性。

      $前缀名称约定

      你可以创建你自己的服务,而且实际上我们将在第十一步 AngularJS REST和自定义服务做这个。作为一个命名约定,Angular的内建服务,作用域方法以及一些别的Angular API在命名前面使用一个$前缀。

      Angular提供的服务的命名空间有$前缀。要想避免冲突,最好避免把你的服务和模块命名成带有$前缀。

      如果你检查一个作用域,你可能还会注意到一些属性以$$开头。这些属性被视为是私有属性,不能访问或者修改。

      在极简化上的一个注记

      因为Angular从参数的名称调用控制器的依赖性到控制器构造器的函数,如果你打算为PhoneListCtrl控制器缩小JavaScript代码,所有的函数参数都会被压缩,而且依赖性注入器将不能正确的识别服务。

      我们可以克服这个问题,通过用依赖性的名称注释这个函数,作为字符串提供,它不会被压缩。提供这种注入注释有两种方法:

      • 在控制器函数中创建一个$inject属性,它可携带一个字符串数组。在数组中的每个字符串都是要注入到对应的参数上的服务的名称。我们可以在自己的示例中这样写:

            function PhoneListCtrl($scope, $http) {...}    PhoneListCtrl.$inject = ['$scope', '$http'];    phonecatApp.controller('PhoneListCtrl', PhoneListCtrl);
      • 在那里使用一个内联注释,并非是只提供这个函数,你还提供了一个数组。这个数组包含了一系列服务名称,后跟着函数本身。

            function PhoneListCtrl($scope, $http) {...}    phonecatApp.controller('PhoneListCtrl', ['$scope', '$http', PhoneListCtrl]);

      两种方法都能与Angular注入的任何函数完美协作,因此要选用哪种方法完全取决于你的项目的编程风格。

      如果使用第二种方法,在注册控制器时,通常以匿名函数的形式提供内联的构造器函数。

          phonecatApp.controller('PhoneListCtrl', ['$scope', '$http', function($scope, $http) {...}]);

      从此刻开始,我们将在本教程中使用内联方法。考虑到这一点,让我们把注释加到PhoneListCtrl上:

      app/js/controllers.js:

      var phonecatApp = angular.module('phonecatApp', []);phonecatApp.controller('PhoneListCtrl', ['$scope', '$http',  function ($scope, $http) {    $http.get('phones/phones.json').success(function(data) {      $scope.phones = data;    });    $scope.orderProp = 'age';  }]);

      测试

      test/unit/controllersSpec.js:

      因为我们开始使用依赖性注入,而且我们的控制器包含了依赖性,在我们的测试中构造控制器就变得有点复杂了。我们可以使用new操作符,并提供带有某种假的$http实现的构造器。然而,Angular提供了一个模拟$http服务,我们可以用在单元测试中。我们通过调用一个称为$httpBackend服务上的方法,为服务器请求配置了“假的”响应。

      describe('PhoneCat controllers', function() {  describe('PhoneListCtrl', function(){    var scope, ctrl, $httpBackend;    // 在每次测试之前载入我们的应用模块定义    beforeEach(module('phonecatApp'));    // 注入器会忽略前面和后面的下划线(例如_$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});    }));

      注意:因为我们在测试环境中载入了Jasmine以及angular-mocks.js,我们得到了两个辅助方法moduleinject,用来访问和配置注入器。

      我们在测试环境中创建控制器,如下所示:

      • 我们使用inject辅助方法,向Jasmine的beforeEach函数注入$rootScope$controller$httpBackend服务的实例,这些实例来自于一个注入器,在每一个测试内部都会被重新创建这个注入器。这保证了每次测试都从一个众所周知的起点开始,每次测试与其它测试相互独立。
      • 通过调用$rootScope.$new()来为我们的控制器创建一个新的作用域。
      • 调用了已注入的$controller函数,以参数的形式传入PhoneListCtrl控制器的名称和创建范围。

      因为我们的代码现在使用$http服务以取回我们的控制器中的手机列表数据,在我们创建PhoneListCtrl子作用域之前,我们需要告诉测试套件等待一个后面的请求,来自控制器。我们可以这样做:

      • 请求把$httpBackend服务注入到我们的beforeEach函数中。这是一个在产品环境中的服务的模拟版本,可以响应各种XHR和JSONP请求。该服务的模拟版本允许你编写测试,不需要处理原生的API和与它相关的全局状态——本来这两者都会使测试变成一个噩梦。

      • 使用$httpBackend.expectGET方法规定$httpBackend服务等待之后的HTTP请求,并告诉它如何响应它。注意,直到我们调用$httpBackend.flush方法,才会返回响应。

      现在我们作了断言以核实在响应到达之前,作用域上不存在手机模块:

          it('should create "phones" model with 2 phones fetched from xhr', function() {      expect(scope.phones).toBeUndefined();      $httpBackend.flush();      expect(scope.phones).toEqual([{name: 'Nexus S'},                                   {name: 'Motorola DROID'}]);    });
      • 通过调用$httpBackend.flush(),我们清空了浏览器中的请求队列。这导致$http服务返回的promise对象由规范的应答来处理。可以在模拟$httpBackend文档中了解为什么必须“清空HTTP请求”的完整解释。

      • 我们制作了断言,核实作用域上已经有手机模块了。

      最后,我们核实已经正确设置了orderProp的默认值。

          it('should set the default value of orderProp model', function() {      expect(scope.orderProp).toBe('age');    });

      现在在Karma标签卡中,你应该看到以下的输出:

      Chrome 22.0: Executed 2 of 2 SUCCESS (0.028 secs / 0.007 secs)

      实验

      • index.html的底部,添加一个<pre>{{phones | filter:query | orderBy:orderProp | json}}</pre>绑定以查看以json格式显示的手机列表。
      • PhoneListCtrl控制器中,通过限制手机的数量为列表的前五个来预处理http响应。在$http回调中使用以下的代码:
      $scope.phones = data.splice(0, 5);

      总结

      现在你已经知道了使用Angular服务是多么容易(幸亏Angular的依赖性注入),前往第六步 模板连接和图像,在那里你将添加一些手机的缩略图以及一些链接。

      模板连接和图像

      在这一步中,你将为手机列表中的手机添加一个缩略图,并链接到想去的地方。在后续的步骤中,你将使用这个链接显示关于目录中的这款手机的额外的信息。

      • 现在列表中的手机已经有链接和图像了。

      把工作空间重置到第六步

      git checkout -f step-6

      刷新你的浏览器或在线检查这一步:Step 6 Live Demo

      下面列出了第五步和第六步之间的最重要的区别。你可以在GitHub里看到完整的差异。

      数据

      注意,phones.json文件包含了针对每款手机的独一无二的ID和图像URL。URL指向app/img/phones目录。

      app/phones/phones.json(示例片段):

      [  {    ...    "id": "motorola-defy-with-motoblur",    "imageUrl": "img/phones/motorola-defy-with-motoblur.0.jpg",    "name": "Motorola DEFYu2122 with MOTOBLURu2122",    ...  },  ...]

      模板

      app/index.html:

      ...        <ul class="phones">          <li ng-repeat="phone in phones | filter:query | orderBy:orderProp" class="thumbnail">            <a href="#/phones/{{phone.id}}" class="thumb"><img ng-src="{{phone.imageUrl}}"></a>            <a href="#/phones/{{phone.id}}">{{phone.name}}</a>            <p>{{phone.snippet}}</p>          </li>        </ul>...

      要想未来动态生成指向手机详情页的链接,我们在元素属性href的值上使用了广为人知的双花括号绑定。在第二步中,我们添加了{{phone.name}}绑定,作为元素内容。在这一步中,{{phone.id}}绑定用在元素属性上。

      我们还在每条记录后面添加了手机图像,用一个带ngSrc指令的图像标签。这个指令防止浏览器字面理解Anglar标签{{ expression }}、向无效的URLhttp://localhost:8000/app/{{phone.imageUrl}}初始化一个请求,如果我们只在一个常规的元素属性src上绑定这个双花括号值(<img src="{{phone.imageUrl}}">),这样的事情真的会发生。使用ngSrc指令以防止浏览器对一个无效的位置发起http请求。

      测试

      test/e2e/scenarios.js:

      ...    it('should render phone specific links', function() {      var query = element(by.model('query'));      query.sendKeys('nexus');      element.all(by.css('.phones li a')).first().click();      browser.getLocationAbsUrl().then(function(url) {        expect(url).toBe('/phones/nexus-s');      });    });...

      我们添加了一个新的端到端测试,以核查应用是否生成了正确的链接,正确的链接要链到手机视图中,我们将在下一步是实现这个视图。

      现在你可以重新运行npm run protractor以查看测试运行。

      实验

      • 用扁平的旧元素属性src代替ng-src指令。使用一些工具,比如说Firebug,或者Chrome的Web Inspector,或者检查web服务器的访问日志,确保应用确实发起过了一个外部请求,指向/app/%7B%7Bphone.imageUrl%7D%7D(或者/app/{{phone.imageUrl}})。

        有个话题是,在Angular已经有机会评估表达式、并注入有效地址之前,当浏览器读取img标签时,浏览器将向非法的图像地址发起一个请求。

      总结

      现在你已经添加了一个手机图像和链接,前往第七步 路由与多视图以学习关于Angular布局模板、以及Angular如何使创建具有多个视图的应用变容易。

      路由与多视图

      在这一步中,你将学会如何通过使用被称为'ngRoute'的Angular模块添加路由,创建一个布局模板,以及如何绑定一个具有多视图的应用。

      • 当你导航到app/index.html上时,你将跳车到app/index.html/#/phones,而且手机列表出现在浏览器中。
      • 当你在手机链接上点击时,url变成特定的手机,出现了手机详情页。

      把工作空间重置到第七步

      git checkout -f step-7

      刷新你的浏览器或在线检查这一步:Step 7 Live Demo

      下面列出了第六步和第七步之间的区别。你可以在GitHub里看到完整的差异。

      依赖性

      这一步中添加路由功能是由 ngRoute模块中的angular提供的,它与核心的Angular框架分离分布。

      我们使用Bower以安装客户端依赖性。这一步更新了bower.json配置文件,以包含新的依赖性:

      {  "name": "angular-phonecat",  "description": "A starter project for AngularJS",  "version": "0.0.0",  "homepage": "https://github.com/angular/angular-phonecat",  "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-route": "1.4.x"告诉bower要安装与v1.4x兼容的angular-router组件版本。我们将告诉bower以下载并安装该依赖性。

      如果你已经全局安装了bower,则你可以只对该项目运行bower install,我们已经预配置了npm,从而为我们运行bower安装:

      npm install

      多个视图、路由和布局模板

      我们的应用渐渐地完善,变得越来越复杂。在第七步之前,应用向我们用户提供了单一视图(手机的列表),而且所有的模板代码都位于index.html文件中。构建应用的下一步是添加一个视图,这个视图将显示我们的列表中每款设备的详细信息。

      要想添加详情视图,我们可以扩展index.html以包含两套视图的模板代码,但是那将很快变得混乱。因此我们不用这种方法,而是把index.html变成“布局模板”。这是一个模板,常用于我们应用中的所有视图。然后别的“局部布局模板”根据当前的“路由”包含到这个布局模板中,从而形成一个完整视图展示给用户。

      通过$routeProvider来声明Angular中的应用程序路由,它是$route服务的提供者。这个服务使接通控制器、视图模板以及浏览器中的当前位置变得容易。利用这个功能,我们可以实现深链接,深链接让我们可以使用浏览器的历史(回退和前进导航)以及书签。

      一条关于DI、注入器和提供者的提醒

      如你已注意到的依赖性注入(DI)是AngularJS的核心,所以对它的工作原理略知一二是很重要的。

      在应用程序引导中,Angular创建了一个注入器,注入器用来寻找并注入你的应用所需要的所有的服务。注入器本身对$http$route服务是做什么的一无所知。实际上,注入器甚至不知道这些服务是否存在,除非用适当的模板定义对它进行配置。

      注入器只在以下步骤中出场:

      • 载入你在你的应用中指定的模块定义。
      • 注册所有的在模块定义中定义的提供者。
      • 当被要求做这的时候,注入一个指定的函数以及一些必要的依赖性(服务),它通过它们的提供者来惰性实例化。

      提供者是提供(创建)服务实例并且对外提供配置API的对象,API可以用来控制一个服务的创建和运行时行为。对于$route来说,$routeProvider对外提供API,API允许你定义针对你的应用程序的路由。

      **注意:**只能够把提供者注入到`config`函数中。因此你不能够把`$routeProvider`注入到`PhoneListCtrl`中。

      Angular模块解决了从应用程序中移除全局状态的问题,并提供配置注入器的方法。相对于AMD或require.js模块,Angular模块并不试图解决脚本载入次序问题或者懒惰式脚本取得问题。这些目标是完全独立的,两个模块系统可以并立存在,并实现他们的目标。

      要想加深你对Angular上的DI的理解,请参看理解依赖性注入

      模板

      $route服务常与ngView指令结合使用。ngView指令的角色是在布局模板中包含用于当前路由的视图模板。这使它完美恰合我们的index.html模板。

      **注意:**从AngularJS v1.2版开始,`ngRoute`在它自己的模块中,必须通过载入额外的`angular-route.js`文件来载入它,我们通过上面的Bower来下载`angular-route.js`文件。

      app/index.html:

      <!doctype html><html lang="en" ng-app="phonecatApp"><head>...  <script src="/attachments/image/wk/angularjs/angular.js"></script>  <script src="/attachments/image/wk/angularjs/angular-route.js"></script>  <script src="/attachments/image/wk/angularjs/app.js"></script>  <script src="/attachments/image/wk/angularjs/controllers.js"></script></head><body>  <div ng-view></div></body></html>

      我们已经在我们的索引文件添加了两个新的<script>标记,从而把外部JavaScript文件载入到我们的应用程序中:

      • angular-route.js : 定义Angular ngRoute模块,ngRoute模块向我们提供了路由。
      • app.js : 现在这个文件控住了我们的应用程序的根模块。

      注意:我们删除了index.html模板中的大部分代码,把它替换成一行代码,包含了一个带有元素属性ng-view的div。我们已经移除的这个代码被放到了phone-list.html模板中:

      app/partials/phone-list.html:

      <div class="container-fluid">  <div class="row">    <div class="col-md-2">      <!--Sidebar content-->      Search: <input ng-model="query">      Sort by:      <select ng-model="orderProp">        <option value="name">Alphabetical</option>        <option value="age">Newest</option>      </select>    </div>    <div class="col-md-10">      <!--Body content-->      <ul class="phones">        <li ng-repeat="phone in phones | filter:query | orderBy:orderProp" class="thumbnail">          <a href="#/phones/{{phone.id}}" class="thumb"><img ng-src="{{phone.imageUrl}}"></a>          <a href="#/phones/{{phone.id}}">{{phone.name}}</a>          <p>{{phone.snippet}}</p>        </li>      </ul>    </div>  </div></div>
      TODO!

      我们还为手机详情视图添加了一个占位符模板:

      app/partials/phone-detail.html:

      TBD: detail view for <span>{{phoneId}}</span>

      注意,我们正在使用的phoneId表达式将在PhoneDetailCtrl控制器中定义。

      应用模块

      要想增强应用的组织,我们动用了Angular的ngRoute模块,我们已经把控制器移到它们自己的模块phonecatControllers中(如下所示)。

      我们给index.html添加angular-route.js,并在controllers.js中创建一个新的phonecatControllers模块。然而,要想使用它们的代码,我们需要做的不止于此。我们还需要添加模块,作为我们的应用的依赖性。通过把两个应用作为phonecatApp的依赖性列表,我们可以使用这些指令以及它们提供的服务。

      app/js/app.js:

      var phonecatApp = angular.module('phonecatApp', [  'ngRoute',  'phonecatControllers']);...

      注意第二个参数传递到angular.module,['ngRoute','phonecatControllers']。这个数组列出了phonecatApp所依赖的模块。

      ...phonecatApp.config(['$routeProvider',  function($routeProvider) {    $routeProvider.      when('/phones', {        templateUrl: 'partials/phone-list.html',        controller: 'PhoneListCtrl'      }).      when('/phones/:phoneId', {        templateUrl: 'partials/phone-detail.html',        controller: 'PhoneDetailCtrl'      }).      otherwise({        redirectTo: '/phones'      });  }]);

      使用phonecatApp.config()方法,我们请求了$routeProvider,它会被注入到我们的配置函数中,并使用?$routeProvider.when()方法以定义我们的路由。

      我们的应用程序路由定义如下:

      • when('/phones'):当URL映射段为/phones的时候。将展示这个手机列表视图。要想构造这个视图,Angular将使用phone-list.html模板,以及PhoneListCtrl控制器。
      • when('/phones/:phoneId'):当URL映射段匹配/phones/:phoneId的时候(其中:phoneId是URL的变量部分),将展示手机详情视图。要想构造手机详情视图,Angular将使用phone-detail.html模板以及PhoneDetailCtrl控制器。
      • otherwise({redirectTo: '/phones'}):当浏览器的地址不匹配我们别的路由的时候,触发一个重定向到/phones

      我们再次使用我们在上一步中构造的PhoneListCtrl控制器,并为手机详情视图向app/js/controllers.js文件添加了一个新的、空的PhoneDetailCtrl控制器。

      注意在第二个路由声明中:phoneId参数的使用。$route服务使用route声明'/phones/:phoneId'作为匹配当前URL的模板。所有用:记号法定义的变量都会提取出来,放到?$routeParams对象上。

      控制器

      app/js/controllers.js:

      var phonecatControllers = angular.module('phonecatControllers', []);phonecatControllers.controller('PhoneListCtrl', ['$scope', '$http',  function ($scope, $http) {    $http.get('phones/phones.json').success(function(data) {      $scope.phones = data;    });    $scope.orderProp = 'age';  }]);phonecatControllers.controller('PhoneDetailCtrl', ['$scope', '$routeParams',  function($scope, $routeParams) {    $scope.phoneId = $routeParams.phoneId;  }]);

      再次提醒注意,我们创建了一个新的模块,称为phonecatControllers。对于小型的AngularJS应用,通常针对所有的控制器只创建一个模板,如果控制器只有为数不多的几个。随着你的应用程序扩大,常常要把你的代码重构到额外的模块中。为了更大的应用,你可能将会想要为你的应用的所有的主要功能创建独立的模块。

      因为我们的应用比较小,我们将把我们所有的控制器添加到phonecatControllers模块中。

      测试

      要想自动核查所有东西都正确连通了,我们编写了一个端到端的测试,导航到不同的URL上,并核查是否呈现了正确的视图。

      ...   it('should redirect index.html to index.html#/phones', function() {    browser.get('app/index.html');    browser.getLocationAbsUrl().then(function(url) {        expect(url).toEqual('/phones');      });  });  describe('Phone list view', function() {    beforeEach(function() {      browser.get('app/index.html#/phones');    });...  describe('Phone detail view', function() {    beforeEach(function() {      browser.get('app/index.html#/phones/nexus-s');    });    it('should display placeholder page with phoneId', function() {      expect(element(by.binding('phoneId')).getText()).toBe('nexus-s');    });  });

      你现在可以再次运行npm run protractor来查看测试的运行。

      实验

      • 尝试添加一个绑定到index.html{{orderProp}},而且你将看到什么事也没有发生,哪怕你正在手机列表视图中。这是因为orderProp模块只有在PhoneListCtrl管理的作用域内是可见的,PhoneListCtrl<div ng-view>元素关联。如果你在phone-list.html模板上添加同样的绑定,绑定将如你的预期运作起来。
      * 在`PhoneCatCtrl`中,创建一个带有`this.hero='Zoro'`的新模块,称为"hero"。在`PhoneListCtrl`中,让我们用`this.hero='Batman'`来遮蔽它。在`PhoneDetailCtrl`中,我们将使用`this.hero = "Captain Proton"`。然后 把`

      hero = {{hero}}

      `添加到全部三个模板`index.html`、`phone-list.html`和`phone-detail.html`上。打开应用,你将看到作用域继承以及模板属性遮蔽做了一些奇观。

      总结

      随着路由设置成功以及手机列表视力的实现,我们已经准备好前往第八步 更多模板,以实现手机详情视图。

      更多模板

      在这一步中,你将实现手机详情视图,当用户在手机列表中点击了一款手机,就会显示这个视图。

      • 当你在列表中点击了一款手机,将显示带手机专有信息的手机详情视图。

      要想实现手机详情视力,我们将使用$http以取回我们的数据,然后具体化phone-detail.html视图模板。

      把工作空间重置到第八步

      git checkout -f step-8

      刷新你的浏览器或在线检查这一步:Step 8 Live Demo

      下面列出了第七步和第八步之间最重要的区别。你可以在GitHub上看到完整的差异。

      数据

      除了phones.jsonapp/phones/目录还包括一个针对每款手机的JSON文件:

      app/phones/nexus-s.json: (sample snippet)

      {  "additionalFeatures": "Contour Display, Near Field Communications (NFC),...",  "android": {      "os": "Android 2.3",      "ui": "Android"  },  ...  "images": [      "img/phones/nexus-s.0.jpg",      "img/phones/nexus-s.1.jpg",      "img/phones/nexus-s.2.jpg",      "img/phones/nexus-s.3.jpg"  ],  "storage": {      "flash": "16384MB",      "ram": "512MB"  }}

      每个文件用同样的数据结构描述了手机的多种属性。我们将在手机详情视图中展示这些数据。

      控制器

      我们将使用$http服务以扩展PhoneDetailCtrl,从而取回JSON文件。这以手机列表控制器中同样的方式起作用。

      app/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;    });  }]);

      要想为HTTP请求构造URL,我们使用从$route服务生成的当前路由中提取到的$routeParames.phoneId

      模板

      该TBD占位符行已经被替换成列表,绑定包含了手机详情。注意我们使用Angular的{{expression}}标签以及ngRepeat的地方,用来从我们的模块把投射手机数据到视图中。

      app/partials/phone-detail.html:

      <img ng-src="{{phone.images[0]}}" class="phone"><h1>{{phone.name}}</h1><p>{{phone.description}}</p><ul class="phone-thumbs">  <li ng-repeat="img in phone.images">    <img ng-src="{{img}}">  </li></ul><ul class="specs">  <li>    <span>Availability and Networks</span>    <dl>      <dt>Availability</dt>      <dd ng-repeat="availability in phone.availability">{{availability}}</dd>    </dl>  </li>    ...  <li>    <span>Additional Features</span>    <dd>{{phone.additionalFeatures}}</dd>  </li></ul>
      TODO!

      测试

      我们写了一个新的单元测试,类似于我们之前在第五步中为PhoneListCtrl控制器所写的步骤。

      test/unit/controllersSpec.js:

        beforeEach(module('phonecatApp'));  ...  describe('PhoneDetailCtrl', function(){    var scope, $httpBackend, ctrl;    beforeEach(inject(function(_$httpBackend_, $rootScope, $routeParams, $controller) {      $httpBackend = _$httpBackend_;      $httpBackend.expectGET('phones/xyz.json').respond({name:'phone xyz'});      $routeParams.phoneId = 'xyz';      scope = $rootScope.$new();      ctrl = $controller('PhoneDetailCtrl', {$scope: scope});    }));    it('should fetch phone detail', function() {      expect(scope.phone).toBeUndefined();      $httpBackend.flush();      expect(scope.phone).toEqual({name:'phone xyz'});    });  });...

      你现在可以看到后来在Karma标签中的输出:

      Chrome 22.0: Executed 3 of 3 SUCCESS (0.039 secs / 0.012 secs)

      我们还添加了一个新的端到端的测试,导航到Nexus S详情页面,并核查了页面上的标题是“Nexus S”。

      test/e2e/scenarios.js:

      ...  describe('Phone detail view', function() {    beforeEach(function() {      browser.get('app/index.html#/phones/nexus-s');    });    it('should display nexus-s page', function() {      expect(element(by.binding('phone.name')).getText()).toBe('Nexus S');    });  });...

      你可以再次运行npm run protractor以看到测试运行。

      实验

      • 使用Protractor API,写一个测试核查到我们在Nexus S详情页面中显示了四个缩略图。

      总结

      现在手机详情视力已经到位了,继续前往第九步 筛选器以学习如何编写你自己的自定义显示过滤器。

      筛选器

      在使用 AngularJS 的过程中会遇到一些特殊的数据处理,你可以通过采用 AngularJS 筛选器的方式来对不同的数据处理实现想要达到的效果。

      在这一步中,你将学会如何创建你自己的自定义显示筛选器。

      • 在上一步中,详情页要么显示“true”,要么显示“false”以指示某个手机功能是否存在。我们已经使用了一个自定义筛选器以将那些文本字符串转换成另一种字型:“true”变成?,“false”变成?。让我们看看筛选器代码看起来如何。

      把工作空间重置到第九步

      git checkout -f step-9

      刷新你的浏览器或在线检查这一步:Step 9 Live Demo

      下面列出了第八步和第九步之间最重要的区别。你可以在GitHub上看到完整的差异。

      自定义筛选器

      为了创建一个新筛选器,你即将创建一个phonecatFilters模块,并用这个模块注册你的自定义滤镜:

      app/js/filters.js:

      angular.module('phonecatFilters', []).filter('checkmark', function() {  return function(input) {    return input ? 'u2713' : 'u2718';  };});

      我们的筛选器的名字是“checkmark”。input要么估值为true,要么估值为false,而且会返回我们选中用来代表true和false的两个unicode字符之一(u2713->?代表true,u2718 -> ?代表false)。

      现在我们的筛选器已经准备好了,我们需要注册phonecatFilters模块作为我们的主phonecatApp模块的依赖性。

      app/js/app.js:

      ...angular.module('phonecatApp', ['ngRoute','phonecatControllers','phonecatFilters']);...

      模板

      因为筛选器生存在app/js/filters.js文件夹中,我们需要在我们的布局模板中包含这个文件。

      app/index.html:

      ... <script src="/attachments/image/wk/angularjs/controllers.js"></script> <script src="/attachments/image/wk/angularjs/filters.js"></script>...

      在Angular模板中使用筛选器的句法如下所示:

      {{ expression | filter }}

      让我们在手机详情模板中采用这个筛选器:

      app/partials/phone-detail.html:

      ...    <dl>      <dt>Infrared</dt>      <dd>{{phone.connectivity.infrared | checkmark}}</dd>      <dt>GPS</dt>      <dd>{{phone.connectivity.gps | checkmark}}</dd>    </dl>...

      测试

      筛选器,就像任何别的组件,必须被测试,而且写这些测试很容易。

      test/unit/filtersSpec.js:

      describe('filter', function() {  beforeEach(module('phonecatFilters'));  describe('checkmark', function() {    it('should convert boolean values to unicode checkmark or cross',        inject(function(checkmarkFilter) {      expect(checkmarkFilter(true)).toBe('u2713');      expect(checkmarkFilter(false)).toBe('u2718');    }));  });});

      我们必须在执行任何筛选器测试之前调用beforeEach(module('phonecatFilters'))。这种调用把我们的phonecatFilter模块载入到注入器,以测试运行。

      注意我们将调用助手函数inject(function(checkmarkFilter) { ... }),从而获得访问我们想要测试的文件。参见angular.mock.inject()

      注意在注入的时候,后缀Filter会追加到你的筛选器名称中。参见筛选器指南?部分,在那里是概述。

      你现在必须在Karma选项卡中看到以下的输出:

      Chrome 22.0: Executed 4 of 4 SUCCESS (0.034 secs / 0.012 secs)

      实验

      • 让我们用一些内建的Angular筛选器来做实验,并把以下绑定添加到index.html

        • {{ "lower cap string" | uppercase }}
        • {{ {foo: "bar", baz: 23} | json }}
        • {{ 1304375948024 | date }}
        • {{ 1304375948024 | date:"MM/dd/yyyy @ h:mma" }}
      • 我们可以创建一个模块,带有一个输入元素,并把它与一个筛选绑定结合起来。向index.html添加以下代码:

        <input ng-model="userInput"> Uppercased: {{ userInput | uppercase }}

      总结

      现在你已经学会了如何编写并测试一个自定义筛选器,前往第十步 事件处理函数以学习我们可以如何用Angular继续丰富手机详情页面。

      事件处理函数

      在这一步中,你将添加一个可点击的手机图像交换器,指向手机详情页面。

      • 手机详情视图显示了当前手机的一张大图像以及若干张小的缩略图。如果我们可以通过在想要的缩略图中点吉,从而把大图像与任何小缩略图作替换,这会很棒。让我们看一看我们可以如何用Angular做到这。

      把工作空间重置到第十步

      git checkout -f step-10

      刷新你的浏览器或在线检查这一步:Step 10 Live Demo

      下面列出了第九步和第十步之间最重要的区别。你可以在GitHub上看到完整的差异。

      控制器

      app/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的值。

      模板

      app/partials/phone-detail.html:

      <img ng-src="{{mainImageUrl}}" class="phone">...<ul class="phone-thumbs">  <li ng-repeat="img in phone.images">    <img ng-src="{{img}}" ng-click="setImage(img)">  </li></ul>...

      我们把大图像的ngSrc指令绑定到mainImageUrl属性上。

      我们还将利用缩略图注册一个ngClick处理函数。当用户在缩图略之一上点击时,处理函数将使用setImage事件处理函数以改变mainImageUrl属性的值,把它变成缩略图的URL。

      TODO!

      测试

      要想验证这个功能,我们添加了两个端到端测试。一个验证了主图像被默认设置为每一个手机图像。另一个测试了在一些缩略图上的点击,并验证了相应的主图像改变。

      test/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.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();      ctrl = $controller('PhoneDetailCtrl', {$scope: scope});    }));    it('should fetch phone detail', function() {      expect(scope.phone).toBeUndefined();      $httpBackend.flush();      expect(scope.phone).toEqual(xyzPhoneData());    });  });

      你的单元测试现在应该通过了。

      实验

      • 让我们给PhoneDetailCtrl添加一个新的控制器方法:

        $scope.hello = function(name) {    alert('Hello ' + (name || 'world') + '!');}

        再添加

        <button ng-click="hello('Elmo')">Hello</button>

        to the phone-detail.html template.

      TODO! 控制器方法在控制器/作用域之间继承,因此你可以在`phone-list.html`模板内使用同样的片段。* 把`hello`方法从`PhoneCatCtrl`移到`PhoneListCtrl`,而且你将看到在`index.html`中声明的按钮将停止工作,与此同时在`phone-list.html`模板中声明的那个按钮依然在运作。

      总结

      随着手机图像交换器到位,我们准备前往第十一步 REST和自定义服务以学习取得数据的一个更好方法。

      REST和自定义服务

      在这一步中,你将改变我们获取数据的方法。

      • 我们定义了一个自定义服务,它代表了一个RESTful客户端。利用该客户端,我们可以用更容易的方式制作一个向服务器索取数据的请求,不需要去处理底层?$http API、HTTP方法以及URL。

      把工作空间重置到第十一步

      git checkout -f step-11

      刷新你的浏览器或在线检查这一步:Step 8 Live Demo

      下面列出了第十步和第十一步之间最重要的区别。你可以在GitHub上看到完整的差异。

      依赖性

      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。

      模板

      我们的自定义源服务将被定义在app/js/services.js中,因此我们需要在我们的布局模板中包含这个文件。另外,我们还需要载入angular-resouces.js文件,它包含了ngResource模块:

      app/index.html.

      ...  <script src="/attachments/image/wk/angularjs/angular-resource.js"></script>  <script src="/attachments/image/wk/angularjs/services.js"></script>...

      服务

      我们创建了自己的服务,以提供对服务器上的手机数据的访问:

      app/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.js.

      ...angular.module('phonecatApp', ['ngRoute', 'phonecatControllers','phonecatFilters', 'phonecatServices'])....

      我们需要把phonecatServices模块依赖性添加到phonecatApp模块的需要数列中。

      控制器

      通过重构掉底层的$http服务,我们简化了我们的子控制器(PhoneListCtrlPhoneDetailCtrl),用称为Phone的服务替代它。Angular的$resource服务比$http更容易使用,用来与作为REST的源对外提供的数据源交互。现在我们更容易理解控制器中的这些代码是干什么的了。

      app/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来演示它。

      测试

      因为我们现在使用了ngResource模块,为了用以angular为源更新Karma配置单文件,它是必要的,这样新测试才能通过。

      test/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匹配器,我们的测试将失败,因为测试值不能与响应严格匹配。要想解决这个问题,我们使用了一个新定义的toEqualData[Jasmine matcher][jasmine匹配器]。当toEqualData匹配器对比两个对象的时候,它考虑对象属性属性而忽略对象方法。

      test/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});    }));    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)

      总结

      现在我们已经看到了如何建立一个自定义的服务,作为REST的客户端,我们已经准备好前往第十二步 应用动画(最后一步)以学会如何用动画提高应用程序。

      应用动画

      在这最后一步中,我们将通过在我们之前创建的模板代码的顶部添加CSS和JavaScript动画丰富我们的手机分类网站应用。

      • 我们现在使用ngAnimate模拟以启用动画,以贯穿这个应用。
      • 我们还使用常用的ng指令以自动触发使动画接入的钩子。
      • 发现一个应用之后,动画将在标准DOM操作之间运行,该标准DOM操作在给定的时间内发布在元素上(例如,在ngRepeat上插入和移除节点,或在ngClass上添加和移除类)。

      把工作空间重置到第十二步

      git checkout -f step-12

      刷新你的浏览器或在线检查这一步:Step 12 Live Demo

      下面列出了第十一步和第十二步之间最重要的区别。你可以在GitHub上看到完整的差异。

      依赖性

      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的一个新版本,然后因为需要安装的angular.js版本之间的冲突,你可能在运行`bower install`指令时遇到问题。如果你遇到了这种问题,只需要在运行`npm install`之前先删除你的`app/bower_components`文件夹。
      **注意:**如果你已经全局安全了bower,你可以运行`bower install`,但是对于这个项目,我们已经预配置了运行`npm install`来运行bower。

      动画如何与ngAnimate协作

      要想知道动画如何与AngularJS协作,请先阅读?AngularJS动画指南

      模板

      在HTML模板代码内部需要修改,以链接asset文件,它定义了动画以及angular-animate.js文件。该动画模块,即ngAnimate,被定义在angular-animate.js内部,并包含了必要的代码,以使你的应用程序变得动感。

      这里是在索引文件中需要修改的地方:

      app/index.html.

      ...  <!-- for CSS Transitions and/or Keyframe Animations -->  <link rel="stylesheet" href="css/animations.css">  ...  <!-- jQuery is used for JavaScript animations (include this before angular.js) -->  <script src="/attachments/image/wk/angularjs/jquery.js"></script>  ...  <!-- required module to enable animation support in AngularJS -->  <script src="/attachments/image/wk/angularjs/angular-animate.js"></script>  <!-- for JavaScript Animations -->  <script src="/attachments/image/wk/angularjs/animations.js"></script>...
      **重要:**确保在使用Augular 1.4的时候,使用jQuery v2.1版或更新的版本;官方不支持jQuery v1.x版。确保在所有的AngularJS脚本之前载入jQuery,否则AugularJS不能侦测jQuery,而且动画将不会如预期那样起作用。

      可以在CSS代码(animations.css)内中创建动画,也可以在JavaScript代码(animations.js)内部创建动画。但是在开始之前,让我们创建一个新模块,它使用ngAnimate模块,作为依赖性,就像我们之前用ngResource所作的。

      模块和动画

      app/js/animations.js.

      angular.module('phonecatAnimations', ['ngAnimate']);  // ...  // this module will later be used to define animations  // ...

      现在让我们把这个模块附加到我们的应用程序模块上……

      app/js/app.js.

      // ...angular.module('phonecatApp', [  'ngRoute',  'phonecatAnimations',  'phonecatControllers',  'phonecatFilters',  'phonecatServices',]);// ...

      现在,手机分类模块已经有动感了。让我们制作更多更多动画吧!

      用CSS过渡动画让ngRepeat动起来。

      我们将从这一步开始,把CSS过渡动画添加到出现在phone-list.html网页上的ngRepeat指令。首先让我们把一个额外的CSS类添加到我们的重复元素上,因此我们可以把它与我们的CSS动画代码连接。

      app/partials/phone-list.html.

      <!--  让我们改变重复器HTML,以包含一个新的CSS类,之后我们将用它实现动画:--><ul class="phones">  <li ng-repeat="phone in phones | filter:query | orderBy:orderProp"      class="thumbnail phone-listing">    <a href="#/phones/{{phone.id}}" class="thumb"><img ng-src="{{phone.imageUrl}}"></a>    <a href="#/phones/{{phone.id}}">{{phone.name}}</a>    <p>{{phone.snippet}}</p>  </li></ul>

      注意我们将如何添加phone-listingCSS类?这是我们让动画起作用,在HTML代码中所需要做的。

      以下是实际的CSS过渡动画代码:

      app/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;  padding-top: 0;  padding-bottom: 0;}

      如你所见,我们的phone-listing CSS类与动画钩子相结合,当列表中插入项目或移除项目时,就会出现动画钩子。

      • 当列表中添加了一款新手机并呈现在网页上时,元素上应用了ng-enter类。
      • 当项目绕着列表移动时,元素上应用了ng-move类。
      • 当项目从列表中移除时,元素上应用了ng-leave类。

      添加或删除手机列表项目取决于传递给元素属性ng-repeat的数据。比如,如果过滤器数据改变了,项目动画地加入或退出重复列表。

      有些很重要的事情需要注意,当动画发生时,元素上添加了CSS类的两个集合:

      1. “开始”类代表动画开始时的样式。
      2. “激活”类代表动画结束时的样式。

      开始类的名称是被激发的事件(就像entermoveleave)的名称带上前缀ng-。所以一个enter事件将导致一个称为ng-enter类。

      激活类名与开始类名相同,但是带了一个后缀-active。这两类CSS命名公约允许开发员精心制作一个动画,自始至终。

      在我们上面的示例中,当元素添加到列表中时,该元素从0高度伸展到120像素高度;在从列表中移除之前,又收缩到0像素。同时还发生了一个渐现和渐消的效果。这里都是由CSS过渡动画处理的,CSS过渡动画声明在上面示例代码的顶部。

      虽然大多数现代浏览器能很好地支持CSS过渡CSS动画。但是如果你想让动画与老旧的浏览器后向兼容,请考虑使用基于JavaScript的动画,将在下面详细讲解它。

      用CSS关键帧动画让ngView动起来

      接下来,让我们为在ngView内部、路由之间的过渡添加一个动画。

      首先,让我们给HTML添加一个新的CSS类,就像我们在上面的示例中所作的。这一次,不是使用ng-repeat元素,而是把它添加到包含了ng-view指令的元素上。为了做到这,我们需要对HTML代码做一些小的改变,从而我们可以对我们的动画,在视图改变之间的动画有更多的控制。

      app/index.html.

      <div class="view-container">  <div ng-view class="view-frame"></div></div>

      利用这个改变,ng-view指令被嵌套在一个带有view-containerCSS类的父元素内部。这个类添加了一个position: relative样式,因此动画过程中,ng-view的定位相对于这个父元素。

      在这里,让我们为过渡动画添加CSS,添加到animations.css文件上:

      app/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; }}@-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-enterng-enter-active CSS类会被移除,导致它用它的默认CSS代码重新呈现、重新定位(因此没有一旦动画结束就没有绝对定位了)。这动作起来非常流畅,因此页面在路由变化时流动自然,不会有任何跳动感。

      应用的CSS类(开始和结束类)与ng-repeat很相像。每当一个新页面载入到ng-view指令中时,将创建它自己的一个副本,下载模板并追加内容。这确保了所有的视图都包含在一个单独的HTML元素中,该元素允许简单的动画控制。

      要想了解更多关于CSS动画的信息,请参阅Web 平台文档

      用JavaScript让ngClass动起来

      让我们向应用程序添加另一个动画。切换到phone-detail.html网页,我们看到已经有一个很棒的缩略图交换器。通过点击在网页列列中的缩略图,资料手机图像就变了。但是我们可以如何在改变它的同时添加动画呢?

      让我们先考虑一下。基本上,当你在一个资料图上点击时,你正在改变图像的状态,以反映新选中的缩略图。在HTML中指定状态改变的最佳方法是使用样式类。和以前很相像,我们使用的CSS样式类以指定指定一个动画,当CSS类本身变化时动画将发生。

      当选中了一个新的手机缩略图时,状态改变了,.activeCSS类添加到匹配的资料图像上,并播放了动画。

      让我们开始,在phone-detail.html网贾上微调HTML代码。注意我们已经改变了显示大图像的方式:

      app/partials/phone-detail.html.

      <!-- We're only changing the top of the file --><div class="phone-images">  <img ng-src="{{img}}"       class="phone"       ng-repeat="img in phone.images"       ng-class="{active:mainImageUrl==img}"></div><h1>{{phone.name}}</h1><p>{{phone.description}}</p><ul class="phone-thumbs">  <li ng-repeat="img in phone.images">    <img ng-src="{{img}}" ng-mouseenter="setImage(img)">  </li></ul>

      就像缩略图,我们使用迭代器来显示所有的资料图像作为一个列表,然而我们没有变动任何迭代相关的动画。而是,我们在ng-class指令上保持关注,因为每当active类变成true,则它将应用到元素上,将呈现为可见。否则,资料图像将隐藏。在我们的案例中,总是有一个元素具有active类,因此,任何时候总会有一款手机的资料图像在屏幕上可见。

      当元素上添加了激活类的时候,先添加了active-add类和adtive-add-active类,以指示Angular引发一个动画。当元素上移除了激活类的时候,元素上应用了active-remove类和active-remove-active,它们反过来又会触发别的动画。

      要想确保手机图像在页面第一次加载时正确地显示,我们还要微调详情页的CSS样式:

      app/css/app.css

      .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可用的动画。虽然我们可以那么做,但是还是让我们抓住机会学习如何用abnimate模块方法创建JavaScript可用的动画吧。

      app/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;    }    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回调函数和removeClass回调函数;在本案例中,注册过的类是.phone。当元素上添加了.active类(通过ng-class指令),将引发addClassJavaScript回调函数,该回调函数带有一个参数element。最后传入的参数是done回调函数。done回调函数的目的是,通过调用该函数,当JavaScript动画结束时,可以让Angular知道。

      removeClass回调函数以同样的方式起作用,但是是在一个类从元素上移除时触发它。

      在JavaScript回调函数中,你通过操纵DOM创建了该动画。在上面的代码中,这就是element.css()element.animate()所做的事情。回调函数用500px的偏移定位了下一个元素,把前一个项目和新的项目往上移500px,使两个项目一起动起来。这导致了一个仿传送带的动画。当animate函数完成它的工作,它会调用 done

      注意addClassremoveClass两者都返回了一个函数。这是一个可选的函数,当动画被取消时(同一个元素上发生了别的动画)时或者动画完成时,会调用这个函数。向这个函数传递一个布尔参数,该参数让开发者知道动画是否被取消了。当动画完成时,这个函数可以用来做一些扫尾工作。

      总结

      现在你学会了!我们在相对短的时间里创建了一个web应用。在完结篇中我们将讨论接下来何去何从。

      相关教程

      CSS教程

      完结篇

      我们的应用程序现在完成了。请随意练习这些代码,用git checkout命令跳回到前面的步骤。

      要想获得我们在本教程中涉及的更多的Angular概念的细节,以及Angular概念的示例,参见开发指导

      当你准备好开始用Angular开发一个项目的时候,我们推荐你用angular种子项目引导你的开发。

      我们希望这篇教程对你有用,使你对Angular有了足够的了解,激起你更大的学习愿望。我们特别希望你能够开发出自己的Angular Web应用,你可能对为Angular贡献产生兴趣。

      如果你有什么问题、反馈,或者想和我们打招呼,请在https://groups.google.com/forum/#!forum/angular上发消息吧。

      Angular是当下非常流行的前端框架,受到了广大前端开发者的喜爱。下面,51coolma将为大家列出一些经典的Angular面试题以及答案,供大家参考。

      timg (1)

      Q1、ng-if跟ng-show/hide的区别有哪些?

      A:区别主要有两点:

      1、ng-if 在后面表达式为 true 的时候才创建dom 节点,而ng-show 是在初始时就创建了,可以用 display:block 和 display:none 来控制显示和不显示。

      2、ng-if 会(隐式地)产生新作用域,ng-switch 、ng-include 等会动态创建一块界面的也是如此。

      这样会导致,在 ng-if 中用基本变量绑定 ng-model,同事在外层的 div 中将 model 绑定给另一个显示区域后,在内层改变时,外层并不会随着内层改变,因为这已经两个不同的变量了。

      <p>{{name}}</p><div ng-if="true">    <input type="text" ng-model="name"></div>

      而在 ng-show 中,却不存在此问题,因为它不自带一级作用域。

      为了避免这类问题的出现,我们可以始终将页面中的元素绑定到对象的属性(data.x),而不是直接绑定到基本变量(x)上。


      Q2、ng-repeat迭代数组的时候,如果数组中有相同值,会有什么问题,如何解决?

      A:会提示 Duplicates in a repeater are not allowed. 出现这种情况的时候,我们可以通过加 track by $index 来解决。当然,也可以 trace by 任何一个普通的值,只要能唯一性标识数组中的每一项即可(建立 dom 和数据之间的关联)。


      Q3、ng-click 中写的表达式,能使用 JS 原生对象上的方法吗?

      A:不止是 ng-click 中,只要是在页面中,都是无法直接调用原生的 JS 方法的,因为这些并不存在于与页面对应的 Controller 的 $scope 中。

      举个例子:

      <p>{{parseInt(55.66)}}<p>

      我们会发现,什么也没有显示。

      但如果在 $scope 中添加了下面这个函数:

      $scope.parseInt = function(x){    return parseInt(x);}

      运行就自然是没什么问题了。

      对于这种需求,使用一个 filter 或许是不错的选择:

      <p>{{13.14 | parseIntFilter}}</p>app.filter('parseIntFilter', function(){    return function(item){        return parseInt(item);    }})


      Q4、{{now | 'yyyy-MM-dd'}} 这种表达式里面,竖线和后面的参数通过什么方式可以自定义?

      A:filter,格式化数据,接收一个输入,按某规则处理,返回处理结果。


      ng 内置的filter主要有九种:

      1:date(日期)

      2:currency(货币)

      3:limitTo(限制数组或字符串长度)

      4:orderBy(排序)

      5:lowercase(小写)

      6:uppercase(大写)

      7:number(格式化数字,加上千位分隔符,并接收参数限定小数点位数)

      8:filter(处理一个数组,过滤出含有某个子串的元素)

      9:json(格式化 json 对象)


      filter 有两种使用方法:

      一种是直接在页面里:

      <p>{{now | date : 'yyyy-MM-dd'}}</p>

      另一种是在 js 里面用:

      // $filter('过滤器名称')(需要过滤的对象, 参数1, 参数2,...)$filter('date')(now, 'yyyy-MM-dd hh:mm:ss');

      自定义 filter

      // 形式app.filter('过滤器名称',function(){    return function(需要过滤的对象,过滤器参数1,过滤器参数2,...){        //...做一些事情          return 处理后的对象;    }});  // 栗子app.filter('timesFilter', function(){    return function(item, times){        var result = '';        for(var i = 0; i < times; i++){            result += item;        }        return result;    }})


      Q5、factory、service 和 provider 是什么关系?

      factory

      把 service 的方法和数据放在一个对象里,并返回这个对象。

      app.factory('FooService', function(){  return {    target: 'factory',    sayHello: function(){      return 'hello ' + this.target;    }  }});

      service

      通过构造函数的方式创建 service,然后返回一个实例化对象。

      app.service('FooService', function(){  var self = this;  this.target = 'service';  this.sayHello = function(){    return 'hello ' + self.target;  }});

      provider

      创建一个可通过 config 配置的 service,$get 中返回的,就是用 factory 创建 service 的内容。

      app.provider('FooService', function(){  this.configData = 'init data';  this.setConfigData = function(data){    if(data){      this.configData = data;    }  }  this.$get = function(){    var self = this;    return {      target: 'provider',      sayHello: function(){        return self.configData + ' hello ' + this.target;      }    }  }}); // 此处注入的是 FooService 的 providerapp.config(function(FooServiceProvider){  FooServiceProvider.setConfigData('config data');});

      从底层实现上来看,三者的关系是:service 调用了 factory,返回其实例;factory 调用了 provider,返回其 $get 中定义的内容。factory 和 service 的功能类似,但是 factory 是普通 function,可以返回任何东西;service 是构造器,可以不返回(绑定到 this 的都可以被访问);provider 是加强版 factory,返回一个可配置的 factory。


      Q6、angular 的数据绑定采用什么机制?详述原理

      A:采用了脏检查机制。

      双向数据绑定是 AngularJS 的核心机制之一。当 view 中有任何一个数据变化时,都会更新到 model 中。如果 model 中的数据有变化时,view 也会同步更新,显然,这需要一个监控。

      原理

      Angular 在 scope 模型上设置了一个监听队列,这个监听队列可以用来监听数据变化并更新 view 。每次绑定一个东西到 view 上时, AngularJS 就会往 $watch 队列里插入一条 $watch ,用来检测它监视的 model 里是否有变化的东西。当浏览器接收到可以被 angular context 处理的事件时, $digest 循环就会触发,遍历所有的 $watch ,最后更新 dom。


      Q7、两个平级界面块 a 和 b,如果 a 中触发一个事件,有哪些方式能让 b 知道?详述原理

      A:这个问题换一种说法就是,如何在平级界面模块间进行通信。有两种方法,一种是共用服务,一种是基于事件。

      共用服务

      在 Angular 中,通过 factory 可以生成一个单例对象,在需要通信的模块 a 和 b 中注入这个对象即可。

      基于事件

      这个又分两种方式

      第一种是借助父 controller。在子 controller 中向父 controller 触发( $emit )一个事件,然后在父 controller 中监听( $on )事件,再广播( $broadcast )给子 controller ,这样通过事件携带的参数,实现了数据经过父 controller,在同级 controller 之间传播。

      第二种是借助 $rootScope 。每个 Angular 应用默认有一个根作用域 $rootScope , 根作用域位于最顶层,从它往下挂着各级作用域。所以,如果子控制器直接使用 $rootScope 广播和接收事件,那么就可实现同级之间的通信。


      Q8、一个 angular 应用应当如何良好地分层?

      目录结构的划分

      对于小型项目,可以按照文件类型组织,比如:

      cssjs controllers models services filterstemplates 

      但是对于规模较大的项目,最好按业务模块划分,比如:

      cssmodules account  controllers  models  services  filters  templates disk  controllers  models  services  filters  templates

      modules 下最好再有一个 common 目录来存放公共的东西。

      逻辑代码的拆分

      作为一个 MVVM 框架,Angular 应用本身就应该按照 模型,视图模型(控制器),视图来划分。

      这里逻辑代码的拆分,主要是指尽量让 controller 这一层很薄。提取共用的逻辑到 service 中 (比如后台数据的请求,数据的共享和缓存,基于事件的模块间通信等),提取共用的界面操作到 directive 中(比如将日期选择、分页等封装成组件等),提取共用的格式化操作到 filter 中等等。

      在复杂的应用中,也可以为实体建立对应的构造函数,比如硬盘(Disk)模块,可能有列表、新建、详情这样几个视图,并分别对应的有 controller,那么可以建一个 Disk 构造函数,里面完成数据的增删改查和验证操作,有跟 Disk 相关的 controller,就注入 Disk 构造器并生成一个实例,这个实例就具备了增删改查和验证方法。这样既层次分明,又实现了复用(让 controller 层更薄了)。


      Q9、angular 应用常用哪些路由库,各自的区别是什么?

      A:Angular1.x 中常用 ngRoute 和 ui.router。

      区别

      ngRoute 模块是 Angular 自带的路由模块,而 ui.router 模块是基于 ngRoute模块开发的第三方模块。

      ui.router 是基于 state (状态)的, ngRoute 是基于 url 的,ui.router模块具有更强大的功能,主要体现在视图的嵌套方面。

      使用 ui.router 能够定义有明确父子关系的路由,并通过 ui-view 指令将子路由模版插入到父路由模板的 <div ui-view></div> 中去,从而实现视图嵌套。而在 ngRoute 中不能这样定义,如果同时在父子视图中 使用了 <div ng-view></div> 会陷入死循环。

      示例

      ngRoute

      var app = angular.module('ngRouteApp', ['ngRoute']);app.config(function($routeProvider){  $routeProvider    .when('/main', {      templateUrl: "main.html",      controller: 'MainCtrl'    })    .otherwise({ redirectTo: '/tabs' });

      ui.router

      var app = angular.module("uiRouteApp", ["ui.router"]);app.config(function($urlRouterProvider, $stateProvider){  $urlRouterProvider.otherwise("/index");  $stateProvider    .state("Main", {      url: "/main",      templateUrl: "main.html",      controller: 'MainCtrl'    })


      Q10、分属不同团队进行开发的 angular 应用,如果要做整合,可能会遇到哪些问题,如何解决?

      A:可能会遇到不同模块之间的冲突。

      比如一个团队所有的开发在 moduleA 下进行,另一团队开发的代码在 moduleB 下

      angular.module('myApp.moduleA', [])  .factory('serviceA', function(){    ...  })   angular.module('myApp.moduleB', [])  .factory('serviceA', function(){    ...  })     angular.module('myApp', ['myApp.moduleA', 'myApp.moduleB'])  

      会导致两个 module 下面的 serviceA 发生了覆盖。

      貌似在 Angular1.x 中并没有很好的解决办法,所以最好在前期进行统一规划,做好约定,严格按照约定开发,每个开发人员只写特定区块代码。


      Q11、angular 的缺点有哪些?

      强约束

      导致学习成本较高,对前端不友好。

      但遵守 AngularJS 的约定时,生产力会很高,对 Java 程序员友好。

      不利于SEO

      因为所有内容都是动态获取并渲染生成的,搜索引擎没法爬取。

      一种解决办法是,对于正常用户的访问,服务器响应 AngularJS 应用的内容;对于搜索引擎的访问,则响应专门针对 SEO 的HTML页面。

      性能问题

      作为 MVVM 框架,因为实现了数据的双向绑定,对于大数组、复杂对象会存在性能问题。

      可以用来 优化 Angular 应用的性能 的办法:

      减少监控项(比如对不会变化的数据采用单向绑定)

      主动设置索引(指定 track by ,简单类型默认用自身当索引,对象默认使用 $$hashKey ,比如改为 track by item.id )

      降低渲染数据量(比如分页,或者每次取一小部分数据,根据需要再取)

      数据扁平化(比如对于树状结构,使用扁平化结构,构建一个 map 和树状数据,对树操作时,由于跟扁平数据同一引用,树状数据变更会同步到原始的扁平数据)

      另外,对于Angular1.x ,存在 脏检查 和 模块机制 的问题。


      Q12、如何看待 angular 1.2 中引入的 controller as 语法?

      A:在 angular 1.2 以前,在 view 上的任何绑定都是直接绑定在 $scope 上的

      function myCtrl($scope){  $scope.a = 'aaa';  $scope.foo = function(){    ...  }}

      使用 controllerAs,不需要再注入 $scope ,controller 变成了一个很简单的 javascript 对象(POJO),一个更纯粹的 ViewModel。

      function myCtrl(){  // 使用 vm 捕获 this 可避免内部的函数在使用 this 时导致上下文改变  var vm = this;  vm.a = 'aaa';}


      Angularjs和jQuery是当前两个比较好用的前端开发工具,下面就简单来说说这两者之间的区别吧。


      概念不同:

      从概念上看,Angularjs属于前端框架,我们写的代码都被其调用;而jQuery则是一个封装了很多功能的库,我们需要调用jQuery中的API去实现一些功能。


      开发模式存在差异

      AngularJS与jQuery在web应用开发模式上有着完全不同的思路:

      jQuery:通过显示操作浏览器中的DOM来创建应用程序(适合回合式应用),对于比较大型且复杂项目,jQuery不利于全面的单元测试;

      AngularJS:将浏览器吸收为应用程序的基础(适合单页应用),比较适合大型及复杂项目,对于全面的单元测试也相对比较容易。


      什么是会和应用和单页应用?


      回合式应用

      在Web应用发展刚起步时,很多开发者都用的是回合式应用。

      相较于单页应用,它就相当于多页应用吧。

      优点:对浏览器的要求少,对客户端的支持比较大;

      缺点:加载等待时间较长,在每次请求加载时,它需要后端服务器来处理所有请求并管理所有的应用程序状态,这就占用了许多带宽。(这时因为每个HTML文档必须是自包含的,也就是每次更新页面,都要把HTML文档中更新的和未更新的内容完整的发送给服务器端,这就导致从服务器产生的每个响应中包含许多相同的内容)。


      单页应用

      该模式下只有一个初始的HTML文档被发送给浏览器,用户交互所产生的Ajax(异步)请求只会请求较小的HTML片段,或者要插入到已有的显示给用户元素中的数据。

      而初始的HTML文档不会被再次加载或者替换,这就造成了Ajax请求被异步执行的同时,用户还可以继续与已有的HTML进行交互,而无需等待。

      优点: 减少向后端请求的数据量,减少了带宽压力,提升了整体web的性能。

      缺点:对浏览器的性能的要求变高,某些浏览器可能会存在不兼容。


      AngularJS与jQuery的适用领域

      1

      上图很明确地阐述了AngularJS与jQuery的区别:

      在项目复杂度比较低时,比较适合使用jQuery框架,而随着项目复杂度的提升,jQuery框架会被逐渐被以单页应用和复杂的回合式应用见长的AngularJS框架所取代。


      推荐阅读;

      jQuery微课——理论实践一把抓

      AngularJS教程

      jQuery教程


      angularjs和vuejs都是MVVM框架,那么这两者有什么区别呢?


      QQ截图20180424103103


      angularjs和vue的区别:

      1、vueJS简单易学,而angularJS的上手较高;

      2、vue专注于View层,主打的是快速便捷,而angularJS功能则比较全面,当然体量也较大,相对没有vue那么便捷;

      3、angularJS的指令都是ng-xxx,而vueJS的指令都是v-xxx;

      4、angularJS的所有指令和方法都是绑定在$scope上的,而vueJS是new出来一个实例,所有的方法和指令都在这个实例上,一个页面上可以有多个vue实例,但是angularJS的对象只能有一个;

      5、angularJS是由google开发和维护的,vueJS是由个人维护的;

      6、vueJS一般用于移动端的开发,而angularJS一般应用于大型的项目


      推荐阅读:

      angularJS教程

      vueJS教程