Angularjs. Организация модели данных
Оригинал статьи: http://www.webdeveasy.com/angularjs-data-model/
По мере того как растет приложение, представление данных в виде набора JSON объектов становится все менее удобным. В этой статье я расскажу про способ организации работы с данными в своих приложениях.
Описание модели
Начнем с простого примера. Создадим страницу с информацией о книге. Контроллер:
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} ] }; }]);
Контроллер инициализирует модель книги, которая используется в представлении:
<div ng-controller="BookController"> Id: <span ng-bind="book.id"></span> <br/> Name:<input type="text" ng-model="book.name" /> <br/> Author: <input type="text" ng-model="book.author" /> </div>
Когда данные о книге нужно получить от бэк-энд сервера, можно использовать$http
сервис:
app.controller('BookController', ['$scope', '$http', function($scope, $http) { var bookId = 1; $http.get('ourserver/books/' + bookId).success(function(bookData) { $scope.book = bookData; }); }]);
Обратите внимание, что bookData
все еще JSON объект.
Нам скорее всего понадобится манипулировать данными. Например, удалять книги, обновлять информацию о них или генерировать url обложки в соответствии с нужными нам размерами. Методы, которые позволят это сделать, могут бюыть определены в коде контроллера.
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; }); }; }]);
Используем эти методы в представлении:
<div ng-controller="BookController"> <div ng-style="{ backgroundImage: 'url(' + getBookImageUrl(100, 100) + ')' }"></div> Id: <span ng-bind="book.id"></span> <br/> Name:<input type="text" ng-model="book.name" /> <br/> Author: <input type="text" ng-model="book.author" /> <br/> Is Available: <span ng-bind="isAvailable() ? 'Yes' : 'No' "></span> <br/> <button ng-click="deleteBook()">Delete</button> <br/> <button ng-click="updateBook()">Update</button> </div>
Использование модели несколькими контроллерами
Если наши методы и данные о книге используются только одним контроллером, дело сделано. Однако по мере роста приложения появится необходимость использовать одну модель в нескольких контроллерах. Для того, чтобы сделать ее доступной для нескольких контроллеров, мы создадим сервис Book, который будет прототипом объектов, описывающих состояние и поведение книг.
app.factory('Book', ['$http', function($http) { function Book(bookData) { if (bookData) { this.setData(bookData): } //что-то, что еще нужно для инициализации книги }; 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; }]);
Используем севрис Book в контроллере.
app.controller('BookController', ['$scope', 'Book', function($scope, Book) { $scope.book = new Book(); $scope.book.load(1); }]);
Теперь, когда вся логика вынесена в модель, в коде контроллера осталось всего две строчки: создание объекта книги и получение данных от бэк-энд сервера. Как только данные будут загружены, они отобразятся в представлении, которое теперь выглядит так:
<div ng-controller="BookController"> <div ng-style="{ backgroundImage: 'url(' + book.getImageUrl(100, 100) + ')' }"></div> Id: <span ng-bind="book.id"></span> <br/> Name:<input type="text" ng-model="book.name" /> <br/> Author: <input type="text" ng-model="book.author" /> <br/> Is Available: <span ng-bind="book.isAvailable() ? 'Yes' : 'No' "></span> <br/> <button ng-click="book.delete()">Delete</button> <br/> <button ng-click="book.update()">Update</button> </div>
Итак, у нас есть сервис Book и несколько контроллеров, которые работают с книгами. У представленной архитектуры есть недостаток. Что случится, если два контроллера будут иметь возможность манипулировать одной и той же книгой?
Представьте, что есть две страницы: одна со списком книг, а другая с формой управления книгой. Для каждой страницы создано по контроллеру. Первый контроллер получает от бэк-энд сервера список книг, а второй информацию об одной из них. Пользователь заходит на вторую страницу, изменяет название книги и нажмиет кнопку "сохранить". Обновление происходит успешно, и название книги изменяется. Однако если он перейдет на первую страницу, то в списке книг увидит старое название. Это произошло потому, что существовало два экземпляра одной и той же книги: один для страницы со списком книг, а другой для страницы управления книгой. Пользователь изменил название только в том экземпляре, что был создан для страницы управления книгой, второй экземпляр остался без изменений.
Чтобы решить эту проблему, во всех контроллерах нужно использовать один и тот же экземпляр объекта книги. В таком случае, если изменить название книги на второй странице, оно изменятся как на первой, так и на всех остальных использующих информацию о книге страницах.
Для реализации решения создадим сервис bookManager (название сервиса пишется не с заглавной буквы, потому что он будет являться объектом без наследников), который будет управлять книгами и отвечать за получение данных. Если запрашиваемая книга не загружена, bookManager будет ее загружать, иначе он будет возвращать уже загруженный экземпляр.
Имейте в виду, что все методы получения книг от бэк-эенд сервера будут определены только в сервисе bookManager, поскольку он должен быть единственным компонентом, предоставляющим эти данные.
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(); }); }, /*Публичные методы*/ /* Получение книги по идентификатору*/ 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; }, /* Получение списка книг */ 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; }, /* Редактирование книги*/ 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; }]);
Сервис Book без метода load
(получение книг теперь реализуется только через bookManager):
app.factory('Book', ['$http', function($http) { function Book(bookData) { if (bookData) { this.setData(bookData): } //что-то, что еще нужно для инициализации книги }; 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; }]);
Контроллеры для страницы со списком книг и страницы редактирования книги:
app .controller('EditableBookController', ['$scope', 'booksManager', function($scope, booksManager) { booksManager.getBook(1).then(function(book) { $scope.book = book }); }]) .controller('BooksListController', ['$scope', 'booksManager', function($scope, booksManager) { booksManager.loadAllBooks().then(function(books) { $scope.books = books }); }]);
Код представлений не изменится.
Теперь для каждой книги будет храниться только один объект и все изменения этого объекта будут отображены на всех страницах, которые его используют.
Оригинал статьи: http://www.webdeveasy.com/angularjs-data-model/
Комментариев нет :
Отправить комментарий