programing tip

AngularJS : 서비스 속성에 올바른 바인딩 방법

itbloger 2020. 6. 1. 19:09
반응형

AngularJS : 서비스 속성에 올바른 바인딩 방법


AngularJS에서 서비스 속성에 바인딩하는 방법에 대한 모범 사례를 찾고 있습니다.

AngularJS를 사용하여 만든 서비스의 속성에 바인딩하는 방법을 이해하기 위해 여러 예제를 살펴 보았습니다.

아래에는 서비스의 속성에 바인딩하는 방법에 대한 두 가지 예가 있습니다. 그들은 둘 다 작동합니다. 첫 번째 예는 기본 바인딩을 사용하고 두 번째 예는 $ scope. $ watch를 사용하여 서비스 속성에 바인딩합니다.

서비스의 속성에 바인딩 할 때 이러한 예제 중 하나를 선호합니까, 아니면 알지 못하는 다른 옵션이 권장됩니까?

이 예제의 전제는 서비스가 5 초마다“lastUpdated”및“calls”속성을 업데이트해야한다는 것입니다. 서비스 속성이 업데이트되면 뷰에 이러한 변경 사항이 반영되어야합니다. 이 두 예제는 모두 성공적으로 작동합니다. 더 좋은 방법이 있는지 궁금합니다.

기본 바인딩

다음 코드를보고 실행할 수 있습니다. http://plnkr.co/edit/d3c16z

<html>
<body ng-app="ServiceNotification" >

    <div ng-controller="TimerCtrl1" style="border-style:dotted"> 
        TimerCtrl1 <br/>
        Last Updated: {{timerData.lastUpdated}}<br/>
        Last Updated: {{timerData.calls}}<br/>
    </div>

    <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.0.5/angular.js"></script>
    <script type="text/javascript">
        var app = angular.module("ServiceNotification", []);

        function TimerCtrl1($scope, Timer) {
            $scope.timerData = Timer.data;
        };

        app.factory("Timer", function ($timeout) {
            var data = { lastUpdated: new Date(), calls: 0 };

            var updateTimer = function () {
                data.lastUpdated = new Date();
                data.calls += 1;
                console.log("updateTimer: " + data.lastUpdated);

                $timeout(updateTimer, 5000);
            };
            updateTimer();

            return {
                data: data
            };
        });
    </script>
</body>
</html>

서비스 속성에 대한 바인딩을 해결하는 다른 방법은 컨트롤러에서 $ scope. $ watch를 사용하는 것입니다.

$ scope. $ watch

다음 코드를보고 실행할 수 있습니다. http://plnkr.co/edit/dSBlC9

<html>
<body ng-app="ServiceNotification">
    <div style="border-style:dotted" ng-controller="TimerCtrl1">
        TimerCtrl1<br/>
        Last Updated: {{lastUpdated}}<br/>
        Last Updated: {{calls}}<br/>
    </div>

    <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.0.5/angular.js"></script>
    <script type="text/javascript">
        var app = angular.module("ServiceNotification", []);

        function TimerCtrl1($scope, Timer) {
            $scope.$watch(function () { return Timer.data.lastUpdated; },
                function (value) {
                    console.log("In $watch - lastUpdated:" + value);
                    $scope.lastUpdated = value;
                }
            );

            $scope.$watch(function () { return Timer.data.calls; },
                function (value) {
                    console.log("In $watch - calls:" + value);
                    $scope.calls = value;
                }
            );
        };

        app.factory("Timer", function ($timeout) {
            var data = { lastUpdated: new Date(), calls: 0 };

            var updateTimer = function () {
                data.lastUpdated = new Date();
                data.calls += 1;
                console.log("updateTimer: " + data.lastUpdated);

                $timeout(updateTimer, 5000);
            };
            updateTimer();

            return {
                data: data
            };
        });
    </script>
</body>
</html>

