AngularJS的增刪改查、state嵌套案例,不涉及服務端

jopen 8年前發布 | 34K 次閱讀 Web框架 angularjs


本篇實踐一個案例,大致是:左邊有導航菜單,右邊顯示列表,并可對列表項編輯或刪除,也可添加新的列表項。借此,可體會到:如何組織可擴展的AngualrJS文件結構,如何點擊左側菜單項右側顯示相應內容,angular-ui-router的使用以及嵌套state,增刪改查,等等。

大致如下:

AngularJS的增刪改查、state嵌套案例,不涉及服務端

當點擊添加按鈕:

AngularJS的增刪改查、state嵌套案例,不涉及服務端

當點擊更新按鈕:

AngularJS的增刪改查、state嵌套案例,不涉及服務端

文件結構

node_modules/

src/

.....app/

..........categories/  

...............categories.js  <包含一個名稱為categories的module>

...............categories.tmpl.html <左側的導航菜單>

...............bookmarks/

....................bookmark.js <包含一個名稱為categories.bookmarks的module>

....................bookmarks.tmpl.html <右側的列表項>

....................create/

.........................bookmark-create.js <包含一個名稱為categories.bookmarks.create的module>

.........................bookmark-create.tmpl.html <添加表單>

....................edit/

.........................bookmark-edit.js <包含一個名稱為categories.bookmarks.edit>

.........................bookmark-edit.tmpl.html <更新表單>

..........common/

...............models/

....................bookmarks-model.js <包含一個名稱為darren.models.bookmarks的module>

....................categoires-model.js <包含一個名稱為darren.models.categories的module>

..........app.js <包含一個名稱為darren的module>

.....asserts/

..........css/

...............animation.css <有關state對應視圖間切換的動畫效果>

...............darren.css <項目css>

...............normalize.css

..........img/

...............logo.png

.....data/

..........bookmarks.json <右側列表的json數據>

..........categories.json <左側菜單的json數據>

.....vendor/

..........angular-ui-router.min.js

..........lodash.min.js

index.html

modules

modules之間的相互依賴如圖:

AngularJS的增刪改查、state嵌套案例,不涉及服務端

萬劍歸一,所有的module都和名稱為Darren的主module產生了聯系。

index.html

<!--引用的css-->

<link rel="stylesheet" href="assets/css/normalize.css"/>
<link rel="stylesheet" href="../node_modules/bootstrap/dist/css/bootstrap.min.css"/>
<link rel="stylesheet" href="assets/css/eggly.css"/>
<link rel="stylesheet" href="assets/css/animations.css"/>

<!--左側菜單->
<div ui-view="categories"></div>

<!--右側列表-->
<div ui-view="bookmarks"></div>

<!--引用的js-->

<script src="../node_modules/jquery/dist/jquery.min.js"></script>
<script src="../node_modules/bootstrap/dist/js/bootstrap.min.js"></script>
<script src="vendor/lodash.min.js"></script>
<script src="../node_modules/angular/angular.min.js"></script>
<script src="../node_modules/angular-animate/angular-animate.min.js"></script>
<script src="vendor/angular-ui-router.min.js"></script>

<!--以下引用js的順序和module之間的依賴關系基本一致-->
<script src="app/app.js"></script>
<script src="app/categories/categories.js"></script>
<script src="app/categories/bookmarks/bookmark.js"></script>
<script src="app/categories/bookmarks/create/bookmark-create.js"></script>
<script src="app/categories/bookmarks/edit/bookmark-edit.js"></script>
<script src="app/common/models/bookmarks-model.js"></script>
<script src="app/common/models/categories-model.js"></script>

以上,<div ui-view="categories"></div>和<div ui-view="bookmarks"></div>的數據從某個state中來,再具體定義state之前,首先先定義一個抽象的state,是在app.js中定義的。

app.js

名稱為Darren的主module就定義在這里,并在module中定義了一個抽象sate,以便state的繼承和嵌套。

angular.module('Darren', ['ngAnimate',
  'ui.router',
  'categories',
  'categories.bookmarks'
])
    .config(function($stateProvider, $urlRouterProvider){

      //默認情況下導航到首頁
      $stateProvider
          .state('darren',{
            url: '',
            abstract: true
          });
      $urlRouterProvider.otherwise('/');
    })
;

AngularJS的增刪改查、state嵌套案例,不涉及服務端

現在,應該到具體的state了,把index.html中<div ui-view="categories"></div>和<div ui-view="bookmarks"></div>的數據顯示出來,但我們還沒有看過數據源呢,本項目的數據源是以json形式存在的。

數據源

categories.json

