From 8c8fd73a835baa1c54830816352c13b6174b7d3a Mon Sep 17 00:00:00 2001 From: maliming Date: Thu, 5 Feb 2026 20:59:25 +0800 Subject: [PATCH] fix: enhance remote environment configuration handling and error reporting --- docs/en/framework/ui/angular/environment.md | 74 +++++++++++++++++++ .../src/lib/tests/environment-utils.spec.ts | 22 +++++- .../core/src/lib/utils/environment-utils.ts | 14 ++-- 3 files changed, 103 insertions(+), 7 deletions(-) diff --git a/docs/en/framework/ui/angular/environment.md b/docs/en/framework/ui/angular/environment.md index 4395141c13..887bd3d4ec 100644 --- a/docs/en/framework/ui/angular/environment.md +++ b/docs/en/framework/ui/angular/environment.md @@ -108,6 +108,80 @@ export interface RemoteEnv { - `method`: HTTP method to be used when retrieving environment config. Default: `GET` - `headers`: If extra headers are needed for the request, it can be set through this field. +### Example RemoteEnv Configuration + +To enable dynamic environment configuration at runtime, add the `remoteEnv` property to your environment file: + +```ts +export const environment = { + // ... other configurations + remoteEnv: { + url: '/getEnvConfig', + mergeStrategy: 'deepmerge' + } +} as Environment; +``` + +### Web Server Configuration + +When using `remoteEnv` with a URL like `/getEnvConfig`, you need to configure your web server to serve the `dynamic-env.json` file with the correct content type (`application/json`). Otherwise, the application may receive HTML (e.g., your `index.html`) instead of JSON, causing configuration to fail silently. + +#### IIS Configuration (web.config) + +```xml + + + + + + + + + + + + + + + + + + + + + +``` + +#### Nginx Configuration + +```nginx +server { + listen 80; + listen [::]:80; + server_name _; + + location / { + root /usr/share/nginx/html; + index index.html index.htm; + try_files $uri $uri/ /index.html =404; + } + + location /getEnvConfig { + default_type 'application/json'; + add_header 'Access-Control-Allow-Origin' '*' always; + add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS' always; + add_header 'Content-Type' 'application/json'; + root /usr/share/nginx/html; + try_files $uri /dynamic-env.json; + } + + error_page 500 502 503 504 /50x.html; + location = /50x.html { + root /usr/share/nginx/html; + } +} +``` + ## Configure Core Provider with Environment `environment` variable comes from angular host application. diff --git a/npm/ng-packs/packages/core/src/lib/tests/environment-utils.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/environment-utils.spec.ts index 66fbf7b29b..c31158468f 100644 --- a/npm/ng-packs/packages/core/src/lib/tests/environment-utils.spec.ts +++ b/npm/ng-packs/packages/core/src/lib/tests/environment-utils.spec.ts @@ -1,7 +1,7 @@ import { HttpClient } from '@angular/common/http'; import { Component, Injector } from '@angular/core'; import { createComponentFactory, Spectator } from '@ngneat/spectator/jest'; -import { BehaviorSubject } from 'rxjs'; +import { BehaviorSubject, throwError } from 'rxjs'; import { Environment, RemoteEnv } from '../models/environment'; import { EnvironmentService } from '../services/environment.service'; import { getRemoteEnv } from '../utils/environment-utils'; @@ -85,7 +85,6 @@ describe('EnvironmentUtils', () => { injectorSpy.mockReturnValueOnce(environmentService); injectorSpy.mockReturnValueOnce(http); - injectorSpy.mockReturnValueOnce({}); requestSpy.mockReturnValue(new BehaviorSubject(customEnv)); @@ -95,5 +94,24 @@ describe('EnvironmentUtils', () => { expect(requestSpy).toHaveBeenCalledWith('GET', '/assets/appsettings.json', { headers: {} }); expect(setStateSpy).toHaveBeenCalledWith(expectedValue); } + + it('should handle request error gracefully and use local environment', async () => { + const injector = spectator.inject(Injector); + const injectorSpy = jest.spyOn(injector, 'get'); + const http = spectator.inject(HttpClient); + const requestSpy = jest.spyOn(http, 'request'); + const environmentService = spectator.inject(EnvironmentService); + const setStateSpy = jest.spyOn(environmentService, 'setState'); + + injectorSpy.mockReturnValueOnce(environmentService); + injectorSpy.mockReturnValueOnce(http); + + requestSpy.mockReturnValue(throwError(() => new Error('Network error'))); + + environment.remoteEnv.mergeStrategy = 'deepmerge'; + await getRemoteEnv(injector, environment); + + expect(setStateSpy).toHaveBeenCalledWith(deepMerge(environment, {})); + }); }); }); diff --git a/npm/ng-packs/packages/core/src/lib/utils/environment-utils.ts b/npm/ng-packs/packages/core/src/lib/utils/environment-utils.ts index 8db9b5e02f..ae11ef002a 100644 --- a/npm/ng-packs/packages/core/src/lib/utils/environment-utils.ts +++ b/npm/ng-packs/packages/core/src/lib/utils/environment-utils.ts @@ -1,10 +1,9 @@ import { HttpClient } from '@angular/common/http'; -import { Injector } from '@angular/core'; +import { Injector, isDevMode } from '@angular/core'; import { of } from 'rxjs'; import { catchError, tap } from 'rxjs/operators'; import { Environment, RemoteEnv } from '../models/environment'; import { EnvironmentService } from '../services/environment.service'; -import { HttpErrorReporterService } from '../services/http-error-reporter.service'; import { deepMerge } from './object-utils'; export function getRemoteEnv(injector: Injector, environment: Partial) { @@ -15,15 +14,20 @@ export function getRemoteEnv(injector: Injector, environment: Partial(method, url, { headers }) .pipe( catchError(err => { - httpErrorReporter.reportError(err); + if (isDevMode()) { + console.warn( + `[ABP Environment] Failed to fetch remote environment from "${url}". ` + + `Error: ${err.message || err}\n` + + `See https://abp.io/docs/latest/framework/ui/angular/environment#example-remoteenv-configuration for configuration details.`, + ); + } return of(null); - }), // TODO: Consider get handle function from a provider + }), tap(env => environmentService.setState( mergeEnvironments(environment, env || ({} as Environment), remoteEnv as RemoteEnv),