上一节咱们走通了基本的SPA基础结构,这一节会更完全的将后端的视图、路由、控制器所有移到前端。篇幅比较长,主要分页面改造、使用AngularUI两大部分以及一些优化路由、使用Angular的其余指令的学习。篇幅虽然长,但熟悉了就是这个套路,特别是第一部分。重点是理解Angular这种操做数据而不是操做Dom的编程方式。javascript
1、移除服务端依赖css
上一节中咱们还保留了基于jade的layout。为此还保留一个Express的控制器。这一节咱们所有在客户端(app_client)实现。先在app_client目录下建立一个index.html(等因而layout.jade生成的页面)html
<!DOCTYPE html> <html ng-app="readApp"> <head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <link rel="stylesheet" href="/bootstrap/css/bootstrap.css" /> <link rel="stylesheet" href="/stylesheets/style.css" /> <title>ReadingClub</title> </head> <body> <nav class="navbar navbar-default navbar-fixed-top navbar-inverse"> <div class="container"> <div class="navbar-header"><a href="/" class="navbar-brand">ReadingClub</a></div> <div class="collapse navbar-collapse"> <ul class="nav navbar-nav pull-right"> <li><a href="/">首页</a></li> <li><a href="/books">读物</a></li> <li><a href="/about">关于</a></li> <li><a href="/register">注册</a></li> <li><a href="/login">登陆</a></li> </ul> </div> </div> </nav> <div id="bodycontent" class="container" > <div ng-view> </div> </div> <footer class="container"> <div class="row"> <div class="col-xs-12"><small>© stoneniqiu 2016</small></div> </div> </footer> <script src="/angular/angular.min.js"></script> <script src="/lib/angular-route.min.js"></script> <script src="/angular/readApp.min.js"></script> <!--script(src='/app.js')--> <!--script(src='/home/home.controller.js')--> <!--script(src='/common/services/ReadData.service.js')--> <!--script(src='/common/filters/formatDate.filter.js')--> <!--script(src='/common/directive/ratingStars/ratingStars.directive.js')--> <script src="/javascripts/jquery-1.11.1.min.js"></script> <script src="/plupload-2.1.8/js/plupload.full.min.js"> </script> <script src="/javascripts/books.js"></script> </body> </html>
咱们已经使用了Angular的路由,就不想还要维护Express的路由。固然这个文件仍是须要Express给咱们返回的,因而修改根目录下的app.js前端
//app.use('/', routes); app.use('/api', routesApi); app.use(function (req, res) { res.sendfile(path.join(__dirname, 'app_client', 'index.html')); });
注释掉app.use('/', routes),保留api部分,使用app.use,只要请求到达这里都会返回index.html。固然Angular不会每次都请求这个页面。这个时候运行,页面上尚未什么变化。index.html页面有太多标签,接下来将header和footer做为指令提出来,这便于之后替换和复用。要注意的是若是你想把指令用作元素,就不能使用html元素的名称命名。因此footer命名为footerNav,或者别的什么你喜欢的名字均可以。html5
先在directive目录下建立一个footer文件夹,建立一个footer.html,把index.html中的footer部分拿过来。java
<footer class="container"> <div class="row"> <div class="col-xs-12"><small>© stoneniqiu 2016</small></div> </div> </footer>
而后在同目录下建立一个footer.js,建立指令为footerNavjquery
(function () { angular .module('readApp') .directive('footerNav', footerNav); function footerNav() { return { restrict: 'EA', templateUrl: '/common/directive/footer/footer.html' }; } })();
别忘记添加进appClientFiles 数组中git
var appClientFiles = [ 'app_client/app.js', 'app_client/home/home.controller.js', 'app_client/common/services/ReadData.service.js', 'app_client/common/filters/formatDate.filter.js', 'app_client/common/directive/ratingStars/ratingStars.directive.js', 'app_client/common/directive/footer/footer.js' ];
使用:程序员
<footer-nav></footer-nav>
会生成:angularjs
这样就完成了footer部分的改造。
同理对于导航条,也是上面的几个步骤,这里就不赘述了。
使用的时候调用,这样就很方便了。
<navigation></navigation>
这样让每一个文件只作一件事,之后须要使用某个组件能够直接拿过去。
如今咱们能够修改下以前定义的home.view.html,将导航和footer加过来。
<navigation></navigation> <div id="bodycontent" class="container"> <div class="row"> <div class="col-md-9 page"> <div class="row topictype"><a href="/" class="label label-info">所有</a><a href="/">读书</a><a href="/">书评</a><a href="/">求书</a><a href="/">求索</a></div> <div class="error">{{ vm.message }}</div> <div class="row topiclist" data-ng-repeat='topic in vm.data'> <img data-ng-src='{{topic.img}}'><span class="count"><i class="coment">{{topic.commentCount}}</i><i>/</i><i>{{topic.visitedCount}}</i></span> <span class="label label-info">{{topic.type}}</span><a href="/">{{topic.title}}</a> <span class="pull-right">{{topic.createdOn | formatDate}}</span><a href="/" class="pull-right author">{{topic.author}}</a> </div> </div> <div class="col-md-3"> <div class="userinfo"> <p>{{vm.user.userName}}</p> </div> </div> </div> </div> <footer-part></footer-part>
页面的结构完整了。增长了navigation、footer和container 。因而乎,index.html只须要保留如下内容
<!DOCTYPE html> <html ng-app="readApp"> <head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <link rel="stylesheet" href="/bootstrap/css/bootstrap.css" /> <link rel="stylesheet" href="/stylesheets/style.css" /> <title>ReadingClub</title> <base href="/" /> </head> <body ng-view> <script src="/angular/angular.min.js"></script> <script src="/lib/angular-route.min.js"></script> <script src="/angular/readApp.min.js"></script> <!--script(src='/app.js')--> <!--script(src='/home/home.controller.js')--> <!--script(src='/common/services/ReadData.service.js')--> <!--script(src='/common/filters/formatDate.filter.js')--> <!--script(src='/common/directive/ratingStars/ratingStars.directive.js')--> <script src="/javascripts/jquery-1.11.1.min.js"></script> <script src="/plupload-2.1.8/js/plupload.full.min.js"> </script> <script src="/javascripts/books.js"></script> </body> </html>
ng-view位于body上了,到目前为止路由、视图都是由Angular管理了。咱们只用Express返回了须要的资源文件。
2、路由优化
在上一节使用Angular路由的时候,地址上回多出一个#号,看上去不太美观,Angular提供了方法从地址栏移除#号,但这个功能在ie9及如下有兼容性问题,因此若是顾及到ie9及如下版本,能够跳过这个部分。由于这里使用了H5的一个特性。使用$locationProvider切到h5模式:
(function() { angular.module('readApp', ['ngRoute']); function config($routeProvider, $locationProvider) { $routeProvider .when('/', { templateUrl: 'home/home.view.html', controller: 'homeCtrl', controllerAs: 'vm' }) .otherwise({ redirectTo: '/' }); $locationProvider.html5Mode(true); } angular .module('readApp') .config(['$routeProvider', '$locationProvider', config]); } )();
可是若是出现了如下错误:
须要在head中作如下修改:
<head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <link rel="stylesheet" href="/bootstrap/css/bootstrap.css" /> <link rel="stylesheet" href="/stylesheets/style.css" /> <title>ReadingClub</title> <base href="/" /> </head>
如今浏览页面,#号已经消失了。IE9中还存在。若是是兼容性视图,页面将是一片空白。AngularJS 1.3抛弃了对IE8的支持,AngularJS 1.2将继续支持IE8,但核心团队已经不打算在解决IE8及以前版本的问题上花时间。因此这一点要注意到。接下来改造更多的页面
3、页面改造
前四章咱们用jade模板制做了几个页面,目前只改造了index.html。接下来继续Angular化。
在app_client目录下建立一个about文件夹,并新建一个about.controller.js。包含一个user,一个title,和一个list,也就是咱们读过的书。定义为aboutCtrl。
(function () { angular .module('readApp') .controller('aboutCtrl', aboutCtrl); function aboutCtrl() { var vm = this; vm.title = 'ReadingClub'; vm.user = { userName: "stoneniqiu", }; vm.list = [ "第一期 《失控》 -- 上海-stoneniqiu", "第二期 《代码整洁之道》 -- 上海-stoneniqiu", "第三期 《女人的起源》 -- 长沙-素情", "第四期 《数学之美》 -- 广州_Watery.D.Lotus", "第五期 《卓有成效的管理者》 -- 北京-卡萨布兰卡", "第六期 《异类》 -- 上海-stoneniqiu", "第七期 《设计心理学》 -- 北京--彦圣", "第八期 《乌合之众》 -- 广州_Watery.D.Lotus & 上海_stoneniqiu", "第九期 《国富论》 -- 上海-stoneniqiu", "第十期 《少有人走的路》 -- 深圳-一路风景", "第十一期 《程序员修炼之道》 -- stoneniqiu", "第十二期 《性格色彩》 -- 上海_星空" ]; } })();
并加入到appClientFiles中。
var appClientFiles = [ 'app_client/app.js', 'app_client/home/home.controller.js', 'app_client/common/services/ReadData.service.js', 'app_client/common/filters/formatDate.filter.js', 'app_client/common/directive/ratingStars/ratingStars.directive.js', 'app_client/common/directive/footer/footer.js', 'app_client/common/directive/navigation/navigation.js', 'app_client/about/about.controller.js', ];
而后再增长一个about.html:
<navigation></navigation> <div id="bodycontent" class="container"> <div class="row"> <div class="col-md-9 page"> <p >欢迎来到{{vm.title}},咱们一块儿读过的书: </p> <ul> <li ng-repeat='book in vm.list' > <span>{{book}}</span> </li> </ul> <img src="imgs/read.jpg"/> </div> <div class="col-md-3"> <div class="userinfo"> <p>{{vm.user.userName}}</p> </div> </div> </div> </div> <footer-part></footer-part>
而后加入路由:
function config($routeProvider, $locationProvider) { $routeProvider .when('/', { templateUrl: 'home/home.view.html', controller: 'homeCtrl', controllerAs: 'vm' }).when('/about', { templateUrl: 'about/about.html', controller: 'aboutCtrl', caseInsensitiveMatch: true, controllerAs: 'vm' }) .otherwise({ redirectTo: '/' }); $locationProvider.html5Mode(true); }
Angular路由默认是大小写敏感的,若是要忽略掉大小写,须要加上caseInsensitiveMatch: true。这个时候访问页面:
若是个人title是这样:
vm.title = '<b>ReadingClub</b>';
页面上会直接获得:
欢迎来到<b>ReadingClub</b>,咱们一块儿读过的书:
如何不把标签当字符输出呢,这就要用到angular-sanitize。下载angular-sanitize的min.js和map文件, 放置在lib目录下。 https://code.angularjs.org/1.4.6/ 并在index.html中引用:
<script src="/lib/angular-sanitize.min.js"></script>
而后还须要增长模块依赖,和路由模块同样,修改app_client/app.js 模块名称为ngSanitize
angular.module('readApp', ['ngRoute', 'ngSanitize']);
而后就能够在页面上使用ng-bind-htm来显示html片断。
<p >欢迎来到<span ng-bind-html="vm.title"></span>,咱们一块儿读过的书: </p>
这稍微显得有点麻烦,多了一个元素,且内容不能拼接。若是是Asp.net MVC 一个@Html.Raw()就好。jade也就多个符号。
books这个页面和index页面很类似,稍微有点不一样的是对应的service:
angular .module('readApp') .service('topicData', topicData) .service('booksData', booksData) .service('userData', userData); topicData.$inject = ['$http']; function topicData ($http) { return $http.get('/api/topics'); }; booksData.$inject = ['$http']; function booksData($http) { var getBooks = $http.get('/api/books'); var getbookById = function(bookid) { return $http.get('/api/book/' + bookid); }; return { getBooks: getBooks, getbookById: getbookById }; }; function userData() { return { userName: "stoneniqiu", }; }
建立一个booksData 服务,包含两个方法,一个是getBooks,一个是getbookById。而后顺便将user部分作成了userData,在后面会使用真正的用户数据。一样在app_client下建立一个books文件夹,新建books.controller.js和books.html
控制器:
(function () { angular .module('readApp') .controller('booksCtrl', booksCtrl); booksCtrl.$inject = ['booksData', 'userData']; function booksCtrl(booksData,user) { var vm = this; vm.message = "loading..."; booksData.getBooks.success(function (data) { vm.message = data.length > 0 ? "" : "暂无数据"; vm.books = data; }).error(function (e) { console.log(e); vm.message = "Sorry, something's gone wrong "; }); vm.user = user; } })();
习惯性啰嗦一句,记得加入appClientFiles,生成压缩文件
视图:
<navigation></navigation> <div id="bodycontent" class="container"> <div class="row"> <div class="col-md-9 page"> <div class="row booklist" ng-repeat="book in vm.books|orderBy:'rating':true"> <div class="col-md-2"> <img data-ng-src='{{book.img}}'></div> <div class="col-md-10"> <p> <a href="/book/{{book._id}}">{{book.title}}</a> <span class="close" data-id="{{book._id}}">×</span> </p> <p>{{book.info}}</p> <p rating-stars rating="book.rating"></p> <p class="tags"> <span ng-repeat="tag in book.tags">{{tag}}</span> </p> </div> </div> </div> <div class="col-md-3"> <div class="userinfo"> <p>{{vm.user.userName}}</p> </div> </div> </div> </div> <footer-part></footer-part>
和以往不一样的是,使用了一个Angular自带的filter:orderby。第一个参数是字段名,第二个参数默认是false,true是表明降序。
路由:
.when('/books', { templateUrl: 'books/books.html', controller: 'booksCtrl', caseInsensitiveMatch: true, controllerAs: 'vm' })
这个时候页面出来了。一见如故:
还须要增长一个detail的页面,但不想再讲上面的步骤了,说白了,都是套路。强调两个不一样的地方。一个是路由传递参数
.when('/book/:bookid', { templateUrl: 'bookDetail/bookDetail.html', controller: 'bookDetailCtrl', caseInsensitiveMatch: true, controllerAs: 'vm' })
这个写法和Express中定义路由参数同样。二个是控制器使用$routeParams获取参数
(function () { angular .module('readApp') .controller('bookDetailCtrl', bookDetailCtrl); bookDetailCtrl.$inject = ['$routeParams','booksData', 'userData']; function bookDetailCtrl($routeParams, booksData, user) { var vm = this; var bookid = $routeParams.bookid; booksData.getbookById(bookid).success(function(data) { vm.book = data; }).error(function (e) { console.log(e); vm.message = "Sorry, something's gone wrong "; }); vm.user = user; vm.closed = false; } })();
其余地方不清楚的能够参考页尾提供的源码。
4、Angular-ui-bootstrap
到如今新增和删除没有作。接下来使用Bootstrap的模态对话框来完成新增功能。惋惜http://angular-ui.github.io/bootstrap/ 官网打不开,能够在 http://www.bootcdn.cn/angular-ui-bootstrap/ 下载。这个AngularUI已经定义了20多个组件,由于没有使用所有的组件,只是使用了modal。因此能够引用定制版 http://files.cnblogs.com/files/stoneniqiu/ui-bootstrap-custom.zip 接下来的部分有点复杂,各位看官请耐心...
<script src="/lib/ui-bootstrap-custom-0.12.0.min.js"></script> <script src="/lib/ui-bootstrap-custom-tpls-0.12.0.min.js"></script>
在booksCtrl上添加modal依赖:
booksCtrl.$inject = ['booksData', 'userData', '$modal']; function booksCtrl(booksData, user, $modal) {
这样就能够在这个控制器中使用模态对话框,而后给页面元素增长点击事件
添加事件使用的是ng-click,这是咱们第一次使用这个指令。后面会用来作删除。
<div class="col-md-3"> <div class="userinfo"> <p>{{vm.user.userName}}</p> <a ng-click="vm.popupForm()" class="btn btn-info">新增推荐</a> </div> </div>
在booksCtrl中添加一个方法vm.popupForm。你能够先试验一下
vm.popupForm = function () { alert("添加"); };
但真正在这个地方咱们须要制定templateUrl和控制器
vm.popupForm = function () { var modalInstance = $modal.open({ templateUrl: '/bookModal/bookModal.html', controller: 'bookModalCtrl as vm', }); };
“bookModalCtrl as vm ”是controllerAs方法另一种用法,意思指定自控制器bookModalCtrl也启用controllerAS语法。咱们还须要建立一个bookModalCtrl控制器和bookModal.html模板视图。如今能够想一下,这个视图须要哪些元素,由于是增长一本新书。天然是要包含模型的一些字段,还注意到咱们建立了一个modalInstance的实例。在app_client下建立一个bookModal文件夹,再建立bookModal.html:
<div class="modal-content"> <form id="addReview" name="addReview" role="form" ng-submit="vm.onSubmit()" class="form-horizontal"> <div class="modal-header"> <button type="button" ng-click="vm.modal.cancel()" class="close"><span aria-hidden="true">×</span><span class="sr-only">Close</span></button> <h4 id="myModalLabel" class="modal-title">新增推荐</h4> </div> <div class="modal-body"> <div role="alert" ng-show="vm.formError" class="alert alert-danger">{{vm.formError}}</div> <div class="form-group"> <label for="name" class="col-xs-2 col-sm-2 control-label">书名</label> <div class="col-xs-10 col-sm-10"> <input id="name" name="name" required="required" ng-model="vm.formData.title" class="form-control" /> </div> </div> <div class="form-group"> <label for="info" class="col-xs-2 col-sm-2 control-label">信息</label> <div class="col-xs-10 col-sm-10"> <input id="info" name="info" required="required" ng-model="vm.formData.info" class="form-control" /> </div> </div> <div class="form-group"> <label for="ISBN" class="col-xs-2 col-sm-2 control-label">ISBN</label> <div class="col-xs-10 col-sm-10"> <input id="ISBN" name="ISBN" required="required" ng-model="vm.formData.ISBN" class="form-control" /> </div> </div> <div class="form-group"> <label for="tags" class="col-xs-2 col-sm-2 control-label">标签</label> <div class="col-xs-10 col-sm-10"> <input id="tags" name="tags" required="required" ng-model="vm.formData.tags" class="form-control" /> </div> </div> <div class="form-group"> <label for="rating" class="col-xs-10 col-sm-2 control-label">推荐指数</label> <div class="col-xs-12 col-sm-2"> <select id="rating" required="required" name="rating" ng-model="vm.formData.rating" class="form-control input-sm"> <option>5</option> <option>4</option> <option>3</option> <option>2</option> <option>1</option> </select> </div> </div> <div class="form-group"> <label for="brief" class="col-sm-2 control-label">简介</label> <div class="col-sm-10"> <textarea id="review" name="brief" rows="5" required="required" ng-model="vm.formData.brief" class="form-control"></textarea> </div> </div> </div> <div class="modal-footer"> <button ng-click="vm.modal.cancel()" type="button" class="btn btn-default">取消</button> <button type="submit" class="btn btn-primary">肯定</button> </div> </form> </div>
这个页面元素比较多,但主要部分仍是一个form。表单提交对应的是ng-submit="vm.onSubmit()" 方法,而不像平时咱们使用action。另外模态对话框的关闭是vm.modal.cancel() 方法。这两个方法待会咱们在控制器中实现。而ng-model="vm.formData.info" 相似这样指令的做用就是在form提交的时候会建立一个对象包含这些字段。接下来看控制器:
在bookModal目录下建立bookModal.controller.js ,定义bookModalCtrl:
(function () { angular .module('readApp') .controller('bookModalCtrl', bookModalCtrl); bookModalCtrl.$inject = ['$modalInstance', 'booksData']; function bookModalCtrl($modalInstance, booksData) { var vm = this; vm.onSubmit = function () { console.log(vm.formData); return false; };
vm.modal = { close : function (result) { $modalInstance.close(result); }, cancel : function () { $modalInstance.dismiss('cancel'); } }; } })();
记得加入appClientFiles中。咱们注入了前面建立的modalInstance实例,close和cancel方法调用了其自身方法,而onsubmit先暂时没有提交,能够看一下传输过来的数据。点击按钮效果以下:
若是提交数据,在console里面能够看到:
这说明控制器中已经获取到表单中的数据了。有一个问题是,若是我想给这个模态对话框传递参数,该怎么作,这要用到resolve。修改booksCtrl,咱们把title内容传过去,注意要使用return语法。
var modalInstance = $modal.open({ templateUrl: '/bookModal/bookModal.html', controller: 'bookModalCtrl as vm', resolve : { viewData: function () { return { title: "新增推荐", }; } } });
在这里建立了一个viewData对象,在模态页面调用以下。
<h4 id="myModalLabel" class="modal-title">{{ vm.viewData.title }}</h4>
接下来就是如何把数据提交到控制器呢? 首先咱们须要定义Service,由于尚未添加book的方法,而后能够想到的是,须要验证数据后,而后提交到api,而后再更新视图。
修改booksData,增长两个方法,一个post方式增长,一个delete方法删除。 这些api都是第三节的时候建立的。
booksData.$inject = ['$http']; function booksData($http) { var getBooks = $http.get('/api/books'); var getbookById = function(bookid) { return $http.get('/api/book/' + bookid); }; var addBook = function(data) { return $http.post("/api/book", data); }; var removeBookById = function(bookid) { return $http.delete('/api/book/' + bookid); }; return { getBooks: getBooks, getbookById: getbookById, addBook: addBook, removeBookById: removeBookById }; };
(function () { angular .module('readApp') .controller('bookModalCtrl', bookModalCtrl); bookModalCtrl.$inject = ['$modalInstance', 'viewData','booksData']; function bookModalCtrl($modalInstance, viewData, booksData) { var vm = this; vm.viewData = viewData; vm.onSubmit = function () { vm.formError = ""; if (!vm.formData.title || !vm.formData.rating || !vm.formData.brief || !vm.formData.info || !vm.formData.ISBN) { vm.formError = "请完成全部栏目!"; return false; } else { console.log(vm.formData); vm.doAddBook(vm.formData);
return false; } }; vm.doAddBook = function (formData) { booksData.addBook({ title: formData.title, info: formData.info, ISBN: formData.ISBN, brief: formData.brief, tags: formData.tags, img: formData.img, rating: formData.rating, }).success(function(data) { console.log("success!"); vm.modal.close(data); }).error(function(data) { vm.formError = "添加失败,请再试一次"; }); return false; }; vm.modal = { close : function (result) { $modalInstance.close(result); }, cancel : function () { $modalInstance.dismiss('cancel'); } }; } })();
以上只是简单的验证,只是判断是否为空,若是有为空的就返回。并赋值vm.formError。
<div role="alert" ng-show="vm.formError" class="alert alert-danger">{{vm.formError}}</div>
咱们在页面上使用了ng-show, ng-show后面的表达式为true的时候,内容就会显示。也就是说字段不为空,就会提示出来。
若是数据都不为空,咱们就提交api。成功以后,记得关闭对话框。也就是在success中调用了modal.close 。可是如何更新视图呢?modal的close方法会返回一个promise到父级控制器。所以能够这样处理。
booksCtrl:
vm.popupForm = function () { var modalInstance = $modal.open({ templateUrl: '/bookModal/bookModal.html', controller: 'bookModalCtrl as vm', resolve : { viewData: function () { return { title: "新增推荐", }; } } }); modalInstance.result.then(function (data) { vm.books.push(data); }); };
这个时候添加完数据,页面上面当即更新了。不像之前操做dom的方式,咱们须要手动拼凑html。如今只须要更新模型了。
如今还差一个删除方法,前面咱们已经使用了ng-click指令,一样,咱们修改books.html这个视图
<p> <a href="/book/{{book._id}}">{{book.title}}</a> <span class="close" ng-click="vm.removeBook(book._id)">×</span> </p>
定义了一个removeBook的方法,接下来在后台实现(booksCtrl):
vm.removeBook = function (id) { if (confirm("肯定删除?")) { booksData.removeBookById(id).success(function () { for (var i = 0; i < vm.books.length; i++) { if (vm.books[i]._id == id) { vm.books.splice(vm.books.indexOf(vm.books[i]), 1); } } }); } };
调用removeBookById方法删除数据,成功以后再在视图模型的中用splice方法删除这个对象。下面看一下连贯起来的效果:
源码:https://github.com/stoneniqiu/ReadingClub 注意分支AngularSPA下
小结:这一节篇幅比较长,但Angular构建SPA的套路已经摸清,只是页面交互方面还不是那么熟悉,特别是这个modal组件的使用可能让你以为复杂,由于从一个控制器中还调用了另一个控制器,且对这种Angular-Bootstrap组件还不熟悉。数据的验证也显得有点弱。可是从第五节到这儿,应该是对Angular有些感受了:和jquery直接操做demo的不一样,它是操做视图模型,页面上全部变化的部分均可以经过模型来实现。另外细心的朋友可能发现了,上传图片的部分尚未讲,限于篇幅,这一篇就先到这,后面咱们讲Angular下上传图片,另外还有一个很重要的部分,用户认证以及会话,尽请期待。