모델 데이터와 행동을 어디에 둘 것인가? [tl; 닥터; 서비스 이용]


341

최신 프로젝트를 위해 AngularJS와 협력하고 있습니다. 설명서 및 자습서에서 모든 모델 데이터는 컨트롤러 범위에 포함됩니다. 컨트롤러에 사용할 수 있어야하므로 해당보기 내에 있어야합니다.

그러나 모델이 실제로 구현되어야한다고 생각하지 않습니다. 예를 들어 복잡하고 개인 속성이있을 수 있습니다. 또한 다른 컨텍스트 / 앱에서 재사용하고 싶을 수도 있습니다. 컨트롤러에 모든 것을 넣으면 MVC 패턴이 완전히 중단됩니다.

모든 모델의 동작에 대해서도 마찬가지입니다. DCI 아키텍처를 사용 하고 데이터 모델과 동작을 분리하려면 동작을 유지하기 위해 추가 객체를 도입해야합니다. 이것은 역할과 상황을 소개함으로써 이루어집니다.

DCI == D ATA C ollaboration I nteraction

물론 모델 데이터와 동작은 일반 자바 스크립트 객체 또는 "클래스"패턴으로 구현할 수 있습니다. 그러나 AngularJS 방법은 무엇입니까? 서비스를 사용하십니까?

따라서이 질문에 귀착됩니다.

AngularJS 모범 사례에 따라 컨트롤러에서 분리 된 모델을 어떻게 구현합니까?


12
DCI를 정의하거나 적어도 철자 양식을 제공 할 수 있다면이 질문에 찬성 투표 할 것입니다. 나는 어떤 소프트웨어 문헌에서도이 약어를 본 적이 없다. 감사.
Jim Raden

13
방금 DCI에 대한 링크를 참조로 추가했습니다.
Nils Blum-Oeste

1
@JimRaden DCI는 Dataq, Context, interaction이며 MVC (Trygve Reenskauge)의 아버지에 의해 처음 공식화 된 패러다임입니다. 지금까지 주제에 대해 꽤 많은 이야기가 있습니다. 잘 읽어야 할 것은 Coplien과 Bjørnvig "Lean architecture"
Rune FS

3
감사. 더 나은지 나쁜지는 대부분의 사람들은 지금까지 원문을 알지 못합니다. 구글에 따르면 MVC에 대한 5,500 만 기사가 있지만 MCI와 MVC에 대해서는 250,000 건에 불과하다. 그리고 Microsoft.com에서? 7. AngularJS.org는 DCI 약어를 언급하지 않았습니다. "검색-사이트 : angularjs.org dci-문서와 일치하지 않습니다."
Jim Raden

자원 객체는 기본적으로 Angular.js의 모델입니다.
Salman von Abbas

답변:


155

여러 컨트롤러에서 사용할 수있는 것을 원하면 서비스를 사용해야합니다. 다음은 간단한 예입니다.

myApp.factory('ListService', function() {
  var ListService = {};
  var list = [];
  ListService.getItem = function(index) { return list[index]; }
  ListService.addItem = function(item) { list.push(item); }
  ListService.removeItem = function(item) { list.splice(list.indexOf(item), 1) }
  ListService.size = function() { return list.length; }

  return ListService;
});

function Ctrl1($scope, ListService) {
  //Can add/remove/get items from shared list
}

function Ctrl2($scope, ListService) {
  //Can add/remove/get items from shared list
}

23
일반 Javascript 객체를 모델로 생성하고이를 컨트롤러 범위에 할당하는 것보다 서비스를 사용하면 어떤 이점이 있습니까?
Nils Blum-Oeste 2016 년

22
여러 컨트롤러간에 동일한 논리를 공유해야하는 경우 또한 이런 방식으로 독립적으로 테스트하는 것이 더 쉽습니다.
앤드류 조슬린

1
마지막 예제는 빨랐습니다. 편집했습니다.
앤드류 조슬린

