Большинство одностраничных приложений включают операции CRUD . Если вы строите CRUD-операции с использованием AngularJS, вы можете использовать возможности службы $resource
. Созданный на основе сервиса $http
, $resource
Angular — это фабрика, которая позволяет легко взаимодействовать с бэкэндами RESTful. Итак, давайте исследуем $resource
и используем его для реализации CRUD-операций в Angular.
Предпосылки
Служба $resource
не поставляется в комплекте с основным скриптом Angular. Вам нужно скачать отдельный файл с именем angular-resource.js
и включить его в свою HTML-страницу. Сценарий можно скачать с http://cdnjs.cloudflare.com/ajax/libs/angular.js/1.2.16/angular-resource.min.js .
Кроме того, ваш основной модуль приложения должен объявить зависимость от модуля ngResource
для использования $resource
. Следующий пример демонстрирует, как это сделать:
angular.module('mainApp',['ngResource']); //mainApp is our main module
Начиная
$resource
ожидает классический RESTful бэкэнд. Это означает, что у вас должны быть конечные точки REST в следующем формате:
Вы можете создавать конечные точки, используя выбранный язык на стороне сервера. Однако я использовал Node + Express + MongoDB для разработки RESTful API для демонстрационного приложения. Как только вы подготовите URL-адреса, вы можете воспользоваться помощью $resource
для взаимодействия с этими URL-адресами. Итак, давайте посмотрим, как именно работает $resource
.
Как работает $ resource?
Чтобы использовать $resource
внутри вашего контроллера / сервиса, вам нужно объявить зависимость от $resource
. Следующим шагом является вызов функции $resource()
с конечной точкой REST, как показано в следующем примере. Этот вызов функции возвращает представление класса $resource
которое можно использовать для взаимодействия с бэкэндом REST.
angular.module('myApp.services').factory('Entry', function($resource) { return $resource('/api/entries/:id'); // Note the full endpoint address });
Результатом вызова функции является объект класса ресурсов, который по умолчанию имеет следующие пять методов:
-
get()
-
query()
-
save()
-
remove()
-
delete()
Теперь давайте посмотрим, как мы можем использовать методы get()
, query()
и save()
в контроллере:
angular.module('myApp.controllers',[]); angular.module('myApp.controllers').controller('ResourceController',function($scope, Entry) { var entry = Entry.get({ id: $scope.id }, function() { console.log(entry); }); // get() returns a single entry var entries = Entry.query(function() { console.log(entries); }); //query() returns all the entries $scope.entry = new Entry(); //You can instantiate resource class $scope.entry.data = 'some data'; Entry.save($scope.entry, function() { //data saved. do something here. }); //saves an entry. Assuming $scope.entry is the Entry object });
Функция get()
в приведенном выше фрагменте кода отправляет запрос GET в /api/entries/:id
. Параметр :id
в URL-адресе заменяется на $scope.id
. Следует также отметить, что функция get()
возвращает пустой объект, который заполняется автоматически, когда фактические данные поступают с сервера. Второй аргумент get()
— это обратный вызов, который выполняется, когда данные поступают с сервера. Это полезный прием, потому что вы можете установить пустой объект, возвращаемый get()
в $scope
и ссылаться на него в представлении. Когда поступают фактические данные и объект заполняется, привязка данных включается, и ваше представление также обновляется.
Функция query()
выдает GET-запрос к /api/entries
(обратите внимание, что id отсутствует) и возвращает пустой массив. Этот массив заполняется, когда данные поступают с сервера. Опять же, вы можете установить этот массив как модель $scope
и ссылаться на него в представлении, используя ng-repeat
. Вы также можете передать обратный вызов в query()
который вызывается, когда данные поступают с сервера.
Функция save()
отправляет POST-запрос в /api/entries
с первым аргументом в качестве тела сообщения. Второй аргумент — это обратный вызов, который вызывается при сохранении данных. Возможно, вы помните, что возвращаемое значение функции $resource()
является классом ресурсов. Таким образом, в нашем случае мы можем вызвать new Entry()
чтобы создать экземпляр реального объекта из этого класса, установить для него различные свойства и, наконец, сохранить объект в бэкэнд.
В идеале вы должны использовать get()
и query()
только для класса ресурсов (в нашем случае это Entry
). Все не GET-методы, такие как save()
и delete()
, также доступны в экземпляре, полученном вызовом new Entry()
(назовите его экземпляром $resource
). Но разница в том, что эти методы имеют префикс $
. Итак, методы, доступные в экземпляре $resource
(в отличие от класса $resource
):
-
$save()
-
$delete()
-
$remove()
Например, метод $save()
используется следующим образом:
$scope.entry = new Entry(); //this object now has a $save() method $scope.entry.$save(function() { //data saved. $scope.entry is sent as the post body. });
Мы исследовали создание, чтение и удаление частей CRUD. Осталось только обновить. Чтобы поддержать операцию обновления, нам нужно изменить нашу собственную фабричную Entity
как показано ниже.
angular.module('myApp.services').factory('Entry', function($resource) { return $resource('/api/entries/:id', { id: '@_id' }, { update: { method: 'PUT' // this method issues a PUT request } }); });
Второй аргумент $resource()
— это хеш, указывающий, каким должно быть значение параметра :id
в URL. Установка его в @_id
означает, что всякий раз, когда мы будем вызывать такие методы, как $update()
и $delete()
для экземпляра ресурса, значение :id
будет установлено в свойство _id
экземпляра. Это полезно для запросов PUT и DELETE. Также обратите внимание на третий аргумент. Это хеш, который позволяет нам добавлять любые пользовательские методы в класс ресурсов. Если метод выдает запрос не GET, он становится доступным для экземпляра $resource
с префиксом $
. Итак, давайте посмотрим, как использовать наш метод $update
. Предполагая, что мы находимся в контроллере:
$scope.entry = Movie.get({ id: $scope.id }, function() { // $scope.entry is fetched from server and is an instance of Entry $scope.entry.data = 'something else'; $scope.entry.$update(function() { //updated in the backend }); });
Когда вызывается функция $update()
, она делает следующее:
- AngularJS знает, что функция
$update()
будет инициировать запрос PUT на URL/api/entries/:id
. - Он читает значение
$scope.entry._id
, присваивает значение:id
и генерирует URL. - Посылает запрос PUT на URL с
$scope.entry
в качестве тела сообщения.
Точно так же, если вы хотите удалить запись, это можно сделать следующим образом:
$scope.entry = Movie.get({ id: $scope.id }, function() { // $scope.entry is fetched from server and is an instance of Entry $scope.entry.data = 'something else'; $scope.entry.$delete(function() { //gone forever! }); });
Он выполняет те же действия, что и выше, за исключением того, что тип запроса — DELETE вместо PUT.
Мы покрыли все операции в CRUD, но остались с небольшой вещью. Функция $resource
также имеет необязательный четвертый параметр. Это хеш с пользовательскими настройками. В настоящее время доступен только один параметр — stripTrailingSlashes
. По умолчанию для этого параметра установлено значение true
, что означает, что конечные слеши будут удалены из URL-адреса, который вы передаете $resource()
. Если вы хотите отключить это, вы можете сделать это так:
angular.module('myApp.services').factory('Entry', function($resource) { return $resource('/api/entries/:id', { id: '@_id' }, { update: { method: 'PUT' // this method issues a PUT request } }, { stripTrailingSlashes: false }); });
Между прочим, я не охватил все до $resource
. Здесь мы рассмотрели основы, которые помогут вам легко начать работу с приложениями CRUD. Если вы хотите подробно изучить $resource
, вы можете просмотреть документацию .
Создание приложения для фильма
Чтобы укрепить концепцию $resource
давайте создадим приложение для любителей кино. Это будет SPA, где пользователи могут добавить новый фильм в нашу базу данных, обновить существующий фильм и, наконец, удалить его. Мы будем использовать $resource
для взаимодействия с REST API. Вы можете проверить живую демонстрацию того, что мы собираемся построить здесь .
Просто обратите внимание, что API, который я создал, поддерживает CORS, поэтому вы можете создать приложение Angular отдельно и использовать URL-адрес http://movieapp-sitepointdemos.rhcloud.com/
в качестве API. Вы можете разработать приложение Angular и поэкспериментировать с ним, не беспокоясь о бэкенде.
Наш API
Я создал RESTful бэкэнд, используя Node и Express. Посмотрите на следующий скриншот, чтобы узнать API.
Структура каталогов
Давайте начнем со следующей структуры каталогов для нашего приложения AngularJS:
movieApp /css bootstrap.css app.css /js app.js controllers.js services.js /lib angular.min.js angular-resource.min.js angular-ui-router.min.js /partials _form.html movie-add.html movie-edit.html movie-view.html movies.html index.html
Просто отметьте, что мы будем использовать Angular UI Router для маршрутизации.
Создание нашего сервиса для взаимодействия с конечными точками REST
Как обсуждалось в предыдущих разделах, мы создадим пользовательский сервис, который будет использовать $resource
для взаимодействия с REST API. Служба определена в js/services.js
.
services.js:
angular.module('movieApp.services', []).factory('Movie', function($resource) { return $resource('http://movieapp-sitepointdemos.rhcloud.com/api/movies/:id', { id: '@_id' }, { update: { method: 'PUT' } }); });
Название нашей фабрики — Movie
. Поскольку мы используем MongoDB, у каждого экземпляра фильма есть свойство с именем _id
. Все остальное просто и понятно.
Теперь, когда наш сервис готов, давайте создадим представления и контроллеры.
index.html
: создание страницы входа в приложение
index.html
— это точка входа в наше приложение. Для начала нам нужно включить все необходимые скрипты и таблицы стилей на этой странице. Мы будем использовать Bootstrap для быстрого создания макета. Вот содержание index.html
.
<!DOCTYPE html> <html data-ng-app="movieApp"> <head lang="en"> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1"> <base href="/"/> <title>The Movie App</title> <link rel="stylesheet" type="text/css" href="css/bootstrap.min.css"/> <link rel="stylesheet" type="text/css" href="css/app.css"/> </head> <body> <nav class="navbar navbar-default" role="navigation"> <div class="container-fluid"> <div class="navbar-header"> <a class="navbar-brand" ui-sref="movies">The Movie App</a> </div> <div class="collapse navbar-collapse"> <ul class="nav navbar-nav"> <li class="active"><a ui-sref="movies">Home</a></li> </ul> </div> </div> </nav> <div class="container"> <div class="row top-buffer"> <div class="col-xs-8 col-xs-offset-2"> <div ui-view></div> <!-- This is where our views will load --> </div> </div> </div> <script type="text/javascript" src="lib/angular.min.js"></script> <script type="text/javascript" src="js/app.js"></script> <script type="text/javascript" src="js/controllers.js"></script> <script type="text/javascript" src="js/services.js"></script> <script type="text/javascript" src="lib/angular-ui-router.min.js"></script> <script type="text/javascript" src="lib/angular-resource.min.js"></script> </body> </html>
Разметка довольно понятна. Просто обратите особое внимание на <div ui-view></div>
. Директива ui-view
исходит из модуля UI Router и действует как контейнер для наших представлений.
Создание основного модуля и состояний
Наш основной модуль и состояния определены в js/app.js
:
app.js:
angular.module('movieApp', ['ui.router', 'ngResource', 'movieApp.controllers', 'movieApp.services']); angular.module('movieApp').config(function($stateProvider) { $stateProvider.state('movies', { // state for showing all movies url: '/movies', templateUrl: 'partials/movies.html', controller: 'MovieListController' }).state('viewMovie', { //state for showing single movie url: '/movies/:id/view', templateUrl: 'partials/movie-view.html', controller: 'MovieViewController' }).state('newMovie', { //state for adding a new movie url: '/movies/new', templateUrl: 'partials/movie-add.html', controller: 'MovieCreateController' }).state('editMovie', { //state for updating a movie url: '/movies/:id/edit', templateUrl: 'partials/movie-edit.html', controller: 'MovieEditController' }); }).run(function($state) { $state.go('movies'); //make a transition to movies state when app starts });
Итак, наше приложение имеет следующие четыре состояния:
-
movies
-
viewMovie
-
newMovie
-
editMovie
Каждое состояние состоит из url
, templateUrl
и controller
. Также обратите внимание, что когда наш основной модуль загружен, мы осуществляем переход к movies
о состоянии, показывая все фильмы в нашей системе. Посмотрите на следующий снимок экрана, чтобы узнать, какому состоянию соответствует какой URL.
Создание шаблонов
Все наши шаблоны находятся внутри partials
. Давайте посмотрим, что делает каждый из них!
_form.html:
_form.html
содержит простую форму, которая позволяет пользователям вводить данные. Обратите внимание, что эта форма будет включена в movie-add.html
и movie-edit.html
потому что они оба принимают входные данные от пользователей.
Вот содержимое _form.html
:
<div class="form-group"> <label for="title" class="col-sm-2 control-label">Title</label> <div class="col-sm-10"> <input type="text" ng-model="movie.title" class="form-control" id="title" placeholder="Movie Title Here"/> </div> </div> <div class="form-group"> <label for="year" class="col-sm-2 control-label">Release Year</label> <div class="col-sm-10"> <input type="text" ng-model="movie.releaseYear" class="form-control" id="year" placeholder="When was the movie released?"/> </div> </div> <div class="form-group"> <label for="director" class="col-sm-2 control-label">Director</label> <div class="col-sm-10"> <input type="text" ng-model="movie.director" class="form-control" id="director" placeholder="Who directed the movie?"/> </div> </div> <div class="form-group"> <label for="plot" class="col-sm-2 control-label">Movie Genre</label> <div class="col-sm-10"> <input type="text" ng-model="movie.genre" class="form-control" id="plot" placeholder="Movie genre here"/> </div> </div> <div class="form-group"> <div class="col-sm-offset-2 col-sm-10"> <input type="submit" class="btn btn-primary" value="Save"/> </div> </div>
Шаблон использует ng-model
для привязки различных деталей фильма к различным свойствам модели scope
.
movie-add.html
Этот шаблон используется для приема пользовательских данных и добавления нового фильма в нашу систему. Вот содержание:
<form class="form-horizontal" role="form" ng-submit="addMovie()"> <div ng-include="'partials/_form.html'"></div> </form>
Когда форма отправляется, addMovie()
функция области addMovie()
которая, в свою очередь, отправляет POST-запрос на сервер для создания нового фильма.
movie-edit.html:
Этот шаблон используется для приема пользовательских данных и обновления существующего фильма в нашей системе.
<form class="form-horizontal" role="form" ng-submit="updateMovie()"> <div ng-include="'partials/_form.html'"></div> </form>
Как только форма отправлена, updateMovie()
функция области updateMovie()
которая updateMovie()
PUT-запрос на сервер для обновления фильма.
movie-view.html:
Этот шаблон используется для отображения подробностей об одном фильме. Содержание выглядит следующим образом:
<table class="table movietable"> <tr> <td><h3>Details for {{movie.title}}</h3></td> <td></td> </tr> <tr> <td>Movie Title</td> <td>{{movie.title}}</td> </tr> <tr> <td>Director</td> <td>{{movie.director}}</td> </tr> <tr> <td>Release Year</td> <td>{{movie.releaseYear}}</td> </tr> <tr> <td>Movie Genre</td> <td>{{movie.genre}}</td> </tr> </table> <div> <a class="btn btn-primary" ui-sref="editMovie({id:movie._id})">Edit</a> </div>
В конце есть кнопка редактирования. После нажатия он меняет состояние на editMovie
с id
фильма в $stateParams
.
movies.html
Этот шаблон отображает все фильмы в системе.
<a ui-sref="newMovie" class="btn-primary btn-lg nodecoration">Add New Movie</a> <table class="table movietable"> <tr> <td><h3>All Movies</h3></td> <td></td> </tr> <tr ng-repeat="movie in movies"> <td>{{movie.title}}</td> <td> <a class="btn btn-primary" ui-sref="viewMovie({id:movie._id})">View</a> <a class="btn btn-danger" ng-click="deleteMovie(movie)">Delete</a> </td> </tr> </table>
Он просматривает все объекты movie
полученные из API, и отображает детали. Также есть кнопка « Add New Movie
которая изменяет состояние на « newMovie
. В результате загружается новый маршрут, и мы можем создать новую запись в фильме.
Для каждого фильма есть две кнопки, View
и Delete
. View
запускает изменение состояния, так что отображаются подробные сведения о фильме. Кнопка Delete
удаляет фильм навсегда.
Создание контроллеров
У каждого государства есть контроллер. Итак, всего у нас есть четыре контроллера для четырех состояний. Все контроллеры идут в js/controllers.js
. Контроллеры просто используют наш пользовательский сервис Movie
и работают так, как мы обсуждали выше. Итак, вот как выглядят наши контроллеры.
controllers.js:
angular.module('movieApp.controllers', []).controller('MovieListController', function($scope, $state, popupService, $window, Movie) { $scope.movies = Movie.query(); //fetch all movies. Issues a GET to /api/movies $scope.deleteMovie = function(movie) { // Delete a movie. Issues a DELETE to /api/movies/:id if (popupService.showPopup('Really delete this?')) { movie.$delete(function() { $window.location.href = ''; //redirect to home }); } }; }).controller('MovieViewController', function($scope, $stateParams, Movie) { $scope.movie = Movie.get({ id: $stateParams.id }); //Get a single movie.Issues a GET to /api/movies/:id }).controller('MovieCreateController', function($scope, $state, $stateParams, Movie) { $scope.movie = new Movie(); //create new movie instance. Properties will be set via ng-model on UI $scope.addMovie = function() { //create a new movie. Issues a POST to /api/movies $scope.movie.$save(function() { $state.go('movies'); // on success go back to home ie movies state. }); }; }).controller('MovieEditController', function($scope, $state, $stateParams, Movie) { $scope.updateMovie = function() { //Update the edited movie. Issues a PUT to /api/movies/:id $scope.movie.$update(function() { $state.go('movies'); // on success go back to home ie movies state. }); }; $scope.loadMovie = function() { //Issues a GET request to /api/movies/:id to get a movie to update $scope.movie = Movie.get({ id: $stateParams.id }); }; $scope.loadMovie(); // Load a movie which can be edited on UI });
Вывод
Предполагая, что приложение развернуто в localhost/movieApp
, вы можете получить к нему доступ по адресу http://localhost/movieApp/index.html
. Если вы любитель кино, вы можете начать добавлять свои любимые фильмы тоже! Исходный код приложения, разработанного в этой статье, доступен для скачивания на GitHub .
Я надеюсь, вам понравился этот урок! Не стесняйтесь комментировать, если вы хотите что-то добавить.