#실행결과 http://study.jeju.onl/ch21/products.html
#Deployd 대시보드 http://study.jeju.onl:5500/dashboard/products/data/
이 장에서는 AngularJS의 RESTful 웹 서비스 지원 기능을 설명한다.
RESTful 서비스를 이용하기 위해서는 명시적으로 URL을 호출하는 $http 또는 요청 URL을 노출하지 않고 이용할 수 있는 $resource 를 사용한다. (내부적으로는 $resouce가 $http를 이용해 작동)
RESTful 서비스를 제공하는 백엔드 서비스는 Deployd가 담당한다.
* 데이터 및 객체 조회: GET /products, /products/<id>
* 객체 생성: POST /products
* 객체 수정: PUT /products/<id>
* 객체 삭제: DELETE /products/<id>
일반적으로 HTTP의 메소드들과 이와 같은 방식으로 연결되어 있기는 하지만, 어떤 서비스들은 그렇지 않은 경우도 있으니 반드시 확인을 해야한다. 일치하지 않는 경우는 config를 통해 메소드들을 Action과 맞게 정의해야 한다.
- $http 서비스 활용
– baseUrl: constant() 에서 정의
– 조회: $http.get( <url> )
– 삭제: $http({ method: “DELETE”, url: <url> })
– 생성: $http.post( <url>, <객체> )
– 수정: $http({ method: “PUT”, url: <url>+<id>, data: <객체> })
– **문제: 로컬데이터와 서버데이터의 동기화 작업
ex) 사용자 화면에서는 값이 증가되었는데, 서버에서 처리가 안된 경우 - $resource 서비스 활용
– ngResource 모듈 로드: angular-resource.js
– 의존성 정의: module에 “ngResource”, controller에 $resource 선언
– 조회: $resource.query(), $resource.get( <id> )
– 삭제: $resource.delete(), $resource.remove()
– 수정: $resource.save()
– 생성: new 연산자 + $resource.create() 또는 $resource.save()
이에 대한 소스코드이다
1) /ch21/products.html
<!DOCTYPE html> <html ng-app="exampleApp"> <head> <title>Ch21. REST서비스 - ProAngularJS</title> <link href="/node_modules/bootstrap/dist/css/bootstrap.css" rel="stylesheet" /> <link href="/node_modules/bootstrap/dist/css/bootstrap-theme.css" rel="stylesheet" /> /node_modules/angular/angular.js /node_modules/angular/angular-resource.js http://products.js http://increment.js </head> <body ng-controller="defaultCtrl"></body> </html>Products
2) /ch21/tableView.html
Name Category Price {{ item.name }} {{ item.category }} {{ item.price | currency }} Delete Edit --> Refresh New</div>3) /ch21/editorView.html
Name:Category:Price:<button class="btn btn-primary" ng-click="saveEdit(currentProduct)">Save</button> <button class="btn btn-primary" ng-click="cancelEdit()">Cancel</button> </div>4) /ch21/products.js
angular.module("exampleApp", ["increment","ngResource"]) .constant("baseUrl", "http://study.jeju.onl:5500/products/") .controller("defaultCtrl", function( $scope, $http, $resource, baseUrl) { $scope.displayMode = "list"; $scope.currentProduct = null; // ':'접두어로 파라미터를 정의하고, 뒤에 객체에서 @컬럼명과 매칭한다 $scope.productsResource = $resource( baseUrl+":id", { id: "@id"}, // method는 Ajax 요청에 사용할 HTTP 방식을 설정한다 { create: { method: "POST"}, save: { method: "PUT"}} ); // 이 외에도 params, url, isArray 등과 $http의 config 항목인 // transformRequest, cache, timeout, withCredentials 등등 가능 $scope.listProducts = function() { /* $scope.products = [ { id: 0, name: "dummy0", category: "Test", price: 1.24 }, { id: 1, name: "dummy1", category: "Test", price: 2.36 }, { id: 2, name: "dummy2", category: "Test", price: 3.48 } ]; */ /* $http.get(baseUrl).success( function( data) { $scope.products = data; }); */ $scope.products = $scope.productsResource.query(); $scope.products.$promise.then( function( data) { // Do something }); } $scope.deleteProduct = function( product) { /* $http({ method: "DELETE", url: baseUrl+product.id }).success( function() { $scope.products.splice( $scope.products.indexOf(product), 1); }); */ product.$delete().then( function() { $scope.products.splice( $scope.products.indexOf(product), 1); }); $scope.displayMode = "list"; } $scope.createProduct = function( product) { /* $http.post( baseUrl, product).success( function( newProduct) { $scope.products.push( newProduct); $scope.displayMode = "list"; }); */ //new $scope.productsResource( product).$save().then( function( newProduct) { // new 연산자와 함께 save() 또는 create() 사용 가능 new $scope.productsResource( product).$create().then( function( newProduct) { $scope.products.push( newProduct); $scope.displayMode = "list"; }); } $scope.updateProduct = function( product) { /* $http({ method: "PUT", url: baseUrl+product.id, data: product }).success( function( modifiedProduct) { for( var i=0; i<$scope.products.length; i++) { if( $scope.products[i].id == modifiedProduct.id) { $scope.products[i] = modifiedProduct; break; } } $scope.displayMode = "list"; }); */ product.$save(); $scope.displayMode = "list"; } $scope.editOrCreateProduct = function( product) { /* // $http 방식에서 원본 데이터에 영향을 안주기 위해서 copy 한다 $scope.currentProduct = product ? angular.copy( product) : {}; */ // $resource 방식에서는 데이터를 감시해야 하므로 원본을 그대로 사용한다 $scope.currentProduct = product ? product : {}; $scope.displayMode = "edit"; } $scope.saveEdit = function( product) { if( angular.isDefined( product.id)) { //console.log("updateProduct "+product.id+": "+product.name+"/"+product.category); $scope.updateProduct( product); } else { $scope.createProduct( product); } } $scope.cancelEdit = function() { // 존재하던 데이터를 수정 취소한다면, 기존 데이터를 다시 $get 한다 // $get 호출 전에 호출이 가능한지 검사 if( $scope.currentProduct && $scope.currentProduct.$get) { $scope.currentProduct.$get(); } $scope.currentProduct = {}; $scope.displayMode = "list"; } $scope.listProducts(); });5) /ch21/increment.js
angular.module("increment", []) /* .directive("increment", function() { return { restrict: "E", scope: { value: "=value" }, link: function( scope, element, attrs) { var button = angular.element( "<button>").text( "+"); button.addClass( "btn btn-primary btn-xs"); element.append( button); button.on("click", function() { scope.$apply( function() { scope.value++; }); }) } }; }) */ .directive("increment", function() { return { restrict: "E", scope: { item: "=item", property: "@propertyName", restful: "@restful", method: "@methodName" }, link: function( scope, element, attrs) { var button = angular.element( "<button>").text( "+"); button.addClass( "btn btn-primary btn-xs"); element.append( button); button.on("click", function() { scope.$apply( function() { scope.item[ scope.property]++; if( scope.restful) { scope.item[ scope.method](); } }); }) } }; }) .directive("decrement", function() { return { restrict: "E", scope: { item: "=item", property: "@propertyName", restful: "@restful", method: "@methodName" }, link: function( scope, element, attrs) { var button = angular.element( "<button>").text( "-"); button.addClass( "btn btn-primary btn-xs"); element.append( button); button.on("click", function() { scope.$apply( function() { if( scope.item[ scope.property] > 1.0) { scope.item[ scope.property]--; } if( scope.restful) { scope.item[ scope.method](); } }); }) } }; });