9
예, 평범한 오래된 자바 스크립트 객체를 사용하면 ListService에 Angular를 주입 할 수 없습니다. 이 예에서와 같이 시작시 List 데이터를 검색하기 위해 $ http.get이 필요하거나 $ rootScope를 주입해야 $ broadcast 이벤트가 발생할 수 있습니다.
앤드류 조슬린

1
이 예제를 더 DCI로 만들려면 데이터가 ListService 외부에 있지 않아야합니까?
PiTheNumber

81

현재 DCI는 아니지만이 패턴을 시도하고 있습니다.이 서비스는 고전적인 서비스 / 모델 분리 (웹 서비스와 통신하는 서비스 (모델 CRUD) 및 객체 속성 및 메소드 정의 모델)를 제공합니다.

모델 객체가 자체 속성에서 작동 하는 메소드가 필요할 때 마다이 패턴 만 사용한다는 점에 유의하십시오 . 개선 된 getter / setter와 같은 모든 곳에서 사용됩니다. 나는 모든 서비스에 대해 체계적으로 이것을 옹호 하지 않습니다 .

편집 :이 패턴은 "앵귤러 모델은 평범한 오래된 자바 스크립트 객체"만트라에 어긋날 것이라고 생각했지만,이 패턴은 완벽하게 괜찮습니다.

편집 (2) : 더 명확하게하기 위해 Model 클래스를 사용하여 간단한 게터 / 세터 (예 : 뷰 템플릿에 사용)를 고려합니다. 큰 비즈니스 로직의 경우 모델에 대해 "알고"별도의 서비스를 사용하는 것이 좋지만 모델과는 별도로 유지되며 비즈니스 로직 만 포함합니다. 원하는 경우 "비즈니스 전문가"서비스 계층이라고 부릅니다.

service / ElementServices.js (선언에 Element가 주입되는 방식에 주목)

