Programing

AngularJS : 서비스 변수를 보는 방법?

lottogame 2020. 2. 21. 22:03
반응형

AngularJS : 서비스 변수를 보는 방법?


서비스가 있습니다.

factory('aService', ['$rootScope', '$resource', function ($rootScope, $resource) {
  var service = {
    foo: []
  };

  return service;
}]);

그리고 fooHTML로 렌더링되는 목록을 제어하는 데 사용 하고 싶습니다 .

<div ng-controller="FooCtrl">
  <div ng-repeat="item in foo">{{ item }}</div>
</div>

컨트롤러 aService.foo가 업데이트 될 때를 감지하기 위해 컨트롤러에 aService를 추가 $scope한 다음 이 패턴을 함께 사용했습니다 $scope.$watch().

function FooCtrl($scope, aService) {                                                                                                                              
  $scope.aService = aService;
  $scope.foo = aService.foo;

  $scope.$watch('aService.foo', function (newVal, oldVal, scope) {
    if(newVal) { 
      scope.foo = newVal;
    }
  });
}

이것은 오래간만에 느껴져서 서비스 변수를 사용하는 모든 컨트롤러에서 반복하고 있습니다. 공유 변수를 시청하는 더 좋은 방법이 있습니까?


폭정과 오버 헤드를 피하려면 항상 좋은 오래된 관찰자 패턴을 사용할 수 있습니다 $watch.

서비스에서 :

factory('aService', function() {
  var observerCallbacks = [];

  //register an observer
  this.registerObserverCallback = function(callback){
    observerCallbacks.push(callback);
  };

  //call this when you know 'foo' has been changed
  var notifyObservers = function(){
    angular.forEach(observerCallbacks, function(callback){
      callback();
    });
  };

  //example of when you may want to notify observers
  this.foo = someNgResource.query().$then(function(){
    notifyObservers();
  });
});

그리고 컨트롤러에서 :

function FooCtrl($scope, aService){
  var updateFoo = function(){
    $scope.foo = aService.foo;
  };

  aService.registerObserverCallback(updateFoo);
  //service now in control of updating foo
};

이와 같은 시나리오에서는 여러 개의 / unowned 객체가 변경에 관심이있을 수 있으므로 변경 $rootScope.$broadcast중인 항목에서 사용 하십시오.

다양한 $ destroy에서 정리해야하는 리스너 레지스트리를 직접 작성하는 대신, $broadcast해당 서비스 를 사용할 수 있어야합니다 .

여전히 $on각 리스너 에서 핸들러를 코딩해야 하지만 패턴이 여러 호출에서 분리 $digest되므로 장기 실행 감시자의 위험을 피할 수 있습니다.

또한이 방법으로 리스너는 서비스의 동작을 변경하지 않고도 DOM 및 / 또는 다른 하위 범위 에서왔다 갔다 할 수 있습니다 .

** 업데이트 : 예 **

브로드 캐스트는 앱의 수많은 다른 요소에 영향을 줄 수있는 "글로벌"서비스에서 가장 적합합니다. 좋은 예는 로그인, 로그 아웃, 업데이트, 유휴 등과 같이 발생할 수있는 많은 이벤트가있는 사용자 서비스입니다. 어떤 범위에서도 이벤트를 수신 할 수 있기 때문에 브로드 캐스트가 가장 적합한 곳이라고 생각합니다. 서비스를 주입하기도하므로 변경 사항을 검사하기 위해 식을 평가하거나 결과를 캐시 할 필요가 없습니다. 그냥 실행하고 잊어 버립니다 (따라서 조치가 필요한 것이 아니라 화재 및 잊어 버린 알림인지 확인하십시오)

.factory('UserService', [ '$rootScope', function($rootScope) {
   var service = <whatever you do for the object>

   service.save = function(data) {
     .. validate data and update model ..
     // notify listeners and provide the data that changed [optional]
     $rootScope.$broadcast('user:updated',data);
   }

   // alternatively, create a callback function and $broadcast from there if making an ajax call

   return service;
}]);

위의 서비스는 save () 함수가 완료되고 데이터가 유효 할 때 모든 범위에 메시지를 브로드 캐스트합니다. 또는 $ resource 또는 ajax 제출 인 경우 브로드 캐스트 호출을 콜백으로 이동하여 서버가 응답 할 때 실행되도록하십시오. 브로드 캐스트는 모든 청취자가 모든 단일 $ digest에서 범위를 검사 할 필요없이 이벤트를 기다리기 때문에 특히이 패턴에 적합합니다. 리스너는 다음과 같습니다.

.controller('UserCtrl', [ 'UserService', '$scope', function(UserService, $scope) {

  var user = UserService.getUser();

  // if you don't want to expose the actual object in your scope you could expose just the values, or derive a value for your purposes
   $scope.name = user.firstname + ' ' +user.lastname;

   $scope.$on('user:updated', function(event,data) {
     // you could inspect the data to see if what you care about changed, or just update your own scope
     $scope.name = user.firstname + ' ' + user.lastname;
   });

   // different event names let you group your code and logic by what happened
   $scope.$on('user:logout', function(event,data) {
     .. do something differently entirely ..
   });

 }]);

이것의 장점 중 하나는 여러 시계를 제거한다는 것입니다. 위의 예와 같이 필드를 결합하거나 값을 파생하는 경우 이름 속성과 성 속성을 모두 감시해야합니다. getUser () 함수를 보는 것은 사용자 객체가 업데이트로 교체 된 경우에만 작동하며, 사용자 객체의 속성이 업데이트 된 경우에만 실행되지 않습니다. 어떤 경우에는 깊은 감시를해야하고 더 집중적입니다.

$ broadcast는 호출 된 범위에서 하위 범위로 메시지를 보냅니다. 따라서 $ rootScope에서 호출하면 모든 범위에서 실행됩니다. 예를 들어 컨트롤러의 범위에서 $ broadcast를한다면 컨트롤러 범위에서 상속 된 범위에서만 실행됩니다. $ emit은 반대 방향으로 가고 스코프 체인을 버블 링한다는 점에서 DOM 이벤트와 유사하게 작동합니다.

$ broadcast가 많은 의미가있는 시나리오가 있으며, 특히 매우 구체적인 시계 표현을 가진 격리 범위에있는 경우 $ watch가 더 나은 옵션 인 시나리오가 있습니다.


@dtheodot와 비슷한 접근법을 사용하고 있지만 콜백을 전달하는 대신 각도 약속을 사용하고 있습니다.

app.service('myService', function($q) {
    var self = this,
        defer = $q.defer();

    this.foo = 0;

    this.observeFoo = function() {
        return defer.promise;
    }

    this.setFoo = function(foo) {
        self.foo = foo;
        defer.notify(self.foo);
    }
})

그런 다음 myService.setFoo(foo)방법을 사용 foo하여 서비스 를 업데이트하십시오 . 컨트롤러에서 다음과 같이 사용할 수 있습니다.

myService.observeFoo().then(null, null, function(foo){
    $scope.foo = foo;
})

처음 두 인수 then는 성공 및 오류 콜백이며, 세 번째 인수 는 알림 콜백입니다.

$ q에 대한 참조.


시계 또는 관찰자 콜백이없는 경우 ( http://jsfiddle.net/zymotik/853wvv7s/ ) :

자바 스크립트 :

angular.module("Demo", [])
    .factory("DemoService", function($timeout) {

        function DemoService() {
            var self = this;
            self.name = "Demo Service";

            self.count = 0;

            self.counter = function(){
                self.count++;
                $timeout(self.counter, 1000);
            }

            self.addOneHundred = function(){
                self.count+=100;
            }

            self.counter();
        }

        return new DemoService();

    })
    .controller("DemoController", function($scope, DemoService) {

        $scope.service = DemoService;

        $scope.minusOneHundred = function() {
            DemoService.count -= 100;
        }

    });

HTML

<div ng-app="Demo" ng-controller="DemoController">
    <div>
        <h4>{{service.name}}</h4>
        <p>Count: {{service.count}}</p>
    </div>
</div>

이 JavaScript는 값이 아닌 서비스에서 객체를 다시 전달할 때 작동합니다. 서비스에서 JavaScript 객체가 반환되면 Angular는 모든 속성에 시계를 추가합니다.

또한 $ timeout이 실행될 때 원래 객체에 대한 참조를 유지해야하므로 'var self = this'를 사용하고 있습니다. 그렇지 않으면 'this'는 창 객체를 나타냅니다.


내가 알 수있는 한, 당신은 그처럼 정교한 것을 할 필요가 없습니다. 서비스에서 범위로 foo를 이미 할당했으며 foo가 배열이므로 참조로 할당 된 객체입니다! 그래서 당신이해야 할 일은 다음과 같습니다.

function FooCtrl($scope, aService) {                                                                                                                              
  $scope.foo = aService.foo;

 }

이 동일한 Ctrl의 다른 변수가 foo change에 의존하고 yes이면 foo를 관찰하고 해당 변수를 변경하는 시계가 필요합니다. 그러나 간단한 참조 관찰이라면 불필요합니다. 도움이 되었기를 바랍니다.


나는 비슷한 질문을 찾기 위해이 질문을 우연히 만났지만 진행중인 일과 몇 가지 추가 솔루션에 대한 철저한 설명이 필요하다고 생각합니다.

사용하는 것과 같은 각도 표현식이 HTML에 있으면 Angular는 자동으로 $watchfor를 설정하고 변경 $scope.foo될 때마다 HTML을 업데이트합니다 $scope.foo.

<div ng-controller="FooCtrl">
  <div ng-repeat="item in foo">{{ item }}</div>
</div>

여기서 언급되지 않은 문제 aService.foo는 변경 사항이 감지되지 않도록 두 가지 중 하나가 영향을 미친다는 것 입니다. 이 두 가지 가능성은 다음과 같습니다.

  1. aService.foo 매번 새 배열로 설정되어 참조가 오래되었습니다.
  2. aService.foo업데이트시 $digest사이클이 트리거되지 않는 방식으로 업데이트됩니다.

문제 1 : 오래된 참조

첫 번째 가능성을 고려할 때 a $digest가 적용되는 경우 aService.foo항상 동일한 배열 인 경우 $watch아래 코드 스 니펫에 표시된대로 자동 설정 이 변경 사항을 감지합니다.

해결 방법 1-a : 각 업데이트 에서 배열 또는 객체가 동일한 객체 인지 확인하십시오.

angular.module('myApp', [])
  .factory('aService', [
    '$interval',
    function($interval) {
      var service = {
        foo: []
      };

      // Create a new array on each update, appending the previous items and 
      // adding one new item each time
      $interval(function() {
        if (service.foo.length < 10) {
          var newArray = []
          Array.prototype.push.apply(newArray, service.foo);
          newArray.push(Math.random());
          service.foo = newArray;
        }
      }, 1000);

      return service;
    }
  ])
  .factory('aService2', [
    '$interval',
    function($interval) {
      var service = {
        foo: []
      };

      // Keep the same array, just add new items on each update
      $interval(function() {
        if (service.foo.length < 10) {
          service.foo.push(Math.random());
        }
      }, 1000);

      return service;
    }
  ])
  .controller('FooCtrl', [
    '$scope',
    'aService',
    'aService2',
    function FooCtrl($scope, aService, aService2) {
      $scope.foo = aService.foo;
      $scope.foo2 = aService2.foo;
    }
  ]);
<!DOCTYPE html>
<html>

<head>
  <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
  <link rel="stylesheet" href="style.css" />
  <script src="script.js"></script>
</head>

<body ng-app="myApp">
  <div ng-controller="FooCtrl">
    <h1>Array changes on each update</h1>
    <div ng-repeat="item in foo">{{ item }}</div>
    <h1>Array is the same on each udpate</h1>
    <div ng-repeat="item in foo2">{{ item }}</div>
  </div>
</body>

</html>

당신이 볼 수 있듯이, 가정에 부착 된 겨-반복 aService.foo할 때 업데이트되지 않습니다 aService.foo변화를, 그러나에 부착 된 NG-반복 aService2.foo 않습니다 . 우리에 대한 언급 aService.foo이 오래되었지만 우리에 대한 언급 aService2.foo이 그렇지 않기 때문입니다. 를 사용하여 초기 배열에 대한 참조를 만든 $scope.foo = aService.foo;다음 다음 업데이트에서 서비스에 의해 삭제되었습니다. 즉, $scope.foo더 이상 원하는 배열을 더 이상 참조하지 않았습니다.

그러나 초기 참조를 그대로 유지하는 몇 가지 방법이 있지만 때로는 개체 나 배열을 변경해야 할 수도 있습니다. 또는 서비스 속성이 a String또는 Number? 이 경우 단순히 참조에 의존 할 수 없습니다. 그래서 우리는 무엇 할 수 있습니까?

이전에 제공된 몇 가지 답변은 이미 해당 문제에 대한 해결책을 제시합니다. 그러나 개인적으로 Jinthetallweeks제안한 간단한 방법을 의견에 개인적으로 찬성 합니다.

html 마크 업에서 aService.foo를 참조하십시오.

해결 방법 1-b : 서비스를 범위에 연결 {service}.{property}하고 HTML을 참조 하십시오.

의미, 그냥 이렇게 :

HTML :

<div ng-controller="FooCtrl">
  <div ng-repeat="item in aService.foo">{{ item }}</div>
</div>

JS :

function FooCtrl($scope, aService) {
    $scope.aService = aService;
}

angular.module('myApp', [])
  .factory('aService', [
    '$interval',
    function($interval) {
      var service = {
        foo: []
      };

      // Create a new array on each update, appending the previous items and 
      // adding one new item each time
      $interval(function() {
        if (service.foo.length < 10) {
          var newArray = []
          Array.prototype.push.apply(newArray, service.foo);
          newArray.push(Math.random());
          service.foo = newArray;
        }
      }, 1000);

      return service;
    }
  ])
  .controller('FooCtrl', [
    '$scope',
    'aService',
    function FooCtrl($scope, aService) {
      $scope.aService = aService;
    }
  ]);
<!DOCTYPE html>
<html>

<head>
  <script data-require="angular.js@1.4.7" data-semver="1.4.7" src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.7/angular.js"></script>
  <link rel="stylesheet" href="style.css" />
  <script src="script.js"></script>
</head>

<body ng-app="myApp">
  <div ng-controller="FooCtrl">
    <h1>Array changes on each update</h1>
    <div ng-repeat="item in aService.foo">{{ item }}</div>
  </div>
</body>

</html>

그렇게하면의 각각 $watch이 해결 aService.foo되어 $digest올바르게 업데이트 된 값을 얻습니다.

This is kind of what you were trying to do with your workaround, but in a much less round about way. You added an unnecessary $watch in the controller which explicitly puts foo on the $scope whenever it changes. You don't need that extra $watch when you attach aService instead of aService.foo to the $scope, and bind explicitly to aService.foo in the markup.


이제 $digest사이클이 적용되는 것으로 가정하면 충분 합니다. 위의 예제에서 Angular의 $interval서비스를 사용하여 배열을 $digest업데이트했습니다. 각 업데이트 후 루프 가 자동으로 시작됩니다 . 그러나 어떤 이유로 든 서비스 변수가 "Angular World"내에서 업데이트되지 않으면 어떻게 될까요? 즉, 우리가 해달라고$digest서비스의 속성이 변경 될 때마다주기가 자동으로 활성화되고?


문제 2 : 누락 $digest

여기에있는 많은 솔루션 이이 문제를 해결할 것이지만 Code Whisperer에 동의합니다 .

우리가 Angular와 같은 프레임 워크를 사용하는 이유는 우리 자신의 관찰자 패턴을 요리하지 않기 위해서입니다.

따라서 aService.foo위의 두 번째 예제와 같이 HTML 마크 업에서 참조 를 계속 사용 하고 Controller 내에 추가 콜백을 등록 할 필요가 없습니다.

해결책 2 : 세터와 게터를 $rootScope.$apply()

아직 아무도 settergetter 의 사용을 제안하지 않은 것에 놀랐습니다 . 이 기능은 ECMAScript5에 도입되어 몇 년 전부터 사용되었습니다. 물론, 어떤 이유로 든 오래된 브라우저를 지원 해야하는 경우이 방법이 작동하지 않지만 getter 및 setter가 JavaScript에서 많이 사용되지 않는 것 같습니다. 이 특별한 경우에는 매우 유용 할 수 있습니다.

factory('aService', [
  '$rootScope',
  function($rootScope) {
    var realFoo = [];

    var service = {
      set foo(a) {
        realFoo = a;
        $rootScope.$apply();
      },
      get foo() {
        return realFoo;
      }
    };
  // ...
}

angular.module('myApp', [])
  .factory('aService', [
    '$rootScope',
    function($rootScope) {
      var realFoo = [];

      var service = {
        set foo(a) {
          realFoo = a;
          $rootScope.$apply();
        },
        get foo() {
          return realFoo;
        }
      };

      // Create a new array on each update, appending the previous items and 
      // adding one new item each time
      setInterval(function() {
        if (service.foo.length < 10) {
          var newArray = [];
          Array.prototype.push.apply(newArray, service.foo);
          newArray.push(Math.random());
          service.foo = newArray;
        }
      }, 1000);

      return service;
    }
  ])
  .controller('FooCtrl', [
    '$scope',
    'aService',
    function FooCtrl($scope, aService) {
      $scope.aService = aService;
    }
  ]);
<!DOCTYPE html>
<html>

<head>
  <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
  <link rel="stylesheet" href="style.css" />
  <script src="script.js"></script>
</head>

<body ng-app="myApp">
  <div ng-controller="FooCtrl">
    <h1>Using a Getter/Setter</h1>
    <div ng-repeat="item in aService.foo">{{ item }}</div>
  </div>
</body>

</html>

서비스 기능에 'private'변수를 추가했습니다 realFoo. 이것은 객체 에서 get foo()set foo()함수를 각각 사용하여 업데이트 및 검색됩니다 service.

$rootScope.$apply()설정 기능에서 의 사용에 유의하십시오 . 이를 통해 Angular는에 대한 변경 사항을 인식 할 수 있습니다 service.foo. 'inprog'오류가 발생 하면이 유용한 참조 페이지 를 보거나 Angular> = 1.3을 사용하면을 사용할 수 있습니다 $rootScope.$applyAsync().

aService.foo성능이 크게 저하 될 수 있으므로 매우 자주 업데이트하는 경우에도주의하십시오 . 성능이 문제가된다면, setter를 사용하여 다른 답변과 비슷한 옵저버 패턴을 설정할 수 있습니다.


$ rootScope에 서비스를 삽입하고 다음을 볼 수 있습니다.

myApp.run(function($rootScope, aService){
    $rootScope.aService = aService;
    $rootScope.$watch('aService', function(){
        alert('Watch');
    }, true);
});

컨트롤러에서 :

myApp.controller('main', function($scope){
    $scope.aService.foo = 'change';
});

다른 옵션은 다음과 같은 외부 라이브러리를 사용하는 것입니다 : https://github.com/melanke/Watch.JS

호환 대상 : IE 9+, FF 4+, SF 5+, WebKit, CH 7+, OP 12+, BESEN, Node.JS, Rhino 1.7+

하나, 많은 또는 모든 객체 속성의 변경 사항을 관찰 할 수 있습니다.

예:

var ex3 = {
    attr1: 0,
    attr2: "initial value of attr2",
    attr3: ["a", 3, null]
};   
watch(ex3, function(){
    alert("some attribute of ex3 changes!");
});
ex3.attr3.push("new value");​

공장 자체 내에서 변경 사항을보고 변경 사항을 브로드 캐스트 할 수 있습니다

angular.module('MyApp').factory('aFactory', function ($rootScope) {
    // Define your factory content
    var result = {
        'key': value
    };

    // add a listener on a key        
    $rootScope.$watch(function () {
        return result.key;
    }, function (newValue, oldValue, scope) {
        // This is called after the key "key" has changed, a good idea is to broadcast a message that key has changed
        $rootScope.$broadcast('aFactory:keyChanged', newValue);
    }, true);

    return result;
});

그런 다음 컨트롤러에서 :

angular.module('MyApp').controller('aController', ['$rootScope', function ($rootScope) {

    $rootScope.$on('aFactory:keyChanged', function currentCityChanged(event, value) {
        // do something
    });
}]);

이러한 방식으로 모든 관련 팩토리 코드를 설명 내에 넣은 다음 외부에서만 브로드 캐스트에만 의존 할 수 있습니다


== 업데이트 ==

$ watch에서 매우 간단합니다.

여기 펜 .

HTML :

<div class="container" data-ng-app="app">

  <div class="well" data-ng-controller="FooCtrl">
    <p><strong>FooController</strong></p>
    <div class="row">
      <div class="col-sm-6">
        <p><a href="" ng-click="setItems([ { name: 'I am single item' } ])">Send one item</a></p>
        <p><a href="" ng-click="setItems([ { name: 'Item 1 of 2' }, { name: 'Item 2 of 2' } ])">Send two items</a></p>
        <p><a href="" ng-click="setItems([ { name: 'Item 1 of 3' }, { name: 'Item 2 of 3' }, { name: 'Item 3 of 3' } ])">Send three items</a></p>
      </div>
      <div class="col-sm-6">
        <p><a href="" ng-click="setName('Sheldon')">Send name: Sheldon</a></p>
        <p><a href="" ng-click="setName('Leonard')">Send name: Leonard</a></p>
        <p><a href="" ng-click="setName('Penny')">Send name: Penny</a></p>
      </div>
    </div>
  </div>

  <div class="well" data-ng-controller="BarCtrl">
    <p><strong>BarController</strong></p>
    <p ng-if="name">Name is: {{ name }}</p>
    <div ng-repeat="item in items">{{ item.name }}</div>
  </div>

</div>

자바 스크립트 :

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

app.factory('PostmanService', function() {
  var Postman = {};
  Postman.set = function(key, val) {
    Postman[key] = val;
  };
  Postman.get = function(key) {
    return Postman[key];
  };
  Postman.watch = function($scope, key, onChange) {
    return $scope.$watch(
      // This function returns the value being watched. It is called for each turn of the $digest loop
      function() {
        return Postman.get(key);
      },
      // This is the change listener, called when the value returned from the above function changes
      function(newValue, oldValue) {
        if (newValue !== oldValue) {
          // Only update if the value changed
          $scope[key] = newValue;
          // Run onChange if it is function
          if (angular.isFunction(onChange)) {
            onChange(newValue, oldValue);
          }
        }
      }
    );
  };
  return Postman;
});

app.controller('FooCtrl', ['$scope', 'PostmanService', function($scope, PostmanService) {
  $scope.setItems = function(items) {
    PostmanService.set('items', items);
  };
  $scope.setName = function(name) {
    PostmanService.set('name', name);
  };
}]);

app.controller('BarCtrl', ['$scope', 'PostmanService', function($scope, PostmanService) {
  $scope.items = [];
  $scope.name = '';
  PostmanService.watch($scope, 'items');
  PostmanService.watch($scope, 'name', function(newVal, oldVal) {
    alert('Hi, ' + newVal + '!');
  });
}]);

dtheodor의 답변 바탕으로 콜백을 등록 취소하는 것을 잊지 않도록 아래와 비슷한 것을 사용할 수 있습니다 ... 일부는 $scope서비스 에 전달하는 것에 반대 할 수 있습니다 .

factory('aService', function() {
  var observerCallbacks = [];

  /**
   * Registers a function that will be called when
   * any modifications are made.
   *
   * For convenience the callback is called immediately after registering
   * which can be prevented with `preventImmediate` param.
   *
   * Will also automatically unregister the callback upon scope destory.
   */
  this.registerObserver = function($scope, cb, preventImmediate){
    observerCallbacks.push(cb);

    if (preventImmediate !== true) {
      cb();
    }

    $scope.$on('$destroy', function () {
      observerCallbacks.remove(cb);
    });
  };

  function notifyObservers() {
    observerCallbacks.forEach(function (cb) {
      cb();
    });
  };

  this.foo = someNgResource.query().$then(function(){
    notifyObservers();
  });
});

Array.remove는 다음과 같은 확장 메소드입니다.

/**
 * Removes the given item the current array.
 *
 * @param  {Object}  item   The item to remove.
 * @return {Boolean}        True if the item is removed.
 */
Array.prototype.remove = function (item /*, thisp */) {
    var idx = this.indexOf(item);

    if (idx > -1) {
        this.splice(idx, 1);

        return true;
    }
    return false;
};

일반적인 접근 방식은 다음과 같습니다.

mainApp.service('aService',[function(){
        var self = this;
        var callbacks = {};

        this.foo = '';

        this.watch = function(variable, callback) {
            if (typeof(self[variable]) !== 'undefined') {
                if (!callbacks[variable]) {
                    callbacks[variable] = [];
                }
                callbacks[variable].push(callback);
            }
        }

        this.notifyWatchersOn = function(variable) {
            if (!self[variable]) return;
            if (!callbacks[variable]) return;

            angular.forEach(callbacks[variable], function(callback, key){
                callback(self[variable]);
            });
        }

        this.changeFoo = function(newValue) {
            self.foo = newValue;
            self.notifyWatchersOn('foo');
        }

    }]);

컨트롤러에서

function FooCtrl($scope, aService) {
    $scope.foo;

    $scope._initWatchers = function() {
        aService.watch('foo', $scope._onFooChange);
    }

    $scope._onFooChange = function(newValue) {
        $scope.foo = newValue;
    }

    $scope._initWatchers();

}

FooCtrl.$inject = ['$scope', 'aService'];

간단한 솔루션을 찾고있는 저와 같은 사람들에게는 이것이 컨트롤러에서 일반 $ watch를 사용하는 것과 거의 같은 일을합니다. 유일한 차이점은 특정 범위가 아닌 자바 스크립트 컨텍스트에서 문자열을 평가한다는 것입니다. 다이제스트 사이클에 올바르게 연결하는 데만 사용되지만 $ rootScope를 서비스에 주입해야합니다.

function watch(target, callback, deep) {
    $rootScope.$watch(function () {return eval(target);}, callback, deep);
};

매우 비슷한 문제에 직면하면서 범위 내에서 함수를보고 함수가 서비스 변수를 반환하도록했습니다. js 바이올린 을 만들었습니다 . 아래 코드를 찾을 수 있습니다.

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

myApp.factory("randomService", function($timeout){
    var retValue = {};
    var data = 0;

    retValue.startService = function(){
        updateData();
    }

    retValue.getData = function(){
        return data;
    }

    function updateData(){
        $timeout(function(){
            data = Math.floor(Math.random() * 100);
            updateData()
        }, 500);
    }

    return retValue;
});

myApp.controller("myController", function($scope, randomService){
    $scope.data = 0;
    $scope.dataUpdated = 0;
    $scope.watchCalled = 0;
    randomService.startService();

    $scope.getRandomData = function(){
        return randomService.getData();    
    }

    $scope.$watch("getRandomData()", function(newValue, oldValue){
        if(oldValue != newValue){
            $scope.data = newValue;
            $scope.dataUpdated++;
        }
            $scope.watchCalled++;
    });
});

나는이 질문에 왔지만 내 문제는 각도 $ 간격 공급자를 사용해야했을 때 setInterval을 사용하고 있다는 것이 밝혀졌습니다. setTimeout의 경우에도 마찬가지입니다 (대신 $ timeout을 사용하십시오). 나는 그것이 OP의 질문에 대한 대답이 아니라는 것을 알고 있지만 그것이 나를 도왔 기 때문에 일부 도움이 될 수 있습니다.


비슷한 문제이지만 완전히 다른 접근법으로 다른 스레드에서 실제로 훌륭한 솔루션을 찾았습니다. 출처 : AngularJS : $ rootScope 값이 변경되면 지시문 내의 $ watch가 작동하지 않습니다

기본적으로 거기에있는 솔루션 은 매우 무거운 솔루션이므로 사용 하지 말라고 지시 $watch합니다. 대신 그들은 $emit을 사용할 것을 제안합니다 $on.

내 문제는 서비스 에서 변수 보고 지시에 반응 하는 것이 었습니다 . 그리고 위의 방법으로 매우 쉽습니다!

내 모듈 / 서비스 예 :

angular.module('xxx').factory('example', function ($rootScope) {
    var user;

    return {
        setUser: function (aUser) {
            user = aUser;
            $rootScope.$emit('user:change');
        },
        getUser: function () {
            return (user) ? user : false;
        },
        ...
    };
});

그래서 기본적으로 내가 보는 내를 user- 그것은 새로운 값 I로 설정 될 때마다 상태입니다.$emituser:change

이제 내 경우에는 지시문 에서 다음을 사용했습니다.

angular.module('xxx').directive('directive', function (Auth, $rootScope) {
    return {
        ...
        link: function (scope, element, attrs) {
            ...
            $rootScope.$on('user:change', update);
        }
    };
});

지금에 지시 나는 청취 $rootScope 주어진 변화 - I는 각각 반응한다. 매우 쉽고 우아합니다!


// 서비스 : (여기에 특별한 것은 없습니다)

myApp.service('myService', function() {
  return { someVariable:'abc123' };
});

// ctrl :

myApp.controller('MyCtrl', function($scope, myService) {

  $scope.someVariable = myService.someVariable;

  // watch the service and update this ctrl...
  $scope.$watch(function(){
    return myService.someVariable;
  }, function(newValue){
    $scope.someVariable = newValue;
  });
});

조금 추악하지만 토글을 위해 서비스에 범위 변수 등록을 추가했습니다.

myApp.service('myService', function() {
    var self = this;
    self.value = false;
    self.c2 = function(){};
    self.callback = function(){
        self.value = !self.value; 
       self.c2();
    };

    self.on = function(){
        return self.value;
    };

    self.register = function(obj, key){ 
        self.c2 = function(){
            obj[key] = self.value; 
            obj.$apply();
        } 
    };

    return this;
});

그리고 컨트롤러에서 :

function MyCtrl($scope, myService) {
    $scope.name = 'Superhero';
    $scope.myVar = false;
    myService.register($scope, 'myVar');
}

이 플 런커를 살펴보십시오. : 이것은 내가 생각할 수있는 가장 간단한 예입니다.

http://jsfiddle.net/HEdJF/

<div ng-app="myApp">
    <div ng-controller="FirstCtrl">
        <input type="text" ng-model="Data.FirstName"><!-- Input entered here -->
        <br>Input is : <strong>{{Data.FirstName}}</strong><!-- Successfully updates here -->
    </div>
    <hr>
    <div ng-controller="SecondCtrl">
        Input should also be here: {{Data.FirstName}}<!-- How do I automatically updated it here? -->
    </div>
</div>



// declare the app with no dependencies
var myApp = angular.module('myApp', []);
myApp.factory('Data', function(){
   return { FirstName: '' };
});

myApp.controller('FirstCtrl', function( $scope, Data ){
    $scope.Data = Data;
});

myApp.controller('SecondCtrl', function( $scope, Data ){
    $scope.Data = Data;
});

큰 응용 프로그램에서 메모리 누수를 일으키는 끔찍한 관찰자 패턴이 있습니다.

조금 늦을 수도 있지만 이렇게 간단합니다.

배열 푸시와 같은 것을보고 싶다면 watch 함수는 참조 변경 (기본 유형)을 감시합니다.

someArray.push(someObj); someArray = someArray.splice(0);

그러면 참조가 업데이트되고 어디서나 시계가 업데이트됩니다. 서비스 getter 메소드를 포함합니다. 프리미티브 인 것은 자동으로 업데이트됩니다.


나는 그 부분에 늦었지만 위에 게시 된 답변보다 더 좋은 방법을 찾았습니다. 서비스 변수의 값을 보유 할 변수를 할당하는 대신 서비스 변수를 반환하는 함수를 범위에 첨부했습니다.

제어 장치

$scope.foo = function(){
 return aService.foo;
}

나는 이것이 당신이 원하는 것을 할 것이라고 생각합니다. 컨트롤러는이 구현으로 서비스의 가치를 계속 확인합니다. 솔직히 이것은 선택한 답변보다 훨씬 간단합니다.


서비스 속성 변경을 추적하는 데 도움이되는 두 가지 간단한 유틸리티 서비스를 작성했습니다.

긴 설명을 건너 뛰려면 해협을 jsfiddle 로 이동 하십시오.

  1. 손목 시계

mod.service('WatchObj', ['$rootScope', WatchObjService]);

function WatchObjService($rootScope) {
  // returns watch function
  // obj: the object to watch for
  // fields: the array of fields to watch
  // target: where to assign changes (usually it's $scope or controller instance)
  // $scope: optional, if not provided $rootScope is use
  return function watch_obj(obj, fields, target, $scope) {
    $scope = $scope || $rootScope;
    //initialize watches and create an array of "unwatch functions"
    var watched = fields.map(function(field) {
      return $scope.$watch(
        function() {
          return obj[field];
        },
        function(new_val) {
          target[field] = new_val;
        }
      );
    });
    //unregister function will unregister all our watches
    var unregister = function unregister_watch_obj() {
      watched.map(function(unregister) {
        unregister();
      });
    };
    //automatically unregister when scope is destroyed
    $scope.$on('$destroy', unregister);
    return unregister;
  };
}

이 서비스는 컨트롤러에서 다음과 같은 방식으로 사용됩니다. 'prop1', 'prop2', 'prop3'특성을 가진 "testService"서비스가 있다고 가정하십시오. 'prop1'및 'prop2'범위를보고 할당하려고합니다. 시계 서비스를 사용하면 다음과 같이 보입니다.

app.controller('TestWatch', ['$scope', 'TestService', 'WatchObj', TestWatchCtrl]);

function TestWatchCtrl($scope, testService, watch) {
  $scope.prop1 = testService.prop1;
  $scope.prop2 = testService.prop2;
  $scope.prop3 = testService.prop3;
  watch(testService, ['prop1', 'prop2'], $scope, $scope);
}

  1. Watch obj 적용은 훌륭하지만 서비스에 비동기 코드가 있으면 충분하지 않습니다. 이 경우 다음과 같은 두 번째 유틸리티를 사용합니다.

mod.service('apply', ['$timeout', ApplyService]);

function ApplyService($timeout) {
  return function apply() {
    $timeout(function() {});
  };
}

비동기 코드 끝에서 트리거하여 $ digest 루프를 트리거합니다. 그렇게 :

app.service('TestService', ['apply', TestService]);

function TestService(apply) {
  this.apply = apply;
}
TestService.prototype.test3 = function() {
  setTimeout(function() {
    this.prop1 = 'changed_test_2';
    this.prop2 = 'changed2_test_2';
    this.prop3 = 'changed3_test_2';
    this.apply(); //trigger $digest loop
  }.bind(this));
}

따라서 모두 함께 표시됩니다 (실행하거나 바이올린을 열 수 있음 ).

// TEST app code

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

app.controller('TestWatch', ['$scope', 'TestService', 'WatchObj', TestWatchCtrl]);

function TestWatchCtrl($scope, testService, watch) {
  $scope.prop1 = testService.prop1;
  $scope.prop2 = testService.prop2;
  $scope.prop3 = testService.prop3;
  watch(testService, ['prop1', 'prop2'], $scope, $scope);
  $scope.test1 = function() {
    testService.test1();
  };
  $scope.test2 = function() {
    testService.test2();
  };
  $scope.test3 = function() {
    testService.test3();
  };
}

app.service('TestService', ['apply', TestService]);

function TestService(apply) {
  this.apply = apply;
  this.reset();
}
TestService.prototype.reset = function() {
  this.prop1 = 'unchenged';
  this.prop2 = 'unchenged2';
  this.prop3 = 'unchenged3';
}
TestService.prototype.test1 = function() {
  this.prop1 = 'changed_test_1';
  this.prop2 = 'changed2_test_1';
  this.prop3 = 'changed3_test_1';
}
TestService.prototype.test2 = function() {
  setTimeout(function() {
    this.prop1 = 'changed_test_2';
    this.prop2 = 'changed2_test_2';
    this.prop3 = 'changed3_test_2';
  }.bind(this));
}
TestService.prototype.test3 = function() {
  setTimeout(function() {
    this.prop1 = 'changed_test_2';
    this.prop2 = 'changed2_test_2';
    this.prop3 = 'changed3_test_2';
    this.apply();
  }.bind(this));
}
//END TEST APP CODE

//WATCH UTILS
var mod = angular.module('watch_utils', []);

mod.service('apply', ['$timeout', ApplyService]);

function ApplyService($timeout) {
  return function apply() {
    $timeout(function() {});
  };
}

mod.service('WatchObj', ['$rootScope', WatchObjService]);

function WatchObjService($rootScope) {
  // target not always equals $scope, for example when using bindToController syntax in 
  //directives
  return function watch_obj(obj, fields, target, $scope) {
    // if $scope is not provided, $rootScope is used
    $scope = $scope || $rootScope;
    var watched = fields.map(function(field) {
      return $scope.$watch(
        function() {
          return obj[field];
        },
        function(new_val) {
          target[field] = new_val;
        }
      );
    });
    var unregister = function unregister_watch_obj() {
      watched.map(function(unregister) {
        unregister();
      });
    };
    $scope.$on('$destroy', unregister);
    return unregister;
  };
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div class='test' ng-app="app" ng-controller="TestWatch">
  prop1: {{prop1}}
  <br>prop2: {{prop2}}
  <br>prop3 (unwatched): {{prop3}}
  <br>
  <button ng-click="test1()">
    Simple props change
  </button>
  <button ng-click="test2()">
    Async props change
  </button>
  <button ng-click="test3()">
    Async props change with apply
  </button>
</div>

참고 URL : https://stackoverflow.com/questions/12576798/angularjs-how-to-watch-service-variables



반응형