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


414

서비스가 있습니다.

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

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


1
aService 및 모든 해당 속성을 자세히 보려면 ​​$ watch에 true로 설정된 세 번째 매개 변수를 전달할 수 있습니다.
— SirTophamHatt

7
$ scope.foo = aService.foo로 충분합니다. 위의 줄을 잃을 수 있습니다. $ scope.foo에 새로운 값을 할당하고 싶다면 $ watch 내부에서하는 일이 의미가 없습니다.
— Jin

4
aService.foohtml 마크 업에서 참조 할 수 있습니까? (이와 같이 : plnkr.co/edit/aNrw5Wo4Q0IxR2loipl5?p=preview )
— thetallweeks

1
콜백이나 $ watches없이 예제를 추가했습니다. 아래 답변을 참조하십시오 ( jsfiddle.net/zymotik/853wvv7s )
— Zymotik

1
@ MikeGledhill, 당신이 맞아요. Javascript의 특성 때문이라고 생각합니다. Angular뿐만 아니라 JS에서도 일반적으로 말하는 다른 곳 에서이 패턴을 볼 수 있습니다. 한편으로는 값을 전송하고 바인딩되지 않습니다. 반면에 객체 (또는 객체를 참조하는 값 ...)를 전송하면 속성이 올바르게 업데이트됩니다 (완벽한 것처럼) 위 Zymotik의 예에 표시됨).
— Christophe Vidal

답변:


277

폭정과 간접비를 피하고 싶을 때는 항상 좋은 오래된 관찰자 패턴을 사용할 수 있습니다 $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
};

21
@Moo $destory는 스코프 에서 이벤트를 듣고 등록 취소 방법을 추가합니다.aService
— Jamie

13
이 솔루션의 장점은 무엇입니까? 서비스에는 더 많은 코드가 필요하고 컨트롤러에는 다소 동일한 양의 코드가 필요합니다 ($ destroy에 등록 취소해야하기 때문에). 나는 실행 속도에 대해 말할 수 있지만 대부분의 경우 중요하지 않습니다.
— Alex Che

6
이것이 $ watch보다 어떻게 더 나은 솔루션인지 확실하지 않은 경우, 질문자는 데이터를 공유하는 간단한 방법을 요구했습니다. 더 번거로워 보입니다. 차라리 이것보다 $ broadcast를 사용하고 싶습니다
— Jin

11
$watchvs 관찰자 패턴은 단순히 폴링 또는 푸시 여부를 선택하는 것이며 기본적으로 성능의 문제이므로 성능이 중요 할 때 사용하십시오. 그렇지 않으면 복잡한 물체를 "깊게"봐야 할 때 관찰자 패턴을 사용합니다. 단일 서비스 값을 보지 않고 $ scope에 전체 서비스를 연결합니다. 악마와 같은 각도의 $ watch를 피하십시오. 지시문과 기본 각도 데이터 바인딩에서 발생하는 것으로 충분합니다.
— dtheodor

107
우리가 Angular와 같은 프레임 워크를 사용하는 이유는 우리 자신의 관찰자 패턴을 요리하지 않기 때문입니다.
— 코드 위스퍼러

230

이와 같은 시나리오에서는 여러 개의 / 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가 더 나은 옵션 인 시나리오가 있습니다.


1
$ 다이제스트주기에서 벗어나는 것은 좋은 일입니다. 특히보고있는 변경 사항이 DOM에 직접적이고 즉각적으로 들어가는 값이 아닌 경우에 좋습니다.
— XML

.save () 메소드를 피하기 위해 멀리 있습니까? sharedService에서 단일 변수의 업데이트를 모니터링하는 경우 오버 킬처럼 보입니다. sharedService 내에서 변수를보고 변경 될 때 브로드 캐스트 할 수 있습니까?
— JerryKur

컨트롤러간에 데이터를 공유하는 몇 가지 방법을 시도했지만 이것이 유일한 방법입니다. 잘 연주했습니다.
— abettermap

나는 이것을 다른 답변보다 선호한다. 덜 해킹 적이다 . 고마워
— JMK

