Q.all.then doesn't wait for completion - angularjs

I have a service in separate JS file, this service is like OOP class that holds 'methods' to load necessary data from the web.
I want call those 'methods' and get the data in my main JS file, actually I want to load data of three types and force JS flow to wait untill that data is retrieved, here's my code:
services.js
// My 'Class' to load data from the web server
myApp.factory("LoadData", ["_gl", function (_gl) {
return {
GetUsers: function ($http) {
$http({
method: 'POST',
url: 'http://localhost/dgis/ps/select.php',
data: { "action": "GetUsers" }
}).then(function successCallback(response) {
// Save the response JSON object to my global objects
_gl.myUsers = response.data;
}, function errorCallback(response) {
console.log("GetUsersError:" + response);
});
},
GetObGroups: function ($http) {
$http({
method: 'POST',
url: 'http://localhost/dgis/ps/select.php',
data: { "action": "GetObGroups" }
}).then(function successCallback(response) {
// Save the response JSON object to my global objects
// This code fills array because it iterates through it
angular.forEach(response.data, function (value, key) {
_gl.myObGroups.push(value)
});
}, function errorCallback(response) {
console.log("GetObGroups:" + response);
});
},
GetObjects: function ($http) {
$http({
method: 'POST',
url: 'http://localhost/dgis/ps/select.php',
data: { "action": "GetObjects" }
}).then(function successCallback(response) {
_gl.myObjects = response.data;
}, function errorCallback(response) {
console.log("GetObjectsError:" + response);
});
}
}
}]);
// My global variables
myApp.factory('_gl', function () {
return {
myUsers: [],
myOrganisations: [],
myObGroups: [],
myObjects: []
}
});
script.js
Q.all([LoadData.GetUsers($http), LoadData.GetObGroups($http), LoadData.GetObjects($http)]).then(function () {
console.log(_gl.myUsers);
console.log(_gl.myObGroups);
console.log(_gl.myObjects);
});
The problem is, the Q.all won't wait till all http request will get the data, it evaluates calls in then before it happens. Sure, I could use some timer and just wait for a second, but I want more proper way to do that, please share with your knowledge.
And one more thing, if I use forEach in then of my get methods then arrays filling all right, but other arrays are empty and I want to know why it happens.
Thank you.

