转载

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

本篇实践一个案例,大致是:左边有导航菜单,右边显示列表,并可对列表项编辑或删除,也可添加新的列表项。借此,可体会到:如何组织可扩展的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> 

结束。☺

正文到此结束
Loading...