在 angular 18 中,我需要在应用程序初始化后设置 API URL(因为出于显而易见的原因,需要从 json 文件中获取 URL)。但是它没有设置。实际上 InjectionToken 之前已经设置过了
在 angular 18 中,我需要在应用程序初始化后设置 API URL(因为出于显而易见的原因,需要从 json 文件中获取 URL)。但是它没有设置。实际上,它 InjectionToken
之前已经设置过, APP_INITIALIZER
尽管它是后来添加到 providers
数组 app.config.ts
参考: https://angular.dev/api/core/APP_INITIALIZER?tab=usage-notes
以下是 Stackblitz
期望设置 API_URL_TOKEN
为 url 值并将其用于所有 API 调用。存储在 localStorage 中并分配。
private apiUrl = inject(API_URL_TOKEN);
loginUser(data: { username: string; password: string }) {
return this.http.post(`${this.apiUrl}/auth`, data).pipe(
tap((res) => {
if (res.success) {
this.addToken(res.data);
}
})
);
}
这是我尝试过的
bootstrapApplication(AppComponent, appConfig)
export const appConfig: ApplicationConfig = {
providers: [
provideHttpClient(withFetch(), withInterceptors([authInterceptor, errorInterceptor])),
provideAppInitializer(),
provideApiUrl(),
provideRouter(routes),
provideAnimationsAsync(),
provideCharts(withDefaultRegisterables()),
],
};
const CONFIG_URL = '/config.json';
function appInitializer(http: HttpClient, storageService: StorageService) {
return async () => {
try {
const config = await firstValueFrom(http.get<{apiUrl: string}>(CONFIG_URL));
storageService.apiUrl = config.apiUrl;
console.log('Config loaded successfully');
} catch (error) {
console.error('Error loading configuration:', error);
throw error;
}
};
}
export function provideAppInitializer(): Provider {
return {
provide: APP_INITIALIZER,
useFactory: appInitializer,
deps: [HttpClient, StorageService],
multi: true,
};
}
export const API_URL_TOKEN = new InjectionToken<string>('API_URL_TOKEN');
function apiUrlFactory(storageService: StorageService): string {
const apiUrl = storageService.apiUrl;
if (apiUrl) {
return apiUrl;
}
throw new Error('API URL not found in configuration');
}
export function provideApiUrl(): Provider {
return {
provide: API_URL_TOKEN,
useFactory: apiUrlFactory,
deps: [StorageService],
multi: false,
};
}
/* 其他方式 */
export const API_URL_TOKEN = new InjectionToken<string>('API_URL_TOKEN');
export function provideApiUrl(): Provider {
return {
provide: API_URL_TOKEN,
useValue: (storageService: StorageService) => storageService.getApiUrl,
deps: [StorageService],
};
}
import { inject, Injectable, InjectionToken } from '@angular/core';
const LOCAL_STORAGE = new InjectionToken<Storage>('Browser Storage', {
providedIn: 'root',
factory: () => localStorage,
});
const API_URL = 'apiUrl';
@Injectable({
providedIn: 'root',
})
export class StorageService {
private readonly storage = inject<Storage>(LOCAL_STORAGE);
get(key: string) {
return this.storage.getItem(key);
}
set(key: string, value: string): void {
this.storage.setItem(key, value);
}
remove(key: string): void {
return this.storage.removeItem(key);
}
clear() {
return this.storage.clear();
}
get apiUrl(): string | null {
return this.storage.getItem(API_URL) || null;
}
set apiUrl(url: string) {
this.storage.setItem(API_URL, url);
}
}
在第二种情况下,很明显,它在初始化之前
下面的 github 问题帮助我了解了问题出在哪里。
HTTP_INTERCEPTOR 不等待 APP_INITIALLIZER
由于您的拦截器使用 HttpClient
,这反过来又调用了拦截器,因此您会遇到此问题。此问题的解决方案是使用 HttpBackend
HttpBackend HttpRequest
function appInitializer(http: HttpBackend, storageService: StorageService) {
return () => {
return http.handle(new HttpRequest('GET', CONFIG_URL)).pipe(
map((config: any) => {
storageService.apiUrl = config.apiUrl;
console.log('Config loaded successfully');
}),
catchError((error: any) => {
console.error('Error loading configuration:', error);
throw error;
})
);
};
}
export function provideAppInitializer(): Provider {
return {
provide: APP_INITIALIZER,
useFactory: appInitializer,
deps: [HttpBackend, StorageService],
multi: true,
};
}
将您的内容放在 config.json
资产文件夹中,以获取数据。然后在 angular.json 的资产文件夹中进行配置,以便可以发现它。如 stackblitz 中所示。
...
},
"options": {
"assets": ["src/assets"],
"index": "src/index.html",
"browser": "src/main.ts",
...
您可以摆脱它 async await
并使用纯 rxjs 来执行此操作。我们可以用来 map
将值分配给存储并用来 catchError
捕获任何异常。
function appInitializer(http: HttpClient, storageService: StorageService) {
return () => {
return http.get<XiConfig>(CONFIG_URL).pipe(
map((config: any) => {
storageService.apiUrl = config.apiUrl;
console.log('Config loaded successfully');
}),
catchError((error: any) => {
console.error('Error loading configuration:', error);
throw error;
})
);
};
}
还有一件事是您需要将应用程序配置导入到引导应用程序。
bootstrapApplication(App, appConfig);
然后最后在组件上访问它。
@Component({
selector: 'app-root',
standalone: true,
template: `
<h1>Hello from {{ name }}!</h1>
<a target="_blank" href="https://angular.dev/overview">
Learn more about Angular
</a>
`,
})
export class App {
name = 'Angular';
constructor(@Inject(API_URL_TOKEN) private urlToken: string) {
console.log(urlToken);
}
}
import { Component, Inject } from '@angular/core';
import { bootstrapApplication } from '@angular/platform-browser';
import 'zone.js';
import { appConfig } from './app/app.config';
import { API_URL_TOKEN } from './app/app-url-token';
@Component({
selector: 'app-root',
standalone: true,
template: `
<h1>Hello from {{ name }}!</h1>
<a target="_blank" href="https://angular.dev/overview">
Learn more about Angular
</a>
`,
})
export class App {
name = 'Angular';
constructor(@Inject(API_URL_TOKEN) private urlToken: string) {
console.log(urlToken);
}
}
bootstrapApplication(App, appConfig);
import { ApplicationConfig } from '@angular/core';
import {
provideHttpClient,
withFetch,
withInterceptors,
} from '@angular/common/http';
import { provideAppInitializer } from './app-initializer';
import { provideApiUrl } from './app-url-token';
//import { provideRouter } from '@angular/router';
//import { provideAnimationsAsync } from '@angular/platform-browser/animations/async';
//import { provideCharts, withDefaultRegisterables } from 'ng2-charts';
//import { routes } from './app.routes';
//import { authInterceptor } from './interceptors/auth.interceptor';
//import { errorInterceptor } from './interceptors/error.interceptor';
export const appConfig: ApplicationConfig = {
providers: [
provideHttpClient(withFetch(), withInterceptors([])),
provideAppInitializer(),
provideApiUrl(),
//provideRouter(routes),
//provideAnimationsAsync(),
//provideCharts(withDefaultRegisterables()),
],
};
import { InjectionToken, Provider } from '@angular/core';
import { StorageService } from './storage.service';
export const API_URL_TOKEN = new InjectionToken<string>('API_URL_TOKEN');
function apiUrlFactory(storageService: StorageService): any {
console.log(storageService.apiUrl);
const apiUrl = storageService.apiUrl;
if (apiUrl) {
console.log('set apiUrl');
return apiUrl;
}
throw new Error('API URL not found in configuration');
}
/*
export const API_URL_TOKEN = new InjectionToken<string>('API_URL_TOKEN');
export function provideApiUrl(): Provider {
return {
provide: API_URL_TOKEN,
useValue: (storageService: StorageService) => storageService.getApiUrl,
deps: [StorageService],
};
}
*/
export function provideApiUrl(): Provider {
return {
provide: API_URL_TOKEN,
useFactory: apiUrlFactory,
deps: [StorageService],
multi: false,
};
}
import { HttpBackend, HttpClient, HttpRequest } from '@angular/common/http';
import { APP_INITIALIZER, Provider } from '@angular/core';
import { firstValueFrom } from 'rxjs';
import { StorageService } from './storage.service';
import { map, catchError } from 'rxjs';
interface XiConfig {
apiUrl: string;
}
const CONFIG_URL = '/assets/config.json';
function appInitializer(http: HttpBackend, storageService: StorageService) {
return () => {
return http.handle(new HttpRequest('GET', CONFIG_URL)).pipe(
map((config: any) => {
storageService.apiUrl = config.apiUrl;
console.log('Config loaded successfully');
}),
catchError((error: any) => {
console.error('Error loading configuration:', error);
throw error;
})
);
};
}
export function provideAppInitializer(): Provider {
return {
provide: APP_INITIALIZER,
useFactory: appInitializer,
deps: [HttpBackend, StorageService],
multi: true,
};
}
感谢您的回答,它在 stackblitz 中有效。但在应用程序中无效。我已将错误的屏幕截图与您的解决方案一起添加。请检查。