You have to return the promises in GetUsers, GetObGroups and GetObjects, otherwise Q.all can't do its job.
Therefore, e.g.:
GetUsers: function ($http) {
return $http({
....
should do the trick.

Related

AngularJS : Render controller without waiting factory variable to be intialized first

First, I'm new to angularjs. I've create a factory to handle most of my data named "store". Here is an example:
app.factory('store', function ($rootScope, $http, $q, api) {
var data = {};
return {
setData: function () {
var deferred = $q.defer();
$http({
method: 'GET',
url: api.getData()
}).then(function successCallback(response) {
// handle data
$rootScope.broadcast('store:data', data);
deferred.resolve();
}, function errorCallback(reponse) {
// do stuff
deferred.reject();
});
},
getData: function () {
return data;
},
addData: function (newData) {
// do stuff
},
editData: function (newData) {
// do stuff
},
deleteData: function (newData) {
// do stuff
}
};
});
I'm initializing this data inside my app.run function. BUT, I don't want my app to wait my data to be initialized first to render the controller. I want it to be rendered first and wait for updating when the data is initialized.
store.setData()
.then(function (response) {
// do stuff
})
.catch(function (response) {
// do stuff
});
Here is how I'm getting the data updated inside my controller to be rendered
$scope.data = store.getData();
$rootScope.$on('store:data', function (event, data) {
$scope.data = data;
})
SO my problem is that I don't want to wait my data to be initialized to render my controller.
Is there a solution to this problem ?
Thanks a lot.
EDIT May 20 2021
Btw if what I'm doing is wrong and there is better things to do, I'm open to any suggestions ! Thnx
EDIT June 9 2021
Now I'm using $resource, but I don't know how can I get the new version of my list of data when I add new element to it.
agents: $resource(
api.getAgents(),
{},
{
get: {method: 'GET', isArray: false, cache: true},
add: {method: 'POST', url: api.addAgent(), hasBody: true},
edit: {method: 'PUT', url: api.editAgent(), params: {agentId: '#id'}, hasBody: true},
delete: {method: 'DELETE', url: api.deleteAgent(), params: {agentId: '#id'}},
}
),
Waiting for an answer. Thank you vm !
There are a couple options you can consider, but first a note on best practices in AngularJS and JavaScript: avoid the deferred antipattern. The $http service returns a promise. You should work with that rather than creating a new promise with $q.defer.
The first option is to change the getData method to return a promise instead of the actual data. It is a good idea to always design your data retrieval services to return promises, even when you intend to pre-retrieve and cache the data. This provides the cleanest way to ensure that the data is available before you try to use it. In your example, you should be able to internally cache the promise rather than the data. So your code would change to something like this:
app.factory('store', function ($rootScope, $http, api) {
var dataPromise;
return {
setData: function () {
dataPromise = $http({
method: 'GET',
url: api.getData()
}).then(function successCallback(response) {
// handle data
$rootScope.broadcast('store:data', data);
}, function errorCallback(reponse) {
// do stuff
});
},
getData: function () {
if (!dataPromise) {
this.setData();
}
return dataPromise;
},
// etc.
};
}
You will of course have to change the code that calls the getData method to work with the promise instead of working directly with the data.
Another option is to use an AngularJS Resource. This feature works very much like your original intent by returning an instance of an object that at some point will get populated with data. It takes advantage of the AngularJS change detection to render the data once it becomes available. Resources also have the ability to cache responses internally so that the call to the server is only made once. Rewriting your service as a resource would look something like this:
app.factory('store', function ($rootScope, $resource, api) {
return $resource(
api.getData(), // the base URL
{}, // parameter defaults
{ // actions
getData: {
method: 'GET',
cache: true
},
// etc.
}
);
}

angular enabling paging from a local json file

I am trying to set up a service that, when I feel like it, I can flip to live data coming from an API. The getData function takes skip/take parameters to define start record and number of records.
My data is currently in a json file:
{
"Data":[{
"Id":"1462",
"Name":"Cynthia"
},{
"Id":"1463",
"Name":"Bob"
},{
...
}],
"Total":71
}
My service currently pulls all json data at once:
var _getData = function (optionsData) {
return $http({
method: 'GET',
url: 'data/packages.json'
})
.then(function successCallback(response) {
return response;
},
function errorCallback(response) {
return response;
});
}
It seems to me that I have to write the paging logic right into the service:
.then(function successCallback(response) {
var records = response.data.Data;
var firstRecord = 0;//optionsData.skip;
var numberOfRecords = 1;//optionsData.take;
response.data.Data = records.slice(firstRecord, firstRecord + numberOfRecords);
return response;
},
Is this the right way in principle?
[ UPDATE ] The controller method:
var getPackageData = function (options){
return dataService.getData(options.data).then(
function successCallback(response) {
options.success(response.data.Data);
$scope.totalCount = response.data.Total;
},
function errorCallback(response) {
// handle error
}
);
};
my errorCallback is wrong? How?
The errorCallback is converting a rejected promise to a fulfilled promise.
To avoid conversion, either throw the response or return $q.reject:
var _getData = function (optionsData) {
return $http({
method: 'GET',
url: 'data/packages.json'
})
.then(function successCallback(response) {
return response;
},
function errorCallback(response) {
//To avoid converting reject to success
//DON'T
//return response;
//INSTEAD
throw response;
//OR
//return $q.reject(response);
});
}
Another common error is to fail to include either a throw or return statement. When a function omits a return statement, the function returns a value of undefined. In that case, the $q service will convert rejected promises to fulfilled promises which yield a value of undefined.

What is the proper way to use factory in controller

I want to write emphasis code only once. How can I achieve that? On submit, I want to perform to task
To save data in Database
Render the submitted data on UI
Code
.controller("myController", function ($scope, $http, getDataService) {
$scope.submit = function () {
var Employee = { "empID": $scope.empId, "Name": $scope.empName, "Gender": $scope.empGender }
$http(
{
method: 'POST',
url: 'http://localhost:58365/home/saveEmpData',
data: { emp: Employee },
headers: { 'Content-Type': 'application/JSON; charset=UTF-8' }
}).then(function (response) {
$scope.data123 = response.data;
});
**getDataService.fetchuserDetails().then(function (response)
{ $scope.data1 = response.data; });**
}
**getDataService.fetchuserDetails().then(function (response)
{ $scope.data1 = response.data; });**
})
Factory
.factory("getDataService", ['$http', function ($http) {
var obj = {};
obj.fetchuserDetails = function ()
{
return $http.get('http://localhost:58365/home/GetEmpData');
}
return obj;
}])
To Save data in database,
$scope.saveData = function (xyz) {
getDataService.save(xyz, function () {
// write action you want to perform after saving your data
})
};
Call saveData( ) method in HTML and pass the variable where you are getting data or the variable where you are going to store temporary data.
To render submitted data it's better to write another controller.
Then run .query() method or .get() and collect it in one $scope variable.

Get response data from Angular Factory using $resource

I try to get response data from angular service created by factory and $resource. I want to send POST request to server to create object in db and received created ID.
So I create service like this:
angular.module('sample')
.factory('Client', function ($resource) {
return $resource('api/clients/:id', {}, {
'query': { method: 'GET', isArray: true},
'get': {
method: 'GET',
transformResponse: function (data) {
data = angular.fromJson(data);
return data;
}
}
});
});
I use Client service in controller:
$scope.create = function () {
Client.save($scope.client,
function (response) {
$state.go("clientDetail", {'id' : whereIsMyId.id? })
});
};
Unfortunately, I do not know how to read data in response.
As response I just get number like "6" but this could be any JSON.
Thank you.

What is a proper way to write an interceptor for the resource?

I have a need to transform response from service on each get, save, update. I've created a resource and added a transformer that gets executed, but the structure of object being returned is not the same as when I don't use transformer. Here I am talking about the structure of the response, not the object I am transforming.
Here is my resource:
angular.module('app')
.factory('Insureds', ['$resource', 'config', function ($resource, config) {
function transform(response) {
var insured = response.data.insured;
return response;
}
var memberServicesHostName = config.memberServicesHostName;
return $resource(memberServicesHostName + '/insureds/:insuredId', null,
{
'get': {
method: 'GET', 'withCredentials': true, interceptor:
{
response: function (response) { return transform(response).data; }
}
},
'update': { method: 'PUT', 'withCredentials': true },
'save': { method: 'POST', 'withCredentials': true }
});
}]);
When I don't use transformer "insured" is on the first level when the promise gets resolved it resolves as an instance of insured object. But with transformer there is wrapper object, that contains insured and responseStatus properties. It probably has to do with what I am returning from the "reponse" in the interceptor. What should one return, original response, like I am doing, or response.data, or response.resource.insured? I am confused...
The default response interceptor is like this:
function defaultResponseInterceptor(response) {
return response.resource;
}
Therefore, if you would like to preserve the default behaviour, you have to return response.resource instead of response.data:
return $resource(memberServicesHostName + '/insureds/:insuredId', null, {
get: {
method: 'GET',
withCredentials: true,
interceptor: {
response: function (response) {
return transform(response).resource;
}
}
},
...
Hope this helps.

Resources