Статьи

Создание приложения CRUD в считанные минуты с помощью $ Angular ресурса

Большинство одностраничных приложений включают операции 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 в следующем формате:

Конечные точки 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 }); 

Результатом вызова функции является объект класса ресурсов, который по умолчанию имеет следующие пять методов:

  1. get()
  2. query()
  3. save()
  4. remove()
  5. 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 ):

  1. $save()
  2. $delete()
  3. $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() , она делает следующее:

  1. AngularJS знает, что функция $update() будет инициировать запрос PUT на URL /api/entries/:id .
  2. Он читает значение $scope.entry._id , присваивает значение :id и генерирует URL.
  3. Посылает запрос 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.

REST 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 }); 

Итак, наше приложение имеет следующие четыре состояния:

  1. movies
  2. viewMovie
  3. newMovie
  4. 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 .

Я надеюсь, вам понравился этот урок! Не стесняйтесь комментировать, если вы хотите что-то добавить.