angular.js开发下一代web读书笔记

目录
  1. 模板和数据绑定
    1. 列表,表格以及其他迭代性元素
    2. 隐藏和显示
    3. css类与样式
  2. 使用Module组织依赖关系
    1. 使用过滤器格式化数据
    2. 使用指令修改DOM

##谈到angular.js可以想到什么?

  • 核心特性:MVC,模块化,自动化双向数据绑定,语义化标签,依赖注入(首次应用于前端框架。)。
  • TDD(测试驱动)。
  • 解决html实现动态页面的困难。

#angular框架中的基本概念

##1.客户端模板
区别于基于ajax的单页面是在服务器端装配模板和data,然后把生成的页面发送到浏览器。而angular.js是服务器把数据和模板都发到浏览器,然后再客户端中进行装配。

1
2
3
4
5
6
7
8
9
10
11
<body>
<div ng-controller="HelloController">
<p>{{greeting.text}}, world</p>
</div>
</body>
<!-- 应用逻辑 -->
<script>
function HelloController($scope){
$scope.greeting = {text: "Hello"};
}
</script>

$scope对象是模板的域模型,也称为作用域实例,为其赋值,可以传到模板中渲染。
$scope对象可以精确的控制域模型的那些数据和操作在视图上是有效的。

其中,使用的是google的CDN。

1
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.0/angular.min.js"></script>

##2.MVC
model:管理数据的代码; angular.js中就是存储数据的对象。
controller:应用逻辑代码; angular.js中就是js类。
view:展示数据的代码; angular.js中DOM。

MVC逻辑图

##3.数据绑定
最新的数据插入到UI中,或者用户的输入修改数据。不必要使用代码来实现。使用映射让它们自动同步: UI中的某个部分映射某个js的属性。

1
<input type="text" ng-model="greeting.text">;

##4.依赖注入
只需要简单的获取它们所需要的东西,而不需要创建那些它们所依赖的东西。比如$scope。

##5.指令
把模板编写成HTML的形式。因为angular引入了一款强大的DOM转换引擎,可以扩展HTML的语法。

ng-app: 告诉angular页面中的哪些部分需要接受它的管理。
ng-xxxController: 控制器。
ng-repeat:
ng-click: 点击触发的事件。

#AngularJs应用骨架

##调用Angular

  • 加载angular.js库。
  • ng-app指令指定angular管理的DOM部分。

###加载脚本
CDN:

1
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.0/angular.min.js"></script>

google服务器快,而且可以在多个应用之间缓存脚本库。

###使用ng-app声明Angular的边界
如果要打造一款纯angular应用,则可以把ng-app指令放在html标签内。

#Model View Controller

  • 容纳数据的模型
  • 展示数据的视图
  • 管理模块和视图之间关系的控制器

双括号插值语法

控制器就是编写的类,告诉angular这个模型是由哪些对象或者基本数据构成的。
如果数据少的话可以直接用基本数据存储,但是大部分的时候是创建模型对象来容纳数据。

1
2
3
4
5
6
7
<script>
function TextController($scope){
var messages = {};
messages.sometext = "huangzhuang.github.io";
$scope.messages = messages;
}
</script>

事实上,控制器的编写不应该实在全局命名空间的,所以正确的做法就是应该把它定义成模块的一部分,然后使用命名空间机制。如下:

1
2
3
4
5
6
7
8
9
10
11
12
<html ng-app = "hzhuang-app">
...
<script>
var appModuel = angular.module("hzhuang-app",[]);
//angular对象创建名为hzhuang-app的模块。后面的空数组表示不依赖其他模块。
appModuel.controller("TextController", function($scope){
//控制器函数传递给appModuel的consroller函数
var messages = {};
messages.sometext = "huangzhuang.github.io";
$scope.messages = messages;
});
</script>

模板和数据绑定

工作流程:

###显示文本
ng-bind指令
虽然双括号插值法也可以,但是这种方法会导致没有渲染好模块会直接显示给用户一段时间的问题。