9
이는 소비 컨트롤러에 가능한 여러 데이터 소스가있는 경우에만 올바른 디자인 패턴입니다. 즉, MIMO 상황이있는 경우 (다중 입력 / 다중 출력). 일대 다 패턴을 사용하는 경우 직접 객체 참조를 사용해야하며 Angular 프레임 워크가 양방향 바인딩을 수행하도록해야합니다. Horkyze는 이것을 아래에 연결했으며 자동 양방향 바인딩에 대한 좋은 설명이며 제한 사항은 stsc3000.github.io/blog/2013/10/26/…
— Charles

47

@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에 대한 참조.


Matt Pileggi가 설명하는 $ broadcast에 비해이 방법의 장점은 무엇입니까?
— Fabio

두 방법 모두 용도가 있습니다. 나를위한 방송의 장점은 사람이 읽을 수 있고 같은 장소에서 더 많은 장소에서들을 수 있다는 것입니다. 브로드 캐스트가 모든 하위 범위에 메시지를 전송하므로 성능 문제 일 수 있다는 것이 주된 단점입니다.
— Krym

2
$scope.$watch서비스 변수 작업이 작동하지 않는 문제가 발생 했습니다 (보고있는 범위는에서 상속 된 모달이었습니다 $rootScope)-이것은 효과가있었습니다. 멋진 트릭, 공유해 주셔서 감사합니다!
— Seiyria

4
이 접근 방식으로 자신을 어떻게 정리하겠습니까? 범위가 파괴되면 약속에서 등록 된 콜백을 제거 할 수 있습니까?
— Abris

좋은 질문. 나는 솔직히 모른다. 약속에서 알림 콜백을 제거하는 방법에 대한 몇 가지 테스트를 시도합니다.
— Krym

41

시계 또는 관찰자 콜백 없음 ( 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'는 창 객체를 나타냅니다.


3
이것은 훌륭한 접근법입니다! 서비스의 속성 만 전체 서비스 대신 범위에 바인딩하는 방법이 있습니까? 그냥 $scope.count = service.count작동하지 않습니다.
— jvannistelrooy

(임의의) 객체 내부에 속성을 중첩시켜 참조로 전달할 수도 있습니다. $scope.data = service.data <p>Count: {{ data.count }}</p>
— Alex Ross

1
훌륭한 접근 방식! 이 페이지에는 강력하고 기능적인 답변이 많이 있지만 이것은 a) 구현하기 가장 쉽고 b) 코드를 읽을 때 이해하기 가장 쉽습니다. 이 답변은 현재보다 훨씬 높아야합니다.
— CodeMoose

@CodeMoose 덕분에 AngularJS / JavaScript를 처음 사용하는 사람들을 위해 오늘 더 단순화했습니다.
— Zymotik

2
신의 축복을 빕니다 내가 말하고 백만 시간을 낭비했습니다. 1.5로 인해 어려움을 겪고 angularjs가 1에서 2로
— 바뀌고

29

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

사용하는 것과 같은 각도 표현식이 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 : 각 업데이트 에서 배열 또는 객체가 동일한 객체 인지 확인하십시오.

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

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

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

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;
}

그 방법은이 $watch해결됩니다 aService.foo각$digest 올바르게 업데이트 된 값을 얻습니다.

이것은 당신이 당신의 해결 방법과 관련이 있었지만, 훨씬 덜 둥근 형태입니다. 당신은 불필요한 추가 $watch명시 적으로두고 컨트롤러에 foo온 $scope이 변경 될 때마다. 당신은 그 여분 필요하지 않습니다 $watch당신이 연결할 때 aService대신 aService.foo받는 $scope바인드 명시하고, aService.foo마크 업에.


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


문제 2 : 누락 $digest

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

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

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

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

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

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

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

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

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

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


3
이것이 정확하고 쉬운 해결책입니다. @NanoWizard가 말했듯이 $ digest services는 서비스 자체에 속하는 속성을 감시 하지 않습니다.
— Sarpdoruk Tahmaz

28

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

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

 }

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