[

{"id": 0, "name": ""},

{"id": 1, "name": ""},

{"id": 2, "name": ""},

{"id": 3, "name": ""}

]

bookmarks.json

[

{"id":0, "title": "", "url": "", "category": "" },

{"id":1, "": "", "url": "", "category": "" },

...

]

接下來就是對這些數據的操作,所有針對category和bookmark的操作都放在了service中。

categories-model.js

這里定義了針對category的service。

angular.module('darren.models.categories',[])
    .service('CategoriesModel', function($http, $q){

        var model=this,
            URLS={
                FETCH:'data/categories.json'
            },
            categories,
            currentCategory;

        model.getCategoires = function(){

            //如果categories存在,使用$q返回promise
            //如果不存在才去提取數據
            return (categories) ? $q.when(categories) : $http.get(URLS.FETCH).then(cacheCategories);
        }

        function extract(result){
            return result.data;
        }

        function cacheCategories(result){
            categories = extract(result);
            return categories;
        }

        model.getCategoryByName = function(categoryName){
            var defered = $q.defer();

            function findCategory(){
                return _.find(categories, function(c){
                   return c.name == categoryName;
                });
            }

            if(categories){
                defered.resolve(findCategory());
            } else{
                model.getCategoires()
                    .then(function(result){
                        defered.resolve(findCategory());
                    });
            }

            return defered.promise;
        };

        model.setCurrentCategory = function(categoryName){
            return model.getCategoryByName(categoryName)
                .then(function(category){
                    currentCategory = category;
                });
        };

        model.getCurrentCategory = function(){
            return currentCategory;
        }

        model.getCurrentCategoryName = function(){
          return currentCategory ? currentCategory.name : '';
        };

    })
;

以上,

● 對外提供了getCategories方法

如果categories存在,通過$q.when返回promise,如果categories不存在,通過或$http.get結合then返回一個promoise。

● 對外提供了getCategoryByName方法,根據categoryName獲取category

通過$q.defer返回一個deferred對象,通過defered.promise返回promise。其中定義了一個內部函數findCategory,通過lodash的find方法獲取到要找的category,如果categories存在,就使用deferred.reslove當前category, 如果categories不存在,就調用getCategories方法獲取到categories后在defered.resolve。

● 對外提供了setCurrentCategory方法

把獲取到的category賦值給了一個currentCategory變量。當點擊界面上左側菜單項的時候需要調用這個方法。

● 對外提供了getCurrentCategory方法

實際是返回currentCategory這個變量值。

● 對外getCurrentCategoryName方法

如果currentCategory變量值為null,就返回空字符串,如果不是null,那就返回currentCategory的name字段值。

接下來,就是針對bookmark的servcie。

bookmarks-model.js

angular.module('darren.models.bookmarks',[])
    .service('BookmarksModel', function($http, $q){
        var model = this,
            bookmarks,
            URL={
                FETCH: 'data/bookmarks.json'
            };

        //獲取
        model.getBookmarks = function(){

            var deferred = $q.defer();

            if(bookmarks){
                deferred.resolve(bookmarks);
            } else{
                $http.get(URL.FETCH).then(function(bookmarks){
                    deferred.resolve(cacheBookmarks(bookmarks));
                });
            }

            return deferred.promise;
        }

        function extract(result){
            return result.data;
        }

        function cacheBookmarks(result){
            bookmarks = extract(result);
            return bookmarks;
        }

        //創建
        model.createBookmark = function(bookmark){
            bookmark.id = bookmarks.length;
            bookmarks.push(bookmark);
        }

        //根據編號查找
        function findBookmark(bookmarkId){
            return _.find(bookmarks, function(bookmark){
               return bookmark.id === parseInt(bookmarkId, 10);
            });
        }

        model.getBookmarkById = function(bookmarkId){
            //創建defered object
            var deferred = $q.defer();

            if(bookmarks){
                deferred.resolve(findBookmark(bookmarkId));
            } else{
                model.getBookmarks().then(function(){
                    deferred.resolve(findBookmark(bookmarkId));
                });
            }

            return deferred.promise;
        }

        //更新
        model.updateBookmark = function(bookmark){
            var index = _.findIndex(bookmarks, function(b){
                return b.id == bookmark.id;
            });

            bookmarks[index] = bookmark;
        }

        //刪除
        model.deleteBookmark = function(bookmark){
            _.remove(bookmarks, function(item){
                return item.id == bookmark.id;
            });
        }
    })
;

以上,

● 對外提供getBookmarks方法

也是返回以promise類型的bookmarks。

● 對外提供createBookmark方法,接受bookmark形參

