Sending Observables from linked API requests with concatMap

Using Angular 9 service class, I'm leveraging RxJS's concatMap to chain multiple HTTP calls. This involves passing the output of the first call as input to 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);
}

To return this.userInfo to a caller, my initial approach was to wrap it in an Observable (return of(this.userInfo)) and use 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);
      });

  }
}

Upon checking the browser console, I noticed that the service calls are successful in populating this.userInfo, but only after encountering an undefined error when attempting to use it:

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":"<a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="89eafbe8e0eea7eae8fce5efe0ece5edc9f1f1f1a7eae6e4">[email protected]</a>","username":"4bfd88a4-5439-4ad6-a399-xxx"}
main-es2015.8df8d853b157ca70b40a.js:1 Got user information: [object Object]

I've explored various solutions without success. Is there anything obvious that I might have overlooked?

Answer №1

It is possible that you are getting a undefined value because the assignment of this.userInfo happens asynchronously. One way to handle this correctly is by returning the HTTP observable and subscribing to it in the controller.

I have also made changes to extract the authorisationCode from the constructor to the ngOnInit() hook. Although the ngOnInit() hook usually runs after the constructor, there is no guarantee that the variable this.authorisationCode will have a value when the hook is triggered.

You can 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); }
  );
}

Answer №2

Initially, the this.UserInfo value is undefined when accessed through the observable. To handle this, one option is to wait for the API response or skip the first emitted value using the operator skip(1) on the observable created by of(this.userInfo). A more efficient approach would be to trigger the user observable only after the API call is completed. This can be achieved by creating a subject that signals completion of the API and then generating the observable from this subject instead of directly returning of(this.userInfo) (e.g.: using the operation userToObservable()).

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

storing information in localStorage using react-big-calendar

Incorporating react-big-calendar into my project, I encountered a problem where the events in the calendar would disappear upon page refresh despite saving them in localStorage. I had planned to store the events using localStorage and retrieve them later, ...

Dynamically attach rows to a table in Angular by triggering a TypeScript method with a button click

I need help creating a button that will add rows to a table dynamically when pressed. However, I am encountering an error when trying to call the function in TypeScript (save_row()). How can I successfully call the function in TypeScript and dynamically a ...

Fade in and out effect for popups on Leaflet markers

As I delve into developing a map web app using Angular, one challenge I face is incorporating fading popups for markers. I envision these popups fading in and out on their own as per a timer, but I lack the know-how to achieve this: My current code for cr ...

AADSTS9002326: Token redemption across origins is only allowed for the client type of 'Single-Page Application'. Origin of request: 'capacitor://localhost'

My Ionic application is having trouble authenticating in Azure. I followed the guidance from a stackoverflow thread: Ionic and MSAL Authentication Everything went smoothly except for iOS, where I encountered the following error: AADSTS9002326: Cross ...

What is the method for including word boundaries in a regex constructor?

export enum TOKENS { CLASS = 1, METHOD, FUNCTION, CONSTRUCTOR, INT, BOOLEAN, CHAR, VOID, VAR, STATIC, FIELD, LET, DO, IF, ELSE, WHILE, RETURN, TRUE, FALSE, NULL, THIS } setTokenPatterns() { let tokenString: s ...

Having trouble with combining two formgroups using concat in an Angular application

I have developed an angular form using reactive forms, with controls displayed on the left and right side. The controls on the right are labeled as "alternate" and are shown based on the selection of a radio button. To accommodate this, I have created two ...

What is the best way to implement bypassSecurityTrustResourceUrl for all elements within an array?

My challenge is dealing with an array of Google Map Embed API URLs. As I iterate over each item, I need to bind them to the source of an iFrame. I have a solution in mind: constructor(private sanitizer: DomSanitizationService) { this.url = sanitizer. ...

Accurate TS declaration for combining fields into one mapping

I have a data structure called AccountDefinition which is structured like this: something: { type: 'client', parameters: { foo: 3 } }, other: { type: 'user', parameters: { bar: 3 } }, ... The TypeScript declaration ...

Are there any methods within Angular 2 to perform Angular binding within a string?

When creating an HTML template with routing, such as shown below: <ul class="sb-sub-menu"> <li> <a [routerLink]="['clientadd']">Client Add</a> </li> </ul> It functions as expected. However, w ...

Testing the NestJS service with a real database comparison

I'm looking to test my Nest service using a real database, rather than just a mock object. While I understand that most unit tests should use mocks, there are times when testing against the actual database is more appropriate. After scouring through ...

Guide on integrating external libraries with Angular CLI

I've been working on incorporating external libraries into my project, and I've been following the instructions provided here. While I know it's possible to use CDNs in my index.html, I'm interested in learning how to do it using TypeS ...

Exploring the power of Vue CLI service in conjunction with TypeScript

I've recently set up a Vue project using the Vue CLI, but now I am looking to incorporate TypeScript into it. While exploring options, I came across this helpful guide. However, it suggests adding a Webpack configuration and replacing vue-cli-service ...

Angular2 is designed to break down complex applications into smaller, more manageable parts

Need for a Solution Recently, I was given responsibility of overseeing a large, legacy web application at work that involves multiple scrum teams and development teams. One major issue we face with this application is that whenever one team makes updates ...

The Angular 14 HTTP requests are being made twice

I am facing an issue with my API calling flow, which goes from the Controller to Service to BaseService. Controller code: this.salesManagerService.getNotificationsCounts(token).subscribe((response:any) => { if(response.response.status){ this.noti ...

Modules failing to load in the System JS framework

Encountering a puzzling issue with System JS while experimenting with Angular 2. Initially, everything runs smoothly, but at random times, System JS struggles to locate modules... An error message pops up: GET http://localhost:9000/angular2/platform/bro ...

Issue with Radio Button Value Submission in Angular 6 and Laravel 5.5

I developed a CRUD application utilizing Angular and Laravel 5.5. Within this application, I included three radio buttons, but encountered an error when trying to retrieve their values... A type error occurred indicating it was unable to read the data t ...

Encountering a Typescript issue with the updated Apollo server version within a NestJS application

After upgrading my nestJS application to use version 3.2 of apollo-server-plugin-base, I encountered two TypeScript errors related to a simple nestJS plugin: import { Plugin } from '@nestjs/graphql' import { ApolloServerPlugin, GraphQLRequest ...

Is it possible to utilize a mat-checkbox in place of a mat-label?

I'm attempting to build a mat-form-field with a checkbox that, when checked, will enable the form field. I believe this can be achieved through CSS adjustments, but I wonder if there is a simpler solution that I might be overlooking. <mat-f ...

Storing the selected value from dynamically generated options in Angular using ngFor

I have a collection of items called Fixtures. Each fixture contains a group of items named FixtureParticipants. Here is my process for generating choices: <tr *ngFor="let fixture of fixtures$ | async; let i=index"> <th scope="row& ...

An issue arises in Slate.js when attempting to insert a new node within a specified region, triggering an error

A relevant code snippet: <Slate editor={editor} value={value} onChange={value => { setValue(value); const { selection } = editor; // if nothing is currently selected under the cursor if (select ...