0

In my Angular2 app I am bootstrapping an auth service LocalStorage that I want shared across my components:

bootstrap(AppComponent, [
    ROUTER_PROVIDERS,
    LocalStorage
]);

LocalStorage is defined as follows:

import {JwtHelper} from 'angular2-jwt/angular2-jwt';
import { Injectable } from 'angular2/core';

@Injectable()
export class LocalStorage {

    key:string = 'jwt';
    jwtHelper:JwtHelper = new JwtHelper();
    username:string;

    constructor() {

        let token = localStorage.getItem(this.key);

        if (token == null) return;

        if (this.jwtHelper.isTokenExpired(token)) {
            localStorage.removeItem(this.key);
        } else {
            this.username = this.jwtHelper.decodeToken(token).username;
        }
    }

    login(jwt:string) {
        localStorage.setItem(this.key, jwt);
    }

    logout() {
        localStorage.removeItem(this.key);
    }

    isLoggedIn():boolean {
        return this.username != null;
    }

    getUsername():string {
        return this.username;
    }

    getToken():string {
        return localStorage.getItem(this.key);
    }
}

The problem is, however, when I share and update it across components only the component that updates it recognizes the changes. It is injected into components and edited like this:

    constructor(private router:Router, private localStorage:LocalStorage) {

        ...
    }

    logout(event) {
        event.preventDefault();
        this.localStorage.logout();
        this.router.navigateByUrl(RoutingPaths.home.path);
    }

Why is it that it seems multiple instances of this service are being created across components? Thanks.

Edit An example of a component template binding is:

Component:

import {Component} from 'angular2/core';
import {Router, RouteConfig, ROUTER_DIRECTIVES} from 'angular2/router';
import {RoutingPaths} from './routing-paths';
import {LoggedInOutlet} from './logged-in-outlet';
import {LocalStorage} from './local-storage'

@Component({
    selector: 'my-app',
    templateUrl: 'app/app.template.html',
    directives: [LoggedInOutlet, ROUTER_DIRECTIVES]
})
export class AppComponent {

    registerName:string;

    constructor(private router:Router, private localStorage:LocalStorage) {
        this.registerName = RoutingPaths.register.name;
    }

    logout(event) {
        event.preventDefault();
        this.localStorage.logout();
        this.router.navigateByUrl(RoutingPaths.home.path);
    }
}

Template:

<a *ngIf="!localStorage.isLoggedIn()" [routerLink]="[registerName]">Register</a>

Final Edit

Well this is embarrassing, after actually editing the username in the service it now works:

    login(jwt:string) {
        localStorage.setItem(this.key, jwt);
        this.username = this.jwtHelper.decodeToken(jwt).username;  // here
    }

    logout() {
        localStorage.removeItem(this.key);
        this.username = null; // here
    }

Sorry for wasting everyone's time. Thanks again.

Sloth Armstrong
  • 1,066
  • 2
  • 19
  • 39
  • What do your component definitions look like, in particular their change detection and providers settings? – Douglas Mar 07 '16 at 22:58
  • As a first step, I suggest putting a `console.log(...)` into the service's constructor to determine if you really are creating multiple instances of the service. – Mark Rajcok Mar 07 '16 at 23:03
  • @MarkRajcok Ok I did that, it seems only once instance is being created.But regardless only the component doing the updating is seeing the changes. I will put more code into my post. – Sloth Armstrong Mar 07 '16 at 23:07
  • @Douglas No providers since I thought for a single instance that should be excluded. I will post more code. – Sloth Armstrong Mar 07 '16 at 23:09
  • Based on the code you currently show, I don't see how any component could be notified of changes. Since you're not sharing a JavaScript reference type (e.g., object or array), each component will need to `subscribe()` to changes. See the cookbook example, which uses Subject to accomplish this: https://angular.io/docs/ts/latest/cookbook/component-communication.html#!#bidirectional-service – Mark Rajcok Mar 07 '16 at 23:12

3 Answers3

1

It's because you assigned LocalStorage as provider somewhere in your code.

Check if any of your component is containing:

@Component({
    providers: [LocalStorage]
}) 

This gives an Injector instruction to create a new Instance for that component and all children if child one again does not have an provided LocalStorage itself.

igorzg
  • 1,506
  • 14
  • 17
0

The problem is, however, when I share and update it across components only the component that updates it recognizes the changes

This is because of angular 2 component model is a Tree:

enter image description here

So only the component that changes and its sub components are rerendered. For stuff like singletons containing state used across components you need something like redux : https://medium.com/google-developer-experts/angular-2-introduction-to-redux-1cf18af27e6e#.yk11zfcwz

basarat
  • 261,912
  • 58
  • 460
  • 511
  • "So only the component that changes and its sub components are rerendered." This is not true. When Angular change detection runs (which is after any asynchronous event), by default, all components are examined for change. Any that have changed are re-rendered. Only if you specify `OnPush` on a component might it be skipped during change detection dirty checking. – Mark Rajcok Mar 07 '16 at 23:06
0

I forgot to actually modify username in the service itself:

    login(jwt:string) {
        localStorage.setItem(this.key, jwt);
        this.username = this.jwtHelper.decodeToken(jwt).username;  // here
    }

    logout() {
        localStorage.removeItem(this.key);
        this.username = null; // here
    }
Sloth Armstrong
  • 1,066
  • 2
  • 19
  • 39