Nodejs之MEAN栈开发(七)---- 用Angular创建单页应用(下)
上一节我们走通了基本的SPA基础结构,这一节会更彻底的将后端的视图、路由、控制器全部移到前端。篇幅比较长,主要分页面改造、使用AngularUI两大部分以及一些优化路由、使用Angular的其他指令的学习。篇幅虽然长,但熟悉了就是这个套路,特别是第一部分。重点是理解Angular这种操作数据而不是操作Dom的编程方式。
一、移除服务端依赖
上一节中我们还保留了基于jade的layout。为此还保留一个Express的控制器。这一节我们全部在客户端(app_client)实现。先在app_client目录下创建一个index.html(等于是layout.jade生成的页面)
<!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,或者别的什么你喜欢的名字都可以。
1.footerNav
先在directive目录下创建一个footer文件夹,创建一个footer.html,把index.html中的footer部分拿过来。
<footer class="container">
<div class="row">
<div class="col-xs-12"><small>© stoneniqiu 2016</small></div>
</div>
</footer>
然后在同目录下创建一个footer.js,创建指令为footerNav
(function () {
angular
.module('readApp')
.directive('footerNav', footerNav); function footerNav() {
return {
restrict: 'EA',
templateUrl: '/common/directive/footer/footer.html'
};
}
})();
别忘记添加进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'
];
使用:
<footer-nav></footer-nav>
会生成:
这样就完成了footer部分的改造。
2.navigation
同理对于导航条,也是上面的几个步骤,这里就不赘述了。
使用的时候调用,这样就很方便了。
<navigation></navigation>
这样让每个文件只做一件事,以后需要使用某个组件可以直接拿过去。
3.home.view.html
现在我们可以修改下之前定义的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返回了需要的资源文件。
二、路由优化
在上一节使用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及之前版本的问题上花时间。所以这一点要注意到。接下来改造更多的页面
三、页面改造
前四章我们用jade模板制作了几个页面,目前只改造了index.html。接下来继续Angular化。
1.about.html
在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。这个时候访问页面:
2.angular-sanitize
如果我的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也就多个符号。
3.books.html
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'
})
这个时候页面出来了。一见如故:
4.bookDetail.html
还需要增加一个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;
}
})();
其他地方不清楚的可以参考页尾提供的源码。
四、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 接下来的部分有点复杂,各位看官请耐心...
1.先在index.html下引用:
<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>
2.控制器上添加依赖
在booksCtrl上添加modal依赖:
booksCtrl.$inject = ['booksData', 'userData', '$modal'];
function booksCtrl(booksData, user, $modal) {
这样就可以在这个控制器中使用模态对话框,然后给页面元素增加点击事件
3.添加事件
添加事件使用的是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>
4.实现popupForm
在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,然后再更新视图。
5.addBook
修改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
};
};
6.验证与提交数据
(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下上传图片,另外还有一个很重要的部分,用户认证以及会话,尽请期待。
Nodejs之MEAN栈开发(七)---- 用Angular创建单页应用(下)的更多相关文章
- Nodejs之MEAN栈开发(六)---- 用Angular创建单页应用(上)
在上一节中我们学会了如何在页面中添加一个组件以及一些基本的Angular知识,而这一节将用Angular来创建一个单页应用(SPA).这意味着,取代我们之前用Express在服务端运行整个网站逻辑的方 ...
- Nodejs之MEAN栈开发(九)---- 用户评论的增加/删除/修改
由于工作中做实时通信的项目,需要用到Nodejs做通讯转接功能,刚开始接触,很多都不懂,于是我和同事就准备去学习nodejs,结合nodejs之MEAN栈实战书籍<Getting.MEAN.wi ...
- Nodejs之MEAN栈开发(三)---- 使用Mongoose创建模型及API
继续开扒我们的MEAN栈开发之路,前面两节我们学习了Express.Jade引擎并创建了几个静态页面,最后通过Heroku部署了应用. Nodejs之MEAN栈开发(一)---- 路由与控制器 Nod ...
- 七天学会ASP.NET MVC(七)——创建单页应用
系列文章 七天学会ASP.NET MVC (一)——深入理解ASP.NET MVC 七天学会ASP.NET MVC (二)——ASP.NET MVC 数据传递 七天学会ASP.NET MVC (三)— ...
- 七天学会ASP.NET MVC(七)——创建单页应用 【转】
http://www.cnblogs.com/powertoolsteam/p/MVC_Seven.html 系列文章 七天学会ASP.NET MVC (一)——深入理解ASP.NET MVC 七天学 ...
- Nodejs之MEAN栈开发(五)---- Angular入门与页面改造
这个系列一共会涉及两个JavaScript框架的讲解,一个是Express用做后端,一个是Angular用于前端.和Express一样,Angular分离内容,处理视图.数据和逻辑.和MVC模式很相似 ...
- Nodejs之MEAN栈开发(八)---- 用户认证与会话管理详解
用户认证与会话管理基本上是每个网站必备的一个功能.在Asp.net下做的比较多,大体的思路都是先根据用户提供的用户名和密码到数据库找到用户信息,然后校验,校验成功之后记住用户的姓名和相关信息,这个信息 ...
- Nodejs之MEAN栈开发(二)----视图与模型
上一节做了对Express做了简单的介绍,提出了controller,介绍了路由.这一节将重点放到视图和模型上,完成几个静态页面并部署到heroku上. 导航 前端布局使用bootstrap,从官网下 ...
- IOS基础学习日志(七)利用dispatch_once创建单例及使用
自苹果引入了Grand Central Dispatch (GCD)(Mac OS 10.6和iOS4.0)后,创建单例又有了新的方法,那就是使用dispatch_once函数,当然,随着演进的进行. ...
随机推荐
- 谈谈一些有趣的CSS题目(三)-- 层叠顺序与堆栈上下文知多少
开本系列,讨论一些有趣的 CSS 题目,抛开实用性而言,一些题目为了拓宽一下解决问题的思路,此外,涉及一些容易忽视的 CSS 细节. 解题不考虑兼容性,题目天马行空,想到什么说什么,如果解题中有你感觉 ...
- C#基础篇 - 正则表达式入门
1.基本概念 正则表达式(Regular Expression)就是用事先定义好的一些特定字符(元字符)或普通字符.及这些字符的组合,组成一个“规则字符串”,这个“规则字符串”用来判断我们给定的字符串 ...
- iOS开发之再探多线程编程:Grand Central Dispatch详解
Swift3.0相关代码已在github上更新.之前关于iOS开发多线程的内容发布过一篇博客,其中介绍了NSThread.操作队列以及GCD,介绍的不够深入.今天就以GCD为主题来全面的总结一下GCD ...
- ASP.NET Core 中文文档 第四章 MVC(4.6)Areas(区域)
原文:Areas 作者:Dhananjay Kumar 和 Rick Anderson 翻译:耿晓亮(Blue) 校对:许登洋(Seay) Areas 是 ASP.NET MVC 用来将相关功能组织成 ...
- 介绍一款原创的四则运算算式生成器:CalculateIt2
家里小朋友读一年级了,最近每天都有一些10以内的加减法口算练习,作为程序员爸爸,自然也是想办法能够偷懒,让电脑出题,给小朋友做些练习.于是,自己在业余时间开发了一个四则运算算式生成器,名为:Calcu ...
- VS2015使用scanf报错的解决方案
1.在程序最前面加: #define _CRT_SECURE_NO_DEPRECATE 2.在程序最前面加: #pragma warning(disable:4996) 3.把scanf改为scanf ...
- python 3.5 成功安装 scrapy 的步骤
http://www.cnblogs.com/hhh5460/p/5814275.html
- [原创]关于Hibernate中的级联操作以及懒加载
Hibernate: 级联操作 一.简单的介绍 cascade和inverse (Employee – Department) Casade用来说明当对主对象进行某种操作时是否对其关联的从对象也作类似 ...
- 如何用Java类配置Spring MVC(不通过web.xml和XML方式)
DispatcherServlet是Spring MVC的核心,按照传统方式, 需要把它配置到web.xml中. 我个人比较不喜欢XML配置方式, XML看起来太累, 冗长繁琐. 还好借助于Servl ...
- JavaScript学习笔记(一)——延迟对象、跨域、模板引擎、弹出层、AJAX示例
一.AJAX示例 AJAX全称为“Asynchronous JavaScript And XML”(异步JavaScript和XML) 是指一种创建交互式网页应用的开发技术.改善用户体验,实现无刷新效 ...