Angular.js简介
AngularJS是为了克服HTML在构建应用上的不足而设计的。HTML是一门很好的为静态文本展示设计的声明式语言,但要构建WEB应用的话它就显得乏力了。所以我做了一些工作(你也可以觉得是小花招)来让浏览器做我想要的事。
通常,我们是通过以下技术来解决静态网页技术在构建动态应用上的不足:
类库 - 类库是一些函数的集合,它能帮助你写WEB应用。起主导作用的是你的代码,由你来决定何时使用类库。类库有:jQuery等
框架 - 框架是一种特殊的、已经实现了的WEB应用,你只需要对它填充具体的业务逻辑。这里框架是起主导作用的,由它来根据具体的应用逻辑来调用你的代码。框架有:knockout、sproutcore等。
AngularJS使用了不同的方法,它尝试去补足HTML本身在构建应用方面的缺陷。AngularJS通过使用我们称为标识符(directives)的结构,让浏览器能够识别新的语法。例如:
使用双大括号{{}}语法进行数据绑定;
使用DOM控制结构来实现迭代或者隐藏DOM片段;
支持表单和表单的验证;
能将逻辑代码关联到相关的DOM元素上;
能将HTML分组成可重用的组件。
实例##
Angularjs下载###
目前1.x的最新版是1.6.2. 暂且用这个来做练习!
Hello World###
学习任何新的语言或技术框架都喜欢写一个经典的"Hello world"程序.
将下面的代码复制到你的编辑器中, 然后在浏览器中打开这个HTML文件.
<!DOCTYPE html>
<html ng-app>
<head lang="en">
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>AngularJS Hello world Demo</title>
<script type="text/javascript" src="../../../static/libs/angularjs/js/angular.js"></script>
</head>
<body>
<div>
<input type="password" ng-model="hello">
<h3>Hello {{ hello || 'world'}}</h3>
</div>
</body>
</html>
在该示例中, 有以下几点需要注意:
- 文本输入指令
<input type="password" ng-model="hello"/>
绑定到一个叫hello
的模型变量 - 双大括号标记将
hello
模型变量添加到问候语文本. - 你不需要为该应用另外注册一个事件侦听或添加事件处理程序!
现在试着在输入框中输入文字, 你输入的内容会立即更新显示在问候语中. 这就是Angularjs双向数据绑定的概念. 输入框的任何更改会立即反映到模型变量(一个方向), 模型变量的任何更改都会立即反映到问候语文本中(另一方向).
AngularJS应用解析##
本节描述AngularJS应用程序的三个组成部分, 并解释它们如何映射到模型-视图-控制器设计模式.
模板(Templates)###
模板是用HTML和CSS编写的文件, 展现应用的视图. 你可以给HTML添加新的元素,属性标记, 作为AngularJS编译器的指令. AngularJS编译器是完全可扩展的, 这意味着通过AngularJS, 你可以在HTML中构建你自己的HTML标记.
应用程序逻辑(Logic)和行为(Behavior)###
应用程序逻辑和行为是你用JavaScript定义的控制器. AngularJS与标准AJAX应用程序不同, 你不需要另外编写侦听器或DOM控制器, 因为它们已经内建到AngularJS中, 这些功能使你的应用程序逻辑很容易编写, 测试和维护.
模型数据(Data)###
模型是从AngularJS作用域对象的属性引申的. 模型中的数据可能是JavaScript对象, 数组或基本类型, 这都不重要, 重要的是, 他们都属性AngularJS作用域对象.
AngularJS通过作用域来保持数据模型与视图界面UI的双向同步. 一旦模型状态发生改变, AngularJS会立即刷新反映在视图界面中, 反之亦然.
此外, AngularJS还提供了一些非常有用的服务特性###
- 底层服务包括依赖注入, XHR, 缓存, URL路由和浏览器抽象服务.
- 你还可以扩展和添加自己特定的应用服务.
- 这些服务可以文具你非常方便地编写WEB应用.
引导程序##
还是继续看上面的代码示例, 来进行分析
代码在做什么呢?
ng-app
指令:####
<html lang="en" ng-app>
ng-app
指令标记了AngularJS脚本的作用域, 在<html>
中添加ng-app
属性即说明整个<html>
都是AngularJS脚本作用域. 开发者也可以在局部使用ng-app
指令, 如<div ng-app>
, 则AngularJS脚本仅在该<div>
中运行.
AngularJS脚本标签:###
<script type="text/javascript" src="../../../static/libs/angularjs/js/angular.js">
这行代码载入AngularJS脚本, 当浏览器将整个HTML页面载入完毕后, 将执行该AngularJS脚本, AngularJS脚本运行后将会寻找含有ng-app
指令的HTML标签, 该标签即定义了AngularJS应用的作用域.
双大括号绑定的表达式###
<h3>Hello {{ hello || 'world'}}</h3>
这么代码演示了AngularJS模板的核心功能---绑定, 这个绑定由双大括号{{}}
和表达式hello || 'world'
组成.
这个绑定告诉AngularJS需要运算其中的表达式并将结果插入DOM中, 接下来的步骤我们将看到, DOM可以随着表达式运算的结果的改变而实时更新.
AngularJS表达式是一种类似于JavaScript的代码片段, AngularJS表达式仅在AngularJS的作用域中运行, 而不是在整个DOM中运行.
引导AngularJS应用###
通过ng-app
指令来自动引导AngularJS应用是一种简洁的方式, 适合大多数情况. 在高级开发中, 例如使用脚本装载应用, 你也可以使用bootstrap
手动引导AngularJS应用.
AngularJS应用引导过程有3个重要点:####
- 注入器, 将用于创建此应用程序的依赖注入(dependency injection).
- 注入器将会创建根作用域作为我们应用模型的范围.
- AngularJS将会链接根作用域的DOM, 从用
ng-app
标记的HTML标签开始, 逐步处理DOM中指令和绑定.
一旦AngularJS应用引导完毕, 它将继续侦听浏览器的HTML触发事件, 如果鼠标点击事件, 按键事件, HTTP传入响应等改变DOM模型的事件, 这类事件一旦发生, AngularJS将会自动检测变化, 并作出相应的处理及更新.
上面这个应用的结构非常简单, 该模板包仅含一个指令和一个静态绑定, 其中的模型也是空的. 下一步我们将尝试稍复杂的应用.
静态模板##
为了说明AngularJS如何增强了标准HTML, 我们将先创建一个静态HTML页面模板, 然后把这个静态HTML页面模板转换成能动态显示的AngularJS模板, 在本步骤中, 我们往HTML页面中添加两个手机的基本信息.
将以下代码添加到上面示例的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>
练习###
尝试添加多个静态HTML代码到HTML示例文件中.例如:
<p>Total number of phones: 2</p>
AngularJS模板##
是时候给这些网页来点动态特性了--- 用AngularJS! 我们这里为后面要加入的控制器添加了一个测试.
一个应用的代码架构有很多种. 对于AngularJS应用, 我们鼓励使用模型-视图-控制器(MVC)模式
解耦代码和分离关注点. 考虑到这一点, 我们用AngularJS来为我们的应用添加一些模型, 视图和控制器.
视图和模板###
在AngularJS中, 一个视图是通过HTML"模板"渲染之后的映射. 这意味着, 不论模型什么时候发生变化, AngularJS会实时更新结合点, 随之更新视图.
比如, 视图组件被AngularJS用下面这个模板构建出来.完整的示例代码如下
--------------------------index.html-------------------------
<!DOCTYPE html>
<html>
<head lang="en">
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>AngularJS Hello world Demo</title>
</head>
<body ng-app="myApp" ng-controller="PhoneListCtrl">
<div>
<input type="password" ng-model="hello">
<h3>Hello {{ hello || 'world'}}</h3>
<div>
<ul>
<li ng-repeat="phone in phones">
{{ phone.name }}
<p>
{{ phone.snippet}}
</p>
</li>
</ul>
</div>
</div>
<script type="text/javascript" src="../../../static/libs/angularjs/js/angular.js"></script>
<script type="text/javascript" src="js/controllers.js"></script>
</body>
</html>
我们刚刚把静态编码的手机列表替换掉了, 因为这里我们使用ng-repeat
指令和两个花括号包AngularJS表达式----{{ phone.name }}和``{{ phone.snippet }}
---能达到同样的效果.
- 在
<li>
标签里面的ng-repeat="phone in phones"
语句是一个AngularJS迭代器, 这个迭代器告诉AngularJS用第一个<li>
标签作为模板为列表中的每一部手机创建一个<li>
元素. - 正如我们在开始时学到的, 包裹在
phone.name
和phone.snippet
周围的花括号标识着数据绑定, 和常量计算不同的是, 这里的表达式实际上是我们应用的一个数据模型引用, 这些我们在PhoneListCtrl控制器里面都设置好了.
模型和控制器###
在PhoneListCtrl
控制器里面初始化了数据模型(这里只不过是一个包含了数组的函数, 数组中存储的对象是手机数据列表):
js/controllers.js
文件中的内容如下:
var myapp = angular.module('myApp', []); // 获取到应用
myapp.controller('PhoneListCtrl', function($scope){ // 给应用添加控制器PhoneListCtrl
$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
---控制器方法的名字(在JS文件controllers.js
中)和<div>
标签里面的ng-controller
指令的值相匹配. - 手机的数据此时与注入到我们控制器函数的作用域(
$scope
)相关联. 当应用启动之后, 会有一个根作用域被创建出来, 而控制器的作用域是根作用域的一个典型后继. 这个控制器的作用域对所有<body ng-app="myApp" ng-controller="PhoneListCtrl">
标记内部的数据绑定有效.
AngularJS的作用域理论非常重要: 一个作用域可以视作模板, 模型和控制器协同工作的粘接器. AngularJS使用作用域, 同时还有模板中的信息, 数据模型和控制器. 这些可以帮助模型和视图分离, 但是它们两者确实是同步的! 任何对于模型的更改都会即时反映在视图上, 任何视图上的更改都会立即体现在模型中.
如果想要更加深入的理解AngularJS的作用域, 请参看AngularJS作用域文档
测试###
关于JS代码单元测试这一块, 还未去过多的了解, 有机会还会再更新这一部分的内容.
迭代器过滤##
我们在上一步做了很多基础性的训练, 所以现在我们可以来做一些简单的事情. 我们要加入全文检索功能(没错, 这个真的非常简单).
我们的应用应该有一个搜索框. 注意: 在页面上手机列表随着用户在搜索框中的输入而变化.
控制器
我们对控制器不做任何修改
模板###
index.html
<!DOCTYPE html>
<html>
<head lang="en">
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>AngularJS Hello world Demo</title>
</head>
<body ng-app="myApp" ng-controller="PhoneListCtrl">
<div>
<input type="password" ng-model="hello">
<h3>Hello {{ hello || 'world'}}</h3>
Search: <input type="text" ng-model="query"/>
<div>
<ul>
<li ng-repeat="phone in phones | filter:query">
{{ phone.name }}
<p>
{{ phone.snippet}}
</p>
</li>
</ul>
</div>
</div>
<script type="text/javascript" src="../../../static/libs/angularjs/js/angular.js"></script>
<script type="text/javascript" src="js/controllers.js"></script>
</body>
</html>
我们现在添加了一个<input>
标签, 并且使用AngularJS的$filter函数来处理ng-repeat指令的输入.
这样允许用户输入一个搜索条件, 立刻就能看到对电话列表的搜索结果. 我们来解释一下新的代码:
- 数据绑定: 这是AngularJS的一个核心特性. 当页面加载的时候, AngularJS会根据输入框的属性值名字, 将其也数据模型中相同名字的变量绑定在一起, 以确保两者的同步性.
在这段代码中, 用户在输入框中输入的数据名字称作query
, 会立刻作为列表迭代器{ phone in phones | filter:query }
其过滤器的输入. 当数据模型引起迭代器输入变化的时候, 迭代器可以高效的更新DOM将数据模型最新的状态反映出来.
- 使用
filter
过滤器: filter函数使用query的值来创建一个只包含匹配query记录的新数组.
ng-repeat
会根据filter
过滤器生成的手机记录数据数组来自动更新视图. 整个过程对于开发者来说都是透明的.
双向绑定##
在这一步你会增加一个用户控制手机列表显示顺序的特性. 动态排序可以这样实现, 添加一个新的模型属性, 把它和迭代器集成起来, 然后让数据绑定完成剩下的事情.
这里, 我们的应用除了之前添加的搜索框之外, 还需要添加一个下拉菜单, 它可以允许控制电话排列的顺序.
模板###
index.html
<!DOCTYPE html>
<html>
<head lang="en">
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>AngularJS Hello world Demo</title>
</head>
<body ng-app="myApp" ng-controller="PhoneListCtrl">
<div>
<input type="password" ng-model="hello">
<h3>Hello {{ hello || 'world'}}</h3>
Search: <input type="text" ng-model="query"/>
Sort by:
<select ng-model="orderProp">
<option value="name">Alphabetical</option>
<option value="age">Newest</option>
</select>
<div>
<ul>
<li ng-repeat="phone in phones | filter:query | orderBy:orderProp">
{{ phone.name }}
<p>
{{ phone.snippet}}
</p>
</li>
</ul>
</div>
</div>
<script type="text/javascript" src="../../../static/libs/angularjs/js/angular.js"></script>
<script type="text/javascript" src="js/controllers.js"></script>
</body>
</html>
我们在index.html中做了如下更改:
- 首先, 增加了一个叫
orderProp
的<select>
标签, 这样我们的用户就可以选择我们提供的两种排序方法. - 然后, 在
filter
过滤器后面添加一个orderBy
过滤器用来处理进入迭代器的数据.orderBy
过滤器以一个数组作为输入, 复制一份副本, 然后把副本重排序再输出到迭代器.
AngularJS在select
元素和orderProp
模型之间创建了一个双向绑定. 而后, orderProp
会被用作orderBy
过滤器输入.
正如我们在前面讨论数据绑定和迭代器的时候所说的一样, 无论什么时候数据模型发生了改变(比如用户在下拉菜单中选了不同的顺序), AngularJS的数据绑定会让视图自动更新. 没有任何笨拙的DOM操作!
控制器###
js/controllers.js
var myapp = angular.module('myApp', []);
myapp.controller('PhoneListCtrl', function ($scope) {
$scope.phones = [
{
"name": "Nexus S",
"snippet": "Fast just got faster with Nexus S.",
"age": 0
},
{
"name": "Motorola XOOM™ with Wi-Fi",
"snippet": "The Next, Next Generation tablet.",
"age": 1
},
{
"name": "MOTOROLA XOOM™",
"snippet": "The Next, Next Generation tablet.",
"age": 2
}
];
$scope.hello = "Hello , James!";
$scope.orderProp = "age";
});
- 我们修改了
phones
模型---手机的数组---为每一个手机记录其增加了一个age
属性. 我们会根据age
属性来对手机进行排序. - 我们在控制器代码里加了一行让
orderProp
的默认值为age
. 如果我们不设置默认值, 这个模型会在我们的用户的下拉菜单中选择一个顺序之前一直处于未初始化状态.
现在我们该好好谈谈双向数据绑定了. 注意到当应用在浏览器中加载时, "Newest"在下拉菜单中被选中. 这是因为我们在控制器中把orderProp
设置成了age
. 所以绑定在我们模型到用户界面的方向上起作用---即数据从模型到视图的绑定. 现在当你在下拉菜单中选择"Aphabetically", 数据模型会被同时更新, 并且手机列表数组会被重新排序. 这个时候数据绑定从另一个方向产生了作用---即数据从视图到模型的绑定.