Let's say in a service I need to make an http request and store the value, and then if something else uses the same service it could just reuse that stored value, without need of sending a request:
_cachedValue = null
.factory('myService', ($http, $q)->
getFoo: ->
dfrd = $q.defer()
if _cachedValue == null
$http.get('/foo').success (data)->
_cachedValue = data
dfrd.resolve(_cachedValue)
else
dfrd.resolve(_cachedValue)
return dfrd.promise
That kinda works. The problem is - when two consumers simultaneously try to invoke the method, it would send two requests. So I need to send the request only if it never has been sent, and when the second consumer invokes getFoo, it would just wait till the request comes through.
Repeatedly checking every few milliseconds until _cachedValue isn't null - sounds pretty dumb, right? I can't $watch on the value change (there's no $scope inside the service). I can though utilize angular's event bus, and emit an event via $rootScope. But that sounds very overkill, since nobody outside of the service would be interested in listening to that kind of event. Can someone suggest a better way?
You should only set the $http cache property to true.
This will prevent more request if a request is already in progress.
see: https://github.com/angular/angular.js/blob/master/src/ng/http.js#L987
Related
I have a variable defined in a controller:
app.controller('myController',['$scope','ajaxCall',function($scope,ajaxCall){
$scope.interviewer = {};
ajaxCall.get(/* A url */).then(function(response){
$scope.interviewer = response.data;
console.log($scope.interviewer);
});
console.log($scope.interviewer);
ajaxCall is a custom service which is used to make ajax calls. Inner console is working fine(i.e. it is showing the complete data) but the outer console is printing an empty object.Why?
Because the first A in AJAX means "Asynchronous".
The function passed to then() is executed asynchronously, a long time after the last console.log() line. It's executed once the response to the asynchronous HTTP request comes back from the server.
If it was synchronous, we wouldn't bother with promises and callback functions. We would just do
var response = ajaxCall.get(url);
But that isn't possible, so we do
ajaxCall.get(/* A url */).then(function(response){
which means: please send this request, and when the response is available, then call this function. I'll do plenty of other things in the meantime.
Trying to get my head around this. I have a simple factory using ngResouce, like this:
.factory('FooResource', function($resource) {
var foo = $resource('/api/foo').get();
return foo;
})
And in my app, in multiple places, in multiple controllers over time, I use the value of 'FooResource.bar' (where 'bar' is returned in the data from the get() call).
Is it true that the network call to '/api/foo' will only happen on the first reference for the life of my SPA? Does that first reference need to 'FooResource.bar' be handled like a promise?
From what I see in my playing around with code, it seems like the first question is 'yes' and the second is 'no', but don't know if that's really true in general, or just happening because its a small test app on my dev box.
Edit: I guess part of what I want validation on is my thinking that since this is in a factory, which is a singelton, the $resource call will only ever be made once. Is that true?
Depends, and yes. You will always need to handle it as a promise, and you can enable/disable the http cache. If you have the cache set to true, then the request will send off once and be cached until the cache is cleared.
You can find more about the $resource caching in the $resource documentation here: https://docs.angularjs.org/api/ngResource/service/$resource
I have a simple controller method that does some action (it controls header of my page). I would like, however, to send a message and confirm that other controllers (such as form controllers on page main body) permit that action.
What I would like to do, is:
from the first controller, send a message "BeforeAction"
in another controller, catch that message, generate a reply that might be context-dependent and somehow deliver that reply to first contoller.
based on reply, either continue my action or drop it.
if no listeners to the message Before Action exist, I would like action to continue seamlessly.
How this can be arranged in angular?
When sharing data between controllers you have 2 options. The better one is probably to create a shared service and inject it into both controllers. In this service you could have a message variable. Controller1 could set the message. Controller2 could have a $watch on the message and act appropriatly when the message value changes.
The other option is to use a custom event. If you use $rootScope.$emit(message) in Controller1 this will travel down the scope chain where you can use $scope.$on(message) to catch it.
I think that for you the service would work better. It woyuld allow you to hold a register of listeners that both controllers could access. For example if Controller2 sets a watch on the message variable save this fact to a register variable. In Controller1 beforeAction you can first check the register. If it's empty proceed, otherwise change the value of the message variable
I've been following this tutorial http://draptik.github.io/blog/2013/07/28/restful-crud-with-angularjs/. I implemented a Grails backend with it instead of the Java one in the tutorial.
I've got the data coming back and forth, with one issue. If I create/update/delete a user, I don't see the changes reflected on my user list when I am redirected back. I have to refresh the page to see the updates.
Looking at the network traffic for an edit, it looks like it does a PUT and fires off the GET before the PUT is complete. Assuming this is because $resource returns a promise so things can be done asynchronously. So how do I handle this so that when $location redirects me, my list is up to date?
I'm guessing the options are to wait for the PUT to complete before redirecting/querying for the list, or to somehow manually manage the $scope.users to match the request?
Or maybe this tutorial is just a bad example? Maybe there is a better way to do it (still using $resource)?
Note: I've seen Restangular out there, and I've seen $http with success callbacks, but I would like to understand the situation above.
One way to overcome this issue would be to not redirect to the list page, till you get a callback, and then do a redirect. You can show some busy indicator till that time. The resource call looks like this.
resource.update(config,data,function() { //gets called on success},
function(error) { //gets called on failure});
In real life scenario waiting for the response of update makes sense as you want to handle the error and success scenarios on the same page.
I don't see your code anywhere so i'm just assuming (based on what you wrote and your current problem)
You are probably doing a full (or partial) get each time you changed a user and (re)binding the result to your scope. Doing this in the callback of the resource should actually start the digest cycle angular does to update modified objects. If you had been doing the fetching outside $resource - for example with custom/jquery ajax you would need to execute $scope.$apply()
What i really don't understand you would need to wait for the callback. You already know you added/modified a user. Instead of 'detaching' that user from your scope, modify it, post it to your rest server, then wait for callback, and reinserting it into the scope - why not modify it directly in the list/array you put on your scope?
var users = Users.get(function () {
$scope.users = users.record; // bind the resulting records to the scope
});
$scope.updateUser = function (user) {
resource.update(...); //pseudo
};
Then in your html, you will keep a reference to the currentUser and the div-list will update automaticly.
<div ng-repeat="user in users" ng-click="currentUser=user">{{user.Name}}</div>
<input ng-model="currentUser.Name">
<button ng-click="updateUser(currentUser);">Update</button>
If you don't want to see the update in the list while you type, but only once your callback fires or when you hit the button, would would instead use another ng-model for your input like this:
<input ng-model="tempUser.Name">
And you would then copy the value other in either the updateUser method or in the resource callback like this:
$scope.updateUser = function (user) {
user.Name = $scope.tempUser.Name; // should update automaticly
resource.update(...) // pseudo
}
Hope it helped!
I'm wondering if this is possible, but I have a series of functions where promises would be a perfect solution for ordering and whatnot, however these functions do not depend on the data from previous promises. I would like to use promises to control execution order but I still need the functions to have access to the scope closure.
Here is some context as to why. On the client I have some object. Various actions cause the client to save/update this object. We recently had a race condition where two actions were done so close together the server actually corrupted the object in the database. I thought it would work well to use a promise chain so update requests will wait until there are no other pending update requests. The update information I need to send to the server obviously exists in the scope so I need to be able to access that when pendingRequestPromise resolves.
I essentially have something that looks like this:
scope.$on("UPDATE", function(event, callback){
$http.post("update", scope.myObj).success(function(updateInfo){
callback(updateInfo);
});
};
If the user does the right actions fast enough, I send 2 objects to the server which caused some problems last week.
I would suggest using the $q service somewhat like this:
//disable all stuff you want to user not to click with ng-disable
$scope.pageLoading=true;
var update1Promise = $http.post("update1", scope.myObj).success(function(updateInfo){
callback(updateInfo);
}),
update2Promise = $http.post("update2", scope.myObj).success(function(updateInfo){
callback(updateInfo);
}),
update3Promise = $http.post("update3", scope.myObj).success(function(updateInfo){
callback(updateInfo);
});
$q.all([update1Promise,update2Promise,update3Promise])then(function() {
$scope.pageLoading=false;
});