Ionic: Using Factories and Web Services for Dynamic Data

posted in: Uncategorized | 42

As I covered in my Structure of an Ionic App post, a typical Ionic app has a Data Layer which uses Factories or Services to provide data to the controllers of your app. This data tends to come from an external backend or web service in the form of JSON or XML.

In this article, I will show how to build a Data Layer for an Ionic app, wired to a web service. (Note: If you are looking to use a SOAP web service, you may want to also check out my post SOAP Web Services in Angular and Ionic)

Basic Structure

The basic structure of a factory is a function that returns a object.

.factory('serviceName', function() {
	return {}
})

… where serviceName is the name you will refer to it as in your controller.

The object should contain the methods you want to be accessible from the controller.

.factory('serviceName', function() {
	return {
		doStuff: function(x){
			return x;
		},
		doMoreStuff: function(y){
			return y;
		}
	}
})

You can also have private variables (and functions) by including them outside (before) the return.

.factory('serviceName', function() {
	var z = 2;

	return {
		doStuff: function(x){
			return x * z;
		},
		doMoreStuff: function(y){
			return y * z;
		}
	}
})

Because we will probably want to call an external backend or web service, we will want to pull in the $http module by including it as a parameter in the function of the factory.

.factory('serviceName', function($http) {
	var z = 2;

	return {
		doStuff: function(x){
			return x * z;
		},
		doMoreStuff: function(y){
			return y * z;
		}
	}
})

To use this service in our controller, we will need to reference it in a similar fashion.

.controller('MainCtrl', function($scope, $stateParams, awesomeService) {
	var test = awesomeService.doStuff(3); //Returns 6
})

Getting a Collection

The first example we will use is getting a collection from a web service, in this case, a collection of users. We will declare a factory called userService with a function getUsers, and include the $http module.

.factory('userService', function($http) {
	return {
		getUsers: function(){
		
		}
	}
})

Assuming a url of https://www.yoursite.com/users will return a JSON response of user objects, let’s start by making a get request to that URL.

.factory('userService', function($http) {
	return {
		getUsers: function(){
			$http.get("https://www.yoursite.com/users");
		}
	}
})

$http.get returns a promise, so let’s reflect that in our code to have the getUsers method return that promise.

.factory('userService', function($http) {
	return {
		getUsers: function(){
			return $http.get("https://www.yoursite.com/users");
		}
	}
})

In theory, we could use this exactly the way it is in a controller.

.controller('MainCtrl', function($scope, $stateParams, userService) {
	userService.getUsers().then(function(users){
		//users is an array of user objects
	});
})

However, we want to store that data in our service for further use. By adding a .then to the .get function, we can process the promise when it results. Whatever we return from THAT function is also returned in a promise.

.factory('userService', function($http) {
	var users = [];

	return {
		getUsers: function(){
			return $http.get("https://www.yoursite.com/users").then(function(response){
				users = response;
				return users;
			});
		}
	}
})

Here we have declared the private member users, and when our request completes, we populate that variable with the response (array of users) and return it, which is returned in a promise. Our controller code is unchanged.

Get a Single Object

Next, we probably want to grab one specific user out of the returned collection from above. Let’s assume our users array looks like this:

[
	{
		id: 1,
		firstName: "Andrew",
		lastName: "McGivery",
	},
	{
		id: 2,
		firstName: "John",
		lastName: "Smith",
	}
]

Let’s modify our userService to allow grabbing a single user by index.

.factory('userService', function($http) {
	var users = [];

	return {
		getUsers: function(){
			return $http.get("https://www.yoursite.com/users").then(function(response){
				users = response;
				return users;
			});
		},
		getUser: function(index){
			return users[index];
		}
	}
})

If you need to do a lookup by a property instead of by index, you’ll need to use a loop.

.factory('userService', function($http) {
	var users = [];

	return {
		getUsers: function(){
			return $http.get("https://www.yoursite.com/users").then(function(response){
				users = response;
				return users;
			});
		},
		getUser: function(id){
			for(i=0;i<users.length;i++){
				if(users[i].id == id){
					return users[i];
				}
			}
			return null;
		}
	}
})

To use this in a controller, something like this…

.controller('UserCtrl', function($scope, $stateParams, userService) {
	var user = userService.getUser($scope.id);
})

Conclusion

Using a data layer in an Ionic App is vital to keeping your code organised. The code I have shown above is the basis of any factory to web service communication. It is also the basis of the common pattern of showing a list of results and then tapping to view the details of a single result. This should give you an idea of how to make your first data-driven Ionic App. Good Luck!

More Reading

Structure of an Ionic App
SOAP Web Services in Angular and Ionic

My name is Andrew McGivery. I currently work full time as an application developer at Manulife Financial in Canada.

