AngularJS數(shù)據(jù)建模
我們知道,AngularJS并沒有自帶立等可用的數(shù)據(jù)建模方案。而是以相當(dāng)抽象的方式,讓我們?cè)赾ontroller中使用JSON數(shù)據(jù)作為模 型。但是隨著時(shí)間的推移和項(xiàng)目的成長,我意識(shí)到這種建模的方式不再能滿足我們項(xiàng)目的需求。在這篇文章中我會(huì)介紹在我的AngularJS應(yīng)用中處理數(shù)據(jù)建 模的方式。
為Controller定義模型
讓我們從一個(gè)簡單的例子開始。我想要顯示一個(gè)書本(book)的頁面。下面是控制器(Controller):
BookController
- app.controller('BookController', ['$scope', function($scope) {
- $scope.book = {
- id: 1,
- name: 'Harry Potter',
- author: 'J. K. Rowling',
- stores: [
- { id: 1, name: 'Barnes & Noble', quantity: 3},
- { id: 2, name: 'Waterstones', quantity: 2},
- { id: 3, name: 'Book Depository', quantity: 5}
- ]
- };
- }]);
這個(gè)控制器創(chuàng)建了一個(gè)書本的模型,我們可以在后面的模板中(templage)中使用它。
template for displaying a book
- <div ng-controller="BookController">
- Id: <span ng-bind="book.id"></span>
- Name:<input type="text" ng-model="book.name" />
- Author: <input type="text" ng-model="book.author" />
- </div>
BookController with $http
- app.controller('BookController', ['$scope', '$http', function($scope, $http) {
- var bookId = 1;
- $http.get('ourserver/books/' + bookId).success(function(bookData) {
- $scope.book = bookData;
- });
- }]);
注意到這里的bookData仍然是一個(gè)JSON對(duì)象。接下來我們想要使用這些數(shù)據(jù)做一些事情。比如,更新書本信息,刪除書本,甚至其他的一些不涉及到后臺(tái)的操作,比如根據(jù)請(qǐng)求的圖片大小生成一個(gè)書本圖片的url,或者判斷書本是否有效。這些方法都可以被定義在控制器中。
BookController with several book actions
- app.controller('BookController', ['$scope', '$http', function($scope, $http) {
- var bookId = 1;
- $http.get('ourserver/books/' + bookId).success(function(bookData) {
- $scope.book = bookData;
- });
- $scope.deleteBook = function() {
- $http.delete('ourserver/books/' + bookId);
- };
- $scope.updateBook = function() {
- $http.put('ourserver/books/' + bookId, $scope.book);
- };
- $scope.getBookImageUrl = function(width, height) {
- return 'our/image/service/' + bookId + '/width/height';
- };
- $scope.isAvailable = function() {
- if (!$scope.book.stores || $scope.book.stores.length === 0) {
- return false;
- }
- return $scope.book.stores.some(function(store) {
- return store.quantity > 0;
- });
- };
- }]);
然后在我們的模板中:
template for displaying a complete book
- <div ng-controller="BookController">
- <div ng-style="{ backgroundImage: 'url(' + getBookImageUrl(100, 100) + ')' }"></div>
- Id: <span ng-bind="book.id"></span>
- Name:<input type="text" ng-model="book.name" />
- Author: <input type="text" ng-model="book.author" />
- Is Available: <span ng-bind="isAvailable() ? 'Yes' : 'No' "></span>
- <button ng-click="deleteBook()">Delete</button>
- <button ng-click="updateBook()">Update</button>
- </div>
#p#
在controllers之間共享Model
如果書本的結(jié)構(gòu)和方法只和一個(gè)控制器有關(guān),那我們現(xiàn)在的工作已經(jīng)可以應(yīng)付。但是隨著應(yīng)用的增長,會(huì)有其他的控制器也需要和書本打交道。那些控制器很 多時(shí)候也需要獲取書本,更新它,刪除它,或者獲得它的圖片url以及看它是否有效。因此,我們需要在控制器之間共享這些書本的行為。我們需要使用一個(gè)返回 書本行為的factory來實(shí)現(xiàn)這個(gè)目的。在動(dòng)手寫一個(gè)factory之前,我想在這里先提一下,我們創(chuàng)建一個(gè)factory來返回帶有這些book輔助 方法的對(duì)象,但我更傾向于使用prototype來構(gòu)造一個(gè)Book類,我覺得這是更正確的選擇:
Book model service
- app.factory('Book', ['$http', function($http) {
- function Book(bookData) {
- if (bookData) {
- this.setData(bookData):
- }
- // Some other initializations related to book
- };
- Book.prototype = {
- setData: function(bookData) {
- angular.extend(this, bookData);
- },
- load: function(id) {
- var scope = this;
- $http.get('ourserver/books/' + bookId).success(function(bookData) {
- scope.setData(bookData);
- });
- },
- delete: function() {
- $http.delete('ourserver/books/' + bookId);
- },
- update: function() {
- $http.put('ourserver/books/' + bookId, this);
- },
- getImageUrl: function(width, height) {
- return 'our/image/service/' + this.book.id + '/width/height';
- },
- isAvailable: function() {
- if (!this.book.stores || this.book.stores.length === 0) {
- return false;
- }
- return this.book.stores.some(function(store) {
- return store.quantity > 0;
- });
- }
- };
- return Book;
- }]);
這種方式下,書本相關(guān)的所有行為都被封裝在Book服務(wù)內(nèi)?,F(xiàn)在,我們?cè)贐ookController中來使用這個(gè)亮眼的Book服務(wù)。
BookController that uses Book model
- app.controller('BookController', ['$scope', 'Book', function($scope, Book) {
- $scope.book = new Book();
- $scope.book.load(1);
- }]);
正如你看到的,控制器變得非常簡單。它創(chuàng)建一個(gè)Book實(shí)例,指派給scope,并從后臺(tái)加載。當(dāng)書本被加載成功時(shí),它的屬性會(huì)被改變,模板也隨著 被更新。記住其他的控制器想要使用書本功能,只要簡單地注入Book服務(wù)即可。此外,我們還要改變template使用book的方法。
template that uses book instance
- <div ng-controller="BookController">
- <div ng-style="{ backgroundImage: 'url(' + book.getImageUrl(100, 100) + ')' }"></div>
- Id: <span ng-bind="book.id"></span>
- Name:<input type="text" ng-model="book.name" />
- Author: <input type="text" ng-model="book.author" />
- Is Available: <span ng-bind="book.isAvailable() ? 'Yes' : 'No' "></span>
- <button ng-click="book.delete()">Delete</button>
- <button ng-click="book.update()">Update</button>
- </div>
到這里,我們知道了如何建模一個(gè)數(shù)據(jù),把他的方法封裝到一個(gè)類中,并且在多個(gè)控制器中共享它,而不需要寫重復(fù)代碼。
在多個(gè)控制器中使用相同的書本模型
我們定義了一個(gè)書本模型,并且在多個(gè)控制器中使用了它。在使用了這種建模架構(gòu)之后你會(huì)注意到有一個(gè)嚴(yán)重的問題。到目前為止,我們假設(shè)多個(gè)控制器對(duì)書本進(jìn)行操作,但如果有兩個(gè)控制器同時(shí)處理同一本書會(huì)是什么情況呢?
假設(shè)我們頁面的一塊區(qū)域我們所有書本的名稱,另一塊區(qū)域可以更新某一本書。對(duì)應(yīng)這兩塊區(qū)域,我們有兩個(gè)不同的控制器。***個(gè)加載書本列表,第二個(gè)加 載特定的一本書。我們的用戶在第二塊區(qū)域中修改了書本的名稱并且點(diǎn)擊“更新”按鈕。更新操作成功后,書本的名稱會(huì)被改變。但是在書本列表中,這個(gè)用戶始終 看到的是修改之前的名稱!真實(shí)的情況是我們對(duì)同一本書創(chuàng)建了兩個(gè)不同的書本實(shí)例——一個(gè)在書本列表中使用,而另一個(gè)在修改書本時(shí)使用。當(dāng)用戶修改書本名稱 的時(shí)候,它實(shí)際上只修改了后一個(gè)實(shí)例中的屬性。然而書本列表中的書本實(shí)例并未得到改變。
解決這個(gè)問題的辦法是在所有的控制器中使用相同的書本實(shí)例。在這種方式下,書本列表和書本修改的頁面和控制器都持有相同的書本實(shí)例,一旦這個(gè)實(shí)例發(fā) 生變化,就會(huì)被立刻反映到所有的視圖中。那么按這種方式行動(dòng)起來,我們需要?jiǎng)?chuàng)建一個(gè)booksManager服務(wù)(我們沒有大寫開頭的b字母,是因?yàn)檫@是 一個(gè)對(duì)象而不是一個(gè)類)來管理所有的書本實(shí)例池,并且富足返回這些書本實(shí)例。如果被請(qǐng)求的書本實(shí)例不在實(shí)例池中,這個(gè)服務(wù)會(huì)創(chuàng)建它。如果已經(jīng)在池中,那么 就直接返回它。請(qǐng)牢記,所有的加載書本的方法最終都會(huì)被定義在booksManager服務(wù)中,因?yàn)樗俏ㄒ坏奶峁緦?shí)例的組件。
#p#
booksManager service
- app.factory('booksManager', ['$http', '$q', 'Book', function($http, $q, Book) {
- var booksManager = {
- _pool: {},
- _retrieveInstance: function(bookId, bookData) {
- var instance = this._pool[bookId];
- if (instance) {
- instance.setData(bookData);
- } else {
- instance = new Book(bookData);
- this._pool[bookId] = instance;
- }
- return instance;
- },
- _search: function(bookId) {
- return this._pool[bookId];
- },
- _load: function(bookId, deferred) {
- var scope = this;
- $http.get('ourserver/books/' + bookId)
- .success(function(bookData) {
- var book = scope._retrieveInstance(bookData.id, bookData);
- deferred.resolve(book);
- })
- .error(function() {
- deferred.reject();
- });
- },
- /* Public Methods */
- /* Use this function in order to get a book instance by it's id */
- getBook: function(bookId) {
- var deferred = $q.defer();
- var book = this._search(bookId);
- if (book) {
- deferred.resolve(book);
- } else {
- this._load(bookId, deferred);
- }
- return deferred.promise;
- },
- /* Use this function in order to get instances of all the books */
- loadAllBooks: function() {
- var deferred = $q.defer();
- var scope = this;
- $http.get('ourserver/books)
- .success(function(booksArray) {
- var books = [];
- booksArray.forEach(function(bookData) {
- var book = scope._retrieveInstance(bookData.id, bookData);
- books.push(book);
- });
- deferred.resolve(books);
- })
- .error(function() {
- deferred.reject();
- });
- return deferred.promise;
- },
- /* 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 */
- setBook: function(bookData) {
- var scope = this;
- var book = this._search(bookData.id);
- if (book) {
- book.setData(bookData);
- } else {
- book = scope._retrieveInstance(bookData);
- }
- return book;
- },
- };
- return booksManager;
- }]);
下面是我們的EditableBookController和BooksListController兩個(gè)控制器的代碼:
EditableBookController and BooksListController that uses booksManager
- app.factory('Book', ['$http', function($http) {
- function Book(bookData) {
- if (bookData) {
- this.setData(bookData):
- }
- // Some other initializations related to book
- };
- Book.prototype = {
- setData: function(bookData) {
- angular.extend(this, bookData);
- },
- delete: function() {
- $http.delete('ourserver/books/' + bookId);
- },
- update: function() {
- $http.put('ourserver/books/' + bookId, this);
- },
- getImageUrl: function(width, height) {
- return 'our/image/service/' + this.book.id + '/width/height';
- },
- isAvailable: function() {
- if (!this.book.stores || this.book.stores.length === 0) {
- return false;
- }
- return this.book.stores.some(function(store) {
- return store.quantity > 0;
- });
- }
- };
- return Book;
- }]);
需要注意的是,模塊(template)中還是保持原來使用book實(shí)例的方式?,F(xiàn)在應(yīng)用中只持有一個(gè)id為1的book實(shí)例,它發(fā)生的所有改變都會(huì)被反映到使用它的各個(gè)頁面上。
總結(jié)
在這片文章中,我建議了AngularJS中建模數(shù)據(jù)的一種架構(gòu)。首先,我展示了AngularJS默認(rèn)的數(shù)據(jù)模型綁定,然后講了如何封裝模型的方 法和操作從而可以在不同的控制其中重用它們,***我解釋了如何管理模型實(shí)例從而使得所有的改變都能被反映到應(yīng)用中各個(gè)相關(guān)的視圖上。
希望這篇文章能在如何實(shí)現(xiàn)數(shù)據(jù)建模上給你一些啟示。