$broadcast event not received by controller
Asked Answered
E

4

7

So I have an angularjs app with several partial views and one controller per partial view. I also have a service which will $broadcast events from the $rootScope:

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

Currently there is only one controller listening for events and I noticed that if I am not on the view that is associated to that one controller (via routes), then the event is never received in that controller and thus never loads the view associated to it. How can I ensure that the controller receives the event broadcast regardless of which view is active? I tried using $scope.$on('MyEvent')... and $rootScope.$on('MyEvent')... in my controller; neither seem to work.

Erigeron answered 28/7, 2014 at 15:38 Comment(3)
Can you provide a fiddle or plunker?Shaped
Obviously when you aren't on the view with controller it doesn't get the event because both controller and listener doesn't exist, but if you already have service you can inject it into controller and make use of two-way data binding i.e. when controller is initialised and service injected check value on service and do somethingLashawn
AFAIK, it is by design of ngRoute, that a controller will be created at the time a user enter an associated view. And the controller will be destroyed when the user navigate away.Kazimir
F
9

This is the expected behavior, as your partial views have individual controllers associated with them. The controller would be in scope only if the view is rendered.The controller comes in to scope only when your ng-controller directive is parsed in your partial view.So expect your controllers to be in scope only after loading the view associated with it.

In your case, when you broadcasted the event myEvent using $rootScope.$broadcast('MyEvent', 'message'); the controller you wanted to receive this event was not in scope.

to see this clearly put a breakpoint on your broadcast step and pause the execution to check the value of angular.element('[ng-controller=urControllerName]').scope() in console and you can see that it would be undefined meaning that the controller is not yet in scope.

Finedrawn answered 19/8, 2015 at 9:41 Comment(0)
J
3

It would help if you included a bit more of your code because there's a right way and a wrong way to do an $on(), and your shorthand would be the wrong way. I saw somebody once who thought this returned a promise and was trying to use .then() on it, and that won't work. It takes a callback:

$rootScope.$on('MyEvent', function(myParam) {
    alert(myParam);
});

Separately, if you're doing pub/sub type work pay attention to the different between $emit and $broadcast. It's important for efficiency reasons, and if you don't need to actually use $broadcast you might want to reconsider it. $broadcast will send the message on that scope plus all child scopes. That means Angular needs to iterate recursively through every scope ever made to look for listeners for the event. If you use a lot of directives (many of those have scopy) and ngRepeats, you could have thousands of these!

Typically you use $broadcast when you want to put something out within a specific scope and its children deliberately, like when you're posting events between siblings in a data table row or something like that. (Hover effects, data updates to the row, that kind of thing...)

What you can do instead is use $emit. This works the same way (you still use $on to listen for it) but this time it goes up the scope chain. That means if you start it on $rootScope it will just hit one quick list of listeners and stop there - it's extremely fast and efficient if you're registering your $on() events at the same level. In that case you would use $rootScope.$on() instead of $scope.$on()... but the efficiency gain is worth it!

Jury answered 28/7, 2014 at 15:54 Comment(9)
This is very useful, but I don't think it is an answer to the question. The pub/sub in controllers will work only on an active view.Kazimir
Yes sorry, the code snippet in the broadcast service is actually: ` $rootScope.$broadcast('MyEvent', msgData);` It works fine if the view is active, but otherwise does nothing. I will look into using $emit as a more efficient alternative, however I don't believe it will fix my issue.Erigeron
Gah, you're right, I missed that detail. :( There is no way around that - your comment is correct that ngRoute does this by design. He would have to create a service (or a controller that WAS working all the time) and listen for this event there, then make it available to controllers when desired.Jury
What about calling the controller directly from the service? Not the best solution because it adds explicit coupling between the two, but is that possible?Erigeron
Sure... you can do anything you want, although that doesn't make it a good idea. You'd just pass a reference to the controller over to the service at some point. The drawback is: how would you know the controller existed? It just snowballs... It might be better to try to engineer after a different design pattern that doesn't require you to put code into this Controller in the first place. If it must be called when the Controller itself doesn't exist... it probably belongs elsewhere anyway!Jury
Ok, let me present a more specific scenario and maybe you can weigh in with your expertise. Lets say the app consists of several partial views and controllers. The user can be on any of the views, but the app needs to listen all the time for push events from the server. Once a push event is received, some code needs to run and ultimately render one of several possible views. Can you propose a sound design solution to this scenario. Thanks!Erigeron
Yes, I do this now in several apps I'm working on. The usual method is a service - it's perfect for this. The service makes the data connection (WebSockets in my case but it could be anything) and manages global data objects that represent the state of what the messages have sent so far. It exposes those as variables in the service so anybody that injects it can see them too. Let's pretend it's an IM buddy list. The service can call the router to trigger state views, and the controllers can just render the data and provide click handlers. Simple, functional, and it works great.Jury
So the service is actually calling routes to invoke a controller, instead of broadcasting an event for controller?Erigeron
You can do both. The service might get a message called AddBuddy, and add that buddy to its internal list of buddies. Controllers can take a reference to this into their $scopes and render the buddies with an ngRepeat. OR... Maybe the next message is InstantMessage. The service adds the message to its IM list but also triggers a state change to open the window to show this IM. That controller then works like the one above, but didn't have to be "active" at the time the message was received. I don't know what app you're building, but that's a common example.Jury
V
1

I had the same issue because I was using a controller declared in a view included via ng-include to be simple something like this :

<div ng-include="'pageToInclude.view.html'" ></div>

pageToInclude contains :

<div class="header" ng-controller="pageCtrl as pageCtrl">.......</div>

My problem was that the event is brodcasted before this page rendering so it didn't catch this event so what I did was simply declare the controller in the ng-include's div like this :

<div ng-include="'pageToInclude.view.html'" ng-controller="pageCtrl as pageCtrl"></div>

and of course delete the ng-controller from inside the page.

Hope my answer helps someone out there, cheers.

Vernverna answered 10/1, 2017 at 9:58 Comment(0)
M
0

use $apply method of $scope.

/* Service.js */
$rootScope.$broadcast('MyEvent', 'message');


/* Controller.js */
$rootScope.$on("MyEvent", function(event, args){

  // works!
  alert("Event!");

  // not works!
  $scope.message = args[0];

  // works!
  $scope.$apply(function(){
    $scope.message = args[0];
  });
});
Manpower answered 23/10, 2014 at 5:17 Comment(1)
$rootScope is supposed to execute a digest cycle or not ? I would like to be sure of this with a jsfiddle.. in a while im going to try itCrookback

© 2022 - 2024 — McMap. All rights reserved.