1

I have service which i pass user token to server and return results to component but it keeps returning token: undefined while my token is exist.

Code

Note: I commented each part for better understanding.

Service

export class GroupsService {

    token: any;

    constructor(
        private storageIonic: NativeStorage,
        private env: EnvService,
        private http: HttpClient,
    ) {
        // Get token
        this.storageIonic.getItem('token').then((token) => {
        this.token = token.access_token;
        }).catch(error => console.error(error));
    }

    getGroups(): Observable<any> {
        // I also add this here to make sure that i will get token in any case, yet it's returning undefined
        if (this.token === undefined) {
        this.storageIonic.getItem('token').then((token) => {
            this.token = token.access_token;
        }).catch(error => console.error(error));
        }

        console.log('token: ', this.token);  // undefined
        const httpOptions = {
        headers: new HttpHeaders({
            Authorization : this.token,  //sending token to server
            Accept: 'application/json, text/plain',
            'Content-Type': 'application/json'
        })
        };
        return this.http.get(`${this.env.GROUPS}`, httpOptions).pipe(
        map(groups => groups)
        );
    }
}

Component

export class Tab1Page implements OnInit {

    groups: any[] = [];
    groupsOpts = {
      loop: false,
      slidesPerView: 3,
      slidesPerColumn: 2
    };

    constructor(
      private groupsService: GroupsService,
      private menu: MenuController,
    ) {
      this.menu.enable(true);
      this.getGroups();
    }

    ngOnInit() {
      //
    }
    // I added async/await yet result hasn't change.
    async getGroups() {
      await this.groupsService.getGroups().subscribe((res) => {
        console.log('res: ', res);
        console.log('res data: ', res.data);
        console.log('res data data: ', res.data.data);
        for (const group of res.data) {
          this.groups.push(group);
        }
      });
    }
}

Any idea how to solve this issue?

2
  • I am not sure about this but using async/await with observable may be redundant since an observable is an open connection and will keep getting data if the sender sends something, that means there is nothing to wait. You may consider changing your observables to promises if the response is sent once, with this way your code can wait if there is an async operation. E.g. observables are similar to stream connections while promises are similar to one time HTTP requests Commented May 16, 2020 at 6:52
  • @Anıl Any suggestion that comes with sample code is most welcome :) please share your solution it might help. Commented May 16, 2020 at 6:54

4 Answers 4

3

You can use switchMap to pipe the token promise data.

import { from } from "rxjs";

export class GroupsService {
  token: any;
  getGroups(): Observable<any> {
    // I also add this here to make sure that i will get token in any case, yet it's returning undefined
    const tokenPromise =
      this.token === undefined
        ? this.storageIonic.getItem("token")
        : Promise.resolve(this.token);

    return from(tokenPromise).pipe(
      switchMap((token) => {
        this.token = token;
        const httpOptions = {
          headers: new HttpHeaders({
            Authorization: this.token, //sending token to server
            Accept: "application/json, text/plain",
            "Content-Type": "application/json",
          }),
        };
        return this.http
          .get(`${this.env.GROUPS}`, httpOptions)
          .pipe(map((groups) => groups));
      })
    );
  }
}
Sign up to request clarification or add additional context in comments.

2 Comments

And i keep async/await in component or remove them?
dont mix async-await with observer. Else hard to maintain. with observer you wont need async-await. unless you want to call on purpose.
0

You need to wait for the promise to return a value before making the http request. You could put your token logic into its own function that returns a promise, and then start an observable using the RxJS from function. Once the promise returns a value you can then use switchMap to make your http request.

I have included your map in the RxJS pipe, although it's doing nothing at the moment.

export class GroupsService {

  token: any;

  constructor(
    private storageIonic: NativeStorage,
    private env: EnvService,
    private http: HttpClient,
  ) {
  }

