Angular 18: установите InjectionToken после поставщика APP_INITIALIZER

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

Вот что я попробовал

main.ts

bootstrapApplication(AppComponent, appConfig)

app.config.ts

export const appConfig: ApplicationConfig = {
  providers: [
    provideHttpClient(withFetch(), withInterceptors([authInterceptor, errorInterceptor])),
    provideAppInitializer(),
    provideApiUrl(),
    provideRouter(routes),
    provideAnimationsAsync(),
    provideCharts(withDefaultRegisterables()),
  ],
};

приложение-initializer.ts

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,
  };
}

приложение-url-token.ts

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],
  };
}

Storage.service.ts

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

На старте

при обновлении

Во втором случае ясно, что это перед инициализатором

О config.json

Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Angular и React для вашего проекта веб-разработки?
Angular и React для вашего проекта веб-разработки?
Когда дело доходит до веб-разработки, выбор правильного front-end фреймворка имеет решающее значение. Angular и React - два самых популярных...
Эпизод 23/17: Twitter Space о будущем Angular, Tiny Conf
Эпизод 23/17: Twitter Space о будущем Angular, Tiny Conf
Мы провели Twitter Space, обсудив несколько проблем, связанных с последними дополнениями в Angular. Также прошла Angular Tiny Conf с 25 докладами.
Угловой продивер
Угловой продивер
Оригинал этой статьи на турецком языке. ChatGPT используется только для перевода на английский язык.
Мое недавнее углубление в Angular
Мое недавнее углубление в Angular
Недавно я провел некоторое время, изучая фреймворк Angular, и я хотел поделиться своим опытом со всеми вами. Как человек, который любит глубоко...
Освоение Observables и Subjects в Rxjs:
Освоение Observables и Subjects в Rxjs:
Давайте начнем с основ и постепенно перейдем к более продвинутым концепциям в RxJS в Angular
1
0
50
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Приведенная ниже проблема с 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);
  }
}

Полный код:

Main.ts

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

app.config.ts

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()),
  ],
};

app.url.token

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,
      };
    }

Демо-версия Stackblitz

Спасибо за ответ, в stackblitz это работает. но не в приложении. Я добавил скриншот ошибки к вашему решению. пожалуйста, проверьте.

skdonthi 30.07.2024 14:55

@skdonthi пропустил шаг, сейчас обновлено, проверьте!

Naren Murali 30.07.2024 15:20

Вы имеете в виду config.json? если да, то он уже был на пути и получал правильные данные. в angular 18 и более поздних версиях он находится в общей папке. в angular.json это так. "assets": [ {"glob": "**/*", "input": "public" } ]. см. прилагаемые скриншоты при запуске и обновлении. вы увидите проблему. заранее спасибо

skdonthi 30.07.2024 15:24

@skdonthi Попробуйте выполнить console.info и проверить, получен ли ответ, возможно, путь для получения JSON неверен.

Naren Murali 30.07.2024 15:26

см. снимок экрана «О Config.json». это правильно. проблема возникает при запуске

skdonthi 30.07.2024 15:31

@skdonthi поделитесь репозиторием на github или сделайте копию на stackblitz, пожалуйста, это нужно для проверки того, что происходит.

Naren Murali 30.07.2024 15:49

Понял, что provideHttpClient(withInterceptors([authInterceptor, errorInterceptor])), является причиной проблемы. не знаю почему. angular.dev/guide/http/interceptors#configuring-interceptors

skdonthi 30.07.2024 16:38

Блин, это из-за того, что в перехватчик впрыскивают сервис. эта служба имеет инжектор API_URL_TOKEN.

skdonthi 30.07.2024 16:48

@skdonthi Спасибо, теперь я понимаю, обновил свой ответ решением этой проблемы.

Naren Murali 30.07.2024 17:58

Другие вопросы по теме