###表单输入
ng-model属性把元素绑定到模型属性上。
ng-change属性指定一个控制器方法,一旦用户修改了输入值,这个方法就会被调用。

1
2
3
4
5
6
7
8
9
10
11
12
<form ng-controller="StartUpController">
Starting: <input type="text" ng-change = "computeNeeded()" ng-model="funding.startingEstimate">
Recommendation: {{funding.needed}}
</form>
<script>
function StartUpController($scope){
$scope.funding = {startingEstimate: 0};
$scope.computeNeeded = function(){
$scope.funding.needed = $scope.funding.startingEstimate * 10;
}
}
</script>

以上的实例会有一个问题:如果还有其他的输入框也绑定到模型的这个属性上,如果接受到服务器端的数据,就会导致模型刷新。
so,scopewatch()函数来解决这个问题。它来监视一个表达式,当这个表达式发生变化的时候就会调用一个回调函数。

1
2
3
4
5
6
7
8
9
<script>
function StartUpController($scope){
$scope.funding = {startingEstimate: 0};
computeNeeded = function(){
$scope.funding.needed = $scope.funding.startingEstimate * 10;
}
$scope.$watch("funding.startingEstimate", computeNeeded);
}
</script>

ng-submit在提交表格的时候执行。

1
<form ng-controller="StartUpController" ng-submit="requestFunding()">

在提交表单的时候,ng-submit会自动组织浏览器执行默认的POST操作。
ng-click
ng-dbclick

###浅谈非入侵式javascript

  • 并不是所有的浏览器都支持js。
  • 有人会使用非常奇怪的浏览器,屏幕阅读器。
  • 在不同的方式运行的效果不同。
  • 事件处理都在全局,不同库相同函数名会冲突。
  • 时间监听会绑定数据结构和行为,难以维护、扩展和理解。

对于angular而言,以上的第三点和第四点,对于内联的时间监听有等价的形式ng-eventhandler = “xxx”。
优点:

  • 屏蔽不同浏览器的差异性。
  • 不会在全局命名空间中操作。

对于第五点,angular处理的时候根本就没有引用DOM,都是在内部完成的。

列表,表格以及其他迭代性元素

主要就是详细讲解ng-repeat

index,middle还有$last

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<ul ng-controller="StudentListController">
<li ng-repeat = "student in students">
<a href="#">{{student.name}} + {{$index}}</a>
</li>
</ul>
<script>
var students = [
{name : "hzhuang" , id : 1},
{name : "huangkai", id : 2},
{name : "fangjie", id : 3}
];
function StudentListController($scope){
$scope.students = students;
$scope.students.splice(1,0,{name : "xuxiaofeng", id : 4});
};
</script>

隐藏和显示

ng-show和ng-hide

css类与样式

1
2
3
4
.menu-disabled-true{
color:gray;
}
<li class = "menu-disabled-{{isDisabled}}"></li>

以上的方法可以实现,但是同时在模板和js中使用的话就会无法维护。
在angular中,ng-classng-style指令。

1
2
3
4
5
6
7
8
9
10
11
<div ng-controller="HeaderConrtoller">
<div ng-class="{error:isError, warning:isWarening}">{{messageText}}</div>
<button ng-click = "showError()">Error</button>
<button ng-click = "showWarning()">Warning</button>

</div>
<script>
function HeaderController($scope){
xxx;
}
</script>

###反思src和href属性

1
2
<img ng-src="http://hz2015.sinaapp.com/static/pictures/{{name}}" alt="">
<a ng-href="http://hz2015.sinaapp.com"></a>

###表达式
表示是可以充分的灵活性在模板、业务逻辑和数据之间建立联系,同时又可以避免业务逻辑渗透到模板中。

###区分UI和控制器的职责
控制器的职责

  • 为模板设置初始状态
  • 通过$Scope对象把数据模型和函数暴露给视图。
  • 监视模板其余部分的变化,并采取相应的动作。

###利用scopescope属性的方法:

  • 表达式法:

    1
    <button ng-click="count=5">press</button>
  • 表单输入项中使用ng-model。在表单项和指定的模板数据之间建立双向绑定关系。

