Reputation: 3538
In an Angular 9 service class I'm chaining a couple of HTTP calls using RxJS's concatMap
, using the output of the first call as input the second:
getUserDetails(authorisationCode: string): Observable<UserInfo> {
this.getAuthorisationTokens(authorisationCode)
.pipe(
concatMap(authorisationTokens => this.getUserInfo(authorisationTokens.access_token)))
.subscribe(
(data: UserInfo) => {
this.userInfo = data;
console.log(JSON.stringify(this.userInfo));
console.log(JSON.stringify(data));
},
(err: any) => console.log('Error getting user info details : ' + err),
() => {
console.log('Got user information: ' + this.userInfo);
}
);
return of(this.userInfo);
}
I want to return this.userInfo
to a caller and my first naive idea was to wrap it in an Observable (return of(this.userInfo)
) and call it like this:
export class LandingComponent implements OnInit {
username: string;
userFirstName: string;
email: string;
constructor(private route: ActivatedRoute, private userService: UserDataService) {
this.authorisationCode = route.snapshot.queryParamMap.get('code');
console.log('Code was ' + this.authorisationCode);
}
ngOnInit(): void {
this.userService.getUserDetails(this.authorisationCode)
.subscribe((data: UserInfo) => {
this.userFirstName = data.given_name;
this.username = data.preferred_username;
this.email = data.email;
console.log('Got: ' + this.userFirstName + ', ' + this.username + ', ' + this.email);
});
}
}
I can see from the browser console that the calls to my services are succeeding and populating my object this.userInfo
, but only after I've tried to use it and got an undefined error:
Code was 2ffa40f9-5e71-4f29-8ddd-318e8d0b99bc
main-es2015.8df8d853b157ca70b40a.js:1 Getting authorisation tokens in exchange for authorisation code 2ffa40f9-5e71-4f29-8ddd-318e8d0b99bc
main-es2015.8df8d853b157ca70b40a.js:1 Header: [object Object]
main-es2015.8df8d853b157ca70b40a.js:1 Body: grant_type=authorization_code&redirect_uri=https://xxx/landing/&client_id=xxx&code=2ffa40f9-5e71-4f29-8ddd-318e8d0b99bc&client_secret=xxx
main-es2015.8df8d853b157ca70b40a.js:1 TOKEN endpoint: https://xxx.amazoncognito.com/oauth2/token
TOKEN endpoint: https://xxx.amazoncognito.com/oauth2/token
main-es2015.8df8d853b157ca70b40a.js:1 ERROR TypeError: Cannot read property 'given_name' of undefined
...
USERINFO endpoint https://xxx.amazoncognito.com/oauth2/userInfo
main-es2015.8df8d853b157ca70b40a.js:1 USERINFO endpoint https://xxx.amazoncognito.com/oauth2/userInfo
main-es2015.8df8d853b157ca70b40a.js:1 {"sub":"4bfd88a4-5439-4ad6-a399-71b02034dfa1","email_verified":"true","given_name":"Craig","family_name":"Caulfield","email":"[email protected]","username":"4bfd88a4-5439-4ad6-a399-xxx"}
main-es2015.8df8d853b157ca70b40a.js:1 Got user information: [object Object]
I've tried to apply the following questions, but I can't find a way to apply them to my case:
So, my async thinking is wrong. Is there something obvious that I haven't done?
Upvotes: 0
Views: 276
Reputation: 31105
You could be returning undefined
because this.userInfo
is assigned value asynchronously. One correct way would be to return the HTTP observable and subscribe to it in the controller.
I've also moved extracting the authorisationCode
from the constructor
to the ngOnInit()
hook. While usually the ngOnInit()
hook will be triggered after the constructor
is called, it can't be guaranteed that the variable this.authorisationCode
would have been assigned the value by the time the hook is triggered.
Try the following
Service
getUserDetails(authorisationCode: string): Observable<any> {
return this.getAuthorisationTokens(authorisationCode)
.pipe(
concatMap(authorisationTokens => this.getUserInfo(authorisationTokens.access_token))
);
}
Controller
constructor(private route: ActivatedRoute, private userService: UserDataService) { }
ngOnInit(): void {
const authorisationCode = route.snapshot.queryParamMap.get('code');
this.userService.getUserDetails(authorisationCode).subscribe(
(data: UserInfo) => {
this.userFirstName = data.given_name;
this.username = data.preferred_username;
this.email = data.email;
console.log('Got: ' + this.userFirstName + ', ' + this.username + ', ' + this.email);
},
(err: any) => { console.log('Error getting user info details : ' + err) },
() => { console.log('Got user information: ' + data); }
);
}
Upvotes: 1
Reputation: 161
When you access this.UserInfo via the observable for the first time it is undefined. You need to wait until the API returns or you have to ignore the first value emitted by observable creted by of(this.userUnfo) (e.g.: apply skip(1) operator). A better solution wohl be to fire the this.user observable only after the API returns. You can create a subject, which fires once the API completes, and instead of returning of(this.userInfo) yiu simply create the observable from zhe above mentiined subject (e.g.: user ToObservable() operation)
Upvotes: 0