fromEvent(document,"storage") not working in Angular service

Viewed 47

We are storing the jwt-token in the localstorage, so I would love to get the event on the localstorage (change-event), so I can react to changes to the storage. For that reason I designed an RxJS Observable pipeline. The isLoggedInSubject is then subscribed from the Angular components.

@Injectable()
export class AuthService {
  private isLoggedInSubject: Observable<boolean> = null;
  private refreshTokenLogginStatus: BehaviorSubject<number> =
    new BehaviorSubject(0);
 
  constructor(
    private http: HttpClient,
    private globals: Globals,
    private router: Router
  ) {
    this.isLoggedInSubject = combineLatest([
      this.refreshTokenLogginStatus,
      fromEvent(window, "storage"),
    ]).pipe(
      tap((v) => {
        console.log("isLoggedInSubject is triggered");
      }),
      map((v) => this.isLoggedIn()),
      distinctUntilChanged((prev, cur) => prev === cur)
    );
  }

As you can see the refreshTokenLogginStatus is a BehaviorSubject which is set on different method with a new random number. If I remove the fromEvent(window, "storage") from the combineLatest everything works fine, that means the pipe runs through. If I add fromEvent(window, "storage") to the combineLatest() the whole pipe never runs (the console.log() never outputs the string). Is the problem the angular service? What confuse me really is, that if the refreshTokenLogginStatus is set with a new value the pipe also does not run through. As I understood it, the combineLatest() should now be performed anyway, because an observable of the two has changed. This is the difference between combineLatest() and zip().

PS: I also tried this version. Here I made an method which sets the refreshtoken, but the method was never called.

PPS: I also tried to get an error on subscribe(), but nothing was fired.

1 Answers

As others have pointed out, your observable isn't firing because the fromEvent() never emits. One way to solve this is to provide a default value using startWith:

this.isLoggedInSubject = combineLatest([
    this.refreshTokenLogginStatus,
    fromEvent(window, "storage").pipe(startWith(localStorage.getItem('token')))
]).pipe(...);

Maybe it's good enough to determine isLoggedIn by presense of the token, in which case you could do something like:

@Injectable({providedIn: 'root'})
export class AuthService {

  private token$ = new BehaviorSubject<string>(localStorage.getItem('token'));

  private externalTokenChange$ = fromEvent<StorageEvent>(window, 'storage').pipe(
    filter(event => event.key === 'token'),
    map(event => event.newValue),
  );

  isLoggedIn$ = merge(this.token$, this.externalTokenChange$).pipe(
    map(token => !!token),
    distinctUntilChanged(),
  );

  setToken(token: string) {
    this.token$.next(token);
    localStorage.setItem('token', token);
  }

}

Here's a fun little StackBlitz example.

Related