По мере того как растет приложение, представление данных в виде набора 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/
Автор: