[Из песочницы] AngularJS. Организация данных
По мере того как растет приложение, представление данных в виде набора 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} ] };}]);
Контроллер инициализирует модель книги, которая используется в представлении:
Id:
Name:
Author:
Когда данные о книге нужно получить от бэк-энд сервера, можно использовать $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; }); }; }]);
Используем эти методы в представлении:
Id:
Name:
Author:
Is Available:
Использование модели несколькими контроллерами
Если наши методы и данные о книге используются только одним контроллером, дело сделано.Однако по мере роста приложения появится необходимость использовать одну модель в нескольких контроллерах. Для того, чтобы сделать ее доступной для нескольких контроллеров, мы создадим сервис 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); }]);
Теперь, когда вся логика вынесена в модель, в коде контроллера осталось всего две строчки: создание объекта книги и получение данных от бэк-энд сервера. Как только данные будут загружены, они отобразятся в представлении, которое теперь выглядит так:
Id:
Name:
Author:
Is Available:
Итак, у нас есть сервис 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 }); }]);
Код представлений не изменится.
Теперь для каждой книги будет храниться только один объект, и все изменеия этого объекта будут отображены на всех страницах, которые его используют.
Оригинал: www.webdeveasy.com/angularjs-data-model/