就是給bookmark賦一個id值,然后放到當前的bookmarks數組中。

● 對外提供getBookmarkById方法,根據bookmarkId形參獲取

返回一個promise的bookmark。

● 對外提供updateBookmark,接受bookmark形參

使用lodash的findIndex方法獲取當前bookmark在數組中的索引值,再根據這個索引值為對應的數組元素賦上新值。

● 對外提供deleteBookmark方法,接受bookmark形參

使用lodash的remove方法刪除數組元素。

好了,數據源以及操作數據的各種方法都具備了,現在就到了路由,控制器等了。

category.js

還記得首頁<div ui-view="categories"></div>和<div ui-view="bookmarks"></div>中的數據還沒著落嗎?其實是在這里定義的。

angular.module('categories',[
  'darren.models.categories'
])
  .config(function($stateProvider){
      $stateProvider
          .state('darren.categories',{
            url: '/',
            views: {
                //target the ui-view named categories in root state
              'categories@':{
                controller: 'CategoriesCtrl as categoriesListCtrl',
                templateUrl: 'app/categories/categories.tmpl.html'
              },
                //target the ui-view named 'bookmarks' in root state to show all bookmarks for all categories
                'bookmarks@':{
                    controller: 'BookmarksCtrl as bookmarksListCtrl',
                    templateUrl: 'app/categories/bookmarks/bookmarks.tmpl.html'
                }
            }
          });
    })
  .controller('CategoriesCtrl', function CategoriesCtrl(CategoriesModel){
      var categoriesListCtrl = this;

        CategoriesModel.getCategoires()
            .then(function(result){
                categoriesListCtrl.categories = result;
            });

    })
;

以上,

● 定義了一個名稱為darren.categories的state

url: '/'表示首頁url路徑,views字段中定義了兩組controller和templateUrl的配對。categories@對應首頁視圖中的<div ui-view="categories"></div>,bookmarks@對應首頁視圖中的<div ui-view="bookmarks"></div>。

在 controller: 'CategoriesCtrl as categoriesListCtrl'中,CategoriesCtrl這個控制器有了別名,也就意味著如果在界面中調用CategoriesCtrl就使用它的別名categoriesListCtrl。

現在,有關state的關系如下:

AngularJS的增刪改查、state嵌套案例,不涉及服務端

當在首頁,即路徑是/,首先經過類型為abstract名稱為darren的state,然后來到darren.categories(這里的書寫格式符合慣例:上一級.下一級)這個state,頁面上的<div ui-view="categories"></div>找到這里的categories@字段,就讓categorieslistCtrl負責為categories.tmpl.html這個視圖提供數據。

<div ui-view="bookmarks"></div>找到這里的bookmarks@字段,讓bookmarksListCtrl和bookmarks.tmpl.html配對,可這里的問題是:bookmarksListCtrl并不在categories這個module中,到底是去哪里找bookmarksListCtrl了?

● 定義了控制器,對外通過了categores這個字段

在控制器中注入了CategoriesModel這個service,通過該service獲取到數據。

categories.tmpl.html

這里即是首頁左側菜單。

<ul>
  <!--判斷是否為選中的category,邏輯是點擊當前的category,把這個category保存在$scope.currentCategory中,再判斷當前的category是否和$scope.currentCategory相等,相等就為選中狀態。就像非常勿擾女嘉賓按燈,系統就記錄下當前按燈的桌號,系統再遍歷所有的桌號,如果桌號和系統記錄的桌號相等,就讓該桌號的燈亮起來,道理是一樣一樣滴。-->
  <li ng-repeat="category in categoriesListCtrl.categories">
    <a ui-sref="darren.categories.bookmarks({category:category.name})">
      {{category.name}}
    </a>
  </li>
</ul>

以上,好玩的是ui-sref="darren.categories.bookmarks({category:category.name}),也就是說,當點擊左側的某個菜單項,就指向darren.categories.bookmarks這個state,并帶著參數category,之所以把category的name鍵值傳遞出去,是因為在右側列表項要顯示點擊了哪個類別。

darren.categories.bookmarks這個state被定義在了bookmark.js中了。

bookmark.js

