Kami mengalami masalah ini bertahun-tahun yang lalu sebelum saya bergabung dan memiliki solusi yang menggunakan penyimpanan lokal untuk informasi pengguna dan lingkungan. Angular 1.0 hari tepatnya. Kami sebelumnya secara dinamis membuat file js saat runtime yang kemudian akan menempatkan url api yang dihasilkan ke dalam variabel global. Kami sedikit lebih didorong OOP hari ini dan tidak menggunakan penyimpanan lokal untuk apa pun.
Saya membuat solusi yang lebih baik untuk menentukan lingkungan dan pembuatan url api.
Apa bedanya?
Aplikasi tidak akan dimuat kecuali file config.json dimuat. Ini menggunakan fungsi pabrik untuk membuat tingkat SOC yang lebih tinggi. Saya dapat merangkum ini menjadi layanan, tetapi saya tidak pernah melihat alasan apa pun ketika satu-satunya kesamaan antara bagian file yang berbeda adalah bahwa mereka ada bersama dalam file. Memiliki fungsi pabrik memungkinkan saya untuk meneruskan fungsi tersebut secara langsung ke dalam modul jika dapat menerima suatu fungsi. Terakhir, saya memiliki waktu yang lebih mudah untuk mengatur InjectionTokens ketika fungsi pabrik tersedia untuk digunakan.
Kerugian?
Anda kurang beruntung menggunakan pengaturan ini (dan sebagian besar jawaban lainnya) jika modul yang ingin Anda konfigurasi tidak mengizinkan fungsi pabrik untuk diteruskan ke forRoot () atau forChild (), dan tidak ada cara lain untuk konfigurasikan paket dengan menggunakan fungsi pabrik.
Instruksi
- Menggunakan fetch untuk mengambil file json, saya menyimpan objek di jendela dan memunculkan acara khusus. - ingat untuk menginstal whatwg-fetch dan menambahkannya ke polyfills.ts Anda untuk kompatibilitas IE
- Minta pendengar acara mendengarkan acara khusus tersebut.
- Pemroses acara menerima acara, mengambil objek dari jendela untuk diteruskan ke yang dapat diamati, dan membersihkan apa yang disimpan di jendela.
- Bootstrap Angular
- Di sinilah solusi saya mulai sangat berbeda -
- Buat file yang mengekspor antarmuka yang strukturnya mewakili config.json Anda - ini sangat membantu dengan konsistensi tipe dan bagian kode berikutnya memerlukan tipe, dan jangan menentukan
{}
atau any
ketika Anda tahu Anda dapat menentukan sesuatu yang lebih konkret
- Buat BehaviorSubject tempat Anda akan meneruskan file json yang sudah diurai di langkah 3.
- Gunakan fungsi pabrik untuk mereferensikan bagian berbeda dari konfigurasi Anda untuk memelihara SOC
- Buat InjectionTokens untuk penyedia yang membutuhkan hasil dari fungsi pabrik Anda
- dan / atau -
- Meneruskan fungsi pabrik langsung ke modul yang mampu menerima fungsi baik dalam metode forRoot () atau forChild ().
- main.ts
Saya memeriksa jendela ["lingkungan"] tidak diisi sebelum membuat pendengar acara untuk memungkinkan kemungkinan solusi di mana jendela ["lingkungan"] diisi dengan cara lain sebelum kode di main.ts pernah dijalankan.
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
Saya menetapkan nilai ke BehaviorSubject menggunakan jendela ["lingkungan"] untuk redundansi. Anda bisa menemukan solusi di mana konfigurasi Anda sudah dimuat sebelumnya dan window ["environment"] sudah diisi pada saat kode aplikasi Angular Anda dijalankan, termasuk kode di 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 - Dipreteli untuk memudahkan pemahaman
Fakta menyenangkan! Versi lama NGXLogger mengharuskan Anda memasukkan objek ke LoggerModule.forRoot (). Nyatanya, LoggerModule masih melakukannya! NGXLogger dengan ramah memaparkan LoggerConfig yang dapat Anda timpa sehingga Anda dapat menggunakan fungsi pabrik untuk pengaturan.
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
Tambahan
Bagaimana cara mengatasi pembuatan url API saya?
Saya ingin dapat memahami apa yang dilakukan setiap url melalui komentar dan ingin pemeriksaan ketik karena itulah kekuatan terbesar TypeScript dibandingkan dengan javascript (IMO). Saya juga ingin menciptakan pengalaman bagi pengembang lain untuk menambahkan titik akhir baru, dan api yang semulus mungkin.
Saya membuat kelas yang mengambil lingkungan (dev, test, stage, prod, "", dan lain-lain) dan meneruskan nilai ini ke serangkaian kelas [1-N] yang tugasnya adalah membuat url dasar untuk setiap koleksi API . Setiap ApiCollection bertanggung jawab untuk membuat url dasar untuk setiap kumpulan API. Bisa jadi API kita sendiri, API vendor, atau bahkan link eksternal. Kelas itu akan meneruskan url dasar yang dibuat ke setiap api berikutnya yang dikandungnya. Baca kode di bawah ini untuk melihat contoh tulang belulang. Setelah penyiapan, sangat mudah bagi pengembang lain untuk menambahkan titik akhir lain ke kelas Api tanpa harus menyentuh apa pun.
TLDR; prinsip dasar OOP dan pengambil lambat untuk pengoptimalan memori
@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/");
}
}