Impostazioni app: in modo angolare?


90

Voglio aggiungere una App Settingssezione nella mia app in cui conterrà alcune cost e valori predefiniti.

Ho già letto questa risposta che usa OpaqueTokenMa è deprecata in Angular. Questo articolo spiega le differenze ma non ha fornito un esempio completo e i miei tentativi non hanno avuto successo.

Ecco cosa ho provato (non so se sia il modo giusto):

//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');

E questo è il componente in cui voglio usare quei const:

//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 {


}

Ma non funziona e ottengo errori.

Domanda:

Come posso consumare i valori "app.settings" in modo angolare?

plunker

NB Certo posso creare il servizio Injectable e inserirlo nel provider del NgModule, ma come ho detto voglio farlo in InjectionTokenmodo Angular.


Puoi controllare la mia risposta qui basata sulla documentazione ufficiale corrente
JavierFuentes

@javier no. Il tuo collegamento ha un problema se due provider forniscono lo stesso nome, quindi ora hai un problema. Entrando opaquetoken
Royi Namir

sai [OpaqueToken è deprecato]. ( angular.io/api/core/OpaqueToken ) Questo articolo parla di come prevenire le collisioni di nomi nei provider angolari
JavierFuentes

Sì, lo so, ma l'articolo collegato è ancora sbagliato.
Royi Namir

2
potrebbe essere il link sottostante può essere utile per tutti coloro che amano utilizzare la nuova architettura dello schema di configurazione angolare devblogs.microsoft.com/premier-developer/…
M_Farahmand

Risposte:


58

Ho capito come farlo con InjectionTokens (vedi esempio sotto) e se il tuo progetto è stato costruito usando Angular CLIpuoi usare i file di ambiente trovati in /environmentsper statico application wide settingscome un endpoint API, ma a seconda dei requisiti del tuo progetto molto probabilmente finirai per utilizzando entrambi poiché i file di ambiente sono solo letterali oggetto, mentre una configurazione iniettabile che utilizza InjectionToken's può utilizzare le variabili di ambiente e poiché è una classe può avere una logica applicata per configurarla in base ad altri fattori nell'applicazione, come i dati della richiesta http iniziale, sottodominio , eccetera.

Esempio di gettoni di iniezione

/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 { }

Ora puoi semplicemente inserirlo in qualsiasi componente, servizio, ecc:

/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);
  }

  // ...
}

Puoi anche digitare check the config usando l'AppConfig esportato.


No, ma puoi letteralmente copiare e incollare la prima parte in un file, importarla nel tuo file app.module.ts e il DI ovunque e visualizzarla sulla console. Ci vorrebbe più tempo per impostarlo in un plunker, quindi farebbe quei passaggi.
mtpultz

Oh, pensavo avessi già un plunker per questo :-) Grazie.
Royi Namir


1
Non credo che sia necessario esportare l'interfaccia / classe AppConfig. Sicuramente non hai bisogno di usarlo quando fai DI. Per fare in modo che funzioni in un file, doveva essere una classe invece di un'interfaccia, ma non importa. In effetti la guida allo stile suggerisce di utilizzare le classi invece delle interfacce poiché significa meno codice e puoi ancora digitare check usandole. Per quanto riguarda il suo utilizzo da parte di InjectionToken tramite generici, è qualcosa che vorresti includere.
mtpultz

1
Sto cercando di inserire dinamicamente l'endpoint dell'API usando le variabili di ambiente di Azure e le funzionalità di trasformazione JSON, ma sembra che questa risposta ottenga solo l'apiEndpoint dal file di ambiente. Come lo prenderesti dalla configurazione e lo esporteresti?
Archibald

138

Se stai usando , c'è ancora un'altra opzione:

Angular CLI fornisce file di ambiente in src/environments(quelli predefiniti sono environment.ts(dev) e environment.prod.ts(produzione)).

Nota che devi fornire i parametri di configurazione in tutti i environment.*file, ad esempio,

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__'
};

e usali nel tuo servizio (il file di ambiente corretto viene scelto automaticamente):

api.service.ts

// ... other imports
import { environment } from '../../environments/environment';

