ChatGPT解决这个技术问题 Extra ChatGPT

Working with $scope.$emit and $scope.$on

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?

Just for future readers: don't use $rootScope for broadcast/emit when it can be avoided.

z
zbynour

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:

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


Is there a way to fire an event from a service to a controller?
Yes theoretically you could inject $rootScope into your service and broadcast the event from the service.
@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.
If you are using a iframe this article will be helpful charemza.name/blog/posts/angularjs/iframe/…
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?
c
clopez

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

This would then basically serve as a central event bus correct?
In a sense yes, the benefit being that you avoid event propagation.
@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!
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.
According to @FilipSobczak comment, you can avoid this unwanted behaviour by unbinding handler on $destroy event with the following code jsfiddle.net/ndqexjsg/1
S
SoEzPz

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.

Hope this helps...


how to decide which one is 3,2 and 1?
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.
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.
According to angular docs on $broadcast 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; }
V
Ved

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.


k
kyasar

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.


s
sg7

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

        }

    }])

P
Peter Mortensen

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

I think this is bad practice as your rootScope will be cluttered. See stackoverflow.com/questions/24830679/…
P
Prashant_M
<!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>

V
Vasyl Gutnyk

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/


s
sg7

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/


W
Wajih Siddiqui

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


S
Sangwin Gawande

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>