906

How can I send my $scope object from one controller to another using .$emit and .$on methods?

function firstCtrl($scope) {
    $scope.$emit('someEvent', [1,2,3]);
}

function secondCtrl($scope) {
    $scope.$on('someEvent', function(mass) { console.log(mass); });
}

It doesn't work the way I think it should. How do $emit and $on work?

Premraj
  • 72,055
  • 26
  • 237
  • 180
Paul Kononenko
  • 9,407
  • 4
  • 18
  • 13

12 Answers12

1511

First of all, parent-child scope relation does matter. You have two possibilities to emit some event:

  • $broadcast -- dispatches the event downwards to all child scopes,
  • $emit -- dispatches the event upwards through the scope hierarchy.

I don't know anything about your controllers (scopes) relation, but there are several options:

  1. If scope of firstCtrl is parent of the secondCtrl scope, your code should work by replacing $emit by $broadcast in firstCtrl:

    function firstCtrl($scope)
    {
        $scope.$broadcast('someEvent', [1,2,3]);
    }
    
    function secondCtrl($scope)
    {
        $scope.$on('someEvent', function(event, mass) { console.log(mass); });
    }
    
  2. In case there is no parent-child relation between your scopes you can inject $rootScope into the controller and broadcast the event to all child scopes (i.e. also secondCtrl).

    function firstCtrl($rootScope)
    {
        $rootScope.$broadcast('someEvent', [1,2,3]);
    }
    
  3. Finally, when you need to dispatch the event from child controller to scopes upwards you can use $scope.$emit. If scope of firstCtrl is parent of the secondCtrl scope:

    function firstCtrl($scope)
    {
        $scope.$on('someEvent', function(event, data) { console.log(data); });
    }
    
    function secondCtrl($scope)
    {
        $scope.$emit('someEvent', [1,2,3]);
    }
    
zbynour
  • 19,747
  • 3
  • 30
  • 44
  • 9
    Is there a way to fire an event from a service to a controller? – Zlatko Jan 16 '14 at 23:14
  • 29
    Yes theoretically you could inject `$rootScope` into your service and broadcast the event from the service. – zbynour Jan 17 '14 at 07:19
  • Thanks, I wanted to see if you could do this without rootscope. – Zlatko Jan 17 '14 at 22:54
  • service \ service function can get a reference to a scope as a parameter – Oleg Belousov Feb 03 '14 at 09:39
  • 14
    @Zlatko I'm pretty sure services by default are scope-less, and you need a scope to participate in the event system. So you somehow need to provide a scope to your service. $rootScope is the most general-purpose solution to that, but if you want your service to send events from a different scope, your controller could pass its scope to the service by setting a property on the service, and now the service can use the controller's scope. A more straight-forward technique might be for the controller to provide a function to the service which the service can call directly. – Oran Dennison Feb 21 '14 at 20:46
  • Where you are doing an $emit, if you know nothing else needs to process it after the parent receives it, then it would be sensible to cancel the event to prevent it needlessly propogating further up the controller hierarchy. – arcseldon May 16 '14 at 03:46
  • 3
    If you are using a iframe this article will be helpful http://charemza.name/blog/posts/angularjs/iframe/same-domain-iframe-communication/ – leticia Sep 21 '14 at 01:29
  • 3
    Services can inject `$rootScope` -- but I want to know that if I emit an event from a service (off of `$rootScope`), that the event will still percolate to `$rootScope`; BECAUSE, if `$broadcast` percolates DOWN the hierarchy, and `$emit` percolates UP -- what happens BETWEEN "UP" and "DOWN" -- since the broadcaster/emitter is also the listener (?). What if I want the event to be silent to ALL "UPWARD" and ALL "DOWNWARD" scopes, but only be 'audible' on the same level as the dispatcher? – Cody Sep 26 '14 at 00:10
  • @zbynour BUT... what if the controller was never executed so that it could subscribe to an event, fired from the service because the service is directly executed via changing the route url? Then the service would fire data but nobody subscribed to it. That would be a bug and nobody can fix it. A bug in the architecture. – HelloWorld Dec 30 '14 at 13:46
  • @HelloWorld if the controller was never executed perhaps the event doesn't need to be handled by it ... – zbynour Dec 30 '14 at 15:27
  • 1
    I think the idea of passing a scope to a service, though possible, is a confusion of levels (between view and model). Stuff on scope is bound to the view, stuff in the service layer is model. You should endeavour not to mix them up. – see sharper Dec 09 '15 at 23:37
  • 1
    @zbynour Injecting the rootScope into a service is fine for the purpose of broadcasts. It can be advantageous to use events over watchers and the events mechanism works well, so in general the right design choice and preferable to reinventing an alternative. Further, there's no penalty as the number of active scopes increases, so remains sound in general even for a single controller that's listening. That said, creating a generic callback registry that the service has an instance of that listeners can register with could offer benefits, but broadcasts should be your initial thought. – Nick Sep 21 '18 at 08:44
