出处不明

我们知道,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. 【报错】java.lang.RuntimeException: Invalid action class configuration that references an unknown class named [xxxAction]

    java.lang.RuntimeException: Invalid action class configuration that references an unknown class name ...

  2. 关于GVIM的配置。

    最近开始考虑从常用的 Dev-Cpp 和 Visual Studio 开发环境中转到gvim+gcc+gdb. 我一直觉得IDE很好方便,不是很明白别人这样的配置.但还是开始有意识的去尝试更换环境. ...

  3. Spring 框架理论基础

    一. IOC 控制反转 概念解释:当我需要一个资源时,容器已经帮我准备好,我只需要接受就可以. // 加载 IOC 容器 ApplicationContext ac = new ClassPathXm ...

  4. iOS拨打电话

    1,这种方法,拨打完电话回不到原来的应用,会停留在通讯录里,而且是直接拨打,不弹出提示NSMutableString * str=[[NSMutableString alloc] initWithFo ...

  5. isset() 与 empty() 的区别

    PHP的isset()函数 一般用来检测变量是否设置 格式:bool isset ( mixed var [, mixed var [, ...]] ) 功能:检测变量是否设置 返回值: 若变量不存在 ...

  6. 第三十二节,datetime时间模块

    首先要引入import datetime时间模块 datetime.date.today()模块函数 功能:输出系统年月日输出格式 2016-01-26[无参] 使用方法:datetime.date. ...

  7. hdu_2243_考研路茫茫——单词情结(AC自动机+矩阵)

    题目链接:hdu_2243_考研路茫茫——单词情结 题意: 让你求包含这些模式串并且长度不小于L的单词种类 题解: 这题是poj2788的升级版,没做过的强烈建议先做那题. 我们用poj2778的方法 ...

  8. LeetCode OJ 202. Happy Number

    Write an algorithm to determine if a number is "happy". A happy number is a number defined ...

  9. vs2013执行Add-Migration出现的问题

    1. 无法将"Add-Migration"项识别为 cmdlet.函数.脚本文件或可运行程序的名称.请检查名称的拼写,如果包括路径,请确保路径正确,然后再试一次 网上解决方法为:h ...

  10. 远程连接(ssh安装)

    更新源列表打开"终端窗口",输入"sudo apt-get update"-->回车-->"输入当前登录用户的管理员密码"--& ...