简体   繁体   中英

Rxjs Nested observer is not executed

I've read a lot of documentation, articles and different thread about how to nest observers in RxJs and Angular, I still missing something and not able to get a result at the end.

Here is my code:

page.ts

export class LiabilitiesPage implements OnInit {
     constructor(
        private liabilityService: LiabilityService,
        private router: Router
     ) {}

     refreshLiabilities() {
      // Get the liabilities
      console.log('refreshing') // passing there
      this.liabilityService.getAllLiabilities().subscribe(
      (response: Liability[]) => {
        console.log(response); // <=== Never pass there !

        if (response) {
          this.liabilities = response;
        } else {
          // empty response code
        }
      }, error => {
        // response error code (never passing there either)
      }
  }
}

liability.service.ts

// all the needed imports

@Injectable({
  providedIn: 'root'
})
export class LiabilityService {
  constructor(
    private authService: AuthService,
    private http: HttpClient,
    ) {}

  // first try : Do not send the http request
  getAllLiabilities(): Observable<Liability[]> {
    return this.authService.getOptions()
        .pipe(
            tap(options => this.http.get<Liability[]>(this.url + 'me/', options))
        );
  }

    // try 2 : Doesn't work either
    getAllLiabilities(): Observable<Liability[]> {
      return this.authService.getOptions()
        .pipe(
            switchMap(options => this.http.get<Liability[]>(this.url + 'me/', options)), // at this point I tried pretty much every operators (map, mergeMap etc.)
            withLatestFrom()
        ); 
  }
    /* this code was working before that I transformed the authService.getOptions in observable (it was just returning the options synchronyously before)
getAllLiabilities(): Observable<Liability[]> {
  return this.http.get<Liability[]>(this.url + 'me/', this.authService.getOptions());
  }*/
}

auth.service.ts


public getOptions(): Observable<any> {
      return new Observable((observer) => {
          this.storage.get('authToken').then((token) => {
              console.log('passing') // Pass here
              if (token && typeof token.auth_token !== 'undefined') {
                  console.log('passing') // pass here as well
                  this.isLoggedIn = true;
                  this.token = token.auth_token;
              }
              // it is returning the value
              return {
                  headers: this.headers.set('Authorization', 'Bearer ' + this.token),
                  params: new HttpParams()
              };
          })
      });
    }

I tried almost all the possible operator combinations to make it works in the liabilityService without any success.

Problem:

The problem is that my page.ts subscribes to the this.http.get<Liability[]>(this.url + 'me/', options) observer but none xhr request is fired. The http get observer is never executed and I don't understand what I'm missing there.

I'm just starting experimenting Angular, but if I understood correctly the operators should do the mapping and flattening but this looks to never happen.

Bonus question:

I'm not catching either why the initial code:

return this.http.get<Liability[]>(this.url + 'me/', this.authService.getOptions());

is returning an Observable<Liability[]>

and with the switchMap:

switchMap(options => this.http.get<Liability[]>(this.url + 'me/', options))

It is returning a Observable<HttpEvent<Liability[]>>

If somebody has a clue and the time to answer me on that, it would be amazing

You have a problem in the promise callback then() :

this.storage.get('authToken').then((token) => {
    return something; // this won't work.
})

instead you can use from , which will convert your promise to an observable.

import { from, Observable } from 'rxjs';
import { map } from 'rxjs/operators';

public getOptions(): Observable<any> {
    return from(this.storage.get('authToken')).pipe(map(token => {
        return headers with token.
    }));
}

So you could rewrite your code like this:

auth service:

private token: string | null = null;


public getOptions(): Observable<any> {
  return this.getToken().pipe(
    map(token => {
      return {
        headers: this.headers.set('Authorization', 'Bearer ' + token),
        params: new HttpParams()
      };
    })
  );
}


private getToken(): Observable<string | null> {
  if (this.token) {
    return of(this.token);
  }

  return from(this.storage.get('authToken')).pipe(
    map(token => token?.authToken || null),
    tap(token => this.token = token)
  );
}

then you can use a switchmap:

getAllLiabilities(): Observable<Liability[]> {
  return this.authService.getOptions().pipe(
    switchMap(options => this.http.get<Liability[]>(this.url + 'me/', options))
  );
}

Update

The reason for getting HttpEvent<T> is because when the overload of .get() receives an any object it leaves the http event handling entirely up to you. If you want it to return the provided element type, you have to satisfy the proper overload. You can achieve that doing it like so:

Instead of returning the entire options, we only return the headers, which should be enough, because we do not really have enough to say about the rest of the options.

auth service

private token: string | null = null;

public createTokenHeaders(): Observable<HttpHeaders> {
  const headers = new HttpHeaders();
  return addToken(headers);
}

public addToken(headers: HttpHeaders): Observable<HttpHeaders> {
  return this.getToken().pipe(
    map(token => headers.set('Authorization', 'Bearer ' + (token || '')))
  );
}

private getToken(): Observable<string | null> {
  if (this.token) {
    return of(this.token);
  }

  return from(this.storage.get('authToken')).pipe(
    map(token => token?.authToken || null),
    tap(token => this.token = token)
  );
}

Then use it like so:

getAllLiabilities(): Observable<Liability[]> {
  const url = this.url + 'me/';
  const headers = new HttpHeaders();
  return this.authService.addToken(headers).pipe(
    switchMap(updatedHeaders => this.http.get<Liability[]>(url, { headers: updatedHeaders }))
  );
}

or:

getAllLiabilities(): Observable<Liability[]> {
  const url = this.url + 'me/';
  return this.authService.createTokenHeaders().pipe(
    switchMap(headers => this.http.get<Liability[]>(url, { headers }))
  );
}

Note: Make sure you use the headers returned from the call to addToken. Reusing your own instantiated headers will not work because setting a header always returns a new HttpHeaders object. It is immutable.

StackBlitz Example

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM