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

其他地方不清楚的可以參考頁尾提供的源碼。

四、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)">&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下上傳圖片,另外還有一個很重要的部分,用戶認證以及會話,盡請期待。


免責聲明!

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



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