35
나는 시도했고 $watch원시적 인 일을 할 수 없었다 . 대신 서비스에서 프리미티브 값을 반환하는 메소드를 정의했습니다. somePrimitive() = function() { return somePrimitive }그리고 그 메소드에 $ scope 속성을 할당했습니다 $scope.somePrimitive = aService.somePrimitive;. 그런 다음 HTML에서 범위 방법을 사용했습니다. <span>{{somePrimitive()}}</span>
— Mark Rajcok

4
@MarkRajcok 기본 요소를 사용하지 마십시오. 개체에 추가하십시오. 프리미티브는 변경 가능하지 않으므로 2way 데이터 바인딩이 작동하지 않습니다.
— Jimmy Kane

3
@JimmyKane, 예, 프리미티브는 양방향 데이터 바인딩에 사용되어서는 안되지만 2 웨이 바인딩을 설정하지 않고 서비스 변수를 보는 것에 대한 질문이라고 생각합니다. 서비스 속성 / 변수 만 볼 필요가있는 경우 객체가 필요하지 않습니다. 기본을 사용할 수 있습니다.
— Mark Rajcok

3
이 설정에서는 범위에서 aService 값을 변경할 수 있습니다. 그러나 aService 변경에 따라 범위가 변경되지 않습니다.
— Ouwen Huang

4
이것은 또한 나를 위해 작동하지 않습니다. 단순히 할당 $scope.foo = aService.foo하면 범위 변수가 자동으로 업데이트되지 않습니다.
— Darwin Tech

9

$ 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");​

2
이 답변이 최고 투표권이 아니라고 믿을 수 없습니다 !!! 정보 엔트로피를 줄이고 추가 조정 처리기의 필요성을 줄일 수 있으므로 가장 세련된 솔루션 (IMO)입니다. 가능하다면 더 많이 투표 하겠어요 ...
— Cody

모든 서비스를 $ rootScope에 추가하면 그 이점과 잠재적 인 함정에 대해 좀 더 자세히 설명합니다 : stackoverflow.com/questions/14573023/…
— Zymotik

6

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

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

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


6

== 업데이트 ==

$ 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 + '!');
  });
}]);

1
PostmanService가 마음에 들지만 둘 이상의 변수를 수신해야하는 경우 컨트롤러에서 $ watch 함수를 어떻게 변경해야합니까?
— jedi

안녕하세요 제디, 고마워요! 펜과 답변을 업데이트했습니다. 다른 시계 기능을 추가하는 것이 좋습니다. PostmanService에 새로운 기능을 추가했습니다. 나는 이것이 도움이되기를 바랍니다 :)
— hayatbiralem

사실, 그렇습니다 :) 문제에 대한 자세한 내용을 공유하면 어쩌면 내가 도울 수 있습니다.
— hayatbiralem

4

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;
};

2

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

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'];

2

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

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

2

매우 비슷한 문제에 직면하면서 범위의 함수를보고 함수가 서비스 변수를 반환하도록했습니다. 나는 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++;
    });
});

2

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


를 사용 setTimeout하거나 다른 비 Angular 함수를 사용할 수 있지만 콜백에서 코드를 래핑하는 것을 잊지 마십시오 $scope.$apply().
— magnetronnie

2

비슷한 문제이지만 완전히 다른 접근법으로 다른 스레드에서 실제로 훌륭한 솔루션을 찾았습니다. 출처 : 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는 각각 반응한다. 매우 쉽고 우아합니다!


1

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

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

1

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

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');
}

감사. 작은 질문 : 왜 this그 서비스 대신에 그 서비스에서 돌아 옵니까 self?
— shrekuu

4
실수가 때때로 발생하기 때문입니다. ;-)
— nclu

return this;생성자 중 우수 사례 ;-)
— Cody

1

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

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

0

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

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

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

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

그러면 참조가 업데이트되고 어디서나 시계가 업데이트됩니다. 서비스 getter 메소드를 포함합니다. 기본 요소는 자동으로 업데이트됩니다.


0

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

제어 장치

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

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


왜 downvoted .. 나는 비슷한 기술을 여러 번 사용하고 작동했습니다.
— undefined

0

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

긴 설명을 건너 뛰려면 해협으로 가십시오.

  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>

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