ProAngularJS Ch.21 REST 서비스

#실행결과 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">

  

Products

</body> </html>

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]();
          }
        });
      })
    }
  };

});

답글 남기기

아래 항목을 채우거나 오른쪽 아이콘 중 하나를 클릭하여 로그 인 하세요:

WordPress.com 로고

WordPress.com의 계정을 사용하여 댓글을 남깁니다. 로그아웃 / 변경 )

Twitter 사진

Twitter의 계정을 사용하여 댓글을 남깁니다. 로그아웃 / 변경 )

Facebook 사진

Facebook의 계정을 사용하여 댓글을 남깁니다. 로그아웃 / 변경 )

Google+ photo

Google+의 계정을 사용하여 댓글을 남깁니다. 로그아웃 / 변경 )

%s에 연결하는 중

%d 블로거가 이것을 좋아합니다: