出处不明

我们知道,AngularJS并没有自带立等可用的数据建模方案。而是以相当抽象的方式,让我们在controller中使用JSON数据作为模 型。但是随着时间的推移和项目的成长,我意识到这种建模的方式不再能满足我们项目的需求。在这篇文章中我会介绍在我的AngularJS应用中处理数据建 模的方式。

为Controller定义模型

让我们从一个简单的例子开始。我想要显示一个书本(book)的页面。下面是控制器(Controller):

BookController

  1. app.controller('BookController', ['$scope', function($scope) {
  2. $scope.book = {
  3. id: 1,
  4. name: 'Harry Potter',
  5. author: 'J. K. Rowling',
  6. stores: [
  7. { id: 1, name: 'Barnes & Noble', quantity: 3},
  8. { id: 2, name: 'Waterstones', quantity: 2},
  9. { id: 3, name: 'Book Depository', quantity: 5}
  10. ]
  11. };
  12. }]);

这个控制器创建了一个书本的模型,我们可以在后面的模板中(templage)中使用它。

template for displaying a book

  1. <div ng-controller="BookController">
  2. Id: <span ng-bind="book.id"></span>
  3. Name:<input type="text" ng-model="book.name" />
  4. Author: <input type="text" ng-model="book.author" />
  5. </div>
假如我们需要从后台的api获取书本的数据,我们需要使用$http:

BookController with $http

  1. app.controller('BookController', ['$scope', '$http', function($scope, $http) {
  2. var bookId = 1;
  3. $http.get('ourserver/books/' + bookId).success(function(bookData) {
  4. $scope.book = bookData;
  5. });
  6. }]);

注意到这里的bookData仍然是一个JSON对象。接下来我们想要使用这些数据做一些事情。比如,更新书本信息,删除书本,甚至其他的一些不涉及到后台的操作,比如根据请求的图片大小生成一个书本图片的url,或者判断书本是否有效。这些方法都可以被定义在控制器中。

BookController with several book actions

  1. app.controller('BookController', ['$scope', '$http', function($scope, $http) {
  2. var bookId = 1;
  3. $http.get('ourserver/books/' + bookId).success(function(bookData) {
  4. $scope.book = bookData;
  5. });
  6. $scope.deleteBook = function() {
  7. $http.delete('ourserver/books/' + bookId);
  8. };
  9. $scope.updateBook = function() {
  10. $http.put('ourserver/books/' + bookId, $scope.book);
  11. };
  12. $scope.getBookImageUrl = function(width, height) {
  13. return 'our/image/service/' + bookId + '/width/height';
  14. };
  15. $scope.isAvailable = function() {
  16. if (!$scope.book.stores || $scope.book.stores.length === 0) {
  17. return false;
  18. }
  19. return $scope.book.stores.some(function(store) {
  20. return store.quantity > 0;
  21. });
  22. };
  23. }]);

然后在我们的模板中:

