Reputation: 1655
Hi have a very simple function that call an endpoint to retrieve my user, I use it in my header to show the connected user but don't want to call the endpoint every time the header is rendered.
So I'm trying something like this:
public getUser(): User {
if (!this.user) {
this.http.get<User>(environment.apiRoot + '/me').subscribe(res => {
this.setUser(res);
return this.user.value;
}, err => {
});
} else {
return this.user.value;
}
}
With the setUser
function:
public setUser(u: User): void {
if (!this.user) {
this.user = new BehaviorSubject(u);
} else {
this.user.next(u);
}
}
And the BehaviorSubject
: private user: BehaviorSubject<User>;
In my header component I call it like this in the ngOnInit
:
this.user = this.appState.getUser();
console.log(this.user);
But the user in the console.log is always undefined, why it doesn't wait for the subscription in the getUser
function ?
Upvotes: 6
Views: 32316
Reputation: 1121
If you want your getUser
function to wait for subscription to finish, you will have to return observable. And then use .subscribe()
with this.appState.getUser()
.
public getUser(): Observable<User> {
if (!this.user) {
return this.http.get<User>(environment.apiRoot + '/me').pipe(map(res => {
this.setUser(res);
return this.user.value;
}, err => {
}));
} else {
return of(this.user.value);
}
}
this.appState.getUser().subscribe(user => {
console.log(user)
});
Upvotes: 0
Reputation: 8879
You have an asynchronous function that you're calling with mixed reactive and imperative programming. When it comes to asynchronous functions, you should stay with the reactive approach and not try to determine when a certain code has finished, but instead just wait for it to finish and then invoke the callback or the responsible handler subsequently.
In your code the getUser()
function is an async
function, since you have the inner http.get()
call. The http.get()
will return an Observable
, which you subscribe
to inside your function unnecessarily. What you should do instead is return the inner observable
from the function, and subscribe
where you actually need it.
We can rewrite your getUser
function like below (note that there is no need for the imperative setUser
-function)
public getUser(): Observable<User> {
return this.http.get<User>(environment.apiRoot + '/me');
}
and in your outer scope subscribe to it like
this.appState.getUser().subscribe(user => {
console.log(user)
});
To take it further, this makes it easier to work with observables in your templates as well.
Suppose you assign the returned observable to a variable, user$
, you don't even have to subscribe to it!
Just get the user like
this.user$ = this.appState.getUser();
to get your observable, and then in your template use the async
pipe to let Angular subscribe and unsubscribe automatically.
<span>Hello, my name is {{user$ | async}}!</span>
Upvotes: 9