###使用$watch监控数据模型的变化

1
$watch(watchFn, watchAction, deepWatch);

watchFn:是一个带有angular表达式或者函数的字符串
watchAction:函数或者表达式,当watchFn变化的时候被调用。函数签名是

1
function(newValue, oldValue, scope);

deepWatch: 检查被监控的对象的每个属性是否发生了变化。

$watch函数会返回一个函数。可以使用这个返回值注销这个函数。

带折扣的购物车以及注意watch()中的性能注意事项见P30-P34。

###监控多个东西

  • 监控把这些属性链接起来之后的值

    1
    $scope.$watch("things.a + things.b", doSomething);
  • 把他们放进一个数组或者对象中,然后给deepWatch参数设置成true

    1
    $scope.$watch("things", doSomething, true);

使用Module组织依赖关系

1
2
3
4
5
<script>
function ShoppingController($scope, Items){
$scope.items = Items.query();
}
</script>

以上假设Items这个对象已经被定义成一个服务。
服务都是单例的对象。
angular.js内置很多服务:

  • $location用来和浏览器的地址栏交互。
  • $route用来根据URL地址的变化切换视图。
  • $http用来和服务器交互。
  • 自定义服务,可以在不同的控制器之间共享。

模型对象的API来定义服务。

函数 定义
provider(name, Object OR constructor()) 返回服务的名称,如果是Object,则Object必须有一个$get的函数。如果是constructor(),则会返回服务实例对象。
factory(name, $getFunction()) 返回服务实例。看成以上Object时候的情形。
server(name, constructor()) 比较简单,看成最上面是constructor的情形
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<script>
function ShoppingController($scope, Items){
$scope.items = Items.query();
}
//创建一个模型来支持视图
var shoppingModule = angular.module("shoppingModule", []);
shoppingModule.factory("Items", function(){
var items ={};
items.query = function(){
return [
{title: 'Paint pots', description: 'Pots full of paint', price: 3.95},
{title: 'Polka dots', description: 'Dots with polka', price: 2.95},
{title: 'Pebbles', description: 'Just little rocks', price: 6.95}
];
}

})
</script>

使用过滤器格式化数据

1
2
{{expression | filterName : parameter1 : ... parameterN}}\
{{12.9 | currency}}

angular内置的过滤器还有:date, number, uppercase
同时也可以自定义过滤器。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<body ng-app="HomeModule" ng-controller="HomeController">
<h1>{{pageHeading | titleCase}}</h1>
</body>
<script>
function HomeController($scope){
$scope.pageHeading = "hzhuang is a very nice man!";
}
var homeModule = angular.module("HomeModule", []);
homeModule.filter("titleCase", function(){
var titleCaseFilter = function(input){
var words = input.split(" ");
for(var i = 0 ,len = words.length; i< len; i++ ){
words[i] = words[i].charAt(0).toUpperCase() + words[i].slice(1);
}
return words.join(" ");
};
return titleCaseFilter;
});

</script>

###使用路由和locationURLangularrouteProvider服务上的函数来创建路由,把需要创建的路由当成一个配置快传给这些函数。

1
2
3
4
5
6
7
8
var someModule = angular.module("someModule", []);
someModule.config(function($routeProvider){
$routeProvider.
when("url", {controller:aController, templateURL:"/path/to/template"}).
when(...other mappings for you app).
...
otherwise(...what to do if no match);
})

以创建邮件为例子。
在第一个加载的页面中仅仅创建一个布局模板,然后用这个模板来容纳各种视图。ng-view指令。

1
2
3
4
5
6
7
8
9
10
11
12
//index.html
<!doctype html>
<html ng-app="AMail">
<head>
<script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.0.8/angular.min.js"></script>
<script src='controllers.js'></script>
</head>
<body>
<h1>A-Mail</h1>
<div ng-view></div>
</body>
</html>

用ng-repeat来遍历邮件列表,然后渲染到table中。