angular.module('categories.bookmarks',[
  'categories.bookmarks.create',
  'categories.bookmarks.edit',
  'darren.models.bookmarks'
])
    .config(function($stateProvider){
      $stateProvider
          .state('darren.categories.bookmarks',{
            url: 'categories/:category',
            views: {
              'bookmarks@':{
                templateUrl: 'app/categories/bookmarks/bookmarks.tmpl.html',
                controller:'BookmarksCtrl as bookmarksListCtrl'
              }
            }
          });
    })

    //不需要$scope的寫法
    .controller('BookmarksCtrl', function($stateParams,BookmarksModel,CategoriesModel){
        var bookmarksListCtrl = this;

        CategoriesModel.setCurrentCategory($stateParams.category);

        BookmarksModel.getBookmarks()
            .then(function(bookmarks){
                bookmarksListCtrl.bookmarks = bookmarks;
            });

        bookmarksListCtrl.getCurrentCategory = CategoriesModel.getCurrentCategory;
        bookmarksListCtrl.getCurrentCategoryName = CategoriesModel.getCurrentCategoryName;
        bookmarksListCtrl.deleteBookmark = BookmarksModel.deleteBookmark;

    })
;

以上,在categories.tmpl.html中點擊菜單項(ui-sref="darren.categories.bookmarks({category:category.name}))傳遞出的category是和這里的categories/:category對應。

AngularJS的增刪改查、state嵌套案例,不涉及服務端

具體來說,當點擊菜單項(ui-sref="darren.categories.bookmarks({category:category.name})),來到darren.categories.bookmarks這個state,且url符合categories/:category格式,stateParams把url中的category存儲下來,首頁上的<div ui-view="bookmarks"></div>找到bookmarks@字段,最終把bookmarksListCtrl和bookmarks.tmpl.html匹配。

另外,在category.js中,還提到了這樣的一個問題:bookmarksListCtrl并不在categories這個module中,到底是去哪里找bookmarksListCtrl了?在這里也找到了答案,當在categories這個module中沒有bookmarksListCtrl的時候,會按照darren→darren.categores→darren.categores.bookmarks整條線路找到bookmarks$對應的controller和view的匹配。

bookmarks.tmpl.html

<!--不使用$scope的寫法,給controller別名的寫法-->
<h1>{{bookmarksListCtrl.getCurrentCategoryName()}}</h1>

<!--對bookmarks進行了過濾,過濾的標準是bookmark的字段category-->
<div ng-repeat="bookmark in bookmarksListCtrl.bookmarks | filter:{category:bookmarksListCtrl.getCurrentCategoryName()}">
  <button type="button" class="close" ng-click="bookmarksListCtrl.deleteBookmark(bookmark)">×</button>
  <button type="button" class="btn btn-link" ui-sref="darren.categories.bookmarks.edit({bookmarkId: bookmark.id})">更新</button>
  <a href="{{bookmark.url}}" target="_blank">{{bookmark.title}}</a>
</div>
<hr/>
<!-- CREATING -->
<ui-view ng-if="bookmarksListCtrl.getCurrentCategory()">
  <button type="button"  ui-sref="darren.categories.bookmarks.create">添加</button>
</ui-view>

以上,

點擊更新按鈕,ui-sref="darren.categories.bookmarks.edit({bookmarkId: bookmark.id})",來到了darren.categories.bookmarks.edit這個state,并帶上了bookmarkId這個參數,這個state被定義在了bookmark-edit.js中了。

點擊添加按鈕。ui-sref="darren.categories.bookmarks.create",來到darren.categories.bookmarks.create這個state,這個state被定義在了bookmark-create.js中了。

即<ui-view></ui-view>中顯示的內容有可能是添加的內容,也有可能是更新的內容。

bookmark-edit.js

angular.module('categories.bookmarks.edit',[])
    .config(function($stateProvider){
        $stateProvider
            .state('darren.categories.bookmarks.edit',{
                url: '/bookmarks/:bookmarkId/edit',
                templateUrl: 'app/categories/bookmarks/edit/bookmark-edit.tmpl.html',
                controller: 'EditBookmarkCtrl as editBookmarkCtrl'
            });
    })
    .controller('EditBookmarkCtrl', function($state, $stateParams, BookmarksModel){
        var editBookmarkCtrl = this;

        //更新成功或取消更新
        function returnToBookmarks(){
            $state.go('darren.categories.bookmarks',{
                category: $stateParams.category
            });
        }

        function cancelEditing(){
            returnToBookmarks();
        }

        //editBookmarkCtrl.bookmark
        //editBookmarkCtrl.editedBookmark
        BookmarksModel.getBookmarkById($stateParams.bookmarkId)
            .then(function(bookmark){
                if(bookmark){
                    editBookmarkCtrl.bookmark = bookmark;
                    editBookmarkCtrl.editedBookmark = angular.copy(editBookmarkCtrl.bookmark);
                } else {
                    returnToBookmarks();
                }
            });

        //更新
        function updateBookmark(){
            editBookmarkCtrl.bookmark = angular.copy(editBookmarkCtrl.editedBookmark);
            BookmarksModel.updateBookmark(editBookmarkCtrl.bookmark);
            returnToBookmarks();
        }

        editBookmarkCtrl.cancelEditing = cancelEditing;
        editBookmarkCtrl.updateBookmark = updateBookmark;
    })
;

以上,點擊更新按鈕,ui-sref="darren.categories.bookmarks.edit({bookmarkId: bookmark.id})"中的bookmarkId被url: '/bookmarks/:bookmarkId/edit'接受。state之間的關系現在變成這樣:

AngularJS的增刪改查、state嵌套案例,不涉及服務端

具體來說,點擊更新按鈕,ui-sref="darren.categories.bookmarks.edit({bookmarkId: bookmark.id})"來到darren.categories.bookmarks.edit這個state,其中的bookmarkId被$stateParams.bookmarkId接受,配對editBookmarkCtrl和bookmark-edit.tmpl.html,bookmark-edit.tmpl.html的內容顯示在bookmarks.tmpl.html中的<ui-view></ui-view>里。

bookmark.edit.tmpl.html

<h4>Editing {{editBookmarkCtrl.bookmark.title}}</h4>
<form class="edit-form" role="form" ng-submit="editBookmarkCtrl.updateBookmark(editBookmarkCtrl.editedBookmark)" novalidate>
  <div class="form-group">
    <label>Bookmark Title</label>
    <input type="text" class="form-control" ng-model="editBookmarkCtrl.editedBookmark.title" placeholder="Enter title">
  </div>
  <div class="form-group">
    <label>Bookmark URL</label>
    <input type="text" class="form-control" ng-model="editBookmarkCtrl.editedBookmark.url" placeholder="Enter URL">
  </div>
  <button type="submit" class="btn btn-info btn-lg">Save</button>
  <button type="button" class="btn btn-default btn-lg pull-right" ng-click="editBookmarkCtrl.cancelEditing()">Cancel</button>
</form>

bookmark-create.js

點擊添加按鈕。ui-sref="darren.categories.bookmarks.create",這里的darren.categories.bookmarks.create定義在了bookmark-create.js中。

angular.module('categories.bookmarks.create',[])

    .config(function($stateProvider){
        $stateProvider
            .state('darren.categories.bookmarks.create',{
                url: '/bookmarks/create',
                templateUrl: 'app/categories/bookmarks/create/bookmark-create.tmpl.html',
                controller: 'CreateBookmarkCtrl as createBookmarkCtrl'
            });
    })
    .controller('CreateBookmarkCtrl', function($state, $stateParams, BookmarksModel){
        var createBookmarkCtrl = this;

        //添加或取消完成后執行
        function returnToBookmarks(){
            $state.go('darren.categories.bookmarks',{
                category: $stateParams.category
            });
        }

        //取消
        function cancelCreating(){
            returnToBookmarks();
        }

        //添加
        function createBookmark(bookmark){
            BookmarksModel.createBookmark(bookmark);
            returnToBookmarks();
        }

        createBookmarkCtrl.cancelCreating = cancelCreating;
        createBookmarkCtrl.createBookmark = createBookmark;

        //重置表單
        function resetForm(){
            createBookmarkCtrl.newBookmark = {
                title: '',
                url: '',
                category: $stateParams.category
            };
        }

        resetForm();
    })
;

state之間的關系現在變成這樣:

AngularJS的增刪改查、state嵌套案例,不涉及服務端

具體來說,點擊添加按鈕。ui-sref="darren.categories.bookmarks.create",來到darren.categories.bookmarks.create這個state,由于url和url: '/bookmarks/create'格式一致,配對createBookmarkCtrl和bookmark-create.tmpl.html,上級state中的$stateParams.category被這里運用到。

bookmark-create.tmpl.html

<form class="create-form" role="form" ng-submit="createBookmarkCtrl.createBookmark(createBookmarkCtrl.newBookmark)" novalidate>
  <div class="form-group">
    <label for="newBookmarkTitle">Bookmark Title</label>
    <input type="text" class="form-control" id="newBookmarkTitle" ng-model="createBookmarkCtrl.newBookmark.title" placeholder="Enter title">
  </div>
  <div class="form-group">
    <label for="newBookmarkURL">Bookmark URL</label>
    <input type="text" class="form-control" id="newBookmarkURL" ng-model="createBookmarkCtrl.newBookmark.url" placeholder="Enter URL">
  </div>
  <button type="submit" class="btn btn-info btn-lg">Create</button>
  <button type="button" class="btn btn-default btn-lg pull-right" ng-click="createBookmarkCtrl.cancelCreating()">Cancel</button>
</form>

結束。

 本文由用戶 jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!