Matt Raible
Matt Raible

Reputation: 8614

TypeError: You provided 'undefined' where a stream was expected

I have an Ionic app that has a user provider with a signup() method:

doSignup() {
  // set login to same as email
  this.account.login = this.account.email;
  // Attempt to login in through our User service
  this.user.signup(this.account).subscribe((resp) => {
    this.navCtrl.push(MainPage);
  }, (err) => {
    //console.log('error in signup', err);
    // ^^ results in 'You provided 'undefined' where a stream was expected'
    //this.navCtrl.push(MainPage);

    // Unable to sign up
    let toast = this.toastCtrl.create({
      message: this.signupErrorString,
      duration: 3000,
      position: 'top'
    });
    toast.present();
  });
}

For some reason, this code never calls the success callback, only the error handler. When it does, it results in the error you see in the comment above.

My user.signup() method looks as follows:

signup(accountInfo: any) {
  return this.api.post('register', accountInfo).share();
}

My Api class looks as follows:

import { HttpClient, HttpParams } from '@angular/common/http';
import { Injectable } from '@angular/core';

/**
 * Api is a generic REST Api handler. Set your API url first.
 */
@Injectable()
export class Api {
  public static API_URL: string = 'http://localhost:8080/api';

  constructor(public http: HttpClient) {
  }

  get(endpoint: string, params?: any, reqOpts?: any) {
    if (!reqOpts) {
      reqOpts = {
        params: new HttpParams()
      };
    }

    // Support easy query params for GET requests
    if (params) {
      reqOpts.params = new HttpParams();
      for (let k in params) {
        reqOpts.params.set(k, params[k]);
      }
    }

    return this.http.get(Api.API_URL + '/' + endpoint, reqOpts);
  }

  post(endpoint: string, body: any, reqOpts?: any) {
    return this.http.post(Api.API_URL + '/' + endpoint, body, reqOpts);
  }

  put(endpoint: string, body: any, reqOpts?: any) {
    return this.http.put(Api.API_URL + '/' + endpoint, body, reqOpts);
  }

  delete(endpoint: string, reqOpts?: any) {
    return this.http.delete(Api.API_URL + '/' + endpoint, reqOpts);
  }

  patch(endpoint: string, body: any, reqOpts?: any) {
    return this.http.put(Api.API_URL + '/' + endpoint, body, reqOpts);
  }
}

I tried removing share() from user.signup(), and returning Observable<any>, but that doesn't help.

Upvotes: 29

Views: 102084

Answers (16)

I had a problem with my interceptor.ts

Old code

intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    return next.handle(request)
      .pipe(
        catchError((error: HttpErrorResponse) => {
          if (error instanceof HttpErrorResponse) {
            if (error.status === 401) {
              const url = this.remoteApi + this.refreshTokenRoute;
              if(url === request.url){
                this.clear();
                this.router.navigateByUrl('');
                return throwError(error);
              }
              else{
                return this.refreshToken(request, next).pipe(
                  switchMap(() => {
                    const authReq = request.clone({
                      headers: request.headers.set('Authorization', 'Bearer ' + this.localStorage.getToken().token),
                    });
                    return next.handle(authReq);
                  })
                );
              }
            }
          }
        })
      );
  }

New code

intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    return next.handle(request)
      .pipe(
        catchError((error: HttpErrorResponse) => {
          if (error instanceof HttpErrorResponse) {
            if (error.status === 401) {
              const url = this.remoteApi + this.refreshTokenRoute;
              if(url === request.url){
                this.clear();
                this.router.navigateByUrl('');
                return throwError(error);
              }
              else{
                return this.refreshToken(request, next).pipe(
                  switchMap(() => {
                    const authReq = request.clone({
                      headers: request.headers.set('Authorization', 'Bearer ' + this.localStorage.getToken().token),
                    });
                    return next.handle(authReq);
                  })
                );
              }
            }
            else{
              return next.handle(request);
            }
          }
        })
      );
  }

My problem was that I did not return the status if the error is not equal to 401

add for other request return next.handle(request);

