如何解决“ERROR NetworkError at XMLHttpRequest.send (...dist\fxcore\server\main.js:200768:19)”错误。

15

我是Angular的新手。我刚刚完成了我的Angular Web应用程序的开发。在生产环境下使用ng serve来提供我的应用程序时,一切正常。我添加了angular universal。现在,当我运行npm run dev:ssr或npm run build:ssr && npm run serve:ssr中的任何一个时,我的应用程序将拒绝打开,并在控制台中抛出NetworkError响应。我注意到这个错误发生的次数是通过类'constructors(){..}'发送HTTP请求的次数。我浏览了很多解决方案,但是找不到我做错了什么的线索。我的后端是使用nodejs和express开发的。我会感激任何帮助。

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)```

这可能会对你有所帮助。https://github.com/angular/universal/issues/1046#issuecomment-455408250 - Shabbir Dhangot
在进行 HTTP 调用时,您是否使用绝对 URL? - David
@David,我在开发模式下使用绝对URL (http://localhost:3000/api/...) 进行HTTP调用。但是在生产模式下,这会更改为相对URL (/api/...)。我使用Angular环境变量实现了这一点。 - Benito
4
使用 Angular Universal 时需要使用绝对 URL。 - David
6个回答

5
我曾苦恼于一个错误,直到我找到了这篇文章,介绍了如何创建一个“相对路径转绝对路径拦截器”relative to absolute interceptor,以下是链接:

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

我在src目录下创建了"universal-relative.interceptor.ts"文件,并将该拦截器代码放入其中。
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. 前往你的"app.server.module.ts"文件
  2. 按照以下方式添加你的拦截器
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 {}

错误已经消失了!


(2)请注意,这不使用<base> href值。 - Pieterjan
谢谢,但我在Angular 15上仍然遇到NetworkError,因为@Optional() @Inject(REQUEST) protected request似乎没有产生任何结果。另外看起来ServerTransferStateModule已经过时了。 - TCB13
显然它不支持预渲染:https://github.com/angular/universal/issues/2147#issuecomment-852775977 - TCB13

5

我一直遇到这个 ERROR NetworkError, 但是我找到了另一种方法让这个错误消失。我认为这个答案很相关,因为我遇到了与上面帖子相同的错误。如果这能帮助任何遇到同样服务器错误的人,那就太好了。

如果在使用 ng-universal 示例时重新加载并且向服务器发出api请求,请首先检查 isPlatformBrowser

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');
    }
  }
}

在从客户端进行服务器调用之前,务必首先检查isPlatformBrowser === true,然后在OnInit中,请注意我遇到了相同的错误,并解决了我的问题。希望这可以帮助解决这个bug。

参考这个答案帮助我解决了这个长期存在的问题。https://dev59.com/UFYO5IYBdhLWcg3wRfZ-#46893433


1
但是如何在服务器上呈现呢?如果您仅在客户端进行调用,则会忽略整个SEO点。 - Mihai Marinescu
2
@MihaiMarinescu 我的主要SEO设置在 app.component.ts 文件中,我不使用上面的示例等待浏览器设置元数据,这也不是导致网络错误的原因。这不应该对您的SEO造成任何干扰。对于我来说,网络错误是由于尝试在浏览器准备好处理数据之前获取数据造成的。您可以查看我的Github站点来了解我如何在我的Angular nguniversal应用程序中处理SEO。 - Ian Poston Framer
@IanPostonFramer 我认为如果需要初始化逻辑才能使应用程序正确地(或完全)呈现,那么这是可能的。至少在我的情况下似乎是这样。 - Wahrenheit Sucher

3

我遇到了同样的错误。尝试从你的app.module中移除TransferHttpCacheModule,并创建自己的自定义http传输拦截器文件。

我创建了一个名为transfer-state.interceptor.ts的文件,然后将其添加到app.module providers:[]中进行处理。下面的示例将展示如何连接它。我不确定这对你是否一定有效,但它确实让那个错误消失了。


//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 { }

这是一种自定义传输状态文件的版本,但如果这个版本不起作用,有几种其他方法可以实现。


//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);
      }
    }
  }
}

如果您想要添加自定义缓存,可以通过安装 memory-cache 实现,但我还没有尝试过。这些文章对我很有帮助,也许它们也能帮到您。

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

如果您尚未添加,可能需要将 ServerTransferStateModule 添加到您的 app.server.module 文件中。


//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 {}

祝你好运!


2
我使用了绝对URL,问题得到了解决。感谢您的贡献。 - Benito

0
如果有人需要,如果您正在使用ng-universal,并且由于服务器端渲染引起了错误,那么您可以简单地使用以下方法。
    if (typeof window === 'object') {
      // your client side httpClient code
    }

0

对我来说,问题很简单,我的API变量未定义,因为Angular SSR生命周期的原因。数据只有在浏览器模块加载后才可用。

我之前使用了类似以下的代码:

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

设定适当的API端点。


0

正如David在原始问题中回复的那样,在我的情况下,我使用的resourceUrl变量在生产环境中不是绝对路径。

environment.ts

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

就像你所看到的,为了开发,我使用了一个绝对URL“http://localhost:8082/api/site”作为resourceUrl环境变量。当然,这在开发模式下是有效的。

environment.prod.ts

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

在生产模式下,我使用了相对 URL (/api/site),这导致在运行“serve:ssr”(即生产环境)时出现了问题。
return this.http.get<ISomething>(`${environment.resourceUrl}/home/${environment.siteId}`);

所以我将environment.prod.ts更改为使用绝对URL。然后问题就解决了。

我添加了这个回复,因为可能有人没有看到David的评论。谢谢David。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接