Nodejs之MEAN棧開發(五)---- Angular入門與頁面改造


      這個系列一共會涉及兩個JavaScript框架的講解,一個是Express用做后端,一個是Angular用於前端。和Express一樣,Angular分離內容,處理視圖、數據和邏輯。和MVC模式很相似,但其實Angular定義是MVW框架,W代表(what ever works for you)。意味着它可以是控制器或者視圖模型,或者服務,就看你怎么定義的。這一節會介紹基本的Angular知識;然后改造我們之前做的頁面;並且調用之前的定義的api來獲取數據。

      Angular的數據綁定是指視圖的改變會更新模型,而模型的改變也會更新視圖。

 

不像我們用jquery去綁定dom事件然后改變dom。類似於wpf中的雙向綁定。

一、第一個例子:數據綁定初體驗

 這樣的例子你可能見得多了,但不要着急,我們循序漸進。

<input />
<h1>Hello </h1>
如何讓input中的輸入立刻顯示在hello的后面?如果是JavaScript或者jquery自然是綁定按鍵事件,然而Angular都不需要再編寫JavaScript代碼。先引入
<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標簽修改如下:

<html ng-app>
ng-app這個屬性其實可以加到任意元素上,這個屬性標記了Angular的作用范圍。添加在html上,意味着這整個頁面Angular都能工作。接下來 我們將input綁定到一個模型:myinput,
<input ng-model="myInput" />

接下來再將模型放到我們要輸出的地方,Angular使用{{}}來綁定。如下,這里兩個地方的名稱必須一致。

<h1>Hello {{ myInput }}</h1>

剩下就交給Angular去做了,演示:

 這和第一幅圖中所表達的意思一致,視圖的改變更新了模型,模型的改變又更新了視圖。

二、設置模型初始值

感受了Angular的神奇之后,接下來通過給模型一個初始值,來了解更多Angular的相關知識。要達到這個目的,我們需要創建一個Angular應用模塊(module),一個controller來管理作用域。 

1.module

<html ng-app="myApp">

module的名稱用於標簽的ng-app屬性。新建一個JavaScript文件,定義如下:

angular.module('myApp', []);

這樣就申明了一個Angular應用模塊,作用於<html>元素。

2.controller

有了module之后,就可以定義controller了,控制器是在JavaScript代碼中定義,附加在某個的html元素上,表示能在這個元素內部工作。如下,我們將控制器附加到body上,命名為myController :

<body ng-controller="myController">

再看JavaScript部分:

function myController() {
    
};
angular.module('myApp').controller('myController', myController);

定義了一個‘myController’,添加在‘myApp’這個模塊中。接下來我們通過scope給模型一個初始值。

3.scope

像JavaScript代碼一樣,Angular也有作用域,Angular有一個rootScope,類似於JavaScript中的全局作用域,包含整個應用。rootScope包含一個或多個子作用域,例如 ng-controller 指令就會創建一個子作用域。作用域關聯着視圖、模型和控制器。上面定義的myController方法可以帶一個$scope參數,且必須為這個名字。它代表着作用域,Angular已自動創建。通過這個參數可以獲取到模型。這樣,設置初始值就簡單了:

var myController = function ($scope) {
    $scope.myInput = "Angular!";
};

這種感覺就是依賴注入,$scope由$scopeProvider提供。再看下效果:

輸入框和h1元素都出現了模型的初始值。

三、頁面改造

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',
    }];
View Code
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。這個時候運行,已經可以看見輸出了:

4.filter

大家可能留意到,上面有這樣一段代碼:

 {{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 -->

5.directive

指令主要是用來創建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)'又能識別。 

6.service

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路由等知識。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM