AngularJS에서 컨트롤러간에 통신하는 올바른 방법은 무엇입니까?


473

컨트롤러간에 통신하는 올바른 방법은 무엇입니까?

나는 현재 window다음 과 관련된 끔찍한 퍼지를 사용하고 있습니다 :

function StockSubgroupCtrl($scope, $http) {
    $scope.subgroups = [];
    $scope.handleSubgroupsLoaded = function(data, status) {
        $scope.subgroups = data;
    }
    $scope.fetch = function(prod_grp) {
        $http.get('/api/stock/groups/' + prod_grp + '/subgroups/').success($scope.handleSubgroupsLoaded);
    }
    window.fetchStockSubgroups = $scope.fetch;
}

function StockGroupCtrl($scope, $http) {
    ...
    $scope.select = function(prod_grp) {
        $scope.selectedGroup = prod_grp;
        window.fetchStockSubgroups(prod_grp);
    }
}

36
완전히 무질서하지만 Angular에서는 항상 기본 JS 창 객체 대신 $ window를 사용해야합니다. 이렇게하면 테스트에서 스텁 할 수 있습니다 :)
Dan M

1
이 문제와 관련하여 아래 답변의 의견을 참조하십시오. $ broadcast는 더 이상 $ emit보다 비싸지 않습니다. 내가 참조한 jsperf 링크를 참조하십시오.
zumalifeguard

답변:


457

편집 :이 답변에서 해결 된 문제는 angular.js 버전 1.2.7 에서 해결되었습니다 . $broadcast이제 등록되지 않은 범위에서 버블 링을 피하고 $ emit만큼 빠르게 실행됩니다. 브로드 캐스트 성능은 각도 1.2.16의 $ emit과 동일합니다.

이제 다음을 수행 할 수 있습니다.

  • 사용하다 $broadcast 으로부터$rootScope
  • 행사에 대해 알아야 $on 할 현지인의$scope 말을 들어보십시오

아래의 원래 답변

$rootScope.$broadcast+ 를 사용 $scope.$on하지 말고 $rootScope.$emit+ 를 사용하는 것이 $rootScope.$on좋습니다. 전자는 @numan으로 인해 심각한 성능 문제를 일으킬 수 있습니다. 이벤트가 모든 것을 통해 버블 링되기 때문입니다 범위에서 입니다.

그러나 후자 ( $rootScope.$emit+ 사용 $rootScope.$on)는이 문제를 겪지 않으므로 빠른 통신 채널로 사용할 수 있습니다!

의 각도 문서에서 $emit:

스코프 계층을 통해 이벤트 이름을 위쪽으로 전달하여 등록되었음을 알립니다.

위의 범위 $rootScope가 없으므로 버블 링이 발생하지 않습니다. $rootScope.$emit()/ $rootScope.$on()EventBus 로 사용하는 것이 안전합니다 .

그러나 컨트롤러 내에서 사용할 때 한 가지 문제가 있습니다. $rootScope.$on()컨트롤러 내에서 직접 바인딩하는 경우 로컬에서 바인딩을 직접 정리해야합니다.$scope 이 손상 . 이는 서비스와 달리 컨트롤러가 응용 프로그램 수명 동안 여러 번 인스턴스화 될 수 있기 때문에 바인딩이 합쳐져 결국 모든 곳에서 메모리 누수가 발생하기 때문입니다. :)

등록을 취소, 당신의 청취 $scope$destroy이벤트 다음에 의해 반환 된 함수를 호출합니다 $rootScope.$on.

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

            var unbind = $rootScope.$on('someComponent.someCrazyEvent', function(){
                console.log('foo');
            });

            $scope.$on('$destroy', unbind);
        }
    ]);

다른 EventBus 구현에도 적용되므로 리소스를 정리해야한다는 것은 특정 각도가 아닙니다.

그러나 그러한 경우 삶을 더 편하게 만들 수 있습니다 . 예를 들어, 원숭이 패치를 수행 $rootScope하여에 생성 $onRootScope된 이벤트를 구독 $rootScope하고 로컬에있을 때 핸들러를 직접 정리할 수 있습니다.$scope 이 파괴 있습니다.

