Javascript App.settings - Angular 的方式?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/43193049/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
App.settings - the Angular way?
提问by Royi Namir
I want to add an App Settingssection into my App where It will contain some consts and pre defined values.
我想在App Settings我的应用程序中添加一个部分,其中将包含一些常量和预定义的值。
I've already read this answerwhich uses OpaqueTokenBut it is deprecated in Angular. This articleexplains the differences but it didn't provide a full example , and my attempts were unsuccessful.
我已经阅读了这个使用OpaqueToken但在 Angular 中已弃用的答案。这篇文章解释了差异,但没有提供完整的例子,我的尝试没有成功。
Here is what I've tried ( I don't know if it's the right way) :
这是我尝试过的(我不知道这是否正确):
//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');
And this is the component where I want to use those consts :
这是我想使用这些常量的组件:
//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 {
}
But it doesn't work and I get errors.
但它不起作用,我收到错误。
Question:
题:
How can I consume "app.settings" values the Angular way?
如何以 Angular 方式使用“app.settings”值?
NB Sure I can create Injectable service and put it in the provider of the NgModule , But as I said I want to do it with InjectionToken, the Angular way.
NB 当然我可以创建 Injectable 服务并将它放在 NgModule 的提供者中,但正如我所说,我想用InjectionTokenAngular 方式来做。
采纳答案by mtpultz
I figured out how to do this with InjectionTokens (see example below), and if your project was built using the Angular CLIyou can use the environment files found in /environmentsfor static application wide settingslike an API endpoint, but depending on your project's requirements you will most likely end up using both since environment files are just object literals, while an injectable configuration using InjectionToken's can use the environment variables and since it's a class can have logic applied to configure it based on other factors in the application, such as initial http request data, subdomain, etc.
我想出了如何使用 InjectionTokens 来做到这一点(见下面的例子),如果你的项目是使用 构建的,Angular CLI你可以像 API 端点一样使用/environments静态环境文件application wide settings,但根据你的项目要求,你很可能最终使用两者,因为环境文件只是对象文字,而使用InjectionToken's 的可注入配置可以使用环境变量,并且由于它是一个类,因此可以根据应用程序中的其他因素(例如初始 http 请求数据、子域)应用逻辑来配置它, 等等。
Injection Tokens Example
注入令牌示例
/app/app-config.module.ts
/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
/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 { }
Now you can just DI it into any component, service, etc:
现在您可以将其 DI 到任何组件、服务等中:
/app/core/auth.service.ts
/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);
}
// ...
}
You can then also type check the config using the exported AppConfig.
然后,您还可以使用导出的 AppConfig 键入检查配置。
回答by tilo
If you are using angular-cli, there is yet another option:
如果您使用angular-cli,还有另一种选择:
Angular CLI provides environment files in src/environments(default ones are environment.ts(dev) and environment.prod.ts(production)).
Angular CLI 提供环境文件src/environments(默认为environment.ts(dev) 和environment.prod.ts(production))。
Note that you need to provide the config parameters in all environment.*files, e.g.,
请注意,您需要在所有environment.*文件中提供配置参数,例如,
environment.ts:
环境.ts:
export const environment = {
production: false,
apiEndpoint: 'http://localhost:8000/api/v1'
};
environment.prod.ts:
环境.prod.ts:
export const environment = {
production: true,
apiEndpoint: '__your_production_server__'
};
and use them in your service (the correct environment file is chosen automatically):
并在您的服务中使用它们(自动选择正确的环境文件):
api.service.ts
api.service.ts
// ... other imports
import { environment } from '../../environments/environment';
@Injectable()
export class ApiService {
public apiRequest(): Observable<MyObject[]> {
const path = environment.apiEndpoint + `/objects`;
// ...
}
// ...
}
Read more on application environments on Github (Angular CLI version 6)or in the official Angular guide (version 7).
在Github(Angular CLI 版本 6)或官方 Angular 指南(版本 7)上阅读更多关于应用程序环境的信息。
回答by Matt Tester
It's not advisable to use the environment.*.tsfiles for your API URL configuration. It seems like you should because this mentions the word "environment".
不建议将这些environment.*.ts文件用于 API URL 配置。似乎您应该这样做,因为这提到了“环境”一词。
Using this is actually compile-time configuration. If you want to change the API URL, you will need to re-build. That's something you don't want to have to do ... just ask your friendly QA department :)
使用它实际上是编译时配置。如果要更改 API URL,则需要重新构建。这是您不想做的事情...只需询问您友好的 QA 部门 :)
What you need is runtime configuration, i.e. the app loads its configuration when it starts up.
您需要的是运行时配置,即应用程序在启动时加载其配置。
Some other answers touch on this, but the difference is that the configuration needs to be loaded as soon as the app starts, so that it can be used by a normal service whenever it needs it.
其他一些答案也涉及到这一点,但不同之处在于需要在应用程序启动后立即加载配置,以便正常服务在需要时可以使用它。
To implement runtime configuration:
实现运行时配置:
- Add a JSON config file to the
/src/assets/folder (so that is copied on build) - Create an
AppConfigServiceto load and distribute the config - Load the configuration using an
APP_INITIALISER
- 将 JSON 配置文件添加到
/src/assets/文件夹(以便在构建时复制) - 创建一个
AppConfigService加载和分发配置 - 使用一个加载配置
APP_INITIALISER
1. Add Config file to /src/assets
1. 添加配置文件 /src/assets
You could add it to another folder, but you'd need to tell the CLI that it is an asset in the angular.json. Start off using the assets folder:
您可以将它添加到另一个文件夹,但您需要告诉 CLI 它是angular.json. 开始使用资产文件夹:
{
"apiBaseUrl": "https://development.local/apiUrl"
}
2. Create AppConfigService
2.创建 AppConfigService
This is the service which will be injected whenever you need the config value:
这是将在您需要配置值时注入的服务:
@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. Load the configuration using an APP_INITIALISER
3. 使用一个加载配置 APP_INITIALISER
To allow the AppConfigServiceto be injected safely, with config fully loaded, we need to load the config at app startup time. Importantly, the initialisation factory function needs to return a Promiseso that Angular knows to wait until it finishes resolving before finishing startup:
为了允许AppConfigService安全注入,在配置完全加载的情况下,我们需要在应用程序启动时加载配置。重要的是,初始化工厂函数需要返回 aPromise以便 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 { }
Now you can inject it wherever you need to and all the config will be ready to read:
现在你可以在任何你需要的地方注入它,所有的配置都可以阅读:
@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;
}
}
I can't say it strongly enough, configuring your API urls as compile-time configuration is an anti-pattern. Use runtime configuration.
我不能说得足够强烈,将您的API url配置为编译时配置是一种反模式。使用运行时配置。
回答by PJM
Here's my solution, loads from .json to allow changes without rebuilding
这是我的解决方案,从 .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);
}
}
and config.json
和 config.json
{
"apiUrl": "http://localhost:3000/api"
}
回答by amku91
Here's my two solutions for this
这是我的两个解决方案
1. Store in json files
1.存储在json文件中
Just make a json file and get in your component by $http.get()method. If I was need this very low then it's good and quick.
只需制作一个json文件并通过$http.get()方法进入您的组件。如果我需要这个非常低,那么它又好又快。
2. Store by using data services
2. 使用数据服务存储
If you want to store and use in all components or having large usage then it's better to use data service. Like this :
如果您想在所有组件中存储和使用或使用量大,那么最好使用数据服务。像这样 :
Just create static folder inside
src/appfolder.Create a file named as
fuels.tsinto static folder. You can store other static files here also. Let define your data like this. Assuming you having fuels data.
只需在
src/app文件夹内创建静态文件夹。fuels.ts在静态文件夹中创建一个名为 as 的文件。您也可以在此处存储其他静态文件。让我们像这样定义您的数据。假设你有燃料数据。
__
__
export const Fuels {
Fuel: [
{ "id": 1, "type": "A" },
{ "id": 2, "type": "B" },
{ "id": 3, "type": "C" },
{ "id": 4, "type": "D" },
];
}
- Create a file name static.services.ts
- 创建文件名 static.services.ts
__
__
import { Injectable } from "@angular/core";
import { Fuels } from "./static/fuels";
@Injectable()
export class StaticService {
constructor() { }
getFuelData(): Fuels[] {
return Fuels;
}
}`
- Now You can make this available for every module
- 现在您可以为每个模块提供此功能
just import in app.module.ts file like this and change in providers
只需像这样导入 app.module.ts 文件并更改提供程序
import { StaticService } from './static.services';
providers: [StaticService]
Now use this as StaticServicein any module.
现在StaticService在任何模块中使用它。
That's All.
就这样。
回答by Glenn
I found that using an APP_INITIALIZERfor this doesn't work in situations where other service providers require the configuration to be injected. They can be instantiated before APP_INITIALIZERis run.
我发现APP_INITIALIZER在其他服务提供商需要注入配置的情况下,使用 an不起作用。它们可以在APP_INITIALIZER运行之前实例化。
I've seen other solutions that use fetchto read a config.json file and provide it using an injection token in a parameter to platformBrowserDynamic()prior to bootstrapping the root module. But fetchisn't supported in all browsers and in particular WebView browsers for the mobile devices I target.
我已经看到其他解决方案fetch用于读取 config.json 文件并platformBrowserDynamic()在引导根模块之前使用参数中的注入令牌提供它。但fetch并非所有浏览器都支持,特别是我定位的移动设备的 WebView 浏览器。
The following is a solution that works for me for both PWA and mobile devices (WebView). Note: I've only tested in Android so far; working from home means I don't have access to a Mac to build.
以下是适用于 PWA 和移动设备 (WebView) 的解决方案。注意:到目前为止,我只在 Android 中进行过测试;在家工作意味着我无法使用 Mac 进行构建。
In main.ts:
在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();
This code:
这段代码:
- kicks off an async request for the
config.jsonfile. - When the request completes, parses the JSON into a Javascript object
- provides the value using the
APP_CONFIGinjection token, prior to bootstrapping. - And finally bootstraps the root module.
- 启动对
config.json文件的异步请求。 - 请求完成后,将 JSON 解析为 Javascript 对象
APP_CONFIG在引导之前使用注入令牌提供值。- 最后引导根模块。
APP_CONFIGcan then be injected into any additional providers in app-module.tsand it will be defined. For example, I can initialise the FIREBASE_OPTIONSinjection token from @angular/firewith the following:
APP_CONFIG然后可以将其注入任何其他提供程序中,app-module.ts并对其进行定义。例如,我可以使用以下内容初始化FIREBASE_OPTIONS注入令牌@angular/fire:
{
provide: FIREBASE_OPTIONS,
useFactory: (config: IConfig) => config.firebaseConfig,
deps: [APP_CONFIG]
}
I find this whole thing a surprisingly difficult (and hacky) thing to do for a very common requirement. Hopefully in the near future there will be a better way, such as, support for async provider factories.
我发现这整个事情对于一个非常常见的需求来说是一件非常困难(而且很棘手)的事情。希望在不久的将来会有更好的方法,例如支持异步提供者工厂。
The rest of the code for completeness...
其余代码的完整性......
In app/lib/angular/injection-tokens.ts:
在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');
and in app/lib/config/config.tsI define the interface for my JSON config file:
并在app/lib/config/config.ts我为我的 JSON 配置文件定义接口:
export interface IConfig {
name: string;
version: string;
instance: string;
firebaseConfig: {
apiKey: string;
// etc
}
}
Config is stored in assets/config/config.json:
配置存储在assets/config/config.json:
{
"name": "my-app",
"version": "#{Build.BuildNumber}#",
"instance": "localdev",
"firebaseConfig": {
"apiKey": "abcd"
...
}
}
Note: I use an Azure DevOps task to insert Build.BuildNumber and substitute other settings for different deployment environments as it is being deployed.
注意:我使用 Azure DevOps 任务插入 Build.BuildNumber 并在部署时为不同的部署环境替换其他设置。
回答by Matthias
Poor man's configuration file:
穷人的配置文件:
Add to your index.html as first líne in the body tag:
添加到 index.html 作为 body 标签的第一行:
<script lang="javascript" src="assets/config.js"></script>
Add assets/config.js:
添加资产/config.js:
var config = {
apiBaseUrl: "http://localhost:8080"
}
Add config.ts:
添加 config.ts:
export const config: AppConfig = window['config']
export interface AppConfig {
apiBaseUrl: string
}