@Injectable()
export class ApiService {     

  public apiRequest(): Observable<MyObject[]> {
    const path = environment.apiEndpoint + `/objects`;
    // ...
  }

// ...
}

Maggiori informazioni sugli ambienti applicativi su Github (Angular CLI versione 6) o nella guida Angular ufficiale (versione 7) .


3
funziona bene Ma durante lo spostamento della build viene anche cambiato come bundle.Dovrei cambiare la configurazione nel mio servizio non nel codice dopo il passaggio alla produzione
kamalav

44
Questo è un po 'un anti-pattern nel normale sviluppo del software; l'URL dell'API è solo configurazione. Non dovrebbe essere necessaria una ricostruzione per riconfigurare un'app per un ambiente diverso. Dovrebbe essere compilato una volta, distribuito molte volte (pre-prod, staging, prod, ecc.).
Matt Tester

3
@MattTester Questa è in realtà quella che ora è una storia ufficiale di Angular-CLI. Se ti capita di avere una risposta migliore a questa domanda: sentiti libero di pubblicarla!
tilo

7
è configurabile dopo ng build?
NK

1
Oh ok, ho letto male i commenti. Sono d'accordo che questo si presta a un anti-pattern, pensavo ci fosse una storia per configurazioni dinamiche del tempo di esecuzione.
Jens Bodal

89

Non è consigliabile utilizzare i environment.*.tsfile per la configurazione dell'URL dell'API. Sembra che dovresti perché questo menziona la parola "ambiente".

L'utilizzo di questo è in realtà una configurazione in fase di compilazione . Se desideri modificare l'URL dell'API, dovrai ricostruirlo. È qualcosa che non vuoi dover fare ... chiedi al tuo amichevole dipartimento di controllo qualità :)

Ciò di cui hai bisogno è la configurazione del runtime , ovvero l'app carica la sua configurazione all'avvio.

Alcune altre risposte toccano questo, ma la differenza è che la configurazione deve essere caricata non appena l'app si avvia , in modo che possa essere utilizzata da un normale servizio ogni volta che ne ha bisogno.

Per implementare la configurazione runtime:

  1. Aggiungi un file di configurazione JSON alla /src/assets/cartella (in modo che venga copiato in build)
  2. Crea un file AppConfigServiceper caricare e distribuire il file config
  3. Carica la configurazione utilizzando un file APP_INITIALIZER

1. Aggiungi il file di configurazione a /src/assets

Potresti aggiungerlo a un'altra cartella, ma dovresti dire alla CLI che si tratta di una risorsa nel file angular.json. Inizia utilizzando la cartella delle risorse:

{
  "apiBaseUrl": "https://development.local/apiUrl"
}

2. Crea AppConfigService

Questo è il servizio che verrà iniettato ogni volta che avrai bisogno del valore di configurazione:

@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. Caricare la configurazione utilizzando un file APP_INITIALIZER

Per consentire AppConfigServiceche venga iniettato in modo sicuro, con la configurazione completamente caricata, è necessario caricare la configurazione all'avvio dell'app. È importante sottolineare che la funzione di fabbrica di inizializzazione deve restituire a in Promisemodo che Angular sappia di attendere fino al termine della risoluzione prima di terminare l'avvio:

@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 { }

Ora puoi iniettarlo ovunque ti serva e tutta la configurazione sarà pronta per essere letta:

@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;
  }

}

Non posso dirlo abbastanza forte, configurare i tuoi URL API come configurazione in fase di compilazione è un anti-pattern . Usa la configurazione runtime.


