Большинство одностраничных приложений включают операции 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 . 
Я надеюсь, вам понравился этот урок! Не стесняйтесь комментировать, если вы хотите что-то добавить.