146

I would additionally suggest a 4th option as a better alternative to the proposed options by @zbynour.

Use $rootScope.$emit rather than $rootScope.$broadcast regardless of the relationship between trasmitting and receiving controller. That way, the event remains within the set of $rootScope.$$listeners whereas with $rootScope.$broadcast the event propagates to all children scopes, most of which will probably not be listeners of that event anyway. And of course in the receiving controller's end you just use $rootScope.$on.

For this option you must remember to destroy the controller's rootScope listeners:

var unbindEventHandler = $rootScope.$on('myEvent', myHandler);
$scope.$on('$destroy', function () {
  unbindEventHandler();
});
clopez
  • 4,372
  • 3
  • 28
  • 42
Thalis K.
  • 7,363
  • 6
  • 39
  • 54
  • 3
    This would then basically serve as a central event bus correct? – jusopi Jun 11 '14 at 03:30
  • 5
    In a sense yes, the benefit being that you avoid event propagation. – Thalis K. Jun 11 '14 at 09:26
  • 3
    @ThalisK. thanks for this option. It avoids the propagation but on the other hand it requires `$rootScope` injection into controllers (what is not needed in general). But surely another option, thx! – zbynour Jul 02 '14 at 13:18
  • you guys should choose this, this is simple and work all the time. All the time, you should prefer simple and easy to manage rather than smart code. – Dzung Nguyen Oct 31 '14 at 11:39
  • 78
    Beware that $rootScope lives forever. If your controller is run twice, any $rootScope.$on inside it will be run twice, and caught events will result in a callback invoked twice. If you use $scope.$on instead, the callback will be destroyed along with your controller implicitly by AngularJS. – Filip Sobczak Nov 28 '14 at 16:09
  • 1
    According to @FilipSobczak comment, you can avoid this unwanted behaviour by unbinding handler on $destroy event with the following code https://jsfiddle.net/ndqexjsg/1/ – Krzysztof Grzybek Mar 24 '16 at 10:57
  • @Thalis K. are there any advantages using $rootScope? I think it is not good idea.... – Oleksandr T. Jun 10 '16 at 18:56
115

How can I send my $scope object from one controller to another using .$emit and .$on methods?

You can send any object you want within the hierarchy of your app, including $scope.

Here is a quick idea about how broadcast and emit work.

Notice the nodes below; all nested within node 3. You use broadcast and emit when you have this scenario.

Note: The number of each node in this example is arbitrary; it could easily be the number one; the number two; or even the number 1,348. Each number is just an identifier for this example. The point of this example is to show nesting of Angular controllers/directives.

                 3
           ------------
           |          |
         -----     ------
         1   |     2    |
      ---   ---   ---  ---
      | |   | |   | |  | |

Check out this tree. How do you answer the following questions?

Note: There are other ways to answer these questions, but here we'll discuss broadcast and emit. Also, when reading below text assume each number has it's own file (directive, controller) e.x. one.js, two.js, three.js.

How does node 1 speak to node 3?

In file one.js

scope.$emit('messageOne', someValue(s));

In file three.js - the uppermost node to all children nodes needed to communicate.

