В angular 18 мне нужно установить URL-адрес API после инициализации приложения (потому что по очевидной причине получение URL-адреса из файла json). Однако это не установка. На самом деле 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.info('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 запросы которого не перехватываются перехватчиком, это решит вашу проблему. Ввод должен быть передан как 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.info('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.info('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.info(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.info(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.info(storageService.apiUrl);
const apiUrl = storageService.apiUrl;
if (apiUrl) {
console.info('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.info('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,
};
}
@skdonthi пропустил шаг, сейчас обновлено, проверьте!
Вы имеете в виду config.json? если да, то он уже был на пути и получал правильные данные. в angular 18 и более поздних версиях он находится в общей папке. в angular.json это так. "assets": [ {"glob": "**/*", "input": "public" } ]
. см. прилагаемые скриншоты при запуске и обновлении. вы увидите проблему. заранее спасибо
@skdonthi Попробуйте выполнить console.info и проверить, получен ли ответ, возможно, путь для получения JSON неверен.
см. снимок экрана «О Config.json». это правильно. проблема возникает при запуске
@skdonthi поделитесь репозиторием на github или сделайте копию на stackblitz, пожалуйста, это нужно для проверки того, что происходит.
Понял, что provideHttpClient(withInterceptors([authInterceptor, errorInterceptor])),
является причиной проблемы. не знаю почему. angular.dev/guide/http/interceptors#configuring-interceptors
Блин, это из-за того, что в перехватчик впрыскивают сервис. эта служба имеет инжектор API_URL_TOKEN.
@skdonthi Спасибо, теперь я понимаю, обновил свой ответ решением этой проблемы.
Спасибо за ответ, в stackblitz это работает. но не в приложении. Я добавил скриншот ошибки к вашему решению. пожалуйста, проверьте.