$rootScope그런 $onRootScope방법 을 제공하기 위해 원숭이 패치를하는 가장 깨끗한 방법 은 데코레이터를 사용하는 것입니다 (실행 블록은 아마 잘 할 것입니다.

있는지 확인하려면 $onRootScope이상 열거하는 속성이 예상치 못한 표시되지 않습니다 $scope우리가 사용 Object.defineProperty()하고 설정 enumerablefalse. ES5 심이 필요할 수 있습니다.

angular
    .module('MyApp')
    .config(['$provide', function($provide){
        $provide.decorator('$rootScope', ['$delegate', function($delegate){

            Object.defineProperty($delegate.constructor.prototype, '$onRootScope', {
                value: function(name, listener){
                    var unsubscribe = $delegate.$on(name, listener);
                    this.$on('$destroy', unsubscribe);

                    return unsubscribe;
                },
                enumerable: false
            });


            return $delegate;
        }]);
    }]);

이 방법을 사용하면 위의 컨트롤러 코드를 다음과 같이 단순화 할 수 있습니다.

angular
    .module('MyApp')
    .controller('MyController', ['$scope', function MyController($scope) {

            $scope.$onRootScope('someComponent.someCrazyEvent', function(){
                console.log('foo');
            });
        }
    ]);

그래서 모든이의 최종 결과로 내가보기 엔 사용하도록 조언 $rootScope.$emit+$scope.$onRootScope .

Btw, 나는 각진 팀이 각진 핵심 내의 문제를 해결하도록 설득하려고 노력하고 있습니다. https://github.com/angular/angular.js/issues/4574 에 대한 토론이 있습니다.

다음은 $broadcast괜찮은 시나리오에서 100 $scope의 성능으로 테이블에 미치는 영향의 정도를 보여주는 jsperf입니다 .

http://jsperf.com/rootscope-emit-vs-rootscope-broadcast

jsperf 결과


두 번째 옵션을 시도하고 있지만 오류가 발생합니다. 잡히지 않은 TypeError : 속성을 재정의 할 수 없음 : $ onRootScope Object.defineProperty를 수행하는 곳에서 바로 ....
Scott

어쩌면 여기에 붙여 넣을 때 무언가를 망 쳤을 수도 있습니다. 나는 그것을 생산에 사용하고 훌륭하게 작동합니다. 나는 내일 살펴볼 것이다 :)
Christoph

@Scott 나는 그것을 붙여 넣었지만 코드는 이미 정확하고 프로덕션에서 사용하는 것과 정확히 같습니다. 사이트에 오타가 없는지 다시 확인할 수 있습니까? 문제 해결에 도움이 될만한 코드가 있습니까?
Christoph

@Christoph는 DOM이 아닌 객체에서 Object.defineProperty를 지원하지 않기 때문에 IE8에서 데코레이터를 수행하는 좋은 방법이 있습니까?
joshschreuder

59
이것은 문제에 대한 매우 영리한 해결책 이었지만 더 이상 필요하지 않습니다. 최신 버전의 Angular (1.2.16) 및 아마도 이전 버전에서는이 문제가 해결되었습니다. 이제 $ broadcast는 이유없이 모든 하위 컨트롤러를 방문하지 않습니다. 실제로 이벤트를 듣고있는 사람들 만 방문합니다. 위에서 언급 한 jsperf를 업데이트하여 문제가 해결되었음을 보여줍니다. jsperf.com/rootscope-emit-vs-rootscope-broadcast/27
zumalifeguard

107

상단의 대답은 여기에 더 이상 (> 버전 적어도 1.2.16와 "아마 이전") @zumalifeguard가 언급 한 것처럼이 존재하는 각도 문제에서 주변의 일이었다. 그러나 실제 해결책 없이이 모든 대답을 읽었습니다.

대답은 이제

  • 사용$broadcast 으로부터$rootScope
  • 행사에 대해 알아야 $on 할 현지인의$scope 말을 들어보십시오

그래서 출판

// EXAMPLE PUBLISHER
angular.module('test').controller('CtrlPublish', ['$rootScope', '$scope',
function ($rootScope, $scope) {

  $rootScope.$broadcast('topic', 'message');

}]);

그리고 구독

// EXAMPLE SUBSCRIBER
angular.module('test').controller('ctrlSubscribe', ['$scope',
function ($scope) {

  $scope.$on('topic', function (event, arg) { 
    $scope.receiver = 'got your ' + arg;
  });

}]);

플 런커

로컬에 리스너를 등록하면 $scope, 그것은됩니다 에 의해 자동으로 파괴 $destroy자체 관련 컨트롤러가 제거 될 때.


1
동일한 패턴을 controllerAs구문 과 함께 사용할 수 있는지 알고 있습니까? $rootScope구독자 를 사용 하여 이벤트를 수신 할 수 있었지만 다른 패턴이 있는지 궁금했습니다.
edhedges

3
@edhedges $scope명시 적으로 주사 할 수 있다고 생각합니다 . 존 파파 (John Papa)는$scope 자신의 컨트롤러에서 "아웃"을 유지하는 일반적인 규칙에 대한 하나의 "예외"이벤트에 대해 씁니다 ( Controller As아직 언급 $scope했듯이 따옴표를 사용하므로 보닛 아래에 있습니다).
poshest

보닛 아래에서 주입을 통해 여전히 얻을 수 있음을 의미합니까?
edhedges

2
@edhedges controller as요청에 따라 구문 대안으로 답변을 업데이트했습니다 . 나는 그것이 당신이 의미 한 바라고 있기를 바랍니다.
poshest

3
@dsdsdsdsd, 서비스 / 공장 / 제공자는 영원히 유지됩니다. Angular 앱에는 항상 하나만 있습니다 (싱글 톤). 반면에 컨트롤러는 기능과 연결되어 있습니다. 컴포넌트 / 지시문 / ng- 컨트롤러 (클래스로 만든 객체와 같이)를 반복 할 수 있으며 필요에 따라오고갑니다. 더 이상 필요하지 않을 때 컨트롤과 컨트롤러가 기존 상태를 유지하는 이유는 무엇입니까? 이것이 바로 메모리 누수의 정의입니다.
poshest


42

defineProperty에는 브라우저 호환성 문제가 있으므로 서비스 사용에 대해 생각할 수 있다고 생각합니다.

angular.module('myservice', [], function($provide) {
    $provide.factory('msgBus', ['$rootScope', function($rootScope) {
        var msgBus = {};
        msgBus.emitMsg = function(msg) {
        $rootScope.$emit(msg);
        };
        msgBus.onMsg = function(msg, scope, func) {
            var unbind = $rootScope.$on(msg, func);
            scope.$on('$destroy', unbind);
        };
        return msgBus;
    }]);
});

다음과 같이 컨트롤러에서 사용하십시오.

  • 컨트롤러 1

    function($scope, msgBus) {
        $scope.sendmsg = function() {
            msgBus.emitMsg('somemsg')
        }
    }
  • 컨트롤러 2

    function($scope, msgBus) {
        msgBus.onMsg('somemsg', $scope, function() {
            // your logic
        });
    }

7
범위가 소멸 될 때 자동 탈퇴의 경우 +1입니다.
Federico Nafria

6
나는이 해결책을 좋아한다. 2 변경 사항 : (1) 사용자가 방출 데이터에 '데이터'를 전달하도록 허용합니다. (2) 'scope'를 선택적으로 전달하여 컨트롤러뿐만 아니라 싱글 톤 서비스에서도 사용할 수 있습니다. 이러한 변경 사항은 여기에서 구현 된 것을 볼 수 있습니다. gist.github.com/turtlemonvh/10686980/…
turtlemonvh


15

이벤트가 스코프 계층 구조를 위아래로 버블 링하여 복잡한 응용 프로그램의 성능 병목 상태로 쉽게 저하 될 수 있으므로 실제로 이미 터 및 브로드 캐스트를 사용하는 것은 비효율적입니다.

서비스를 사용하는 것이 좋습니다. - 저는 여기에 최근에 내 프로젝트 중 하나에 구현하는 방법이다 https://gist.github.com/3384419 .

기본 아이디어-pubsub / event 버스를 서비스로 등록하십시오. 그런 다음 이벤트 / 주제를 구독하거나 게시해야하는 경우 해당 이벤트 버스를 주입하십시오.


