Search code examples
angularangular-universalhttp-proxy-middleware

Is there something wrong with making an http request in the ngOnInit of a component when using Angular Universal?


I have been running my Angular app via ng serve for development and was not experiencing any errors. When using SSR, however, I get an error in my server log whenever I load a component that makes an http request as part of it's ngOnInit method. Is there something wrong with my strategy?

I have not found any useful information from googling my error. Should I be waiting before the page is fully loaded to make my request, or using some other method? If so, how would I do this?

I am using the http-proxy-middleware package to send requests to a django server. https://github.com/chimurai/http-proxy-middleware

This is the error I get when the request is made as part of the ngOnInit:

ERROR Error
    at XMLHttpRequest.send (C:\{...}\dist\{...}\server\main.js:201490:19)
    at Observable.rxjs__WEBPACK_IMPORTED_MODULE_1__.Observable [as _subscribe] (C:\{...}\dist\{...}\server\main.js:23724:17)
    at Observable._trySubscribe (C:\{...}\dist\{...}\server\main.js:186583:25)
    at Observable.subscribe (C:\{...}\dist\{...}\server\main.js:186569:22)
    at scheduleTask (C:\{...}\dist\{...}\server\main.js:106745:32)
    at Observable.rxjs__WEBPACK_IMPORTED_MODULE_7__.Observable [as _subscribe] (C:\{...}\dist\{...}\server\main.js:106807:13)
    at Observable._trySubscribe (C:\{...}\dist\{...}\server\main.js:186583:25)
    at Observable.subscribe (C:\{...}\dist\{...}\server\main.js:186569:22)
    at subscribeToResult (C:\{...}\dist\{...}\server\main.js:196664:23)
    at MergeMapSubscriber._innerSub (C:\{...}\dist\{...}\server\main.js:191854:116)

Here is the relevant part of my test component:

export class TestComponent implements OnInit {
  output: String = "The server is not running or is not connected"

  constructor(private httpTestService: HttpTestService) { }

  ngOnInit(): void {
    this.testGetRequest();
  }

  testGetRequest() {
    this.httpTestService.testGetRequest().subscribe(temp => {
      this.output = temp.message; // response is json with a 'message' attribute
    });
  }

}

Here is the relevant part of my HttpTestService:

import { HttpClient } from '@angular/common/http';
  constructor(private http: HttpClient) { }

  testGetRequest(): Observable<any> {
    return this.http.get('/api/endpoint1');
  }

I believe this part of my server.ts may be important:

import { createProxyMiddleware } from 'http-proxy-middleware';
export function app() {
  const server = express();
  const distFolder = join(process.cwd(), 'dist/.../browser');
  const indexHtml = existsSync(join(distFolder, 'index.original.html')) ? 'index.original.html' : 'index';

  server.engine('html', ngExpressEngine({
    bootstrap: AppServerModule,
  }));

  server.set('view engine', 'html');
  server.set('views', distFolder);

  // re-route requests to /api/ to the django REST api
  server.use('/api/**', createProxyMiddleware({ target: 'http://localhost:8000', changeOrigin: true }));

Even when running with SSR, the app functions normally except for the error I get in the server console.


Solution

  • You should use absolute URLs when doing HTTP calls. docs

    So the problem possibly is here

    testGetRequest(): Observable<any> {
      return this.http.get('/api/endpoint1');
    }
    
    

    To fix it you can use the interceptor that will change relative URLs to absolute and provide this interceptor inside AppServerModule.

    Interceptor is:

    import { HttpHandler, HttpInterceptor, HttpRequest } from '@angular/common/http';
    import { Inject, Injectable, Optional } from '@angular/core';
    import { REQUEST } from '@nguniversal/express-engine/tokens';
    import { Request } from 'express';
    
    @Injectable()
    export class UniversalInterceptor implements HttpInterceptor {
    
      constructor(@Optional() @Inject(REQUEST) protected request: Request) {}
    
      intercept(req: HttpRequest<any>, next: HttpHandler) {
        let serverReq: HttpRequest<any> = req;
        if (this.request && req.url.indexOf('http') !== 0) {
          let newUrl = `${this.request.protocol}://${this.request.get('host')}`;
          if (!req.url.startsWith('/')) {
            newUrl += '/';
          }
          newUrl += req.url;
          serverReq = req.clone({url: newUrl});
        }
    
        return next.handle(serverReq);
      }
    }
    

    AppServerModule is

    @NgModule({
      imports: [
        ...
      ],
      bootstrap: [AppComponent],
      providers: [
        {
          provide: HTTP_INTERCEPTORS,
          useClass: UniversalInterceptor,
          multi: true,
        },
      ],
    })
    export class AppServerModule {}
    

    Hope it helps