I have a newbie question here.
I am coding a factory in angularJS. With it I want to have a list of users, and also a method to fill it.
So this is my code ...
The factory
app.factory("usuariosFactory", function ($http) {
var f = {};
f.users = [];
f.getUsers = function (callback) {
var token = window.localStorage.getItem("_token");
$http.get("http://localhost:8000/api/user/list?token=" + token).then(function (response) {
f.users = response.data.users;
/* the console.log outputs OK with the users from the server */
console.log(f.users);
});
};
return f;
});
The controller
app.controller("usuariosController", function ($scope, usuariosFactory) {
var scope = this;
/* link users from factory to controllerÅ› scope .. NOT WORKING */
usuariosFactory.getUsers();
scope.usuarios = usuariosFactory.users;
});
I am hitting my head to the desk right now. I dont understand how to achieve this.
You should just return the promise from the factory to controller
Then in controller, you should subscribe to that promise and assign data to your scope variable
Factory:
app.factory("usuariosFactory", function ($http) {
var f = {};
f.users = [];
f.getUsers = function (callback) {
var token = window.localStorage.getItem("_token");
return $http.get("http://localhost:8000/api/user/list?token=" + token);
};
return f;
});
Controller:
app.controller("usuariosController", function ($scope, usuariosFactory) {
var scope = this;
usuariosFactory.getUsers().then(function (response) {
scope.usuarios = response.data;
});
});
The usuariosFactory.getUsers is an asynchronous function, due to $http.get inside. So, to have your data, you have to use the callback function that you've already put in the getUsers. The code should be like:
usuariosFactory.getUsers(function () {
scope.usuarios = usuariosFactory.users;
});
and after the f.users = response.data.users; you have to call the callback function. Like this:
f.getUsers = function (callback) {
var token = window.localStorage.getItem("_token");
$http.get("http://localhost:8000/api/user/list?token=" + token).then(function (response) {
f.users = response.data.users;
callback();
});
};
That way you will handle ansynchronous functions with a callback funtion. Another way to do this is with promises, in that way, your code should be like this:
The factory
app.factory("usuariosFactory", function ($http, $q) {
var f = {};
f.users = [];
f.getUsers = function (callback) {
var token = window.localStorage.getItem("_token");
var deferred = $q.defer(); // Creates the object that handles the promise
$http.get("http://localhost:8000/api/user/list?token=" + token)
.then(function (response) {
f.users = response.data.users;
deferred.resolve('You can pass data!'); // Informs that the asynchronous operation have finished
});
return deferred.promise; // Returns a promise that something will happen later
};
return f;
});
The controller
app.controller("usuariosController", function ($scope, usuariosFactory) {
var scope = this;
// Now you can use your function just like you use $http
// This way, you know that watever should happen in getUsers, will be avaible in the function
usuariosFactory.getUsers()
.then(function (data) {
console.log(data) // Print 'You can pass data!'
scope.usuarios = usuariosFactory.users;
});
});
Related
Using Angular 1.5.9 on frontend and WebAPI 2 on server. Calling a standard $http.get in the service to Get() method on controller. This is returning the ViewModel that I want to populate variables with in angular controller.
var carListController = function ($http, $scope, carService) {
var model = this;
model.carList = carService.getCarsByMake('Bentley', 10);
console.log(model.carList);
model.cars = model.carList[0].cars;
model.totalCars = model.carList[0].totalCars;
model.numberOfPages = model.carList[0].numberOfPages;
};
I get this error:
Cannot read property 'cars' of undefined
As you can see the console.log is showing the model.carList so I know issue is in controller code populating the other variables. What am I missing here? Any help appeciated.
Edit: carService
var cars = [];
var carService = function ($http) {
var getCarsByMake = function (make, size) {
$http.get('http://localhost:50604/api/cars?make=' + make + '&size=' + size)
.then(function (response) {
// Success
angular.copy(response.data, cars);
}, function () {
// Failure
});
return cars;
};
return {
getCarsByMake: getCarsByMake
};
};
You have to wrap your $scope variable population in a promise approach.
Since the model.carList data is not yet loaded when the population is happening, it's normal that the error arrises (Cannot read property 'cars' of undefined; meaning carList is undefined).
In your service carService.getCarsByMake you have to return a promise( $http.get method)
Only when the promise is resolved, you can populate your $scopevariables with this data.
var carListController = function ($http, $scope, carService) {
var model = this;
carService.getCarsByMake('Bentley', 10).then(function(response){
model.carList = response.data;
model.cars = model.carList.cars;
model.totalCars = model.carList.totalCars;
model.numberOfPages = model.carList.numberOfPages;
});
};
Return $http request on the service side :
var cars = [];
var carService = function ($http) {
var getCarsByMake = function (make, size) {
return $http.get('http://localhost:50604/api/cars?make=' + make + '&size=' + size);
};
return {
getCarsByMake: getCarsByMake
};
};
I do a ajax (get) request and obtain a promise with json data in a angular "jobList" service.
Then I update the scope with the obtained data. But my problem is that to update a scope variable 'X' I need to create a function per variable "readX" (see bellow).
Is there a way to add parameters, like in the last function in the following code?
app.controller("JobListController", ['$scope', '$timeout', 'jobList',
function ($scope, $timeout, jobList) {
var readList = function (response) {
if (response) {
$timeout(function () {
$scope.list = response;
$scope.$apply();
});
}
};
var readFamilies = function (response) {
if (response) {
$timeout(function () {
$scope.allFamilies = response;
$scope.$apply();
});
}
};
var readRegions = function (response) {
if (response) {
$timeout(function () {
$scope.allRegions = response;
$scope.$apply();
});
}
};
// !!! ----- HERE ------------- !!!
var readSomething = function (response, something) {
if (response) {
$timeout(function () {
$scope[something] = response;
$scope.$apply();
});
}
};
jobList.get().then(readList);
jobList.getAll("allFamilies").then(readFamilies);
jobList.getAll("allRegions").then(readRegions);
}]);
For a start you could simplify those callback functions: provided the callback happens inside angular (and it you're using $http it will) you don't need the $timeout call nor the $scope.$apply() call. Also you should write your service to only succeed if it returns data, reject the promise if it fail;s and that way you don't need the if So each callback could just be the assignment.
If you are making multiple calls that return promises, can you collapse the calls together?
$q.all([jobList.get(), jobList.getAll("allFamilies"), jobList.getAll("allRegions")])
.then(([list, families, regions]) => {
$scope.list = list;
$scope.allFamilies = families;
$scope.allRegions = regions;
});
I used es6 syntax here: it's well worth setting up your build chain to use something like babeljs so you can use the shorthand notation for simple callbacks.
If you really want to make the calls separately (they still evaluate in parallel) you can write a factory to generate the callbacks:
function assignToScope(name) {
return success;
function success(data) {
$scope[name] = data;
}
}
jobList.get().then(assignToScope('list'));
jobList.getAll("allFamilies").then(assignToScope('allFamilies'));
jobList.getAll("allRegions").then(assignToScope('allRegions'));
you could save the required property in a scope variable, before getting the data.
Something like this:
$scope.property = "list";
jobList.get().then(readSomething);
and your function would now become:
var readSomething = function (response) {
if (response) {
$timeout(function () {
$scope[$scope.property] = response;
$scope.$apply();
});
}
};
P.S:
I guess you can also use closures to do something like this:
var readSomething = function (something) {
return function(response){
if (response) {
$timeout(function () {
$scope[something] = response;
$scope.$apply();
});
}
}
};
Try this:
jobList.get().then(function (response) {
readSomething(response);
});
And function readSomething can have response only as in input.
This is the service where im saving the data and returning the result
nurseService.js
(function () {
'use strict';
angular.module('app.services')
.factory('NurseService', NurseService);
NurseService.$inject = ['$http', '$q','Constants'];
function NurseService($http, $q, Constants){
var service = {
saveSample:saveSample
};
return service;
function saveSample(data) {
var deferred = $q.defer();
$http({method:"POST", data:data, url:Constants.API_URL_SAVE_SAMPLE_COLLECATION}).then(function(result){
return deferred.resolve(result.data);
});
};
return deferred.promise;
}
})();
This is the controller where im using the return value and based on the value returned im calling another http get method and printing it.
vm.saveSamples = function() {
var data = {
visitId: visitId,
orders: vm.gridTestApi.selection.getSelectedRows()
};
var url = Constants.API_URL_SAVE_SAMPLE_COLLECATION;
var barCodeResponse = null;
var sampleId = "";
var myDataPromise = NurseService.saveSample(data);
myDataPromise.then(function(result) {
console.log("data.name"+ JSON.stringify(result));
vm.printBarCode(result.sampleId);
// if(sampleId != ""){
printElement("printThisElement");
// }
});
//Barcode method this should call after saving the data and returned the sampleId
vm.printBarCode = function(sampleId) {
$http.get("master/barcode/"+sampleId).then(function (response) {
vm.barCodeImage = angular.copy(response.data.result);
});
}
But here before the saving print is calling. How can I hadle so that the first call should finish before the second http call to barcode and print it
//Print code
function printElement(elem) {
var printSection = document.getElementById('printSection');
// if there is no printing section, create one
if (!printSection) {
printSection = document.createElement('div');
printSection.id = 'printSection';
document.body.appendChild(printSection);
}
var elemToPrint = document.getElementById(elem);
// clones the element you want to print
var domClone = elemToPrint.cloneNode(true);
printSection.innerHTML = '';
printSection.appendChild(domClone);
window.print();
window.onafterprint = function () {
printSection.innerHTML = '';
}
};
You have to return the $http call in printBarCode and use a .then like so:
//Barcode method this should call after saving the data and returned the sampleId
vm.printBarCode = function(sampleId) {
return $http.get("master/barcode/"+sampleId).then(function (response) {
vm.barCodeImage = response.data.result;
});
}
myDataPromise.then(function(result) {
console.log("data.name"+ JSON.stringify(result));
return vm.printBarCode(result.sampleId)
}).then(
function() {
printElement("printThisElement");
},
function(error) {
// error handler
}
);
printElement will now wait for the printBarCode promise and .then to fulfil before executing.
You also don't have to use a $q.defer when doing a $http call, $http is already a promise so you can just return that like so:
function saveSample(data) {
return $http({method:"POST", data:data, url:Constants.API_URL_SAVE_SAMPLE_COLLECATION})
.then(
function(result) {
return result.data;
},
function(error) {
// don't forget to handle errors
}
);
}
First of all, $http internally implements promises you you dont have to explicitly create them.
Secondly, you should put all your http requests in the service/factory
The modified code looks like
angular.module('app.services')
.factory('NurseService', function($http){
var service = {
saveSample : function(data){
//first http implementation here
return $http.post(....);
}
getBarcode : function(sampleId){
//http implementation here for barcode
return $http.get(....);
}
}
return service;
});
and your controller can use the service like
angular.module('app.services')
.controller('saveSampleCtrl',function($scope,NurseService){
var postData = {
//your post data here
}
NurseService.saveSample(postData)
.then(function(data){
//read sampleId here from data
var sampleId = data.sampleId;
NurseService.getBarcode(sampleId)
.then(function(){
//your print statement here
});
});
}
there might be typos in the code but this is just a basic idea on how you could do that. Hope it helps
I am returning an $http.get object from a service to a controller.
function in the geturl service -
this.fetch = function(index){
var url = some_url;
return $http.get(url,{timeout:8000});
};
In the controller I have --
var request = geturl.fetch(0);
request.success(function(data,status,headers,config){
// some logic
}).error(function(data,status,headers,config){
// some logic
});
$scope.promise.push(request); // $scope.promise is an array which contains all the promises
whenAll($scope.promise).done(function(){
});
function whenAll -
function whenAll(promises) {
var i, data = [],
dfd = $.Deferred();
for (i = 0; i < promises.length; i++) {
promises[i].done(function(newData) {
// something
}).fail(function() {
//something
});
}
return dfd.promise();
}
I am getting the following error -
TypeError: promises[i].done is not a function
If you want to execute something when all your promises resolve...
...you should take a look at $q.all()
Enjoy!
This is not the angular way I should say you must use $q service for it.:-
$q.all([Your array]).then(function(values) {
//Your code
});
Service:-
this.fetch = function(index){
var url = some_url;
var def = $q.defer();
$http.get(url,{timeout:8000}).success(function(data) {
def.resolve(data);
})
.error(function() {
def.reject("Failed to get albums");
});
return def.promise;;
};
Controller:-
var promises = [];
var promise1 = geturl.fetch(0);
var promise2 = geturl.fetch(1);
promises.push(promise1);
promises.push(promise2);
$q.all(promises).then(function(results){
});
Hope it help :)
I'm using &http in angular js to make REST calls. And when I make a new call, I want to dismiss the previous call. So I wrap the data call in the service and call the service in the controller. And I'm using the global parameter to save the last call object. So whenever I call the function getsth(), it will replace the lastcall with the new one. But when I debug, it did replace the lastcall with new one, but the previous then still triggers. One solution is the cancel the previous call and I tried it works. But my question is can I overwrite the $http object so that I don't have to handle it. Thanks
Controller:
var lastCall;
$scope.getsth = function(){
lastcall = service.datacall();
lastcall.then()
}
Service:
service.datacall = function(){
var promises = [];
promises.push($http({url:method...}).then(function))
return $q.all(promises);
}
This blogpost explains your use-case pretty well:
http://odetocode.com/blogs/scott/archive/2014/04/24/canceling-http-requests-in-angularjs.aspx
app.factory("movies", function($http, $q){
var getById = function(id){
var canceller = $q.defer();
var cancel = function(reason){
canceller.resolve(reason);
};
var promise =
$http.get("/api/movies/slow/" + id, { timeout: canceller.promise})
.then(function(response){
return response.data;
});
return {
promise: promise,
cancel: cancel
};
};
return {
getById: getById
};
});
app.controller("mainController", function($scope, movies) {
$scope.movies = [];
$scope.requests = [];
$scope.id = 1;
$scope.start = function(){
var request = movies.getById($scope.id++);
$scope.requests.push(request);
request.promise.then(function(movie){
$scope.movies.push(movie);
clearRequest(request);
}, function(reason){
console.log(reason);
});
};
$scope.cancel = function(request){
request.cancel("User cancelled");
clearRequest(request);
};
var clearRequest = function(request){
$scope.requests.splice($scope.requests.indexOf(request), 1);
};
});