Here is a service:
angular.module('core').factory('ServerErrorAlert', ['toaster',
function(toaster) {
return function(errorResponse) {
toaster.pop('error', 'Error', errorResponse);
if (deferred) {
deferred.reject(errorResponse);
}
}
}
]);
Here is how I call it :
function update(updatedTransaction, originalTransaction, updateLocal) {
var deferred = $q.defer();
updatedTransaction.$update(function() {
if (updateLocal) {angular.extend(originalTransaction, updatedTransaction);}
deferred.resolve(updateLocal ? originalTransaction : false);
} , ServerErrorAlert);
return deferred.promise;
};
The second update function that is calling ServerErrorAlert is also a service. I would like for it to return a promise so that it can be chained.
As you can see ServerErrorAlert is merely a convenience function. I do not define deferred in the service, instead I was hoping that it would recognize it. However, this is not the case, deferred is not recognized by the service.
I guess I am just confused because if I replace the ServerErrorAlert argument in $update with the return value of the service, namely the function definition, I am confident that it would recognize deferred. Why does it not recognize it once the method is extracted?
Basically, I want ServerErrorAlert to handle all server errors arising from $resource calls but I do not want to keep copy and pasting the function definition as the error callback. Instead I was hoping to just paste one word in there (for code reuse).
How can I accomplish what I am trying to accomplish?
Thanks in advance.
Simply return a rejected promise, eg
.factory('ServerErrorAlert', ['$q', 'toaster', function($q, toaster) {
return function(errorResponse) {
toaster.pop('error', 'Error', errorResponse);
return $q.reject(errorResponse);
}
}]);
I would also change the way you're resolving the resource promise. You don't really need to create your own deferred object. Try this instead...
return updatedTransaction.$update().then(function() {
if (updateLocal) { // assuming this is actually defined and in scope
angular.extend(originalTransaction, updatedTransaction);
return originalTransaction;
}
return false;
}, ServerErrorAlert);
Related
I have seen answers on StackOverflow where people suggest furnishing a callback function to an AngularJS service.
app.controller('tokenCtrl', function($scope, tokenService) {
tokenService.getTokens(function callbackFn(tokens) {
$scope.tokens = tokens;
});
});
app.factory('tokenService', function($http) {
var getTokens = function(callbackFn) {
$http.get('/api/tokens').then (function onFulfilled(response) {
callbackFn(response.data);
});
};
return {
getTokens: getTokens
};
});
This seems to me to be an Anti-Pattern. The $http service returns promises and having .then methods execute callback functions feels like an unhealthy inversion of control.
How does one re-factor code like this and how does one explain why the original way was not a good idea?
You should change it to
var getTokens = function() {
return $http.get('/api/tokens');
};
And, then in other module use
yourModule.getTokens()
.then(function(response) {
// handle it
});
As to why it's an anti-pattern, I'd say that, first, it doesn't allow you to further chain your success/fail handler methods. Second, it handles the control of processing the response from caller-module to called module (which might not be super-important here, but it still imposes same inversion of control). And finally, you add the concept of promises to your codebase, which might not be so easy to understand for some of the teammates, but then use promises as callbacks, so this really makes no sense.
The code can be re-factored as follows:
app.controller('tokenCtrl', function($scope, tokenService) {
tokenService.getTokens.then ( callbackFn(tokens) {
$scope.tokens = tokens;
});
});
app.factory('tokenService', function($http) {
var getTokens = function() {
//return promise
return $http.get('/api/tokens').then (function onFulfilled(response) {
//return tokens
return response.data;
}
);
};
return {
getTokens: getTokens
};
});
By having the service return a promise, and using the .then method of the promise, the same functionality is achieved with the following benefits:
The promise can be saved and used for chaining.
The promise can be saved and used to avoid repeating the same $http call.
Error information is retained and can be retrieved with the .catch method.
The promise can be forwarded to other clients.
I am pretty new to Angular and have problems with making a synchronous operation. I have resolved few issues which came my way with the angular controller, where I get the error 'Cannot call method then of undefined' thrown from the newController file.
angular.module('newApp.newController', ['angularSpinner', 'ui.bootstrap'])
.controller('newController', function($q, $scope, utilityFactory, $http) {
utilityFactory.getData().then(function(data) {
console.log("success");
console.log(data);
});
});
angular.module('newApp.utility', [])
.factory('utilityFactory', function($q, $http) {
var utils = {};
//This is a cordova plugin
var getLauncher = function() {
return window.plugin.launcher;
};
var success = function(data) {
console.log(device);
return device;
}
var fail = function(error) {
console.log("error", error);
};
utils.getData = function() {
/* Get the store number details initially before initalizing the application */
if (window.plugin) {
var launcher = getLauncher();
console.log("Fetching data from device");
//Cordova js is returning this method
return launcher.getDevice(success, fail);
}
};
return utils;
})
With the understanding that :
Launcher.prototype.getDevice = function(successCallback, failureCallback) {
exec(successCallback, failureCallback, KEY, 'getDevice', []);
}
, we know that window.plugin.launcher.getDevice() returns undefined, not a data object. Instead, it provides its response via its success/failure callbacks.
Therefore, to work with promises, window.plugin.launcher.getDevice() needs to be "promisified", involving the explicit creation of a new Promise() and its resolution/rejection by .getDevice's callbacks. (Simply wrapping in $q(...) is not the same thing, and won't work).
angular.module('newApp.utility', []).factory('utilityFactory', function($q, $http) {
return {
getDevice: function() {
return $q.defer(function(resolve, reject) {
window.plugin.launcher.getDevice(resolve, reject); // If this line throws for whatever reason, it will be automatically caught internally by Promise, and `reject(error)` will be called. Therefore you needn't explicitly fork for cases where `window.plugin` or `window.plugin.launcher` doesn't exist.
}).promise;
}
};
});
Calling from the controller should now work :
angular.module('newApp.newController', ['angularSpinner', 'ui.bootstrap']).controller('newController', function($q, $scope, utilityFactory, $http) {
return utilityFactory.getDevice().then(function(data) {
console.log(data);
}).catch(function(error) {
console.error(error);
});
});
return launcher.getDevice(success, fail);
this line is the issue, I would just wrap it with a promise:
return $q(launcher.getDevice.bind(launcher, success, fail));
Edit: also you need to take care of else condition, so code would be:
utils.getData = function() {
/* Get the store number details initially before initalizing the application */
if (window.plugin) {
var launcher = getLauncher();
console.log("Fetching data from device");
//Cordova js is returning this method
return $q(launcher.getDevice.bind(launcher, success, fail));
}
return $q.resolve(); // or $q.reject(reason);
};
1) Your actual Module should be "newApp", not "newApp.newController" and 'newApp.utility'. That is placing those two components into separate modules instead of in the myApp module.
2) You should only use the syntax of
angular.module('newApp', [])
whenever you are declaring a new module. When you want to access the module, you should use
angular.module('newApp')
https://docs.angularjs.org/api/ng/function/angular.module
3) Your utilityFactory is returning a variable 'device' that hasn't been declared anywhere
4) You can't use 'then' without returning a promise in your getData function. Then is a method that is implemented in Javascript promises, so you can't just use it anywhere in your code.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/then
utils.getData = function() {
var deferred = $q.defer();
if (window.plugin) {
var launcher = getLauncher();
console.log("Fetching data from device");
//Cordova js is returning this method
return launcher.getDevice(success, fail);
}
return deferred.promise;
};
Here is a codepen I used when debugging your code. I modified your code a little, but it will give an example of the function working when returning a promise. http://codepen.io/anon/pen/QNEEyx?editors=1010
As some of the other answers mentioned, you need to return a Promise from your utils.getData function. Angular's $q helper lets you do just that. However, the way that some of the other answers are showing you to do it goes against best practices. When using $q, best practice is to do the following:
var myPromise = $q(function (resolve, reject) {
// Do some logic in here that is asynchronous and either invoke resolve with
// the results or reject with an error that may have occurred
});
Therefore, your code becomes:
angular.module('newApp.utility', [])
.factory('utilityFactory', function($q, $http) {
var utils = {};
//This is a cordova plugin
var getLauncher = function() {
return window.plugin.launcher;
};
var success = function(data) {
console.log(device);
return device;
}
var fail = function(error) {
console.log("error", error);
};
utils.getData = function() {
/* Get the store number details initially before initalizing the application */
return $q(function (resolve, reject) {
if (!window.plugin) {
// You can handle this case as a rejection of the promise
reject(new Error('Window plugin not found'));
return;
}
var launcher = getLauncher();
console.log("Fetching data from device");
//Cordova js is returning this method
// When device is ready it will "resolve" the promise and
// invoke any of the ".then()" functions you add to the promise
// If an error occurs, it will invoke any ".catch()" functions
// that you have added.
launcher.getDevice(resolve, reject);
});
};
return utils;
})
For more information on the $q service, check this post out from the official AngularJS documentation:
https://docs.angularjs.org/api/ng/service/$q
Also, some resources if you want to learn more about promises and asynchronous programming in JavaScript:
Neat visualization tool for Promises - http://bevacqua.github.io/promisees/#
Tutorial on promises - https://www.toptal.com/javascript/javascript-promises
Another thing to look into as a general guide for AngularJS best practices is the angular style-guide by John Papa: https://github.com/johnpapa/angular-styleguide
Lastly, the way you have your modules setup is slightly off. Every call to angular.module(moduleName, dependencies) will create a new module with those dependencies. While it is a good idea to decompose your angular app into multiple modules, you need to make sure that your root or "main" app that is referenced using the ng-app directive has references to all of your sub modules and that any module that references dependencies from another module has that module included in its dependency list.
In your case, you create a module called newApp.newController, but as you have it, it will not work because it tries to reference utilityFactory, which is defined in a separate module called newApp.utility, but is not referenced by your newApp.newController module. To fix this, do the following:
angular.module('newApp.newController', ['angularSpinner', 'ui.bootstrap', 'newApp.utility'])
// Make sure to add 'newApp.utility' to the dependencies of the 'newApp.newController' module.
Alternatively, you can just create both the controller and the utility factory in the same module:
// Create the module once
angular.module('newApp', ['angularSpinner', 'ui.bootstrap']);
// Reference it by invoking it with just one parameter
angular.module('newApp').controller('newController', ...);
angular.module('newApp').factory('utilityFactory', ...);
Usage and best practices around the angular module system can be found here:
https://github.com/johnpapa/angular-styleguide/blob/master/a1/README.md#modules
I am new at AngularJs and very new at Typescript.
I included Typescript in my AngularJs project but couldn't handle a service where i return a $q(function(){...})
my code looks like:
function foo(request, monitor, currentMonitorPropertys) {
var currentChart;
return $q(function (resolve) {
$http(request).success(function (chartResponse) {
...
resolve(monitor);
}).error(function(response){
...
});
});
I work with VS2013(TypeScript), if i implement this method like above, there comes an compilererror: Value of type 'IQService' is not callable. Did you mean to include 'new'?
So how could I implement the function with Typescript.
Thank you for your answer.
There are several ways to return a promise... $http returns a promise with each of its ajax calls, $timeout also returns a promise.
That being said you want to return a promise based upon something other than a scheduled event ($timeout, $interval) via $q you can do this...
// assume $q is injected into your service/controller/factory
// create a defer object
var defer = $q.defer();
// do something...
if (doSomething()){
defer.resolve(); //something went right
else {
defer.reject(); //something went wrong
}
//make sure you return out the promise, so the consumer can act upon it.
return defer.promise;
Also, $q has some nice helper methods to return a promise that you can use when you stub out some logic;
// this will a promise that will resolve with the value provided
return $q.when({some: 'result'});
// this will return a promise that will reject with the error specified
return $q.reject('some error message');
$q isn't a function but a service. If you want a defer, you can use the following code for example:
var deferred = $q.defer();
$http(request).success(function (chartResponse) {
deferred.resolve(monitor);
}).error(function(response){
deferred.reject(response);
});
// return promise
return deferred.promise;
What you can keep in mind if you don't do anything else, you can just return the $http call, because it is a promise itself:
return $http(request);
As you're using $http (that already returns a promise) why not returning this promise directly? Simpler and faster.
function foo(request, monitor, currentMonitorPropertys) {
var currentChart;
return $http(request).then(function (chartResponse) {
//...
return monitor;
});
});
Then when consuming this service you could manage success and error from there, which makes for a tidier implementation:
// for ex. in a controller
foo().then(mySuccessCallback)
.catch(myErrorHandler)
You need to defer $q before resolve it This is code try this one
(function retriveDemoJsonData(){
angular.module('myApp').factory('actionData', function ($q, $http) {
var data={};
data.actionDataJson = function(id){
//The original business logic will apply based on URL Param ID
var defObj = $q.defer();
$http.get('demodata.json')
.then(function(res){
defObj.resolve(res.data[0]);
});
return defObj.promise;
}
return data;
});
})();
----------+
I hope this will help you......
I'm an ionic/Angular n00b and I having trouble wrapping my head around how to do this.
I have a factory defined as such:
angular.module('starter.services', [])
.factory('Calendars', function () {
var calendars;
var success = function(message) {
calendars = message;
return calendars;
};
var error = function(message) {alert("Error: " + message)};
window.plugins.calendar.listCalendars(success,error);
return {
all: function() {
return calendars;
},
get: function(calendarId) {
return calendars[calendarId];
}
}
});
And I'm trying to retrieve the calendars within my controller like this:
.controller('CalendarsCtrl', function($scope,Calendars) {
$scope.calendars = Calendars.all();
})
The factory method is being called but the results are not available until the 'success' callback is invoked so the CalendarsCtrl is always undefined.
How to solve this?
Edit - I've corrected the call within the controller. The same issue remains though, that the function does not return results until the success callback.
You will have to use a promise.
First add the dependency $q
.factory('Calendars', function ($q) {
then in all() you do this
all: function() {
var deferred = $q.defer();
window.plugins.calendar.listCalendars(function(data) {
deferred.resolve(data);
}
,function(error) {
deferred.reject(error); // something went wrong here
});
return deferred.promise;
now this will make the return after the data has been resolved (no more undefined).
One last thing, now when you get the data back at your controller you do this
var promise = Calendars.all();
promise.then(function(data) {
console.log('Success: you can use your calendar data now');
}, function(error) {
console.log('Failed for some reason:' + error);
});
You can read some more about promises here: https://docs.angularjs.org/api/ng/service/$q
I know it's hard to grasp the first time.
Angular factories are returning an object, in order to call their methods you must call them with Calendar.all() which will invoke the inner function.
I am new to angular, and I am having a tough time getting to the bottom of this problem.
I am writing a single-page application, and am working on the authentication portion. I have a service called "sessionService" that I want to be able to use throughout the app to determine if the user is logged in or not. It is simple if I do something like this:
...service('sessionService', function(...) {
/*...snip...*/
this.isLoggedIn = function() {
return this.authenticated;
};
});
Where "authenticated" is just private to the service. However, the falls apart if I refresh the page. So, my thought was to do something like this:
/*...snip...*/
this.isLoggedIn = function() {
var deferred = $q.defer()
, self = this
;
function handleLoggedInStatus(status) {
if (status) {
self.authenticated = true;
deferred.resolve();
}
else {
deferred.reject();
}
}
if (this.authenticated === null) {
$http.get('/user')
.success(function(response) {
handleLoggedInStatus(response.success);
});
}
else {
handleLoggedInStatus(this.authenticated);
}
return deferred.promise;
};
And then in my controller I would do something like this:
$scope.isLoggedIn = sessionService.isLoggedIn;
And in my template I would do:
...data-ng-show="isLoggedIn()"
However, doing that would result in the following error:
10 $digest() iterations reached. Aborting!
I tried a few different ways of referencing the sessionService.isLoggedIn function, such as:
$scope.isLoggedIn = sessionService.isLoggedIn();
$scope.isLoggedIn = sessionService.isLoggedIn.bind(sessionService)();
$scope.isLoggedIn = function() { return sessionService.isLoggedIn() }
But they either didn't work, or just gave me the same error.
Basically, I just want to be able to return a promise that will tell me whether or not the user is logged in. If we don't know if they are logged in (like after a page refresh), the promise will be resolved after an ajax request. If we do know already (like with normal navigation throughout the single page app) then the promise will be resolved immediately. I would then like to use that in my views so I can show/hide certain things, such as links to logout or view the account page.
What am I doing wrong?
You're resolving your promise, but not with a value--so the value of the promise on the $scope when resolved is undefined, which is falsy, thus your ng-show is not triggering.
It seems you're looking for something more like this:
In the service:
function handleLoggedInStatus(status) {
if (status) {
self.authenticated = true;
}
deferred.resolve(status); // always resolve, even if with falsy value
}
if (this.authenticated === null) {
$http.get('/user')
.success(function(response) {
handleLoggedInStatus(response.success);
})
.error(function(data) {
deferred.reject(data.errorMsg); // reject if there was an error
});
} else {
handleLoggedInStatus(this.authenticated);
}
In the controller:
$scope.loggedIn = sessionService.isLoggedIn();
In the HTML:
<div ng-show='loggedIn'>...</div>
Here is a JSFiddle demonstrating resolving the deferred with a truthy value and binding to the $scope.
Note that you can't bind the function itself to the scope
$scope.loggedIn = sessionService.isLoggedIn
and call the function in the view
<div ng-show="loggedIn()">...</div>
because the function returns a different promise each digest cycle (which is why you were getting the '10 digest cycles' error). You could, however, ensure that extra calls to sessionService.isLoggedIn returns the same promise instead of creating a new one, since you can call then on a promise multiple times (and in fact this is one of the benefits of promises):
deferred = null;
isLoggedIn: function() {
if (!deferred) {
deferred = $q.defer();
$http.get('/user')
.success(function(response) {
deferred.resolve(response.success); // resolve if true or false
})
.error(function(data) {
deferred.reject(data.errorMsg); // reject if there was an error
});
}
return deferred.promise;
}
You could then get rid of the this.authenticated boolean, as you do not need to keep track of a previously-logged-in user across function calls (since the promise does this for you).
However, while this gets rid of the digest cycle error, you still cannot call the function from the view--I suspect Angular is treating the return value (the promise itself) as a truthy value, rather than binding to the promise's resolved value. Here's an example of it not working; notice the div is displayed even though the promise is resolving with false.
To use deferred.reject to indicate the user was not authenticated, as in your original service, you'd want to do something more like this in the controller, though I believe that resolveing with false is cleaner:
sessionService.isLoggedIn()
.then(function() {
$scope.loggedIn = true; // resolved
}).then(function() {
$scope.loggedIn = false; // rejected
});