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
- En utilisant fetch pour récupérer un fichier json, je stocke l'objet dans la fenêtre et déclenche un événement personnalisé. - n'oubliez pas d'installer whatwg-fetch et de l'ajouter à votre polyfills.ts pour la compatibilité IE
- Demandez à un écouteur d'événement d'écouter l'événement personnalisé.
- L'écouteur d'événement reçoit l'événement, récupère l'objet de window pour le transmettre à un observable et efface ce qui était stocké dans window.
- Bootstrap Angulaire
- C'est là que ma solution commence à vraiment différer -
- Créez un fichier exportant une interface dont la structure représente votre config.json - cela aide vraiment à la cohérence de type et la section suivante de code nécessite un type, et ne spécifiez pas
{}
ou any
lorsque vous savez que vous pouvez spécifier quelque chose de plus concret
- Créez le BehaviorSubject dans lequel vous passerez le fichier json analysé à l'étape 3.
- Utilisez les fonctions d'usine pour référencer les différentes sections de votre configuration pour maintenir le SOC
- Créez des jetons d'injection pour les fournisseurs ayant besoin du résultat de vos fonctions d'usine
- et / ou -
- Passez les fonctions d'usine directement dans les modules capables d'accepter une fonction dans ses méthodes forRoot () ou forChild ().
- 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());
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 {
get MemberInfo() {
this.attachProperty("MemberInfo", `${this.base}basic-info`);
return this.MemberInfo;
}
constructor(baseUrl: string) {
super(baseUrl + "member/api/");
}
}