블로그 이미지
윤영식
Frontend Application Architecter, Full Stacker, KnowHow Dispenser and Bike Rider

Publication

Statistics Graph

Recent Comment

2013.11.27 04:20 My Projects/BI Dashboard

Angular.js에서 Controller는 View DOM객체의 표현을 담당한다면 Service 모듈은 View단에 표현되는 NonVisible한 비즈니스 업무 데이터를 담당한다. 이전 블로그까지 모든 데이터 핸들링은 Controller에 담겨 있었는데 이를 Service 모듈로 분리 래팩토링 해보자. 또한 모듈분리를 하면서 $http를 추상화한 $resource를 사용해 보자 




1. factory vs service 무엇을 쓸까? (참조)

  - <module>.factory는 객체를 new 해서 return 해 주어야 한다. 

    따라서 별도의 function을 정의하고 해당 펑션을 new해서 return하는 구조일 때 사용

  - <module>.service는 function의 내용을 정의하고 new한 객체를 return할 필요가 없다 

  - <module>.factory 는 공통된 것을 function으로 이미 만들어 놓은 것을 사용할 때. 즉, 공통 모듈 <Module>Svc 에 생성

  - <module>.service 는 업무적으로 $resource/$http 이용하여 RESTful API 호출할  때. 즉 업무 모듈 <Module>Biz 에 생성



2. $resource 사용하기 

  - $resource 객체는 RESTful 클라이언트 애플리케이션 개발을 위한 $http 상위 레벨의 서비스이다

  - http에 대한 보다 정교한 header configuration이 가능하다

  - resource interceptors 가 있다

  - 보다 향상된 promises api를 제공한다 

  - API

// 사용법

$resource(url[, paramDefaults][, actions]);


// 의미 

- url

  string 이고, /user/:username 처럼 :<param> 형태로 입력가능하다. 전체 url을 넣어도 됨 


- paramDefaults 

  url parameters object 이고, /path/:verb 일때 파라미터값이 {verb:'greet', name:'dowon'} 이면

  url 결과는 /path/greet?name=dowon 으로 간다. @가 앞에 붙으면 전달하는 객체(json)에서 값을 추출하여 대입한다

 예) 

  var User = $resource('/user/:userId', {userId:'@id'});

  var user = User.get({userId:123}, function() {

     user.abc = true;

     user.$save();

  });


- actions 

  object.<object> 이고, $http.config의 action 을 상속받으며 사용자 정의하여 action을 추가할 수 있다.

  형식)

  {action1: {method:?, params:?, isArray:?, headers:?, ...},

   action2: {method:?, params:?, isArray:?, headers:?, ...},

   ...}


- 리턴타입

  기본적으로 resource에서 제공하는 action과 사용자 정의 action을 담아서 리턴한다 

  기본 action)

{ 'get':    {method:'GET'},

  'save':   {method:'POST'},

  'query':  {method:'GET', isArray:true},

  'remove': {method:'DELETE'},

  'delete': {method:'DELETE'} };



3. $resource 공통 모듈 만들기

  - $resource의 기본 action을 보면 'update': {method:'PUT'} 이 존재하지 않는다. 이를 custom action으로 포함시키고 공통 모듈로 만들어 보자. 

  - 공통 모듈을 만들기 전에 ngResource를 추가해야 한다. 애플리케이션에 없다는 다음과 과정을 거친다 

// bower로 angular resource 설치 및 bower.json에 추가 

$ bower install angular-resource --save


// index.html 에 추가 

<!-- build:js scripts/core.js -->

    <script src="bower_components/jquery/jquery.js"></script>

    <script src="bower_components/jquery-migrate/jquery-migrate.min.js"></script>

    <script src="bower_components/angular/angular.js"></script>

    <script src="bower_components/angular-route/angular-route.js"></script>

    <script src="bower_components/angular-resource/angular-resource.js"></script>

    <script src="bower_components/angular-cookies/angular-cookies.js"></script>

    <script src="bower_components/angular-sanitize/angular-sanitize.js"></script>

<!-- endbuild --> 


// ngResource가 필요한 모듈에 추가하기  

