Je veux ajouter une App Settings
section dans mon application où elle contiendra des consts et des valeurs prédéfinies.
J'ai déjà lu cette réponse qui utilise OpaqueToken
Mais elle est obsolète dans Angular. Cet article explique les différences, mais il n'a pas fourni un exemple complet et mes tentatives ont échoué.
Voici ce que j'ai essayé (je ne sais pas si c'est la bonne façon):
//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');
Et c'est le composant où je veux utiliser ces 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 {
}
Mais cela ne fonctionne pas et j'obtiens des erreurs.
Question:
Comment puis-je consommer les valeurs "app.settings" de manière angulaire?
NB Bien sûr, je peux créer le service Injectable et le mettre dans le fournisseur du NgModule, mais comme je l'ai dit, je veux le faire avec InjectionToken
, de manière angulaire.
la source
Réponses:
J'ai compris comment faire cela avec InjectionTokens (voir l'exemple ci-dessous), et si votre projet a été construit en utilisant le,
Angular CLI
vous pouvez utiliser les fichiers d'environnement trouvés dans/environments
pour statiqueapplication wide settings
comme un point de terminaison API, mais en fonction des exigences de votre projet, vous finirez probablement en utilisant les deux car les fichiers d'environnement ne sont que des littéraux d'objet, tandis qu'une configuration injectable utilisantInjectionToken
's peut utiliser les variables d'environnement et comme il s'agit d'une classe, une logique peut être appliquée pour la configurer en fonction d'autres facteurs de l'application, tels que les données de demande http initiales, le sous-domaine , etc.Exemple de jetons d'injection
/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 { }
Maintenant, vous pouvez simplement le DI dans n'importe quel composant, service, etc.:
/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); } // ... }
Vous pouvez également taper vérifier la configuration à l'aide de l'AppConfig exporté.
la source
Si vous utilisez angular-cli, il existe encore une autre option:
Angular CLI fournit des fichiers d'environnement dans
src/environments
( les fichiers par défaut sontenvironment.ts
(dev) etenvironment.prod.ts
(production)).Notez que vous devez fournir les paramètres de configuration dans tous les
environment.*
fichiers, par exemple,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__' };
et utilisez-les dans votre service (le fichier d'environnement correct est choisi automatiquement):
api.service.ts
// ... other imports import { environment } from '../../environments/environment'; @Injectable() export class ApiService { public apiRequest(): Observable<MyObject[]> { const path = environment.apiEndpoint + `/objects`; // ... } // ... }
En savoir plus sur les environnements applicatifs sur Github (Angular CLI version 6) ou dans le guide officiel Angular (version 7) .
la source
Il n'est pas conseillé d'utiliser les
environment.*.ts
fichiers pour la configuration de l'URL de votre API. Il semble que vous devriez le faire, car cela mentionne le mot «environnement».L'utilisation de ceci est en fait une configuration au moment de la compilation . Si vous souhaitez modifier l'URL de l'API, vous devrez reconstruire. C'est quelque chose que vous ne voulez pas avoir à faire ... demandez simplement à votre sympathique service QA :)
Ce dont vous avez besoin est la configuration d'exécution , c'est-à-dire que l'application charge sa configuration au démarrage.
Certaines autres réponses abordent ce sujet, mais la différence est que la configuration doit être chargée dès le démarrage de l'application , afin qu'elle puisse être utilisée par un service normal chaque fois qu'il en a besoin.
Pour implémenter la configuration d'exécution:
/src/assets/
dossier (afin qu'il soit copié lors de la construction)AppConfigService
pour charger et distribuer la configurationAPP_INITIALIZER
1. Ajoutez le fichier de configuration à
/src/assets
Vous pouvez l'ajouter à un autre dossier, mais vous devez indiquer à la CLI qu'il s'agit d'un actif dans le
angular.json
. Commencez par utiliser le dossier des actifs:{ "apiBaseUrl": "https://development.local/apiUrl" }
2. Créez
AppConfigService
C'est le service qui sera injecté chaque fois que vous aurez besoin de la valeur de configuration:
@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. Chargez la configuration à l'aide d'un
APP_INITIALIZER
Pour permettre à l '
AppConfigService
d'être injecté en toute sécurité, avec une configuration entièrement chargée, nous devons charger la configuration au démarrage de l'application. Il est important de noter que la fonction d'usine d'initialisation doit retourner unPromise
afin qu'Angular sache attendre la fin de sa résolution avant de terminer le démarrage:@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 { }
Vous pouvez maintenant l'injecter partout où vous en avez besoin et toute la configuration sera prête à être lue:
@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; } }
Je ne peux pas le dire assez fortement, configurer vos URL d'API en tant que configuration au moment de la compilation est un anti-modèle . Utilisez la configuration d'exécution.
la source
APP_INITIALIZER
mais je ne pense pas que vous puissiez facilement les rendre dépendants les uns des autres. Il semble que vous ayez une bonne question à poser, alors peut-être un lien vers elle ici?Voici ma solution, se charge à partir de .json pour permettre les modifications sans reconstruction
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); } }
et config.json
{ "apiUrl": "http://localhost:3000/api" }
la source
environments.prod.ts
afterng build --prod
sera dans un.js
fichier à un moment donné. Même si obscurcies, les données deenvironments.prod.ts
seront en texte clair. Et comme tous les fichiers .js, il sera disponible sur la machine de l'utilisateur final.Fichier de configuration du pauvre homme:
Ajoutez à votre index.html comme première ligne dans la balise body:
<script lang="javascript" src="assets/config.js"></script>
Ajoutez des actifs / config.js:
var config = { apiBaseUrl: "http://localhost:8080" }
Ajoutez config.ts:
export const config: AppConfig = window['config'] export interface AppConfig { apiBaseUrl: string }
la source
J'ai trouvé que l'utilisation d'un
APP_INITIALIZER
for this ne fonctionne pas dans les situations où d'autres fournisseurs de services exigent que la configuration soit injectée. Ils peuvent être instanciés avant leurAPP_INITIALIZER
exécution.J'ai vu d'autres solutions qui
fetch
permettent de lire un fichier config.json et de le fournir à l'aide d'un jeton d'injection dans un paramètreplatformBrowserDynamic()
avant de démarrer le module racine. Maisfetch
n'est pas pris en charge dans tous les navigateurs et en particulier les navigateurs WebView pour les appareils mobiles que je cible.Ce qui suit est une solution qui fonctionne pour moi à la fois pour PWA et les appareils mobiles (WebView). Remarque: je n'ai testé que sous Android jusqu'à présent; travailler à domicile signifie que je n'ai pas accès à un Mac pour construire.
Dans
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();
Ce code:
config.json
fichier.APP_CONFIG
jeton d'injection, avant l'amorçage.APP_CONFIG
peut ensuite être injecté dans tout fournisseur supplémentaire dansapp-module.ts
et il sera défini. Par exemple, je peux initialiser leFIREBASE_OPTIONS
jeton d'injection à partir@angular/fire
de ce qui suit:{ provide: FIREBASE_OPTIONS, useFactory: (config: IConfig) => config.firebaseConfig, deps: [APP_CONFIG] }
Je trouve que tout cela est étonnamment difficile (et hacky) à faire pour une exigence très courante. Espérons que dans un proche avenir, il y aura un meilleur moyen, tel que la prise en charge des usines de fournisseurs asynchrones.
Le reste du code par souci d'exhaustivité ...
Dans
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');
et dans
app/lib/config/config.ts
je définis l'interface pour mon fichier de configuration JSON:export interface IConfig { name: string; version: string; instance: string; firebaseConfig: { apiKey: string; // etc } }
La configuration est stockée dans
assets/config/config.json
:{ "name": "my-app", "version": "#{Build.BuildNumber}#", "instance": "localdev", "firebaseConfig": { "apiKey": "abcd" ... } }
Remarque: j'utilise une tâche Azure DevOps pour insérer Build.BuildNumber et remplacer d'autres paramètres pour différents environnements de déploiement lors de son déploiement.
la source
Voici mes deux solutions pour cela
1. Stocker dans des fichiers json
Créez simplement un fichier json et entrez dans votre composant par
$http.get()
méthode. Si j'avais besoin de ce très bas alors c'est bon et rapide.2. Stocker en utilisant les services de données
Si vous souhaitez stocker et utiliser tous les composants ou avoir une utilisation importante, il est préférable d'utiliser le service de données. Comme ça :
Créez simplement un dossier statique dans le
src/app
dossier.Créez un fichier nommé comme
fuels.ts
dans le dossier statique. Vous pouvez également stocker d'autres fichiers statiques ici. Laissez définir vos données comme ceci. En supposant que vous ayez des données sur les carburants.__
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; } }`
importez simplement dans le fichier app.module.ts comme celui-ci et changez de fournisseur
import { StaticService } from './static.services'; providers: [StaticService]
Maintenant, utilisez-le comme
StaticService
dans n'importe quel module.C'est tout.
la source
Je trouve que ce guide angulaire: fichiers de configuration modifiables des blogs Microsoft Dev est la meilleure solution. Vous pouvez configurer les paramètres de build dev ou les paramètres de build prod.
la source
Nous avons eu ce problème il y a des années avant mon adhésion et mis en place une solution utilisant le stockage local pour les informations sur les utilisateurs et l'environnement. Angular 1.0 jours pour être exact. Nous étions auparavant en train de créer dynamiquement un fichier js au moment de l'exécution qui placerait ensuite les URL d'API générées dans une variable globale. Nous sommes un peu plus motivés par la POO ces jours-ci et n'utilisons pas le stockage local pour quoi que ce soit.
J'ai créé une meilleure solution pour déterminer l'environnement et la création d'URL d'API.
En quoi cela diffère-t-il?
L'application ne se charge que si le fichier config.json est chargé. Il utilise des fonctions d'usine pour créer un degré plus élevé de SOC. Je pourrais encapsuler cela dans un service, mais je n'ai jamais vu de raison pour laquelle la seule similitude entre les différentes sections du fichier est qu'elles existent ensemble dans le fichier. Avoir une fonction d'usine me permet de passer la fonction directement dans un module s'il est capable d'accepter une fonction. Enfin, j'ai plus de facilité à configurer InjectionTokens lorsque les fonctions d'usine sont disponibles.
Des inconvénients?
Vous n'avez pas de chance d'utiliser cette configuration (et la plupart des autres réponses) si le module que vous souhaitez configurer ne permet pas à une fonction d'usine d'être transmise à forRoot () ou forChild (), et qu'il n'y a pas d'autre moyen de configurer le package en utilisant une fonction d'usine.
Instructions
- C'est là que ma solution commence à vraiment différer -
{}
ouany
lorsque vous savez que vous pouvez spécifier quelque chose de plus concret- et / ou -
- main.ts
Je vérifie que la fenêtre ["environnement"] n'est pas remplie avant de créer un écouteur d'événements pour permettre la possibilité d'une solution où la fenêtre ["environnement"] est remplie par d'autres moyens avant que le code de main.ts ne s'exécute.
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)); }); }
--- environment-resolvers.ts
J'attribue une valeur au BehaviorSubject en utilisant window ["environment"] pour la redondance. Vous pouvez concevoir une solution où votre configuration est déjà préchargée et la fenêtre ["environnement"] est déjà remplie au moment où le code de votre application Angular est exécuté, y compris le code dans 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 - Supprimé pour une meilleure compréhension
Fait amusant! Les anciennes versions de NGXLogger nécessitaient que vous passiez un objet dans LoggerModule.forRoot (). En fait, le LoggerModule le fait toujours! NGXLogger expose gentiment LoggerConfig que vous pouvez remplacer vous permettant d'utiliser une fonction d'usine pour la configuration.
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
Addenda
Comment ai-je résolu la création de mes URL API?
Je voulais être capable de comprendre ce que faisait chaque URL via un commentaire et je voulais une vérification de type car c'est la plus grande force de TypeScript par rapport au javascript (IMO). Je voulais également créer une expérience pour d'autres développeurs afin d'ajouter de nouveaux points de terminaison et des apis aussi transparents que possible.
J'ai créé une classe qui prend dans l'environnement (dev, test, stage, prod, "", etc.) et passé cette valeur à une série de classes [1-N] dont le travail est de créer l'URL de base pour chaque collection d'API . Chaque ApiCollection est responsable de la création de l'URL de base pour chaque collection d'API. Il peut s'agir de nos propres API, des API d'un fournisseur ou même d'un lien externe. Cette classe transmettra l'URL de base créée à chaque API ultérieure qu'elle contient. Lisez le code ci-dessous pour voir un exemple simple. Une fois la configuration terminée, il est très simple pour un autre développeur d'ajouter un autre point de terminaison à une classe Api sans avoir à toucher à autre chose.
TLDR; principes de base de la POO et getters paresseux pour l'optimisation de la mémoire
@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/"); } }
la source