289

I have a footerController and codeScannerController with different views.

angular.module('myApp').controller('footerController', ["$scope", function($scope) {}]);

angular.module('myApp').controller('codeScannerController', ["$scope", function($scope) {
console.log("start");
$scope.startScanner = function(){...

When I click on a <li> in footer.html I should get this event in codeScannerController.

<li class="button" ng-click="startScanner()">3</li>

I think it can be realised with $on and $broadcast, but I don't know how and can't find examples anywhere.

Gargaroz
  • 313
  • 9
  • 28
Alice Polansky
  • 3,231
  • 3
  • 18
  • 25

4 Answers4

647

If you want to $broadcast use the $rootScope:

$scope.startScanner = function() {

    $rootScope.$broadcast('scanner-started');
}

And then to receive, use the $scope of your controller:

$scope.$on('scanner-started', function(event, args) {

    // do what you want to do
});

If you want you can pass arguments when you $broadcast:

$rootScope.$broadcast('scanner-started', { any: {} });

And then receive them:

$scope.$on('scanner-started', function(event, args) {

    var anyThing = args.any;
    // do what you want to do
});

Documentation for this inside the Scope docs.

Davin Tryon
  • 66,517
  • 15
  • 143
  • 132
  • 2
    You can name the event anything you like. – Davin Tryon Oct 18 '13 at 10:11
  • 5
    Make sure you are $scope.$apply(); your changes! – Ismail Aug 09 '14 at 16:19
  • 4
    @Ismail Why... and where? – Jaans Aug 25 '14 at 09:06
  • @Jaans Sometimes it didn't update my changed. I can't give an specific answer, I am new to angularjs. But I found out that it fixed my problem. – Ismail Aug 25 '14 at 21:37
  • @Ismail OK thanks. I'm new too :-) I have since found out that it is best to wrap a `$broadcast()` call with a `$timeout()` call to "ensure that the broadcast is performed on the next available digest cycle" - what ever that really means. Something to do with the fact that Angular how angular does change/data tracking. – Jaans Aug 26 '14 at 04:08
  • 7
    Are there any recommended practices for storing these strings somewhere rather than hardcoding the broadcast message? – rperryng Sep 13 '14 at 05:52
  • @Rperryng that is a slippery slope. The reason you hard code these as literals is that it gives the developer team a visual record of the event which is occurring. Same reason we give variables logical names. – kingPuppy Mar 06 '15 at 21:35
  • `$broadcast` or `$emit` could be used for this. `$rootScope.$emit()` may be a better option if only want to fire if on the $rootScope and want to prevent unnecessary traversing of all children in the scope hierarchy. – th3uiguy Jun 24 '15 at 09:57
  • 1
    @th3uiguy Not really. `$emit` travels *up* the scope hierarchy, while `$broadcast` travels *down* the scope hierarchy. Since `$rootScope` is the top of the hierarchy, `$emit` would not work in this case. – Davin Tryon Jun 24 '15 at 09:59
  • 8
    @Ismail `$scope.$apply()` is only necessary when changing the model outside of the angular framework (like in a setTimeout, a dialog callback, or an ajax callback), in other words `$apply()` is already triggered after all code in `.$on()` is finished. – th3uiguy Jun 24 '15 at 10:05
  • @DavinTryon see my jsfiddle of how `$emit()` could work: https://jsfiddle.net/th3uiguy/kjgj7Ldz/ – th3uiguy Jun 24 '15 at 10:12
  • is there any way that i can send data to this broadcast function from native side , lets say i have declared android broadcast , is there anyway that i can communicate with this ??? – Mr.G Nov 10 '15 at 09:45
  • Are you sure that `$scope` can be here? I had to use `$rootScope.$on` to it work. – Honza Feb 28 '16 at 13:38
103

First, a short description of $on(), $broadcast() and $emit():

  • .$on(name, listener) - Listens for a specific event by a given name
  • .$broadcast(name, args) - Broadcast an event down through the $scope of all children
  • .$emit(name, args) - Emit an event up the $scope hierarchy to all parents, including the $rootScope

Based on the following HTML (see full example here):

<div ng-controller="Controller1">
    <button ng-click="broadcast()">Broadcast 1</button>
    <button ng-click="emit()">Emit 1</button>
</div>

<div ng-controller="Controller2">
    <button ng-click="broadcast()">Broadcast 2</button>
    <button ng-click="emit()">Emit 2</button>
    <div ng-controller="Controller3">
        <button ng-click="broadcast()">Broadcast 3</button>
        <button ng-click="emit()">Emit 3</button>
        <br>
        <button ng-click="broadcastRoot()">Broadcast Root</button>
        <button ng-click="emitRoot()">Emit Root</button>
    </div>
</div>

The fired events will traverse the $scopes as follows:

  • Broadcast 1 - Will only be seen by Controller 1 $scope
  • Emit 1 - Will be seen by Controller 1 $scope then $rootScope
  • Broadcast 2 - Will be seen by Controller 2 $scope then Controller 3 $scope
  • Emit 2 - Will be seen by Controller 2 $scope then $rootScope
  • Broadcast 3 - Will only be seen by Controller 3 $scope
  • Emit 3 - Will be seen by Controller 3 $scope, Controller 2 $scope then $rootScope
  • Broadcast Root - Will be seen by $rootScope and $scope of all the Controllers (1, 2 then 3)
  • Emit Root - Will only be seen by $rootScope

JavaScript to trigger events (again, you can see a working example here):

app.controller('Controller1', ['$scope', '$rootScope', function($scope, $rootScope){
    $scope.broadcastAndEmit = function(){
        // This will be seen by Controller 1 $scope and all children $scopes 
        $scope.$broadcast('eventX', {data: '$scope.broadcast'});

        // Because this event is fired as an emit (goes up) on the $rootScope,
        // only the $rootScope will see it
        $rootScope.$emit('eventX', {data: '$rootScope.emit'});
    };
    $scope.emit = function(){
        // Controller 1 $scope, and all parent $scopes (including $rootScope) 
        // will see this event
        $scope.$emit('eventX', {data: '$scope.emit'});
    };

    $scope.$on('eventX', function(ev, args){
        console.log('eventX found on Controller1 $scope');
    });
    $rootScope.$on('eventX', function(ev, args){
        console.log('eventX found on $rootScope');
    });
}]);
th3uiguy
  • 1,879
  • 1
  • 12
  • 8
  • how can I imagine the hierarchy of my app with the example that you have given. How can a controller be a parent or child.?? What I am trying to say is that I have series of state eg. LoginCtrl ->homeCrl -> notificationCtrl and so on. – HIRA THAKUR Dec 23 '15 at 11:00
27

One thing you should know is $ prefix refers to an Angular Method, $$ prefixes refers to angular methods that you should avoid using.

below is an example template and its controllers, we'll explore how $broadcast/$on can help us achieve what we want.

<div ng-controller="FirstCtrl">
    <input ng-model="name"/> 
    <button ng-click="register()">Register </button>
</div>

<div ng-controller="SecondCtrl">
    Registered Name: <input ng-model="name"/> 
</div>

The controllers are

app.controller('FirstCtrl', function($scope){
    $scope.register = function(){

    }
});

app.controller('SecondCtrl', function($scope){

});

My question to you is how do you pass the name to the second controller when a user clicks register? You may come up with multiple solutions but the one we're going to use is using $broadcast and $on.

$broadcast vs $emit

Which should we use? $broadcast will channel down to all the children dom elements and $emit will channel the opposite direction to all the ancestor dom elements.

The best way to avoid deciding between $emit or $broadcast is to channel from the $rootScope and use $broadcast to all its children. Which makes our case much easier since our dom elements are siblings.

Adding $rootScope and lets $broadcast

app.controller('FirstCtrl', function($rootScope, $scope){
    $scope.register = function(){
        $rootScope.$broadcast('BOOM!', $scope.name)
    }
});

Note we added $rootScope and now we're using $broadcast(broadcastName, arguments). For broadcastName, we want to give it a unique name so we can catch that name in our secondCtrl. I've chosen BOOM! just for fun. The second arguments 'arguments' allows us to pass values to the listeners.

Receiving our broadcast

In our second controller, we need to set up code to listen to our broadcast

app.controller('SecondCtrl', function($scope){
  $scope.$on('BOOM!', function(events, args){
    console.log(args);
    $scope.name = args; //now we've registered!
  })
});

It's really that simple. Live Example

Other ways to achieve similar results

Try to avoid using this suite of methods as it is neither efficient nor easy to maintain but it's a simple way to fix issues you might have.

You can usually do the same thing by using a service or by simplifying your controllers. We won't discuss this in detail but I thought I'd just mention it for completeness.

Lastly, keep in mind a really useful broadcast to listen to is '$destroy' again you can see the $ means it's a method or object created by the vendor codes. Anyways $destroy is broadcasted when a controller gets destroyed, you may want to listen to this to know when your controller is removed.

yangli-io
  • 16,760
  • 8
  • 37
  • 47
  • 2
    As a warning, try not to use too many broadcasts/emits in your app. They can be extremely hard to manage especially in a big app since tracing the roots of these events is a very hard task. – yangli-io Apr 29 '15 at 06:27
1
//Your broadcast in service

(function () { 
    angular.module('appModule').factory('AppService', function ($rootScope, $timeout) {

    function refreshData() {  
        $timeout(function() {         
            $rootScope.$broadcast('refreshData');
        }, 0, true);      
    }

    return {           
        RefreshData: refreshData
    };
}); }());

//Controller Implementation
 (function () {
    angular.module('appModule').controller('AppController', function ($rootScope, $scope, $timeout, AppService) {            

       //Removes Listeners before adding them 
       //This line will solve the problem for multiple broadcast call                             
       $scope.$$listeners['refreshData'] = [];

       $scope.$on('refreshData', function() {                                                    
          $scope.showData();             
       });

       $scope.onSaveDataComplete = function() { 
         AppService.RefreshData();
       };
    }); }());
Sandy
  • 31
  • 1
  • 5