Table of contents

< Previous chapterNext chapter >

Last updated .

Services and dependency injection

Up till now, I have just played around with some Angular directives to get a feeling for what it's all about. I think I am starting to get an idea of what Angular is good for, and I hope you are too. It's probably time to consider the broader picture. What are the design principles behind Angular.js?

According to the Angular docs, Angular is what HTML would have been, had it been designed for applications. Therefore, a key feature is that it extends the syntax of html to allow the developer to succinctly express the building blocks of an application. This is primarily accomplished through the use of directives, as we have already seen. Another key tenet of the Angular architecture is the principle of separation of concerns. In the context of Angular this means that, for example, a controller object should function only as the link between the view and the model and that other functionality should be delegated to services. The word 'service' in this context simply means an object that encapsulates view-independent logic and is not directly related to the concept of service in WCF or WebAPI, for example. A parallel in the world of .Net could be a class in a component that implements the business logic of an application or some more general utility function.

Services in Angular are javascript functions that are 'injected' into the client components that use them. The term 'injected' just means that an instance of the service is passed to the client in its constructor, rather than being instantiated by the client itself. This moves the responsibility of instantiation and the dependency on any concrete service from within the client to something external to it. This pattern of dependency injection (DI) is central in Angular. To make DI work, there needs to be a place where services are registered and instances created. In Angular, this is the module. The previous chapters in this series have already used Angular modules - there wouldn't be much of an application without the root module. But so far, we haven't declared dependency on any services.

Using dependency injection in a strongly-typed environment such as .Net, would require the client of services to refer to interfaces rather than concrete classes. That way, one service implementation can be swapped with another without the client caring. In Angular, which is just javascript, there is no type safety and no concept of interfaces. Instead of relying on interfaces, a service can simply be a function. After all, one javascript function can easily be swapped for another as long as they take the same sort of arguments and return the same kind of thing.

Angular comes with a bunch of built-in services, which can be recognized by a '$' prefix in their names. In addition to that, the Angular developer can create and register custom services.

To demo using a custom service I will start out by creating a new Scripts/TimeService.js script file containing the code to create a service. Not that it's terribly useful, but I will make a simple service that can calculate the number of seconds between two dates. I can then use that service in the controller to calculate the number of seconds elapsed since the page was loaded. I have also taken the opportunity to remove everything from the Scripts/App.js script file except the creation of the root module:

