Reputation: 3
I am struggling with a simple concept I can easily do in C#, but not in Angular.
I am trying to store the user profile in a service I add in every component, so I do not get the profile every time a component is loaded/displayed.
So, I created a service, where the constructor gets the user profile, and saves it in a variable, and use a getter for all my components.
The issue I have, is that when the components call the getter, the variable is net set yet, as the service has not responded yet. This leads to an undefined error and breaks the component as the profile is vital in the code of the component.
In C#, I would simply put 1 await when getting the profile, so that everything else can run without waiting, as I know the data is there.
But in Angular, I tried that, but it does not seem to work.
export class SharedService {
private userProfile;
constructor(private http: HttpService) {
this.initProfile();
}
async initProfile(): Promise < void > {
this.userProfile =await this.http.getProfile().subscribe((data: any) => {
//...
}
});
getUserProfile() {
return this.userProfile;
}
}
The getprofile:
getProfile() {
return this.shttp.get(environment.apiEndpoint + 'Getprofile/', {
headers: new HttpHeaders({
'Content-Type': 'application/json',
Accept: '*/*'
})
});
}
In the components, when I call my getUserProfile function, the service answers immediately with the empty userProfile.
EDIT: I manage to have the desired behavior, with very nasty code snippet, but this should make clear what I am trying to accomplish. In my profile.component.ts:
async ngOnInit(): Promise<void> {
while (this.profile == null) {
await this.sharedService.sleep(500);
this.profile = this.sharedService.getUserProfile();
}
if (this.profile.isadmin){
this.CalculateStuff();
}
//
//imagine here even more code using the this.profile variable in if statements,
//cases, etc.
//
}
The sleep function
sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
The fact I have to code a sleep function to achieve this, is a clear signal to me that i am doing something wrong. That is why I am here.
Upvotes: 0
Views: 2302
Reputation: 2895
You're almost there. You're awaiting a subscription which doesn't work with async
/ await
. Just do the following. Just convert it to a promise so you can await
it.
async ngOnInit(): Promise<void> {
this.profile = await this.sharedService.getUserProfile().toPromise();
if (this.profile.isadmin){
this.CalculateStuff();
}
}
This question is similar How exactly works this async-await method call? is it correct?
To give you a bit more detail. What you're doing is assigning this.userProfile
to the RXJS subscription object. This will not give you the value that you need. If you want to stay with Rxjs as suggested in one of the comments. You can just do all the logic for the user profile inside the subscribe block.
this.http.getProfile().subscribe((userProfile: any) => {
// do userProfile actions here
});
//Since subscribe is an async callback, anything outside that block will have userProfile undefined.
Also as mentioned, a rudimentary knowledge of RxJs will be very useful and solve a lot of your early issues. https://www.learnrxjs.io/learn-rxjs/concepts/rxjs-primer
Upvotes: 0
Reputation: 31125
One other way would be to make the userProfile
in the service a multicast observable (like RxJS ReplaySubject
with buffer 1). That way there isn't a need to mix observables with promises.
Service
import { ReplaySubject } from 'rxjs';
export class SharedService {
private userProfile = new ReplaySubject<any>(1);
constructor(private http: HttpService) {
this.initProfile();
}
initProfile() {
this.http.getProfile().subscribe({
next: (data: any) => {
this.userProfile.next(data); // <-- push the new profile
},
error: (error: any) => { } // <-- handle error
});
getUserProfile(): Observable<any> { // <-- return observable here
return this.userProfile.asObservable();
}
}
Now in the component you could subscribe to the subject from the service. Additionally you could use takeUntil
operator with Subject
to close the open subscription when the component is closed/destroyed.
Component
import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
export class SomeComponent implements OnInit, OnDestroy {
public userProfile: any;
private closed$ = new Subject<any>();
constructor(private shared: SharedService) { }
ngOnInit() {
this.shared.getUserProfile().pipe(
takeUntil(this.closed$)
).subscribe({
next: (data: any) => {
this.userProfile = data;
// other statements that depend on `this.userProfile`
}
});
}
ngOnDestroy() {
this.closed$.next(); // <-- close open subscription(s)
}
}
Upvotes: 2