How to make Angular2 Service singleton?

2.4k views Asked by At

I'm trying to implement an auth guard in my app. ie; Only authenticated users can access certain routes of my app. I'm following the tut given here.

Once the user is logged in I change a boolean value in my AuthService to true to indicate that the use has logged in. Which needs to be retained through out the life of app.

Given below the source code:

auth-guard.service.ts

import { Injectable }     from '@angular/core';
import  { 
  CanActivate, Router, 
  ActivatedRouteSnapshot, 
  RouterStateSnapshot
}                       from '@angular/router';
import { AuthService }  from './auth.service';

@Injectable()
export class AuthGuardService implements CanActivate {
  constructor(private authService: AuthService, private router: Router) {}
  canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean {        
    let url: string = state.url;
    return this.checkLogin(url);
  }

  checkLogin(url: string): boolean {
    console.log('Auth Guard Service: ' + this.authService.isLoggedIn);
    if (this.authService.isLoggedIn) { return true; }
    // Store the attempted URL for redirecting
    this.authService.redirectUrl = url;

    // Navigate to the login page with extras
    this.router.navigate(['/admin', 'admin-login']);
    return false;
  }
}

auth.service.ts

    import { Injectable } from '@angular/core';
    import { Http } from '@angular/http';

    import {  User } from '../user/shared/user.model';

    import { ServiceBase } from '../../core/service.base';
    import { appConfig } from '../../core/app.config';

    @Injectable()
    export class  AuthService extends ServiceBase {
        public isLoggedIn: boolean = false;
        redirectUrl: string;
        apiUrl: string;
        constructor(private http: Http) {
            super();
            this.apiUrl = appConfig.apiBaseUrl + '/users/signin';
        }
        signin(user: User, successCallback, errorCallback) {
            return this.http.post(this.apiUrl, user).subscribe(
                res => {
                    this.isLoggedIn = true;
                    successCallback(res);
                },
                err => {
                    //this.isLoggedIn = false;
                    errorCallback(err);
                }
            );
        }            
    }

login.component.ts

import { Component, OnInit } from '@angular/core';
import { FormGroup, FormBuilder, FormControl, Validators } from '@angular/forms';
import { Router } from '@angular/router';
import {  User } from '../../user/shared/user.model';
import {  AuthService } from '../auth.service';

@Component({
    moduleId: module.id,
    templateUrl: 'login.component.html'
})
export class AdminLoginComponent implements OnInit{

    user: User;
    userLoginForm: FormGroup;

    constructor(
        private formBuilder: FormBuilder, 
        private authService: AuthService,
        private router: Router){
        this.user = new User();
    }

    ngOnInit(){
        this.buildLoginForm();
    }
    buildLoginForm() {
        ...
    }
    login() {
        if(!this.userLoginForm.valid) return false;
        this.authService.signin(this.user,
            response => {
                console.log('Login Component: ' + this.authService.isLoggedIn);
                this.router.navigate(['/admin', 'products']);
            },
            response => {} 
        );
    }
}

Console output

XHR finished loading: POST "http://localhost:3000/api/users/signin".
login.component.ts:40 Login Component: true
auth-guard.service.ts:23 Auth Guard Service: false

Edit: app.module.ts

    import { NgModule } from '@angular/core';
    ...
    import { AuthGuardService } from './auth/auth-guard.service';
    import { AuthService } from './auth/auth.service';

    @NgModule({
        imports: [
            ...
            AdminRoutingModule
        ],
        exports: [
        ],
        declarations: [
            ...
            AdminLoginComponent,
            ...
        ],
        providers: [
            AuthGuardService,
            AuthService,
            ...
        ],
        bootstrap:[]
    })
    export class AdminModule {}

What am I doing wrong here? Any help would be appreciated.

1

There are 1 answers

2
Günter Zöchbauer On BEST ANSWER

As mentioned by ranakrunal9, if you provide a service at a component or directive, you get a new instance for each of such a component instance.

You also get a new instance for lazy loaded modules (loaded with loadChildren). Lazy loaded modules get their own root scope and components and services within this module will get a different instance of a service injected if that service is provided within that lazy loaded module.

To ensure you only have a single instance for your whole application, provide it only in your AppModule or a module loaded by AppModule directly or indirectly using imports: [...].

See also https://stackoverflow.com/a/40981772/217408