Welcome to Software Development on Codidact!
Will you help us build our independent community of developers helping developers? We're small and trying to grow. We welcome questions about all aspects of software development, from design to code to QA and more. Got questions? Got answers? Got code you'd like someone to review? Please join us.
Retrieve user details from service in Angular
Angular 12; .NET Core 3.1
Following a code-maze tutorial, I have a working authentication (registration/login) service using .NET Core Identity. After login, I would like to add 'username' data to my app's header component and a 'Welcome username' message in the home component.
I used ng g s shared/user
to create a user service. The service's getCurrentUser() code:
public currentUser?: any;
...
getCurrentUser(): Observable<any> {
if (this._authService.isAuthenticated) {
this._repoService.getData('api/accounts/user')
.subscribe(res => {
this.currentUser = res;
console.log('UserService: currentUser = ' + this.currentUser?.email);
});
}
return this.currentUser;
}
My header component and home component attempt to display the user's name (email) with the following:
getUsername(): string {
this._userService.getCurrentUser().subscribe(
res => {
this.userName = (res as ApplicationUserDTO).email;
}
);
return this.userName;
}
This does not result in the name being displayed. The browser console shows:
ERROR TypeError: Cannot read properties of undefined (reading 'subscribe')
.
Navigating to another page and back to the home page results in a slightly different console error: ERROR TypeError: this._userService.getCurrentUser(...).subscribe is not a function
However the console.log call in the user.service.ts correctly shows my logged in user's email.
The service originally tried returning an ApplicationUserDTO
which was resulting in only the ...subscribe is not a function
error. I checked this on SO and found Angular subscribe to a service: subscribe is not a function and adjusted my service code to return Observable<any>
. Making this change, added the ... cannot read properties of undefined (reading 'subscribe')
error.
Where have I gone wrong?
1 answer
The following users marked this post as Works for me:
User | Comment | Date |
---|---|---|
mcalex | (no comment) | Oct 25, 2021 at 09:43 |
There are quite a few bugs there :-)
Let's start with with the big one:
In Angular (and most client side web frameworks) requests to the server happen asynchronously. That is, when a method does an http call, the method doesn't wait for the response, but continues immediately.
So when you do:
this._repoService.getData('api/accounts/user')
.subscribe(res => {
this.currentUser = res;
console.log('UserService: currentUser = ' + this.currentUser?.email);
});
return this.currentUser;
What happens is this:
send http request to api/accounts/user
return this.currentUser
--- angular updates the UI and waits for clicks or other events
--- once the response arrives
this.currentUser = res;
console.log(...)
That is, you're returning this.currentUser
before it is assigned, which means that it is undefined.
The whole point of Observables is dealing with values that may not be here yet. You should read up about Observables and functional reactive programming.
Second bug: You're making an API call every time angular invokes getCurrentUser()
. If you're doing this in a data binding expression, that will be after every change detection. Change detection happens every time angular updates the UI in response to a user interaction or other event. Every keypress, every mouse click, every animation step. Your poor server will be swamped with requests. And that's totally pointless: A user is very unlikely to change their name every millisecond.
So you should be making this request once after login, or maybe in regular intervals if you expect relevant changes to user data.
Third bug: You've typed currentUser as any
. That's why TypeScript doesn't realize that returning currentUser is not compatible with the Observable the getCurrentUser() is declared to return. That is, if you had specified a more expressive type, TypeScript would have told you that returning currentUser doesn't make sense here. Help TypeScript help you be telling it about the types you expect, and avoid any
wherever possible.
Overall, I'd therefore start with something like this:
export class UserService {
currentUser?: ApplicationUserDTO;
constructor(repoService: RepoService) {
repoService.getData('api/accounts/user').subscribe(res => {
this.currentUser = res as ApplicationUserDTO;
console.log('UserService: currentUser = ' + this.currentUser?.email);
});
}
}
This presumes that your App is reinitialized after login, which happens naturally with many login methods. If NET Core Identity is different you could move the request from the constructor to a method that you manually invoke after login.
Having done that, everyone can access the current user through userService.getCurrentUser, bearing in mind that this is undefined while the user is not logged in, and while the user details are in the process of being fetched from the server. That's probably easier than dealing with Observables all over the place.
A remaining detail: What should happen when the call to get the user details fails. Should the call be retried? If so, how often? I'm leaving that to you.
0 comment threads