How to make Angular2 Service singleton?
Asked Answered
R

1

6

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.

Revocation answered 30/12, 2016 at 6:54 Comment(2)
to make service singleton you have to add all your services inside providers section of your modules. If you have added services inside providers section of your component then for that component new service instance will be initialized.Roofer
Show us where u added this services ? App.moduleCowpea
W
3

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://mcmap.net/q/467555/-service-is-not-being-singleton-for-angular2-router-lazy-loading-with-loadchildren

Wince answered 30/12, 2016 at 9:44 Comment(2)
I provided the AuthService in my sub module(Admin Module). As you suggested I provided the AuthService in AppModule, now everything works fine. Thank you!Revocation
You're welcome. Glad to hear you could make it work.Perineurium

© 2022 - 2024 — McMap. All rights reserved.