[Из песочницы] 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:         
       Delete       
       Update 

Использование модели несколькими контроллерами

Если наши методы и данные о книге используются только одним контроллером, дело сделано.Однако по мере роста приложения появится необходимость использовать одну модель в нескольких контроллерах. Для того, чтобы сделать ее доступной для нескольких контроллеров, мы создадим сервис 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:         
       Delete       
       Update  

Итак, у нас есть сервис 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/

© Habrahabr.ru