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')
);
Related
I understand what is The deferred antipattern, also mentioned as "The Forgotten Promise" HERE.
I also read: what-is-the-explicit-promise-construction-antipattern-and-how-do-i-avoid-it.
Anyways I should try to get rid of using $q.defer().
But I don't understand how to handle case where I got response and I need to reject it because it doesn't have proper key.
This is my method in Service that returns promise to controller.
self.findTime = function(data){
var deferred = $q.defer();
apiClient.findTime(data)
.then(function (response) {
if (response.result !== "success") {
deferred.reject(response);
}
deferred.resolve(response);
}
, function (error) {
deferred.reject(error);
console.error(error);
});
return deferred.promise;
};
So I tried to change it to:
self.findTime = function(data){
return apiClient.findTime(data)
.then(function (response) {
if (response.result !== "success") {
// return WHAT ??? <-------------
}
return response;
}
, function (error) {
// return WHAT ??? <-------------
console.error(error);
});
};
How to handle reject?
In controller, on reject I show some morning message.
Service.findTime(data).then(function (response) {
// do something
}, function (error) {
// show warn dialog
});
You have two options.
You can return a rejection promise from within your resolve:
return apiClient.findTime(data)
.then(function (response) {
if (response.result !== "success") {
return $q.reject('reason'); // Convert to rejection
}
return response;
}, function (error) {
console.error(error);
return $q.reject('reason'); // Chain rejection
});
or you can throw an exception:
return apiClient.findTime(data)
.then(function (response) {
if (response.result !== "success") {
throw new Error('reason');
}
return response;
}, function (error) {
console.error(error);
throw new Error(error);
});
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;
});
//the controller that creates the datatable
app.controller('AdminListCtrl', function ($scope, $compile, DTOptionsBuilder, DTColumnBuilder, adminService) {
var vm = this;
function stateChange(iColumn, bVisible) {
console.log('The column', iColumn, ' has changed its status to', bVisible);
}
//vm.dtOptions = DTOptionsBuilder.fromSource('http://localhost/api-v1/admin')
vm.dtOptions = DTOptionsBuilder.fromFnPromise(function() {
return adminService.loadAdmin();
})
.withPaginationType('full_numbers')
.withOption('createdRow', createdRow)
// Add Bootstrap compatibility
.withBootstrap()
// Active ColVis plugin
.withColVis()
// Add a state change function
.withColVisStateChange(stateChange)
// Exclude the last column from the list
.withColVisOption('aiExclude', [2])
// Add Table tools compatibility
.withTableTools('scripts/vendor/datatables/TableTools/swf/copy_csv_xls_pdf.swf')
.withTableToolsButtons([
'copy',
'print', {
'sExtends': 'collection',
'sButtonText': 'Save',
'aButtons': ['csv', 'xls', 'pdf']
}
]);
//adminService to request for all administrators
app.factory('adminService', ['ApiService', function (ApiService) {
return {
loadAdmin: function () {
ApiService.get("admin").then(function (response) {
if (response) {
if (response.success === true) {
return response;
}else{
console.log(response);
}
}else {
console.log('error request ');
}
});
}
};
}]);
//apiservice to interact with api
app.factory('ApiService', function ($http, $q, $localStorage) {
return {
get: function (apiresource) {
var returnData = $q.defer();
$http({
url: api + apiresource,
method: 'GET',
headers: {"Auth-Token": $localStorage.user_data.auth_token}
})
.success(function (data) {
returnData.resolve(data);
})
.error(function (error) {
returnData.resolve();
});
return returnData.promise;
}};
});`enter code here`
When ever I am in that view it throws this errorCannot read property 'then' of undefined. I am following examples from these two sources
http://www.revillweb.com/angularjs-by-example/4-sharing-data-with-angularjs-services/
http://l-lin.github.io/angular-datatables/#/withPromise
You need to return promise object (result of ApiService.get("admin") call) from loadAdmin method.
Also make sure you don't "swallow" rejections inside of the then (in console.log branches) - what happens when you unintentionally handle errors by not passing it further. For this return rejected promise or simply throw error, so that rejection will propagate further down the promise chain:
app.factory('adminService', ['ApiService', function (ApiService) {
return {
loadAdmin: function () {
return ApiService.get("admin").then(function (response) {
if (response) {
if (response.success === true) {
return response;
} else{
console.log(response);
throw response;
// or custom error object: throw {message: 'Error loadAdmin', response}
}
} else {
console.log('error request ');
throw new Error('error request');
}
});
}
};
}]);
I am having problem calling my $apartments service within this controller.
$scope.checkout = () => {
$scope.stripe_err = null;
if ($scope.subscription.type === '') {
$scope.stripe_err = 'No subscription selected';
return;
}
Stripe.card.createToken($scope.stripeToken, stripeResponse);
}
let stripeReponse = (status, response) => {
if (status !== 200) {
$scope.stripe_err = status;
return;
} else {
$stripe.checkout(token, response.id)
.then((result) => {
$scope.receipt = result.data.receipt / 100; // Stripe amount is in cents
$scope.totalPrice = '';
$scope.stripeToken = { number: '', cvc: '', exp_month: '', exp_year: '' };
})
.catch(err => console.error(err));
}
$apartments.postApt(token, $scope.userInfo); // The request finishes when my services is outside the .then()
};
but when I replace the parameters with the wrong one $apartments.postApt(result, $scope.userInfo);
then I get this error PUT http://localhost:7000/landlord-api/units/[object%20Object]?access_token=[object%20Object] 500 (Internal Server Error)
This is how my $apartments service looks like. I'm not sure if its my service because if I call it outside of $stripe.checkout() it will get invoke.
exports.$apartments = function ($http, $q) {
function postApt (landlord_id, newApt) {
var deferred = $q.defer();
$http.put('/landlord-api/units/' + landlord_id + '?access_token=' + landlord_id, newApt)
.then(function (result) {
return deferred.resolve(result);
})
.catch(err => deferred.reject(err));
return deferred.promise;
return {
postApt: postApt
}
}
UPDATE: I still haven't solved it. What I noticed is if I put my $apartments service in .then() the request just stays pending, but if I put it outside the .then() it finishes the request and I get the appropriate response.
Check your landlord_id, look at the response, you are sending an object:
access_token=[object%20Object]
Make a console.log of landlord_id so you get the structure.
A common posibility is that it is an array.
Try like this .
exports.$apartments = function ($http, $q) {
function postApt (landlord_id, newApt) {
var deferred = $q.defer();
angular.element(document.getElementById("YOUR_FORM_ID")).scope().getlandlord.then
(function (result)){
return deferred.resolve(result);
}).catch(err => deferred.reject(err));
return deferred.promise;
return {
postApt: postApt
}
}
scope.getlandlord(landlord_id,newApt){
return $http.post('/landlord-api/units/' + landlord_id + '?access_token=' + landlord_id, newApt).then(function(response) {
return response;
});
};
Well the problem probably exists here:
$stripe.checkout(token, response.id)
.then((result) => {
// your stuff
})
.then((result) => {
// your stuff
})
.catch((err) => { });
Remove the first .then() and see if you get the $result.data.success logic working in the second (now only) .then()
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)
}
},