1
2
3
4
5
6
7
8
9
10
11
12
13
//list.html
<table>
<tr>
<td><strong>Sender</strong></td>
<td><strong>Subject</strong></td>
<td><strong>Date</strong></td>
</tr>
<tr ng-repeat='message in messages'>
<td>{{message.sender}}</td>
<td><a ng-href='#/view/{{message.id}}'>{{message.subject}}</a></td>
<td>{{message.date}}</td>
</tr>
</table>

想达到用户点击主题就会跳转到相应的邮件中,我们可以在URL和message.id中数据绑定。点击id=1的邮件就会被导航到/#/view/1。
创建一个模板,用来展示单个邮件对象上的属性。

1
2
3
4
5
6
7
8
9
<div><strong>Subject:</strong> {{message.subject}}</div>
<div><strong>Sender:</strong> {{message.sender}}</div>
<div><strong>Date:</strong> {{message.date}}</div>
<div>
<strong>To:</strong>

<span ng-repeat='recipient in message.recipients'>{{recipient}} </span>
</div>

<div>{{message.message}}</div>
<a href='#/'>Back to message list</a>

就下来就可以配置URL

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<script>
var aMailServices = angular.module("AMail", []);
function emailRouteConfig($routeProvider){
$routeProvider.
when("/", {controller: ListController, templateUrl: "list.html"}).
when("/view/:id",{controller: DetailController, templateUrl: "detail.html"}).
otherwise({redirectTo: "/"});

}

aMailServices.config(emailRouteConfig);
function ListController($scope) {
$scope.messages = messages;
}

function DetailController($scope, $routeParams) {
$scope.message = messages[$routeParams.id];
}

</script>

可见,可以实现修改url的值实现切换视图。且实际上只有一个唯一真实的html页面。

###与服务器交互(httphttp提供一个可扩展的抽象方法列表,使得与服务器交互更加简单。支持http、Jsonp和cors方式。包含安全性支持。

如果查询“/products”时可以返回一个json数据。
则:

1
2
3
4
5
function shoppingController($scope, $http){
$http.get("/products").success(function(data, status, headers, config){
$scope.items = data;
})
}

然后后ng-repeat指令就可以把这些json数据的信息渲染到视图中。

使用指令修改DOM

angular内置没有这个功能的指令,所以可以自定义指令。
通过模块对象的API来定义指令

1
2
3
4
var appModule = angular.module("appModule", []);
appModule.directive("directiveName", directiveFunction);
//调用directive()即可。
//directiveFunction是一个工厂函数,用来定义指令的特性。

实例:

1
2
3
4
5
6
7
8
var appModule = angular.module("appModule", []);
appModule.directive("ngFocus", function(){
return {
link: function(scope, element, attrs, controller){
element[0].focus();
}
}
})

###校验用户输入
angular中,必须所有的元素都是合法的情况下才会允许提交。
思路是这样的,在html5中使用required,以及输入的;类型比如number,email等,如果浏览器不支持h5的这些属性,angular会自动通过指令来实现。

在控制器中,可以使用validtruengdisabledvalid属性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<body>
<h1>Sign Up</h1>
<form name='addUserForm' ng-controller="AddUserController">
<div ng-show='message'>{{message}}</div>
<div>First name: <input name='firstName' ng-model='user.first' required></div>
<div>Last name: <input ng-model='user.last' required></div>
<div>Email: <input type='email' ng-model='user.email' required></div>
<div>Age: <input type='number'
ng-model='user.age'
ng-maxlength='3'
ng-min='1'></div>

<div><button ng-click='addUser()'
ng-disabled='!addUserForm.$valid'>Submit</button></div>

</form>
<script>
function AddUserController($scope) {
$scope.message = '';

$scope.addUser = function () {
// TODO for the reader: actually save user to database...
$scope.message = 'Thanks, ' + $scope.user.first + ', we added you!';
};
}
</script>

</body>

几点说明:

  • ng-disabled=’!addUserForm.$valid’来实现党所有的输入项合法就会恢复可点击。
  • .ng-invalid {border-color: red;}输入项还不是合法的时候的样式。(内置的)