서비스에서 $ rootscope. $ broadcast를 사용할 수 있고 컨트롤러에서 $ root. $ on을 사용할 수 있다는 것을 알고 있지만 첫 번째 브로드 캐스트에서 $ broadcast / $를 사용하도록 만든 다른 예에서는 브로드 캐스트되는 추가 호출이 컨트롤러에서 트리거됩니다. $ rootscope. $ broadcast 문제를 해결하는 방법을 알고 있다면 답변을 제공하십시오.

그러나 앞에서 언급 한 내용을 다시 설명하기 위해 서비스 속성에 바인딩하는 방법에 대한 모범 사례를 알고 싶습니다.


최신 정보

이 질문은 원래 2013 년 4 월에 요청 및 답변되었습니다. 2014 년 5 월, Gil Birman은 새로운 답변을 제공했는데, 정답으로 바뀌 었습니다. Gil Birman의 답변에는 투표가 거의 없기 때문에이 질문을 읽는 사람들은 더 많은 투표로 다른 답변에 찬성하여 그의 답변을 무시할 것입니다. 최선의 답변이 무엇인지 결정하기 전에 Gil Birman의 답변을 적극 권장합니다.


두 번째 접근 방식의 장단점을 고려하십시오 .

  • {{lastUpdated}}대신 {{timerData.lastUpdated}}쉽게 {{timer.lastUpdated}}읽을 수있는 0 대신에 0 은 더 읽기 쉽습니다 (논쟁하지 말고 ...이 점에 중립 등급을 부여하므로 직접 결정하십시오)

  • +1 컨트롤러가 마크 업을위한 일종의 API 역할을하는 것이 편리 할 수 ​​있습니다. 따라서 데이터 모델의 구조가 변경되는 경우 이론적으로 html 부분을 건드리지 않고 컨트롤러의 API 매핑업데이트 할 수 있습니다 .

  • -1 그러나 이론은 항상 실천되지 않고 나는 보통 자신을 마크 업 수정하지 찾을 수 변경이 요구 될 때, 컨트롤러 로직을 어쨌든 . 따라서 API를 작성하려는 추가 노력으로 인해 이점이 무효화됩니다.

  • -1 또한이 방법은 그리 건조하지 않습니다.

  • -1 데이터를 ng-model코드 에 바인딩 $scope.scalar_values하려면 컨트롤러에서 패키지를 다시 패키지하여 새로운 REST 호출 을 수행해야하므로 DRY가 줄어 듭니다 .

  • -0.1 추가 감시자를 만들면 성능이 약간 저하됩니다. 또한 데이터 속성이 특정 컨트롤러에서 감시 할 필요가없는 모델에 연결되어 있으면 심층 감시자에게 추가 오버 헤드가 발생합니다.

  • -1 여러 컨트롤러에 동일한 데이터 모델이 필요한 경우 어떻게합니까? 즉, 모든 모델 변경 시마다 업데이트 할 여러 API가 있습니다.

$scope.timerData = Timer.data;지금 막 강렬한 유혹을 느끼기 시작하고 있습니다 ... 마지막 요점을 조금 더 깊이 살펴 보도록하겠습니다. 백엔드 (서버)의 모델? 아니면 프론트 엔드에서만 만들어지고 존재하는 모델입니까? 어느 경우이든, 본질적으로 데이터 맵핑 API프론트 엔드 서비스 계층 (각 팩토리 또는 서비스)에 속한다 . (첫 번째 예-선호 사항)에는 서비스 계층 에 이러한 API 가 없으므로 필요하지 않기 때문에 간단합니다.

결론적 으로 모든 것을 분리 할 필요는 없습니다. 그리고 마크 업을 데이터 모델에서 완전히 분리하는 한 단점이 장점보다 중요합니다.


컨트롤러는 일반적으로 로 흩어지지 않아야합니다 $scope = injectable.data.scalar. 오히려, 그들은 함께 뿌려해야한다 $scope = injectable.data의, promise.then(..)의, 그리고 $scope.complexClickAction = function() {..}

