Communities

Writing
Writing
Codidact Meta
Codidact Meta
The Great Outdoors
The Great Outdoors
Photography & Video
Photography & Video
Scientific Speculation
Scientific Speculation
Cooking
Cooking
Electrical Engineering
Electrical Engineering
Judaism
Judaism
Languages & Linguistics
Languages & Linguistics
Software Development
Software Development
Mathematics
Mathematics
Christianity
Christianity
Code Golf
Code Golf
Music
Music
Physics
Physics
Linux Systems
Linux Systems
Power Users
Power Users
Tabletop RPGs
Tabletop RPGs
Community Proposals
Community Proposals
tag:snake search within a tag
answers:0 unanswered questions
user:xxxx search by author id
score:0.5 posts with 0.5+ score
"snake oil" exact phrase
votes:4 posts with 4+ votes
created:<1w created < 1 week ago
post_type:xxxx type of post
Search help
Notifications
Mark all as read See all your notifications »
Q&A

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.

Comments on Retrieve user details from service in Angular

Parent

Retrieve user details from service in Angular

+3
−0

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?

History
Why does this post require moderator attention?
You might want to add some details to your flag.
Why should this post be closed?

0 comment threads

Post
+3
−0

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.

History
Why does this post require moderator attention?
You might want to add some details to your flag.

1 comment thread

Many thanks @meriton lol, ok so the code I had sort of took care of bugs #2 and #3 ie I had, a) wra... (3 comments)
Many thanks @meriton lol, ok so the code I had sort of took care of bugs #2 and #3 ie I had, a) wra...
mcalex‭ wrote over 2 years ago

Many thanks @meriton lol, ok so the code I had sort of took care of bugs #2 and #3 ie I had, a) wrapped the api call in the service in if(!this.currentUser) { } to save hitting the server and b) like you said, I was returning the ApplicationUserDTO type instead of any.

I'm still a bit confused about the Observable bit. I thought that by making it an Observable and subscribing, the framework automatically dealt with handling the awaited result. Implementing your approach resulted in some interesting behaviour. The home component (with 'Welcome user') works as desired. After login, page displays the correct message. :-) The header doesn't work, at all and a separate page ('user-details') where I show all fields of the ApplicationUser works the third time I access the page. I will investigate further and edit the question post tomorrow. Thanks again

meriton‭ wrote over 2 years ago

To clarify: Bug 3 is about returning an ApplicationUserDTO rather than an Observable<ApplicationUserDTO>. Those are different types, offering different properties and methods. TypeScript doesn't realize this because you are declaring public currentUser?: any;

Angular will not rewrite the code of your component class. If your code returns this.currentUser before you assign it, undefined will be returned. I am not sure why you'd expect otherwise.

As for the header and user-details not working, I'd need to see the code of that header, or a more precise description of the bug, to help.

mcalex‭ wrote over 2 years ago

Bug 3 is about returning an ApplicationUserDTO: Understood, it was the SO answer that sent me down the Observable path. re: returning before being assigned, I figured that returning a subscribed observable was similar to return awaiting in C#. I also didn't realise about the observable being polled on every single event. I thought it was only when the component made a request (page load etc), so that's good to know.. I'm new at Angular, lol. I didn't find RxJS doco easy to read: even the tl/dr; refers to a package I don't know, but I will be reading up on functional reactive programming this weekend. There's a locked question on some other site relating to FRP - I might ask in meta if a similar, 'what's it all about?' question would be on-topic here.