这个系列一共会涉及两个JavaScript框架的讲解,一个是Express用作后端,一个是Angular用于前端。和Express同样,Angular分离内容,处理视图、数据和逻辑。和MVC模式很类似,但其实Angular定义是MVW框架,W表明(what ever works for you)。意味着它能够是控制器或者视图模型,或者服务,就看你怎么定义的。这一节会介绍基本的Angular知识;而后改造咱们以前作的页面;而且调用以前的定义的api来获取数据。html
Angular的数据绑定是指视图的改变会更新模型,而模型的改变也会更新视图。前端
不像咱们用jquery去绑定dom事件而后改变dom。相似于wpf中的双向绑定。node
1、第一个例子:数据绑定初体验jquery
这样的例子你可能见得多了,但不要着急,咱们按部就班。程序员
<input /> <h1>Hello </h1>
<script src="angular.min.js"></script> 或 <script src="http://apps.bdimg.com/libs/angular.js/1.4.6/angular.min.js"></script>
而后咱们还须要告诉Angular这个页面是一个Angular应用,也就是在html标签修改以下:ajax
<html ng-app>
<input ng-model="myInput" />
接下来再将模型放到咱们要输出的地方,Angular使用{{}}来绑定。以下,这里两个地方的名称必须一致。express
<h1>Hello {{ myInput }}</h1>
剩下就交给Angular去作了,演示:json
这和第一幅图中所表达的意思一致,视图的改变动新了模型,模型的改变又更新了视图。后端
2、设置模型初始值api
感觉了Angular的神奇以后,接下来经过给模型一个初始值,来了解更多Angular的相关知识。要达到这个目的,咱们须要建立一个Angular应用模块(module),一个controller来管理做用域。
<html ng-app="myApp">
module的名称用于标签的ng-app属性。新建一个JavaScript文件,定义以下:
angular.module('myApp', []);
这样就申明了一个Angular应用模块,做用于<html>元素。
有了module以后,就能够定义controller了,控制器是在JavaScript代码中定义,附加在某个的html元素上,表示能在这个元素内部工做。以下,咱们将控制器附加到body上,命名为myController :
<body ng-controller="myController">
再看JavaScript部分:
function myController() { }; angular.module('myApp').controller('myController', myController);
定义了一个‘myController’,添加在‘myApp’这个模块中。接下来咱们经过scope给模型一个初始值。
像JavaScript代码同样,Angular也有做用域,Angular有一个rootScope,相似于JavaScript中的全局做用域,包含整个应用。rootScope包含一个或多个子做用域,例如 ng-controller 指令就会建立一个子做用域。做用域关联着视图、模型和控制器。上面定义的myController方法能够带一个$scope参数,且必须为这个名字。它表明着做用域,Angular已自动建立。经过这个参数能够获取到模型。这样,设置初始值就简单了:
var myController = function ($scope) { $scope.myInput = "Angular!"; };
这种感受就是依赖注入,$scope由$scopeProvider提供。再看下效果:
输入框和h1元素都出现了模型的初始值。
3、页面改造
Angular是运行于客户端的JavaScript文件,咱们须要告诉Express框架,在请求Angular的脚本文件时当成静态文件传送就行,而不须要运行它。Public文件夹已经设置为静态。
app.use(express.static(path.join(__dirname, 'public')));
因此能够在public文件下新建一个angular文件夹,放置Angular脚本文件,并新建一个readApp.js. 在里面定义:
angular.module('readApp', []);
而后在layout.jade里添加文件:
script(src='/angular/angular.min.js')
script(src='/angular/readApp.js')
html(ng-app='readApp')
这样意味着全部页面都支持Angular了。但若是要用Angular展现数据,那么Angular得先拿到数据(再也不使用jade去渲染视图),实现这个有三步
1)拿掉Express中对应的首页控制器中的api的调用。
2)在Angular应用的scope中添加编码。
3)更新视图模板并绑定到Angular数据。
第一步,能够先注释掉index方法中请求api的代码,直接返回视图,给Angular腾出场子。
module.exports.index = function (req, res) { //var requestOptions, path; //path = "/api/topics"; //requestOptions = { // url: apiOptions.server + path, // method: "GET", // json: {}, //} //request(requestOptions, function (err, response, body) { // if (response.statusCode == 200) { // res.render('index', { title: 'Index', topics: body }); // } else { // res.render('error', { message: err.message, error: err }); // } //}); res.render('index', { title: 'Index' }); };
第二步再在readApp.js中增长一个控制器,先用静态数据:
var topics = [ { title: "书山有路第十一期:程序员修炼之道-第八章-注重实效的项目--第二十二天", type: "读书", visitedCount: 80, commentCount: 2, createdOn: '2016/7/05 21:32', author: 'stoneniqiu', img: 'http://upload.jianshu.io/users/upload_avatars/133630/d5370e672fd4.png?imageMogr/thumbnail/90x90/quality/100' }, { title: "《明朝那些事儿》之闲言散语", type: "书评", visitedCount: 180, commentCount: 20, createdOn: '2016/5/15 21:32', author: '卡卡卡萨布兰卡 ', img: 'http://upload.jianshu.io/users/upload_avatars/1675188/2d0810ccc03d.jpg?imageMogr/thumbnail/90x90/quality/100' }, { title: "有《程序员修炼之道》高清版吗?", type: "求书", visitedCount: 90, commentCount: 1, createdOn: '2016/5/15 21:32', author: '吾不知 ', img: 'http://upload.jianshu.io/users/upload_avatars/1125491/3910f3825f73.jpg?imageMogr/thumbnail/90x90/quality/100', }];
var homeController = function($scope) { $scope.data = topics; };
并注册:
angular.module('readApp')
.controller('homeController', homeController)
第三步:更新视图(index.jade)
.col-md-9.page(ng-controller="homeController")
.row.topictype
a.label.label-info(href='/') 所有
a(href='/') 读书
a(href='/') 书评
a(href='/') 求书
a(href='/') 求索
.row.topiclist(ng-repeat='topic in data')
img(ng-src='{{topic.img}}')
span.count
i.coment {{topic.commentCount}}
i /
i {{topic.visitedCount}}
span.label.label-info {{topic.type}}
a(href='/') {{topic.title}}
span.pull-right {{topic.createdOn|formdate}}
a.pull-right.author(href='/') {{topic.author}}
咱们将homeController加载.page这个元素上。在第二步里面,咱们定义了一个Data的模型,实际上是一个数组集合,这里用
(ng-repeat='topic in data')
循环输出。代替了jade的each语法:‘each topic in topics‘,和jade不一样的是,jade的循环语句位于.topiclist的上方,而Angular的repeat指令要添加在你须要重复的元素了。而后还要注意的是,全部的元素内容中的{{}}前的等号要拿掉,换成空格赋值的语法(等号表示变量,空格表示是字符串),否则没法输出。再注意一个是img的src要用ng-src,否则不能输出url。这个时候运行,已经能够看见输出了:
你们可能留意到,上面有这样一段代码:
{{topic.createdOn|formdate}}
属性的后面跟上|号和一个名字,这就叫过滤器(filter),故名思议,经过特定的规则,将源数据转换成须要的数据格式。这里的formdate的定义是:
var formdate = function() { return function(dateStr) { var date = new Date(dateStr); var d = date.getDate(); var monthNames = ["1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12"]; var m = monthNames[date.getMonth()]; var y = date.getFullYear(); var output = y + '-' + m + '-' + d; return output; }; }; angular.module('readApp') .controller('homeController', homeController) .filter('formdate', formdate)
形式上和注册controller是同样,不一样的是,filter须要返回一个函数。其实Angular自带一些数据格式,好比data,currency等。以下
<div>{{50.25|currency }}</div> $50.25 <div>{{50.25|currency:'¥' }}</div> ¥50.25
<div>{{ "Let's shout" | uppercase }}</div> <!-- 输出: LET’S SHOUT --> {{ timestamp | date:"d MMM yyyy" }} <!-- 输出: 21 Aug 2014 -->
指令主要是用来建立html片断,一个html代码片断能够被多个不一样的controller和view使用,这容易保持一致性且容易维护。这些片断运行在Angular应用的上下文中,照样可使用数据绑定,并且浏览器能够缓存这些指令为html文件,当用户在不一样的view来回切换时,这有利于加速应用。接下来演示如何添加一个指令,改造以前显示星星的部分(切换到books.jade)。
先模拟的个数据,在homeController中加入models
$scope.models = [{ rating: 4 }, { rating: 5 }];
而后定义一个ratings指令:
var ratingStars = function () { return { template : "{{book.rating}}", }; };
并注册,
angular.module('readApp') .controller('homeController', homeController) .filter('formdate', formdate) .directive('ratingStars', ratingStars)
在视图上输出。这里有一个注意的地方,html属性是大小写不敏感的,驼峰式的命名须要用转换一下,也就是ratingStars 匹配的是rating-stars,即大写字母转成'-' 加小写字母。
p(ng-repeat='book in models')
small(rating-stars)
页面上会输出4和5.这天然还不能知足咱们的要求。有两点:1属性名限制的太死了,不能老是'book.rating'。2是须要把数字变成星星。第一个问题是一个做用域的问题,须要建立一个做用域变量。
return { scope: { thisRating : '=rating' }, template : "{{ thisRating }}" };
建立了一个thisRating的做用域变量,而'=rating'告诉Angular去匹配带有‘rating'的属性。而后在Angular文件夹下建立一个rating-stars.html。
<span class="glyphicon glyphicon-star{{ thisRating<1 ? '-empty' : ''}}"></span> <span class="glyphicon glyphicon-star{{ thisRating<2 ? '-empty' : ''}}"></span> <span class="glyphicon glyphicon-star{{ thisRating<3 ? '-empty' : ''}}"></span> <span class="glyphicon glyphicon-star{{ thisRating<4 ? '-empty' : ''}}"></span> <span class="glyphicon glyphicon-star{{ thisRating<5 ? '-empty' : ''}}"></span>
而后用templateUrl指向这个片断
var ratingStars = function () { return { scope: { thisRating : '=rating' }, templateUrl: '/angular/rating-stars.html' }; };
在视图上运用:
p(rating-stars, rating=book.rating)
获得星星
指令的这个scope显得不是很方便。最开始用模拟数据是由于 small(rating-stars) 这种写法不识别jade中的循环(each book in books)中的book对象,而'p(rating-stars, rating=book.rating)'又能识别。
service在Angular中应用比较多,大部分的应用逻辑均可以用service来实现,并且能够给多个controller调用。接下来将controller的中数据移到一个service中去,而后让controller调用service。
建立一个方法,命名为topicData,用来返回topic数据。
var topicData = function ($http) { return topics; };
注册service:
.service('topicData', topicData);
使用service:
var homeController = function($scope, topicData) { $scope.data = topicData; };
不要忘记在参数里面加入须要调用的服务名称。到如今完成了一个服务的调用,接下来咱们从api来获取数据。JavaScript发送http请求不是什么新鲜事了,Jquery的ajax,node里面的request模块,而Angular有一个自带的服务:$http,用来处理请求。接下来就用它来请求api。
var topicData = function ($http) { return $http.get('/api/topics'); };
$http有一个get方法,参数就是一个url。这里调用咱们在第三节定义好的api。--> 使用Mongoose建立模型及API
这是一个异步的方法,因此还须要改造Controller中的代码:
var homeController = function ($scope, topicData) { topicData.success(function (data) { console.log(data); $scope.data = data; }).error(function (e) { console.log(e); }); };
这时候运行,能够看到所有的数据了。
异步加载数据的一个问题是用户刚打开页面的时候多是一片空白,因此加一个过渡的内容好一点。
var homeController = function ($scope, topicData) { $scope.message = "loading..."; topicData.success(function (data) { console.log(data); $scope.message = data.length > 0 ? "" : "暂无数据"; $scope.data = data; }).error(function (e) { console.log(e); $scope.message = "Sorry, something's gone wrong "; }); };
页面加一个:
.error {{ message }}
这样避免出现一个短期的空白。
小结:这一节咱们体验了Angular的数据绑定模式,并学习了如何定义并使用module、Controller、directive、filter和service。简单了改造了首页,将Express的一部分工做转移到了前端。其实涉及到Angular的每个部分都不够深刻,只是按需分配,用到多少就讲多少。我以为这样按部就班的比较好。下一节将介绍用Angular作一个单页应用(SPA),讲解Angular路由等知识。