Angular 9 HTTP Post request Error. You provided 'undefined' where a stream was expected. You can provide an Observable, Promise, Array, or Iterable

Hi,
I have been setting up a app.service file for various API calls to a backend system. I recently upgraded the platform from Ionic 3 to Ionic 5 and I did the appropriate updates to the HTTP request library. But whenever I try to make a call to the API I always receive the following error:

vendor.js:42843 ERROR TypeError: You provided 'undefined' where a stream was expected. You can provide an Observable, Promise, Array, or Iterable.
    at subscribeTo (vendor.js:121254)
    at subscribeToResult (vendor.js:121414)
    at CatchSubscriber.push../node_modules/rxjs/_esm5/internal/operators/catchError.js.CatchSubscriber.error (vendor.js:113654)
    at MapSubscriber.push../node_modules/rxjs/_esm5/internal/Subscriber.js.Subscriber._error (vendor.js:110606)
    at MapSubscriber.push../node_modules/rxjs/_esm5/internal/Subscriber.js.Subscriber.error (vendor.js:110586)
    at MapSubscriber.push../node_modules/rxjs/_esm5/internal/Subscriber.js.Subscriber._error (vendor.js:110606)
    at MapSubscriber.push../node_modules/rxjs/_esm5/internal/Subscriber.js.Subscriber.error (vendor.js:110586)
    at FilterSubscriber.push../node_modules/rxjs/_esm5/internal/Subscriber.js.Subscriber._error (vendor.js:110606)
    at FilterSubscriber.push../node_modules/rxjs/_esm5/internal/Subscriber.js.Subscriber.error (vendor.js:110586)
    at MergeMapSubscriber.push../node_modules/rxjs/_esm5/internal/OuterSubscriber.js.OuterSubscriber.notifyError (vendor.js:110097)

This is the API function that is called whenever a user tries to log in:

import {throwError as observableThrowError, Observable} from 'rxjs';

import {catchError, map} from 'rxjs/operators';
import {Component, ViewChild} from '@angular/core';
import {Injectable} from '@angular/core';
import {HttpClient, HttpHeaders} from '@angular/common/http';
import {env} from '../env/env';
import {ToastController, IonApp, NavController} from '@ionic/angular';

import {NavigationExtras} from '@angular/router';

@Injectable()
export class ApiService {
    httpOptions = {
        headers: new HttpHeaders({
            'Content-Type': 'application/json',
            'No-Auth': 'True',
        }),
    };
  constructor(
      private http: HttpClient,
      public toastCtrl: ToastController,
      public appCtrl: NavController,
      // public device: Device
  ) {
  }
addUser(user) {

    const headers = new HttpHeaders();
    headers.append('Accept', 'application/json');
    headers.append('Content-Type', 'application/json');
    const requestOptions = {headers};

    let userDob = user.dob.split('-');
    userDob = userDob.join('/');

    const optInDate = new Date();
    const month = optInDate.getMonth() + 1;
    const day = optInDate.getDate();
    const year = optInDate.getFullYear();
    const oDate = month + '/' + day + '/' + year;


    return new Promise<any>((resolve, reject) => {
      const payload = {
        firstName: user.firstname,
        middleName: '',
        lastName: user.lastname,
        doorNo: user.address,
        streetAddress: '',
        city: user.city,
        state: user.state,
        zip: user.zipcode,
        country: 'US',
        gender: user.gender,
        dateOfBirth: userDob,
        emailId: user.email,
        patientConsent: 'Y',
        phoneNo: user.mobile,
        optIn: oDate,
        optOut: oDate,
        userType: 'AppUser'
      };

      this.http.post(`${env.baseApiUrl}/addUser/`, payload, this.httpOptions).pipe(
            map((res: Response) => res),
            catchError(error => {
                console.log(error);
                reject(error);
                // changes made by Jas 2020_03_31 changed toast
                this.showLongToast('Inside Send Verification Code Method');
                return this.handleError(error);
            }), ).subscribe(data => resolve(data));
          });
  }

This is where I am calling the actual function to log the user in, contained within the login.page.ts:

