I would like an efficient way to store (and update) the current user details in the frontend, rather than making new HTTP GET requests to the backend whenever a new component is loaded.
I have added a UserService class which sets/updates a currentUser object when a method in the class is called.
import { Http, Response } from '@angular/http';
import { Injectable } from '@angular/core';
@Injectable()
export class UserService {
public currentUser: any;
constructor(private http: Http) { }
updateCurrentUser() {
this.http.get('api/login/currentUser').subscribe(data => {
this.currentUser = data;
});
}
}
I have found this method to cause a race condition problems. For example, an error occurs if the currentUser.username
is requested in the profile component but the service class hasn't completed the request yet.
I've also tried checking if the currentUser
is undefined and then calling the updateCurrentUser()
but it doesn't seem to work.
if(this.userService.currentUser === undefined){
this.userService.updateCurrentUser();
}
Update:The error message shown in the browser console
Update on HTML used:
I am using data binding to display the username (calling it directly from the UserService class). E.g.
<span class="username-block">{{userService.currentUser.username}}</span>
I have also tried assigning the currentUser object to a variable within the component that I trying to display the username but it has the same result.
I would appreciate any help/feedback.
You can try the Elvis/existential operator (I think that's the right name) in the html. The question mark after currentUser says don't bother trying to evaluate username if currentUser is undefined.
Assuming your async code will eventually fill it in, the page should then display it.
<span class="username-block">{{userService.currentUser?.username}}</span>
Edit
I just noticed you are outputting userService.currentUser.username. You may want to return an observable from the service to the component so that the data is reactive. Something like,
updateCurrentUser() {
return this.http.get('api/login/currentUser')
.catch(error => this.handleError(error, 'currentUser'));
}
Then in the component,
private currentUser;
ngOnInit() {
this.userService.updateCurrentUser()
.take(1)
.subscribe(data => { this.currentUser = data });
}
and the html now refers to the local component copy, initially undefined but eventually filled in by the subscription. Note, take(1)
is to close the subscription and avoid memory leaks.
<span class="username-block">{{currentUser?.username}}</span>
Edit #2
Apologies, your question asks about avoiding multiple HTTP calls.
In that case the service code should be
export class UserService {
private currentUser: any; // private because only want to access through getCurrentUser()
constructor(private http: Http) { }
getCurrentUser() {
return this.currentUser
? Observable.of(this.currentUser) // wrap cached value for consistent return value
: this.http.get('api/login/currentUser')
.do(data => { this.currentUser = data }) // cache it for next call
.catch(error => this.handleError(error, 'currentUser'));
}
Are you using routing? If so, you could set up a route resolver on your first route that needs the user data. The resolver then always waits to display the view until the resolver data is retrieved.
For example, here is one of my route resolvers. Notice that it calls a service to retrieve the data.
import { Injectable } from '@angular/core';
import { Resolve, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router';
import { Observable } from 'rxjs/Observable';
import { IMovie } from './movie';
import { MovieService } from './movie.service';
@Injectable()
export class MovieResolver implements Resolve<IMovie> {
constructor(private movieService: MovieService) { }
resolve(route: ActivatedRouteSnapshot,
state: RouterStateSnapshot): Observable<IMovie> {
const id = route.paramMap.get('id');
return this.movieService.getMovie(+id);
}
}
The resolver is then added to the appropriate route, something like this:
{
path: ':id',
resolve: { movie: MovieResolver },
component: MovieDetailComponent
},
That route won't be displayed then until the data is retrieved.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With