Access parameter of function in promise in AngularJS - angularjs

I have an AngularJS service that contains the function getServiceData:
var DataService = (function () {
function DataService($log, $http, config) {
this.$log = $log;
this.$http = $http;
this.config = config;
}
DataService.prototype.getServiceData = function (fullUrl, qsData, rootNode) {
var _this = this;
if (qsData === null || typeof qsData === "undefined") {
qsData = null;
}
return this.$http.jsonp(fullUrl, { params: qsData }).then(function (response) {
var data = rootNode === null ? response.data : eval("response.data." + rootNode);
return data;
});
};
return DataService;
})();
JSON data is returned, but if I pass a rootNode argument, in the "then" code it is always null. Any idea on how I can access the rootNode argument within the "then" code?

$http return promise, from AngularJs Promise Api
then(successCallback, errorCallback, notifyCallback) – regardless of when the promise was or will be resolved or rejected, then calls one of the success or error callbacks asynchronously as soon as the result is available. The callbacks are called with a single argument: the result or rejection reason.
then function take three callback function not any valued parameter and it is called after your ajax request completed. So if you want to access any variable or object within then, you need to assign outer scope of then what you already did in comment.
You should use angular eval method or #Khanh TO mentioned way instead of JavaScript eval.

The scope of eval is unreliable across browsers, that's the problem.
JavaScript Closure - Eval() and capturing variables in Eval()'s scope
Using eval is not recommended. In your case, you should use bracket notation syntax instead:
var data = rootNode === null ? response.data : response.data[rootNode];

Related

AngularJS : How to initialize service with async data and inject it into depend service

I have a similar situation like in AngularJS : Initialize service with asynchronous data, but I need to inject my base service with asynchronous data into depend services. It looks like this:
Base service:
angular.module('my.app').factory('baseService', baseService);
baseService.$inject = ['$http'];
function baseService($http) {
var myData = null;
var promise = $http.get('api/getMyData').success(function (data) {
myData = data;
});
return {
promise: promise,
getData: function() {
return myData;
}};
}
Dependent service (in which I inject base service)
angular.module('my.app').factory('depentService', depentService);
depentService.$inject = ['baseService'];
function depentService(baseService) {
var myData = baseService.getData();
....
return {...};
}
Route:
angular.module('my.app', ["ngRoute"])
.config(function ($routeProvider) {
$routeProvider.when('/',
{
resolve: {
'baseService': function (baseService) {
return baseService.promise;
}
}
});
});
But nothing happens, baseService.getData() still returns null (coz async api call still in progress). Seems like my ngRoute config is invalid, but I cant indicate any controller/template into it.
How can I correctly resolve my baseService and get data in Depend service?
Dependent services should work with promises and return promises:
angular.module('my.app').factory('depentService', depentService);
depentService.$inject = ['baseService'];
function depentService(baseService) {
̶v̶a̶r̶ ̶m̶y̶D̶a̶t̶a̶ ̶=̶ ̶b̶a̶s̶e̶S̶e̶r̶v̶i̶c̶e̶.̶g̶e̶t̶D̶a̶t̶a̶(̶)̶;̶
var promise = baseService.promise;
var newPromise = promise.then(function(response) {
var data = response.data;
//...
var newData //...
return newData;
});
return newPromise;
}
To attempt to use raw data risks race conditions where the dependent service operates before the primary data returns from the server.
From the Docs:
The .then method returns a new promise which is resolved or rejected via the return value of the successCallback, errorCallback (unless that value is a promise, in which case it is resolved with the value which is resolved in that promise using promise chaining).
— AngularJS $q Service API Reference - The Promise API

How do I make a promise resolve as an object in the view?

