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,主要功能为图书管理,对图书的增删改查并显示图书清单,下图是首页截图

接下来说说代码,后端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
