Я хочу добавить App Settings
в свое приложение раздел, в котором он будет содержать некоторые константы и предварительно определенные значения.
Я уже читал этот ответ, в котором используется, OpaqueToken
но он устарел в Angular. Эта статья объясняет различия, но не дает полного примера, и мои попытки не увенчались успехом.
Вот что я пробовал (не знаю, правильно ли это):
//ServiceAppSettings.ts
import {InjectionToken, OpaqueToken} from "@angular/core";
const CONFIG = {
apiUrl: 'http://my.api.com',
theme: 'suicid-squad',
title: 'My awesome app'
};
const FEATURE_ENABLED = true;
const API_URL = new InjectionToken<string>('apiUrl');
И это компонент, в котором я хочу использовать эти константы:
//MainPage.ts
import {...} from '@angular/core'
import {ServiceTest} from "./ServiceTest"
@Component({
selector: 'my-app',
template: `
<span>Hi</span>
` , providers: [
{
provide: ServiceTest,
useFactory: ( apiUrl) => {
// create data service
},
deps: [
new Inject(API_URL)
]
}
]
})
export class MainPage {
}
Но это не работает, и я получаю ошибки.
Вопрос:
Как я могу использовать значения app.settings в Angular?
NB. Конечно, я могу создать Injectable-сервис и поместить его в провайдер NgModule, но, как я уже сказал, я хочу сделать это с InjectionToken
помощью Angular.
javascript
angular
Ройи Намир
источник
источник
Ответы:
Я понял, как это сделать с помощью InjectionTokens (см. Пример ниже), и если ваш проект был построен с использованием,
Angular CLI
вы можете использовать файлы среды, найденные в,/environments
для статических,application wide settings
таких как конечная точка API, но в зависимости от требований вашего проекта вы, скорее всего, закончите с использованием обоих, поскольку файлы среды являются просто объектными литералами, в то время как вводимая конфигурация с использованиемInjectionToken
's может использовать переменные среды и, поскольку это класс, может иметь логику, применяемую для его настройки на основе других факторов в приложении, таких как исходные данные HTTP-запроса, поддомен , так далее.Пример токенов инъекций
/app/app-config.module.ts
import { NgModule, InjectionToken } from '@angular/core'; import { environment } from '../environments/environment'; export let APP_CONFIG = new InjectionToken<AppConfig>('app.config'); export class AppConfig { apiEndpoint: string; } export const APP_DI_CONFIG: AppConfig = { apiEndpoint: environment.apiEndpoint }; @NgModule({ providers: [{ provide: APP_CONFIG, useValue: APP_DI_CONFIG }] }) export class AppConfigModule { }
/app/app.module.ts
import { BrowserModule } from '@angular/platform-browser'; import { NgModule } from '@angular/core'; import { AppConfigModule } from './app-config.module'; @NgModule({ declarations: [ // ... ], imports: [ // ... AppConfigModule ], bootstrap: [AppComponent] }) export class AppModule { }
Теперь вы можете просто ввести его в любой компонент, службу и т. Д .:
/app/core/auth.service.ts
import { Injectable, Inject } from '@angular/core'; import { Http, Response } from '@angular/http'; import { Router } from '@angular/router'; import { Observable } from 'rxjs/Observable'; import 'rxjs/add/operator/map'; import 'rxjs/add/operator/catch'; import 'rxjs/add/observable/throw'; import { APP_CONFIG, AppConfig } from '../app-config.module'; import { AuthHttp } from 'angular2-jwt'; @Injectable() export class AuthService { constructor( private http: Http, private router: Router, private authHttp: AuthHttp, @Inject(APP_CONFIG) private config: AppConfig ) { } /** * Logs a user into the application. * @param payload */ public login(payload: { username: string, password: string }) { return this.http .post(`${this.config.apiEndpoint}/login`, payload) .map((response: Response) => { const token = response.json().token; sessionStorage.setItem('token', token); // TODO: can this be done else where? interceptor return this.handleResponse(response); // TODO: unset token shouldn't return the token to login }) .catch(this.handleError); } // ... }
Затем вы также можете ввести проверку конфигурации, используя экспортированный файл AppConfig.
источник
Если вы используете angular-cli, есть еще один вариант:
Angular CLI предоставляет файлы среды
src/environments
(по умолчаниюenvironment.ts
(dev) иenvironment.prod.ts
(production)).Обратите внимание, что вам необходимо предоставить параметры конфигурации во всех
environment.*
файлах, например,environment.ts :
export const environment = { production: false, apiEndpoint: 'http://localhost:8000/api/v1' };
environment.prod.ts :
export const environment = { production: true, apiEndpoint: '__your_production_server__' };
и используйте их в своем сервисе (правильный файл среды выбирается автоматически):
api.service.ts
// ... other imports import { environment } from '../../environments/environment'; @Injectable() export class ApiService { public apiRequest(): Observable<MyObject[]> { const path = environment.apiEndpoint + `/objects`; // ... } // ... }
Подробнее о средах приложений читайте на Github (Angular CLI версии 6) или в официальном руководстве по Angular (версия 7) .
источник
Не рекомендуется использовать
environment.*.ts
файлы для конфигурации URL-адреса API. Похоже, вы должны это сделать, потому что здесь упоминается слово «среда».Это фактически конфигурация времени компиляции . Если вы хотите изменить URL-адрес API, вам нужно будет перестроить. Это то, чего вы не хотите делать ... просто спросите свой дружелюбный отдел контроля качества :)
Что вам нужно, так это конфигурация времени выполнения , то есть приложение загружает свою конфигурацию при запуске.
Некоторые другие ответы касаются этого, но разница в том, что конфигурация должна быть загружена сразу после запуска приложения , чтобы она могла использоваться обычной службой всякий раз, когда ей это нужно.
Чтобы реализовать конфигурацию времени выполнения:
/src/assets/
папку (чтобы он был скопирован при сборке)AppConfigService
для загрузки и распространения конфигурацииAPP_INITIALIZER
1. Добавьте файл конфигурации в
/src/assets
Вы можете добавить его в другую папку, но вам нужно будет сообщить CLI, что это актив в
angular.json
. Начните с папки с ресурсами:{ "apiBaseUrl": "https://development.local/apiUrl" }
2. Создайте
AppConfigService
Это сервис, который будет внедряться всякий раз, когда вам понадобится значение конфигурации:
@Injectable({ providedIn: 'root' }) export class AppConfigService { private appConfig: any; constructor(private http: HttpClient) { } loadAppConfig() { return this.http.get('/assets/config.json') .toPromise() .then(data => { this.appConfig = data; }); } // This is an example property ... you can make it however you want. get apiBaseUrl() { if (!this.appConfig) { throw Error('Config file not loaded!'); } return this.appConfig.apiBaseUrl; } }
3. Загрузите конфигурацию с помощью
APP_INITIALIZER
Чтобы обеспечить
AppConfigService
безопасное внедрение с полностью загруженной конфигурацией, нам нужно загрузить конфигурацию во время запуска приложения. Важно отметить, что функция фабрики инициализации должна возвращать,Promise
чтобы Angular знал, что нужно дождаться завершения разрешения перед завершением запуска:@NgModule({ declarations: [ AppComponent ], imports: [ BrowserModule, HttpClientModule ], providers: [ { provide: APP_INITIALIZER, multi: true, deps: [AppConfigService], useFactory: (appConfigService: AppConfigService) => { return () => { //Make sure to return a promise! return appConfigService.loadAppConfig(); }; } } ], bootstrap: [AppComponent] }) export class AppModule { }
Теперь вы можете ввести его куда угодно, и вся конфигурация будет готова к чтению:
@Component({ selector: 'app-test', templateUrl: './test.component.html', styleUrls: ['./test.component.scss'] }) export class TestComponent implements OnInit { apiBaseUrl: string; constructor(private appConfigService: AppConfigService) {} ngOnInit(): void { this.apiBaseUrl = this.appConfigService.apiBaseUrl; } }
Я не могу сказать это достаточно четко, настройка URL-адресов API в качестве конфигурации времени компиляции - это анти-шаблон . Используйте конфигурацию времени выполнения.
источник
APP_INITIALIZER
но я не думаю, что вы легко можете сделать их зависимыми друг от друга. Похоже, у вас есть хороший вопрос, так что, может быть, ссылка на него здесь?Вот мое решение, загружается из .json, чтобы разрешить изменения без перестройки
import { Injectable, Inject } from '@angular/core'; import { Http } from '@angular/http'; import { Observable } from 'rxjs/Observable'; import { Location } from '@angular/common'; @Injectable() export class ConfigService { private config: any; constructor(private location: Location, private http: Http) { } async apiUrl(): Promise<string> { let conf = await this.getConfig(); return Promise.resolve(conf.apiUrl); } private async getConfig(): Promise<any> { if (!this.config) { this.config = (await this.http.get(this.location.prepareExternalUrl('/assets/config.json')).toPromise()).json(); } return Promise.resolve(this.config); } }
и config.json
{ "apiUrl": "http://localhost:3000/api" }
источник
environments.prod.ts
afterng build --prod
будет на некоторых.js
то момент файле. Даже если они запутаны, данныеenvironments.prod.ts
будут в виде открытого текста. Как и все файлы .js, он будет доступен на машине конечного пользователя.Файл конфигурации бедняги:
Добавьте в свой index.html в качестве первой строки в теге body:
<script lang="javascript" src="assets/config.js"></script>
Добавьте assets / config.js:
var config = { apiBaseUrl: "http://localhost:8080" }
Добавьте config.ts:
export const config: AppConfig = window['config'] export interface AppConfig { apiBaseUrl: string }
источник
Я обнаружил, что использование
APP_INITIALIZER
для этого не работает в ситуациях, когда другие поставщики услуг требуют ввода конфигурации. Их можно создать доAPP_INITIALIZER
запуска.Я видел другие решения, которые используются
fetch
для чтения файла config.json и предоставления его с помощью токена внедрения в параметреplatformBrowserDynamic()
до начальной загрузки корневого модуля. Ноfetch
не поддерживается во всех браузерах и, в частности, браузерах WebView для мобильных устройств, на которые я нацелен.Ниже приведено решение, которое подходит как для PWA, так и для мобильных устройств (WebView). Примечание. Пока я тестировал только на Android; работа из дома означает, что у меня нет доступа к Mac для сборки.
В
main.ts
:import { enableProdMode } from '@angular/core'; import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; import { AppModule } from './app/app.module'; import { environment } from './environments/environment'; import { APP_CONFIG } from './app/lib/angular/injection-tokens'; function configListener() { try { const configuration = JSON.parse(this.responseText); // pass config to bootstrap process using an injection token platformBrowserDynamic([ { provide: APP_CONFIG, useValue: configuration } ]) .bootstrapModule(AppModule) .catch(err => console.error(err)); } catch (error) { console.error(error); } } function configFailed(evt) { console.error('Error: retrieving config.json'); } if (environment.production) { enableProdMode(); } const request = new XMLHttpRequest(); request.addEventListener('load', configListener); request.addEventListener('error', configFailed); request.open('GET', './assets/config/config.json'); request.send();
Этот код:
config.json
файла.APP_CONFIG
токен внедрения, до начальной загрузки.APP_CONFIG
затем может быть введен в любые дополнительные поставщики,app-module.ts
и он будет определен. Например, я могу инициализироватьFIREBASE_OPTIONS
токен инъекции@angular/fire
следующим образом:{ provide: FIREBASE_OPTIONS, useFactory: (config: IConfig) => config.firebaseConfig, deps: [APP_CONFIG] }
Я считаю все это на удивление сложным (и хакерским) делом для очень распространенного требования. Надеюсь, что в ближайшем будущем появится лучший способ, например, поддержка фабрик поставщиков асинхронных программ.
Остальной код для полноты ...
В
app/lib/angular/injection-tokens.ts
:import { InjectionToken } from '@angular/core'; import { IConfig } from '../config/config'; export const APP_CONFIG = new InjectionToken<IConfig>('app-config');
и в
app/lib/config/config.ts
я определяю интерфейс для моего файла конфигурации JSON:export interface IConfig { name: string; version: string; instance: string; firebaseConfig: { apiKey: string; // etc } }
Конфиг хранится в
assets/config/config.json
:{ "name": "my-app", "version": "#{Build.BuildNumber}#", "instance": "localdev", "firebaseConfig": { "apiKey": "abcd" ... } }
Примечание. Я использую задачу Azure DevOps для вставки Build.BuildNumber и замены других параметров для различных сред развертывания по мере ее развертывания.
источник
Вот два моих решения для этого
1. Хранить в файлах json
Просто создайте файл json и войдите в свой компонент по
$http.get()
методам. Если бы мне было нужно это очень мало, то это было бы хорошо и быстро.2. Хранить с помощью служб передачи данных
Если вы хотите хранить и использовать все компоненты или широко использовать, тогда лучше использовать службу данных. Как это :
Просто создайте статическую папку внутри
src/app
папки.Создайте файл с именем
fuels.ts
в статической папке. Здесь вы также можете хранить другие статические файлы. Позвольте определить ваши данные вот так. Предполагая, что у вас есть данные о топливе.__
export const Fuels { Fuel: [ { "id": 1, "type": "A" }, { "id": 2, "type": "B" }, { "id": 3, "type": "C" }, { "id": 4, "type": "D" }, ]; }
__
import { Injectable } from "@angular/core"; import { Fuels } from "./static/fuels"; @Injectable() export class StaticService { constructor() { } getFuelData(): Fuels[] { return Fuels; } }`
просто импортируйте в файл app.module.ts вот так и измените провайдеров
import { StaticService } from './static.services'; providers: [StaticService]
Теперь используйте это как
StaticService
в любом модуле.Вот и все.
источник
Я считаю, что это руководство по Angular: редактируемые файлы конфигурации из блогов разработчиков Microsoft является лучшим решением. Вы можете настроить параметры сборки разработчика или параметры сборки продукта.
источник
У нас была эта проблема много лет назад, до того, как я присоединился к нам, и у нас было решение, которое использовало локальное хранилище для информации о пользователях и среде. Angular 1.0 дня, если быть точным. Раньше мы динамически создавали js-файл во время выполнения, который затем помещал сгенерированные URL-адреса api в глобальную переменную. В наши дни мы немного больше ориентируемся на ООП и не используем локальное хранилище ни для чего.
Я создал лучшее решение как для определения среды, так и для создания URL-адресов API.
Чем это отличается?
Приложение не загрузится, пока не будет загружен файл config.json. Он использует фабричные функции для создания более высокого уровня SOC. Я мог бы инкапсулировать это в службу, но я никогда не видел причины, по которой единственное сходство между различными разделами файла состоит в том, что они существуют вместе в файле. Наличие фабричной функции позволяет мне передавать функцию непосредственно в модуль, если он способен принять функцию. Наконец, мне проще настроить InjectionTokens, когда заводские функции доступны для использования.
Минусы?
Вам не повезло с этой настройкой (и большинством других ответов), если модуль, который вы хотите настроить, не позволяет передавать заводскую функцию в forRoot () или forChild (), и нет другого способа настроить пакет с помощью заводской функции.
инструкции
- Вот здесь мое решение начинает действительно отличаться -
{}
илиany
когда вы знаете, что можете указать что-то более конкретное- и / или -
- main.ts
Я проверяю, что window ["environment"] не заполнено перед созданием прослушивателя событий, чтобы разрешить возможность решения, в котором window ["environment"] заполняется другими способами до того, как код в main.ts когда-либо будет выполнен.
import { enableProdMode } from '@angular/core'; import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; import { AppModule } from './app/app.module'; import { configurationSubject } from './app/utils/environment-resolver'; var configurationLoadedEvent = document.createEvent('Event'); configurationLoadedEvent.initEvent('config-set', true, true); fetch("../../assets/config.json") .then(result => { return result.json(); }) .then(data => { window["environment"] = data; document.dispatchEvent(configurationLoadedEvent); }, error => window.location.reload()); /* angular-cli only loads the first thing it finds it needs a dependency under /app in main.ts when under local scope. Make AppModule the first dependency it needs and the rest are done for ya. Event listeners are ran at a higher level of scope bypassing the behavior of not loading AppModule when the configurationSubject is referenced before calling platformBrowserDynamic().bootstrapModule(AppModule) example: this will not work because configurationSubject is the first dependency the compiler realizes that lives under app and will ONLY load that dependency, making AppModule an empty object. if(window["environment"]) { if (window["environment"].production) { enableProdMode(); } configurationSubject.next(window["environment"]); platformBrowserDynamic().bootstrapModule(AppModule) .catch(err => console.log(err)); } */ if(!window["environment"]) { document.addEventListener('config-set', function(e){ if (window["environment"].production) { enableProdMode(); } configurationSubject.next(window["environment"]); window["environment"] = undefined; platformBrowserDynamic().bootstrapModule(AppModule) .catch(err => console.log(err)); }); }
--- среда-resolvers.ts
Я присваиваю значение BehaviorSubject, используя window ["environment"] для избыточности. Вы можете разработать решение, в котором ваша конфигурация уже предварительно загружена, а окно ["environment"] уже заполнено к моменту запуска любого кода вашего приложения Angular, включая код в main.ts
import { BehaviorSubject } from "rxjs"; import { IConfig } from "../config.interface"; const config = <IConfig>Object.assign({}, window["environment"]); export const configurationSubject = new BehaviorSubject<IConfig>(config); export function resolveEnvironment() { const env = configurationSubject.getValue().environment; let resolvedEnvironment = ""; switch (env) { // case statements for determining whether this is dev, test, stage, or prod } return resolvedEnvironment; } export function resolveNgxLoggerConfig() { return configurationSubject.getValue().logging; }
- app.module.ts - Урезано для облегчения понимания
Интересный факт! Более старые версии NGXLogger требовали, чтобы вы передавали объект в LoggerModule.forRoot (). Фактически, LoggerModule все еще работает! NGXLogger любезно предоставляет LoggerConfig, который вы можете переопределить, позволяя вам использовать заводскую функцию для настройки.
import { resolveEnvironment, resolveNgxLoggerConfig, resolveSomethingElse } from './environment-resolvers'; import { LoggerConfig } from 'ngx-logger'; @NgModule({ modules: [ SomeModule.forRoot(resolveSomethingElse) ], providers:[ { provide: ENVIRONMENT, useFactory: resolveEnvironment }, { provide: LoggerConfig, useFactory: resolveNgxLoggerConfig } ] }) export class AppModule
Дополнение
Как я решил создать свои URL-адреса API?
Я хотел понять, что делает каждый URL-адрес, через комментарий, и хотел проверить тип, поскольку это самая сильная сторона TypeScript по сравнению с javascript (IMO). Я также хотел создать для других разработчиков возможность добавлять новые конечные точки и API, которые были бы максимально удобными.
Я создал класс, который принимает среду (dev, test, stage, prod, "" и т. Д.), И передал это значение серии классов [1-N], задача которых - создать базовый URL-адрес для каждой коллекции API. . Каждый ApiCollection отвечает за создание базового URL-адреса для каждой коллекции API. Это могут быть наши собственные API, API поставщика или даже внешняя ссылка. Этот класс будет передавать созданный базовый URL-адрес в каждый последующий api, который он содержит. Прочтите приведенный ниже код, чтобы увидеть простой пример. После настройки другому разработчику очень просто добавить еще одну конечную точку в класс Api, не касаясь чего-либо еще.
TL; DR; основные принципы ООП и ленивые геттеры для оптимизации памяти
@Injectable({ providedIn: 'root' }) export class ApiConfig { public apis: Apis; constructor(@Inject(ENVIRONMENT) private environment: string) { this.apis = new Apis(environment); } } export class Apis { readonly microservices: MicroserviceApiCollection; constructor(environment: string) { this.microservices = new MicroserviceApiCollection(environment); } } export abstract class ApiCollection { protected domain: any; constructor(environment: string) { const domain = this.resolveDomain(environment); Object.defineProperty(ApiCollection.prototype, 'domain', { get() { Object.defineProperty(this, 'domain', { value: domain }); return this.domain; }, configurable: true }); } } export class MicroserviceApiCollection extends ApiCollection { public member: MemberApi; constructor(environment) { super(environment); this.member = new MemberApi(this.domain); } resolveDomain(environment: string): string { return `https://subdomain${environment}.actualdomain.com/`; } } export class Api { readonly base: any; constructor(baseUrl: string) { Object.defineProperty(this, 'base', { get() { Object.defineProperty(this, 'base', { value: baseUrl, configurable: true}); return this.base; }, enumerable: false, configurable: true }); } attachProperty(name: string, value: any, enumerable?: boolean) { Object.defineProperty(this, name, { value, writable: false, configurable: true, enumerable: enumerable || true }); } } export class MemberApi extends Api { /** * This comment will show up when referencing this.apiConfig.apis.microservices.member.memberInfo */ get MemberInfo() { this.attachProperty("MemberInfo", `${this.base}basic-info`); return this.MemberInfo; } constructor(baseUrl: string) { super(baseUrl + "member/api/"); } }
источник