how to call a service in another module using $on and $broadcast - angularjs

I'm trying to do a call from factory A to factory B using angular's $broadcast. These two factories are defined in separate modules.. Below is the code that attempts to do this.
angular.module('secondApp', [])
.service('B', function($rootScope, $scope) {
$rootScope.$on('test', function(event, data) {
console.log(event, data);
});
});
var app = angular.module('firstApp', ['secondApp']);
app.controller('MainCtrl', function($scope, A) {
$scope.test = function() {
A.test();
};
});
app.service('A', function($rootScope) {
this.test = function() {
$rootScope.$broadcast('test', 'Hello from service A!');
};
});

You should use $rootScope.$emit() instead of $rootScope.$broadcast().
Because $rootScope.$broadcast() will despatch downwards.
Edit:
Made a sample code to test emit/broadcast/on base on Angular guide: Plunker.
Turns out that using $rootScope.$on to listen event will be triggered by $rootScope.$emit and $rootScope.$broadcast. The difference is $broadcast will dispatch downwards, so all scopes will receive the event also.
So if you just want to notify $rootScope, just using $rootScope.$emit. $rootScope.$broadcast will waste resource.
Hope it will help.

Related

AngularJS: inject dependency into every controller

Suppose that I have a huge web application (that uses AngularJS) with a lot of controllers. Is there a way to inject $log service in every controller? To be more clear, I want to write something like this:
.config(function($log) {
allMyControllers.inject($log);
})
instead of
.controller('Controller1', function($log) {...})
.controller('Controller2', function($log) {...})
.controller('Controller3', function($log) {...})
.controller('Controller4', function($log) {...})
Possible thing that you can do is, create a controller that has all needed dependencies and make it as base controller and other controllers can extend it using angular extend api.
some clear example code which I came accross :
.controller('baseController', function(someService) {
this.someService = someService;
})
.controller('extendedController', function($scope, $controller) {
angular.extend(this, $controller('baseController', { $scope: $scope }));
this.alert = this.someService.alert;
})
.service('someService', function() {
this.alert = function() {
window.alert('alert some service');
};
});
Working solution of above code can be found here.

AngularJS 1.6 - On a config phase $http defaults requestError, broadcast event to main scope?

I need to broadcast an event after receiving a $http.defaults request error (400 status code, mainly) but I cannot access $rootScope within config phase.
Code:
var app = angular.module('app', []);
app.config(['$httpProvider','$injector', function($httpProvider, $injector) {
$httpProvider.interceptors.push(['$q', function($q) {
return {
'responseError': function (rejection) {
/*
Doesn't work:
$rootScope = $injector.get("$rootScope");
$rootScope.$emit("RESPONSE_ERROR");
*/
return $q.reject(rejection);
}
};
}]);
}]);
app.controller("Controller",function($scope){
$scope.$on("RESPONSE_ERROR",function(event,params){
alert("WORKING!");
});
});
Any suggestions? Thanks.
The problem is caused by the fact that there are two different injectors, as explained in this answer. In the code above $injector is the one that was injected during config phase, it deals with service providers.
Another one should be injected during run phase in order to get access to service instances like $rootScope:
$httpProvider.interceptors.push(function($q, $injector) { ... });
As another answer already explains, $rootScope can be injected into the interceptor directly. While $injector is a common way to avoid circular dependencies in interceptors and inject $http there.
You can try the following code injecting $rootScope into custom interceptor function.
var app = angular.module('app', []);
app.config(['$httpProvider','$injector', function($httpProvider, $injector) {
$httpProvider.interceptors.push(['$rootScope', '$q', function($rootScope, $q) {
return {
'responseError': function (rejection) {
$rootScope.$broadcast('RESPONSE_ERROR', {
// Custom properties
});
return $q.reject(rejection);
}
};
}]);
}]);

Broadcast different controllers in AngularJS

I am developing a permission system for my app using a factory that looks like this:
angular.module('ecosystemServices', [])
.factory('Guard', function($http, $rootScope) {
var permissions = [];
return {
ready: function() {
if (permissions.length == 0) {
$http.get('/api/users/own-permissions')
.success(function(data){
permissions = data.user_permissions;
$rootScope.$broadcast('permissionsReady', 1);
});
} else {
$rootScope.$broadcast('permissionsReady', 1);
}
return true;
}
}
})
I have to wait until the permissions are loaded to start making queries, so I'm performing a broadcast to the controller, that looks something like this:
appControllers.controller('AgencyPanelCtrl', ['$rootScope', '$scope', '$location', '$routeParams', '$http','Guard',
function ($rootScope, $scope, $location, $routeParams, $http, Guard) {
$scope.loading = true;
Guard.ready();
$scope.has_permission = function(permission) {
return Guard.can(permission);
}
$rootScope.$on('permissionsReady', function(event, ready) {
$scope.initialize();
});
$scope.initialize = function() {
console.log("Initialized");
}
}]);
It's working, but if I change the controller and do the same (change the view and change the controller), the broadcast arrives to the old controller.
Does anyone knows why? Or how to fix it?
You are defining your event listener on the $rootScope. As the name would suggest, there is only one $rootScope, and it's at the root of the document.
As such, when your view changes and the old controller is no longer relevant, the $rootScope still has the listener, which still holds a reference to the old controller's $scope through a closure, and hence everything still goes to the old controller.
Instead, you should define the listener on the $scope of the controller:
$scope.$on('permissionsReady', function(event, ready) {
$scope.initialize();
});
This should fix your problem.

Angular factory returning a promise

