How to handle chained promises - angularjs

I'm a little bit confused how should I handle the promises in my situation.
This is my factory:
return {
getCategory: function(categoryId) {
var ref = firebase.database().ref().child('categories').child(categoryId);
var category = $firebaseObject(ref);
return category.$loaded().then(function() {
return category;
});
},
getEntry: function(categoryId, entryId) {
var ref = firebase.database().ref().child('entries').child(categoryId).child(entryId);
var entry = $firebaseObject(ref);
return entry.$loaded().then(function() {
return entry;
});
}
}
In my factory I try to avoid doing like this:
var d = $q.defer();
if() {
d.resolve();
}
return d.promise;
Because the $loaded() returns a promise itself.
And this is my controller:
var categoryId = 'abc';
var entryId = 'def';
// so here i'm getting the category
MyFactory.getCategory(categoryId)
.then(function(category) {
if(category.$value === null)
{
$scope.error = 'The category does not exist';
}
else if(new Date() > new Date(category.expireDate))
{
$scope.error = 'The category has expired';
}
else {
$scope.category = category;
// if no errors
return MyFactory.getEntry(category.$id, entryId);
}
})
.then(function(entry) {
if(entry.$value === null)
{
$scope.error = 'No such entry';
}
else {
$scope.entry = entry;
}
})
.catch(function(error) {
console.error(error);
});
What I want to achieve is to get the category first, and then whether there are some errors or not, get the entry respectively. The data is coming from a Firebase database.
This is kind of working, however I'm not really sure how should I handle the promise when I want to do a next .then and don't nest them one in the other like this:
MyFactory.getCategory().then(function(category) {
if(no category errors) {
MyFactory.getEntry().then(function() {
// ...
});
}
});
For now I'm getting an error in the console (it's type error entry undefined) when for example the category expired or does not exist.
I think I did something wrong in the controller when I return but I'm not really sure and hope you can help me dispel all doubts.
So the real question is how should I handle this correctly, to work as expected?
Thanks.

You should return a rejected promise when there is an error.
Look at the following example:
MyFactory
.getCategory(categoryId)
.then(function (category) {
if (category.$value === null) {
return $q.reject('The category does not exist');
}
if (new Date() > new Date(category.expireDate)) {
return $q.reject('The category has expired');
}
$scope.category = category;
return MyFactory.getEntry(category.$id, entryId);
})
.then(function (entry) {
if (entry.$value === null) {
return $q.reject('No such entry');
}
$scope.entry = entry;
})
.catch(function (error) {
$scope.error = error;
});
Do not forget to inject $q to your controller.
Edit
I would also suggest you move the "error logic" to your service, so the controller would always received either data in .then(data => { ... }) or an error string in .catch(error => { ... }). This would make your controllers cleaner and if you use those service method in a different controller, you would not have to replicate your logic there as well.
Service
return {
getCategory: function(categoryId) {
var ref = firebase.database().ref().child('categories').child(categoryId);
var category = $firebaseObject(ref);
return category.$loaded().then(function() {
if (category.$value === null) {
return $q.reject('The category does not exist');
}
if (new Date() > new Date(category.expireDate)) {
return $q.reject('The category has expired');
}
return category;
});
},
getEntry: function(categoryId, entryId) {
var ref = firebase.database().ref().child('entries').child(categoryId).child(entryId);
var entry = $firebaseObject(ref);
return entry.$loaded().then(function() {
if (entry.$value === null) {
return $q.reject('No such entry');
}
return entry;
});
}
}
Controller
MyFactory
.getCategory(categoryId)
.then(function (category) {
$scope.category = category;
return MyFactory.getEntry(category.$id, entryId);
})
.then(function (entry) {
$scope.entry = entry;
})
.catch(function (error) {
$scope.error = error;
});

Related

Convert promise in angular 1 to observable in angular 2