4
File locale o servizio diverso, la configurazione in fase di compilazione non deve essere utilizzata per un URL API. Immagina se la tua app viene venduta come prodotto (l'acquirente deve installarla), non vuoi che la compili, ecc. In ogni caso, non vuoi ricompilare qualcosa che è stato creato 2 anni fa, solo perché l'URL dell'API è cambiato. Il rischio!!
Matt Tester

1
@Bloodhound Puoi averne più di uno, APP_INITIALIZERma non credo che tu possa facilmente renderli dipendenti l'uno dall'altro. Sembra che tu abbia una buona domanda da porre, quindi magari collegalo qui?
Matt Tester

2
@MattTester - Se Angular implementasse mai questa funzione risolverebbe il nostro problema: github.com/angular/angular/issues/23279#issuecomment-528417026
Mike Becatti

2
@CrhistianRamirez È dal punto di vista dell'app: la configurazione non è nota fino al runtime e il file statico è al di fuori della build e può essere impostato in molti modi al momento del deploy. Il file statico va bene per la configurazione non sensibile. Un'API o un altro endpoint protetto è possibile con la stessa tecnica, ma come autenticarsi per renderlo protetto è la tua prossima sfida.
Matt Tester

1
@DaleK Leggendo tra le righe, stai distribuendo utilizzando Web Deploy. Se stai usando una pipeline di distribuzione, come Azure DevOps, è possibile impostare correttamente il file di configurazione come passaggio successivo. L'impostazione della configurazione è responsabilità del processo / pipeline di distribuzione, che può sovrascrivere i valori nel file di configurazione predefinito. Spero che questo chiarisca.
Matt Tester

8

Ecco la mia soluzione, carica da .json per consentire le modifiche senza ricostruire

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);
    }
}

e config.json

{
    "apiUrl": "http://localhost:3000/api"
}

1
Il problema con questo approccio è che config.json è aperto al mondo. Come impedirebbe a qualcuno di digitare www.mywebsite.com/assetts/config.json?
Alberto L. Bonfiglio

1
@ AlbertoL.Bonfiglio si configura il server per non consentire l'accesso dall'esterno al file config.json (o inserirlo in una directory che non ha accesso pubblico)
Alex Pandrea

Questa è anche la mia soluzione preferita, ma ancora preoccupata per i rischi per la sicurezza.
ViqMontana

7
Per favore, puoi aiutarmi a farlo bene? Come è più rischioso del tradizionale per ambienti angolari? Il contenuto completo di environments.prod.tsafter ng build --prodsarà in qualche .jsfile ad un certo punto. Anche se offuscati, i dati di environments.prod.tssaranno in chiaro. E come tutti i file .js, sarà disponibile sul computer dell'utente finale.
igann

5
@ AlbertoL.Bonfiglio Poiché un'app Angular è per natura un'applicazione client e JavaScript verrà utilizzato per il passaggio di dati e configurazione, non dovrebbe essere utilizzata alcuna configurazione segreta; tutte le definizioni di configurazione segreta dovrebbero essere dietro un livello API dove il browser o gli strumenti del browser di un utente non possono accedervi. Valori come l'URI di base di un'API sono consentiti per l'accesso pubblico perché l'API dovrebbe avere le proprie credenziali e sicurezza in base all'accesso di un utente (token portante su https).
Tommy Elliott

5

File di configurazione del povero uomo:

Aggiungi al tuo index.html come prima riga nel tag body:

<script lang="javascript" src="assets/config.js"></script>

Aggiungi assets / config.js:

var config = {
    apiBaseUrl: "http://localhost:8080"
}

Aggiungi config.ts:

export const config: AppConfig = window['config']

export interface AppConfig {
    apiBaseUrl: string
}

Scherzi a parte, +1 per ridurre una soluzione nei suoi componenti più elementari e mantenere la coerenza del tipo.
Luminoso

5

Ho scoperto che l'utilizzo di un APP_INITIALIZERper questo non funziona in situazioni in cui altri fornitori di servizi richiedono l'inserimento della configurazione. Possono essere istanziate prima che APP_INITIALIZERvenga eseguito.

Ho visto altre soluzioni che utilizzano fetchper leggere un file config.json e fornirlo utilizzando un token di iniezione in un parametro platformBrowserDynamic()prima del bootstrap del modulo root. Ma fetchnon è supportato in tutti i browser e in particolare nei browser WebView per i dispositivi mobili che scelgo come destinazione.

Quella che segue è una soluzione che funziona per me sia per PWA che per dispositivi mobili (WebView). Nota: finora ho provato solo su Android; lavorare da casa significa che non ho accesso a un Mac per creare.