7
더 이상 컨트롤러가 필요하지 않은 경우 어떻게 자동으로 구독을 취소합니까? 이 작업을 수행하지 않으면 폐쇄로 인해 컨트롤러가 메모리에서 제거되지 않으며 여전히 메시지를 감지하게됩니다. 이를 피하려면 수동으로 제거한 다음 제거해야합니다. $ on을 사용하면 발생하지 않습니다.
Renan Tomal Fernandes

1
그것은 공정한 포인트입니다. 응용 프로그램을 설계하는 방법으로 해결할 수 있다고 생각합니다. 내 경우에는 단일 페이지 응용 프로그램이 있으므로 더 다루기 쉬운 문제가 있습니다. 앵귤러에 이와 같은 것을 와이어 / 와이어 링 할 수있는 구성 요소 수명주기 후크가 있다면 훨씬 더 깨끗할 것이라고 생각합니다.
numan salati

6
아무도 전에 언급하지 않았으므로 여기에 그대로 두십시오. EventBus로 rootScope 사용이다 하지 비효율적 $rootScope.$emit()만 위쪽 거품. 그러나 위의 범위가 없기 $rootScope때문에 두려워 할 것이 없습니다. 그래서 그냥 사용하는 경우 $rootScope.$emit()그리고 $rootScope.$on()당신은 넓은 빠른 시스템 EventBus있을 것이다.
Christoph

1
$rootScope.$on()컨트롤러 내부에서 사용하는 경우 이벤트 바인딩을 정리해야합니다. 그렇지 않으면 컨트롤러가 인스턴스화 될 때마다 이벤트 바인딩을 새로 만들 때마다 이벤트 바인딩을 정리해야합니다. 당신이 $rootScope직접 바인딩하고 있기 때문에 당신을 위해 자동으로 파괴 됩니다.
Christoph

최신 버전의 Angular (1.2.16) 및 아마도 이전 버전에서는이 문제가 해결되었습니다. 이제 $ broadcast는 이유없이 모든 하위 컨트롤러를 방문하지 않습니다. 실제로 이벤트를 듣고있는 사람들 만 방문합니다. 위에서 언급 한 jsperf를 업데이트하여 문제가 해결되었음을 보여줍니다. jsperf.com/rootscope-emit-vs-rootscope-broadcast/27
zumalifeguard

14

서비스 내에서 get 및 set 메소드를 사용하면 컨트롤러간에 메시지를 매우 쉽게 전달할 수 있습니다.

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

myApp.factory('myFactoryService',function(){


    var data="";

    return{
        setData:function(str){
            data = str;
        },

        getData:function(){
            return data;
        }
    }


})


myApp.controller('FirstController',function($scope,myFactoryService){
    myFactoryService.setData("Im am set in first controller");
});



myApp.controller('SecondController',function($scope,myFactoryService){
    $scope.rslt = myFactoryService.getData();
});

HTML HTML에서는 다음과 같이 확인할 수 있습니다

<div ng-controller='FirstController'>  
</div>

<div ng-controller='SecondController'>
    {{rslt}}
</div>

+1 분명한 방법 중 하나입니다-훌륭합니다! set (key, value) 및 get (key) 메소드를 사용하여보다 일반적인 버전을 구현했습니다. $ broadcast의 유용한 대안입니다.
TonyWilk

8

원래 코드와 관련하여 범위간에 데이터를 공유하려고합니다. $ scope간에 Data 또는 State를 공유하려면 문서에서 서비스 사용을 제안하십시오.

  • 컨트롤러간에 공유되는 상태 비 저장 또는 상태 저장 코드를 실행하려면 — 대신 각도 서비스를 사용하십시오.
  • 다른 구성 요소의 수명주기를 인스턴스화하거나 관리합니다 (예 : 서비스 인스턴스 생성).

참고 : 각도 문서 링크는 여기


5

실제로 Postal.js를 컨트롤러 간의 메시지 버스로 사용하기 시작했습니다.

AMQP 스타일 바인딩과 같은 메시지 버스, 우편이 iFrame과 웹 소켓을 통합 할 수있는 방법 등 많은 이점이 있습니다.

