How do I broadcast from the http interceptor? - angularjs

Using AngularJS 1.2
My interceptor looks like this:
$httpProvider.interceptors.push(['$q', '$log', '$rootScope', function ($q, $log, $rootScope) {
return {
'request': function(config) {
$rootScope.$broadcast('spin');
console.info('request!');
return config || $q.when(config);
},
...
In my nav controller (which handles and binds the loader/spinner to the view):
$rootScope.$watch('spin', function(event) {
console.info('spin');
$scope.spinner++;
});
The broadcast seems to happen only once at the end of all the responses received, even though I can see many request! in the console log.
How must I manage my global spinner/loader?
EDIT
I wish to show a loader/spinner in my navbar whenever data is being loaded.

The $watch function doesn't listen for broadcast messages. It watches for changes on the scope. In this case, you are calling a function whenever $rootScope.spin changes, which gets called (by default) immediately, which is why you got called once.
The $on function is what you want here, as it is what will listen to broadcast events.
$rootScope.$on('spin', function(msg, data) {
console.info('spin');
$scope.spinner++;
});
I've put together a complete working example if you are curious:
http://codepen.io/BrianGenisio/pen/wIBHz

Instead of using watcher you should just use on in the module run function
angular.module('test',[]).run(['$rootScope' function ($rootScope) {
$rootScope.$on("$spin", function () {
// set the spinner here
});
}]);

Related

Utilising $scope.apply()

I have the following which works fine, drawing info from a RESTful api feed
app.controller('servicesController', ['$scope', '$location', '$http', '$interval',
function($scope, $location, $http, $interval) {
var getData = function() {
// Initialize $scope using the value of the model attribute, e.g.,
$scope.url = "https://(remote link to JSON api)";
$http.get($scope.url).success(function(data) {
$scope.listOfServices = data.runningServices; // get data from json
});
};
getData();
$interval(getData(), 10000);
}
]);
However my view is not updating every 10 seconds as expected. I have read that I need to use $scope.apply() somewhere in this above code.
I tried placing the following (in the appropriate place above)
$http.get($scope.url).success(function(data) {
$scope.listOfServices = data.runningServices; // get data from json
$scope.apply(); //I also tried $scope.runningServices.apply()
});
$scope.apply is not your problem, the scope will be digested automatically at the end of the $http request and $interval. Certain actions automatically "inform" Angular that the scope may have changed and trigger a digest; only if you're writing "non-Angular" code may you have to explicitly trigger a scope digest, since otherwise Angular wouldn't notice any changes.
No, your issue is that you're calling getData(), and then have its return value (undefined) execute every ten seconds. Which is obviously nonsense. You just want to pass the function itself to $interval:
$interval(getData, 10000);
// look ma, ^^^^^, no parentheses

How to make controller wait untill app.run finishes

In my application, I'm getting some data in app.run using $http.get()
The result from this is required to proceed to the controller.
Currently my controller is executing before the completion of this $http.get
How can I make my controller's execute after the execution of $http.get()
app.run(function ($rootScope, $http, $cookies) {
var currentLanguageId = angular.fromJson($cookies.get('mykey')).UserInfo.Country;
$http.get('myurl').then(function (serviceInfo) {
$rootScope.serviceURL = serviceInfo.data.serviceURL;
}, function (error) {
alert('error service info');
});
run can't handle asynchronous work at the moment. See this issue : https://github.com/angular/angular.js/issues/4003
You have multiple solutions for that. You can get your data without Angular and start Angular manually ( How to wait for a promise in a Run block in Angular? )
Or you can use the resolve of your route to do this : AngularJS : Initialize service with asynchronous data
You can use angular scope event. When data is fetched, you can broadcast an event from $rootScope to all controllers and receive this event in target controller.
You may use $rootScope.$broadcast();in the app.run and then use $rootScope.$on() in your controller.
app.run(function ($rootScope, $http, $cookies) {
var currentLanguageId = angular.fromJson($cookies.get('mykey')).UserInfo.Country;
$http.get('myurl').then(function (serviceInfo) {
$rootScope.serviceURL = serviceInfo.data.serviceURL;
$rootScope.$broadcast('serviceInfoReceived')
}, function (error) {
alert('error service info');
});
});
In your Controller
app.controller ('myCtrl' , function($rootScope) {
$rootScope.$on("serviceInfoReceived", function(){
console.log($rootScope.serviceURL)
});
})
Hope this may help you.
This is an old question, however the $rootScope.$on solution will not always work. It will depend on the timing of the controller registering the listener. The way I have found that works is to set a $rootScope property, and then configure a recursive timeout to wait for it to be set.
function waitForRun() {
if($rootScope.runFinalized){
// do something
} else {
$timeout(waitForRun, 500)
}
}
waitForRun();
and after the last .run block:
.run(function($rootScope) { $rootScope.runFinalized = true; })
Ugly, but it works.

calling a method from controller when $locationChangeStart is broadcasted

In my controller I changed the url with varying parameters depending on geolocation changes of my map. This is my setLocation method:
$scope.setLocation = function(lat,lng){
$location.search('lat',lat);
$location.search('lng',lng);
$scope.$apply();
};
I have another method that loads location data on the map. This is my getProjectsByCenter:
$scope.getProjectsByCenter = function(){
var center = getProjectsByCenter();
$scope.setLocation(center.lat(),center.lng());
};
Once the location starts changing the browser saves all these url changes in its history, but when I click on the back button I can't figure out how to call my controller's method. Back Button does change the $location and the following broadcast listener is called. In fact the following broadcast listener is called everytime I setLocation(), but in this case everything is working as it should.
app.run(['$rootScope', '$location',
function ($rootScope, $location) {
//Client-side security. Server-side framework MUST add it's
//own security as well since client-based “security” is easily hacked
$rootScope.$on('$locationChangeStart', function (event, next, current) {
if( next !== current && (hasBackButtonBeenClicked() || scopeFunctionWasNotCalled()) ){
// fetch project again.
// $state.reload();
// $rootScope.$apply(); <--- doesn't work
console.log("location has changed...now find a way to call controller's $scope.getProjectsByCenter()...");
}
});
}]);
Any suggestions?
You can capture the change event in the following manner.
Add this controller in the destination page. The below method will capture the change event and you should be able to trigger the controller.
.controller('MyController', function() {
$scope.$on('$routeChangeSuccess', function () {
// Do your work
});
})
Hope this is what you are looking for.

How to make sure $http.get in service completes before directive is initialized with data?

How do I make it such that all the data "MyService" needs to retrieve is retrieved before my 'control' directive is created or some function to actually add those items is called? If not, is there some other recommended way involving controllers, etc. Have yet to see a basic example of similar sequence. Note that data may have more values added to it via other functions, in which a method would have to be called explicitly for those new data elements.
Service:
// var app = angular.module...
app.service('MyService', function($http) {
this.data = [];
// Called once to first initalize the data
$http.get('data.json').then(function data(response) {
this.data.push(response);
}
})
Directive (with its own isolate controller? or point to the main app controller)?:
myDirective.directive('control', ['Params', 'MyService', function(Params, MyService) {
// Call on a method "addData(MyService.data) when the data is actually loaded
}]);
I don't think there is any way to stop directive form initiating and wait for a async function call of a service. However, several alternatives here:
1. Use ng-route and set up a resolve with $routeProvider.
API: http://docs.angularjs.org/api/ngRoute.$routeProvider
Basically, you return a promise in a route's resolver, the route's view will wait for that promise to resolve. After resolve, it will load the view and initiate whatever controller or directive within the view.
2. $watch the data property of MyService.
When data changes, do whatever you want
$scope.$watch(function() {
return MyService.data;
}, function(newVal, oldVal) {
// do something
}, true);
3. Trigger a ready event on MyService, listen to MyService in your directive.
This requires you to include some event library such as 'EventEmitter`, and mix it into MyService.
4. $broadcast ready event on $rootScope from MyService, and listen to it in your directive.
app.service('MyService', function($http, $rootScope) {
this.data = [];
var _this = this;
// Called once to first initalize the data
$http.get('data.json').then(function data(response) {
_this.data.push(response);
$rootScope.$broadcast('MyServiceReady');
}
})
myDirective.directive('control', ['Params', 'MyService', function(Params, MyService) {
return function(scope) {
scope.$on('MyServiceReady', function() {
// do something
});
};
}]);

Angular.JS Fire off two async service calls, then perform action after both have completed

I'm still wrapping my brain around Angular.JS.
I have two independent $http calls that retrieve data from remote web services. I have an action that I want to fire off after both service calls have been completed.
The first service call will populate the $scope.model
The second service call, modifies data in the $scope.model (it adds some counter properties that are rendered in the view)
Another unique requirement is that eventually the second service call will be called and updated outside the controller with the $scope.model. It's a notification message pump.
I'm guessing I'm going to use promises $q and possibly $service, but I'm not really sure where to start for something like this following some best practices.
I know it doesn't sound like async calls are appropriate here, since my example it could be simplified by doing it syncronously. However, the second service call is a notification updater, so it'll get continually polled to the server (eventually a websocket will be used).
It's a common pattern I'll see in this application.
You'll want to use $q promises. Specifically $q.all(). All $http methods will return promises. $q.all([promise, promise, promise]).then(doSomething) will wait for all promises to resolve then call doSomething passing an array of the promises results to it.
app.service('myService', ['$http', '$q', function($http, $q) {
return {
waitForBoth: function() {
return $q.all([
$http.get('/One/Thing'),
$http.get('/Other/Thing')
]);
};
}
}]);
Then call it:
app.controller('MyCtrl', ['$scope', 'myService', function($scope, myService) {
myService.waitForBoth().then(function (returnValues){
var from1 = returnValues[0].data;
var from2 = returnValues[1].data;
//do something here.
});
}]);
Here's a demonstration Plunker for you.
Create a service for your first service call. Pete's answer will help with that: https://stackoverflow.com/a/12513509/215945
In your controller, in the then() callback, add a $watch for the appropriate $scope.model property:
app.controller('MainCtrl', function(myService, $scope) {
myService.async().then(function(myData) {
$scope.model = myData;
$scope.$watch('model.???', function(newVal, oldVal) {
if(newVal !== oldVal) {
// do something here, now that $scope.model.??? has changed
}
})
});
});

Resources