這個系列一共會涉及兩個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路由等知識。