데이터 디커플링 따라서보기 - 캡슐화하는 유일한 장소 달성하기위한 다른 방법으로 정말 모델에서보기 분리하는 것이 합리적 이다 지시문과를 . 그러나 거기에도 또는 함수 $watch에 스칼라 값을 두지 마십시오 . 시간을 절약하거나 코드를 유지 관리하거나 읽을 수있게하지는 않습니다. 각도에서의 강력한 테스트는 일반적으로 결과 DOM 을 테스트하기 때문에 테스트를 더 쉽게 할 수 없습니다 . 오히려 지시문에서 데이터 API 를 객체 형태로 요구하고에 의해 생성 된 er 만 사용 하는 것이 좋습니다.controllerlink$watchng-bind


http://plnkr.co/edit/MVeU1GKRTN4bqA3h9Yio

<body ng-app="ServiceNotification">
    <div style="border-style:dotted" ng-controller="TimerCtrl1">
        TimerCtrl1<br/>
        Bad:<br/>
        Last Updated: {{lastUpdated}}<br/>
        Last Updated: {{calls}}<br/>
        Good:<br/>
        Last Updated: {{data.lastUpdated}}<br/>
        Last Updated: {{data.calls}}<br/>
    </div>

    <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.0.5/angular.js"></script>
    <script type="text/javascript">
        var app = angular.module("ServiceNotification", []);

        function TimerCtrl1($scope, Timer) {
            $scope.data = Timer.data;
            $scope.lastUpdated = Timer.data.lastUpdated;
            $scope.calls = Timer.data.calls;
        };

        app.factory("Timer", function ($timeout) {
            var data = { lastUpdated: new Date(), calls: 0 };

            var updateTimer = function () {
                data.lastUpdated = new Date();
                data.calls += 1;
                console.log("updateTimer: " + data.lastUpdated);

                $timeout(updateTimer, 500);
            };
            updateTimer();

            return {
                data: data
            };
        });
    </script>
</body>

업데이트 : 마지막 으로이 질문으로 돌아와 두 가지 접근 방식이 "잘못된"것이라고 생각하지 않는다고 덧붙였습니다. 원래 나는 Josh David Miller의 대답이 틀렸다고 썼지 만 회고하면 그의 견해, 특히 우려의 분리에 관한 그의 견해는 완전히 유효합니다.

우려를 따로 (그러나 접선 적으로 관련) 분리하면, 방어 카피 에는 고려해야 또 다른 이유가 있습니다. 이 질문은 주로 서비스에서 직접 데이터를 읽는 것에 관한 것입니다. 그러나 팀의 개발자가 뷰가 데이터를 표시하기 전에 컨트롤러가 데이터를 사소한 방식으로 변환해야한다고 결정하면 어떻게 될까요? (컨트롤러가 데이터를 변환해야하는지 여부에 대해서는 또 다른 논의가 있습니다.) 오브젝트의 사본을 먼저 작성하지 않으면 동일한 데이터를 사용하는 다른보기 구성 요소에서 무의식적으로 회귀가 발생할 수 있습니다.

이 질문에서 실제로 강조하는 것은 전형적인 앵귤러 애플리케이션 (그리고 실제로는 모든 자바 스크립트 애플리케이션)의 구조적 단점입니다. 최근 React 불변의 데이터 구조를 사용하여 설계 응용 프로그램에 매료되었습니다 . 그렇게하면 다음 두 가지 문제가 훌륭하게 해결됩니다.

  1. 우려 분리 : 구성 요소는 소품을 통해 모든 데이터를 사용하며 전역 단일 톤 (예 : 각도 서비스)에 거의 의존하지 않으며 뷰 계층에서 그 위에 일어난 일에 대해서는 전혀 알지 못합니다 .

  2. 변경 성 : 모든 소품은 변경이 불가능하여 의도 하지 않은 데이터 변이의 위험을 제거합니다.

Angular 2.0은 이제 React에서 많은 돈을 빌려 위의 두 가지 포인트를 달성 할 준비가되었습니다.