scope.$on('messageOne', someValue(s));

How does node 2 speak to node 3?

In file two.js

scope.$emit('messageTwo', someValue(s));

In file three.js - the uppermost node to all children nodes needed to communicate.

scope.$on('messageTwo', someValue(s));

How does node 3 speak to node 1 and/or node 2?

In file three.js - the uppermost node to all children nodes needed to communicate.

scope.$broadcast('messageThree', someValue(s));

In file one.js && two.js whichever file you want to catch the message or both.

scope.$on('messageThree', someValue(s));

How does node 2 speak to node 1?

In file two.js

scope.$emit('messageTwo', someValue(s));

In file three.js - the uppermost node to all children nodes needed to communicate.

scope.$on('messageTwo', function( event, data ){
  scope.$broadcast( 'messageTwo', data );
});

In file one.js

scope.$on('messageTwo', someValue(s));

HOWEVER

When you have all these nested child nodes trying to communicate like this, you will quickly see many $on's, $broadcast's, and $emit's.

Here is what I like to do.

In the uppermost PARENT NODE ( 3 in this case... ), which may be your parent controller...

So, in file three.js

scope.$on('pushChangesToAllNodes', function( event, message ){
  scope.$broadcast( message.name, message.data );
});

Now in any of the child nodes you only need to $emit the message or catch it using $on.

NOTE: It is normally quite easy to cross talk in one nested path without using $emit, $broadcast, or $on, which means most use cases are for when you are trying to get node 1 to communicate with node 2 or vice versa.

How does node 2 speak to node 1?

In file two.js

scope.$emit('pushChangesToAllNodes', sendNewChanges());

function sendNewChanges(){ // for some event.
  return { name: 'talkToOne', data: [1,2,3] };
}

In file three.js - the uppermost node to all children nodes needed to communicate.

We already handled this one remember?

In file one.js

scope.$on('talkToOne', function( event, arrayOfNumbers ){
  arrayOfNumbers.forEach(function(number){
    console.log(number);
  });
});

You will still need to use $on with each specific value you want to catch, but now you can create whatever you like in any of the nodes without having to worry about how to get the message across the parent node gap as we catch and broadcast the generic pushChangesToAllNodes.

starball
  • 20,030
  • 7
  • 43
  • 238
