I am a newbie at angularjs and i am creating a web application to earn experience and practice. The problem i have is that $scope.$emit does not seem to be working, i am looking for ways to contact functions between controllers and so far i have found on the internet that $scope.emit and $scope.on seems to fit for this kind of task, if there is another way, i would like to know, anyways the code are written like this:
loginController.js
(function(angular)
{
var app = angular.module('Organizer');
// inject $scope to the controller in order to try $scope.$emit
app.controller('login', function($http, $scope)
{
// i define the scope like this so i can access inside other functions
var scope = this;
scope.processing = false;
scope.message = null;
scope.submit = function()
{
scope.processing = true;
// store data for post
var post = {
username: scope.username,
password: scope.password
};
// send post data
$http.post('api/default/login', post).
success(function(data, status)
{
// hide processing feedback and show the result
scope.processing = false;
scope.message = data.message;
}).
error(function(data, status)
{
scope.processing = false;
});
};
// Function i use to emit
this.closeDialog = function()
{
$scope.$emit('closeDialog');
};
});
})(angular);
siteController.js
(function(angular)
{
var app = angular.module('Organizer');
app.controller('site', function($mdDialog, $scope)
{
this.menu = ['test1', 'test2'];
this.dialog = function()
{
$mdDialog.show({
templateUrl: 'site/login',
});
};
// this does not seem to be working
$scope.$on('closeDialog', function(event)
{
console.log('close dialog');
});
});
})(angular);
Note: i am using angular material and you can see i am showing a dialog which is a login, the login has its controller (i wanted it to use the same site controller, but i don't know how) and this dialog has a button which calls the function closeDialog() in loginControler and should close the dialog, but for now for testing reasons i am just logging if it's calling the event
The $emit function propagate an event only to the scopes parents.
The $broadcast function propagate an event to the scopes childs.
So what you need depends on how the controllers are use it...
If you want an event to reach all the app you have to use the $rootScope:
$rootScope.$broadcast('myEvent');
Here you have the doc of the scope, include $emit and $broadcast
You could not emit or broadcast in dialog controller because dialog in angular material has isolated scope. Because of that, when you emit or broadcast an event, it does not go anywhere. The $emit and $broadcast only works when you have scope hierarchy. $emit propagate event up the hierarchy and $broadcast propagate event down the hierarchy.
Related
I am using socket.io to enable chat in my app and i am using a service SocketService to perform all the socket stuff. When a message came then i want to trigger a function of a controller from the service SocketService to make some changes in the UI.
So i want to know that how can i access the function of a controller from the service.
Sample Code:
.service('SocketService', function ($http,$rootScope,$q) {
this.connect = function(){
var socket = io();
socket.on('connect',function(){
// Call a function named 'someFunction' in controller 'ChatController'
});
}
});
This is the sample code for service.
Now the code for controller
.controller('ChatController',function('SocketService',$scope){
$scope.someFunction = function(){
// Some Code Here
}
});
You could achieve this by using angular events $broadcast or $emit.
In your case $broadcast would be helpful,
You need to broadcast your event in $rootscope that can be listen by all the child scopes which has $on with same event name.
CODE
.service('SocketService', function($http, $rootScope, $q) {
this.connect = function() {
var socket = io();
socket.on('connect', function() {
// Call a function named 'someFunction' in controller 'ChatController'
$rootScope.$broadcast('eventFired', {
data: 'something'
});
});
}
});
.controller('ChatController', function('SocketService', $scope) {
$scope.someFunction = function() {
// Some Code Here
}
$scope.$on('eventFired', function(event, data) {
$scope.someFunction();
})
});
Hope this could help you, Thanks.
I know this is an old question, but I have another option. I have a personal bias against $broadcast - it just doesn't feel very 'angularish', I prefer making explicit calls in my code.
So instead of broadcasting to the controller and triggering another digest cycle, I prefer to have the controller register itself to the service, as below. Just be careful not to introduce any circular dependencies if the controller makes use of the same service. This works best with the controllerAs syntax, so that the calling service does not need to care about $scope.
Yes, this is more code than $broadcast, but it does give the service total access to the entire controller - all of it's methods and properties.
.service('SocketService', function ($http,$rootScope,$q) {
var _this = this;
this.chatController = null;
this.registerCtrlr = function (ctrlr) {
_this.chatController = ctrlr;
};
this.unRegisterCtrlr = function () {
_this.chatController = null;
};
this.connect = function(){
var socket = io();
socket.on('connect',function(){
// Call chatController.someFunction if chatController exists
if (_this.chatController) {
_this.chatController.someFunction();
}
});
};
});
.controller('ChatController',['SocketService', '$scope', function(SocketService, $scope){
SocketService.registerCtrlr(this);
//-- make sure controller unregisters itself when destroyed - need $scope for this
$scope.$on('$destroy', function () {
SocketService.unRegisterCtrlr();
});
this.someFunction = function(){
// Some Code Here
}
}]);
I realize this post is old but I'd like to give my two cents after dealing with Angular JS for several years. I personally would reconsider this approach. Ideally with AngularJS you'd modify your controller/directive to facilitate transferring data to the view model and ultimately bind an HTML template to what I call "the user friendly" view model. This view model should simply reflect what you want the user to see and when in general. Using this method the moment connect event happens your view model which should be bound to the service's data will reflect changes to the data the moment the data arrives.
I have a single page app that opens a gallery. I want to bind document level keyup event (for keyboard gallery controlls) only when the gallery is open, ie. when route matches
.when('/reference/:galleryId/:imageId/', { templateUrl: '/partials/gallery.htm', controller: 'galleryController', controllerAs: 'reference' })
and I want to unbind it when I leave this route.
One thing that might be a problem is, I block reloading the view between images within the same gallery with this:
.run(['$route', '$rootScope', '$location', function ($route, $rootScope, $location) {
var original = $location.path;
$location.path = function (path, reload) {
if (reload === false) {
var lastRoute = $route.current;
var un = $rootScope.$on('$locationChangeSuccess', function () {
$route.current = lastRoute;
un();
});
}
return original.apply($location, [path]);
};
}])
Demo (Click on "Fotografie" to open the gallery)
http://tr.tomasreichmann.cz/
Angular wiz to the rescue?
Thank you for your time and effort
You could bind a keyup event to $document in your controller's constructor and then unbind the event when the controller's $scope is destroyed. For example:
.controller('galleryController', function ($scope, $document) {
var galleryCtrl = this;
function keyupHandler(keyEvent) {
console.log('keyup', keyEvent);
galleryCtrl.keyUp(keyEvent);
$scope.$apply(); // remove this line if not need
}
$document.on('keyup', keyupHandler);
$scope.$on('$destroy', function () {
$document.off('keyup', keyupHandler);
});
...
});
There will be nothing left behind when the view become inactive.
If you feel it isn't right to do this in the controller, you could move this into a custom directive and place it in a template of the view.
Finally I stuck with
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:og="http://ogp.me/ns#"
xmlns:fb="http://www.facebook.com/2008/fbml"
lang="cz"
ng-app="profileApp"
ng-keyup="$broadcast('my:keyup', $event)" >
Not sure if this is good practice, but it registers within my controller
$scope.$on('my:keyup', function(event, keyEvent) {
console.log('keyup', keyEvent);
galleryCtrl.keyUp(keyEvent);
});
And doesn't do anything when the current route is not active
I found this answer here: https://groups.google.com/forum/#!searchin/angular/document$20level$20events/angular/vXqVOKcwA7M/RK29o3oT9GAJ
There is another way to bind it globally which wasn't my goal, the original code in question did what I needed.
I am trying to call a method of second controller in first controller by using scope variable. This is a method in my first controller:
$scope.initRestId = function(){
var catapp = document.getElementById('SecondApp');
var catscope = angular.element(catapp).scope();
catscope.rest_id = $scope.user.username;
catscope.getMainCategories();
};
I am able to set the value of rest_id but I cannot call getMainCategories for some reason. The console shows this error:
TypeError: Object # has no method 'getMainCategories'
Is there a way to call the above method?
Edit:
I used the following approach to load two apps at the same time;
angular.bootstrap(document.getElementById('firstAppID'), ['firstApp']);
angular.bootstrap(document.getElementById('secondAppID'), ['secondApp']);
I could definitely use a service here, but I wanted to know if there are any other options to do the same!
The best approach for you to communicate between the two controllers is to use events.
Scope Documentation
In this check out $on, $broadcast and $emit.
In general use case the usage of angular.element(catapp).scope() was designed for use outside the angular controllers, like within jquery events.
Ideally in your usage you would write an event in controller 1 as:
$scope.$on("myEvent", function (event, args) {
$scope.rest_id = args.username;
$scope.getMainCategories();
});
And in the second controller you'd just do
$scope.initRestId = function(){
$scope.$broadcast("myEvent", {username: $scope.user.username });
};
Edit: Realised it was communication between two modules
Can you try including the firstApp module as a dependency to the secondApp where you declare the angular.module. That way you can communicate to the other app.
Here is good Demo in Fiddle how to use shared service in directive and other controllers through $scope.$on
HTML
<div ng-controller="ControllerZero">
<input ng-model="message" >
<button ng-click="handleClick(message);">BROADCAST</button>
</div>
<div ng-controller="ControllerOne">
<input ng-model="message" >
</div>
<div ng-controller="ControllerTwo">
<input ng-model="message" >
</div>
<my-component ng-model="message"></my-component>
JS
var myModule = angular.module('myModule', []);
myModule.factory('mySharedService', function($rootScope) {
var sharedService = {};
sharedService.message = '';
sharedService.prepForBroadcast = function(msg) {
this.message = msg;
this.broadcastItem();
};
sharedService.broadcastItem = function() {
$rootScope.$broadcast('handleBroadcast');
};
return sharedService;
});
By the same way we can use shared service in directive. We can implement controller section into directive and use $scope.$on
myModule.directive('myComponent', function(mySharedService) {
return {
restrict: 'E',
controller: function($scope, $attrs, mySharedService) {
$scope.$on('handleBroadcast', function() {
$scope.message = 'Directive: ' + mySharedService.message;
});
},
replace: true,
template: '<input>'
};
});
And here three our controllers where ControllerZero used as trigger to invoke prepForBroadcast
function ControllerZero($scope, sharedService) {
$scope.handleClick = function(msg) {
sharedService.prepForBroadcast(msg);
};
$scope.$on('handleBroadcast', function() {
$scope.message = sharedService.message;
});
}
function ControllerOne($scope, sharedService) {
$scope.$on('handleBroadcast', function() {
$scope.message = 'ONE: ' + sharedService.message;
});
}
function ControllerTwo($scope, sharedService) {
$scope.$on('handleBroadcast', function() {
$scope.message = 'TWO: ' + sharedService.message;
});
}
The ControllerOne and ControllerTwo listen message change by using $scope.$on handler.
Each controller has it's own scope(s) so that's causing your issue.
Having two controllers that want access to the same data is a classic sign that you want a service. The angular team recommends thin controllers that are just glue between views and services. And specifically- "services should hold shared state across controllers".
Happily, there's a nice 15-minute video describing exactly this (controller communication via services): video
One of the original author's of Angular, Misko Hevery, discusses this recommendation (of using services in this situation) in his talk entitled Angular Best Practices (skip to 28:08 for this topic, although I very highly recommended watching the whole talk).
You can use events, but they are designed just for communication between two parties that want to be decoupled. In the above video, Misko notes how they can make your app more fragile. "Most of the time injecting services and doing direct communication is preferred and more robust". (Check out the above link starting at 53:37 to hear him talk about this)
i have been able to get controller to use the $on listener
with $scope.$on.
but i don't see any documentation on how to get services to listen for events.
I tried $rootScope.$on, but that only allows one listener. i want listeners in multiple services regardless of whether their parent controllers are in scope or not.
after experimenting a fair bit it turns out that getting events to the service can be done with minimal code.
sample service code follows in case anyone else runs into this.
The sample saves and restores the service model to local storage when it gets the respective broadcasts
app.factory('userService', ['$rootScope', function ($rootScope) {
var service = {
model: {
name: '',
email: ''
},
SaveState: function () {
sessionStorage.userService = angular.toJson(service.model);
},
RestoreState: function () {
service.model = angular.fromJson(sessionStorage.userService);
}
}
$rootScope.$on("savestate", service.SaveState);
$rootScope.$on("restorestate", service.RestoreState);
return service;
}]);
Since $on is a scope method, you could create a scope in your service, then listen for events on it:
app.factory('myService', function($rootScope) {
var scope = $rootScope.$new(); // or $new(true) if you want an isolate scope
scope.$on('testEvent', function() {
console.log('event received');
})
return {}
});
function MyCtrl($scope, myService, $rootScope) {
$rootScope.$broadcast('testEvent');
}
fiddle
However, I would not recommend this approach, since scopes are not normally associated with services.
PRELIMINARIES
I am developing a web app using angularjs. At some point, my main controller connects to a web service which sends data continuously. To capture and process the stream I am using (http://ajaxpatterns.org/HTTP_Streaming). Everything works like a charm. I would like to share these streaming data with another controller that will process and display them via a jquery chart library (not yet decided which one I gonna use but it is out of the scope of this question). To share these data I have followed this jsfiddle (http://jsfiddle.net/eshepelyuk/vhKfq/).
Please find below some relevant parts of my code.
Module, routes and service definitions:
var platform = angular.module('platform', ['ui']);
platform.config(['$routeProvider',function($routeProvider){
$routeProvider.
when('/home',{templateUrl:'partials/home.html',controller:PlatformCtrl}).
when('/visu/:idVisu', {templateUrl: 'partials/visuTimeSeries.html',controller:VisuCtrl}).
otherwise({redirectTo:'/home',templateUrl:'partials/home.html'})
}]);
platform.factory('mySharedService', function($rootScope) {
return {
broadcast: function(msg) {
$rootScope.$broadcast('handleBroadcast', msg);
}
};
});
PlatformCtrl definition:
function PlatformCtrl($scope,$http,$q,$routeParams, sharedService) {
...
$scope.listDataVisu ={};
...
$scope.listXhrReq[idVisu] = createXMLHttpRequest();
$scope.listXhrReq[idVisu].open("get", urlConnect, true);
$scope.listXhrReq[idVisu].onreadystatechange = function() {
$scope.$apply(function () {
var serverResponse = $scope.listXhrReq[idVisu].responseText;
$scope.listDataVisu[idVisu] = serverResponse.split("\n");
sharedService.broadcast($scope.listDataVisu);
});
};
$scope.listXhrReq[idVisu].send(null);
var w = window.open("#/visu/"+idVisu);
$scope.$on('handleBroadcast', function(){
console.log("handleBroadcast (platform)");
});
}
VisuCtrl definition:
function VisuCtrl($scope,$routeParams,sharedService) {
$scope.idVisu = $routeParams.idVisu;
$scope.data = [];
/* ***************************************
* LISTENER FOR THE HANDLEBROADCAST EVENT
*****************************************/
$scope.$on('handleBroadcast', function(event,data){
console.log("handleBroadcast (visu)");
$scope.data = data[$scope.idVisu];
});
}
Injection:
PlatformCtrl.$inject = ['$scope','$http','$q','$routeParams','mySharedService'];
VisuCtrl.$inject = ['$scope','$routeParams','mySharedService'];
PROBLEM DEFINITION
When running this code, it looks like only the PlatformCtrl controller listens for the handleBroadcast event. Indeed, having a look to the console all what is displayed is only handleBroadcast (platform) every time new data arrive. I am very surprised because I have read in the official documentation that the $broadcast function
dispatches an event name downwards to all child scopes (and their
children) notifying the registered ng.$rootScope.Scope#$on listeners.
Since all the scopes in a given app inherits from $rootScope, I do not get why the $on function in VisuCtrl is not launched every time new data are broadcasted.
What I think is that when you open a new browser window you are launching a new AngularJS instance. This way it's not possible that the two controllers are able to communicate via a service.
If you have problems with scopes communicating, you can inject the $rootScope and see whether all the scopes that should communicate are actually instanciated.
function VisuCtrl($scope, $routeParams, sharedService, $rootscope) {
console.log($rootScope);
}
Your request flow comes out of the angular, therefore it would not be recognized until the next $digest phase (see how angular handles two-way binding via dirty matching). To get in to the angular world you need to use $apply:
$scope.listXhrReq[idVisu].onreadystatechange = function() {
$scope.$apply(function () {
var serverResponse = $scope.listXhrReq[idVisu].responseText;
$scope.listDataVisu[idVisu] = serverResponse.split("\n");
sharedService.broadcast($scope.listDataVisu);
});
};
Could it be that your VisuCtrl hasn't been initialized yet, since you are using custom routing?
Is it still the same, when you navigate to /visu/:idVisu?