42 Responses

  1. Hi,
    Thx for this post.
    How do you manage errors . By example with a WS not accessible ?

    • Hey Jeba,

      A promise’s .then function can actually accept 2 parameters, the first being a success and the second being a failure condition. You can either handle these errors at the factory level or just forward them onto your controller and have your controller deal with it.

      .factory('userService', function($http) {
      	var users = [];
      
      	return {
      		getUsers: function(){
      			return $http.get("https://www.yoursite.com/users").then(function(response){
      				users = response;
      				return users;
      			}, function(error){
      				//something went wrong!
      				//Optionally, we can just: return error;
      			});
      		},
      		getUser: function(id){
      			for(i=0;i<users.length;i++){
      				if(users[i].id == id){
      					return users[i];
      				}
      			}
      			return null;
      		}
      	}
      })
      

      if we choose to pass it on, same idea to handle it in the controller.

      .controller('MainCtrl', function($scope, $stateParams, userService) {
      	userService.getUsers().then(function(users){
      		//users is an array of user objects
      	},function(error){
      		//Something went wrong!
      	});
      })
      

      You can also do it like this if you prefer: http://odetocode.com/blogs/scott/archive/2014/04/21/better-error-handling-in-angularjs.aspx

  2. how do you cache the query? I would be happy read your code about cachefactory ?

  3. Hi Andrew,

    Can you give an example of post on a api and then handling the response.?

    • Hey vaibhavn1,

      Instead of using $http.get, use $http.post:

      return $http.post("https://www.yoursite.com/method",{param: value}).then(function(response){
      	users = response;
      	return users;
      });
      
  4. […] Ionic: Using Factories and Web Services for Dynamic Data The Stack Overflow Question Models and Services in Angular What is a collection? (Backbone Tutorials) […]

  5. […] Ionic: Using Factories and Web Services for Dynamic Data Structure of an Ionic App Creating Views with Ionic Original Question/Answer on Stack Overflow […]

  6. Hi,
    Can we call a SOAP Web Service from Ionic in a similar fashion? Can you please help with an example if you have tried.

    Thanks

  7. There’s a typo in the userService, I think you meant to say users[index] instead of:

    getUser: function(index){
    return users[i];
    }

  8. dajackel2010

    Thanks for your ionic Posts, hope you write more.

  9. How you would handle any calls to the ‘getUser’ function that could occurr prior to the ‘users’ variable being populated? For example, if the GET response was much slower than anticipated.

    • I’d suggest using a loading indicator and promises to prevent this kind of scenario.

      • I pondered about that part as well. In the example it didn’t show that getUsers() was called before getUser() and I wondered how could it work. Thanks to Gabe’s comment, I guess getUsers() must be called some where, am I right?

        It would be great if you add a little more to the example, make it complete and explain “a loading indicator and promises to prevent this kind of scenario”.

        Your posts have helped me a lot to understand ionic in general! Very nice job!

  10. Hi Andrew, thanks for your nice tutorial but I have a question :

    When i’m checking the console output of the data returned by the service (eg : users = User.getAll() ), instead of getting a json containing user objects, I get a ” d {$$state: Object, then: function, catch: function, finally: function} “, a $$state object, but this object also contains the data from my request, I saw some people talking about “unwrapping” the promise but I just can’t make it, can you help ?

  11. hello Andrew,

    I am a beginner on the use of the framework ionic, now i am trying to connect my application with server to get datas, so that i am using a RESTfull web service developed on the Slim framework(response of request with JSON).The web service is already done, my problem now i don’t know how to implement it in my application.

    Please can you help me with examples or links.

    Thank you in advance for understanding and for listening keenly hoping for a response from you.

  12. hi
    Nice post !
    In my json feed i have xyz when i click in that link app crash as that link not found .
    Is there any way to create a custom link to display the post .

  13. […] my post Ionic: Using Factories and Web Services for Dynamic Data, I covered the basics of consuming a RESTful web service from within an Ionic app using […]

  14. Hi Andrew,

    This article is very useful. Could you write a article or provide tips to create a factory method and fetch the data from Mongodb? I need to fetch from collections and show it in views.

    Thanks much.

    • You will need to create an API in your backend (Node I’m guessing) that exposes your data to your app. Your app can’t directly talk to your database.

  15. Hi ! Thanks for this post. However, i am wondering about one or two things :
    1) what if the getuser() method is called before getusers()?
    2) how would you avoid calling $http.get if the users variable is already populated ? Like when getusers() is called twice or more ? How could getusers() return a promise if users is empty/null, and the actual users if they have already been fetched ?
    Hope i am understandable 🙂

    • Hello! Thanks for your comment!

      1. IF it is called first with the above code, you’ll most likely get an error. The code is assuming a master-detail pattern where you would not be able to get to the detail without first visiting the master. One way to get around this would be to check if it has been populated before calling getUser(). Lots of options here.
      2. In your method, before the $http call, you could check is users.length > 0 and if it is, return that instead of doing the http call.

      Hope that helps!

  16. Hi everybody and thank you a lot Andrew for your post. it’s very helpful, but i have some issues.

    1) service

    .factory(‘serviceName’, function($http) {
    return {
    getData: function(){
    return $http.get(“http://api.openweathermap.org/data/2.5/weather?q=London,us&appid=44db6a862fba0b067b1930da0d769e98”);
    }
    }
    })

    2) controller

    .controller(‘WeatherCtrl’, function($scope, $ionicLoading, $stateParams, $http, serviceName){
    $scope.weather = serviceName.getData();
    })

    my problem is: serviceName.getData() returned null.

  17. app.factory(“gettodoservice”,function($http){
    //var response={ “records”:[{“todos”:”This is my First Todo list.”},{“todos”:”This is my Second Todo list.”} ]};
    var todo=[];
    return{
    gettodo : function(){
    return $http.get(“myjson.json”).then(function(response){
    todo = response.records;
    return todo;
    });
    }
    }

    });
    app.controller(“mainctrl”,function($scope,gettodoservice){
    $scope.name=gettodoservice.gettodo();
    });

    this is my code but it is not working. value not get another file myjson.json or another link please help me …………….

    The connection to ws://localhost:35729/livereload was interrupted while the page was loading.

    error show in console

Leave a Reply