나는 우편물을 설치하기 위해 데코레이터를 사용했습니다 $scope.$bus...

angular.module('MyApp')  
.config(function ($provide) {
    $provide.decorator('$rootScope', ['$delegate', function ($delegate) {
        Object.defineProperty($delegate.constructor.prototype, '$bus', {
            get: function() {
                var self = this;

                return {
                    subscribe: function() {
                        var sub = postal.subscribe.apply(postal, arguments);

                        self.$on('$destroy',
                        function() {
                            sub.unsubscribe();
                        });
                    },
                    channel: postal.channel,
                    publish: postal.publish
                };
            },
            enumerable: false
        });

        return $delegate;
    }]);
});

여기 주제에 대한 블로그 게시물에 대한 링크가 있습니다 ...
http://jonathancreamer.com/an-angular-event-bus-with-postal-js/


3

이것이 공장 / 서비스 및 간단한 의존성 주입 (DI)으로 수행하는 방법 입니다.

myApp = angular.module('myApp', [])

# PeopleService holds the "data".
angular.module('myApp').factory 'PeopleService', ()->
  [
    {name: "Jack"}
  ]

# Controller where PeopleService is injected
angular.module('myApp').controller 'PersonFormCtrl', ['$scope','PeopleService', ($scope, PeopleService)->
  $scope.people = PeopleService
  $scope.person = {} 

  $scope.add = (person)->
    # Simply push some data to service
    PeopleService.push angular.copy(person)
]

# ... and again consume it in another controller somewhere...
angular.module('myApp').controller 'PeopleListCtrl', ['$scope','PeopleService', ($scope, PeopleService)->
  $scope.people = PeopleService
]

1
두 컨트롤러는 통신하지 않으며 동일한 서비스 만 사용합니다. 그것은 같은 것이 아닙니다.
Greg Greg

@Greg 당신은 공유 서비스를 가지고 필요한 곳에 $ watches를 추가함으로써 적은 코드로 같은 것을 달성 할 수 있습니다.
Capaj

3

나는 $rootscope.emit인터 커뮤니케이션을 달성 하는 방법을 좋아했다 . 전 세계 공간을 오염시키지 않고 깨끗하고 성능 효율적인 솔루션을 제안합니다.

module.factory("eventBus",function (){
    var obj = {};
    obj.handlers = {};
    obj.registerEvent = function (eventName,handler){
        if(typeof this.handlers[eventName] == 'undefined'){
        this.handlers[eventName] = [];  
    }       
    this.handlers[eventName].push(handler);
    }
    obj.fireEvent = function (eventName,objData){
       if(this.handlers[eventName]){
           for(var i=0;i<this.handlers[eventName].length;i++){
                this.handlers[eventName][i](objData);
           }

       }
    }
    return obj;
})

//Usage:

//In controller 1 write:
eventBus.registerEvent('fakeEvent',handler)
function handler(data){
      alert(data);
}

//In controller 2 write:
eventBus.fireEvent('fakeEvent','fakeData');

메모리 누수의 경우 이벤트 리스너에서 등록을 해제하는 추가 방법을 추가해야합니다. 어쨌든 좋은 사소한 샘플
Raffaeu

2

빠르고 더러운 방법이 있습니다.

// Add $injector as a parameter for your controller

function myAngularController($scope,$injector){

    $scope.sendorders = function(){

       // now you can use $injector to get the 
       // handle of $rootScope and broadcast to all

       $injector.get('$rootScope').$broadcast('sinkallships');

    };

}

형제 컨트롤러 중 하나에 추가하는 함수의 예는 다음과 같습니다.

$scope.$on('sinkallships', function() {

    alert('Sink that ship!');                       

});

물론 HTML은 다음과 같습니다.

<button ngclick="sendorders()">Sink Enemy Ships</button>

16
왜 그냥 주사하지 $rootScope?
Pieter Herroelen

1

앵귤러 1.5부터 시작하여 컴포넌트 기반 개발에 중점을 둡니다. 컴포넌트가 상호 작용하는 권장 방법은 'require'특성을 사용하고 특성 바인딩 (입력 / 출력)을 사용하는 것입니다.

