How do I broadcast a message between controllers?
Here is what I have tried:
function Ctrl1($scope) {
$scope.$broadcast('Update');
}
Ctrl1.$inject = ['$scope'];
function Ctrl2($scope) {
$scope.updated = false;
$scope.$on('Update', function () {
$scope.updated = true;
});
}
Ctrl2.$inject = ['$scope'];
To see it running: view the Plnkr.
Instead of using $broadcast() a shared service and $watch() might be a better alternative.
var myApp = angular.module('myApp', []);
myApp.factory("MyService", function () {
return {
updated: false
};
});
function Ctrl1($scope, MyService, $timeout) {
$timeout(function () { //Some work occurs and sets updated to true
MyService.updated = true;
}, 1000)
}
Ctrl1.$inject = ['$scope', "MyService", "$timeout"];
function Ctrl2($scope, MyService) {
$scope.$watch(function () {
return MyService.updated;
}, function (oldValue, newValue) {
$scope.updated = MyService.updated;
});
}
Ctrl2.$inject = ['$scope', "MyService"];
Updated Plnkr
It depends on the scopes hierarchy and therefore on where you bootstrap your Ctrl1 and Ctrl2 in your dom.
Say Ctrl1 is the parent of Ctrl2. $broadcast will transmit the event to child scopes: Ctrl2 in this case will notice it (use $on).
If you need to transmit an event from Ctrl2 to Ctrl1, use $emit that transmit the event to parent scopes.
Related
I already have seem other topics with this kind of issue, but no one could help me... So here is my issue:
I have a navbar with a button for search, this buttons makes and get request from a webservice and returns a json object which must be apply to fill an table list. The problem is, my button and my table are in separated controllers, and it does work like I expected.
var app = angular.module('clientRest', []).controller('lista', ['$scope', 'loadLista', function($scope, loadLista) {
$scope.contatos = loadLista.getContatos();
}]).controller('pesquisa', ['$scope', '$http', 'loadLista', function($scope, $http, loadLista) {
$scope.listar = function() {
$http.get("http://localhost/wsRest/index.php/contato").success(function(response) {
loadLista.setContatos(response);
});
};
}]).service('loadLista', function() {
var contatos = [];
return {
getContatos: function() {
return contatos;
},
setContatos: function(c) {
contatos = c;
}
};
});
My code...
When I call listar() from pesquisa controller I need to send received data to $scope.contatos from lista controller to make my ng-repeat work, everything with a single click.
How can I do it?
Thanks everyone
Better to use a service to share data between two controllers / modules as this might be the best approach. You can refer the code segment given below to understand the concept.
angular.module('app.A', [])
.service('ServiceA', function() {
this.getValue = function() {
return this.myValue;
};
this.setValue = function(newValue) {
this.myValue = newValue;
}
});
angular.module('app.B', ['app.A'])
.service('ServiceB', function(ServiceA) {
this.getValue = function() {
return ServiceA.getValue();
};
this.setValue = function() {
ServiceA.setValue('New value');
}
});
In order to trigger the data receipt event, you may use
Broadcast / emit messages - with #broadcast / #emit
An angular promise with a call back
Controller initiation function to reload the previously read information from a service
.controller('MyController', function($scope, ServiceA) {
$scope.init = function() {
$scope.myValue = ServiceA.getValue();
};
// Call the function to initialize during Controller instantiation
$scope.init();
});
Use $rootScope.$emit to emit a change event when setting the variable and use $on to get the value in the lista controller. I used customListAr here just to demostrate a button click. Does this help?
var app = angular.module('clientRest', [])
.controller('lista', ['$scope', 'loadLista', '$rootScope',
function($scope, loadLista, $rootScope) {
console.log(loadLista);
$scope.contatos = loadLista.getContatos();
$rootScope.$on('change', function() {
$scope.contatos = loadLista.getContatos();
});
}
])
.controller('pesquisa', ['$scope', '$http', 'loadLista',
function($scope, $http, loadLista) {
$scope.listar = function() {
$http.get("http://localhost/wsRest/index.php/contato").success(function(response) {
loadLista.setContatos(response);
});
};
$scope.customListAr = function() {
loadLista.setContatos(["item 1" , "item 2", "item 3"]);
}
}
])
.service('loadLista', ['$rootScope',
function($rootScope) {
var contatos = [];
return {
getContatos: function() {
return contatos;
},
setContatos: function(c) {
contatos = c;
$rootScope.$emit('change');
}
};
}
]);
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="clientRest">
<div ng-controller="lista">
<ul>
<li ng-repeat="a in contatos">{{a}}</li>
</ul>
</div>
<div ng-controller="pesquisa">
<button ng-click="customListAr()">Click Me</button>
</div>
</div>
Your problem is that when you do $scope.contatos = loadLista.getContatos(); you are setting a static value, and angular is unable to effectively create a watcher for that object because your setContatos method is creating a new object each time. To get around this, have the controller's scope hold a reference to the parent object and then it will automatically have a watcher on that object.
var app = angular.module('clientRest', [])
.controller('lista', ['$scope', 'loadLista', function($scope, loadLista) {
$scope.contatos = loadLista.contatos;
}])
.controller('pesquisa', ['$scope', '$http', 'loadLista', function($scope, $http, loadLista) {
$scope.listar = function() {
$http.get("http://localhost/wsRest/index.php/contato"
).success(function (response) {
loadLista.contatos.data = response;
});
};
}])
.service('loadLista', function() {
var lista = {
contatos: {},
};
return lista;
});
// view:
<ul>
<li ng-repeat="contato in contatos.data">
{{ contato }}
</li>
</ul>
I'm new to angular and have to fix bugs of my collegue. I found this factory with functions. How can I put a reset_All-function that calls the reset-function of all modules ?
.factory('MyCollection', function() {
return {
resetAll: function(projectId) {
// call the reset-function of SomeController
}
}
})
.controller('SomeController', function($scope, a) {
$scope.reset = function() {
...........
}
}
If you want to prevent tight coupling of the different modules, you can broadcast an event and catch it in the respective controllers:
.factory('MyCollection', ['$rootScope', function($rootScope) {
return {
resetAll: function(projectId) {
$rootScope.$broadcast('reset');
}
};
}]);
.controller('SomeController', ['$scope', 'a', function($scope, a) {
$scope.reset = function() {
// do something here
};
$scope.$on('reset', function() {
$scope.reset();
});
}]);
To learn about Angular's events have a look at Understanding Angular’s $scope and $rootScope event system $emit, $broadcast and $on
You need to depend on MyCollection, so you can inject it:
.controller('SomeController', [ '$scope', 'a', 'MyCollection',
function ($scope, a, MyCollection) {
$scope.reset = function(id) {
MyCollection.resetAll(id)
}
}]);
Here is the documentation to read.
I've created a service to pass a boolean flag between controllers.
Service looks like this :
angular.module('MyApp')
.service('sharedProperties', function () {
var flag = false;
return {
getProperty: function () {
return flag;
},
setProperty: function(value) {
flag = value;
}
};
});
First controller where flag is set is this
angular.module('MyApp')
.controller('test1', function($scope, $location, $http, $window, sharedProperties) {
$scope.flag1 = function () {
if ($location.path() == '/') {
sharedProperties.setProperty(false);
return false;
}else {
sharedProperties.setProperty(true);
return true;
};
};
});
And controller receiving argument is this
angular.module('MyApp')
.controller('test2', function($scope, $location, $http, $window, sharedProperties) {
$scope.flag2 = sharedProperties.getProperty();
});
flag1 in test1 takes a correct value, but test2 is always false. If I initialize flag at service as true - it's always true at test2. If I don't initialize it - it's undefined at test2.
This behaviour is totally expected and is not linked to angularjs in particular, it works like this in vanilla javascript. The issue you encounter is due to the fact that javascript primitives are passed by value and not by reference. You can read more about it here: Is JavaScript a pass-by-reference or pass-by-value language?
How to fix your code? Use an object which will be passed by reference.
Could you try this:
angular.module('MyApp')
.service('sharedProperties', function () {
var flag = {
value: false
};
return {
getProperty: function () {
return flag;
},
setProperty: function(value) {
flag.value = value;
}
};
});
angular.module('MyApp')
.controller('test1', function($scope, $location, $http, $window, sharedProperties) {
$scope.flag1 = function () {
if ($location.path() == '/') {
sharedProperties.setProperty(false);
return false;
}else {
sharedProperties.setProperty(true);
return true;
};
};
});
angular.module('MyApp')
.controller('test2', function($scope, $location, $http, $window, sharedProperties) {
$scope.flag2 = sharedProperties.getProperty();
// use flag2.value in your markup
});
The reason being is I believe that Angular initializes a service with new when added to a controller.
So when manipulating in test1, its an instance of the service in that controller which is not shared in the other controller. I could be wrong though and would recommend firstly using this.flag instead of var flag. In addition, your test2 controller makes a call on angular load, rendering false but it doesn't watch for changes.
Created a JSBIN to solve it for you: http://jsbin.com/xoqup/1/edit
I want to use a dependency in listener but the websocket was undefined
$rootScope.$on('websocket.connected', function() {
$websocket.request(.....).then();
});
and a want to call a service method (who depend on asyncron method) when it ready
app.controller('MyCtrl', function(myServ, $log) {
myServ.getInfos();
});
thank you.
Code in jsfiddle http://jsfiddle.net/8DHfY/3/ or here
var app = angular.module('myApp', ['myServ'])
.config(['$websocketProvider', function ($websocketProvider) {
$websocketProvider.setHost('ws://echo.websocket.org/');
}])
.controller('MyCtrl', function(myServ, $log) {
$log.log('I want to call myServ.getInfos() from a controler');
});
angular.module('myServ', ['websocket']).service('myServ', ['$log', '$rootScope', '$websocket', function($log, $rootScope, $websocket) {
$log.error('websocket is %o ... ???', $websocket); // return undefined
$rootScope.$on('websocket.connected', function() {
$log.error('websocket is still %o', $websocket); // return undefined
});
return {
getInfos: function() {
$websocket.request(JSON.stringify({'key': 'value'}));
}
};
}]);
angular.module('websocket', []).provider('$websocket', [function() {
var _this = this;
_this.host = '';
_this.connection = null;
_this.setHost = function(host) {
this.host = host;
return this;
};
_this.request = function(request) {
//request method for websocket
};
this.$get = ['$log', '$rootScope', function($log, $rootScope) {
_this.connection = new WebSocket(this.host);
_this.connection.onopen = function(){
$log.log('Websocket connected to %s', _this.host);
$rootScope.$emit('websocket.connected');
};
}];
}]);
Providers invoke the $get function upon injection and use the singleton of whatever is returned from that function.
This means since you do not return anything from the $get function, it uses undefined.
Here's an updated fiddle: http://jsfiddle.net/8DHfY/4/
this.$get = ['$log', '$rootScope', function($log, $rootScope) {
_this.connection = new WebSocket(this.host);
_this.connection.onopen = function(){
$log.log('Websocket connected to %s', _this.host);
$rootScope.$emit('websocket.connected');
};
return _this;
}];
I'd like to understand how can i design single ajax-method for several controllers, which also can influence on user interface ('loading' animation, for example).
Idea is (without promises):
var myApp = angular.module('myApp', []);
myApp.controller('myCtrl',
function myCtrl($scope, myFactory){
$scope.loading = false;
$scope.someStuff = myFactory.getStuff(params);
});
myApp.factory('myFactory', function(myService){
return{
getStuff: function(params){
return myService.ajax(params);
}
}
});
myApp.service('myService', function($http) {
this.ajax = function(params){
// switch $scope.loading = true;
// make request
// return $http result
// switch $scope.loading = false;
};
});
As i know, i need use $scope for UI changes and ajax-method should be taken out to custom service. Services in Angularjs does not work with $scope and i have no idea how can i solve this problem.
I think, there must be a service with chain of promises.
How can it be designed?
Upd: I hope, with the time the documentation will be more complete and clear. But community of angular users is already great. Thanks.
In my project I have defined a service called appState which has (among other) methods: showGlobalSpinner and hideGlobalSpinner which modify a variable on the $rootScope.
Basically:
(…)
.factory('appState', ['$rootScope', function ($rootScope) {
return {
showGlobalSpinner: function () {
++$rootScope.loadingInProgress;
},
hideGlobalSpinner: function () {
if ($rootScope.loadingInProgress > 0) {
--$rootScope.loadingInProgress
}
}
};
}]);
What I do next is I show spinners wherever I need them using ng-show directive:
<div class="spinner" ng-show="loadingInProgress"></div>
Before each AJAX I just call AppState.showGlobalSpinner() and after success/error I call AppState.hideGlobalSpinner()
You could add this method to any of your controllers:
.controller('HeaderCtrl',['$scope','httpRequestTracker', function($scope,httpRequestTracker) {
$scope.hasPendingRequests = function () {
return httpRequestTracker.hasPendingRequests();
};
}]);
angular.module('services.httpRequestTracker', []);
angular.module('services.httpRequestTracker').factory('httpRequestTracker', ['$http', function($http){
var httpRequestTracker = {};
httpRequestTracker.hasPendingRequests = function() {
return $http.pendingRequests.length > 0;
};
return httpRequestTracker;
}]);
You can try a more generic approach by using an HTTP interceptor, some events and a directive:
Javascript
app.factory('myHttpInterceptor', function($q, $rootScope) {
return function(promise) {
$rootScope.$broadcast('RequestStarted');
var success = function(response) {
$rootScope.$broadcast('RequestFinished', true);
};
var error = function(response) {
$rootScope.$broadcast('RequestFinished', false);
return $q.reject(response);
};
promise.then(success, error);
return promise;
}
})
.config(function($httpProvider) {
$httpProvider.responseInterceptors.push('myHttpInterceptor');
})
.directive('loading', function() {
return {
restrict: 'E',
transclude: true,
template: '<div ng-show="visible" ng-transclude></div>',
controller: function($scope) {
$scope.visible = false;
$scope.$on('RequestStarted', function() {
$scope.visible = true;
});
$scope.$on('RequestFinished', function() {
$scope.visible = false;
});
}
};
});
HTML
...
<loading><h1>Loading...</h1></loading>
...
You can see a working demo here.
By using an HTTP interceptor, you'll be able to track every HTTP request made by the $http service ($resource included) across you Angular application and show the load animation accordingly.