Scripts/App.js: ( function () { // Create root module var app = angular.module('AngularProject', []); } )(); Scripts/TimeService.js: ( function () { // Retrieve root module var app = angular.module('AngularProject'); // Register service app.service('timeService', function () { this.SecondsBetweenDates = function (date1, date2) { var diff = date1.getTime() - date2.getTime(); return Math.abs(Math.round(diff / 1000)); }; }); } )();

As you may recall, calling angular.module with just the name parameter returns a module instead of creating it. Once the module has been obtained, a service can be registered by calling module.service with the name of the service and a constructor function. Now that the service has been registered, it is ready to be used. Next up is the controller code:

Scripts/MyController.js: ( function () { // Retrieve root module var app = angular.module('AngularProject'); // Create controller with dependency on timeService app.controller("myController", [ "timeService", MyController ]); function MyController(timeService) { var vm = this; vm.LoadedTime = new Date(); vm.SecondsSinceLoaded = "0"; vm.Button_click = function () { var now = new Date(); vm.SecondsSinceLoaded = timeService.SecondsBetweenDates(now, vm.LoadedTime); }; } } )();

Notice that this time, the controller is created with a dependency on the timeService. The third parameter in the call to app.controller is an array of dependencies; in this case just the one. This causes the controller constructor function to receive an instance of the service as its first and only argument. Also note that there is no need to explicitly keep a reference to the service instance inside the controller; that instance has been injected into the controller. Inside the controller function, the timestamp of the page load is recorded in the LoadedTime property and there is a Button_click function which can now be used to call the service and calculate the number of seconds since load time. Finally, the markup, which is simple enough:

Time.html: <!doctype html> <html ng-app="AngularProject"> <head> <title></title> <meta charset="utf-8" /> <script src="Scripts/Angular/angular-1.5.0-rc.0/angular.js"></script> </head> <body ng-controller="myController as vm"> <button ng-click="vm.Button_click()">Update!</button> <p>Seconds since page load: {{ vm.SecondsSinceLoaded }}</p> <script src="Scripts/App.js"></script> <script src="Scripts/TimeService.js"></script> <script src="Scripts/MyController.js"></script> </body> </html>

The three script files are now included. One has to be careful to include them in the right order. In this case, the root module must be created first, then the service, and finally the controller - because the controller depends on the service, which in turn depends on the module. A click on the button will now cause the Button_click function to be called, causing an update of the display.

Showing the number of seconds since load time
Showing the number of seconds since load time

New stuff

module.service: The service(name, constructor) function is used to register a service with the module.

Services and factories

A service defined with the module.service function is a singleton. All users of that service get the same instance and any state it may contain is thus shared between clients. That may be fine in many instances; it was okay in the above example with the timeService, because that service needed no state at all. But commonly there is a need for a stateful service. A least I can imagine some use cases for that. Such a stateful service can be implemented using a factory. In the example below, a service is defined which has both state (data) and behaviour (methods). In the example, a service factory is defined, which is then used by a controller:

( function () { var app = angular.module("app", []); function apeServiceFactoryFunc() { function ServiceConstructor(name, upperCase) { this.Name = name; this.LogState = function () { var v = upperCase ? this.Name.toUpperCase() : this.Name; console.log(v); } } return { NewApeService: function (name, upperCase) { return new ServiceConstructor(name, upperCase); } }; } app.factory("apeServiceFactory", [apeServiceFactoryFunc]); function MyController(apeServiceFactory) { var service1 = apeServiceFactory.NewApeService("Gorilla", false); var service2 = apeServiceFactory.NewApeService("Orangutang", true); service1.LogState(); // logs "Gorilla" service2.LogState(); // logs "ORANGUTANG" } app.controller("myController", ["apeServiceFactory", MyController]); } )();

If you want to play with that code, you will need a bit of markup (partial):

<div ng-app="app" ng-controller="myController as vm"></div>

In this code, a factory is registered by calling the module.factory method. The factory returns an object with a single function, in this case the NewApeService function, which calls the ServiceConstructor function. The latter function can be thought of as the constructor for the service. The controller can get a hold on the factory by declaring a dependency on it. The controller can then instantiate as many service instances as required by calling the NewApeService method of the factory. Notice that the service actually has no name; only the factory has a name.

In this example, the service constructor is passed two arguments. The name argument value is used to seed a property called Name, while the upperCase argument is not used in that way. You can think of the Name property as a public property. And the upperCase property can be thought of as a private property, since there is no way for outsiders to access it once it has been passed in the constructor (unless you cheat).

It is worth noting that the factory function (in the above example the apeServiceFactoryFunc function) is never called more than once. It is called the first time the factory is requested but after that, an object with a constructor function is then returned.

New stuff

module.factory: The factory(name, [dependencies, func]) function is used to register a factory with a module. The factory is then used for constructing service instances.

The value method

The value construct can be used to define a named value (of any type) that can then be shared between all components in a module. Next up is an example of setting up such an object: an object with the parameters the client will need for accessing an external api:

( function () { var app = angular.module("app", []); app.value("serviceConfig", { serviceUrl: "", userName: "James", }); function MyController(serviceConfig) { console.log(serviceConfig.serviceUrl); console.log(serviceConfig.userName); } app.controller("myController", ["serviceConfig", MyController]); } )();

New stuff

module.value: The value(name, value) function is used to register a named value. The value is then available throughout the module.

There is also a const method, which is very similar to value. The differences are subtle and beyond the scope of this document.

The service, factory, value and const methods are really just syntactic sugary replacements for the provider method, to which they all delegate. I have yet to come up with a use case for using provider directly. Reading the documentation, it seems very likely I will never need it.

< Previous chapterNext : Forms, input and validation >

2016 by Niels Hede Pedersen Linked in