구성 요소는 다른 구성 요소 (예 : 루트 구성 요소)가 필요하며 해당 구성 요소의 컨트롤러에 대한 참조를 가져옵니다.

angular.module('app').component('book', {
    bindings: {},
    require: {api: '^app'},
    template: 'Product page of the book: ES6 - The Essentials',
    controller: controller
});

그런 다음 자식 구성 요소에서 루트 구성 요소의 방법을 사용할 수 있습니다.

$ctrl.api.addWatchedBook('ES6 - The Essentials');

이것은 루트 컴포넌트 컨트롤러 기능입니다.

function addWatchedBook(bookName){

  booksWatched.push(bookName);

}

다음은 완전한 아키텍처 개요입니다. 구성 요소 통신


0

모듈의 어느 곳에서나이 hello 기능에 액세스 할 수 있습니다

컨트롤러 하나

 $scope.save = function() {
    $scope.hello();
  }

두 번째 컨트롤러

  $rootScope.hello = function() {
    console.log('hello');
  }

더 많은 정보는 여기에


7
파티에 조금 늦었지만 이렇게하지 마십시오. 루트 스코프에 함수를 두는 것은 함수를 전역으로 만드는 것과 유사하며 모든 종류의 문제를 일으킬 수 있습니다.
Dan Pantry

0

서비스를 만들고 알림을 사용하겠습니다.

  1. 알림 서비스에서 메소드 작성
  2. Notification Service에서 알림을 브로드 캐스트하는 일반적인 방법을 만듭니다.
  3. 소스 컨트롤러에서 notificationService.Method를 호출하십시오. 필요한 경우 지속되도록 해당 객체를 전달합니다.
  4. 메서드 내에서 알림 서비스의 데이터를 유지하고 일반 알림 메서드를 호출합니다.
  5. 대상 컨트롤러에서 브로드 캐스트 이벤트를 수신하고 ($ scope.on) Notification Service의 데이터에 액세스합니다.

어느 시점에서든 Notification Service는 싱글 톤이므로 지속적인 데이터를 제공 할 수 있어야합니다.

도움이 되었기를 바랍니다


0

AngularJS 내장 서비스를 사용할 수 있습니다 $rootScope 를 사용하고이 서비스를 두 컨트롤러에 모두 주입 . 그런 다음 $ rootScope 객체에서 시작된 이벤트를 수신 할 수 있습니다.

$ rootScope는 이벤트 디스패치 $emit and $broadcast(맞춤 이벤트 일 수 있음)를 담당하고 $rootScope.$on이벤트 리스너를 추가 하는 기능을 사용 하는 두 개의 이벤트 디스패처를 제공합니다 .


0

$rootscope전체 Application에서 액세스 할 수 있으므로 Service를 사용해야하며 로드가 증가하거나 데이터가 더 이상없는 경우 rootparams를 사용합니다.


0
function mySrvc() {
  var callback = function() {

  }
  return {
    onSaveClick: function(fn) {
      callback = fn;
    },
    fireSaveClick: function(data) {
      callback(data);
    }
  }
}

function controllerA($scope, mySrvc) {
  mySrvc.onSaveClick(function(data) {
    console.log(data)
  })
}

function controllerB($scope, mySrvc) {
  mySrvc.fireSaveClick(data);
}

0

$ emit 및 $ broadcast 인 각도 이벤트를 사용하여이를 수행 할 수 있습니다. 우리의 지식에 따르면 이것은 가장 효과적이고 효과적인 방법입니다.

먼저 하나의 컨트롤러에서 함수를 호출합니다.

var myApp = angular.module('sample', []);
myApp.controller('firstCtrl', function($scope) {
    $scope.sum = function() {
        $scope.$emit('sumTwoNumber', [1, 2]);
    };
});
myApp.controller('secondCtrl', function($scope) {
    $scope.$on('sumTwoNumber', function(e, data) {
        var sum = 0;
        for (var a = 0; a < data.length; a++) {
            sum = sum + data[a];
        }
        console.log('event working', sum);

    });
});

$ scope 대신 $ rootScope를 사용할 수도 있습니다. 그에 따라 컨트롤러를 사용하십시오.

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