Nodejs之MEAN棧開發(七)---- 用Angular建立單頁應用(下)

上一節咱們走通了基本的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

1.footerNav

先在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部分的改造。

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返回了須要的資源文件。

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化。 

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}}">&times;</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;
    }
})();

其餘地方不清楚的能夠參考頁尾提供的源碼。

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  接下來的部分有點複雜,各位看官請耐心...

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。如今只須要更新模型了。

5、刪除

如今還差一個刪除方法,前面咱們已經使用了ng-click指令,一樣,咱們修改books.html這個視圖

 <p>
     <a href="/book/{{book._id}}">{{book.title}}</a>
      <span class="close" ng-click="vm.removeBook(book._id)">&times;</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下上傳圖片,另外還有一個很重要的部分,用戶認證以及會話,盡請期待。

相關文章
相關標籤/搜索