15

I am new to Angular. I just finished developing my angular web application. When I use ng serve to serve my application during production, everything works fine. I added angular universal. Now when I run any of npm run dev:ssr or npm run build:ssr && npm run serve:ssr, my application will refuse to open, throwing NetworkError response in the console. I noticed this error occurs for the number of times http requests where sent via class 'constructors(){..}'. I have browsed through several solution but couldn't get a clue of what I'm not doing right. My backend is developed with nodejs and express. I'll appreciate any help I can get. Here is a full example of the error response I always get in the console.

ERROR NetworkError
    at XMLHttpRequest.send (C:\Users\MRBEN\Desktop\Angular\fxcore\dist\fxcore\server\main.js:200768:19)
    at Observable._subscribe (C:\Users\MRBEN\Desktop\Angular\fxcore\dist\fxcore\server\main.js:19025:17)
    at Observable._trySubscribe (C:\Users\MRBEN\Desktop\Angular\fxcore\dist\fxcore\server\main.js:186304:25)
    at Observable.subscribe (C:\Users\MRBEN\Desktop\Angular\fxcore\dist\fxcore\server\main.js:186290:22)
    at scheduleTask (C:\Users\MRBEN\Desktop\Angular\fxcore\dist\fxcore\server\main.js:105897:32)
    at Observable._subscribe (C:\Users\MRBEN\Desktop\Angular\fxcore\dist\fxcore\server\main.js:105959:13)
    at Observable._trySubscribe (C:\Users\MRBEN\Desktop\Angular\fxcore\dist\fxcore\server\main.js:186304:25)
    at Observable.subscribe (C:\Users\MRBEN\Desktop\Angular\fxcore\dist\fxcore\server\main.js:186290:22)
    at subscribeToResult (C:\Users\MRBEN\Desktop\Angular\fxcore\dist\fxcore\server\main.js:196385:23)
    at MergeMapSubscriber._innerSub (C:\Users\MRBEN\Desktop\Angular\fxcore\dist\fxcore\server\main.js:191575:116)```
Benito
  • 181
  • 1
  • 9
  • This may help you. https://github.com/angular/universal/issues/1046#issuecomment-455408250 – Shabbir Dhangot Apr 27 '20 at 01:15
  • Are you using absolute URLs when making http calls? – David Apr 27 '20 at 06:14
  • @David, I use absolute urls `(http://localhost:3000/api/...)` when making http calls in dev mode. But in prod mode, this changinges to relative urls `(/api/...)`. I achieved this using angular environment variables. – Benito Apr 27 '20 at 22:22
  • 4
    You need to use absolute URLs when you use angular universal – David Apr 28 '20 at 06:07

6 Answers6

5

I was still getting this ERROR NetworkError but I found another way to make this error go away. I think this answer is relevant since I was getting the same error posted above. If this can help anyone with that same server error then that's great.

If the api request is made to the server OnInit when reloading check isPlatformBrowser first when using ng-universal example.

import { Component, OnInit, PLATFORM_ID, Inject } from '@angular/core';
import { isPlatformBrowser } from '@angular/common';
import { HttpClient, HttpHeaders } from '@angular/common/http';

export class HomeComponent implements OnInit {

  public testBrowser  : boolean;
  public data         : any;
  
  constructor(private http: HttpClient, @Inject(PLATFORM_ID) platformId: string) {
    this.testBrowser = isPlatformBrowser(platformId);
  }

  ngOnInit() {
    if (this.testBrowser) {
      //avoid server NETWORK error
      this.data = this.http.get('/api');
    }
  }
}

I was getting this same error trying to make server calls from the client before checking isPlatformBrowser === true first OnInit and this solved my problem. Hopefully this can help this bug.

For reference this answer helped me squash this long standing bug. https://stackoverflow.com/a/46893433/4684183

Ian Poston Framer
  • 938
  • 12
  • 16
  • 1
    But how do you render on the server? It misses the whole SEO point if you do the call only on the client. – Mihai Marinescu Oct 06 '21 at 12:46
  • 2
    @MihaiMarinescu my main SEO is set in my `app.component.ts` and I don't use the above example to wait for if browser to set meta data and It is not what was causing the network error in the first place. this should not interfere with your SEO at all. the network error for me is caused by trying to fetch data before the browser is ready to handle it. you can check out my Github site to see how I handle SEO in my angular nguniversal apps. – Ian Poston Framer Oct 07 '21 at 16:51
  • @IanPostonFramer I believe its possible if initialization logic is required for the app to render properly (or at all). At least that seems to be the case in my situation. – Wahrenheit Sucher Mar 23 '22 at 15:20
4

I was struggling with this error for days until I found this article About how to create a relative to absolute interceptor here's the link

https://bcodes.io/blog/post/angular-universal-relative-to-absolute-http-interceptor

  1. I created "universal-relative.interceptor.ts" file at my src folder
  2. put this interceptor code in "universal-relative.interceptor.ts" file
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';

// case insensitive check against config and value
const startsWithAny = (arr: string[] = []) => (value = '') => {
    return arr.some(test => value.toLowerCase().startsWith(test.toLowerCase()));
};

// http, https, protocol relative
const isAbsoluteURL = startsWithAny(['http', '//']);

@Injectable()
export class UniversalRelativeInterceptor implements HttpInterceptor {
    constructor(@Optional() @Inject(REQUEST) protected request: Request) {}

    intercept(req: HttpRequest<any>, next: HttpHandler) {
        if (this.request && !isAbsoluteURL(req.url)) {
            const protocolHost = `${this.request.protocol}://${this.request.get(
                'host'
            )}`;
            const pathSeparator = !req.url.startsWith('/') ? '/' : '';
            const url = protocolHost + pathSeparator + req.url;
            const serverRequest = req.clone({ url });
            return next.handle(serverRequest);
        } else {
            return next.handle(req);
        }
    }
}
  1. Go to your "app.server.module.ts" file
  2. add your interceptor like this
import { NgModule } from '@angular/core';
import {
  ServerModule,
  ServerTransferStateModule,
} from "@angular/platform-server";

import { AppModule } from './app.module';
import { AppComponent } from './app.component';
import { UniversalRelativeInterceptor } from 'src/universal-relative.interceptor';
import { HTTP_INTERCEPTORS } from '@angular/common/http';

@NgModule({
  imports: [AppModule, ServerModule, ServerTransferStateModule],
  providers: [
    {
      provide: HTTP_INTERCEPTORS,
      useClass: UniversalRelativeInterceptor,
      multi: true,
    },
  ],
  bootstrap: [AppComponent],
})
export class AppServerModule {}

And the error was GONE!

Binary_Hits00
  • 570
  • 6
  • 7
  • (2) Take into account that this does not use the `` href value – Pieterjan Oct 20 '22 at 19:07
  • Thank you but I'm still getting a `NetworkError` on Angular 15 because `@Optional() @Inject(REQUEST) protected request` doesn't seem to produce anything. Also looks like `ServerTransferStateModule` is deprecated. – TCB13 Apr 12 '23 at 23:11
  • Apparently it doesn't work with pre-rendering: https://github.com/angular/universal/issues/2147#issuecomment-852775977 – TCB13 Apr 12 '23 at 23:35
3

I am getting the same error. Try to remove TransferHttpCacheModule from your app.module and create your own custom http transfer interceptor file.

I made a file called transfer-state.interceptor.ts and then added it to app.module providers:[] to handle this. The examples below will show how I hooked it up. I am not sure if this will definitely work for you but it did make that error go away for me.


//app.module.ts

import { BrowserModule, BrowserTransferStateModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { HttpClientModule, HTTP_INTERCEPTORS } from "@angular/common/http";
//import {TransferHttpCacheModule } from '@nguniversal/common';

import { AppRoutingModule } from './app-routing/app-routing.module';
import { AppComponent } from './app.component';
import { HomeComponent } from './modules/home/home.component';
import { SliderComponent } from './components/slider/slider.component';
import { WindowRefService } from './services/window-ref.service';
//import { TransferHttpInterceptorService } from './services/transfer-http-interceptor.service';
import { TransferStateInterceptor } from './interceptors/transfer-state.interceptor';
import { ServiceWorkerModule } from '@angular/service-worker';
import { environment } from '../environments/environment';

@NgModule({
  declarations: [
    AppComponent,
    HomeComponent,
    SliderComponent
  ],
  imports: [
    BrowserModule.withServerTransition({ appId: 'serverApp' }),
    BrowserTransferStateModule,
    AppRoutingModule,
    HttpClientModule,
    ServiceWorkerModule.register('ngsw-worker.js', { enabled: environment.production })
  ],
  providers: [
    WindowRefService,
    {
      provide: HTTP_INTERCEPTORS,
      useClass: TransferStateInterceptor,
      multi: true
    }
],
  bootstrap: [AppComponent]
})
export class AppModule { }

This is one version of a custom transfer state file but there are a few ways to do this if this one doesn't work.


//transfer-state.interceptor.ts

import { HttpEvent, HttpHandler, HttpInterceptor, HttpRequest, HttpResponse } from '@angular/common/http';
import { Inject, Injectable, PLATFORM_ID } from '@angular/core';
import { Observable, of } from 'rxjs';
import { StateKey, TransferState, makeStateKey } from '@angular/platform-browser';
import { isPlatformBrowser, isPlatformServer } from '@angular/common';
import { tap } from 'rxjs/operators';

@Injectable()
export class TransferStateInterceptor implements HttpInterceptor {

  constructor(
    private transferState: TransferState,
    @Inject(PLATFORM_ID) private platformId: any,
  ) {}

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

    // For this demo application, we will only worry about State Transfer for get requests.
    if (request.method !== 'GET') {
      return next.handle(request);
    }


    // Use the request url as the key.
    const stateKey: StateKey<string> = makeStateKey<string>(request.url);

    // For any http requests made on the server, store the response in State Transfer.
    if (isPlatformServer(this.platformId)) {
      return next.handle(request).pipe(
        tap((event: HttpResponse<any>) => {
          this.transferState.set(stateKey, event.body);
        })
      );
    }

    // For any http requests made in the browser, first check State Transfer for a 
    // response corresponding to the request url.
    if (isPlatformBrowser(this.platformId)) {
      const transferStateResponse = this.transferState.get<any>(stateKey, null);
      if (transferStateResponse) {
        const response = new HttpResponse({ body: transferStateResponse, status: 200 });

        // Remove the response from state transfer, so any future requests to 
        // the same url go to the network (this avoids us creating an 
        // implicit/unintentional caching mechanism).
        this.transferState.remove(stateKey);
        return of(response);
      } else {
        return next.handle(request);
      }
    }
  }
}

If you want to add custom cache to this you can by installing memory-cache but I haven't tried that out yet. For more references these articles helped me out a lot and maybe they can help you too.

https://itnext.io/angular-universal-caching-transferstate-96eaaa386198

https://willtaylor.blog/angular-universal-for-angular-developers/

https://bcodes.io/blog/post/angular-universal-relative-to-absolute-http-interceptor

If you haven't you may need to add ServerTransferStateModule to your app.server.module file.


//app.server.module

import { NgModule } from '@angular/core';
import {
  ServerModule,
  ServerTransferStateModule
} from "@angular/platform-server";

import { AppModule } from './app.module';
import { AppComponent } from './app.component';

@NgModule({
  imports: [
    AppModule,
    ServerModule,
    ServerTransferStateModule
  ],
  bootstrap: [AppComponent],
})
export class AppServerModule {}

good luck!

Ian Poston Framer
  • 938
  • 12
  • 16
0

For me simply the error was that my API variable was undefined, because of the Angular SSR life-cycle. The data was only available after the browser module loaded.

I was using something like

this.isBrowser$.subscribe(isBrowser => { ... });

to set the appropriate api endpoint.

alex351
  • 1,826
  • 1
  • 21
  • 32
0

As David replied in the original issue, in my case was the resourceUrl variable that I was using, was not absolute for production environment.

environment.ts

export const environment = {
  resourceUrl: 'http://localhost:8082/api/site',
  siteId: '1111'
};

Like you see, for development, I was using an absolute url "http://localhost:8082/api/site" for resourceUrl environment variable. Ofcourse this was working on development mode.

environment.prod.ts

export const environment = {
  resourceUrl: '/api/site',
  siteId: '1111'
};

In production mode I was using a relative url (/api/site), and this was causing the issue while running "serve:ssr" which is production.

return this.http.get<ISomething>(`${environment.resourceUrl}/home/${environment.siteId}`);

So I changed environment.prod.ts to use an absolute URL. Then the issue was gone.

I am adding this reply, since maybe someone doesnt look at David comment. Thanks David.

Juano7894
  • 703
  • 1
  • 8
  • 21
0

In case someone needs, if you are using ng-universal, and because the server side rendering caused the error, then you can simply use

    if (typeof window === 'object') {
      // your client side httpClient code
    }
uuware
  • 1
  • 1