template for displaying a complete book

  1. <div ng-controller="BookController">
  2. <div ng-style="{ backgroundImage: 'url(' + getBookImageUrl(100, 100) + ')' }"></div>
  3. Id: <span ng-bind="book.id"></span>
  4. Name:<input type="text" ng-model="book.name" />
  5. Author: <input type="text" ng-model="book.author" />
  6. Is Available: <span ng-bind="isAvailable() ? 'Yes' : 'No' "></span>
  7. <button ng-click="deleteBook()">Delete</button>
  8. <button ng-click="updateBook()">Update</button>
  9. </div> 

    在controllers之间共享Model

    如果书本的结构和方法只和一个控制器有关,那我们现在的工作已经可以应付。但是随着应用的增长,会有其他的控制器也需要和书本打交道。那些控制器很 多时候也需要获取书本,更新它,删除它,或者获得它的图片url以及看它是否有效。因此,我们需要在控制器之间共享这些书本的行为。我们需要使用一个返回 书本行为的factory来实现这个目的。在动手写一个factory之前,我想在这里先提一下,我们创建一个factory来返回带有这些book辅助 方法的对象,但我更倾向于使用prototype来构造一个Book类,我觉得这是更正确的选择:

    Book model service

    1. app.factory('Book', ['$http', function($http) {
    2. function Book(bookData) {
    3. if (bookData) {
    4. this.setData(bookData):
    5. }
    6. // Some other initializations related to book
    7. };
    8. Book.prototype = {
    9. setData: function(bookData) {
    10. angular.extend(this, bookData);
    11. },
    12. load: function(id) {
    13. var scope = this;
    14. $http.get('ourserver/books/' + bookId).success(function(bookData) {
    15. scope.setData(bookData);
    16. });
    17. },
    18. delete: function() {
    19. $http.delete('ourserver/books/' + bookId);
    20. },
    21. update: function() {
    22. $http.put('ourserver/books/' + bookId, this);
    23. },
    24. getImageUrl: function(width, height) {
    25. return 'our/image/service/' + this.book.id + '/width/height';
    26. },
    27. isAvailable: function() {
    28. if (!this.book.stores || this.book.stores.length === 0) {
    29. return false;
    30. }
    31. return this.book.stores.some(function(store) {
    32. return store.quantity > 0;
    33. });
    34. }
    35. };
    36. return Book;
    37. }]);

    这种方式下,书本相关的所有行为都被封装在Book服务内。现在,我们在BookController中来使用这个亮眼的Book服务。

    BookController that uses Book model

    1. app.controller('BookController', ['$scope', 'Book', function($scope, Book) {
    2. $scope.book = new Book();
    3. $scope.book.load(1);
    4. }]);

    正如你看到的,控制器变得非常简单。它创建一个Book实例,指派给scope,并从后台加载。当书本被加载成功时,它的属性会被改变,模板也随着 被更新。记住其他的控制器想要使用书本功能,只要简单地注入Book服务即可。此外,我们还要改变template使用book的方法。

    template that uses book instance

    1. <div ng-controller="BookController">
    2. <div ng-style="{ backgroundImage: 'url(' + book.getImageUrl(100, 100) + ')' }"></div>
    3. Id: <span ng-bind="book.id"></span>
    4. Name:<input type="text" ng-model="book.name" />
    5. Author: <input type="text" ng-model="book.author" />
    6. Is Available: <span ng-bind="book.isAvailable() ? 'Yes' : 'No' "></span>
    7. <button ng-click="book.delete()">Delete</button>
    8. <button ng-click="book.update()">Update</button>
    9. </div>

    到这里,我们知道了如何建模一个数据,把他的方法封装到一个类中,并且在多个控制器中共享它,而不需要写重复代码。

    在多个控制器中使用相同的书本模型

    我们定义了一个书本模型,并且在多个控制器中使用了它。在使用了这种建模架构之后你会注意到有一个严重的问题。到目前为止,我们假设多个控制器对书本进行操作,但如果有两个控制器同时处理同一本书会是什么情况呢?

    假设我们页面的一块区域我们所有书本的名称,另一块区域可以更新某一本书。对应这两块区域,我们有两个不同的控制器。第一个加载书本列表,第二个加 载特定的一本书。我们的用户在第二块区域中修改了书本的名称并且点击“更新”按钮。更新操作成功后,书本的名称会被改变。但是在书本列表中,这个用户始终 看到的是修改之前的名称!真实的情况是我们对同一本书创建了两个不同的书本实例——一个在书本列表中使用,而另一个在修改书本时使用。当用户修改书本名称 的时候,它实际上只修改了后一个实例中的属性。然而书本列表中的书本实例并未得到改变。

    解决这个问题的办法是在所有的控制器中使用相同的书本实例。在这种方式下,书本列表和书本修改的页面和控制器都持有相同的书本实例,一旦这个实例发 生变化,就会被立刻反映到所有的视图中。那么按这种方式行动起来,我们需要创建一个booksManager服务(我们没有大写开头的b字母,是因为这是 一个对象而不是一个类)来管理所有的书本实例池,并且富足返回这些书本实例。如果被请求的书本实例不在实例池中,这个服务会创建它。如果已经在池中,那么 就直接返回它。请牢记,所有的加载书本的方法最终都会被定义在booksManager服务中,因为它是唯一的提供书本实例的组件。

