Search code examples
angularrxjsrxjs5http-cachingangular2-observables

Rxjs/Angular 4- using publishReplay() on Auth status checking makes my app unresponsive if I enter an AuthGuarded URL on the address bar


first of all, my app without the publishReplay() is working fine now but I want to optimize the requests by using some kind of caching to avoid going to the backend everytime to check if the user is logged in. I have the following context. I have auth aware components that need to be hidden or shown depending on the user being logged in or not. I have

a)an AuthGuard to guard for certain routes, it redirects the user to sign in route if the user is not logged in:

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


@Injectable()
export class AuthGuard implements CanActivate {

  constructor(private authService: AuthService, private router: Router){}

  canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean> | Promise<boolean> | boolean {
     return this.authService.isAuthenticated().map(isAuth => {
       console.log('is authenticated',isAuth);
            if (isAuth) {
                return true;
            }else{
                this.router.navigate(['/signin']);
                return false;
            }
        });
  }
}

b) an AuthService with the method isAuthenticated to check with the backend if the user is logged in(I'm using conventional sessions stored on a db). Here at the end of the chain im using publishReplay(3) cause I have 3 components that are auth aware at loading time. Note that if I remove that method everything works just fine and the authguard does its job, if I add it then I go to for examples localhost:4200/dashboard and the app freezes, links doesn't work and auth guard code is not getting to get executed since I put a console.log 'is authenticated' (as shown above) on AuthGuard and it's not showing on the console, giving the idea that the execution never gets there. If I remove publishReplay then I see the message again on the console:

isAuthenticated(): Observable {

  let options = new RequestOptions({ headers: this.getHeaders(), withCredentials: true });
  return this.http.get('http://localhost:3000/api/addsfiliates/sponsor/check/login',options)
    .map(response => {
      let res = response.json();
      console.log("response");
      if (res.code == 200) {
        this.userIsAuthenticated.next(true);
        return true;
      }
    }
  ).catch((err)=>{
    //maybe add in the future if the code is 403 then send him to login otherwise send him elsewhere
    return Observable.of(false);
  }).publishReplay(3);

}

c)A route file guarding the routes, just for context explaining:

const appRoutes: Routes = [
  { path: '', redirectTo: '/', pathMatch:'full'},
  { path: '', component: MainComponent },
  { path: 'signin', component:SigninComponent},
  { path: 'signup', component: SignupComponent},
  { path: 'dashboard', canActivate:[AuthGuard],component: DashboardComponent,
    children: [
      { path: '', redirectTo:'dashboard/overview', pathMatch: 'full'},
      { path: 'overview', component: OverviewCampaignsComponent },
      { path: 'active', component: ActiveCampaignsComponent},
      { path: 'history', component: HistoryCampaignsComponent}
    ] }

]

Am I taking the right approach here to do the caching for the auth aware components? If I am, how can I use this publish replay method to make it work with my use case? Thank you very much


Solution

  • publishReplay returns ConnectableObservable you need to call connect on it then it will connect to the source observable:

    let obs = this.http.get(...)
        .publishReplay(1);
    obs.connect;
    return obs;
    

    This should fix your app but you will not accomplish what you want. Because on each call you still create a new observable and thus make a call to the server.

    The simpliest straightforward solution is:

    export class AuthService {
        private isAuthenticatedValue: boolean;
    
        isAuthenticated(): Observable<boolean> {
            if(this.isAuthenticatedValue != null) {
                return Observable.of(this.isAuthenticatedValue);
            }
    
            return this.http.get(...)
                ...
                .do(flag => {
                    this.isAuthenticatedValue = flag;
                })
        }
    }
    

    Additionaly you should clear cached value periodically because session can expire on the server. You can do it by clearing the value with setTimeout.