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
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
.