When my app starts I load some settings from a server. Most of my controllers need this before anything useful can be done. I want to simplify the controller's code as much as possible. My attempt, which doesn't work, is something like this:
app.factory('settings', ['$rootScope', '$http', '$q', function($rootScope, $http, $q) {
var deferred = $q.defer();
$http.get('/api/public/settings/get').success(function(data) {
$rootScope.settings = data;
deferred.resolve();
});
return deferred.promise;
}]);
app.controller('SomeCtrl', ['$rootScope', 'settings', function($rootScope, settings) {
// Here I want settings to be available
}]);
I would like to avoid having a lot of settings.then(function() ...) everywhere.
Any ideas on how to solve this in a nice way?
$http itself return promise you don't need to bind it inside the $q this is not a good practice and considered as Anti Pattern.
Use:-
app.factory('settings', ['$rootScope', '$http', '$q', function($rootScope, $http) {
return $http.get('/api/public/settings/get')
}]);
app.controller('SomeCtrl', ['settings',$scope, function(settings,$scope) {
settings.then(function(result){
$scope.settings=result.data;
});
}]);
Your way can be done as :-
app.factory('settings', ['$rootScope', '$http', '$q', function($rootScope, $http, $q) {
var deferred = $q.defer();
$http.get('/api/public/settings/get').success(function(data) {
deferred.resolve(data);
});
return deferred.promise;
}]);
app.controller('SomeCtrl', ['$scope', 'settings', function($scope, settings) {
settings.then(function(data){
$scope.settings=data;
})
}]);
Don't overload $rootScope if you wanted it you need to use $watch for the changes in $rootScope(Not recommended).
Somewhere you would need to "wait".
The only built-in way in Angular to completely absolve the controller from having to wait on its own for async data to be loaded is to instantiate a controller with $routeProvider's route's resolve property (or the alternative $stateProvider of ui.router). This will run controller only when all the promises are resolved, and the resolved data would be injected.
So, ng-route alternative - plunker:
$routeProvider.when("/", {
controller: "SomeCtrl",
templateUrl: "someTemplate.html",
resolve: {
settings: function(settingsSvc){
return settingsSvc.load(); // I renamed the loading function for clarity
}
});
Then, in SomeCtrl you can add settings as an injectable dependency:
.controller("SomeCtrl", function($scope, settings){
if (settings.foo) $scope.bar = "foo is on";
})
This will "wait" to load someTemplate in <div ng-view></div> until settings is resolved.
The settingsSvc should cache the promise so that it won't need to redo the HTTP request. Note, that as mentioned in another answer, there is no need for $q.defer when the API you are using (like $http) already returns a promise:
.factory("settingsSvc", function($http){
var svc = {settings: {}};
var promise = $http.get('/api/public/settings/get').success(function(data){
svc.settings = data; // optionally set the settings data here
});
svc.load = function(){
return promise;
}
return svc;
});
Another approach, if you don't like the ngRoute way, could be to have the settings service broadcast on $rootScope an event when settings were loaded, and controllers could react to it and do whatever. But that seems "heavier" than .then.
I guess the third way - plunker - would be to have an app-level controller "enabling" the rest of the app only when all the dependencies have preloaded:
.controller("AppCtrl", function($q, settingsSvc, someOtherService){
$scope.loaded = false;
$q.all([settingsSvc.load, someOtherService.prefetch]).then(function(){
$scope.loaded = true;
});
});
And in the View, toggle ng-if with loaded:
<body ng-controller="AppCtrl">
<div ng-if="!loaded">loading app...</div>
<div ng-if="loaded">
<div ng-controller="MainCtrl"></div>
<div ng-controller="MenuCtrl"></div>
</div>
</body>
Fo ui-router this is easily done with having an application root state with at least this minimum definition
$stateProvider
.state('app', {
abstract: true,
template: '<div ui-view></div>'
resolve: {
settings: function($http){
return $http.get('/api/public/settings/get')
.then(function(response) {return response.data});
}
}
})
After this you can make all application states inherit from this root state and
All controllers will be executed only after settings are loaded
All controllers will gain access to settings resolved value as possible injectable.
As mentioned above resolve also works for the original ng-route but since it does not support nesting the approach is not as useful as for ui-router.
You can manually bootstrap your application after settings are loaded.
var initInjector = angular.injector(["ng"]);
var $http = initInjector.get("$http");
var $rootScope = initInjector.get("$rootScope");
$http.get('/api/public/settings/get').success(function(data) {
$rootScope.settings = data;
angular.element(document).ready(function () {
angular.bootstrap(document, ["app"]);
});
});
In this case your whole application will run only after the settings are loaded.
See Angular bootstrap documentation for details

AngularJS Intercept and extend controller $scope

There is a lot of reusable functionality that I have defined in my application that EVERY controller uses with the $scope variable. Instead of me having to create a shared service each time, is there a way to extend the $scope variable so that I can have my extended code available everywhere?
Something like:
//I've tested this out and it doesn't work, but this is what I want to do.
angular.module('App',[]).config(['$scopeProvider',function($scope) {
$scope.method1 = function() { ... };
$scope.method2 = function() { ... };
}]);
Then later on:
var HomeCtrl = function($scope) {
$scope.method1();
};
Is this possible? Or do I need to create a shared service and then have the $scope extend from that for the first line of each controller?
Instead of .config try .run, this will do exactly what you want.
angular.module('App', []).run(['$rootScope', function($rootScope) {
$rootScope.foo = function() {
alert("WIN!");
};
}]);
angular.module('App').controller('HomeCtr', ['$scope', function($scope) {
$scope.foo(); #will call the alert
}]);
NOTE I have only used module.controller because I like it, var HomeCtrl = function($scope) { will have the same effect.

Resources