Upvotes: 0

info2ankit
info2ankit

Reputation: 303

Request needs to handled at last.

For interceptor, you need to throw error or handle it your way. eg:

return next.handle(req).pipe(
  catchError((error: HttpErrorResponse) => {
    if (error.status === 401) {
      this._auth.logout();
      return throwError(error);  // <==========  do somethig at the end to your request
    } else if (error.status === 500) {
      this._swalAlertService.error('Oopss! looks like server issue.');
      return throwError(error); // <==========  do somethig at the end to your
    }
  })
);

Upvotes: 0

German Portillo
German Portillo

Reputation: 11

I got this error while working on my Ionic app and it turned out to be a null pointer in the service class where I was trying to read and write from session storage. It is unclear why the error shown wasn't more specific, but I believe that the thing reporting the error, the subscription in the component, was covering up the underlying error with its complaint about expecting a stream. I was trying to do:

sessionStorage.setItem('someItem', someObject.itemValue);

In my case someObject was now null because of a change I had made (but forgot to update the setItem call). After fixing that null reference, my "stream" error was fixed.

Upvotes: 0

Emmanuel Osimosu
Emmanuel Osimosu

Reputation: 6004

I had this issue because I forgot to import HttpClientModule.

Upvotes: 0

xianshenglu
xianshenglu

Reputation: 5309

For me, I met this because I was upgrading ngrx to 13 and rxjs to 7. Old code blocks are

let actions$
// other codes

provideMockActions(()=>actions$)

And I fixed it by

let actions$ = new ReplaySubject()
// other codes

provideMockActions(()=>actions$)

Upvotes: 0

King
King

Reputation: 2488

For me, I had a subscription piped with takeUntil like this:

this.route.queryParams
  .pipe(takeUntil(this.ngUnsubscribe))
  .subscribe(async(queryParams) => {
  });

I forgot to initialize this.ngUnsubscribe with an observable. When I set the value to an observable in the component (ngUnsubscribe = new Subject()), the error was gone.

Upvotes: 8

Jalal El-Shaer
Jalal El-Shaer

Reputation: 14710

I was using ngrx with Angular to store the token. I faced this error in my interceptor.ts.

The wrong code was this:

intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {

        this._store
            .pipe(
                select(fromSelectors.selectAuth),
            )
            .subscribe((state: fromReducers.AuthState) => {

                if (state.token != null) {
                    const clonedReq = req.clone({
                        headers: req.headers.set('Authorization', 'Bearer ' + state.token)
                    });
                    return next.handle(clonedReq);
                }
                else {
                    this.router.navigate(['login']);
                }
            });
    }

I fixed the code by returning the flow:

 intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {

        return this._store
            .pipe(
                select(fromSelectors.selectAuth),
                switchMap((state: fromReducers.AuthState) => {

                    if (state.token != null) {
                        const clonedReq = req.clone({
                            headers: req.headers.set('Authorization', 'Bearer ' + state.token)
                        });
                        return next.handle(clonedReq);
                    }
                    else {
                        this.router.navigate(['login']);
                        // return EMPTY stream
                        return of({}) as Observable<HttpEvent<any>>;
                    }
                })
            );

    }

Upvotes: 3

In my case, the error was caused by a different problem.

I was providing the service in two different points. I had written:

@Injectable({provideIn: 'root'}) and I was also providing the module in the app.module.ts. So if you encountered this error you can check this before going ahead

Upvotes: 0

Zachary Bennett
Zachary Bennett

Reputation: 962

In my case I was neglecting to return an Action in my NgRx Effect. I had a conditional statement which was only returning an Action on one condition.

Upvotes: 0

amit
amit

Reputation: 51

I had the same error and typical scenario was that I was returning different actions based on certain condition. But handling of one condition was missing and I was not returning anything under my effects. That's when this issue came up. It's always better to have a default return action to avoid such instances.

