Access object from callback angular [duplicate] - javascript

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.

Related

How to simplify this with promises

I have the following javascript function :
function render(id) {
var deferred = $q.defer();
Flights.get(id).then(function(flightDto){
Arrivals.getDemoProfile(flightDto.id).then(function(arrivalDto) {
self.arivalId = arrivalDto.id;
deferred.resolve(self);
});
});
return deferred.promise;
}
Is there any way I can simplify better using promise so that the promise only resolves after the arrivals call is made? I am using angular and the built in $q library.
function render(id) {
return Flights.get(id).then(function(flightDto) {
return Arrivals.getDemoProfile(flightDto.id).then(function(arrivalDto) {
self.arivalId = arrivalDto.id;
return self;
});
});
}
Anything you return inside a then will be treated as the resolve of that promise.
Unless you return a promise, in which case that will be waited for, and the result will be treated as the resolve of that promise.
This means you can nest the then as deeply as you need and just keep returning from the nested functions.
The great thing about promises is that they can be chained instead of being nested. This makes the code a lot clearer and easier to reason (about which comes first for example). Following is the code from Buh Buh's answer, improved to chain the second promise instead of nesting:
function render(id) {
return Flights.get(id)
.then(function(flightDto) {
return Arrivals.getDemoProfile(flightDto.id);
})
.then(function(arrivalDto) {
self.arivalId = arrivalDto.id;
return self; // I AM NOT SURE ABOUT THE USEFULNESS OF THIS LINE...
})
;
}

TypeError: Cannot call method 'then' of undefined Angularjs

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

One-time resolving promise singleton (Angular service)

The question applies to promises in general and isn't specific to Angular, but the example makes use of Angular $q and service singletons.
Here is a plunker
var app = angular.module('app', []);
app.factory('onetimeResolvingService', function ($q) {
var promise = $q(function(resolve, reject) {
setTimeout(function () {
resolve();
}, 500);
});
return promise;
});
app.controller('AController', function (onetimeResolvingService, $q) {
onetimeResolvingService.then(function () {
console.log('A resolved');
return $q.reject();
}).then(function () {
console.log('A resolved');
});
});
app.controller('BController', function (onetimeResolvingService, $q) {
onetimeResolvingService.then(function () {
console.log('B resolved');
return $q.reject();
}).then(function () {
console.log('B resolved');
});
});
and the document is
<body ng-app="app">
<div ng-controller="AController"></div>
<div ng-controller="BController"></div>
</body>
It will naturally output
A resolved
B resolved
What would be a good pattern to make the singleton promise resolve only the first time, i.e.
A resolved
and not the subsequent times?
Something like onetimeResolvingService.$$state.status = 2 could possibly can do the trick, but it looks like $q hack and smells bad.
What would be a good pattern to make the singleton promise resolve only the first time
To not to. One of the key facets of a promise is that once it's settled, it's settled, and both the settled state (resolved or rejected) and the value are at that point unchanging. See §2.1.2 and §2.1.3 of the A+ promises spec:
2.1.2 When fulfilled, a promise:
2.1.2.1 must not transition to any other state.
2.1.2.2 must have a value, which must not change.
2.1.3 When rejected, a promise:
2.1.3.1 must not transition to any other state.
2.1.3.2 must have a reason, which must not change.
If the callbacks added via then are not satisfied at some stage (e.g., your second hookup), it's not a promise. It's something...else.
T.J. Crowder is correct in that the functionality you're looking for in a promise does not exist. The question of how to achieve what you're looking for however can be found in a structure like below:
function OnetimeValue($q) {
var promisedValue = $q(function(resolve, reject) {
setTimeout(function () {resolve('The one time value');}, 500);
});
var valueGot = false;
this.GetValue = function GetValue() {
var res;
if (!valueGot) {
res = promisedValue;
} else {
res = $q(function(resolve, reject) {
resolve(null);
});
}
valueGot = true;
return res;
};
}
Assuming you new this once (as angular services do), GetValue() will return the promisified string upon the first call. Subsequent calls return null.
This plunker shows the above in action
Edit: Whoops, misread the question.
There is now a way to do this with EcmaScript promises. The static Promise.resolve() method takes a promise and waits on its value; if it has already been resolved, it simply returns the value.
For example, here's how we're using this method to make multiple calls to fetchQuery rely on a single async authentication call:
fetchQuery gets an auth token (JWT) with:
const authToken = await AuthToken.get();
And AuthToken looks like (TypeScript):
class AuthToken {
private static _accessTokenPromise: Promise<string>;
public static async get() {
if (!this._accessTokenPromise)
this._accessTokenPromise = this.AuthFunction(); // AuthFunction returns a promise
return await Promise.resolve(this._accessTokenPromise);
}
}
Or simply call then() twice on the same promise object per the OP's question to have two calls wait on the same async operation.
If you're using lodash you can simply use memoize like this:
const returnFirstRunResultAlways = _.memoize(async function(params) { ... })

AngularJS how to use $q in Typescript

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......

service method does not recognize variable defined outside

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);

Categories

Resources