I am trying to initialise a Angualr scope variable using a function that calls resource.query() from service and its returning empty.
Below is my code
Controller
$scope.categories = _processedCategories($scope.excludedCategories);
// excluded categories is an array of objects
function _processedCategories(excludedCategories) {
preDefinedCategoryService.rest.query({},
function (data) {
categories = _.filter(data._embedded.categories, function (n) {
return (n.id !== excludedCategories.id);
});
return categories
},
function () {
console.log ('error');
});
}
I am getting $scope.categories empty, but when I $scope.categories = _.filter(..) instead, it populate them directly. I know its something related to $promise but how to resolve it according to my requirement? Becase I want to reuse this function later aswell.
You can't return from asynchronous operation. Use Promise API to provide a callback to be invoked when data is loaded and processed:
_processedCategories($scope.excludedCategories).$promise.then(function(categories) {
$scope.categories = categories;
});
// excluded categories is an array of objects
function _processedCategories(excludedCategories) {
return preDefinedCategoryService.rest.query({}, function (data) {
return categories = _.filter(data._embedded.categories, function (n) {
return (n.id !== excludedCategories.id);
});
}, function () {
console.log('error');
});
}
Note, that _processedCategories now returns result of preDefinedCategoryService.rest.query function invocation, which is promise object.
Related
I've got a problem with filtering data from JSON file, which is an array of 20 objects.
in my factory I have these two functions.
function getData() {
return $http
.get('mock.json')
.success(_handleData)
.error(_handleError);
}
function _handleData(data) {
var filteredData = _filterData(data, "name", "XYZ");
console.log('filteredData', filteredData);
return filteredData;
}
and here console.log("filteredData") shows only filtered elements (i.e. 3 out of 20);
next - in a service I've got this one on ng-click:
var filterMe = function () {
DataFactory
.getData(_address)
.success(_handleServiceData );
}
where
var _handleServiceData = function (data) {
filtered = data;
};
the thing is - why the 'data' in _handleServiceData shows all of the elements instead of these previously filtered?
edit: here's the plunk - results are logged in console
Because the filteredData you return from _handleData function is not passed to the success callback you attach in filterMe function. That's because you attach that callback on the very same promise, since success function doesn't create new promise like the then method does. So to solve this modify your code like this:
function getData() {
return $http
.get('mock.json')
.then(_handleData, _handleError); //use "then" instead of "success"
}
Then in filterMe function:
var filterMe = function () {
DataFactory
.getData(_address)
.then(_handleServiceData );
}
Because promises are asynchronous, and you seem to return the value of filtered to your caller before it could be assigned.
You should be doing
function getData() {
return $http
.get('mock.json')
.then(_handleData); // then (for chaining), not success!
}
var filterMe = function () {
return DataFactory
// ^^^^^^ return a promise, not assign globals in async callbacks
.getData(_address)
.catch(_handleError); // I assume you want to deal with errors only in the end
}
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 saw following code in the HotTowel project. In the following code, callback method for then returns value return vm.messabeCount = data;
(function () {
'use strict';
function dashboard(common, datacontext) {
vm.messageCount = 0;
function getMessageCount() {
return datacontext.getMessageCount().then(function (data) {
/******* Here ********/
return vm.messageCount = data;
});
}
}
})();
Am wondering why & to whom it's returning value. Is it some standard practice? Can't the code be just.
return datacontext.getMessageCount().then(function (data) {
vm.messageCount = data;
});
Or
return datacontext.getMessageCount().then(function (data) {
vm.messageCount = data;
return;
});
getMessageCount is a function returning a promise object. then method of this promise returns another promise again. It makes possible to chain multiple then parts. Each then(function() { ... }) has an ability to modify a data to be passed to the next then invocation. So this construction:
return datacontext.getMessageCount().then(function(data) {
return vm.messageCount = data;
});
means to modify a data passed to resolve callbacks. Without this return success functions would be resolved with undefined value, while we need it to be resolved with data.
If I have the following functions:
doTask1: function ($scope) {
var defer = $q.defer();
$http.get('/abc')
.success(function (data) {
defer.resolve();
})
.error(function () {
defer.reject();
});
return defer.promise;
},
doTask2: function ($scope) {
var defer = $q.defer();
var x = 99;
return defer.promise;
},
I'm told that I can wait for both promises like this:
$q.all([
doTask1($scope),
doTask2($scope)
])
.then(function (results) {
});
How about if task 2 does not return a promise? I saw in the $q documentation
for AngularJS that there is a "when". However I am not clear on how to use it
and there's no example.
Is it the case that I MUST have doTask2 return a promise by having the two lines:
var defer = q.defer()
return defer.promise
or is there an easier way to do this?ll
$q.when is used in scenarios where you don't know upfront whether the function is returning a promise or a direct value.
The following example/plunker shows a method, whose result is used in $q.all, and which returns different type of object (int or promise) every time it's called:
PLUNKER
app.controller('MainController', function($scope, $q, $http) {
var count = 0;
function doTask1() {
var defer = $q.defer();
$http.get('abc.json')
.success(function (data) {
defer.resolve(data);
})
.error(function () {
defer.reject();
});
return defer.promise;
}
/**
* This method will return different type of object
* every time it's called. Just an example of an unknown method result.
**/
function doTask2() {
count++;
var x = 99;
if(count % 2){
console.log('Returning', x);
return x;
} else {
var defer = $q.defer();
defer.resolve(x);
console.log('Returning', defer.promise);
return defer.promise;
}
}
$scope.fetchData = function(){
// At this point we don't know if doTask2 is returning 99 or promise.
// Hence we wrap it in $q.when because $q.all expects
// all array members to be promises
$q.all([
$q.when(doTask1()),
$q.when(doTask2())
])
.then(function(results){
$scope.results = results;
});
};
});
<body ng-app="myApp" ng-controller='MainController'>
<button ng-click="fetchData()">Run</button>
<pre>{{results|json}}</pre>
</body>
is there an easier way to do this [than manually constructing and resolving a deferred and returning a promise]?
Yes, use the $q.when function:
doTask2: function ($scope) {
return $q.when( 99 );
},
However, you don't actually need to do this. $q.all will - though not stated in the docs - also work with non-promise values (implementation calls _ref which converts it). So just
return 99;
is fine as well. However, if you know beforehand that it's synchronous, using promises doesn't seem to make much sense.
So we all know that 'this' is a tricky keyword in JavaScript, and anonymous functions and AngularJS promises make it even trickier.
QUESTION (TL&DR Version)
What is the right (and angular) way to allow promise callbacks
to use the same "this" as the service that initiated the request?
See this fiddle for an example:
http://jsfiddle.net/tpeiffer/CFD3e/
All of these methods on the controller calls off to the Tier1Service. Tier1Service then calls off to the WorkerService to get data. When the data is loaded, it returns said data via a promise to the Tier1Service. The data returned gets set into the Tier1Service_data property.
Alternate 3 is clean and it works, but it feels like there has to be a better way.
Alternate 4 is also very clean and it works, but again it seems wrong.
Now what I would REALLY like is for $q's promise to do all of this for me. :)
Here is the relevant code:
// App.js
angular.constructor.prototype.call = function (scope, func) {
return function () {
func.apply(scope, arguments);
};
};
// Tier1Service
get coolData() {
return this._data;
},
set coolData(val) {
this._data = val;
},
doWorkAlt1: function () {
mySubWorkerService.someData.then(function (data) {
// FAILS because 'this' is the window,
// not the service
if (data) this._data = data;
});
},
doWorkAlt2: function () {
mySubWorkerService.someData.then((function (data) {
// FAILS because data is undefined because
// the function is wrapped in an anonymous
// function
if (data) this._data = data;
}).call(this));
},
doWorkAlt3: function () {
// WORKS because I keep track of the instance
var instance = this;
mySubWorkerService.someData.then(function (data) {
if (data) instance._data = data;
});
},
doWorkAlt4: function () {
// WORKS because I keep pass 'this' around
mySubWorkerService.someData.then(angular.call(this, function (data) {
if (data) this._data = data;
}));
}
// WorkerService
get someData() {
var deferred = $q.defer();
deferred.resolve('i got back data!!');
return deferred.promise;
}
You should be able to use Function.bind to achieve the result you want:
doWork: function () {
mySubWorkerService.someData.then((function(data) {
//this now refers to whatever it referred to in the doWork function
}).bind(this));
}
Do note however that bind is not available in older browsers. However it's very easy to patch it in to the prototype manually if necessary.