MyApp.service('ElementServices', function($http, $q, Element)
{
    this.getById = function(id)
    {
        return $http.get('/element/' + id).then(
            function(response)
            {
                //this is where the Element model is used
                return new Element(response.data);
            },
            function(response)
            {
                return $q.reject(response.data.error);
            }
        );
    };
    ... other CRUD methods
}

model / Element.js (angularjs 팩토리를 사용하여 오브젝트 작성 용)

MyApp.factory('Element', function()
{
    var Element = function(data) {
        //set defaults properties and functions
        angular.extend(this, {
            id:null,
            collection1:[],
            collection2:[],
            status:'NEW',
            //... other properties

            //dummy isNew function that would work on two properties to harden code
            isNew:function(){
                return (this.status=='NEW' || this.id == null);
            }
        });
        angular.extend(this, data);
    };
    return Element;
});

4
방금 Angular에 들어가고 있지만 재향 군인이 이것이 이단이라고 생각하는지 궁금합니다. 이것은 아마도 내가 처음에 접근하는 방법 일 것입니다. 누군가 피드백을 줄 수 있습니까?
Aaronius 2019

2
@Aaronius는 분명합니다 .angangjs 문서 나 블로그에서 실제로 "당신은 절대로 그렇게해서는 안됩니다"를 읽지 못했지만 항상 "angularjs는 모델이 필요하지 않습니다. , 나는이 패턴을 스스로 발견해야했습니다. 이것이 AngularJS에 대한 첫 번째 실제 프로젝트이므로 사람들이 먼저 생각하지 않고 복사 / 붙여 넣지 않도록 강력한 경고를하고 있습니다.
Ben G

나는 대략 비슷한 패턴으로 정착했습니다. Angular가 모델을 "클래식"의미로 실제로 지원하지 않는 것 (또는 지원하고 싶어하는 것)은 부끄러운 일입니다.
drt

3
그것은 나에게 이단처럼 보이지 않습니다. 당신은 팩토리를 사용하여 객체를 만드는 데 사용했습니다. "angularjs에는 모델이 필요하지 않습니다"라는 문구는 "특별 클래스에서 상속하거나, ko.observable, knockout과 같은 특수 메서드를 사용하여 각도, 순수한 js 객체로 충분합니다 ".
Felipe Castro

1
각 컬렉션에 대해 적절한 이름의 ElementService가 없으면 거의 동일한 파일이 생성됩니까?
Collin Allen

29

Angularjs 문서는 다음과 같이 명확하게 설명합니다.

다른 많은 프레임 워크와 달리 Angular는 모델에 대한 제한이나 요구 사항이 없습니다. 모델에 액세스하거나 모델을 변경하기 위해 상속 할 클래스 나 특수 접근 자 메서드가 없습니다. 모델은 기본, 객체 해시 또는 전체 객체 유형일 수 있습니다. 간단히 말해서 모델은 일반 JavaScript 객체입니다.

AngularJS 개발자 안내서-V1.5 개념-모델

따라서 모델을 선언하는 방법은 귀하에게 달려 있습니다. 간단한 자바 스크립트 객체입니다.

나는 개인적으로 Angular Services를 사용하지 않을 것입니다.


설명서에 명시된 위치에 대한 링크를 제공해야합니다. 나는 구글 검색을했다 "Angular는 모델에 대한 제한이나 요구 사항을 만들지 않습니다"를 했으며 공식 문서의 어느 곳에서도 알 수없는 한 나타나지 않습니다.

4
그것은 오래된 angularjs 문서 (응답 중 살아있는 것)에 있었습니다 : github.com/gitsome/docular/blob/master/lib/angular/ngdocs/guide/…
SC

8

DCI는 패러다임이므로 언어를 지원하는 DCI 또는 지원하지 않는 앵귤러 JS 방법이 없습니다. 소스 변환을 기꺼이 사용하려는 경우 JS는 DCI를 잘 지원하고 그렇지 않은 경우 몇 가지 단점이 있습니다. 다시 DCI는 C # 클래스가 서비스를 제공한다고 말하는 것보다 의존성 주입과 더 이상 관련이 없습니다. 따라서 angulusJS를 사용하여 DCI를 수행하는 가장 좋은 방법은 DCI를 JS 방식으로 수행하는 것입니다. 이는 DCI가 처음에 공식화되는 방식과 매우 비슷합니다. 소스 변환을 수행하지 않으면 역할 메소드가 컨텍스트 외부에서도 오브젝트의 일부가되기 때문에 소스 변환을 완전히 수행 할 수 없지만 이는 일반적으로 메소드 주입 기반 DCI의 문제점입니다. fullOO.info 를 보면 DCI에 대한 권위있는 사이트에서는 메소드 주입을 사용하는 루비 구현을 보거나 볼 수 있습니다. 여기 DCI에 대한 자세한 정보를. 그것은 주로 RUby 예제와 관련이 있지만 DCI는 그와 무관합니다. DCI의 핵심 중 하나는 시스템의 기능과 시스템의 기능이 분리되어 있다는 것입니다. 따라서 데이터 객체는 꽤 멍청하지만 컨텍스트 역할 방법의 역할에 바인딩되면 특정 동작을 사용할 수 있습니다. 역할은 단순히 식별자이며, 그 식별자를 통해 객체에 액세스 할 때 역할 방법을 사용할 수 있습니다. 역할 객체 / 클래스가 없습니다. 방법 주입을 통해 역할 방법의 범위는 정확히 설명 된 것과 같지 않지만 가깝습니다. JS 컨텍스트의 예는 다음과 같습니다.

function transfer(source,destination){
   source.transfer = function(amount){
        source.withdraw(amount);
        source.log("withdrew " + amount);
        destination.receive(amount);
   };
   destination.receive = function(amount){
      destination.deposit(amount);
      destination.log("deposited " + amount);
   };
   this.transfer = function(amount){
    source.transfer(amount);
   };
}

1
DCI 관련 내용을 설명해 주셔서 감사합니다. 대단한 글입니다. 그러나 제 질문은 "모델 객체를 angularjs에 넣을 위치"를 목표로합니다. DCI는 참조 용으로, 모델이있을뿐 아니라 DCI 방식으로 분할 할 수도 있습니다. 더 명확하게 질문을 편집합니다.
Nils Blum-Oeste

7

AngularJS의 모델에 대한이 기사는 다음과 같은 도움이 될 수 있습니다.

http://joelhooks.com/blog/2013/04/24/modeling-data-and-state-in-your-angularjs-application/


7
참고 것을 링크 전용 답변 낙심, SO 응답 솔루션 (대 아직 시간이 지남에 따라 부패하는 경향 참조의 다른 경유지)에 대한 검색의 엔드 포인트이어야한다. 링크를 참조로 유지하면서 독립형 시놉시스를 여기에 추가하십시오.
kleopatra

질문에 대한 의견에 그러한 링크를 추가하는 것이 좋습니다.
jorrebor

이 링크는 실제로 매우 좋은 기사이지만, SO에 적합하도록 답변으로
만들어야합니다.

5

다른 포스터에서 언급했듯이 Angular는 모델링을위한 기본 클래스를 제공하지 않지만 여러 기능을 유용하게 제공 할 수 있습니다.

  1. RESTful API와 상호 작용하고 새 객체를 만드는 방법
  2. 모델 간 관계 설정
  3. 백엔드에 유지하기 전에 데이터 유효성 검증 실시간 오류 표시에도 유용
  4. 불필요한 HTTP 요청을 방지하기위한 캐싱 및 지연 로딩
  5. 상태 머신 후크 (저장, 업데이트, 생성, 신규 등의 전후)

이 모든 것들을 잘 수행하는 하나의 라이브러리는 ngActiveResource ( https://github.com/FacultyCreative/ngActiveResource입니다. )입니다. 전체 공개-이 라이브러리를 작성했으며 여러 엔터프라이즈 규모 응용 프로그램을 작성하는 데 성공적으로 사용했습니다. 잘 테스트되었으며 Rails 개발자에게 친숙한 API를 제공합니다.

우리 팀과 저는이 라이브러리를 계속해서 적극적으로 개발하고 있으며, 더 많은 Angular 개발자들이이 라이브러리에 기여하고 전투 테스트하는 것을보고 싶습니다.


야! 정말 대단해! 지금 내 앱에 연결하겠습니다. 전투 테스트가 시작되었습니다.
J. Bruni

1
나는 단지 당신의 게시물을보고 당신 ngActiveResource과 Angular의 $resource서비스 의 차이점이 무엇인지 궁금 합니다. 나는 Angular를 처음 접했고 두 문서 세트를 빠르게 탐색했지만 많은 중복을 제공하는 것으로 보입니다. 서비스를 제공 ngActiveResource하기 전에 개발 되었습니까 $resource?
Eric B.

5

오래된 질문이지만 Angular 2.0의 새로운 방향을 제시하면 주제가 그 어느 때보 다 관련성이 있다고 생각합니다. 가장 좋은 방법은 가능한 한 특정 프레임 워크에 대한 종속성이 거의없는 코드를 작성하는 것입니다. 직접적인 가치를 추가하는 프레임 워크 특정 부분 만 사용하십시오.

현재 Angular 서비스는 차세대 Angular에 적용 할 수있는 몇 가지 개념 중 하나 인 것 같습니다. 따라서 모든 논리를 서비스로 이동하는 일반적인 지침을 따르는 것이 현명 할 것입니다. 그러나 Angular 서비스에 직접 의존하지 않아도 분리 된 모델을 만들 수 있다고 주장합니다. 필요한 종속성과 책임만으로 자체 포함 된 객체를 만드는 것이 좋습니다. 또한 자동화 된 테스트를 수행 할 때 삶을 훨씬 쉽게 만듭니다. 단일 책임은 요즘 번거로운 일이지만 많은 의미가 있습니다!

다음은 객체 모델을 돔에서 분리하는 데 도움이되는 패턴의 예입니다.

http://www.syntaxsuccess.com/viewarticle/548ebac8ecdac75c8a09d58e

핵심 목표는 뷰 에서처럼 단위 테스트에서 사용하기 쉬운 방식으로 코드를 구성하는 것입니다. 이를 달성하면 현실적이고 유용한 테스트를 작성할 수있는 좋은 위치에 있습니다.


4

이 블로그 게시물 에서 정확한 문제를 해결하려고 했습니다. .

기본적으로 데이터 모델링을위한 최상의 홈은 서비스 및 공장입니다. 그러나 데이터를 검색하는 방법과 필요한 동작의 복잡성에 따라 구현 방법은 다양합니다. 각도는 현재 표준 이 없습니다 방법이나 모범 사례 .

이 게시물은 $ http , $ resourceRestangular를 사용하는 세 가지 접근 방식을 다룹니다. .

다음은 각각에 대한 예제 코드입니다. getResult() 은 작업 모델에 메소드 .

Restangular (쉬운 peasy) :

angular.module('job.models', [])
  .service('Job', ['Restangular', function(Restangular) {
    var Job = Restangular.service('jobs');

    Restangular.extendModel('jobs', function(model) {
      model.getResult = function() {
        if (this.status == 'complete') {
          if (this.passed === null) return "Finished";
          else if (this.passed === true) return "Pass";
          else if (this.passed === false) return "Fail";
        }
        else return "Running";
      };

      return model;
    });

    return Job;
  }]);

$ 리소스 (약간 더 복잡하다) :

angular.module('job.models', [])
    .factory('Job', ['$resource', function($resource) {
        var Job = $resource('/api/jobs/:jobId', { full: 'true', jobId: '@id' }, {
            query: {
                method: 'GET',
                isArray: false,
                transformResponse: function(data, header) {
                    var wrapped = angular.fromJson(data);
                    angular.forEach(wrapped.items, function(item, idx) {
                        wrapped.items[idx] = new Job(item);
                    });
                    return wrapped;
                }
            }
        });

        Job.prototype.getResult = function() {
            if (this.status == 'complete') {
                if (this.passed === null) return "Finished";
                else if (this.passed === true) return "Pass";
                else if (this.passed === false) return "Fail";
            }
            else return "Running";
        };

        return Job;
    }]);

$ http (하드 코어) :

angular.module('job.models', [])
    .service('JobManager', ['$http', 'Job', function($http, Job) {
        return {
            getAll: function(limit) {
                var params = {"limit": limit, "full": 'true'};
                return $http.get('/api/jobs', {params: params})
                  .then(function(response) {
                    var data = response.data;
                    var jobs = [];
                    for (var i = 0; i < data.objects.length; i ++) {
                        jobs.push(new Job(data.objects[i]));
                    }
                    return jobs;
                });
            }
        };
    }])
    .factory('Job', function() {
        function Job(data) {
            for (attr in data) {
                if (data.hasOwnProperty(attr))
                    this[attr] = data[attr];
            }
        }

        Job.prototype.getResult = function() {
            if (this.status == 'complete') {
                if (this.passed === null) return "Finished";
                else if (this.passed === true) return "Pass";
                else if (this.passed === false) return "Fail";
            }
            else return "Running";
        };

        return Job;
    });

블로그 게시물 자체는 컨트롤러에서 모델을 사용하는 방법에 대한 코드 예제뿐만 아니라 각 접근 방식을 사용해야하는 이유에 대한 자세한 내용을 제공합니다.

AngularJS 데이터 모델 : $ http VS $ resource VS Restangular

Angular 2.0은 모든 사람들이 같은 페이지에있는 데이터 모델링에 대한보다 강력한 솔루션을 제공 할 가능성이 있습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.