From AngularJS to Angular2, the hierarchical dependency injection may be tricky. angular2 , angularjs , import , licencing https://octoperf.com/blog/2016/03/29/singleton-service-using-angular2/ OctoPerf ZI Les Paluds, 276 Avenue du Douard, 13400 Aubagne, France +334 42 84 12 59 contact@octoperf.com Development 708 2021-01-04

Creating a singleton Service in Angular2

OctoPerf is JMeter on steroids!
Schedule a Demo

Motivation, building a licencing application

I already use AngularJS for the frontend of OctoPerf, a load testing solution. I will need to migrate my application from AngularJS to Angular2 in a few months.

To get ready for it I created a licenses management application. I started from the excellent angular2-webpack-starter.

The sample AuthenticationService

I quickly needed to create a singleton service and found out that it’s not as straightforward as in AngularJS.

Let’s say we need an AuthenticationService that holds the user credentials. It is used in the login page to store it, and in the home page to retrieve the information.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
import {Injectable} from 'angular2/core';

@Injectable()
export class AuthenticationService {
  private credentials:string = '';

  getCredentials():string {
    return this.credentials;
  }

  setCredentials(credentials:string) {
    this.credentials = credentials;
  }
}

In Angular2 we use components to build our GUI. We use a tree of components in fact. And the root node is a component named App:

1
2
3
4
5
App
 |
 |----- Login
 |
 |----- Home

The only use of this App component is to handle the routing (redirecting the user among login and home pages):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
import {Component} from 'angular2/core';
import {RouteConfig, Router} from 'angular2/router';

import {Home} from './home/home';
import {Login} from './login/login';

@Component({
  selector: 'app',
  pipes: [ ],
  providers: [],
  directives: [ ],
  styles: [ require('./app.css') ],
  template: require('./app.html'),
})
@RouteConfig([
  { path: '/login', name: 'Login', component: Login, useAsDefault: true },
  { path: '/home',  name: 'Home',  component: Home }
])
export class App {
  constructor() {
  }
}

App.html:

1
<router-outlet></router-outlet>

The login page component is pretty simple. It has an onSignIn() method that:

  • stores the credentials in the AuthenticationService
  • redirects the user to the Home page.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import {Component} from 'angular2/core';
import {AuthenticationService} from '../services/authentication';
import {Router} from 'angular2/router';

@Component({
  selector: 'login',
  providers: [ ],
  directives: [ ],
  pipes: [ ],
  styles: [ require('./login.css') ],
  template: require('./login.html')
})
export class Login {
  private credentials: string = '';

  constructor(private authService: AuthenticationService, private router: Router) {
  }

  onSignIn(){
    this.authService.setCredentials(this.credentials);
    this.router.navigate(['Home']);
  }
}

To do so it needs the AuthenticationService and the Router. Both are injected in this component constructor.

Dependency injection

The sample code as it is would get us a dependency injection error: Angular2 could not inject our AuthenticationService into our Login component.

To allow it, we may add the AuthenticationService as a provider for the component: @Component({providers: [ AuthenticationService ]}). The error would be gone, and we can do the same on our Home page component to retrieve the credentials once the user is logged in.

In AngularJS a service is always a singleton. But this is the tricky part with Angular2: it uses a hierarchical dependency injection. So in this case we would end up with two instances of our AuthenticationService, one for the Login component and one for Home. Definitely not what we were trying to achieve as we could not share the credentials across our application.

In fact there are several injectors, organized in a tree that maps our component tree. So when a component needs a dependency, it ask for it to its injector. If the dependency is not found in the current injector providers, the request is raised to its parent injector, in our case to the App component’s one.

So the solution is simple as declaring our AuthenticationService provider on the App component instead of doing it on Login and Home:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
 import {Component} from 'angular2/core';
 import {RouteConfig, Router} from 'angular2/router';

 import {Home} from './home/home';
 import {Login} from './login/login';
 import {AuthenticationService} from './services/authentication';

 @Component({
   selector: 'app',
   pipes: [ ],
   providers: [ AuthenticationService ],
   directives: [ ],
   styles: [ require('./app.css') ],
   template: require('./app.html'),
 })
 @RouteConfig([
   { path: '/login', name: 'Login', component: Login, useAsDefault: true },
   { path: '/home',  name: 'Home',  component: Home }
 ])
 export class App {
   constructor() {
   }
 }
Creating a singleton Service in Angular2
Tags:
Share:
Comments (1)
  • What you suggest here makes sense. Our attempts to make Singleton services never succeeded. Putting the service in the providers list for the App Component didn’t seem to do the trick either. I wonder if it some attempt at clever recycling support internally.

Want to become a super load tester?
OctoPerf Superman