#11 – Building a custom AngularJS Service
We saw how to use AngularJS services through the use of some built-in AngularJS services. We will be using the ones previously mentioned extensively throughout the blog going forward, so don’t worry that we didn’t get to see them all in action yet. The core AngularJS services only touch the tip of the iceberg in terms of the functionality we will need when we start creating our own AngularJS applications. But how do we decide between embedding our functionality right in the controller or putting it in a service? We should consider creating an AngularJS service if what we are implementing falls into one of the following broad criteria:
It needs to be reusable
More than one controller or service will need to access the particular function that is being implemented.
Controllers get created and destroyed. If we need state stored across our application, it belongs in a service.
It is independent of the view
If what we are implementing is not directly linked to a view, it probably belongs in a service.
It integrates with a third-party service
We need to integrate a third-party service (think SocketIO, BreezeJS, etc.), but we want to be able to mock or replace it in our unit tests. A service makes that easy.
Do we need an object cache? Or something that creates model objects? Services are our best bet.
Services themselves can depend on other built-in services or our own services. So traditional software engineering concepts like modularity, composite services, and even
hierarchy of services are still applicable.
Creating a Simple AngularJS Service
Let’s take an example of how to create a simple service. We will take the very first example from this chapter, which demonstrated the problem with using just controllers, and use a service to share the state between the two views:
The html and app.js file (which houses the controllers and services), looks something like this:
We changed the following things from the previous example:
- Instead of the list being instantiated and stored in the SubCtrl (and getting destroyed and re-created), we are storing the list in a service called ItemService.
- The SubCtrl has a function called list(), which just delegates and returns the value of ItemService.list() function.
- The SubCtrl has a function called add() that delegates and adds an item to the ItemService.
- The HTML now binds the ng-repeat to ctrl.list() instead of ctrl.list. So it calls the function and uses its return value to display the array in the UI.
We created the ItemService using an AngularJS module function called factory:
- The factory function follows a similar declaration style like the controller. So we declare the name of the service, ItemService, in the first argument and then the array syntax for Dependency Injection with our actual service function as the second argument.
- In the service definition function, we return an object, which becomes the API for the service. In this case, the ItemService defines two functions, list and add, which all users of the service can access.
- In the service definition function, we also declare some local variables (in this case, the items array). These are private to the service, and cannot be accessed directly (though they are accessible through the list function here) by any users of the service. Therefore, no controller can access ItemService.items directly.
The ItemService gets instantiated once when the application loads and the SubCtrl is loaded, at this point AngularJS decides it needs an instance of the ItemService. After it is created, all other controllers that ask for the ItemService will get the exact same instance that was returned the very first time. This is why both the tabs in our example show the exact same list, and if we click “Add” in one tab and then move to the other tab, the items still show up.
To summarize, when we create our own AngularJS service:
- Use the angular.module().factory function to declare the service’s name and dependencies.
- Return an object, or a function from within the service definition, which becomes the public API for our service.
- Hold internal state as local variables inside the service. This is important because in a Single Page Application where controllers can get created and destroyed, the service can act as an application-level store.
AngularJS guarantees the following:
- The service will be lazily instantiated. The very first time a controller, service, or directive asks for the service, it will be created.
- The service definition function will be called once, and the instance stored. Every caller of this service will get this same, singleton instance handed to them. This is important because in a Single Page Application, the HTML and controllers can get destroyed and created multiple times in an application.
In this way, we can create our own AngularJS service, and define the API of how someone interacts with our own service. Notice that we call ItemService a service, even though we defined it using a function called factory. We will touch upon this in the next blog.