AngularJS入门的小Demo

AngularJS诞生于2009年,由Misko Hevery等人创建,后为Google所收购。是一款优秀的前端JS框架,已经被用于Google的多款产品当中(Gmail)。AngularJS有着诸多特性,最为核心的是:MVVM、模块化、自动化双向数据绑定、语义化标签、依赖注入,等等。
今年暑假才接触AngularJS,之前还完全不理解MVC思想。在我见过几乎所有的教程中,讲解AngularJS的第一个例子都是它(注意下面span标签中有”“,由于Markdown行内代码在转义的时候有些问题):

1
2
3
4
5
6
7
8
9
<!DOCTYPE html>
<html ng-app>
<head>
<script src="http://cdn.bootcss.com/angular.js/1.0.1/angular.min.js"></script>
</head>
<body>
<input type="text" ng-model="text"><span>{{text}}</span>
</body>
</html>

这个最简单的Demo的效果就是你在输入框里输入什么,就会在输入框后面显示什么,完全同步。回想一下,如果用传统的JavaScript写的话,大致思路是这样的,监听input值的变化,再每次去更新显示出来的值,代码比这个多的去了。
So 这就是AngularJS的双向数据绑定,多么炫酷!!!

AngularJs主要分为数据(Model)、模板(View)、控制器(Controller)三大块,典型的MVC前端框架。当然AngularJs中还有许多酷炫的东西,比如说用户绑定数据的$Scope、纯前端的路由Routes、过滤器Filters(巨给力)、指令Directive(同过滤器给力)、还有服务Service。
p
言归正传,说说这个Demo,主要功能为图书管理,对图书的增删改查并显示图书清单,下图是首页截图

Book--简易图书管理系统
接下来说说代码,后端RESTful类型的Api不是这儿要说的重点,忽略掉。
进入这个项目的文件夹之后,访问index.html,所以index.html就作为这个项目的入口

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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
<!DOCTYPE html>
<html lang="en" ng-app="book">
<head>
<meta charset="utf-8"/>
<title>Book--简易图书管理系统</title>
<link href="vendor/bootstrap.min.css" rel="stylesheet">
<link href="css/app.css" rel="stylesheet"/>
</head>
<body>
<nav class="navbar navbar-inverse navbar-fixed-top" role="navigation">
<div class="container-fluid">
<div class="navbar-header">
<a class="navbar-brand" href="#/">Book--简易图书管理系统</a>
</div>
<div class="collapse navbar-collapse" id="bs-example-navbar-collapse-5">
<p class="navbar-text navbar-right">Code in <a href="https://github.com/zhujun24/book" target="_blank" class="navbar-link">Github</a></p>
</div>
</div>
</nav>
<div class="container">
<div class="btn-group-vertical col-lg-2">
<a href="#/new/" type="button" class="btn btn-primary btn-lg btn-block">Add Book</a>
<a href="#/" type="button" class="btn btn-default btn-lg btn-block">Book List</a>
</div>
<div class="col-lg-10">
<div ng-view></div>
</div>
</div>
<!-- JavaScript -->
<script src="vendor/angular.min.js"></script>
<script src="vendor/jquery.min.js"></script>
<script src="vendor/bootstrap.min.js"></script>
<script src="js/app.js"></script>
<script src="js/controllers.js"></script>
<script src="js/services.js"></script>
<script src="js/filters.js"></script>
<script src="js/directives.js"></script>
</body>
</html>

这里面最重要的就是一个最高优先级的指令(Directive)ng-view,后面几乎所有的视图(View)都会在这里呈现,下面开始JavaScript了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
'use strict';
var book = angular.module('book', []);
book.config(['$routeProvider', function ($routeProvider) {
$routeProvider.
when('/', {
controller: 'ListCtrl',
templateUrl: 'views/list.html'
}).when('/edit/:id', {
controller: 'EditCtrl',
templateUrl: 'views/edit.html'
}).when('/view/:id', {
controller: 'ViewCtrl',
templateUrl: 'views/view.html'
}).when('/new', {
controller: 'NewCtrl',
templateUrl: 'views/edit.html'
}).otherwise({
redirectTo: '/'
});
}]);

app.js这段代码定义了一个模块book,这个项目只有这一个模块,前面的use strict是开启JavaScript严格模式,后面的$routeProvider服务定义了路由,插一句题外话,路由中有了otherwise那还需要404页面吗?接着往下看

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
26
27
28
29
30
31
32
33
'use strict';
book.factory('books', ['$http', function ($http) {
var url = "api/book";
var factory = {};
factory.all = function () {
var books = $http.get(url).then(function (resp) {
return resp.data.book;
});
return books;
};
factory.get = function (id) {
var book = $http.get(url + '/' + id).then(function (resp) {
return resp.data;
});
return book;
};
factory.add = function (book) {
$http.post(url, book).then(function (resp) {
return resp;
});
};
factory.update = function (book) {
$http.put(url + '/' + book.id, book).then(function (resp) {
return resp;
});
};
factory.delete = function (id) {
$http.delete(url + '/' + id).then(function (resp) {
return resp;
});
};
return factory;
}]);

