Andrėjus Lazauskas
Andrėjus Lazauskas

Reputation: 247

Angular Node server returns You provided 'undefined' where a stream was expected

I am using angular 9 + universal. No errors while i run ng serve , then I build the app with npm run build:ssr and try to run with node : node dist/app/server/main.js and get the following error in terminal :

Node Express server listening on http://localhost:4000 TypeError: You provided 'undefined' where a stream was expected. You can provide an Observable, Promise, Array, or Iterable. at subscribeTo (C:\Users\andri\OneDrive\Desktop\devfox\autorent\ng-videocms\dist\autorent\server\main.js:1:2547459) at subscribeToResult (C:\Users\andri\OneDrive\Desktop\devfox\autorent\ng-videocms\dist\autorent\server\main.js:1:2775326) at CatchSubscriber.error (C:\Users\andri\OneDrive\Desktop\devfox\autorent\ng-videocms\dist\autorent\server\main.js:1:1997435) at Observable_Observable._trySubscribe (C:\Users\andri\OneDrive\Desktop\devfox\autorent\ng-videocms\dist\autorent\server\main.js:1:1952954) at Observable_Observable.subscribe (C:\Users\andri\OneDrive\Desktop\devfox\autorent\ng-videocms\dist\autorent\server\main.js:1:1952574) at CatchOperator.call (C:\Users\andri\OneDrive\Desktop\devfox\autorent\ng-videocms\dist\autorent\server\main.js:1:1996823) at Observable_Observable.subscribe (C:\Users\andri\OneDrive\Desktop\devfox\autorent\ng-videocms\dist\autorent\server\main.js:1:1952428) at _task (C:\Users\andri\OneDrive\Desktop\devfox\autorent\ng-videocms\dist\autorent\server\main.js:1:1751796) at Observable_Observable.Observable.a.observer [as _subscribe] (C:\Users\andri\OneDrive\Desktop\devfox\autorent\ng-videocms\dist\autorent\server\main.js:1:1752141) at Observable_Observable._trySubscribe (C:\Users\andri\OneDrive\Desktop\devfox\autorent\ng-videocms\dist\autorent\server\main.js:1:1952792)

As I've explored, my app does 2 api calls on start :

app.component.ts :

ngOnInit(){
get1();
get2();
}

  get1() {
    const loc = this.locationService.getPickupLocations().subscribe((data: Location[]) => {
      this.pickupLocations = data;
      this.formGroup.get(LocationFields.pickup).setValue(data[0].getId());
      this.pickupLocationsList = this.pickupLocations.map((data): ISelectOption => {
        return {
          label: data.getName(),
          value: data.getId(),
        };
      });
    },
    (error)=> {
      console.log(error)
    },
    () => {
      this.subs.add(loc);
      this.pickupDateChange(this.formGroup.get(this.LocationFields.pickupDate).value);
    });
  }


  get2() {
    const drop = this.locationService.getDropOffLocations().subscribe((data: Location[]) => {
      this.dropoffLocations = data;
      this.formGroup.get(LocationFields.dropoff).setValue(data[1].getId());
      this.dropoffLocationsList = this.dropoffLocations.map((data): ISelectOption => {
        return {
          label: data.getName(),
          value: data.getId(),
        };
      });
    },(error)=> {
      console.log(error)
    },
    () => {
      this.subs.add(drop);
    });
  }

LocationService.ts :

  static locationsEndpoint = 'public/locations/rental';
  getPickupLocations(): Observable<Location[]> {
    const reqHeader = new HttpHeaders({ 'Content-Type': 'application/json', 'No-Auth': 'True' });
    return this.http.get(`${LocationsService.locationsEndpoint}/pickup`, { headers: reqHeader }).pipe(
     map((data: ILocationResponse) => this.hydrateCollectionData(data, LocationsHydrator))
    );
  }

  getDropOffLocations(): Observable<Location[]> {
    const reqHeader = new HttpHeaders({ 'Content-Type': 'application/json', 'No-Auth': 'True' });
    return this.http.get(`${LocationsService.locationsEndpoint}/dropoff`, { headers: reqHeader }).pipe(
     map((data: ILocationResponse) => this.hydrateCollectionData(data, LocationsHydrator))
    );
  }