I'm trying to wrap a third party library to return an object that resolves into an object that can be displayed in the view, similar to how $resource() works. I'm aware that I can manually do .then() on the promise and then set the value, but I wanted the result to seamlessly return similar to how I can do:
this.Value = $resource("/someresource").get();
How would I change the below SomeThirdPartyFunction() to return an object that resolves in the view.
Here's an example of what I'm trying to do:
angular.module('testApp', []).controller('TestController', function ($timeout, $q) {
var TestController = this;
var SomeThirdPartyFunction = function () {
var Deferred = $q.defer();
var Promise = Deferred.promise;
$timeout(function () {
Deferred.resolve("abcd");
}, 3000);
return Promise;
};
TestController.Value = SomeThirdPartyFunction();
/* I don't want to do this:
SomeThirdPartyFunction().then(function(Value) {
TestController.Value = Value;
});*/
});
And here's a plunker: https://plnkr.co/edit/HypQMkaqXmFZkvYZXFXf?p=preview
Every example I've seen using promises just wraps $http calls, but I haven't seen any examples of calling third party libraries that return promises that resolve into objects.
From the AngularJS document:
It is important to realize that invoking a $resource object method
immediately returns an empty reference (object or array depending on
isArray). Once the data is returned from the server the existing
reference is populated with the actual data.
So instead of return a promise, you can do something like this:
var SomeThirdPartyFunction = function() {
var getAComplextObject = function() {
return {
number: 42,
method: function() {
return this.number + 1;
}
};
};
var returnValue = {};
$timeout(function() {
console.log("Resolved!");
Object.assign(returnValue, getAComplextObject());
}, 1000);
return returnValue;
};
You can wrap it in a promise and make the promise part of the return value, doing that you can make it thenable (aka a Promise)
Under-the-hood, $resource uses angular.copy:
function myResourceGet(params) {
var emptyObj = {};
emptyObj.$resolved = false;
emptyObj.$promise = $http.get(url, {params:params})
.then(function(response) {
angular.copy(response.data, emptyObj);
}).finally(function() {
emptyObj.$resolved = true;
});
return emptyObj;
}
From the Docs:
angular.copy Overview
Creates a deep copy of source, which should be an object or an array.
If a destination is provided, all of its elements (for arrays) or properties (for objects) are deleted and then all elements/properties from the source are copied to it.
The $resource service only works when the data is an object or an array. It does not work with primitives such as a string or number.

Using the service data in the controller

I have placed the service and the controller in the same js file. So Im trying to fetch the data from the service and use it in my html. In my code Im able to generate the data from the service but not able to assign it to a $scope in the controller and use it in the html. So how do I get the data and assign it to the $scope so that I can use it in my html.
var app = angular.module("app",[]);
app.factory('factoryServices',function($http){
var newObject = {};
var _getChart= function(){
$http.get("http://citibikenyc.com/stations/json")
.success(function(data, status){
if(data) {
return data;
}
}).error(function(data,status){
return error;
});
}
newObject.getChart = _getChart;
return newObject;
});
app.controller("chartController",function($scope,$http,factoryServices){
factoryServices.getChart($scope.chartServicesCompleted);
$scope.chartServicesCompleted = function(data){
$scope.serviceResponse = data;
}
})
If you rewrite your code like this, it should work as expected:
var app = angular.module("app",[]);
app.factory('factoryServices',function($http){
var newObject = {};
var _getChart= function(){
return $http.get("http://citibikenyc.com/stations/json")
.then(function(response){
if(response.data) {
return response.data;
}
}, function(response){
console.error("getChart failed with ",response);
});
}
newObject.getChart = _getChart;
return newObject;
});
and your controller
app.controller("chartController",function($scope,$http,factoryServices){
factoryServices.getChart().then(chartServicesCompleted);
function chartServicesCompleted(data){
$scope.serviceResponse = data;
}
})
The reason your initial code doesn't work, is because your getChart doesn't actually take an argument. So passing your callback like this: getChart($scope.chartServicesCompleted) doesn't do anything. In the rewritten code, I've made it so the getChart function returns the promise created by $http.get(..) which then allows you to use .then([callback]) in your controller.
you are passing a callback function but not handling inside the service method.
do change as
var _getChart= function(callback){
$http.get("http://citibikenyc.com/stations/json")
.success(function(data, status){
callback(data);
}).error(function(data,status){
callback(data);
});
}
now factoryServices.getChart($scope.chartServicesCompleted); will work
you can make more generic by handling success and error callback separately.
or one more way is to implement the success and error logic inside your controller.
but do not forget to check the function type i.e
if(typeof callback == 'function'){
callback(data);
}
Edit: as per advanced you call implement promises.
if you are using angular version 1.6, the success and error methods have been depreciated.
secondly you can do inside service return the http object
var _getChart= function(){
return $http.get("http://citibikenyc.com/stations/json");
}
and then handle the promise in the controller like
factoryServices.getChart().then(successMethod, error method);
Let the service return the promise to the controller. ex:
var _getChart= function(){
return $http.get("http://citibikenyc.com/stations/json");
}
and in the controller handle the promise. Use 'then' instead
factoryServices.getChart().then(function(response){var theDate = response.data},function(error){});
you can declare methods instead in the controller for handling the success and error
factoryServices.getChart).then(onSuccess,onError);
Don use the .success method and .error method. They dont't behave as other promises. So get used to 'then'
You really don't need to handle errors in the service method.I use angular interceptors in most of the cases. Check em out. But sometimes you need to handle the error in the controller. So its good to get the callback in the controller

Data in callback when using 'Controller as' pattern in Angular