i'm still learning observable in angular 2, and has not been able to figure out how to convert my code in angular 1 to angular 2.
function promiseFunc() {
var deferred = $q.defer();
$http.post(url, something)
.then(function (response) {
if (response === 1) deferred.resolve(response.data);
else deferred.reject();
}).catch(function (e) {
deferred.reject(e);
});
return deferred.promise;
}
Can anyone tell me how i can convert this code with angular 2 observable?
EDIT:
And what if the http.post is optional?
function promiseFunc(param1) {
var deferred = $q.defer();
if (param1 === 1) {
deferred.resolve(1);
} else {
$http.post(url, something)
.then(function (response) {
if (response.x === 1) deferred.resolve(response);
else deferred.reject();
}).catch(function (e) {
deferred.reject(e);
});
}
return deferred.promise;
}
What i'm missing in observable is the ability to call resolve and reject. Can it be done in observable?
someMethod() {
if(param === 1) {
return Observable.of(1);
} else {
return this.http.post(url, something)
.map(
response => {
let data = response.json();
if(data === 1) {
return 1;
}
throw 'some error';
}
);
}
}
then use it like
this.someMethod().subscribe(
data => console.log(data),
error => console.log(error),
() => console.log('completed')
);

Nested $promise undefined error

I am getting the error Cannot read property of '$promise' of undefined.
Here is the code that is throwing it:
var myPromise = sharedDataService.getData($scope.entityId).$promise;
resolvePromise(myPromise, 'entityData');
the resolvePromise method:
function resolvePromise(promise, resultObject){
promise.then(function(response){
$scope[resultObject] = result;
});
promise['catch'](function(error){
//error callback
});
promise['finally'](function(){
//finally callback
});
sharedDataService looks like this:
var publicInterface = {
getData: getData
};
var storedData;
function getData(entityId) {
if(storedData.entityId === entityId){
return storedData;
}else{
var entityDataPromise = dataAccessService.getEntityData(entityId).$promise;
entityDataPromise.then(function (response) {
storedData = response;
return storedData ;
});
entityDataPromise['catch'](function(error) {
//throw error;
});
entityDataPromise['finally'](function(done) {
//do finally
});
}
}
return publicInterface;
finally, the dataAccessService:
var publicInterface = {
getEntityData: getEntityData
}
var entityData = $resource(apiUrl + 'Entity', {}, {
'getEntityData': {
method: 'GET',
url: apiUrl + 'Entity/getEntityDataById'
}
}
function getEntityData(entityId){
return entityData.getEntityData({entityId: entityId})
}
return publicInterface;
the original promise is throwing an error. When I put breakpoints various places, I can see my data is being returned sometimes. The functionality of sharedDataService is almost one of a chaching service.
Why is my original promise returning undefined?
Your getData() method doesn't have a return when if is false. So you would need to return entitiyDataPromise.
But, that would mean one condition returns a promise and the other returns an object
So both conditions need to return a promise and we can use $q for the first condition
function getData(entityId) {
if(storedData.entityId === entityId){
// return to getData()
return $q.resolve(storedData);
}else{
var entityDataPromise = dataAccessService.getEntityData(entityId).$promise;
// return to getData()
return entityDataPromise.then(function (response) {
storedData = response;
return storedData ;
});
entityDataPromise['catch'](function(error) {
//throw error;
});
entityDataPromise['finally'](function(done) {
//do finally
});
}
}
Be sure to inject $q in service.
In controller would be:
var myPromise = sharedDataService.getData($scope.entityId);
resolvePromise(myPromise, 'entityData');

AngularJS Multiple GET requests, only first returning correctly

I have the following in my controller:
ApiRequest.get('locations').then(function(locations) {
$scope.locations = locations.locations;
});
ApiRequest.get('sublocations').then(function(sublocations) {
$scope.sublocations = sublocations.sublocations;
});
ApiRequest.get('varieties').then(function (varieties) {
$scope.varieties = varieties.varieties;
});
ApiRequest.get('tasks').then(function(tasks) {
$scope.tasks = tasks.tasks;
});
ApiRequest.get('customers').then(function(customers) {
$scope.customers = customers.customers;
});
ApiRequest.get('batches').then(function(batches) {
$scope.batches = batches.batches;
$ionicLoading.hide();
});
The data from each of these requests goes on to poplate select boxes in a form.
Here is my APIRequest service:
return {
get: function(entity) {
if($rootScope.online == false) {
var data = {};
data = JSON.parse(localStorage.getItem('data-' + entity));
console.log(data);
deferred.resolve(data);
} else {
$http.get($rootScope.baseUrl + entity).success(function(data) {
deferred.resolve(data);
})
}
return deferred.promise;
},
}
It would appear that for some reason the results aren't getting back from the service on time to display them in the view.
Is this something to do with the way I am handling the promise?
At first look, you declared the promise with $q outside your function as global (because I don't see inside). Try this one:
get: function(entity) {
var deferred = $q.defer();
if($rootScope.online == false) {
var data = {};
data = JSON.parse(localStorage.getItem('data-' + entity));
console.log(data);
deferred.resolve(data);
} else {
$http.get($rootScope.baseUrl + entity).success(function(data) {
deferred.resolve(data);
})
}
return deferred.promise;
},
your current implementation has little to no error handling and is executing multiple API requests in parallel; I would recommend chaining the promises.
ApiRequest.get('locations').then(function(locations) {
$scope.locations = locations.locations;
return ApiRequest.get('sublocations');
}).then(function(sublocations) {
$scope.sublocations = sublocations.sublocations;
return ApiRequest.get('varieties')
}).then(function (varieties) {
$scope.varieties = varieties.varieties;
return ApiRequest.get('tasks')
}).then(function(tasks) {
$scope.tasks = tasks.tasks;
return ApiRequest.get('customers')
}).then(function(customers) {
$scope.customers = customers.customers;
return ApiRequest.get('batches')
}).then(function(batches) {
$scope.batches = batches.batches;
$ionicLoading.hide();
}, function(_error) {
$ionicLoading.hide();
console.log(_error);
});
and then your service can be simplified; the $http client returns a promise and using $q.when can return a promise also
get: function(entity) {
if($rootScope.online == false) {
var data = {};
data = JSON.parse(localStorage.getItem('data-' + entity));
console.log(data);
$q.when(data);
} else {
return $http.get($rootScope.baseUrl + entity)
}
},

How to update several nodes in firebase using then

I want to update several nodes in firebase since data is denormalized. So when updating each node have to wait until the previous updates is success.
So I have something below (what I currently use), you can see it is not readable if getting more nodes to update.
if(foo1) {
firebaseRef.update(data, function(error) {
if(error){
console.log(error);
} else {
firebaseRef.update(data, function(error){
if(error){
console.log(error);
}else{
//Update Another
};
});
};
});
};
So any idea how to use .then in angularFire or pure firebase js api something like this.
firebaseRef().then(function(){
return doSomething;
}).then(function(){
return doSomething;
}).then(function(){
return doSomething;
}).then(function(){
return doSomething;
});
There are no futures in Firebase or AngularFire, at present. So you would need to use a decorator/wrapper strategy.
jQuery:
function update(ref, data) {
$.Deferred(function(def) {
ref.update(data, function(err) {
if( err ) def.reject(err);
else def.resolve();
});
});
}
var fb = new Firebase(URL);
update(fb.child('path1'), 'foo')
.then(update.bind(null, fb.child('path2'), 'bar'))
.then(update.bind(null, fb.child('path3'), 'foobar'))
.done(function() { console.log('yay!'); });
Angular:
angular.service('update', function($q, $timeout) {
return function(ref, data) {
var def = $q.defer();
ref.update(data, function(err) {
if( err ) def.reject(err);
else def.resolve();
});
return def.promise;
}
});
angular.controller('ctrl', function(update) {
var fb = new Firebase(URL);
update(fb.child('path1'), 'foo')
.then(update.bind(null, fb.child('path2'), 'bar'))
.then(update.bind(null, fb.child('path3'), 'foobar'))
.done(function() { console.log('yay!'); });
});
Bind polyfill (rather handy with Futures): https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/bind#Compatibility
You could also pull this off without using .bind if you like this syntax better:
function updateHandler(ref, data) {
return function() {
$.Deferred(function(def) {
ref.update(data, function(err) {
if( err ) def.reject(err);
else def.resolve();
});
});
}
}
var fb = new Firebase(URL);
updateHandler(fb.child('path1'), 'foo')()
.then(updateHandler(fb.child('path2'), 'bar'))
.then(updateHandler(fb.child('path3'), 'foobar'))
.done(function() { console.log('yay!'); });

Angularjs async callback return undefined under $scope.$apply();

This is my factory code. The callback is async so i put it under $rootScope.safeApply().
Then I call console.log(authService.authUser) in my controller but it still return undefined when user logged in. But it is find if user not login and will show 'not login' in console. Any idea?
myapp.factory('authService', ['$rootScope', function($rootScope) {
var auth = {};
$rootScope.safeApply = function(fn) {
var phase = this.$root.$$phase;
if (phase == '$apply' || phase == '$digest') {
if(fn && (typeof(fn) === 'function')) {
fn();
}
} else {
this.$apply(fn);
}
};
auth.firebaseAuthClient = new FirebaseAuthClient(FIREBASEREF, function(error, user) {
$rootScope.safeApply(function() {
if (user) {
auth.authUser = user;
//auth.isLoggedIn = true;
} else if (error) {
auth.authError = error;
} else {
auth.not = 'not login';
//auth.isLoggedIn = false;
}
});
});
auth.login = function() {
this.firebaseAuthClient.login('facebook');
};
auth.logout = function() {
this.firebaseAuthClient.logout();
};
return auth;
}]);
UPDATED
auth.callback = function(error, user) {
if (user) {
deferred.resolve(user);
} else if (error) {
deferred.reject(error);
} else {
//deferred.reject('not login'); // there is no callback value here
}
return deferred.promise;
}
in controller
callback().then(function(response) {
$scope.isLoggedIn = true;
}, function(response) {
$scope.isLoggedIn = false //How can i set false here?
});
UPDATE 2
Now every thing work fine, I'm able to monitoring user login state. But still having a problem. Check the code below
authService.callback().then(function(success){
$rootScope.isLoggedIn = true; //If promise return success set isLoggedIn true
}, function(fail){
**//If user not login set isLoggedIn false;
//I have problem here because i'm not able to deferred.reject below**
$rootScope.isLoggedIn = false;
})
auth.callback = function(error, user) {
$timeout(function() {
if (user) {
deferred.resolve(user);
} else if (error) {
deferred.reject(error);
} else {
//If this line is added,
//.then() will not return anything not even undefined with no error,
//No mater user logged-in or not login.
//If I comment it out, everything will work fine but how can I
//set isLoggedIn = false?
deferred.reject();
}
}, 0);
return deferred.promise;
}
Wrap the outside service's deferred resolve in a $timeout block to let angular know when its resolved. This way when your controller runs then callback, it'll be in a $digest cycle.
See this fiddle as a working proof of concept: http://jsfiddle.net/Zmetser/rkJKt/
// in controller
authService.login().then(success, error);
// service
myapp.factory('authService', ['$q', '$timeout', function( $q, $timeout ) {
var auth = {},
deferred;
firebaseAuthClient = new FirebaseAuthClient(FIREBASEREF, afterAuth);
function afterAuth( error, user ) {
// Let angular know the deferred has been resolved.
$timeout(function () {
if (user) {
deferred.resolve(user);
} else if (error) {
deferred.reject(error);
} else {
deferred.reject(); // there is no callback value here
}
}, 0);
}
auth.login = function() {
deferred = $q.defer();
firebaseAuthClient.login('facebook');
return deferred.promise;
};
auth.logout = function() {
deferred = $q.defer();
firebaseAuthClient.logout();
return deferred.promise;
};
return auth;
}]);

Resources