Mieliśmy ten problem wiele lat temu, zanim dołączyłem do programu i opracowałem rozwiązanie wykorzystujące lokalną pamięć masową do przechowywania informacji o użytkowniku i środowisku. Angular 1.0 dnia, aby być dokładnym. Wcześniej dynamicznie tworzyliśmy plik js w czasie wykonywania, który następnie umieszczałby wygenerowane adresy URL interfejsu API w zmiennej globalnej. W dzisiejszych czasach jesteśmy nieco bardziej nastawieni na OOP i do niczego nie używamy lokalnej pamięci.
Stworzyłem lepsze rozwiązanie zarówno do określania środowiska, jak i tworzenia adresu URL interfejsu API.
Czym się to różni?
Aplikacja nie zostanie załadowana, dopóki nie zostanie załadowany plik config.json. Wykorzystuje funkcje fabryczne do tworzenia wyższego stopnia SOC. Mógłbym zawrzeć to w usłudze, ale nigdy nie widziałem żadnego powodu, dla którego jedynym podobieństwem między różnymi sekcjami pliku jest to, że istnieją one razem w pliku. Posiadanie funkcji fabrycznej pozwala mi przekazać tę funkcję bezpośrednio do modułu, jeśli jest w stanie zaakceptować funkcję. Wreszcie, mam łatwiejsze konfigurowanie wtrysków, gdy dostępne są funkcje fabryczne.
Wady?
Nie masz szczęścia używając tej konfiguracji (i większości innych odpowiedzi), jeśli moduł, który chcesz skonfigurować, nie pozwala na przekazanie funkcji fabrycznej do forRoot () lub forChild () i nie ma innego sposobu, aby skonfigurować pakiet przy użyciu funkcji fabrycznej.
Instrukcje
- Używając funkcji fetch do pobrania pliku json, przechowuję obiekt w oknie i wywołuję zdarzenie niestandardowe. - pamiętaj, aby zainstalować whatwg-fetch i dodać go do swoich polyfills.ts w celu zapewnienia zgodności z IE
- Niech detektor zdarzeń nasłuchuje zdarzenia niestandardowego.
- Detektor zdarzeń odbiera zdarzenie, pobiera obiekt z okna, aby przekazać go do obserwowalnego, i czyści to, co zostało zapisane w oknie.
- Bootstrap Angular
- Tutaj moje rozwiązanie zaczyna się naprawdę różnić -
- Utwórz plik eksportując interfejs, którego struktura reprezentuje twój config.json - to naprawdę pomaga w spójności typu, a następna sekcja kodu wymaga typu i nie określaj
{}
lub any
kiedy wiesz, że możesz określić coś bardziej konkretnego
- Utwórz BehaviorSubject, do którego przekażesz przeanalizowany plik JSON w kroku 3.
- Użyj funkcji fabrycznych, aby odwołać się do różnych sekcji konfiguracji, aby zachować SOC
- Utwórz InjectionTokens dla dostawców potrzebujących wyników funkcji fabrycznych
- i / lub -
- Przekaż funkcje fabryczne bezpośrednio do modułów, które mogą akceptować funkcję w jej metodach forRoot () lub forChild ().
- main.ts
Sprawdzam, czy okno [„środowisko”] nie jest zapełnione przed utworzeniem nasłuchiwania zdarzeń, aby umożliwić rozwiązanie, w którym okno [„środowisko”] jest wypełniane w inny sposób, zanim kod w main.ts zostanie kiedykolwiek wykonany.
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));
});
}
--- środowisko-resolvers.ts
Przypisuję wartość do BehaviorSubject przy użyciu window [„environment”] do redundancji. Możesz wymyślić rozwiązanie, w którym Twoja konfiguracja jest już wstępnie załadowana, a okno [„środowisko”] jest już wypełnione do czasu uruchomienia dowolnego kodu aplikacji Angulara, w tym kodu z 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 - uproszczony dla łatwiejszego zrozumienia
Śmieszny fakt! Starsze wersje NGXLoggera wymagały przekazania obiektu do LoggerModule.forRoot (). W rzeczywistości LoggerModule nadal to robi! NGXLogger uprzejmie udostępnia LoggerConfig, który możesz zmienić, umożliwiając użycie funkcji fabrycznej do konfiguracji.
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
Uzupełnienie
Jak rozwiązałem problem tworzenia moich adresów URL API?
Chciałem być w stanie zrozumieć, co robił każdy adres URL za pomocą komentarza i chciałem sprawdzać typy, ponieważ jest to największa siła TypeScript w porównaniu z javascript (IMO). Chciałem również stworzyć środowisko dla innych programistów, aby dodać nowe punkty końcowe i interfejs API, który był tak płynny, jak to tylko możliwe.
Utworzyłem klasę, która przyjmuje środowisko (programowanie, testowanie, etap, produkcja, „” itd.) I przekazałem tę wartość do serii klas [1-N], których zadaniem jest utworzenie podstawowego adresu URL dla każdej kolekcji API . Każda kolekcja ApiCollection jest odpowiedzialna za tworzenie podstawowego adresu URL dla każdego zbioru interfejsów API. Mogą to być nasze własne interfejsy API, interfejsy API dostawcy, a nawet łącze zewnętrzne. Ta klasa przekaże utworzony podstawowy adres URL do każdego kolejnego zawartego w niej interfejsu API. Przeczytaj poniższy kod, aby zobaczyć przykład gołej kości. Po skonfigurowaniu inny programista może bardzo łatwo dodać kolejny punkt końcowy do klasy API bez konieczności dotykania czegokolwiek innego.
TLDR; podstawowe zasady OOP i leniwe metody pobierające do optymalizacji pamięci
@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/");
}
}