And Interceptors :

 private static BASE_URL = environment.apiUrl;

  readonly HEADER_AUTHORIZATION = 'Authorization';
  readonly HEADER_ACCEPT = 'Accept';
  readonly HEADER_CONTENT_TYPE = 'Content-Type';
  readonly ACCEPT_LANGUAGE = 'Accept-Language';

  constructor(
    private authService: AuthService,
    private localeService: LocaleService
  ) { }

  intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    if (req.headers.get('skip')) {
          return next.handle(req);
    }
    if (req.url.startsWith('./assets')) {
      return next.handle(req);
    }
    req = req.clone({
      url: this._prefixUrl(req.url)
    });

    req = req.clone({
      headers: req.headers.set(this.HEADER_ACCEPT, 'application/json')
    });

    req = req.clone({
      headers: req.headers.set(this.HEADER_CONTENT_TYPE, 'application/json')
    });

    req = req.clone({
      headers: req.headers.set(this.ACCEPT_LANGUAGE, this.localeService.getLocale())
    });

    // Set token if exists
    const token = this.authService.getToken();

    if (token) {
      req = req.clone({
        headers: req.headers.set(this.HEADER_AUTHORIZATION, `Bearer ${token}`)
      });
    }

    return next.handle(req).pipe(
      catchError((httpErrorResponse: HttpErrorResponse) => {
        if(httpErrorResponse.error !== undefined){
          const customError: ApiErrors = {
            name: httpErrorResponse.error.name,
            message: httpErrorResponse.error.message,
            errors: httpErrorResponse.error.errors
          };
          return throwError(customError);
        }
      })
    );
  }

  private _prefixUrl(path: string): string {
    if (path.indexOf('/') === 0) {
      path = path.substr(1, path.length - 1);
    }

    return `${Interceptor.BASE_URL}/${path}`;
  }

I tried without these calls , tried to comment one of them.

When i disable those calls (comment them) , app works fine.

When i call them later, after i commented them, (onclick), app works fine.

When i disable interceptors , it works (SO the problem is in them, what to change ?? )

How to fix it ? And why it is happening ?

Upvotes: 1

Views: 241

Answers (1)

Picci
Picci

Reputation: 17762

The trace seems to suggest the problem is related to CatchOperator.

I see only one place where catchError operator is used in your code

return next.handle(req).pipe(
      catchError((httpErrorResponse: HttpErrorResponse) => {
        if(httpErrorResponse.error !== undefined){
          const customError: ApiErrors = {
            name: httpErrorResponse.error.name,
            message: httpErrorResponse.error.message,
            errors: httpErrorResponse.error.errors
          };
          return throwError(customError);
        }
      })
    );

In your code you seem to assume that the function passed to catchError will receive always an instance of HttpErrorResponse which is not the case. catchError will be used for any error and so the function passed to it can receive any type of error.

What happens if httpErrorResponse.error is null but you still have an error in the upstream Observables somewhere? According to the code above you do not return anything, so this may be the reason why the log says You provided 'undefined' where a stream was expected.

Rather than not doing anything, you can throw an error, so that you should get the details of the error causing the fact that you enter catchError operator, so something like this

  catchError(err => {
    if(error instanceof HttpErrorResponse && httpErrorResponse.error !== undefined){
      const customError: ApiErrors = {
        name: httpErrorResponse.error.name,
        message: httpErrorResponse.error.message,
        errors: httpErrorResponse.error.errors
      };
      return throwError(customError);
    } else {
      throw err
    }
  })
);

Upvotes: 2

Related Questions