  getGroups(): Observable<any> {
    return from(this.getToken()).pipe(
      switchMap(() => {
        const httpOptions = {
          headers: new HttpHeaders({
            Authorization : this.token,  //sending token to server
            Accept: 'application/json, text/plain',
            'Content-Type': 'application/json'
          })
        };

        return this.http.get(`${this.env.GROUPS}`, httpOptions);
      }),
      map(groups => groups)
    );          
  }

  private getToken(): Promise<any> {
    if (this.token) {
      return new Promise((resolve, reject) => resolve(this.token));    
    }

    return this.storageIonic.getItem('token')
      .then((token) => {
          this.token = token.access_token;
      }).catch(error => console.error(error));
  }
}

Comments

0
 this.storageIonic.getItem('token').then((token) => {
            this.token = token.access_token;
        }).catch(error => console.error(error));
 }

This call is asychronous, you will not get the token in the next line Try it out like this

export class GroupsService {

    token: any;

    constructor(
        private storageIonic: NativeStorage,
        private env: EnvService,
        private http: HttpClient,
    ) {
        // Get token
        this.storageIonic.getItem('token').then((token) => {
            this.token = token.access_token;
        }).catch(error => console.error(error));
    }

    getGroups(): Observable < any > {
        // I also add this here to make sure that i will get token in any case, yet it's returning undefined
        let response = new Observable<any>();
        if (this.token === undefined) {
            this.storageIonic.getItem('token').then((token) => {
                this.token = token.access_token;

                console.log('token: ', this.token); // undefined
                const httpOptions = {
                    headers: new HttpHeaders({
                        Authorization: this.token, //sending token to server
                        Accept: 'application/json, text/plain',
                        'Content-Type': 'application/json'
                    })
                };
                response = this.http.get(`${this.env.GROUPS}`, httpOptions).pipe(
                    map(groups => groups)
                );
            }).catch(error => console.error(error));
        }

        return response;

    }
}

4 Comments

this line seems to be extra }).catch(error => console.error(error));
I have corrected this. Basically the idea is to put the rest of the code inside storageIonic.getIem response
as we put all codes in if statement now it returns this error A function whose declared type is neither 'void' nor 'any' must return a value.ts(2355)
Initialized the variable to fix this. Please check now
0

I am not sure about the source of your problem but you may want to change your observables to promises as I have explained in the comments.

    getGroups(): Promise<any> {
        // I also add this here to make sure that i will get token in any case, yet it's returning undefined
        if (this.token === undefined) {
        this.storageIonic.getItem('token').then((token) => {
            this.token = token.access_token;
        }).catch(error => console.error(error));
        }

        console.log('token: ', this.token);  // undefined
        const httpOptions = {
        headers: new HttpHeaders({
            Authorization : this.token,  //sending token to server
            Accept: 'application/json, text/plain',
            'Content-Type': 'application/json'
        })
        };
        return this.http.get(`${this.env.GROUPS}`, httpOptions).toPromise();
        // Also probably the below one works too, you can try to find the proper syntax, it was something like this
        return this.http.get(`${this.env.GROUPS}`, httpOptions).pipe(
        map(groups => groups)
        ).toPromise();
    }

I have changed some lines. (Change method signature, Observable => Promise and add toPromise() to return line)

You can call the method like below.

const response = await getGroups(); // This one will return the response of the request. 
If you debug the code you will see that your code will wait here until it gets a response.

// IF YOU DO LIKE BELOW IT WON'T MAKE ANY SENSE
const response = getGroups(); // This will not make the call, it will just return the request object.
// In order to do the operation defined in a promise, you must call it with await prefix.

You may need apply the solution above to other parts of your code too. E.g. you are initializing the token under the constructor which it is not a good practice as I know, you may want to move that initialization under onInit() and make onInit function async. This way you can make sure that token is defined when you are making the call, otherwise your token may not beer initialized while you are making the request. And since you are not waiting your code in undefined check same thing will happen again. (Convert your storageIonic.getItem(token: string) function to a promise and return the token from that function)

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.