내 관점에서 볼 $watch때 가장 좋은 방법입니다.

실제로 예제를 약간 단순화 할 수 있습니다.

function TimerCtrl1($scope, Timer) {
  $scope.$watch( function () { return Timer.data; }, function (data) {
    $scope.lastUpdated = data.lastUpdated;
    $scope.calls = data.calls;
  }, true);
}

그게 당신이 필요한 전부입니다.

속성이 동시에 업데이트되므로 하나의 시계 만 필요합니다. 또한 그것들은 하나의 작은 물체에서 나왔기 때문에 Timer.data속성을 보도록 변경했습니다 . 전달 된 마지막 매개 변수 $watch는 참조가 동일한 지 확인하지 않고 깊은 평등을 검사 하도록 지시합니다.


약간의 맥락을 제공하기 위해 서비스 방법을 범위에 직접 배치하는 것보다이 방법을 선호하는 이유는 우려를 적절히 분리하는 것입니다. 서비스를 운영하기 위해 서비스에 대해 알 필요가 없습니다. 컨트롤러의 역할은 모든 것을 하나로 묶는 것입니다. 업무는 서비스에서 데이터를 가져와 필요한 방식으로 처리 한 다음 필요한 특정 정보를 제공하는 것입니다. 그러나 나는 그 임무가 단지 서비스를 바로보기에 전달하는 것이라고 생각하지 않습니다. 그렇지 않으면 컨트롤러가 무엇을하고 있습니까? AngularJS 개발자들은 템플릿에 "논리적"을 포함시키지 않기로 선택했을 때와 같은 추론을 따랐습니다 (예 : if진술).

공평하게, 여기에 여러 관점이있을 수 있으며 다른 답변을 기대합니다.


파티에 늦었지만 미래의 Google 직원에게는 제공된 답변을 사용하지 마십시오.

JavaScript는 "숫자, 문자열 등"값에 대한 얕은 사본 만 전달하는 반면 참조로 오브젝트를 전달하는 메커니즘을 가지고 있습니다.

위의 예에서 서비스의 속성을 바인딩하는 대신 서비스를 범위에 노출시키지 않는 이유는 무엇입니까?

$scope.hello = HelloService;

이 간단한 접근 방식을 통해 각도는 양방향 바인딩과 필요한 모든 마법 같은 작업을 수행 할 수 있습니다. 감시자 또는 불필요한 마크 업으로 컨트롤러를 해킹하지 마십시오.

뷰가 실수로 서비스 속성을 덮어 쓰는 것이 걱정되는 경우 defineProperty이를 사용 하여 읽기, 열거 가능, 구성 가능 또는 게터 및 세터를 정의하십시오. 서비스를보다 견고하게 만들어 많은 제어권을 얻을 수 있습니다.