In 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();

Questo codice:

  1. avvia una richiesta asincrona per il config.jsonfile.
  2. Quando la richiesta viene completata, analizza il JSON in un oggetto Javascript
  3. fornisce il valore utilizzando il APP_CONFIGtoken di iniezione, prima del bootstrap.
  4. E infine avvia il modulo di root.

APP_CONFIGpuò quindi essere iniettato in qualsiasi provider aggiuntivo in app-module.tse verrà definito. Ad esempio, posso inizializzare il FIREBASE_OPTIONStoken di iniezione da @angular/firecon quanto segue:

{
      provide: FIREBASE_OPTIONS,
      useFactory: (config: IConfig) => config.firebaseConfig,
      deps: [APP_CONFIG]
}

Trovo che l'intera cosa sia sorprendentemente difficile (e hacky) da fare per un requisito molto comune. Si spera che nel prossimo futuro ci sarà un modo migliore, come il supporto per le fabbriche di provider asincroni.

Il resto del codice per completezza ...

In 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');

e in app/lib/config/config.tsdefinisco l'interfaccia per il mio file di configurazione JSON:

export interface IConfig {
    name: string;
    version: string;
    instance: string;
    firebaseConfig: {
        apiKey: string;
        // etc
    }
}

La configurazione è memorizzata in assets/config/config.json:

{
  "name": "my-app",
  "version": "#{Build.BuildNumber}#",
  "instance": "localdev",
  "firebaseConfig": {
    "apiKey": "abcd"
    ...
  }
}

Nota: utilizzo un'attività di Azure DevOps per inserire Build.BuildNumber e sostituire altre impostazioni per diversi ambienti di distribuzione durante la distribuzione.


2

Ecco le mie due soluzioni per questo

1. Memorizza in file json

Basta creare un file json e accedere al componente tramite il $http.get()metodo. Se avevo bisogno di questo molto basso, allora è buono e veloce.

2. Archiviare utilizzando i servizi di dati

Se si desidera archiviare e utilizzare tutti i componenti o con un utilizzo elevato, è meglio utilizzare il servizio dati. Come questo :

  1. Basta creare una cartella statica all'interno della src/appcartella.

  2. Crea un file denominato come fuels.tsnella cartella statica. Puoi anche memorizzare altri file statici qui. Lascia definire i tuoi dati in questo modo. Supponendo che tu abbia dati sui combustibili.

__

export const Fuels {

   Fuel: [
    { "id": 1, "type": "A" },
    { "id": 2, "type": "B" },
    { "id": 3, "type": "C" },
    { "id": 4, "type": "D" },
   ];
   }
  1. Crea un nome file static.services.ts

__

import { Injectable } from "@angular/core";
import { Fuels } from "./static/fuels";

@Injectable()
export class StaticService {

  constructor() { }

  getFuelData(): Fuels[] {
    return Fuels;
  }
 }`
  1. Ora puoi renderlo disponibile per ogni modulo

basta importare nel file app.module.ts in questo modo e modificare i provider

import { StaticService } from './static.services';

providers: [StaticService]

Ora usalo come StaticServicein qualsiasi modulo.

È tutto.


Buona soluzione dato che non devi ricompilare. L'ambiente è come l'hard-coding nel codice. Cattiva. +1
Terrance00


0

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

  1. 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
  2. Chiedi a un ascoltatore di eventi che ascolti l'evento personalizzato.
  3. 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.
  4. Bootstrap angolare

- È qui che la mia soluzione inizia a differire davvero -

  1. 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 anyquando sai di poter specificare qualcosa di più concreto
  2. Crea il BehaviorSubject a cui passerai il file json analizzato nel passaggio 3.
  3. Usa le funzioni di fabbrica per fare riferimento alle diverse sezioni della tua configurazione per mantenere SOC
  4. Crea InjectionTokens per i fornitori che necessitano del risultato delle tue funzioni di fabbrica

- e / o -

  1. 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());

/*
  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

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) {
 // case statements for determining whether this is dev, test, stage, or prod
  }
  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 {

  /**
  * 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/");
  }
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.