services.js这儿用factory定义了一些服务,用于和后端进行数据交互,控制器(Controller)再将这些数据绑定到$Scope上,再在视图(View)上呈现给用户,下面看看比较复杂的控制器代码

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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
'use strict';
book.controller('ListCtrl', ['$scope', '$filter', 'books',
function ($scope, $filter, books) {
$scope.loadList = function () {
books.all().then(function (books) {
$scope.books = books;
});
};
//init load data
$scope.loadList();
$scope.deleteBook = function ($index, id) {
if (confirm("确定删除?")) {
books.delete(id);
$scope.books.splice($index, 1);
}
};
//设置分页
//初始化分页参数
$scope.itemsPerPage = 10;
$scope.currentPage = 0;
$scope.prevPage = function () {
if ($scope.currentPage > 0) {
$scope.currentPage--;
}
};
$scope.prevPageDisabled = function () {
return $scope.currentPage == 0;
};
//书名搜索关键词,主要用于更新books数组
$scope.bookFilterdInput = '';
$scope.pageCount = function () {
if ($scope.books) {
//根据用户输入来过滤更新数组,主要用来更新页数
$scope.updatePage = $filter('bookname')($scope.books, $scope.bookFilterdInput);
//向上取整求出总页数
return Math.ceil($scope.updatePage.length / $scope.itemsPerPage);
} else {
return false;
}
};
$scope.nextPage = function () {
if ($scope.currentPage < $scope.pageCount()) {
$scope.currentPage++;
}
};
$scope.nextPageDisabled = function () {
return $scope.currentPage + 1 == $scope.pageCount();
};
$scope.$watch('bookFilterdInput', function () {
//console.log('change');
if ($scope.pageCount() <= $scope.currentPage) {
$scope.currentPage = 0;
}
})
$scope.$watch('itemsPerPage', function () {
//console.log('change');
if ($scope.pageCount() <= $scope.currentPage) {
$scope.currentPage = 0;
}
})
}
]);
book.controller('ViewCtrl', ['$scope', '$routeParams', 'books',
function ($scope, $routeParams, books) {
//用指令代替了这块功能,该controller和directiveCtrl完全相同
//books.get($routeParams.id).then(function (book) {
// $scope.book = book;
//});
}
]);
book.controller('EditCtrl', ['$scope', '$routeParams', '$location', 'books',
function ($scope, $routeParams, $location, books) {
books.get($routeParams.id).then(function (book) {
$scope.book = book;
});
$scope.new = function (book) {
books.update(book);
$location.path('/');
};
}
]);
book.controller('NewCtrl', ['$scope', '$location', 'books',
function ($scope, $location, books) {
$scope.new = function (book) {
books.add(book);
$location.path('/');
};
}
]);
book.controller('directiveCtrl', ['$scope', '$routeParams', 'books',
function ($scope, $routeParams, books) {
books.get($routeParams.id).then(function (book) {
$scope.book = book;
});
}
]);

在controllers.js中几乎包含了整个项目的业务逻辑,在服务(Service)和视图(View)中双向绑定数据,这块代码中用到了自定义的过滤器(Filter),主要用于图书清单的分页功能,为了优化这个分页,自定义分页过滤器,并在控制器(Controller)用$watch监听一些数据的变化来优化分页。下面说过滤器(Filter)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
'use strict';
book.filter('paging', function () {
return function (input, start) {
if (input) {
return input.slice(start);
}
};
})
.filter('bookname', function () {
return function (input, bookFilterdInput) {
if (input) {
var result = new Array();
for (var i = 0; i < input.length; i++) {
if (input[i].name.toLowerCase().indexOf(bookFilterdInput.toLowerCase()) != -1) {
result.push(input[i]);
}
}
return result;
}
};
})
;

过滤器无非就是对数据进行过滤,用于ng-repeat数组的过滤,根据过滤条件匹配每条数据并返回符合条件的数据,最后看看指令(Directive)

1
2
3
4
5
6
7
8
9
'use strict';
book.directive('myDirective', function () {
return {
restrict: 'E',
controller: 'directiveCtrl',
templateUrl: 'views/directive.html'
}
})
;

这是最最简单的指令了,没有比这个更简单的了,指令是AngularJS中很重要很复杂很酷炫的部分,里面水深
好了,这个Demo主要的代码都在这儿了,代码加数据库全部托管在Github上了,点击查看。或者直接用Git克隆

1
git clone https://github.com/zhujun24/book.git

Fork me on GitHub