마지막 팁 : 컨트롤러보다 서비스에서 작업하는 데 시간을 보낸다면 잘못하고 있습니다 :(.

제공 한 특정 데모 코드에서 다음을 수행하는 것이 좋습니다.

 function TimerCtrl1($scope, Timer) {
   $scope.timer = Timer;
 }
///Inside view
{{ timer.time_updated }}
{{ timer.other_property }}
etc...

편집하다:

위에서 언급했듯이 다음을 사용하여 서비스 속성의 동작을 제어 할 수 있습니다 defineProperty

예:

// Lets expose a property named "propertyWithSetter" on our service
// and hook a setter function that automatically saves new value to db !
Object.defineProperty(self, 'propertyWithSetter', {
  get: function() { return self.data.variable; },
  set: function(newValue) { 
         self.data.variable = newValue; 
         // let's update the database too to reflect changes in data-model !
         self.updateDatabaseWithNewData(data);
       },
  enumerable: true,
  configurable: true
});

이제 컨트롤러에서

$scope.hello = HelloService;
$scope.hello.propertyWithSetter = 'NEW VALUE';

우리의 서비스는 propertyWithSetter어떻게 든 가치를 변화시키고 새로운 가치를 데이터베이스에 게시 할 것입니다!

또는 원하는 방식으로 접근 할 수 있습니다.

에 대한 MDN 설명서참조하십시오 defineProperty.


나는이 질문에 맥락 적 구성 요소가 있다고 생각합니다.

단순히 서비스에서 데이터를 가져 와서 그 정보를 볼 수 있다면 서비스 속성에 직접 바인딩하는 것이 좋습니다. 서비스 속성을 단순히 모델 속성에 매핑하여 내 관점에서 소비 하는 많은 상용구 코드 를 작성 하고 싶지 않습니다 .

또한 각도에서의 성능은 두 가지를 기반으로합니다. 첫 번째는 페이지에 몇 개의 바인딩이 있는지입니다. 두 번째는 getter 함수의 가격입니다. Misko는 여기 에 대해 이야기합니다 .

서비스 자체에 적용된 데이터 마사지와 달리 서비스 데이터에 대해 인스턴스 특정 로직을 수행해야하고 그 결과가 뷰에 노출 된 데이터 모델에 영향을 미치는 경우 $ watcher가 적절하다고 말할 수 있습니다. 기능이 엄청나게 비싸지 않는 한. 비싼 함수의 경우 결과를 로컬 (컨트롤러) 변수에 캐싱하고 $ watcher 함수 외부에서 복잡한 작업을 수행 한 다음 범위를 그 결과에 바인딩하는 것이 좋습니다.

경고로 $ scope에서 직접 속성을 걸면 안됩니다 . $scope변수는 모델이 아닙니다. 모델에 대한 참조가 있습니다.

내 생각에는 단순히 서비스에서 아래로 정보를 발산하는 "모범 사례":

function TimerCtrl1($scope, Timer) {
  $scope.model = {timerData: Timer.data};
};

그리고 당신의 견해에는가 포함 {{model.timerData.lastupdated}}됩니다.


위의 예제를 바탕으로 컨트롤러 변수를 서비스 변수에 투명하게 바인딩하는 방법으로 던질 것이라고 생각했습니다.

아래 예에서 Controller $scope.count변수 에 대한 변경 사항 은 Service count변수 에 자동으로 반영됩니다 .

프로덕션 환경에서는 실제로이 바인딩을 사용하여 서비스의 ID를 업데이트 한 다음 비동기 적으로 데이터를 가져오고 서비스 변수를 업데이트합니다. 추가 바인딩은 서비스가 자체적으로 업데이트 될 때 컨트롤러가 자동으로 업데이트됨을 의미합니다.

아래 코드는 http://jsfiddle.net/xuUHS/163/ 에서 작동하는 것으로 볼 수 있습니다

전망:

<div ng-controller="ServiceCtrl">
    <p> This is my countService variable : {{count}}</p>
    <input type="number" ng-model="count">
    <p> This is my updated after click variable : {{countS}}</p>

    <button ng-click="clickC()" >Controller ++ </button>
    <button ng-click="chkC()" >Check Controller Count</button>
    </br>

    <button ng-click="clickS()" >Service ++ </button>
    <button ng-click="chkS()" >Check Service Count</button>
</div>

서비스 / 컨트롤러 :

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

app.service('testService', function(){
    var count = 10;

    function incrementCount() {
      count++;
      return count;
    };

    function getCount() { return count; }

    return {
        get count() { return count },
        set count(val) {
            count = val;
        },
        getCount: getCount,
        incrementCount: incrementCount
    }

});

function ServiceCtrl($scope, testService)
{

    Object.defineProperty($scope, 'count', {
        get: function() { return testService.count; },
        set: function(val) { testService.count = val; },
    });

    $scope.clickC = function () {
       $scope.count++;
    };
    $scope.chkC = function () {
        alert($scope.count);
    };

    $scope.clickS = function () {
       ++testService.count;
    };
    $scope.chkS = function () {
        alert(testService.count);
    };

}

서비스 자체 의 속성 대신 서비스 자체에 바인딩 하는 것이 더 좋은 방법이라고 생각 합니다.

이유는 다음과 같습니다.

<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.7/angular.min.js"></script>
<body ng-app="BindToService">

  <div ng-controller="BindToServiceCtrl as ctrl">
    ArrService.arrOne: <span ng-repeat="v in ArrService.arrOne">{{v}}</span>
    <br />
    ArrService.arrTwo: <span ng-repeat="v in ArrService.arrTwo">{{v}}</span>
    <br />
    <br />
    <!-- This is empty since $scope.arrOne never changes -->
    arrOne: <span ng-repeat="v in arrOne">{{v}}</span>
    <br />
    <!-- This is not empty since $scope.arrTwo === ArrService.arrTwo -->
    <!-- Both of them point the memory space modified by the `push` function below -->
    arrTwo: <span ng-repeat="v in arrTwo">{{v}}</span>
  </div>

  <script type="text/javascript">
    var app = angular.module("BindToService", []);

    app.controller("BindToServiceCtrl", function ($scope, ArrService) {
      $scope.ArrService = ArrService;
      $scope.arrOne = ArrService.arrOne;
      $scope.arrTwo = ArrService.arrTwo;
    });

    app.service("ArrService", function ($interval) {
      var that = this,
          i = 0;
      this.arrOne = [];
      that.arrTwo = [];

      $interval(function () {
        // This will change arrOne (the pointer).
        // However, $scope.arrOne is still same as the original arrOne.
        that.arrOne = that.arrOne.concat([i]);

        // This line changes the memory block pointed by arrTwo.
        // And arrTwo (the pointer) itself never changes.
        that.arrTwo.push(i);
        i += 1;
      }, 1000);

    });
  </script>
</body> 

You can play it on this plunker.


I would rather keep my watchers a less as possible. My reason is based on my experiences and one might argue it theoretically.
The issue with using watchers is that you can use any property on scope to call any of the methods in any component or service you like.
In a real world project, pretty soon you'll end up with a non-tracable (better said hard to trace) chain of methods being called and values being changed which specially makes the on-boarding process tragic.


To bind any data,which sends service is not a good idea (architecture),but if you need it anymore I suggest you 2 ways to do that

1) you can get the data not inside you service.You can get data inside your controller/directive and you will not have a problem to bind it anywhere