booksManager service

  1. app.factory('booksManager', ['$http', '$q', 'Book', function($http, $q, Book) {
  2. var booksManager = {
  3. _pool: {},
  4. _retrieveInstance: function(bookId, bookData) {
  5. var instance = this._pool[bookId];
  6. if (instance) {
  7. instance.setData(bookData);
  8. } else {
  9. instance = new Book(bookData);
  10. this._pool[bookId] = instance;
  11. }
  12. return instance;
  13. },
  14. _search: function(bookId) {
  15. return this._pool[bookId];
  16. },
  17. _load: function(bookId, deferred) {
  18. var scope = this;
  19. $http.get('ourserver/books/' + bookId)
  20. .success(function(bookData) {
  21. var book = scope._retrieveInstance(bookData.id, bookData);
  22. deferred.resolve(book);
  23. })
  24. .error(function() {
  25. deferred.reject();
  26. });
  27. },
  28. /* Public Methods */
  29. /* Use this function in order to get a book instance by it's id */
  30. getBook: function(bookId) {
  31. var deferred = $q.defer();
  32. var book = this._search(bookId);
  33. if (book) {
  34. deferred.resolve(book);
  35. } else {
  36. this._load(bookId, deferred);
  37. }
  38. return deferred.promise;
  39. },
  40. /* Use this function in order to get instances of all the books */
  41. loadAllBooks: function() {
  42. var deferred = $q.defer();
  43. var scope = this;
  44. $http.get('ourserver/books)
  45. .success(function(booksArray) {
  46. var books = [];
  47. booksArray.forEach(function(bookData) {
  48. var book = scope._retrieveInstance(bookData.id, bookData);
  49. books.push(book);
  50. });
  51. deferred.resolve(books);
  52. })
  53. .error(function() {
  54. deferred.reject();
  55. });
  56. return deferred.promise;
  57. },
  58. /*  This function is useful when we got somehow the book data and we wish to store it or update the pool and get a book instance in return */
  59. setBook: function(bookData) {
  60. var scope = this;
  61. var book = this._search(bookData.id);
  62. if (book) {
  63. book.setData(bookData);
  64. } else {
  65. book = scope._retrieveInstance(bookData);
  66. }
  67. return book;
  68. },
  69. };
  70. return booksManager;
  71. }]);

下面是我们的EditableBookController和BooksListController两个控制器的代码:

EditableBookController and BooksListController that uses booksManager

  1. app.factory('Book', ['$http', function($http) {
  2. function Book(bookData) {
  3. if (bookData) {
  4. this.setData(bookData):
  5. }
  6. // Some other initializations related to book
  7. };
  8. Book.prototype = {
  9. setData: function(bookData) {
  10. angular.extend(this, bookData);
  11. },
  12. delete: function() {
  13. $http.delete('ourserver/books/' + bookId);
  14. },
  15. update: function() {
  16. $http.put('ourserver/books/' + bookId, this);
  17. },
  18. getImageUrl: function(width, height) {
  19. return 'our/image/service/' + this.book.id + '/width/height';
  20. },
  21. isAvailable: function() {
  22. if (!this.book.stores || this.book.stores.length === 0) {
  23. return false;
  24. }
  25. return this.book.stores.some(function(store) {
  26. return store.quantity > 0;
  27. });
  28. }
  29. };
  30. return Book;
  31. }]);

需要注意的是,模块(template)中还是保持原来使用book实例的方式。现在应用中只持有一个id为1的book实例,它发生的所有改变都会被反映到使用它的各个页面上。

总结

在这片文章中,我建议了AngularJS中建模数据的一种架构。首先,我展示了AngularJS默认的数据模型绑定,然后讲了如何封装模型的方 法和操作从而可以在不同的控制其中重用它们,最后我解释了如何管理模型实例从而使得所有的改变都能被反映到应用中各个相关的视图上。

希望这篇文章能在如何实现数据建模上给你一些启示。

原文链接:http://www.webdeveasy.com/angularjs-data-model/

译文链接:http://blog.jobbole.com/54817/

AngularJS数据建模(转载)的更多相关文章

  1. 《Entity Framework 6 Recipes》翻译系列 (3) -----第二章 实体数据建模基础之创建一个简单的模型

    第二章 实体数据建模基础 很有可能,你才开始探索实体框架,你可能会问“我们怎么开始?”,如果你真是这样的话,那么本章就是一个很好的开始.如果不是,你已经建模,并在实体分裂和继承方面感觉良好,那么你可以 ...

  2. 《驾驭Core Data》 第三章 数据建模

    本文由海水的味道编译整理,请勿转载,请勿用于商业用途.    当前版本号:0.1.2 第三章数据建模 Core Data栈配置好之后,接下来的工作就是设计对象图,在Core Data框架中,对象图被表 ...

  3. NoSQL 数据建模技术(转)

    本文转载自:http://coolshell.cn/articles/7270.html ================================================ 全文译自墙外 ...

  4. MongoDB 数据建模

    版权所有,未经许可,禁止转载 章节 MongoDB 入门 MongoDB 优势 MongoDB 安装 MongoDB 数据建模 MongoDB 创建数据库 MongoDB 删除数据库 MongoDB ...

  5. 数据建模软件Chiner,颜值与实用性并存

    目录 一.chiner介绍 二.值得关注的功能点 2.1. 兼容各种格式的数据建模文件 2.2. 支持多数据库.代码生成 2.3. 支持逻辑视图与物理视图设计 2.4. 自动生成数据库文档 三.总结 ...

  6. 【翻译】ScyllaDB数据建模的最佳实践

    文章翻译自Scylla官方文档:https://www.scylladb.com/2019/08/20/best-practices-for-data-modeling/ 转载请注明出处:https: ...

  7. Elasticsearch 数据建模指南

    文章转载自:https://mp.weixin.qq.com/s/vSh6w3eL_oQvU1mxnxsArA 0.题记 我在做 Elasticsearch 相关咨询和培训过程中,发现大家普遍更关注实 ...

  8. 【mysql的设计与优化专题(1)】ER图,数据建模与数据字典

    需求分析是做项目中的极为重要的一环,而作为整个项目中的'血液'--数据,更是重中之重.viso,workbench,phpmyadmin等软件可以帮我们更好的处理数据分析问题. ER图 E-R方法是& ...

  9. EF数据建模(一)

    大中型软件开发过程中常会使用ORM技术,ORM全称是“对象-关系映射Object-Relation-Mappping”.是将数据库中的数据对象的形式表现出来,并将通过面向对象的方式将这些对象组织起来, ...

随机推荐

  1. html 图片上传预览

    Html5 upload img 2012年12月27日 20:36 <!DOCTYPE HTML> <html> <head> <meta http-equ ...

  2. 使用纯css3实现图片轮播

    <!DOCTYPE html> <html> <head> <title> 飛天网事--纯CSS代码实现图片轮播 </title> < ...

  3. sql存储过程——名称 ****不是有效的标识符

    转载自http://blog.csdn.net/xb12369/article/details/8202703 假设存储过程:proc_test create proc proc_test @Prod ...

  4. Spring框架--AOP编程

    2 手动实现AOP编程 AOP 面向切面的编程, AOP可以实现"业务代码"与"关注点代码"分离 // 保存一个用户 public void add(User ...

  5. sql or 与and同时有时要注意

    如果是一天sql语句中有or和and同时在 正确:where xxx=xx and (xxx=xx or xxx=xx) 错误:where xxx=xx and xxx=xx or xxx=xx (这 ...

  6. JPA的介绍

    一.JPA概述 1.JPA是什么? JPA:Java Persistence API:用于对象持久化的 API,JPA是Java EE 5.0 平台标准的 ORM 规范, 使得应用程序以统一的方式访问 ...

  7. android常用工具类

    import android.content.Context; import android.net.ConnectivityManager; import android.net.NetworkIn ...

  8. 无法识别的配置节 system.serviceModel

    也是从网上四处搜来的答案,的确是解决了问题 不知道是不是补丁更新的原因,之前运行好的程序,突然就不行了, 一开始,运行直接就闪退了,在事件查看器里,也看不到具体的错误信息,幸亏是cmd的程序,所以 , ...

  9. <转>如何高效快速看懂Android源码

    原网址:http://jingyan.baidu.com/article/574c5219ca78ed6c8d9dc12a.html 在Android系统上工作了一段时间,经常会遇到题目中的问题,下面 ...

  10. JS总结之一:字符串的调用方法

    字符串的调用方法:var s="hello, world";document.write(s.charAt(0)); //第一个字符document.write(s.charAt( ...