//inside a service PService
this.getPTypes = function(){
var types = PTypesFactory.get({});
return types.$promise.then(function(result)
{
console.log(result.groups);
return result.groups;
});
}
//inside a controller
$scope.groups = PService.getPTypes();
console log shows correct fetched REST data, but when I do
console.log($scope.groups);
I get
Object {then: function, catch: function, finally: function}
which is promise API instead of the correct resolved data.
The problem is that you trying to use a asynchronous function like it was a synchronous one.
then is a method which returns a promise.
when invoking it with a callback that callback would not be invoked immediately, only when the response get back from the server.
You can write it like so:
Service
this.getPTypes = function(callback){
PTypesFactory.get({}).then(callback);
}
Controller
PService.getPTypes(function(res){
$scope.groups = res.data;
});
Promises are used to handle asynchronous operations. The function you pass to the then method is called at some indeterminable point in time. You can't return a value from within it to some other point in execution.
Instead of calling then in your service, just return the promise:
this.getPTypes = function(){
return PTypesFactory.get({}).$promise;
}
and handle its resolution in the controller:
$scope.groups = PService.getPTypes().then(function(result) {
console.log(result.groups);
});
Related
I would like to test this function:
function initializeView() {
var deferred = $q.defer();
if(this.momentArray) {
core.listMoments(constants.BEST_MOMENT_PREFIX, '').then(function(moments) {
//Ommitted
deferred.resolve(moments);
}, function(error) {
console.log("ERROR");
deferred.reject(error);
});
}
else {
deferred.resolve();
}
return deferred.promise;
};
The function calls core.listMoments:
function listMoments(prefix, startAfter) {
// var deferred = $q.defer();
var promises = [];
return awsServices.getMoments(prefix, startAfter).then(function(moments) { //Mocked
console.log("getMoments Returned"); //Does not print
for(var i = 0; i < moments.length; i++) {
// moments[i].Key = constants.IMAGE_URL + moments[i].Key;
promises.push(getMomentMetaData(moments[i]));
}
return $q.all(promises);
});
};
Here is my test function:
it('Should correctly initialize the view', function(done) {
spyOn(awsServices, 'getMoments').and.callFake(function() {
console.log("getMoments Has been mocked"); //This prints
return $q.resolve(mock_moment);
});
service.initializeView().then(function() {
done();
})
});
The problem is with the awsServices 'getMoments' mock. The call to awsServices.getMoments is in the listMoments function. I would like to mock out this function but when I do it does not execute the "then" part of the promise.
So based on my console logs it would print the 'getMoments Has been mocked' log but it would not print 'getMoments Returned' log. So the function is mocked but for some reason it is not moving into the then statement and my test just times out.
In order to get the .then() part of a promise to work in such a test, you need to use a $rootScope.$apply(). This is needed whether the promise is in your test code or in a referenced library that is being tested. Think of it like the flush() function for $http or $timeout calls.
The Testing example from the Angular documentation's $q page shows how to use it:
it('should simulate promise', inject(function($q, $rootScope) {
var deferred = $q.defer();
var promise = deferred.promise;
var resolvedValue;
promise.then(function(value) { resolvedValue = value; });
expect(resolvedValue).toBeUndefined();
// Simulate resolving of promise
deferred.resolve(123);
// Note that the 'then' function does not get called synchronously.
// This is because we want the promise API to always be async, whether or not
// it got called synchronously or asynchronously.
expect(resolvedValue).toBeUndefined();
// Propagate promise resolution to 'then' functions using $apply().
$rootScope.$apply();
expect(resolvedValue).toEqual(123);
}));
Note that they inject $rootScope.
$q promises can be synchronous (when they are resolved synchronously) and depend on digest cycles.
There should generally be no asynchronous done callback in Angular tests.
Angular tests are supposed to be synchronous, so are $q promises. In order to achieve that a digest should be triggered manually when an existing promise (the ones that is returned from getMoments and initializeView) is chained with then. If done callback is placed inside then and a digest is not triggered, this will result in spec timeout.
spyOn(awsServices, 'getMoments').and.callFake(function() {
console.log("getMoments Has been mocked"); //This prints
return $q.resolve(mock_moment);
});
service.initializeView();
$rootScope.$digest();
The thing that can be improved here is isolation. There are several units (methods) involved in a single test. This will affect troubleshooting when one of them fails.
Usually unit testing implies that only one unit is tested at time, while the rest are mocked or stubbed. In this case in one test service.listMoments is called and awsServices.getMoments is mocked, and in another test service.initializeView is called and service.listMoments is mocked.
Constants.getContants is a promise which will get all the constants that are used in my application. I would like to save this to a $scope variable so that I can access it anywhere in the controller or application. Now, whenever I need to access it, I need to repeat the call and do the operation there itself.
Even If I try to save it in the $scope it will not be available outside the corresponding handler. How to solve this issue.
Following is the code that I'm using:
Constants.getConstants().then(function (AppContants) {
$scope.responseCount = AppContants.data.serverUrl+AppContants.data.appId
console.log($scope.responseCount);
//$scope.$apply();
});
console.log($scope.responseCount);
Here AJAX call is going out of sync also. I know that actions need to performed inside the handler function so that we can be sure that the intended action is executed only after a successful AJAX call. I need to use these variables outside the function. I tried $scope.$apply() operation as well. It didn't help. Is there a way to solve this? Thanks in advance.
Constants.getConstants().then(function(response)
{
$scope.responseCount = response.data;
}, function(error)
{
console.log(error);
});
And in your service you should have something like
this.getConstants= function($username){
var endpoint = "url";
return $http({
method: 'get',
url: endpoint
});
};
In your case the 2nd Console.Log executes just after placing the AJAX call. It does not wait for the AJAX to respond as it is a asynchronous call.
You can only be able to use '$scope.responseCount' property after the AJAX cal is resolved.
As a workaround you can:
Place this call to fetch constants at the time of application startup and save the constants in some service (shared service).
Do your operation in the 'then' block of this AJAX call.
Here's the thing. When you are Constants.getConstants() it return response as promise. since javascript asynchronous it does not wait until response return. It just keep on executing. That's why the console outside the then function display undefined.
workaround is you can add a function inside promise and put you operations inside that function
Constants.getConstants().then(function(AppContants) {
$scope.responseCount = AppContants.data.serverUrl + AppContants.data.appId
console.log($scope.responseCount);
sampleFunc()
});
function sampleFunc() {
// do your oprations here
console.log($scope.responseCount);
}
You can cache the promise in a service:
app.service("ConstantCache", function(Constants) {
var promiseCache;
this.getPromise = function() {
if ( promiseCache } {
return promiseCache;
} else {
promiseCache = Constants.getConstants();
return promiseCache;
};
};
this.trashCache = function() {
promiseCache = null;
};
});
Then the cached promise can be used in any controller as many times as desired:
ConstantCache.getPromise().then(function(AppContants) {
$scope.responseCount = AppContants.data.serverUrl + AppContants.data.appId
console.log($scope.responseCount);
sampleFunc()
});
I'm building a ticket admin table, and I have some issues trying to export a variable outside the ajax function.
My code:
app.controller('bodyController',function($scope,$http,$sce){
$scope.ticketList = [];
$http.get("tickets.php")
.then(function(response) {
$scope.ticketModify = response.data;
console.log($scope.ticketModify); //this one return the correct data.
return $scope.ticketModify;
});
console.log($scope.ticketModify); //this return undefine
Same result with factory if I try to return the response.data into any variable
Just because code comes physically above other code doesn't mean that it gets executed from top to bottom. Think about your program like this:
app.controller('bodyController',function($scope,$http,$sce){
$scope.ticketList = [];
$http.get("tickets.php")
.then(handleResponse);
console.log($scope.ticketModify); //this return undefine
function handleResponse(response) {
$scope.ticketModify = response.data;
console.log($scope.ticketModify); //this one return the correct data.
return $scope.ticketModify;
}
Do you see now why $scope.ticketModify is still undefined? The position of the code does not matter, what matters is the time at which it is executed. You should chain another then on to the then you have there if you want to do more with the newly modified $scope.ticketModify. Or, call another function and pass $scope.ticketModify from your current then. You can do anything!
What executes in the .then is executed when the server responded.
But just after the ´$http.get´Promise, the next code will be executed.
You should look into async programming and Promises. https://developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Objets_globaux/Promise
app.controller('bodyController', function($scope, $http, $sce) {
$scope.ticketList = [];
$scope.ticketModify = "";
$http.get("tickets.php")
.then(function(response) {
$scope.ticketModify = response.data;
console.log($scope.ticketModify);
return $scope.ticketModify;
});
console.log($scope.ticketModify);
// This should print a empty String
}
the place where it is working alright for you, is after the call to tickets.php is returned.
The place where it is not working is due to the fact that the ticketModify is not defined by the time you are accessing. You called the tickets.php as an async call and immediately trying to access the variable which is being populated after the promise is resolved.
So, all you could do is to declare ticketModify on scope before the http call to tickets.php and update the value on success / then callback on tickets.php
This question already has answers here:
How can I access a variable outside a promise `.then` method?
(2 answers)
Closed 5 years ago.
I have a service in AngularJs which will return a value from Database.
userData.getUserData(function(response) {
$scope.uid = response
});
when I inject this service in my controller it will return a Promise,
But i need this Promise value Outside of my Function, how can i do that ?
Plunkr link of code
From your plunker code you have a service which looks like this:
angular.module('plunker');
.service('myService', function($firebaseRef, $firebaseObject){
this.getUserData = function(el) {
$firebaseObject($firebaseRef.users.child(this.localStorage().uid)).$loaded(function(data) {
el(data);
})
}
});
and a controller like this:
app.controller('MainCtrl', function($scope, myService) {
myService.getUserData(function(response) {
$scope.uid = response;
})
console.log($scope.uid);
$scope.postRequest = function(val) {
$firebaseArray($firebaseRef.requests.child($scope.uid)).$add(val);
console.log(val)
console.log($scope.request);
}
});
The problem is that the line console.log($scope.uid); prints undefined.
You are thinking in the terms of a standard blocking programming, but in this case the call to getUserData is non-blocking which means that you don't wait for the response, instead you only send the request to the server (Firebase) and continue to the next statement which is console.log.
The callback function(response) { $scope.uid = response; } will be invoked when the client reads success response (HTTP 2xx) returned by the server. This takes at least the time request travels to the server and response to travel back + the time it takes for the server to actually get the data. For example 150ms.
So, basically at the time the console.log statement is executed, the response callback was still not invoked, ie. the $scope.uid is not set which means that the console.log will print undefined.
To resolve this you need to execute your code, which depends on the response from the server, in the callback itself. For example something like this:
app.controller('MainCtrl', function($scope, myService) {
myService.getUserData(function(response) {
$scope.uid = response;
console.log($scope.uid);
// and any other code which depends on the $scope.uid
});
// ...
});
The cool factor would be to use AngularJS promises via $q service. For example, you could redefine your service like this:
angular.module('plunker');
.service('myService', function($q, $firebaseRef, $firebaseObject){
var deferred = $q.defer();
this.getUserData = function(el) {
$firebaseObject($firebaseRef.users.child(this.localStorage().uid)).$loaded(function(data) {
deferred.resolve(data);
});
};
return deferred.promise;
});
then in your controller you can use your service method like this:
app.controller('MainCtrl', function($scope, myService) {
myService.getUserData()
.then(function(data) {
$scope.uid = data;
console.log($scope.uid);
// and any other code
// you can also return promises here and then chain
// them, read about AngularJS promises
});
// ...
});
This is basically same as the example before, but with added benefit of better readability which is accomplished by avoiding callback hell.
I noticed that you have postRequest function which uses $scope.uid. I guess that you do not want to execute this function if you do not have $scope.uid. I also guess that this function is called by some event, like click on a button. My recommendation is that you disable the button or whatever else invokes this function until the $scope.uid is loaded.
For example like this:
<button type="button" ng-click="postRequest(something)" ng-disabled="uid === undefined">Post</button>
Hope this helps.
You issue that has been discussed has to do with the fact that you are trying to use $scope.uid before your promise has returned anything.
You can get around things like this by taking a few steps, mainly, you can init the scope var before you use. For instance if the response is an object you could just do something like this:
$scope.uid = {};
userData.getUserData(function(response) {
$scope.uid = response;
});
Then your var wont be undefined. But you should also consider when and how you are using this variable, that will effect if you want to init like this or not.
If you log like this, it will work
userData.getUserData(function(response) {
$scope.uid = response;
console.log($scope.uid);
});
and if you log like this it will not work because this log is not going to wait for you promise to return before logging;
userData.getUserData(function(response) {
$scope.uid = response;
});
console.log($scope.uid);
You'd need to provide more information to determine how best to deal with using this returned information and local variable. But the general idea of the problem is that you are attempting to log the variable before the promise is back.
TL:DR You have access to $scope.uid outside of the function, you need to wait for the reponse to give it the data before it will be inside, you can init it if you do not want it to start out as undefined
UPDATE : you need to use a callback to fire the second call After you have the first call back
userData.getUserData(function(response) {
$scope.postRequest(response);
});
$scope.postRequest = function(val) {
$firebaseArray($firebaseRef.requests.child($scope.uid)).$add(val);
console.log(val) console.log($scope.request);
}
Your plunk fixed : https://plnkr.co/edit/KbVoni3jfnHm54M80kYl?p=preview
You have to wait until the process of getting the response from userData.getUserData is done.
There are 3 ways as far as I know to solve this:
Using Callback
function getUserData(callback){
userData.getUserData(function(response) {
callback(response);
});
}
then you call that function
getUserData(function(response){
$scope.uid = response;
// then you can proceed and use the $scope.uid here
});
Wrap it in function
getUserData(function(response){
callAnotherFunction(response);
});
function callAnotherFunction(response){
console.log(response);
// You can use the value inside this function
}
Or use timeout
You can use $timeout to give time to the request and assign it to $scope.uid
I have this factory which is called by some controllers.
app.factory('fileData', function($http) {
return {
get: function(filename){ return $http({ method: 'GET', url: filename});
}
};
});
Now I want to call it from a regular function and return the data from the factory. How can I do that? This one does not work because the fileData passed in is not recognized without $scope.
function getData (file, fileData) {
rels = [];
var handleSuccess = function(data, status) {
rels = data;
console.log(rels);
};
fileData.get(filename).success(handleSuccess);
return rels;
}
Any idea how to go around this?
Move return rels inside handleSuccess, you're returning before handleSuccess is getting called. So you're signaling that the function completed execution prematurely.
function getData (file, fileData) {
rels = [];
var handleSuccess = function(data, status) {
rels = data;
console.log(rels);
return rels; // Return rels when the Promise is finished
};
fileData.get(file).success(handleSuccess);
}
Since you're returning the promise from $http, you need to handle its execution in an asynchronous way which, is simply not returning until the promise is resolved.
It would also be a good idea to add a reject handler.
Additionally, .success() is deprecated and you should use .then() and .catch() respectively for handling resolve() and reject() of the Promise appropriately.
See below for the preferred approach to Promises with $http
fileData.get(file)
.then(handleSuccess)
.catch(handleFailure); // Handle any errors returned from $http
EDIT Update for DI example for controller
You need to inject the fileData factory into your Angular Controller. For more info on Dependency Inject see the Angular Docs for DI. Just to note, DI is a huge piece of Angular so it is pretty fundamental to understand this before proceeding.
angular
.module('yourApp')
.controller('yourController', ['$scope', 'fileData', function($scope, fileData) {
// Expose getData via $scope
$scope.getData = getData;
function getData(file, fileData) {
var rels = [];
var handleSuccess = function(data, status) {
rels = data;
console.log(rels);
return rels;
};
fileData.get(file)
.then(handleSuccess);
}
}]);
Your call to the method exposed in the factory (get) uses the $http service which returns a promise. So you cannot get the result of this call in a synchronous way.
In your sample the return rels happens way before the handleSuccess method is called and thus you are returning the old value (an empty array).
The reason it works inside of a controller function is that the controller instance is living for a longer time and when the server call returns successfully, the variable in you controller's $scope is assigned (which then is reflected in your UI via the angular data binding).
So the best way to solve this, is to avoid having a synchronous api in your "regular function". Could you probably make this also async by returning a promise?