...mergeMap((res: FetchWorkflowStatusResponse) => {
   if(this){
      return action1
   } else if(that){
      return action2
   }
}

Now if some value other than this and that comes then there will not be any return statement and the error will be thrown.

Upvotes: 5

Leo Lanese
Leo Lanese

Reputation: 496

It happened to me doing unit-test on watch mode. Stop the build, trigger it again = the error is gone.

Upvotes: 0

Christian Vincenzo Traina
Christian Vincenzo Traina

Reputation: 10384

In my case, the error was caused by a different problem.

I was providing the service in two different points. I had written:

@Injectable({provideIn: 'root'})

and I was also providing the module in the app.module.ts. So if you encountered this error you can check this before going ahead

Upvotes: 0

FindOutIslamNow
FindOutIslamNow

Reputation: 1236

In my case I was returing with empty return:

if (...)
   return; // problem here

To fix, I returned the observed object:

if (...)
   return req.next();

Upvotes: 12

Bmaed Riasbet
Bmaed Riasbet

Reputation: 14998

Resolved here the right way :)

I have been facing this issue when trying to authenticate a user using JSON Web Token. in my case it's related to authentication interceptor.

Sending a request to authenticate a user doesn't have to provide a token since it doesn't exist yet.

Check that your interceptor include this:

if (req.headers.get('No-Auth') == "True")
            return next.handle(req.clone());

And that you provide {'No-Auth':'True'} to your header's request like this:

  authenticateUser(user): Observable<any> {
    const headers = new HttpHeaders({'No-Auth':'True'});
    headers.append('Content-Type', 'application/json');
    return this.httpClient.post(`${this.apiEndpoint}/auth/authenticate`, user, {headers: headers});
  }

Upvotes: 1

St&#233;phane Seyvoz
St&#233;phane Seyvoz

Reputation: 264

I faced this issue when creating a new project using generator-jhipster-ionic (yo jhipster-ionic giving v3.1.2), following Matt Raible's (OP) Use Ionic for JHipster to Create Mobile Apps with OIDC Authentication blog article, but choosing JWT authentication instead of OIDC.

The origin of the issue was a mix of problems.

I had the issue when running a Ionic app with the livereload server, CORS issues happening and Angular HTTP returning the classic HTTP failure response for (unknown url): 0 Unknown Error, where 0 is the HTTP error code. However, in the current case the issue was hidden by bad error handling at the Observable level.

When you follow Angular's HttpClient #Error Details section advices, and add an Observable pipe with a catchError operator on the HTTP POST call, you can get the proper HTTP error details. In this case, instead of going down to the rxjs/util/subscribeToResult.js:71 (TS source #L80) TypeError: You provided 'undefined' where a stream was expected default error case, it goes to rx/util/subscribeToResult.js:23 (TS source #L34), and the error is handled properly in the piped method.

After following the Observable calls, I found that the current default authentication interceptor, as seen in this template src/providers/auth/auth-interceptor.ts catches HTTP error 401 and does nothing for the others, basically muting them and preventing their propagation.

TL;DR In the JWT case, the solution is to simply remove the src/providers/auth/auth-interceptor.ts .catch(...) block, allowing error propagation to login.service.ts, and in its this.authServerProvider.login(credentials).subscribe((data) => { ... }, (err) => { ... }) error callback.

I believe the issue and solution could be the same for your OIDC case, its signup method, and error callback.

[Edit] Even more since the same .catch code can be found in the starter example mentioned in the first post comments: ionic-jhipster-starter - auth-interceptor.ts#L31

Upvotes: 24

SidMorad
SidMorad

Reputation: 974

I did face this issue and in my case responseType suppose to be text (because of designed API end-point) rather than default json.

import { HttpClient } from '@angular/common/http';

Before:

getToken(tokenCommand) {
    return this.http.post(API_BASE_URL + 'user/token', tokenCommand);
}

After fixed:

getToken(tokenCommand) {
    return this.http.post(API_BASE_URL + 'user/token', tokenCommand
                                                     , { responseType: 'text' });
}

I think this error message is too general and it will be nice if it's developers could provide more detail/helpful error message. Thanks.

Upvotes: 5

Related Questions