AngularJS. Организация данных

в 14:24, , рубрики: 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}
        ]
    };
}]);

Контроллер инициализирует модель книги, которая используется в представлении:

<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  
          });  
     }]);

Код представлений не изменится.

Теперь для каждой книги будет храниться только один объект, и все изменеия этого объекта будут отображены на всех страницах, которые его используют.

Оригинал: www.webdeveasy.com/angularjs-data-model/

Автор:

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js