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? 이 경우 단순히 참조에 의존 할 수 없습니다. 그래서 우리는 무엇 할 수 있습니까?

이전에 제공된 몇 가지 답변은 이미 해당 문제에 대한 해결책을 제시합니다. 그러나 개인적으로 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;
}

그 방법은이 $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()

아직 아무도 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;
      }
    };
  // ...
}

서비스 기능에 '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.