var DashboardApp = angular.module('DasbhoardApp', [

  'ngRoute',                                                  

  'ngCookies',

  'ngResource',

  'ngSanitize',

  'DasbhoardApp.CommonCtrl',

  'DasbhoardApp.RestTestBiz',

]);

  - $resource를 이용하여 공통 모듈을 만들어 보자 

// common/services/RestfulSvc.js 파일을 생성한다 

'use strict';

// 모듈 명칭 정의

var RestfulSvc = angular.module('DasbhoardApp.RestfulSvc', []);


RestfulSvc.factory('RestfulSvcApi', ['$resource', function ($resource) {

/**

* Restful API version. it must be attached the called url.

*/

var prefixUrl = '/api/v1';

/**

* @domain : biz object ex) person

* @key : biz object id ex) persion id is 123

* @action : server action name. if it exist, you can define or not.

* ex) http://www.bennadel.com/blog/2433-Using-RESTful-Controllers-In-An-AngularJS-Resource.htm

* add a update action for method of PUT

*/

return $resource( 

                           // 호출하는 url 형식

    prefixUrl + '/:domain/:key/:action',

                           // 호출 url 형식의 :domain :key :action 에 동적으로 받게 되는 파라미터

   {

    domain: "@domain",

    key: "@key",

    action: "@action"

   },

                            // 추가 action들 one, all, update 

   {

     one: { method: 'GET', isArray: false },

     all: { method: 'GET', isArray: true },

      update: {method:'PUT'}

    }

   );

}]);


// 새롭게 서비스 공통 모듈을 생성하였으니 index.html 과 DashboardApp 모듈에 추가한다 
// index.html 에서 
    <script src="scripts/common/controllers/CommonCtrl.js"></script>
    <script src="scripts/common/services/RestfulSvc.js"></script>

// DashboardApp.js 에서 
var DashboardApp = angular.module('DasbhoardApp', [
  'ngRoute',                                                  
  'ngCookies',
  'ngResource',
  'ngSanitize',
  'DasbhoardApp.CommonCtrl',
  'DasbhoardApp.RestTestBiz',
  'DasbhoardApp.RestfulSvc'
]);

 


4. Controller에 Service 모듈의 서비스 호출하기 

  - $http 방식을 $resource에 대한 추상 모듈 서비스로 대체한다

  - 기존의 RestTestBiz.js의 "RestTestBiz.personCtrl" 컨트롤러 로직을 리팩토링한다 

'use strict';

// 의존하는 모듈 주입

var RestTestBiz = angular.module('DasbhoardApp.RestTestBiz', ['DasbhoardApp.RestfulSvc']);


// 사용할 모듈의 서비스 주입 

RestTestBiz.controller('RestTestBiz.personCtrl', ['$scope', 'RestfulSvcApi', function ($scope, RestfulSvcApi) {


  var bizDomain = 'person';

  var load = function () {

    console.log('before calling...');

       // 사용자 정의 액션 all 호출 

    RestfulSvcApi.all(

    // param key domain의 값인 bizDomain는 @domain에 맵핑된다 

    { domain: bizDomain },

    // success

    function (response) {

    $scope.persons = response;

    console.log('persons is ', $scope.persons);

    }, 

    // error

    function (response) {

    });

     };


  load();


  $scope.save = function () {

  console.log('Save Person : ', $scope.person);

  RestfulSvcApi.save(

                        // 파라미터를 열거하면 하나의 json 객체로 합쳐준다 

  { domain: bizDomain }, $scope.person,

  function(response) { load(); },

  function(response) {}

  );

  };


  $scope.update = function () {

  console.log('Update Person : ', $scope.person);

  RestfulSvcApi.update(

  { domain: bizDomain }, $scope.person ,

  function(response) { load(); },

  function(response) {}

  );

  };


  $scope.deletePerson = function(personId) {

  console.log('Delete Person : ', personId);

  RestfulSvcApi['delete'](

  { domain: bizDomain, key: personId }, 

  function(response) { load(); },

  function(response) {}

  );

  };


}]);

  - RestfulSvcApi 와 같은 모듈 서비스를 해주는 추상화 공개 모듈이 존재한다. (참조)

    + Restangular

    + ModelCore

    + Angular-ServiceStack

    + Angular-ActiveRecord

    + Breezejs


  * 소스 : https://github.com/ysyun/SPA_Angular_SpringFramework_Eclipse_ENV/tree/feature_angular_service