I have the simplest angular controller:
tc.controller('PurchaseCtrl', function () {
var purchase = this;
purchase.heading = 'Premium Features';
this.onSuccess = function (response) {
console.log('Success', response);
lib.alert('success', 'Success: ', 'Loaded list of available products...');
purchase.productList = response;
};
this.onFail = function (response) {
console.log('Failure', response);
};
console.log('google.payments.inapp.getSkuDetails');
lib.alert('info', 'Working: ', 'Retreiving list of available products...');
google.payments.inapp.getSkuDetails(
{
'parameters': {'env': 'prod'},
'success': purchase.onSuccess,
'failure': purchase.onFail
});
});
And the view:
<div class="col-md-6 main" ng-controller="PurchaseCtrl as purchase">
{{purchase}}
</div>
This prints out:
{"heading":"Premium Features"}
I thought that when the callback returned, the view would be update with any new data. Am I missing something? The callback returns and I see the dtaa in the console.
Using the $scope pattern I think that I would use $scope.$apply to async method, but I'm not sure how to do that here.
Using controllerAs does not change the way digest cycle works or anything. It is just a sugar that adds a property (with the name same as alias name when used) to the current scope with its value pointing to the controller instance reference. So you would need to manually invoke the digest cycle (using scope.$apply[Asyc]() or even with a dummy $timeout(angular.noop,0) or $q.when() etc) in this case as well. But you can avoid injecting scope by abstracting it out into an angular service and returning a promise from there, i.e
myService.$inject = ['$q'];
function myService($q){
//pass data and use it where needed
this.getSkuDetails = function(data){
//create deferred object
var defer = $q.defer();
//You can even place this the global variable `google` in a
//constant or something an inject it for more clean code and testability.
google.payments.inapp.getSkuDetails({
'parameters': {'env': 'prod'},
'success': function success(response){
defer.resolve(response);// resolve with value
},
'failure': function error(response){
defer.reject(response); //reject with value
}
});
//return promise
return defer.promise;
}
}
//Register service as service
Now inject myService in your controller and consume it as:
myService.getSkuDetails(data).then(function(response){
purchase.productList = response;
}).catch(function(error){
//handle Error
});

Error: [$injector:undef] is occurring in service angularjs

Am getting an error Error: [$injector:undef] when am using service and http. I couldn't find why is it coming after changing all the changes that has been suggested for this particular error. Kindly check the below code
mainApp.controller('deviceDataController',["$scope","mainService", function ($scope,mainService) {
console.log(mainService);
mainService.deviceDataVar().success(function (data) {
// $scope.deviceDetails = mainService.devices;
console.log(data);
});
}]);
mainApp.factory("mainService",["$http", function ($http) {
angular.forEach(deviceIds, function(value, key) {
var timezone = user_response.timezone;
var DataUrl = LAX_API + "device_info.php?deviceid=" + value + "&limit=288&timezone=" + timezone;
return {
deviceDataVar: function () {
return $http.get(DataUrl).success(function (response) {
devices = response.data;
return devices;
}).error(function (data, status, headers, config) {
// log error
console.log(data)
});;
}
}
});
}]);
kindly help me out with my issue
Thanks!!
Your factory declaration is not valid.
Your factory should return only single method
Create a method that returns $http promises
Agregate all promises inside $q, that will wait for all of them to return a response
Agregate all responses
Return them inside a promise- you cannot return a value, because AJAX calls are async.
Your factory should look like this:
mainApp.factory("mainService", function ($http, $q) {
/* generate single link for deviceID */
function getDevice(value){
var timezone = user_response.timezone;
var dataUrl= LAX_API + "device_info.php?deviceid=" + value + "&limit=288&timezone=" + timezone;
return $http.get(dataUrl);
}
function deviceDataVar(){
// map method will transform list of deviceIds into a list of $http promises
var allRequests = deviceIds.map(getDevice);
// this promise will wait for all calls to end, then return a list of their results
$q.all(allRequests).then(function(arrayOfResults) {
// here you will have all responses, you just need to agregate them into single collection.
// secondly you will need to wrap them into a promise and return
return ...
});
}
/* Return only public methods, at the end */
return {
deviceDataVar: deviceDataVar
}
});
(I was writing this on fly, so there could be few mistakes :), but the conception is right though )
Useful links:
Aggregating promises: angular -- accessing data of multiple http calls - how to resolve the promises
Array.prototype.map: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map
How to apss a promise to a factory: How can I pass back a promise from a service in AngularJS?
UPDATE:
To make it work by invokiing small steps you should :
Mock the factory method, let it return a hardcoded value. Return it through a promise.
replace hardcoded value with single (hardcoded) URL to a selected device.
use aggregation ($q.all) for this single $http call.
replace single $http with array made from deviceIds list.

Resources