SoEzPz
  • 14,958
  • 8
  • 61
  • 64
  • how to decide which one is 3,2 and 1? – HIRA THAKUR Sep 18 '15 at 13:58
  • The 3, 2, and 1 are either nested controllers or directives. As you create your app, keep in mind your nesting and apply the logic above. For an example, we could say 3 is the $rootScope of the application; and everything is nested below it. 3, 2, and 1 are arbitrary. – SoEzPz Sep 18 '15 at 16:00
  • Great examples! But I'm still thinking that better to use **own** event-dispatcher in parent to communicate group of controllers. Also useful to keep dispatcher's creation as service to use it as pattern. – DenisKolodin Oct 20 '15 at 10:31
  • 1
    According to [angular docs on $broadcast](https://docs.angularjs.org/api/ng/type/$rootScope.Scope) `The event life cycle starts at the scope on which $broadcast was called. All listeners listening for name event on this scope get notified. ` therefore you (like me) will get an infinite loop if you implement ctrl1 talking to ctrl2 with `$on('x', function(e, data) { $broadcast('x', data) })` on ctrl3. You will need these lines before broadcasting; `if (e.targetScope.$id === $scope.$id) { return; }` – Renato Gama Mar 17 '16 at 19:06
39

To send $scope object from one controller to another, I will discuss about $rootScope.$broadcast and $rootScope.$emit here as they are used most.

Case 1:

$rootScope.$broadcast:-

$rootScope.$broadcast('myEvent',$scope.data);//Here `myEvent` is event name

$rootScope.$on('myEvent', function(event, data) {} //listener on `myEvent` event

$rootScope listener are not destroyed automatically. You need to destroy it using $destroy. It is better to use $scope.$on as listeners on $scope are destroyed automatically i.e. as soon as $scope is destroyed.

$scope.$on('myEvent', function(event, data) {}

Or,

  var customeEventListener = $rootScope.$on('myEvent', function(event, data) {

  }
  $scope.$on('$destroy', function() {
        customeEventListener();
  });

Case 2:

$rootScope.$emit:

   $rootScope.$emit('myEvent',$scope.data);

   $rootScope.$on('myEvent', function(event, data) {}//$scope.$on not works

The major difference in $emit and $broadcast is that $rootScope.$emit event must be listened using $rootScope.$on, because the emitted event never comes down through the scope tree..
In this case also you must destroy the listener as in the case of $broadcast.

Edit:

I prefer not to use $rootScope.$broadcast + $scope.$on but use $rootScope.$emit+ $rootScope.$on. The $rootScope.$broadcast + $scope.$on combo can cause serious performance problems. That is because the event will bubble down through all scopes.

Edit 2:

The issue addressed in this answer have been resolved in angular.js version 1.2.7. $broadcast now avoids bubbling over unregistered scopes and runs just as fast as $emit.

Ved
  • 11,837
  • 5
  • 42
  • 60
10

You must use $rootScope to send and capture events between controllers in same app. Inject $rootScope dependency to your controllers. Here is a working example.

app.controller('firstCtrl', function($scope, $rootScope) {        
        function firstCtrl($scope) {
        {
            $rootScope.$emit('someEvent', [1,2,3]);
        }
}

app.controller('secondCtrl', function($scope, $rootScope) {
        function secondCtrl($scope)
        {
            $rootScope.$on('someEvent', function(event, data) { console.log(data); });
        }
}

Events linked into $scope object just work in the owner controller. Communication between controllers is done via $rootScope or Services.

kyasar
  • 439
  • 5
  • 7
7

You can call a service from your controller that returns a promise and then use it in your controller. And further use $emit or $broadcast to inform other controllers about it. In my case, I had to make http calls through my service, so I did something like this :

function ParentController($scope, testService) {
    testService.getList()
        .then(function(data) {
            $scope.list = testService.list;
        })
        .finally(function() {
            $scope.$emit('listFetched');
        })


    function ChildController($scope, testService) {
        $scope.$on('listFetched', function(event, data) {
            // use the data accordingly
        })
    }

and my service looks like this

    app.service('testService', ['$http', function($http) {

        this.list = [];

        this.getList = function() {
            return $http.get(someUrl)
                .then(function(response) {
                    if (typeof response.data === 'object') {
                        list = response.data.results;

                        return response.data;
                    } else {
                        // invalid response
                        return $q.reject(response.data);
                    }

                }, function(response) {
                    // something went wrong
                    return $q.reject(response.data);
                });

        }

    }])
sg7
  • 6,108
  • 2
  • 32
  • 40
ribhu
  • 171
  • 1
  • 5
4

This is my function:

$rootScope.$emit('setTitle', newVal.full_name);

$rootScope.$on('setTitle', function(event, title) {
    if (scope.item) 
        scope.item.name = title;
    else 
        scope.item = {name: title};
});
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
trai bui
  • 588
  • 12
  • 36
  • 1
    I think this is bad practice as your rootScope will be cluttered. See http://stackoverflow.com/questions/24830679/why-we-use-rootscope-broadcast-in-angularjs – SKuijers Mar 18 '15 at 10:14
4
<!DOCTYPE html>
<html>

<head>
<script src= "http://ajax.googleapis.com/ajax/libs/angularjs/1.3.14/angular.min.js"></script>
<script>
var app = angular.module('MyApp',[]);
app.controller('parentCtrl',function($scope){
  $scope.$on('MyEvent',function(event,data){    
    $scope.myData = data;
  });
 });

app.controller('childCtrl',function($scope){
  $scope.fireEvent = function(){ 
  $scope.$emit('MyEvent','Any Data');
  }  
 });
</script>
</head>
<body ng-app="MyApp">
<div ng-controller="parentCtrl" ng-model="myName">

{{myData}}

 <div ng-controller="childCtrl">
   <button ng-click="fireEvent()">Fire Event</button>
 </div>

</div>
</body>
</html>
Prashant_M
  • 2,868
  • 1
  • 31
  • 24
2

Below code shows the two sub-controllers from where the events are dispatched upwards to parent controller (rootScope)

<body ng-app="App">

    <div ng-controller="parentCtrl">

        <p>City : {{city}} </p>
        <p> Address : {{address}} </p>

        <div ng-controller="subCtrlOne">
            <input type="text" ng-model="city" />
            <button ng-click="getCity(city)">City !!!</button>
        </div>

        <div ng-controller="subCtrlTwo">

            <input type="text" ng-model="address" />
            <button ng-click="getAddrress(address)">Address !!!</button>

        </div>

    </div>

</body>

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

// parent controller
App.controller('parentCtrl', parentCtrl);

parentCtrl.$inject = ["$scope"];

function parentCtrl($scope) {

    $scope.$on('cityBoom', function(events, data) {
        $scope.city = data;
    });

    $scope.$on('addrBoom', function(events, data) {
        $scope.address = data;
    });
}

// sub controller one

App.controller('subCtrlOne', subCtrlOne);

subCtrlOne.$inject = ['$scope'];

function subCtrlOne($scope) {

    $scope.getCity = function(city) {

        $scope.$emit('cityBoom', city);    
    }
}

// sub controller two

App.controller('subCtrlTwo', subCtrlTwo);

subCtrlTwo.$inject = ["$scope"];

function subCtrlTwo($scope) {

    $scope.getAddrress = function(addr) {

        $scope.$emit('addrBoom', addr);   
    }
}

http://jsfiddle.net/shushanthp/zp6v0rut/

sg7
  • 6,108
  • 2
  • 32
  • 40
Shushanth Pallegar
  • 2,832
  • 1
  • 14
  • 16
2

Scope(s) can be used to propagate, dispatch event to the scope children or parent.

$emit - propagates the event to parent. $broadcast - propagates the event to children. $on - method to listen the events, propagated by $emit and $broadcast.

example index.html:

<div ng-app="appExample" ng-controller="EventCtrl">
      Root(Parent) scope count: {{count}}
  <div>
      <button ng-click="$emit('MyEvent')">$emit('MyEvent')</button>
      <button ng-click="$broadcast('MyEvent')">$broadcast('MyEvent')</button><br>

      Childrent scope count: {{count}} 
  </div>
</div>

example app.js:

angular.module('appExample', [])
.controller('EventCtrl', ['$scope', function($scope) {
  $scope.count = 0;
  $scope.$on('MyEvent', function() {
    $scope.count++;
  });
}]);

Here u can test code: http://jsfiddle.net/zp6v0rut/41/

Vasyl Gutnyk
  • 4,813
  • 2
  • 34
  • 37
0

According to the angularjs event docs the receiving end should be containing arguments with a structure like

@params

-- {Object} event being the event object containing info on the event

-- {Object} args that are passed by the callee (Note that this can only be one so better to send in a dictionary object always)

$scope.$on('fooEvent', function (event, args) { console.log(args) }); From your code

Also if you are trying to get a shared piece of information to be available accross different controllers there is an another way to achieve that and that is angular services.Since the services are singletons information can be stored and fetched across controllers.Simply create getter and setter functions in that service, expose these functions, make global variables in the service and use them to store the info

0

The Easiest way :

HTML

  <div ng-app="myApp" ng-controller="myCtrl"> 

        <button ng-click="sendData();"> Send Data </button>

    </div>

JavaScript

    <script>
        var app = angular.module('myApp', []);
        app.controller('myCtrl', function($scope, $rootScope) {
            function sendData($scope) {
                var arrayData = ['sam','rumona','cubby'];
                $rootScope.$emit('someEvent', arrayData);
            }

        });
        app.controller('yourCtrl', function($scope, $rootScope) {
            $rootScope.$on('someEvent', function(event, data) {
                console.log(data); 
            }); 
        });
    </script>
Sangwin Gawande
  • 7,658
  • 8
  • 48
  • 66