I have a ionic1 application which works with my API endpoints.
I've created a service which on call gets categories.
.factory('expenses', function($http) {
var expenseCategories = [];
return {
getCategories: function(){
return $http.get('URL_HERE').then(function(response){
expenseCategories = response.data.data[0];
return expenseCategories.categories;
});
}
}
})
I call the service in my controller
.controller('expenseCategoryCtrl', function($scope, $state, expenses) {
$scope.categories = expenses;
$scope.data = $scope.categories.getCategories();
console.log($scope.data);
})
When console logging the response it returns a promise rather than my usual json from the API. If I call the API in the controller I can ng-repeat the collection with no issues however now it's in a service it's wrapped in this "promise" and I'm unsure how to output the collection.
I've tried
<li class="item item-toggle" ng-repeat="categories as category">
{{ category }}
</li>
but I get the error
[ngRepeat:iexp] Expected expression in form of 'item in
collection[ track by id]' but got 'categories as category'.
I believe this is a case of not understanding the promise.
hope this will work:
.controller('expenseCategoryCtrl', function($scope, $state, expenses) {
$scope.categories = expenses;
$scope.data = $scope.categories.getCategories().then(function(response){
console.log(response);
},function(error){
console.log(error);
});
console.log($scope.data);
})
Async operation doesn't work the way you're thinking. You will get data when promise accomplished. So on getCategories method you could put .then function and it will call successFn one promise fulfilled otherwise on error it calls errorFn.
$scope.categories.getCategories().then(function successFn(data){
$scope.data = data
}, function errorFn(error){ console.log(error) });
In console what you were getting was promise object returned by $http service as you directly assigned promise to $scope.data.
Related
I am using Angular 1.5.
I can't access my data, from the http.get, out the http.get.
Let me explain:
I have my component:
(function(){
'use strict';
class myComponent {
constructor(
$http,
$scope)
{
var self = this;
self.test="this is a test";
$http.get(MYAPI).then(function(response){
self.MYDATA = response.data;
console.log(self.MYDATA)
});
console.log(self.test)
console.log(self.MYDATA)
}
}
angular.module('myApp')
.component('myApp.test', {
templateUrl: 'myTemplate.html',
controller: myComponent,
controllerAs:'vm',
});
})();
The console.Log give me:
this is a test --> for the test
undefined --> out the http.get
Object {id: 1…} --> in the http.get
So I can't access to my data out the http.get and this is what I want.
$http.get is an asynchronous call meaning the program execution doesn't wait for the data to be fetched from server .
Thus by the time console.log(self.MYDATA) located outside of $http.get() is executed , the data has not been fetched from server that is why you get a undefined error.
To solve this problem or to handle asynchronous calls , you could do something like this :
var promise = $http.get(MYAPI);
then access data in the following manner with the help of callbacks :
promise.then(
function successCallBack(response)
{
self.MYDATA = response.data;
console.log(self.MYDATA)
}
).then(
function errorCallBack(response)
{
console.log(response);
}
);
Here is a nice article on callbacks that could help you !
I am facing issue while displaying data in table.
I am using a customservice to fetch data from a json file and then inserting that data into $rootScope of an object.
But when I run the view,it comes blank with no errors.In the view,i am using below line of code in the view to iterate the array of objects "books"
Please guide me.
controller.js
Controllers.controller('BookListCtrl_Student', ['$scope','$rootScope','$http','$location','BookData',
function ($scope, $rootScope, $http, $location, BookData) {
$rootScope.books=[];
$rootScope.books.push(BookData.getData());
$scope.bookLists = ['All Books', 'Available Books'];
$scope.selection = $scope.bookLists[0];
$scope.backToLogin = function() {
$location.path("/main");
}
}
]);
customservice.js
Controllers.factory('BookData',['$http',function(http){
return {
getData: function() {
return http.get('data/books.json').then(function(result){
return result.data;
});
}
};
}
]);
The problem is when you do $rootScope.books.push(BookData.getData()) it calls your getData(), but it never executes the promise. To fix it you would need to handle the promise within the controller and assign the data then.
customservice.js
return http.get('data/books.json');
controller.js
BookData.getData().then(function(resp){
$rootScope.books.push(resp.data);
})
Heres a plunker with a quick example - https://plnkr.co/edit/ivdLd9wilmWW8oUnrMsh?p=preview
I'm trying to create a db factory that returns data from the database to the client after the data is successfuly posted but it returns as 'undefined' for some reason.
My factory function looks like this:
uno.factory('adbFactory', ['$http', function($http){
var fact = {};
fact.get = function(http, query, isAll) {
//var query = "get all blog_posts";
http.post('php/adb/adb.php', {'query': query, 'all': isAll})
.success(function(data){
//console.log(data);
return data;
})
.error(function(){
console.log('Error...');
});
};
return fact;
}]);
And my controller resembles this:
uno.controller('newsCtrl', function($scope, $http, adbFactory){
$scope.derp = 'derp!!!!!';
console.log(adbFactory.get($http, 'get users 1', false));
});
don't worry about the 'get users 1 etc etc' string, i created a function in php that renders a SQL query based on given parameters. Is there something in my factory code i need to improve on??
I would advice returning the promise from the factory and handling the success and error events in the controller instead.
fact.get = function(http, query, isAll) {
return http.post('php/adb/adb.php', {'query': query, 'all': isAll});
};
uno.controller('newsCtrl', function($scope, $http, adbFactory){
adbFactory.get($http, 'get users 1', false).success(function(data) {
console.log(data);
});
});
fact.get method has no return statement, that's why it returns undefined.
Also, this callback is useless because it is called asynchronously
.success(function(data){
//console.log(data);
return data;
})
I think you want somethig like:
fact.get = function(http, query, isAll) {
return http.post('php/adb/adb.php', {'query': query, 'all': isAll});
};
uno.controller('newsCtrl', function($scope, $http, adbFactory){
adbFactory
.get($http, 'get users 1', false)
.success(function(data){
console.log(data);
})
.error(function(){
console.log('Error...');
});
});
You have to keep in mind that you are performing some asynchronous request.
You have two way to retrieve your data :
Following the callback way
Following the promise way
As you know, $http service return promise, and has some callback method, like .success() and .then() for example.
For promise, $q.defer() is a promise manager from the deferred API.
$q.defer() get 2 methods :
resolve(value) : which resolve our associated promise, by giving her the final value
reject(reason) : which resolve an promise error.
So you can do :
Service
(function(){
function Service($http, $q){
var defer = $q.defer();
//Callback way
function get(callback){
$http.get('app.php').success(function(data){
//Pass our data to the callback
callback(data);
});
}
//Promise ways
function getPromise(){
$http.get('app.php').success(function(data){
//Resolve the data
defer.resolve(data);
});
//Return our promise
return defer.promise;
}
return {
get: get,
getPromise: getPromise
};
}
angular
.module('app')
.factory('Service', Service);
})();
Controller
(function(){
function Controller($scope, Service) {
//Our callback method
function print(data){
console.log(data);
}
//Retrieve our data by using callback way
Service.get(print);
//Retrieve our data by using promise way
var promise = Service.getPromise();
//When promise is resolved
promise.then(function(data){
//Retrieve our data
console.log(data);
});
}
angular
.module('app', [])
.controller('ctrl', Controller);
})();
But what should i use ? I think that use promise is better than callback, because you can handle easily your request. Moreover, you can perform promise chaining, and avoid the famous callback hell.
First project in AngularJS and I started creating my services (factories) that I made modular like this
angular.module('app.services.public', [])
.factory('publicService', ['$http', function publicService($http) {
var results = {};
results.contact = function (name, email, message){
return $http.get();
};
return results;
}]);
That I then call in my main angular app by including it. When I call it, I need to listen for success or error
publicService.contact().success(callback).error(callback)
My question is, I'm going to be doing a lot of API requests through these services and seems to be bad code to listen to the error everytime since 90% of the time it will do the same thing.
How can I create a wrapper around the $http.get or around all factory calls?
So something like
apiCall = function (url, data, successCallback, errorCallback){
$http.get(url,data).success(function(){
successCallback()
}).error(function(){
if(errorCallback()){ errorCallback(); return; }
// or display general error message
})
}
I would recommend against converting promise-based into callback-based APIs. Angular adopted promises and it best to stay with them.
Also, stay away from $http-specific .success/.error and use promise .then/.catch APIs.
How wide do you need to cast your net to handle $http errors?
1) Say, it only applies to your publicService service, then you can "handle" it at the each function:
.factory("publicService", function($http, $q){
function handleError(){
// invokes error handlers
}
return {
onError: function(cb){
// register error handlers
},
doSomethingA: function(){
return $http.get("some/url/A")
.then(function(response){
return response.data;
})
.catch(function(error){
handleError(error);
return $q.reject(error); // still "rethrow" the error
}
},
doSomethingB: function(){
// similar to above
},
// etc...
};
})
Then you could separate request from error handling:
.controller("MainCtrl", function($scope, publicService){
publicService.onError(function(error){
$scope.showError = true; // or something like that
})
})
.controller("FunctionACtrl", function($scope, publicService){
publicService.doSomethingA()
.then(function(data){
$scope.data = data;
});
})
2) Of course, the above, would only apply to request made via publicService. If you want to catch all $http errors, you could implement an $http interceptors. I won't go into detail - there is enough info in documentation and elsewhere - but it would could work like below:
.factory("ErrorService", function(){
return {
onError: function(cb){
// register error handlers
},
broadcastError: function(error){
// invoke error handlers
}
};
})
Then in interceptor, use ErrorService as a dependency:
'responseError': function(rejection) {
ErrorService.broadcastError(rejection);
return $q.reject(rejection);
}
Then you could handle the errors globally:
.controller("MainCtrl", function($scope, ErrorService){
ErrorService.onError(function(error){
$scope.showError = true; // or something like that
})
})
You have the right idea. You can do it easily with a Factory.
myApp.factory(APIService, function(publicService, $http) {
return {
// create methods in here
...
contact: function(cb) {
$http.get(url,data).success(cb).error(function(err){
console.error('oh no!', err);
});
}
};
});
Then you can use it in your controllers.
APIService.contact(function(data){
console.log('response from the api!', data);
});
You can even move your error handler to its own factory as well.
I would suggest an implementation using angular's $q service.
angular.module('app.services.public', [])
.factory('publicService', ['$http', '$q', function publicService($http, $q) {
var results = {};
results.contact = function (name, email, message){
return $q.when($http.get());
};
return results;
}]);
Or rather than use the $q.when(...) method you can use $q.deferred like so:
angular.module('app.services.public', [])
.factory('publicService', ['$http', '$q', function publicService($http, $q) {
var deferred = $q.deferred();
var results = {};
results.contact = function (name, email, message){
$http.get().success(function(data){
deferred.resolve({
// assumes data retried from http request has a title and price attribute
title: data.title,
cost: data.price});
}).error(function(data){
deferred.reject(data);
});
};
return deferred.promise;
}]);
I have a question regarding angularJS services.
From what I have read, there are two ways of using services.
[1] Have a service return a promise to return data. If you use this method, in your routeProvider, you can make sure Angular resolves this promise to return data BEFORE it loads the page.
e.g.
App.factory('BooksService', function($q, $http) {
var deferred = $q.defer();
$http.get('/rest/books').then(function(data) {
deferred.resolve(data);
}, function(err) {
deferred.reject(data);
});
return deferred.promise;
};
Then, in my route provider:
...
$routeProvider.when('/books', {
controller : 'BooksCtrl',
templateUrl: '/partials/books.html',
resolve: {
books: 'BooksService'
}
});
...
Then, in my controller:
App.controller('AddPaypalAccountCtrl', function($scope, BooksService) {
$scope.books = BooksService;
}
[2] Have a service return an object that contains functions and data.
e.g.
App.factory('BooksService', function($q, $http) {
var books = [];
var service = {
getBooks : function() {
return books;
},
addBook: function(book) {
books.push(book);
}
};
return service;
};
My question: Is it possible to get the best of both worlds and have a service return a promise that when resolves returns an object that contains functions and data?
I want the $http call to get the books to be resolved before I load the '/books' page, BUT I also want access to a service that can manage said books. Of course I can write two separate services, but I wonder if it's more efficient to keep them both in the same service and write a service that kills two birds with one stone like so:
Here's an example of my factory that returns a promise for retrieving the books.
App.factory('BooksService', function($q, $http) {
var books = [];
var service = {
getBooks: function() {
return books;
},
addBook: function(book) {
books.push(book);
}
}
var deferred = $q.defer();
$http.get('/books').then(function(data) {
books = data.data;
deferred.resolve(service);
, function(err){
deferred.reject(err);
});
return service;
};
Then, as per before, my route provider is as follows, requiring that books be retrieved before I go to the /books page:
...
$routeProvider.when('/books', {
controller : 'BooksCtrl',
templateUrl: '/partials/books.html',
resolve: {
books: 'BooksService'
}
});
...
Then, in my controller, I will attach books to the scope like so.
App.controller('AddPaypalAccountCtrl', function($scope, BooksService) {
$scope.books = BooksService.getBooks();
}
I haven't seen anyone do this yet, so I'm wondering if this is OK.
I feel you are trying to break the SRP - Single Responsibility Principle.
What is the Responsibility of your service?
Provide an API for async request or make the request?
If it provides API, it should not be loaded async.
If too make the request, it should be a method of the service, not the service itself. A service is the interface to your request, not the request!
Rarely you may need to get logic back from your server, but again, you have to separate concerns:
Get the logic (e.g. Angular expression as a string) from server.
Parse into a function performing the logic (can be done with Angular $parse service).
Inject your logic function wherever you need to use it.