出处不明

我们知道,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. mysql5.7.16安装

    系统:centOS6.5 mysql: 5.7.16 wget http://dev.mysql.com/get/Downloads/MySQL-5.7/mysql-5.7.16-linux-glib ...

  2. Quartz(任务调度)- 入门学习

    参照:http://blog.csdn.net/szwangdf/article/details/6158646 1.自定义定时任务管理类 QuartzManager 参照大神基础上新增:1.添加jo ...

  3. 软件测试之α测试和Beta测试

    实施验收测试的常用策略有三种,它们分别是: · 正式验收 · 非正式验收或Alpha 测试 · Beta 测试 因此,Alpha测试和Beta测试都属于验收测试.所谓验收测试是软件产品完成了功能测试和 ...

  4. github 上传项目基本步骤

    说来也惭愧,我是最近开始用github,小白一个,昨天研究了一个下午.终于可以上传了,所以今天写点,一来分享是自己的一些经验,二来也是做个记录,万一哪天又不记得了:) 废话不多说,直接来,这次主要介绍 ...

  5. PE结构之重定位表

    什么是重定位: 重定位就是你本来这个程序理论上要占据这个地址,但是由于某种原因,这个地址现在不能让你占用,你必须转移到别的地址,这就需要基址重定位.你可能会问,不是说过每个进程都有自己独立的虚拟地址空 ...

  6. hdu_3518_Boring counting(后缀数组)

    题目链接:hdu_3518_Boring counting 题意: 给你一个字符串,让你找不重叠且出现大于1次以上的字串个数 题解: 后缀数组height数组的应用,我们枚举字串的长度,然后将heig ...

  7. NSBundle的用法

    bundle是一个目录,其中包含了程序会使用到的资源. 这些资源包含了如图像,声音,编译好的代码,nib文件(用户也会把bundle称为plug-in). 对应bundle,cocoa提供了类NSBu ...

  8. IIS7禁止后台访问

    设置只能内网访问 1.添加允许内网访问规则 2.编辑功能设置

  9. 把perl脚本编译成exe

    来源:http://www.veryhuo.com/a/view/38338.html 今天想把 perl 脚本编译成 exe 以便脱离 perl 解释器独立运行.都可以生成PERL的PE文件,在PE ...

  10. 如何修改android工程的包名?

    在我们android项目开发到一定的程度时由于需要,我们必须修改一下工程的包名,以便更好的发布我们的项目.但是在这个过程中有时候修改好了之后会出现一些错误.下面由小编一步步教你如何更改包名,和解决出现 ...