调用Angular
- 使用Angular首先要做两件事:
- 加载angular.js库
- 使用
ng-app
指令告诉Angular应该管理DOM中哪一部分
- 鉴于天朝在网络世界中修筑了万里长城,推荐使用国内的CDN缓存代码库(
cdn.css.net
网站中提供各种CDN的搜索)
<script
src="https://cdn.css.net/files/angular.all/1.2.18/angular-all.min.js">
</script>
- 用
ng-app
指令告诉Angular应该盖页面的哪一块,即Angular的模块。
<!-- 如果是构建纯Angular应用,可以写在`<html>`元素中 -->
<html ng-app>
···
</html>
或者
<html>
<!-- 如果应用已经使用了类似JSP、Rails的其他技术,也可以写在要管理的DOM元素中 -->
<div ng-app>
···
</div>
</html>
Model View Controller
- 第一章提到Angular框架是通过MVC风格来设计web应用的,使之具有很强的灵活性。MVC包括:
- 模型(Model)用来容纳数据,代表应用当前的状态;
- 视图(View)用来展示数据;
- 控制器(Controller)来管理模型和视图之间的关系;
- 我们可以通过
$scope
对象的属性来创建数据模型,可以只用基本数据类型来存储数据,也可以用一个JavaScript类来存储数据。 - 定义Angular的正确方式应该是,先定义Angular模块,然后把控制器定义为模块的一个方法。 通过模块机制可以帮助我们把代码从全局命名空间中隔离开来。
<!doctype html>
<!-- 通过ng-app设置Angular管理的范围 -->
<html ng-app='myApp'>
<head>
<!-- 引入Angular的文件 -->
<script src="https://cdn.css.net/files/angular.all/1.2.18/angular-all.min.js"></script>
</head>
<!-- 通过ng-controller声明控制器-->
<body ng-controller='TextController'>
<!-- 通过双花括号定义双向绑定的数据 -->
<p>{{hello}} {{TextObj.message}}!</p>
<script type="text/javascript">
//通过angular.module()方法声明模块,第一个参数是模块名,第二个参数是需要依赖的模块(这里是空数组)
var myAppModule = angular.module('myApp',[]);
//通过module.controller()方法声明控制器,参数是控制器方法的具体实现
myAppModule.controller('TextController',function($scope){
//变量
var hello = 'hello';
//对象
var TextObj = {
message : "World"
};
//赋值给$scope
$scope.TextObj = TextObj;
$scope.hello = hello;
});
</script>
</body>
</html>
模板和数据绑定
<div ng-repeat='item in items'>
<span>{{item.title}}</span>
</div>
- 如上代码所示,Angular的模板是HTML片段,而
items
数组是数据模型,通过ng-repeat
指令Angular框架会遍历items数组的每一项,并给<div>
元素生成一份拷贝,延展出HTML内容。 - 在实际应用中,
items
数组往往是通过请求服务端的数据结果。 - 一个Angular应用的基本运作流程如下:
① 用户请求Angualr应用起始页;
② 浏览器向服务器发起HTTP连接,加载起始页.html
页面(页面中包含模板);
③ 开始解析页面,加载Angualr框架到页面,DOM构建完毕后开始查找ng-app
指令,根据这个指令来定义Angular的模板边界;
④ Angular框架的初始化工作:注册监听器、执行一些DOM操作、从服务器获取初始化数据。初始化工作完毕后,模板被转换成DOM视图;
⑤ 连接到服务器,加载需要展示给用户看的其他数据;
显示文本
- 将JS变量的文本值显示到UI中,除了使用双花括号之外,还可以使用'ng-bind'指令。两种形式的输出内容是相同的。
- 使用使用双花括号的方式,可以让模板代码阅读起来更自然,但有一个问题,就是在Angular使用数据替换这些花括号之前,未被渲染好的模板可能会被用户看到。造成这种现象的原因是,浏览器需要先加载并渲染HTML页面,Angular才开始初始化工作。
表单输入
- 对所有标准的表单元素,Angualr框架都支持,并且操作起来非常方便,只需要将
ng-model
属性声明到要操作的元素即可。 - 案例:Angular操作复选框
<from ng-controller='SomeController'>
<!-- 通过ng-model将控件值绑定到youCheckedIt变量中 -->
<input type="checkbox" ng-model='youCheckedIt' />
</from>
- 我们还可以通过
ng-change
指令监听控件的改变 - 案例:自动获取十倍的数字计算控件
<from ng-controller='StartUpController'>
Starting: <input type="text" ng-model='funding.startingEstimate' />
Remomendation:{{funding.needed}}
</from>
<script type="text/javascript">
var myAppModule = angular.module('myApp',[]);
myAppModule.controller('StartUpController',function($scope){
$scope.funding = {startingEstimate : 0};
$scope.computeNeeded = function(){
$scope.funding.needed = $scope.funding.startingEstimate * 10;
};
});
</script>
-
ng-change
指令只能监听页面控件的变化,如果是接收到服务器返回的信息,需要刷新数据模型,该怎么办呢? -
$watch()
函数用来监听一个表达式,当表达式发送改变时会调用一个回调函数
myAppModule.controller('StartUpController',function($scope){
$scope.funding = {startingEstimate : 0};
$scope.computeNeeded = function(){
$scope.funding.needed = $scope.funding.startingEstimate * 10;
};
//添加$watch
$scope.watch('funding.startingEstimate',computeNeeded);
});
- Angular支持通过
ng-submit
指令监听表单提交动作。
<from ng-controller='SomeController' ng-submit='requestFnding()'>
···
</from>
- Angular提供一系列类似浏览器原生事件属性的指令,比如
ng-click
和ng-dbclick
等
浅谈非入侵式JavaScript
- 非入侵式JavaScript代码主要可以从以下两点来解读:
- JavaScript的兼容性问题,需要根据在不同浏览器编写不同的代码;
- 不同类库集成进来引发的全局命名冲突问题;
- 不同的事件监听器绑定数据结构和行为,让代码变得难以理解和维护,扩展性差;
- 一开始我们会把事件处理器定义在元素上,然后为这些元素赋ID值,获取元素的引用。或者,也可以通过一个统一的处理器来管理这些事件,但大多数应用,最后还是会把代码弄得乱七八糟。
- 而Angular是通过
ng-eventhandler="expression"
来处理,这里的eventhandler可以被替换成click
、mousedown
等原生的JS事件。 - 虽然也还是将事件绑定通过元素属性进行声明,但是具有以下特点:
- Angular帮助开发者屏蔽浏览器的差异性;
- 通过Angular控制器的作用域范围避免扰乱全局命名空间的秩序;
- 没有在任何地方引用DOM或者DOM事件,所有定位元素和处理事件监听都在Angular内部完成;
- 传统的JavaScript程序的页面逻辑和DOM是强耦合的,给单元测试增加了复杂性,而Angular则不会有这些问题;
列表、表格以及其他迭代类型
-
ng-repeat
指令可以遍历数据模型,并动态构建DOM节点,可算是Angular最常用的指令之一了。 - 案例:学生花名册系统
<!DOCTYPE html>
<!-- 1.通过ng-app设置Angular管理的范围 -->
<html ng-app='myApp'>
<head>
<meta charset="UTF-8">
<title></title>
</head>
<!-- 2.引入Angular的文件 -->
<script src="http://cdn.static.runoob.com/libs/angular.js/1.4.6/angular.min.js"></script>
<body>
<ul ng-controller='StudentListController'>
<li ng-repeat='student in students'>
<a href="javascript:void(0);">{{$index+1}}.{{student.name}}</a>
</li>
</ul>
<button ng-click='addStudent()'>Add</button>
<script type="text/javascript">
//伪数据
var students = [
{name : 'Mary Contrary',id:1},
{name : 'Jack Spart',id:2},
{name : 'Jill Hill',id:3},
{name : 'William Liang',id:4},
{name : 'Yoki Chen',id:5}
];
var myApp = angular.module('myApp',[]);
myApp.controller('StudentListController',function($scope){
$scope.students = students;
$scope.addStudent = function(){
$scope.students.splice(1,0,{name:'test',id:6});
}
});
</script>
</body>
</html>
- 在
ng-repeat
指令中还可以通过$index
返回当前引用元素的序号,通过$first
、$middle
以及$last
返回布尔值,判断当前元素是否为首个元素、是否为中间元素以及末尾元素。
显示和隐藏
- Angualr通过
ng-show
和ng-hide
指令显示和隐藏元素,当ng-show
在表达式为true
时显示元素,false时
隐藏元素,ng-hide
则恰好相反。
<!DOCTYPE html>
<!-- 1.通过ng-app设置Angular管理的范围 -->
<html ng-app='myApp'>
<head>
<meta charset="UTF-8">
<title></title>
</head>
<!-- 2.引入Angular的文件 -->
<script src="http://cdn.static.runoob.com/libs/angular.js/1.4.6/angular.min.js"></script>
<body>
<div ng-controller='SwichMenuController'>
<button ng-click='toggleMenu()'>Add</button>
<ul ng-show='menuState.show'>
</ul>
</div>
<script type="text/javascript">
var myApp = angular.module('myApp',[]);
myApp.controller('SwichMenuController',function($scope){
$scope.menuState.show = false;
$scope.toggleMenu = function(){
$scope.menuState.show = !$scope.menuState.show;
}
});
</script>
</body>
</html>
CSS类和样式
- Angular可以动态设置CSS类和样式,通过双花括号进行数据绑定。
- 案例:动态修改颜色
<!DOCTYPE html>
<html ng-app='myApp'>
<head>
<meta charset="UTF-8">
<title></title>
<style type="text/css">
.menu-disabled-true{ color:gray }
</style>
</head>
<script src="http://cdn.static.runoob.com/libs/angular.js/1.4.6/angular.min.js"></script>
<body>
<div ng-controller='StudentListController'>
<ul>
<li class="menu-disabled-{{isDisabled}}" ng-click='stun()'></li>
</ul>
</div>
<script type="text/javascript">
var myApp = angular.module('myApp',[]);
myApp.controller('SwichMenuController',function($scope){
$scope.isDisabled = false;
$scope.stun = function(){
$scope.isDisabled = true;
}
});
</script>
</body>
</html>
- Angular框架同样适用于内联样式,比如
style="{{someexpression}}"
,这种方式具有很大的灵活性。 - 除此之外,Angular框架还提供
ng-class
和ng-style
指令,接受一个表达式。 - 案例:通过ng-class显示错误和警告信息
<!DOCTYPE html>
<html ng-app='myApp'>
<head>
<meta charset="UTF-8">
<title></title>
<style type="text/css">
.error{background-color: red;}
.warrning{background-color: yellow;}
</style>
</head>
<script src="http://cdn.static.runoob.com/libs/angular.js/1.4.6/angular.min.js"></script>
<body>
<div ng-controller='HeaderController'>
<div ng-class='{error:isError,warning:isWarning}'>
{{messageText}}
</div>
</div>
<button ng-click='showError()'>Simulate Error</button>
<button ng-click='showWarning()'>Simulate Warning</button>
<script type="text/javascript">
var myApp = angular.module('myApp',[]);
myApp.controller('HeaderController',function($scope){
$scope.isError = false;
$scope.isWarning = false;
$scope.showError = function(){
$scope.messageText = 'This is an Error';
$scope.isError = true;
$scope.isWarning = false;
};
$scope.showWarning = function(){
$scope.messageText = 'This is an Warning';
$scope.isWarning = true;
$scope.isError = false;
};
});
</script>
</body>
</html>
反思src和href属性
- Angular通过
ng-src
和ng-href
指令动态设置超链接和图片属性
<img ng-src='/images/cats/{{favoriteCat}}'>
<a ng-href='/shop/category={{numberOfBalloons}}'>some text</a>
表达式
- Angular在模板中使用表达式是为了在模板、业务逻辑和数据之间建立联系,避免让业务逻辑渗透到模板中。
- angular的表达式可以进行简单的数据运算(+、-、*、/、%),进行比较运算(==、!=、>、<、>=、<=),执行布尔逻辑(&&、||、!),以及位运算(^、&、|)。
- 但是我们应该清晰地区分视图和控制器之间的职责,尽量不要类似运算符的业务逻辑操作放到模板中。
- 表达式是通过Angular内置的解析器执行的,但是不支持循环结构(for、while)、流程控制操作(if-else、throw)以及修改数据的操作符(++、--),请在控制器中执行或者通过指令执行。
区分UI和控制器和职责
- 控制器有三种职责:
- 为数据模型设置初始状态;
- 通过
$scope
对象把数据模型和函数暴露给函数; - 监视模型其余部分的变化,并采取相应的动作;
- 为了让控制器保持小巧和可管理的状态,我们建议为视图中的每一块功能区域创建一个控制器。
- Angular中把控制器绑定到DOM节点上主要有两种方式:
- 在模板中通过
ng-controller
属性声明; - 通过路由把控制器绑定到DOM模板片段;
- 在模板中通过
- 同时,开发者还可以创建嵌套的控制器,使它们可以通过继承树的结构来共享数据模型和函数,让应用的代码保持简介,容易维护。
<!-- ChildController的$scope对象可以访问ParentController的$scope对象上所有的属性 -->
<div ng-controller='ParentController'>
<div ng-controller='ChildController'></div>
</div>
利用$scope暴露模型数据
- Angular利用向控制器传递$scope对象的机制,可以把模型数据暴露给视图。可以把$scope当成一个上下文环境,而在这个环境中,数据模型的变化变得可以观察,甚至还可以通过模板自身创建数据模型。
<!-- ① 可以直接在模板表达式中定义变量 -->
<button ng-click='count=3'>Set Count to Three</button>
<!-- ② 也可以通过构造器函数定义变量 -->
<div ng-controller='CountController'>
<button ng-click='setCount()'>Set Count to Three</button>
</div>
<script type="text/javascript">
function CountController($scope){
$scope.setCount = function(){
$scope.count = 3;
};
}
</script>
使用$watch监控数据模型的变化
-
$scope
的$watch
函数可以实现,当数据模型发生变化时,及时得到通知。 - 函数签名:
$watch(watchFn,watchAction,deepWatch)
-
watchFn
是一个字符串,可以是Angular表达式或者对象名,它代表数据模型的当前值。值得注意的是,这个字符串所代表的对象会被轮训检查多次,所以务必要保证它不会产生其他的副作用。 -
watchAction
是当数据模型发生变化时调用的函数或者表达式,其函数签名是function(newValue,oldValue.scope)
。 -
deepWatch
是个可选的boolean值,带包是否检查数据模型的每个属性。如果想要监控数组中的元素,或者的对象中所有的属性变化,可以使用这个参数。 - 同时,
$watch()
执行后还会返回一个函数,当你不再需要接收变更通知时,可以执行这个返回的函数,以销毁整个监听器。
//监听someProperty属性值的变化
var dereg = $scope.$watch('someModel.someProperty',callbackOnChange());
dereg(); //销毁
watch()中的性能注意事项
- 实际上,Angular在实现
watch()
方法时,会把所有被监控的属性都拷贝一份,然后拿来和当前的值进行比较,检查是否有变化。 - 这样的运行机制会造成性能损耗,推荐通过一个Chrome调试插件(Batatrang)来检测,看看那些数据绑定的操作比较昂贵。
- 有两种方式可以监控多个属性或者对象:
- 通过“+”号连接这些属性;
$scope.$watch('things.a+things.b',callMe(...));
2. 将要监控的属性放到一个数组或者对象中,并将`deepWatch`参数设置成true;
$scope.$watch('things',callMe(...),true);
### 使用Module(模块)组织依赖关系
- 在开发中经常会遇到的难题是 __如何把代码组织在合理的功能范围内?__
- MVC的处理方式,是将控制器看做一个解耦的方法,把正确的数据模型暴露给视图模板。但其他用来支撑我们应用的代码该怎么办?要把它们写在控制器函数里面吗?这么做会使控制器变成一个垃圾场,使得控制器的代码变得难以理解,无法维护。
- `模块机制`解决了上诉问题,它把那些负责提供特殊服务的代码称为`依赖服务`,它专门来组织应用中各个功能区块的依赖关系,甚至可以自动进行依赖注入。
- 案例:当控制器需要从服务器获取商品列表时,通常情况我们会把相关的处理通通写到一个方法中
function ItemsViewController($scope){
var items = [];
//1.向服务器发起请求
//2.解析响应并放到Items对象
//3.将Items数组设置到$scope上,以便视图能够显示
$scope.items = items;
}
- 这种做法虽然可行,但却存在很多问题:
* 功能代码无法重用:同样的代码片段,如果其他控制器也需要从服务端获取商品列表时,却只能重写一遍,这会给代码维护工作造成很大的负担;
* 混淆控制器与与获取数据功能之间的代码边界:获取数据功能如果需要加上一些,例如服务器认证、解析数据等因素时,代码的复杂性将变得不可控制;
* 难以进行单元测试:进行测试的工作变得复杂,甚至可能需要真正启动一个服务器来返回模拟数据进行测试,使得测试工作难以把控;
通过模块化和Angular内置的依赖注入功能,可以这样写:
//创建模块
var myApp = angular.module('shoppingModule',[]);
//设置服务工厂
shoppingModule.factory('Items',function(){
var items = {};
items.query = function(){
//这里返回虚拟数据,真实的场景是从服务器拉取数据
return [
{name : 'Mary Contrary',id:1},
{name : 'Jack Spart',id:2},
{name : 'Jill Hill',id:3},
{name : 'William Liang',id:4},
{name : 'Yoki Chen',id:5}
];
};
return items;
});
//调用
function ShoppingController($scope,Items){
$scope.items = items.query();
}
我们把`Items`对象定义成一个服务,把`$scope`对象和`Items`服务作为参数传递进去。
> 提示:传递Angular在创建控制器时,是通过参数名匹配来注入所需要的服务的,比如`ShoppingController($scope,Items)`中查找`Items`服务。由于是以字符串的形式查找依赖关系的,所以控制器的参数是没有顺序的。
- 服务在Angular当中都是单例的对象,它们用来执行不要的任务,支撑应用的功能。开发者通过创建自定义的服务,共享在控制器之间,实现代码的复用。
- Angular内置了很多服务,例如`location`服务,用来和浏览器的地址栏做交互;还有`router`服务,用来根据URL地址的变化切换视图;以及`http`服务,用来和服务器进行交互等等;
- 根据Angular的API,有三种创建服务的函数:
* __`provider(name,Object OR constructor())`__ :`name`是服务的名字,后面接一个对象或者构造函数。如果是Object的话,对象当中必须返回一个名为`$get()`的函数,否则Angular会认为传递的是一个构造函数,通过调用改构造函数返回服务的实例;
* __`factory(name,$getFunction())`__ :和`provider()`函数相似,服务名后面接一个构造函数,当调用这个函数时,会返回服务的实例;
* __`service(name,constructor())`__ :和`constructor`参数类似,Angular调用它来创建服务实例;
> 需要注意的是:Angular内置服务通常以符号开头,为了避免命名冲突,定义服务名时应当避免以$符号打头。
- 服务自身可以相互依赖,也可以通过`Module`接口定义模块之间的依赖关系。比如:A模块要引用SnazzyUIWidgets和SuperDataSync模块
var appMod = angular.module('app',['SnazzyUIWidgets','SuperDataSync']);
### 使用过滤器格式数据
- 合理化地在应用界面中显示数字、日期以及金额对于用户体验来说是很重要的,但这些字符串的格式化处理对逻辑来说是没有意义的。
- Angular通过过滤器来格式化数据,其语法是:`{{expression | filterName : parameter1 : ...parameterN }}`
- `expression`可以是任意的Angular表达式,`filter`是需要使用的过滤器名称,过滤器通过管道符号`|`来绑定,过滤器的参数之间使用冒号`:`分隔。
- 案例:Angular内置了众多过滤器帮助我们格式化数据,同时也可以创建自定义的过滤器
//大小写转换
{{ "lower cap string" | uppercase }} //结果:LOWER CAP STRING
{{ "TANK is GOOD" | lowercase }} //结果:tank is good
//JSON格式化
{{ {foo: "bar", baz: 23} | json }} //结果:{ "foo": "bar", "baz": 23 }
//时间或日期格式化
{{ 1304375948024 | date:"yyyy-MM-dd hh:mm:ss" }} //结果:2011-05-03 06:39:08
//数字格式化
{{ 1.234567 | number:1 }} //结果:1.2
//金额格式化
{{ 250 | currency:"人民币: ¥ " }} //结果:“人民币: ¥ 250.00 ”
//排序格式化
{
{ [{"age": 20,"id": 10,"name": "iphone"},
{"age": 12,"id": 11,"name": "sunm xing"},
{"age": 44,"id": 12,"name": "test abc"}
] | orderBy:'id':true }} //根id降序排
//查找格式化
{
{ [{"age": 20,"id": 10,"name": "iphone"},
{"age": 12,"id": 11,"name": "sunm xing"},
{"age": 44,"id": 12,"name": "test abc"}
] | filter:'s'}} //查找含有有s的行
//字符串截取的格式化
{{ "i love tank" | limitTo:6 }} //结果:i love
{{ "i love tank" | limitTo:-4 }} //结果:tank
### 使用路由和$location切换视图
- 虽然从技术角度来说,Ajax应用是单页面应用,但很多时候处于各种原因,我们需要切换跳转页面,而Angular的`$router`服务负责应对这样的场景。
- 路由服务机制是这样定义的:当浏览器指向特定的URL时,Angular会加载显示一个模板,并实例化一个控制器为此模板提供内容。
- 开发者可以通过`$routerProvider`服务的函数来创建路由,把需要创建的路由当做一个配置项传递给这些函数。
//创建模块
var someModule = angular.module('someModule',[...module dependencies...]);
someModule.config(function($routerProvider){
//通过$routerProvider定义url对应的跳转动作所需要的模板和控制器
$routerProvider.
when('url',{controller:aController,templateUrl:'/path/to/tempate'}).
othenwise('badUrl',{controller:notFoundController,templateUrl:'/path/to/tempate'});
});
### 与服务器交互
- Angular提供的`$http`服务使得与服务器交互更加容易。它支持HTTP、JSONP和CORS方式。它还包含了安全性支持,避免JSON格式的脆弱性和XSRF。
function ShoppingController($scope,$http){
$http.get('/products').success(function(data,status,headers.config){
$scope.items = data;
});
};
### 使用指令修改DOM
- Angular指令扩展了HTML的语法,通过自定义的元素或者属性,把行为和DOM转换关联到一起。通过Angular可以创建可复用的UI组件。
- Angular指令创建语法:(directiveFn是一个工厂函数,用来定义指令的特性)
var app = angular.module('myapp',[]);
app.directive(name,fn);
- 案例:定义ts-hello指令,向DOM中输出“Hello,Angular Directive!”
<!DOCTYPE html>
<html ng-app="DirectiveTestApp">
<head>
<meta charset="UTF-8">
<title>AngularJS Directive</title>
<style></style>
</head>
<script src="http://cdn.static.runoob.com/libs/angular.js/1.4.6/angular.min.js"></script>
<body>
<div ng-controller="SomeController">
<ts-hello></ts-hello>
<div ts-hello></div>
<div class="ts-hello"></div>
<div data-ts-hello></div>
</div>
</body>
<script type="text/javascript">
var app = angular.module('DirectiveTestApp', []);
app.directive("tsHello",function(){
return {
restrict : 'EAC',
template : '<h5>Hello,Angular Directive!</h5>'
}
});
</script>
</html>
### 校验用户输入
- 在表单校验上,Angular允许开发者为表单的元素定义一个个发的状态,只有当所有元素都是合法状态时,才允许提交表单。
- 案例:一个用户注册页面,要求必须输入用户名和邮箱,使用H5的required属性
<!DOCTYPE html>
<html ng-app="myApp">
<head>
<meta charset="UTF-8">
<title></title>
</head>
<body>
<h1>Sign Up</h1>
<form name="addUserForm" ng-controller="AddUserController">
<div ng-show="message">{{message}}</div>
First name : <input name="firstName" required/>
Last name : <input name="lastName" required/>
Email : <input name="email" required type="email"/>
Age : <input name="age" required type="number"/>
<button ng-click="addUser()" ng-disabled="!addUserForm.$valid">Submit</button>
</form>
<script src="http://cdn.static.runoob.com/libs/angular.js/1.4.6/angular.min.js"></script>
<script type="text/javascript">
var myApp = angular.module('myApp',[]);
myApp.controller('AddUserController',function($scope){
$scope.message = '';
$scope.AddUser = function(){
alert("add user success!");
}
});
</script>
</body>
</html>