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.
Related
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;
});
});
How do I resolve the $promise generated by apiResource inside getDB() before it's actually returned back to my controller? Right now I get undefined.
people.factory('uniqueContacts', ['apiResource', function(apiResource) {
return{
getDB: function () {
apiResource.query({api_resource:'people'}).$promise.then(function(response){
return response.data
});
}
}
}]);
$resolvedData = uniqueContacts.getDB();
console.log($resolvedData);
There are 2 things:
first your function needs to actually return something
second here you have an async process so you'll need a callback, you cannot simply store the return in a variable like that
Check that code instead:
people.factory('uniqueContacts', ['apiResource', function(apiResource) {
return {
getDB: function () {
return apiResource.query({api_resource:'people'}).$promise.then(function(response){
return response.data
});
}
};
}]);
uniqueContacts.getDB().then(function (data) {
console.log(data);
});
Probably something like that. I made async the function getDB.
people.factory('uniqueContacts', ['apiResource','$q', function(apiResource,$q) {
return{
getDB: function () {
var d = $q.defer
apiResource.query({api_resource:'people'}).then(function(response){
d.resolve(response);
},function(err){
d.reject(err);
});
}
}
}]);
uniqueContacts.getDB().then(function(response){
$resolvedData = response;
console.log($resolvedData);
});
If you want to wait for the async call to be finished, add a promise table and wait for $q.all like that.
var promises = [];
promise.push(uniqueContacts.getDB().then(function(response){
$resolvedData = response;
}));
$q.all(promises).then(function(){
console.log($resolvedData);
});
Is there a way to return an HttpPromise (or something similar) to mimic a call to $http? I want to set a global variable that indicates whether the real HTTP request is made or whether a fake HttpPromise object is returned with fake data.
For example, I have a service that is similar to this:
angular
.module('myservice')
.factory('MyService', ['$http', function($http) {
return {
get : function(itemId) {
if (isInTestingMode) {
// return a promise obj that returns success and fake data
}
return $http.get("/myapp/items/" + itemId);
}
};
} ]);
And in my controller, I have a call to the aforementioned service that looks similar to this:
// Somewhere in my controller
MyService.get($scope.itemId)
.success(function(data) {
$scope.item = data;
})
.error(function(data, status, headers, config) {
$scope.notFound = true;
});
I'm trying to not change the controller code; I want the success and error chaining to still work when in my "isInTestMode".
Is it possible to fake an HttpPromise in the way that I described in the service?
Below is a revised edition of the "MyService" above (a snippet) containing a success and error on the promise object. But, how do I execute the success method?
return {
get : function(itemId) {
if (isInTestingMode) {
var promise = $.defer().promise;
// Mimicking $http.get's success
promise.success = function(fn) {
promise.then(function() {
fn({ itemId : "123", name : "ItemName"}, 200, {}, {});
});
return promise;
};
// Mimicking $http.get's error
promise.error = function(fn) {
promise.then(null, function(response) {
fn("Error", 404, {}, {});
});
return promise;
};
return promise;
}
return $http.get("/myapp/items/" + itemId);
}
}
Just use the deferred method of the $qservice
var fakeHttpCall = function(isSuccessful) {
var deferred = $q.defer()
if (isSuccessful === true) {
deferred.resolve("Successfully resolved the fake $http call")
}
else {
deferred.reject("Oh no! Something went terribly wrong in your fake $http call")
}
return deferred.promise
}
And then you can call your function like an $http promise (you have to customize whatever you want to put inside of it, of course).
fakeHttpCall(true).then(
function (data) {
// success callback
console.log(data)
},
function (err) {
// error callback
console.log(err)
})
I found that this post is similar to what I was asking.
However, I wanted a way to mock my service call so that fake data could be returned instead of issuing a true HTTP request call. The best way to handle this situation, for me, is to use angular's $httpBackend service. For example, to bypass a GET request to my "items" resource BUT to not bypass GETs of my partials/templates I would do something like this:
angular
.module('myApp', ['ngMockE2E'])
.run(['$httpBackend', function($httpBackend) {
$httpBackend
.whenGET(/^partials\/.+/)
.passThrough();
$httpBackend
.whenGET(/^\/myapp\/items\/.+/)
.respond({itemId : "123", name : "ItemName"});
}]);
See this documentation for more information on $httpBackend.
I finally found a way using jasmin. $httpBackend was no option for me, as there were also non-$http-methods I needed mock on the same service. I also think that the controller test needing to specify the url is not perfect as imho the controller and its test should not need to know about it.
Here is how it works:
beforeEach(inject(function ($controller, $rootScope, $q) {
scope = $rootScope.$new();
mockSvc = {
someFn: function () {
},
someHttpFn: function () {
}
};
// use jasmin to fake $http promise response
spyOn(mockSvc, 'someHttpFn').and.callFake(function () {
return {
success: function (callback) {
callback({
// some fake response
});
},
then: function(callback) {
callback({
// some fake response, you probably would want that to be
// the same as for success
});
},
error: function(callback){
callback({
// some fake response
});
}
}
});
MyCtrl = $controller('MyCtrl', {
$scope: scope,
MyActualSvc: mockSvc
});
}));
You can implement your FakeHttp class:
var FakeHttp = function (promise) {
this.promise = promise;
this.onSuccess = function(){};
this.onError = function(){};
this.premise.then(this.onSuccess, this.onError);
};
FakeHttp.prototype.success = function (callback) {
this.onSuccess = callback;
/**You need this to avoid calling previous tasks**/
this.promise.$$state.pending = null;
this.promise.then(this.onSucess, this.onError);
return this;
};
FakeHttp.prototype.error = function (callback) {
this.onError = callback;
/**You need this to avoid calling previous tasks**/
this.promise.$$state.pending = null;
this.promise.then(this.onSuccess, this.onError);
return this;
};
Then in your code, you would return a new fakeHttp out of the promise.
if(testingMode){
return new FakeHttp(promise);
};
The promise must be asynchronous, otherwise it won't work. For that you can use $timeout.
easy peasy!
You can do it using angular-mocks-async like so:
var app = ng.module( 'mockApp', [
'ngMockE2E',
'ngMockE2EAsync'
]);
app.run( [ '$httpBackend', '$q', function( $httpBackend, $q ) {
$httpBackend.whenAsync(
'GET',
new RegExp( 'http://api.example.com/user/.+$' )
).respond( function( method, url, data, config ) {
var re = /.*\/user\/(\w+)/;
var userId = parseInt(url.replace(re, '$1'), 10);
var response = $q.defer();
setTimeout( function() {
var data = {
userId: userId
};
response.resolve( [ 200, "mock response", data ] );
}, 1000 );
return response.promise;
});
}]);
I'm using an Angular factory that retrieves data from a feed and does some data manipulation on it.
I'd like to block my app from rendering the first view until this data preparation is done. My understanding is that I need to use promises for this, and then in a controller use .then to call functions that can be run as soon as the promise resolves.
From looking at examples I'm finding it very difficult to implement a promise in my factory. Specifically I'm not sure where to put the defers and resolves. Could anyone weigh in on what would be the best way to implement one?
Here is my working factory without promise:
angular.module('MyApp.DataHandler', []) // So Modular, much name
.factory('DataHandler', function ($rootScope, $state, StorageHandler) {
var obj = {
InitData : function() {
StorageHandler.defaultConfig = {clientName:'test_feed'};
StorageHandler.prepData = function(data) {
var i = 0;
var maps = StorageHandler.dataMap;
i = data.line_up.length;
while(i--) {
// Do loads of string manipulations here
}
return data;
}
// Check for localdata
if(typeof StorageHandler.handle('localdata.favorites') == 'undefined') {
StorageHandler.handle('localdata.favorites',[]);
}
},
};
return obj;
});
Here's what I tried from looking at examples:
angular.module('MyApp.DataHandler', []) // So Modular, much name
.factory('DataHandler', function ($rootScope, $q, $state, StorageHandler) {
var obj = {
InitData : function() {
var d = $q.defer(); // Set defer
StorageHandler.defaultConfig = {clientName:'test_feed'};
StorageHandler.prepData = function(data) {
var i = 0;
var maps = StorageHandler.dataMap;
i = data.line_up.length;
while(i--) {
// Do loads of string manipulations here
}
return data;
}
// Check for localdata
if(typeof StorageHandler.handle('localdata.favorites') == 'undefined') {
StorageHandler.handle('localdata.favorites',[]);
}
return d.promise; // Return promise
},
};
return obj;
});
But nothing is shown in console when I use this in my controller:
DataHandler.InitData()
.then(function () {
// Successful
console.log('success');
},
function () {
// failure
console.log('failure');
})
.then(function () {
// Like a Finally Clause
console.log('done');
});
Any thoughts?
Like Florian mentioned. Your asynchronous call is not obvious in the code you've shown.
Here is the gist of what you want:
angular.module("myApp",[]).factory("myFactory",function($http,$q){
return {
//$http.get returns a promise.
//which is latched onto and chained in the controller
initData: function(){
return $http.get("myurl").then(function(response){
var data = response.data;
//Do All your things...
return data;
},function(err){
//do stuff with the error..
return $q.reject(err);
//OR throw err;
//as mentioned below returning a new rejected promise is a slight anti-pattern,
//However, a practical use case could be that it would suppress logging,
//and allow specific throw/logging control where the service is implemented (controller)
});
}
}
}).controller("myCtrl",function(myFactory,$scope){
myFactory.initData().then(function(data){
$scope.myData = data;
},function(err){
//error loudly
$scope.error = err.message
})['finally'](function(){
//done.
});
});
I am an angular newbie, I am building an application, one thing really puzzling me is there are couple of ways of defining a service, and I read more from this link: How to define service
then it seems there is no big difference among the ways of defining a service.
but I just noticed one difference which I think is different:
see this service I get from here http://jsfiddle.net/2by3X/5/
var app = angular.module('myApp', []);
app.service('test', function($timeout, $q) {
var self = this;
this.getSomething = function() {
return self.getData().then(function(data) {
return self.compactData(data);
});
};
this.getData = function() {
var deferred = $q.defer();
$timeout(function() {
deferred.resolve("foo");
}, 2000);
return deferred.promise;
};
this.compactData = function(data) {
var deferred = $q.defer();
console.log(data);
$timeout(function() {
deferred.resolve("bar");
}, 2000);
return deferred.promise;
};
});
if I define this service using "factory" like below, one function cannot call other functions of the service.
app.factory('test', function($timeout, $q) {
return {
getSomething : function() {
return getData().then(function(data) {
return compactData(data);
});
},
getData : function() {
var deferred = $q.defer();
$timeout(function() {
deferred.resolve("foo");
}, 2000);
return deferred.promise;
},
compactData : function(data) {
var deferred = $q.defer();
console.log(data);
$timeout(function() {
deferred.resolve("bar");
}, 2000);
return deferred.promise;
},
};
});
I will get this in the browser console:
[08:41:13.701] "Error: getData is not defined
.getSomething#http://fiddle.jshell.net/_display/:47
Ctrl1#http://fiddle.jshell.net/_display/:75
invoke#http://code.angularjs.org/1.0.0/angular-1.0.0.js:2795
instantiate#http://code.angularjs.org/1.0.0/angular-1.0.0.js:2805
Can anyone explain this? thanks.
you have two big problems there:
the factory returns an object with incorrect syntax.
javascript scope of variables is functional
That is,
You should be returning an object like {key: value, key: value}
values can be functions. however, you return {key = value, key = value}
First fix:
return {
getSomething : function() {...},
getData : function...
}
Secondly, not being able to call functions is normal. See this jsfiddle. I mocked everything. You can call one of the functions returned by the service. However, when from getSomething try to call getData, you get "undefined":
app.factory('testSO', function () {
return {
getSomething: function () {
console.log('return getsomething');
getData();
},
getData: function () {
console.log('return getData');
}...
This would be the same as declaring everything in the scope of the factory function and return references see in jsfiddle:
app.factory('testSO', function () {
var getSomething = function () {
console.log('return getsomething');
};
...
return {
getSomething: getSomething,
...
}
and now you can call local functions as shown in the final version of the jsfiddle:
app.factory('testSO', function () {
var getSomething = function () {
console.log('return getsomething');
getData();
};
...
The original service has something important in it: var self = this; . Some people use var that = this. It's a workaround for an error in ECMA.
In the case of the original code, it's used to "put everything in one object". Functions (properties that happen to be functions) in self need a reference to know where the function you want to call is. Try it yourself here http://jsfiddle.net/Z2MVt/7/