 signIn() {

    if ( (this.user.username) && (this.user.password) ) {

      this.loadingCtrl.create({
        message: 'Please wait...'
      }).then((res) => {
        res.present();

        this.ApiService.login(this.user)
            .then(r => {
              localStorage.setItem('userToken', r.token);
              localStorage.setItem('userId', r.userId);

              // Check if user has reminders.
              this.ApiService.getReminders()
                  .then(r => {
                    res.dismiss();
                    this.navCtrl.navigateRoot('/dashboard');
                    this.ApiService.showToast('Logged In');
                  })
                  .catch(e => {
                    this.navCtrl.navigateRoot('/medicines');
                    res.dismiss();
                    this.ApiService.showToast('Logged In');
                  });

              this.menuCtrl.enable(true);
              this.menuCtrl.get().then((menu: HTMLIonMenuElement) => {
                menu.swipeGesture = true;
              });

              if (localStorage.getItem('userNotifications') === null) {
                localStorage.setItem('userNotifications', JSON.stringify([]));
              }

            })
            .catch(e => {
              console.log (e);
              if (e.status == '404') {
                this.ApiService.showToast('User does not exist');
              }
              if (e.status == '400') {
                this.ApiService.showToast('Invalid credentials');
                this.showForgotPassword = true;
              }
              res.dismiss();
            });
      });
    } else {
      this.ApiService.showToast('Please enter username and password');
    }

  }

There are a lot of things I would change here.

  • You probably don’t need either of the custom headers. You definitely don’t need Content-Type and shouldn’t be setting it manually. Let HttpClient manage that.
  • I would use date-fns for all the date mangling
  • addUser should have types for both its user parameter and its return value
  • I would not manually instantiate a Promise here. I would work with the Observable that HttpClient gives me instead. If you absolutely insist on returning a Promise, I would use the toPromise operator.
  • I would not be doing error management where you are. Either shift it to an interceptor if it can be centralized, or deal with it in calling code if it can’t be, but I would not have it in a service method. If you follow only one recommendation here, make it this one, because I think the proximate cause of your error is that handleError is not returning anything, breaking the contract of catchError.
  • I would get rid of the map((res: Response) => res) line: it does nothing.
  • I would shift to Ionic Storage instead of using localStorage because it’s more reliably persistent.
1 Like

Hi, so I took your recommendation on moving to the Ionic Storage instead of local storage and I have been trying to modify my API calls because the way it was structure before was slowing down the application because of too many calls to the storage component. But when I try to make the call I seem to be receiving a 503 error. I tried to set up a way to construct the methods for the API call and then the page would have to simply call the API function:

import {throwError as observableThrowError, Observable} from 'rxjs';

import {catchError, map} from 'rxjs/operators';
import {Component, ViewChild} from '@angular/core';
import {Injectable} from '@angular/core';
import {HttpClient, HttpHeaders, HttpRequest} from '@angular/common/http';
import {env} from '../env/env';
import {ToastController, IonApp, NavController} from '@ionic/angular';
import 'rxjs/add/operator/toPromise';
import {NavigationExtras} from '@angular/router';
import { Storage } from '@ionic/storage';

@Injectable()
export class ApiService {
  private userID: string;
  private userNotification: string;
  private userDetails: string;
  private userToken: string;
  constructor(
      public storage: Storage,
      private http: HttpClient,
      public toastCtrl: ToastController,
      public appCtrl: NavController,
      // public device: Device
  ) {

  }

getReminders(){
    return this.requestOptions().then(headers => {
      this.makeURL(env.p3ApiUrl, 'users/reminders').then((url) => {
        return this.http.get(url, {headers}).pipe(
            catchError(this.handleError)).toPromise();
      });
    });
  }

makeURL(url, extension): any{
    return this.getID().then((id) => {
      return `${url}/${extension}/${id}`;
    });
  }

getID(): Promise<any> {
    return this.storage.get('userId');
  }

example usage:

getReminders() {
    this.loadingCtrl.create({
      message: 'Fetching records ...'
    }).then((res) => {
      res.present();
      this.ApiService.getReminders()
              .then((r: any) => {
                this.reminders = r;
                this.getMeds();
                res.dismiss();
              })
              .catch(e => {
                this.getMeds();
                res.dismiss();
              });
        });
  }

error:

 Uncaught (in promise): HttpErrorResponse: {"headers":{"normalizedNames":{},"lazyUpdate":null},"status":502,"statusText":"OK","url":/users/reminders/50992","ok":false,"name":"HttpErrorResponse","message":"Http failure response for users/reminders/50992: 502 OK","error":"<html>\r\n<head><title>502 Bad Gateway</title></head>\r\n<body bgcolor=\"white\">\r\n<center><h1>502 Bad Gateway</h1></center>\r\n</body>\r\n</html>\r\n"}

Thank you again for your previous solution.

Please give proper types to all parameters and all return values of all methods in your code (and no, any does not qualify). It may seem tedious, but it will really help you unravel snarls like the one you have here. Try filling in the following skeleton:

export interface Reminder {
  // idk what goes in here, but you do
}
getReminders(): Promise<Reminder[]> (
  // if you can manage to write this so that tsc is happy, it will probably work as you expect
}