<참조>

  - Angular.js v1.2 에서 향상된 내역들

  - ngResource를 통하여 트위터 연동하기 예제

  - $resource를 통한 RESTful API 구현하기 (필독)

  - rest 스타일의 ORM 모듈들

저작자 표시 비영리 변경 금지
신고
posted by peter yun 윤영식
2013.08.22 15:36 AngularJS/Concept

AngularJS의 서비스 종류는 여러가지가 있습니다. Factory만 알았다면 Costant, Value, Provider 그리고 서비스의 확장 방법등에 대해 알아보자. "AngularJS Service is an Application Brain"



1. 개념

  - 서비스는 여러 종류가 있다

  - 항시 Singleton 이다. 즉, 하나의 Instance만 존재한다 



2. Constant

  - 공통으로 사용하는 환경값을 지정할 때 상수를 지정한다 

  - 사용자 정의 Directive에서 환경값을 가져다 쓰고 싶을 때 유용하다

// script 

app = angular.module("app", []);


app.controller('MainCtrl', function($scope, fooConfig) {

  $scope.fooConfig = fooConfig;

});


app.constant('fooConfig', {

  config1: true,

  config2: "Default config2"

});


// html

<!DOCTYPE html>

<html ng-app="app">

<head>

<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.0.7/angular.min.js"></script>

<meta charset=utf-8 />

<title>JS Bin</title>

</head>

  <body ng-controller="MainCtrl">

    config1: {{fooConfig.config1}}

    <br />

    config2: {{fooConfig.config2}}

  </body>

</html>



3. Value

  - Value Service는 Constant Service와 유사하지만 최초 한번은 변경을 할 수 있다. Factory 서비스의 작은 동생뻘이다 

  - Directive에서 사용하기 유용하다 

  - Value를 가지고 계산할 수는 없다 

// script 

app = angular.module("app", []);


app.controller('MainCtrl', function($scope, fooConfig) {

  $scope.fooConfig = fooConfig;

  // 최초 한번의 변경이 가능

  angular.extend(fooConfig, {config3: "I have been extended"}); 

});


app.value('fooConfig', {

  config1: true,

  config2: "Default config2 but it can changes"

});


// html 

<!DOCTYPE html>

<html ng-app="app">

<head>

<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.0.7/angular.min.js"></script>

<meta charset=utf-8 />

<title>JS Bin</title>

</head>

  <body ng-controller="MainCtrl">

    config1: {{fooConfig.config1}}

    <br />

    config2: {{fooConfig.config2}}

    <br />

    config3: {{fooConfig.config3}}

  </body>

</html>



4. Factory

  - 가장 일반적으로 사용하는 서비스 종류이다 

  - 팩토리는 어떤 형태의 데이터타입이라도 리턴할 수 있다. 리턴된 오브젝트를 가지고 작업을 한다 

  - 단, 리턴된 오브젝트의 값을 변경하면 해당 팩토리의 인스턴스를 사용하는 모든 곳에 변경값이 반영된다 

  - Revealing Module Pattern 식으로 작성을 한다 

// script 

app = angular.module("app", []);

app.controller('MainCtrl', function($scope, foo) {

  $scope.foo = foo;

});


// revealing module pattern 방식의 팩토리 서비스

app.factory('foo', function() {

  var thisIsPrivate = "Private";

  function getPrivate() {

    return thisIsPrivate;

  }

  return {

    variable: "This is public",

    getPrivate: getPrivate

  };

});


// html

<!DOCTYPE html>

<html ng-app="app">

<head>

<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.0.7/angular.min.js"></script>

<meta charset=utf-8 />

<title>JS Bin</title>

</head>

  <body ng-controller="MainCtrl">

    public variable: {{foo.variable}}

    <br />

    private variable (through getter): {{foo.getPrivate()}}

  </body>

</html>



5. Service 

  - "service" service는 Factory service와 유사사지만 생성자명칭(== 서비스명칭)을 넘기면 자동으로 new를 통하여 생성된다 

    즉, Factory 처럼 오브젝트의 리턴이 필요없다 

  - 하기 예는 완전히 동일하다. 주의 할 것은 factory안에서 또는 service사용시 new을 여러번 하여도 반드시 한번만 인스턴스화 한다

    즉, Singleton 패턴으로 객체 하나만이 생성된다 

// script 

app = angular.module("app", []);


// Service를 통해서 생성 

app.controller('MainCtrl', function($scope, foo) {

  $scope.foo = foo;

});


app.service('foo', function() {

  var thisIsPrivate = "Private";

  this.variable = "This is public";

  this.getPrivate = function() {

    return thisIsPrivate;

  };

});


// Factory를 통해서 생성 : MainCtrl 에서 foo 파라미터 값과 하위 로직을 foo2로 바꿔도 동일 결과 

app.controller('MainCtrl', function($scope, foo2) {

  $scope.foo = foo2;

});


app.factory('foo2', function() {

  return new Foobar();

});


function Foobar() {

  var thisIsPrivate = "Private";

  this.variable = "This is public";

  this.getPrivate = function() {

    return thisIsPrivate;

  };

}


// 또는 펑션을 넘길 수도 있다 : : MainCtrl 에서 foo 파라미터 값과 하위 로직을 foo3로 바꿔도 동일 결과 

app.controller('MainCtrl', function($scope, foo3) {

  $scope.foo = foo3;

});


app.service('foo3', Foobar);


// html 

<!DOCTYPE html>

<html ng-app="app">

<head>

<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.0.7/angular.min.js"></script>

<meta charset=utf-8 />

<title>JS Bin</title>

</head>

  <body ng-controller="MainCtrl">

    public variable: {{foo.variable}}

    <br />

    private variable (through getter): {{foo.getPrivate()}}

  </body>

</html>

 


6. Provider

  - Provider는 factory의 큰형님 뻘이다 

  - $get fuction을 가지고 있어야 한다. 만일 Provider 명칭이 foo이고 controller에 inject될 때 실제는 $get function의 결과값이 inject되는 것이다. 왜 factory로 사용하면 간편한데 굳이 이렇게 할까?

  - 이유인즉슨, config function을 통해서 provide에 대한 환경설정을 할 수 있기 때문이다 

  - 예를 보자 

    1) thisIsPrivate 변수가 $get 펑션 밖에 위치한다 

    2) 따라서 setPrivate 메소드를 통하여 thisIsPrivate 변수값을 변경할 수 있다 

    3) 이런게 하는 이유는 우리가 필요로 하는 다양한 환경값을 바꾸어서 환경설정을 하고 싶기 때문이다

    4) Provider명칭이 'foo' 이므로 app.config 에서 'fooProvider' 명을 준다 

// script 

app = angular.module("app", []);

app.controller('MainCtrl', function($scope, foo) {

  $scope.foo = foo;

});


app.provider('foo', function() {

  var thisIsPrivate = "Private";

  return {

    setPrivate: function(newVal) {

      thisIsPrivate = newVal; 

    },

    

    $get: function() {

      function getPrivate() {

        return thisIsPrivate;

      }


      return {

        variable: "This is public",

        getPrivate: getPrivate

      };

    } 

  };

});


app.config(function(fooProvider) {

  fooProvider.setPrivate('New value from config');

});


// html 

<!DOCTYPE html>

<html ng-app="app">

<head>

<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.0.7/angular.min.js"></script>

<meta charset=utf-8 />

<title>JS Bin</title>

</head>

  <body ng-controller="MainCtrl">

    public variable: {{foo.variable}}

    <br />

    private variable (through getter): {{foo.getPrivate()}}

  </body>

</html>



7. Decorator (보너스1)

  - 기존 서비스에 새로운 펑션을 넣고 싶을 경우 사용한다

  - 예를 보자 

    1) $provide는 모든 서비스들을 내부적으로 생성할 때 사용한다 

    2) $provide.decorator 를 통해서 서비스를 확장할 수 있다. 

        첫번째 인자는 서비스 명칭, 두번째는 콜백으로 서비스 인스턴스인 $delegate을 받는다 

    3) $delegate에 greet 라는 메소드를 확장 정의 한다 

    4) 만일 3rd party 라이브러리 사용시 확장하고 싶을 경우 decorator를 사용한다 

