At Reonomy, we scaffold many of our client-side products with the Angular 1.* framework. One of our trickiest tasks is hooking up a detailed search form to validate and submit dynamically while the user is populating it.
We’ve had to get very clever about how and why we use services to keep things optimized and readable. Doing a deep dive into the world of Angular services has revealed some insights that are not obvious in the current Angular documentation.
For a more detailed introduction of the Reonomy product, read on here.
This article seeks to explain the different service types in Angular 1.* and provide a proactive approach to using them semantically and with purpose.
It is by no means a hierarchically correct way of doing things, but an approach that can help you and others who touch your code read and refactor with less confusion.
The Angular docs describe services like this:
Angular services are substitutable objects that are wired together using dependency injection (DI). You can use services to organize and share code across your app.
I like to believe this is intentionally abstract or vague, and that the smart folks on the Angular team are leaving it open to the developer to discover how best to use their tools.
The Angular docs tell us that services are lazily instantiated and that they are singletons, but start to thin out when describing the different service types. Add to that the fact that there is a service service, and things get confusing quick.
Different service types include:
- provider
- factory
- service
- value
- constant
- decorator
To add to the confusion, when you begin to search for tutorials/examples, you’ll see services being used for different purposes, and get some strong opinions on correct use cases.
Some more common use cases are:
- model
- promise
- constructor
- constants
I’ve heard arguments over why using services for one purpose over another is the right way to go. I’d like to suggest that all purposes are correct, and that it is just up to the developer to use them with intention while making it obvious when and why you are using a service in a certain way.
Different Types of Services
There are many articles that break down in-depth what a service is and the difference between service types. While that’s not the purpose of this article, I do want to touch on them.
For a more in depth breakdown click any of these links:
- Understanding Service Types
- Differences Between Providers in AngularJS
- AngularJS Providers Explained
Generally speaking, I have yet to see a reason why factory should be used over service or vice-versa. It is nice however to pick one within an organization and stick with it.
Providers allow you to add methods to configure a service before your app inits.
These are available to you in the angular module config method.
A good example of this is the angular-bootstrap tooltip directive, which has a $tooltipProvider
which exposes a setTriggers()
method.
This way if you don’t like the directives initial default, you can decide what event will be the default trigger for a tooltip before the app is initialized.
Keeping in line with that thinking, it’s reasonable to use a provider if you’re creating a service that will be shared across apps that may have different desired default behavior.
Values and constants allow you to create light-weight services that do not require a function. They can be simple variable types like strings, numbers, booleans and object literals.
Other than syntactical sugar™, the main difference between values and constants is that a value can be decorated before the app has init, and a constant can not.
This sort of helps enforce the idea of a constant being a constant and not changing over time. Though there is nothing in the Angular 1.* code that stops you from changing a constant once the app has initialized.
Provider
Factory, service and value inherit from provider. The provider service is the first service an Angular app has access to. This means you have access to provider before the app has initialized.
This is advantageous if you wish to configure values in your service before the app has init. Within the provider structure you create a factory and a service. Providers are available inside the config method of a module.
I will go deeper into configuring providers in the Decorating Services section below.
angular.module(‘myApp’, [])
.provider(‘myService’, function() {
var configurableVar = ‘foo’;
return {
// all keys in this object are provider level, and are available before the app inits
setConfigurableVar: function(val) {
configurableVar = val;
},
$get: function() {
// this function is the factory
return {
// this object is the service
configurableVar: configurableVar
};
}
};
})
.config(function(myServiceProvider) {
myServiceProvider.setConfigurableVar(‘bar’);
})
…
Factory
Factory is the next step up the chain. A factory inherits from a provider and returns a service.
While factories and services are different under the hood, the difference between using them in practice is mainly syntactical. You can make vars private by not including them in the returned object.
angular.module(‘myApp’, [])
.factory(‘myFactory’, function() {
var returnObj = {}; // we’ll return everything in this object and make it public.
var privateVar = ‘private’; // We can use this var without exposing it outside of our factory.
returnObj.publicVar = ‘foo’; // this will be available to any controller that injects ‘myFactory’.
return returnObj;
})
…
Service
Confusingly named “service”, the service service is at the end of the chain in the provider/factory/service paradigm. Now you are writing directly inside a constructor object that will be called with the new
operator the first time it is instantiated in the app lifecycle. Syntactically this means you make things private by not including them in this
.
angular.module(‘myApp’, [])
.service(‘myService’, function() {
var privateVar = ‘private’; // We can use this var without exposing it outside of our service.
this.publicVar = ‘foo’; // this will be available to any controller that injects ‘myService’.
})
…
Value
Values are a simplified service type that return a single var. Granted, this var can become multi-dimensional if it is an object.
A noteworthy difference between a value and a service is in a service you are inside a constructor function, whereas in a value you can simply return the variable type of your choice.
angular.module(‘myApp’, [])
.value(‘stringValue’, ‘foo’)
.value(‘numberValue’, 5)
.value(‘objectValue’, {
foo: ‘bar’
})
…
Constant
In practice the difference between value and constant is largely “syntactical sugar.” A constant implies that the variable is not going to change over the lifecycle of the app.
There is nothing implicitly in Angular 1.* that will enforce that. It’s basically up to you to keep the syntactical contract alive. The only technical difference is that value is available in the module config method and constant is not. More on that below.
angular.module(‘myApp’, [])
.constant(‘stringValue’, ‘foo’)
.constant(‘numberValue’, 5)
.constant(‘objectValue’, {
foo: ‘bar’
})
…
Decorator
A decorator can change the values of a provider before it is available to your app.
The provider/decorator relationship is unique in this way among the services. A decorator also can only exist inside an apps config method. A common use case for this would be to configure defaults of a service without touching the provider itself.
All services except constants (and provider) inherit from a provider, and can be decorated.
angular.module(‘myApp’, [])
.value(‘myValue’, ‘foo’)
.config(function($provide) {
$provide.decorator(‘myValue’, function() {
return ‘bar’; // myValue is now ‘bar’
});
})
…
Configuring and Decorating Services
I have mentioned decorating and providers a few times in this article. This topic get’s confusing quick as you can create a provider service, but at the same time services, factories and values have providers. I’ll try to clear that up a little.
The Module Config Method
The config method is available in any module and is available before your app has init. Config methods are run in the order they are declared in your code. This is the only place you have access to your app before it has initialized.
angular.module(‘myModule’, [])
.config(function() {
// Any code in here will run before the app is init
})
…
Configuring a Provider
Typically if you’re going to go through the trouble of writing out the full provider syntax, it’s because you want to create explicit methods for configuring your service before an app inits.
You need to inject your provider into the config method with Provider
appended to the end of its name. For example, if your provider is called breakfast
, you have to inject breakfastProvider
into the config method.
angular.module(‘myApp’, [])
.provider(‘breakfast’, function() {
var defaultFood = ‘eggs’;
return {
setDefaultFood: function(food) {
// this method allows you to override defaultFood before the app inits
defaultFood = food;
},
$get: function() {
return {
// this is where defaultFood is made available to your app
defaultFood: defaultFood
};
}
};
})
// append ‘Provider’ to breakfast to inject it into the config method.
.config([‘breakfastProvider’, function(breakfastProvider) {
breakFastProvider.setDefaultFood(‘bacon’);
// when the app inits breakfast.defaultFood will be ‘bacon’
}])
…
Decorating a Service, Factory or Value
Services, factories and values can also be configured before the app inits. The syntax for achieving this is a little different.
We still execute our code in the config method, but we have to inject $provide
and use the $provide.decorator()
method. This is called (you guessed it) decorating. The callback inside the decorator method gives you the $delegate
parameter, which contains the value(s) of your service and can be manipulated before it is returned.
angular.module(‘myApp’, [])
.value(‘myValue’, ‘foo’)
.service(‘myService’, function() {
this.foo = ‘foo’;
this.bar = ‘bar’;
})
.factory(‘myFactory’, function() {
return {
foo: ‘foo’,
bar: ‘bar’
};
})
.config(function($provide) {
$provide.decorator(‘myValue’, function($delegate) {
return $delegate + ‘bar’;
// returns ‘foobar’
});
$provide.decorator(‘myService’, function($delegate) {
$delegate.foo = ‘FOO’;
$delegate.baz = ‘baz’;
return $delegate;
// returns {foo: ‘FOO’, bar: ‘bar’, baz: ‘baz’}
});
$provide.decorator(‘myFactory’, function($delegate) {
$delegate.foo = ‘FOO’;
$delegate.myBool = true;
return $delegate;
// returns {foo: ‘FOO’, bar: ‘bar’, myBool: true}
});
})
…
Purposes of Services
Services are only tied to a few rules. They’re dependency injected, they are lazily instantiated and they are singletons.
Beyond that, and the individual types quirks, it’s up to you to figure out how and why to use them. Below are some common patterns.
Service as a Model
Since services are singletons the same instance of your service will be shared across multiple controllers.
In addition, if one controller is destroyed, and later in the life of your app another is created, the values will remain the same. This makes services a great place to store data you want to persist for the lifecycle of your app. The only service I would not use as a model is constant.
Using a Value as a Model
An easy way to store a persistent data model is to make a value an object literal.
angular.module(‘myApp’, [])
.value(‘myBreakfastModel’, {
prices: {
eggs: 2.50,
coffee: 3.25
},
quantity: {
eggs: 0,
coffee: 0
}
})
…
Using a Service/Factory/Provider as a Model
Using service, factory and provider gives you some more firepower, allowing you to include methods and private vars easily. In the below example I’ll use a factory, but this could apply to service and provider as well.
angular.module(‘myApp’, [])
.factory(‘myBreakfastModel’, [function() {
var returnObj = {}; // everything in this object will be public
var tax = 0.08; // this var will remain private
returnObj.prices = {
eggs: 2.50,
coffee: 3.25
};
returnObj.quantity = {
eggs: 0,
coffee: 0
};
returnObj.getPrice = function() {
var price = 0;
price += returnObj.prices.eggs * returnObj.quantity.eggs;
price += returnObj.prices.coffee * returnObj.quantity.coffee;
price += (price * tax);
return price;
};
return returnObj;
}])
Service as a Promise
Services can be a nice way to encapsulate promises. If multiple controllers/services need the same data this is an easy way to be sure you only call for it once.
angular.module(‘myApp’, [])
.factory(‘myPromise’,
[‘$http’, function($http,) {
var promise = $http.get(‘/some-url’);
return promise;
}])
.controller(‘myController’,
[‘$scope’, ‘myPromise’,
function($scope, myPromise) {
myPromise.then(function(res) {
$scope.data = res.data;
});
}])
…
Service as a Constructor
Factories can be used as a way to share constructors across controllers. This is a bit different from how the service service is a constructor, in that the service service will automatically call the new
operator and deliver the service service as a singleton across controllers.
Creating a constructor inside a factory allows you to use the new
operator in your controller to generate new instances of it in controllers.
angular.module(‘myApp’, [])
.factory(‘myBreakfastConstructor’, [function() {
var BreakfastConstructor = function(eggNum, coffeeNum) {
this.eggNum = eggNum;
this.coffeeNum = coffeeNum;
};
BreakfastConstructor.prototype = {
…
};
return BreakfastConstructor;
}])
.controller(‘myController’,
[‘$scope’, ‘myBreakfastConstructor’,
function($scope, myBreakfastConstructor) {
$scope.breakfast1 = new myBreakfastConstructor(3, 1);
$scope.breakfast2 = new myBreakfastConstructor(2, 2);
}])
…
Service as a Constant
Services are a great place to share constants across your app. Whether it is a single constant, or an object literal with many constants.
Though there is a constant service type, you could presumably store them in any service dependent on your love of semantics (but seriously, don’t do that).
angular.module(‘myApp’, [])
.constant(‘myConstant’, ‘foo’)
.constant(‘myConstantsObj’, {
STR_FOO: ‘foo’,
STR_BAR: ‘bar’,
NUM_FIFTEEN: 15
})
…
Naming Services Semantically
I’m of the belief that if you want code you can share across your app, a singleton, or code that is lazily instantiated, services are a great way to keep things organized.
There is no incorrect use of a service so long as it fits into at least one of these criteria.
Using services for different purposes gets confusing when you are relying on the service type to dictate what it’s purpose is.
If you use the constant service for constants it’s great, but what if sometimes a factory is a promise and other times a factory is a constructor? Using some variation of the word factory as a prefix or suffix wont suffice, i.e. factBreakfast
or breakfastFact
.
Once I’ve injected the breakfast factory I don’t know if I am handling the then()
method of a promise or if I have to use the new
operator to create a new instance.
I would suggest naming your services according to what they do rather then what they are. This could be the prefix/suffix method, or whatever makes sense to you, so long as it’s consistent.
Examples
model
angular.module(‘myApp’, [])
.value(‘breakfastModel’, {
prices: {
eggs: 2.50,
coffee: 3.25
},
quantity: {
eggs: 0,
coffee: 0
}
})
…
promise
angular.module(‘myApp’, [])
.service(‘breakfastPromise’, [‘$http’, function($http) {
this.promise = $http.get(‘/breakfast’);
}])
…
constructor
angular.module(‘myApp’, [])
.factory(‘breakfastConstructor’, function() {
var Breakfast = function(eggNum, coffeeNum) {
this.eggNum = eggNum;
this.coffeeNum = coffeeNum;
};
Breakfast.prototype = { … };
return Breakfast;
})
…
constants
angular.module(‘myApp’, [])
.constant(‘breakfastConstants’, {
NUM_EGGS: 3,
NUM_COFFEE: 1,
STR_EGGS: ‘country fresh eggs`,
STR_COFFEE: ‘roasted Brazilian coffee‘
})
…