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

У міру того як росте додаток, подання даних у вигляді набору JSON об'єктів стає все менш зручним. У цій статті я розповім про спосіб організації роботи з даними в своїх додатках.
 
Почнемо з простого прикладу. Створимо сторінку з інформацією про книгу. Контролер:
 
 
 
app. controller ('BookController' , ['$ scope' , function ($ scope ) {
$ Scope. book = {
id : 1 ,
name : 'Harry Potter' ,
author : 'JK 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/

Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.