Abbiamo riscontrato questo problema anni fa prima che mi iscrivessi e disponessi di una soluzione che utilizzava l'archiviazione locale per le informazioni sull'utente e sull'ambiente. Angolare 1.0 giorni per essere precisi. In precedenza stavamo creando dinamicamente un file js in fase di esecuzione che avrebbe quindi inserito gli URL API generati in una variabile globale. Oggigiorno siamo un po 'più orientati all'OOP e non utilizziamo l'archiviazione locale per nulla.
Ho creato una soluzione migliore sia per determinare l'ambiente che per la creazione di URL API.
In cosa differisce?
L'app non verrà caricata a meno che non venga caricato il file config.json. Utilizza le funzioni di fabbrica per creare un grado più elevato di SOC. Potrei incapsularlo in un servizio, ma non ho mai visto alcun motivo quando l'unica somiglianza tra le diverse sezioni del file è che esistono insieme nel file. Avere una funzione di fabbrica mi permette di passare la funzione direttamente in un modulo se è in grado di accettare una funzione. Infine, ho un tempo più facile impostare InjectionTokens quando le funzioni di fabbrica sono disponibili per l'utilizzo.
Aspetti negativi?
Sei sfortunato usando questa configurazione (e la maggior parte delle altre risposte) se il modulo che vuoi configurare non consente il passaggio di una funzione di fabbrica in forRoot () o forChild () e non c'è altro modo per configurare il pacchetto utilizzando una funzione di fabbrica.
Istruzioni
- Usando fetch per recuperare un file json, memorizzo l'oggetto nella finestra e sollevo un evento personalizzato. - ricorda di installare whatwg-fetch e aggiungilo al tuo polyfills.ts per la compatibilità con IE
- Chiedi a un ascoltatore di eventi che ascolti l'evento personalizzato.
- Il listener di eventi riceve l'evento, recupera l'oggetto dalla finestra per passare a un osservabile e cancella ciò che è stato memorizzato nella finestra.
- Bootstrap angolare
- È qui che la mia soluzione inizia a differire davvero -
- Crea un file esportando un'interfaccia la cui struttura rappresenta il tuo config.json: aiuta davvero con la coerenza del tipo e la sezione successiva di codice richiede un tipo, e non specificare
{}
o any
quando sai di poter specificare qualcosa di più concreto
- Crea il BehaviorSubject a cui passerai il file json analizzato nel passaggio 3.
- Usa le funzioni di fabbrica per fare riferimento alle diverse sezioni della tua configurazione per mantenere SOC
- Crea InjectionTokens per i fornitori che necessitano del risultato delle tue funzioni di fabbrica
- e / o -
- Passa le funzioni di fabbrica direttamente ai moduli in grado di accettare una funzione nei metodi forRoot () o forChild ().
- main.ts
Controllo che la finestra ["ambiente"] non sia popolata prima di creare un listener di eventi per consentire la possibilità di una soluzione in cui la finestra ["ambiente"] è popolata con altri mezzi prima che il codice in main.ts venga eseguito.
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
Assegno un valore a BehaviorSubject utilizzando la finestra ["environment"] per la ridondanza. Potresti escogitare una soluzione in cui la tua configurazione è già precaricata e la finestra ["environment"] è già popolata nel momento in cui viene eseguito il codice dell'app Angular, incluso il codice in 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) {
}
return resolvedEnvironment;
}
export function resolveNgxLoggerConfig() {
return configurationSubject.getValue().logging;
}
- app.module.ts - Spogliato per una più facile comprensione
Fatto divertente! Le versioni precedenti di NGXLogger richiedevano di passare un oggetto a LoggerModule.forRoot (). In effetti, il LoggerModule lo fa ancora! NGXLogger mostra gentilmente LoggerConfig che puoi sovrascrivere permettendoti di usare una funzione di fabbrica per la configurazione.
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
Addendum
Come ho risolto la creazione dei miei URL API?
Volevo essere in grado di capire cosa faceva ogni URL tramite un commento e volevo il controllo del tipo poiché è il più grande punto di forza di TypeScript rispetto a javascript (IMO). Volevo anche creare un'esperienza per altri sviluppatori per aggiungere nuovi endpoint e API che fosse il più semplice possibile.
Ho creato una classe che accetta l'ambiente (dev, test, stage, prod, "" e così via) e ho passato questo valore a una serie di classi [1-N] il cui compito è creare l'URL di base per ogni raccolta API . Ogni ApiCollection è responsabile della creazione dell'URL di base per ogni raccolta di API. Potrebbero essere le nostre API, le API di un fornitore o anche un collegamento esterno. Quella classe passerà l'URL di base creato a ogni successiva API che contiene. Leggi il codice seguente per vedere un esempio di ossa nude. Una volta configurato, è molto semplice per un altro sviluppatore aggiungere un altro endpoint a una classe Api senza dover toccare nient'altro.
TLDR; principi OOP di base e getter pigri per l'ottimizzazione della memoria
@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/");
}
}