// script 

app = angular.module("app", []);


app.controller('MainCtrl', function($scope, foo) {

  $scope.foo = foo;

});


app.factory('foo', function() {

  var thisIsPrivate = "Private";

  function getPrivate() {

    return thisIsPrivate;

  }

  return {

    variable: "This is public",

    getPrivate: getPrivate

  };

});


app.config(function($provide) {

  $provide.decorator('foo', function($delegate) {

    $delegate.greet = function() {

      return "Hello, I am a new function of 'foo'";

    };

    

    return $delegate;

  });

});


// html 

<!DOCTYPE html>

<html ng-app="app">

<head>

<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.0.7/angular.min.js"></script>

<meta charset=utf-8 />

<title>JS Bin</title>

</head>

  <body ng-controller="MainCtrl">

    public variable: {{foo.variable}}

    <br />

    private variable (through getter): {{foo.getPrivate()}}

    <br />

    greet: {{foo.greet()}}

  </body>

</html>



8. 새로운 인스턴스 생성하기 (보너스2)

  - 팩토리 메소드를 두어서 호출시 마다 무언가 새로 생성하고 싶을 경우 사용한다 

  - 예를 보자 

    1) Function 1급 class인 Person을 만든다 

    2) MainCtrl, SecondCtrl 각각 getById(1) 을 호출하지만 서로 다른 인스턴스가 전달되었다

    3) "Update It" 버튼을 클릭하면 두번째 열만 "Dave - Canada" 로 바뀜을 확인 할 수 있다 

// script 

app = angular.module('app', []);

app.controller('MainCtrl', function($scope, personService) {

  $scope.aPerson = Person.getById(1);

});

app.controller('SecondCtrl', function($scope, personService) {

  $scope.aPerson = Person.getById(1);

  $scope.updateIt = function() {

    $scope.aPerson.update();

  };

});


// Our class

function Person( json ) {

  angular.extend(this, json);

}


Person.prototype = {

  update: function() {

    // Update it (With real code :P)

    this.name = "Dave";

    this.country = "Canada";

  }

};


Person.getById = function( id ) {

  // Do something to fetch a Person by the id

  return new Person({

    name: "Jesus",

    country: "Spain"

  });

};


// Our factory

app.factory('personService', function() {

  return {

    getById: Person.getById

  };

});


// html

<!DOCTYPE html>

<html ng-app="app">

<head>

<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.0.7/angular.min.js"></script>

<meta charset=utf-8 />

<title>JS Bin</title>

</head>

  <body>

    <p ng-controller="MainCtrl">{{aPerson.name}} - {{aPerson.country}}</p>

    <div ng-controller="SecondCtrl">

      {{aPerson.name}} - {{aPerson.country}}

      <button ng-click="updateIt()">Update it</button>

    </div>

  </body>

</html>

  - 위의 script 코드를 CoffeeScript로 하면 class 표현을 쉽게 할 수 있다 

app.controller 'MainCtrl', ($scope, personService) -> $scope.aPerson = personService.getById(1) app.controller 'SecondCtrl', ($scope, personService) -> $scope.aPerson = personService.getById(2) $scope.updateIt = () -> $scope.aPerson.update() class Person constructor: (json) -> angular.extend @, json update: () -> @name = "Dave" @country = "Canada" @getById: (id) -> new Person name: "Jesus" country: "Spain" app.factory 'personService', () -> { getById: Person.getById 

}



<참조>

  - 원문 : Understanding Service Types

  - 가장 잘 설명된 글 : http://stackoverflow.com/questions/15666048/angular-js-service-vs-provider-vs-factory (강추)

  - 관련글 : GDG Korea WebTech 고재도님 링크

  - AngularJS Creating Service 매뉴얼

  - 이미지 : AngularJS Large Scale Architecture

  - JavaScript에서 객체를 만드는 3가지 방법

  - $provider를 통한 생성

저작자 표시 비영리 변경 금지
신고
posted by peter yun 윤영식
prev 1 next