45

I was trying to figure out how to get a menu to appear and disappear based on being logged in in a previous post. But I think a better and possibly easier question would be, how can I watch for changes to localstorage?

I am using json web tokens in local storage for my authentication, I would love to watch for a change to localStorage and then re-update my view on new information.

I set my localStorage with this

localStorage.setItem('jwt', my_token);

The things I would like to do is check if I have a token, if I don't nothing happens, but when there is a change fire an event. I would especially like it if I could only watch for a certain named event like localStorage.getItem('jwt').

Thanks!

EDIT:

Gunter pointed me in the right direction but just in case anyone is still fairly confused by that, here is a plunker showing you how to do it. http://plnkr.co/edit/TiUasGdutCsll1nI6USC?p=preview

Morgan G
  • 3,089
  • 4
  • 18
  • 26
  • How did you get that to work with local storage? – pwborodich Sep 09 '16 at 19:08
  • 3
    Basically you can't watch what enters into local-storage, but you can create an observable to whatever changes the local-storage and watch for that change. So what you do is load a global service up, have it read local-storage once, and then whenever that variable might change via me changing my webtoken or something, I wrap that variable with an observable and watch for changes to that variable, including re-updating local storage, and whatever else I want to do with that change. – Morgan G Sep 09 '16 at 21:07

4 Answers4

58

The key thing is to use window.addEventListener("storage", . While the library probably does it the "right" way for angular, here is a "light" version I put together, using .bind(this) instead of mucking about in angular's internals.

    import { Injectable, OnDestroy } from '@angular/core';
    import { Subject } from 'rxjs/Subject';
    import { share } from 'rxjs/operators';
    
    @Injectable()
    export class StorageService implements OnDestroy {
      private onSubject = new Subject<{ key: string, value: any }>();
      public changes = this.onSubject.asObservable().pipe(share());
    
      constructor() {
        this.start();
      }
    
      ngOnDestroy() {
        this.stop();
      }
    
      public getStorage() {
        let s = [];
        for (let i = 0; i < localStorage.length; i++) {
          s.push({
            key: localStorage.key(i),
            value: JSON.parse(localStorage.getItem(localStorage.key(i)))
          });
        }
        return s;
      }
    
      public store(key: string, data: any): void {
        localStorage.setItem(key, JSON.stringify(data));
        this.onSubject.next({ key: key, value: data})
      }
    
      public clear(key) {
        localStorage.removeItem(key);
        this.onSubject.next({ key: key, value: null });
      }
    
    
      private start(): void {
        window.addEventListener("storage", this.storageEventListener.bind(this));
      }
    
      private storageEventListener(event: StorageEvent) {
        if (event.storageArea == localStorage) {
          let v;
          try { v = JSON.parse(event.newValue); }
          catch (e) { v = event.newValue; }
          this.onSubject.next({ key: event.key, value: v });
        }
      }
    
      private stop(): void {
        window.removeEventListener("storage", this.storageEventListener.bind(this));
        this.onSubject.complete();
      }
    }

LocalStorageTwoTabs

Maurice
  • 6,698
  • 9
  • 47
  • 104
Stephen
  • 1,603
  • 16
  • 19
23

Use a service and only access LocalStorage through this service from everywhere.
The service can then provide observables that emit events on changes and you can subscribe to these observables to get notified.

Günter Zöchbauer
  • 623,577
  • 216
  • 2,003
  • 1,567
  • Thank you! Are there any code examples of this? I looked at the Plunker posted by @MorganG, but am still a bit confused. I'd like to use localStorage to store settings for my app that will update in each relevant component when changed from a settings component. – Harry Oct 28 '16 at 14:12
  • You can try a package like https://www.npmjs.com/package/ng2-local-storage (not tried myself). It would be easier if you'd post a new question with the code that demonstrates what you have tried and explain what causes you troubles. Hard to know what's unclear to you. – Günter Zöchbauer Oct 28 '16 at 14:46
  • 2
    Thanks @GünterZöchbauer; I have got something working in my app, if I have time I'll put it in a Plunker. The unclear thing isn't your answer, it's that I'm still learning about Reactive Programming and trying to get my head around it, so I was wondering if there were already any specific examples written. I understood/stand the concept, but was struggling more with the implementation. I'll keep at it :) – Harry Oct 28 '16 at 16:22
  • 1
    @GünterZöchbauer can you help me with a way to watch on changes made to localstorage in other tab . so the data changes through the service but that is in diffrent tab but i need that to reflect in the current tab . case senario :- logout from a tab should propogate to other tabs so that api doen't return 401. or product added to cart in each tab should show in checkout . – Ashutosh Raj Nov 20 '17 at 14:58
13

Updated version of https://stackoverflow.com/a/47683091/3098882 with new rxjs 6.

import { Injectable, OnDestroy } from '@angular/core';
import { Subject } from "rxjs";
import { share } from 'rxjs/operators';

@Injectable()
export class StorageService implements OnDestroy {
    private onSubject = new Subject<{ key: string, value: any }>();
    public changes = this.onSubject.asObservable().pipe(share());

    constructor() {
        this.start();
    }

    ngOnDestroy() {
        this.stop();
    }

    public getStorage() {
        let s = [];
        for (let i = 0; i < localStorage.length; i++) {
            s.push({
                key: localStorage.key(i),
                value: JSON.parse(localStorage.getItem(localStorage.key(i)))
            });
        }
        return s;
    }

    public store(key: string, data: any): void {
        localStorage.setItem(key, JSON.stringify(data));
        // the local application doesn't seem to catch changes to localStorage...
        this.onSubject.next({key: key, value: data})
    }

    public clear(key) {
        localStorage.removeItem(key);
        // the local application doesn't seem to catch changes to localStorage...
        this.onSubject.next({key: key, value: null});
    }


    private start(): void {
        window.addEventListener("storage", this.storageEventListener.bind(this));
    }

    private storageEventListener(event: StorageEvent) {
        if (event.storageArea == localStorage) {
            let v;
            try {
                v = JSON.parse(event.newValue);
            } catch (e) {
                v = event.newValue;
            }
            this.onSubject.next({key: event.key, value: v});
        }
    }

    private stop(): void {
        window.removeEventListener("storage", this.storageEventListener.bind(this));
        this.onSubject.complete();
    }
}
jeckep
  • 2,972
  • 1
  • 14
  • 10
1

I know this post is slightly old but there are libraries that can do this for you. For instance the h5webstorage will let you treat localStorage and sessionStorage like regular objects and synchronization is handled automatically. Even changes to the storage directly will flow back into your app.

SirDarquan
  • 81
  • 2