With Angular 2 on its way, it seems like a lot of developers are now afraid of Angular 1.x. I have nothing bad to say about Angular. Sure, some things could be made easier but that’s why Angular 2 is on its way. I’ve played with Angular 2 a bit, and it only works in Chrome. I’m fairly confident Angular 1.x will be around for quite a while.
In that light, let’s talk about creating configurable services. One reason why AngularJS is so popular is because you can easily create modular, reusable code. This code can encapsulate logic, directives, extensible services and factories (via decorators), as well as configurability. I’ll talk about creating directives and decorators in future articles.
This example will be a simple URL constructor for placekitten.com. Any AngularJS service can be written to be configurable (if there’s reason to configure the service, of course).
First of all, let’s see how this service will be used.
.controller('View1Ctrl', ['$scope', 'kittyService', function ($scope, kittyService) {
$scope.kittycat = kittyService.getUrl();
}])
<p>How about a kitty cat?</p>
<p><img ng-src="{{kittycat}}"/></p>
Pretty simple. We’ll call kittyService.getUrl()
and use the string returned from the service as the src for an image.
To create a configurable service, we’ll use an angular provider instead of a service or factory. The structure I use for a provider is this:
(function () {
'use strict';
var module = angular.module('myApp.services', []);
module.provider('serviceName', function ServiceName() {
// use `this` for configurable properties or functions
this.$get = [function () {
var service = {};
// This can be any valid return that you'd normally use
// with module.service or module.factory
return service;
}];
});
})();
This structure is only a little different from a service or factory. Rather than return an object or constructor function, you use this.$get
to create your reusable code. What you assign to this property is the array injector for your service definition. You’ll feel comfortable with this syntax if you define your services using the array-based dependency injection:
angular.module('myApp')
.service('serviceName', [function (){
var service = {};
// This can be any valid return that you'd normally use
// with module.service or module.factory
return service;
}]);
You can use instance properties and methods on the provider definition to configure your service, then use the private context of your provider to share the configuration with the service. Here’s the full code for the kittyService:
(function () {
'use strict';
var module = angular.module('myApp.services', []);
module.provider('kittyService', function KittyServiceProvider() {
var height = 100,
width = 100;
this.setHeight = function (h) {
height = h;
};
this.setWidth = function (w) {
width = w;
};
this.$get = [function () {
var service = {};
service.getUrl = function () {
return 'http://placekitten.com/g/' + width + '/' + height;
};
// This can be any valid return that you'd normally use
// with module.service or module.factory
return service;
}];
});
})();
The provider has two private variables: height and width. The two methods on the provider object allow you to update these values, but only during the config phase. After your application’s config function has completed, your provider’s functions are no longer accessible.
One problem with this service is that placekitten.com shows the same image for a given combination of height and width. In the config phase of our application, the service’s height and width can be configured randomly:
.config(['kittyServiceProvider', function (kittyServiceProvider) {
var widths = [200,300,400,500,600];
var heights = [300,400,500,600,700];
kittyServiceProvider.setHeight(heights[Math.floor(Math.random()*heights.length)]);
kittyServiceProvider.setWidth(widths[Math.floor(Math.random()*widths.length)]);
}]);
The name injected into the config phase is kittyServiceProvider
and not kittyService
. This is important. AngularJS knows that when you request kittyServiceProvider in the config phase, you want the object containing your service definition (the KittyServiceProvider function in the example above). Once the config phase completes, the $injector
service will prepare your service from the this.$get
definition of your service.
If you load the example code, refresh the page a few times and you’ll receive a new kitty image.
NOTE: The heights and widths may not always combine in a way that retrieves an image. Just refresh for another example.
Why not do this in a service?
True, you could do this directly in the service if this was just your code. The benefit here is when you want to share a service with other developers, perhaps on a different project within your company or publicly on GitHub. What if your team wants a 200×300 kitty and HR wants a 500×500 kitty? In these situations you can’t just modify the code with your hard-coded values. True, in this example you could use constants or values instead but then your service would have a dependency on the consumer (this is bad).
A service that dumps a string for displaying a cat image is probably not that useful in the real world. Consider other possibilities. I’ve used providers for creating a reusable notification display which allows you to define whether a notification will automatically close and how long notifications will be displayed if they auto-close. Suppose your application often receives 40x errors when users leave their browsers unattended long enough for an authenticated session to time out; you could create a provider which caches failed requests and retries up to X (configurable) times before giving up. Or, you could create a configurable service which caches those failed requests, displays a login dialog, then automatically retries the failed requests X (configurable) milliseconds after logging in successfully. Those examples would require the use of an http interceptor, and would have been quite a bit more complex than a kitty image linker.
Code
Sample code for this blog post is available on github at jimschubert/blogs.
Further Reading
If you really want to dig into the guts of Providers (versus services, factories, constants, or values), check out Adrian Castillo’s blog post, AngularJS Providers under the hood.
For reference to each recipe, check out AngularJS Provider Recipes on the official docs.