2) you can use angularjs events.Whenever you want,you can send a signal(from $rootScope) and catch it wherever you want.You can even send a data on that eventName.

Maybe this can help you. If you need more with examples,here is the link

http://www.w3docs.com/snippets/angularjs/bind-value-between-service-and-controller-directive.html


What about

scope = _.extend(scope, ParentScope);

Where ParentScope is an injected service?


The Most Elegant Solutions...

app.service('svc', function(){ this.attr = []; return this; });
app.controller('ctrl', function($scope, svc){
    $scope.attr = svc.attr || [];
    $scope.$watch('attr', function(neo, old){ /* if necessary */ });
});
app.run(function($rootScope, svc){
    $rootScope.svc = svc;
    $rootScope.$watch('svc', function(neo, old){ /* change the world */ });
});

Also, I write EDAs (Event-Driven Architectures) so I tend to do something like the following [oversimplified version]:

var Service = function Service($rootScope) {
    var $scope = $rootScope.$new(this);
    $scope.that = [];
    $scope.$watch('that', thatObserver, true);
    function thatObserver(what) {
        $scope.$broadcast('that:changed', what);
    }
};

Then, I put a listener in my controller on the desired channel and just keep my local scope up to date this way.

In conclusion, there's not much of a "Best Practice" -- rather, its mostly preference -- as long as you're keeping things SOLID and employing weak coupling. The reason I would advocate the latter code is because EDAs have the lowest coupling feasible by nature. And if you aren't too concerned about this fact, let us avoid working on the same project together.

Hope this helps...

참고URL : https://stackoverflow.com/questions/15800454/angularjs-the-correct-way-of-binding-to-a-service-properties

반응형