Creating a Singleton Service in Angular2

Creating a Singleton Service in Angular2

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.

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:

App
 |
 |----- Login
 |
 |----- Home

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

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:

<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.
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:

 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() {
   }
 }
By - CEO.
Tags: Angular2 Angularjs Import Licencing

Comments

Henrik Vendelbo  

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.
Reply
 

Thank you

Your comment has been submitted and will be published once it has been approved.

OK

OOPS!

Your post has failed. Please return to the page and try again. Thank You!

OK