App.settings - cara Angular?


90

Saya ingin menambahkan App Settingsbagian ke App saya di mana itu akan berisi beberapa konstanta dan nilai yang telah ditentukan sebelumnya.

Saya sudah membaca jawaban yang menggunakan OpaqueTokenTapi itu tidak digunakan lagi di Angular. Artikel ini menjelaskan perbedaannya tetapi tidak memberikan contoh lengkap, dan upaya saya tidak berhasil.

Inilah yang saya coba (saya tidak tahu apakah itu cara yang benar):

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

Dan ini adalah komponen tempat saya ingin menggunakan konstanta tersebut:

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


}

Tapi itu tidak berhasil dan saya mendapatkan kesalahan.

Pertanyaan:

Bagaimana saya bisa menggunakan nilai "app.settings" dengan cara Angular?

plunker

NB Tentu saya bisa membuat layanan Injectable dan menaruhnya di penyedia NgModule, Tapi seperti yang saya katakan saya ingin melakukannya dengan InjectionToken, cara Angular.


Anda dapat memeriksa jawaban saya di sini berdasarkan dokumentasi resmi terkini
JavierFuentes

@javier no. Tautan Anda bermasalah jika dua penyedia menyediakan nama yang sama sehingga Anda sekarang mengalami masalah. Entring opaquetoken
Royi Namir

Anda tahu [OpaqueToken tidak digunakan lagi]. ( angular.io/api/core/OpaqueToken ) Artikel ini membahas tentang cara mencegah tabrakan nama di Angular Providers
JavierFuentes

Yaeh i iknow tapi tetap saja artikel yang ditautkan salah.
Royi Namir

2
mungkin tautan di bawah ini dapat bermanfaat bagi setiap orang yang suka menggunakan arsitektur baru skema konfigurasi sudut devblogs.microsoft.com/premier-developer/…
M_Farahmand

Jawaban:


58

Saya menemukan cara melakukan ini dengan InjectionTokens (lihat contoh di bawah), dan jika proyek Anda dibangun menggunakan, Angular CLIAnda dapat menggunakan file lingkungan yang ditemukan /environmentsuntuk statis application wide settingsseperti titik akhir API, tetapi tergantung pada persyaratan proyek Anda, kemungkinan besar Anda akan berakhir. menggunakan keduanya karena file lingkungan hanyalah literal objek, sedangkan konfigurasi yang dapat diinjeksi menggunakan InjectionTokendapat menggunakan variabel lingkungan dan karena ini adalah kelas dapat menerapkan logika untuk mengonfigurasinya berdasarkan faktor lain dalam aplikasi, seperti data permintaan http awal, subdomain , dll.

Contoh Token Injeksi

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

Sekarang Anda dapat memasukkannya ke dalam komponen, layanan, dll.:

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

  // ...
}

Anda kemudian dapat juga mengetik periksa konfigurasi menggunakan AppConfig yang diekspor.


Tidak, tetapi Anda benar-benar dapat menyalin dan menempel bagian pertama ke dalam file, mengimpornya ke file app.module.ts Anda, dan DI di mana saja dan menampilkannya ke konsol. Saya akan membutuhkan waktu lebih lama untuk mengatur ini di plunker daripada melakukan langkah-langkah itu.
mtpultz

Oh saya pikir Anda sudah memiliki plunker untuk ini :-) Terima kasih.
Royi Namir


1
Saya tidak yakin Anda perlu mengekspor antarmuka / kelas AppConfig. Anda pasti tidak perlu menggunakannya saat melakukan DI. Untuk membuat ini berfungsi dalam satu file, itu harus menjadi kelas, bukan antarmuka, tetapi itu tidak masalah. Faktanya, panduan gaya menyarankan penggunaan kelas daripada antarmuka karena ini berarti lebih sedikit kode dan Anda masih bisa mengetik cek menggunakannya. Sehubungan dengan penggunaannya oleh InjectionToken melalui obat generik, itu adalah sesuatu yang ingin Anda sertakan.
mtpultz

1
Saya mencoba memasukkan titik akhir API secara dinamis menggunakan variabel lingkungan Azure dan fitur transformasi JSON, tetapi sepertinya jawaban ini hanya mendapatkan apiEndpoint dari file lingkungan. Bagaimana Anda mengambilnya dari konfigurasi dan mengekspornya?
Archibald

138

Jika Anda menggunakan , masih ada opsi lain:

CLI Angular menyediakan file lingkungan dalam src/environments(yang default adalah environment.ts(dev) dan environment.prod.ts(produksi)).

Perhatikan bahwa Anda perlu memberikan parameter konfigurasi di semua environment.*file, misalnya,

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

dan menggunakannya dalam layanan Anda (file lingkungan yang benar dipilih secara otomatis):

api.service.ts

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

@Injectable()
export class ApiService {     

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

// ...
}

Baca lebih lanjut tentang lingkungan aplikasi di Github (Angular CLI versi 6) atau di panduan Angular resmi (versi 7) .


3
itu berfungsi dengan baik. Tapi saat memindahkan build, itu juga diubah sebagai bundel. Saya harus mengubah konfigurasi di servis saya bukan dalam kode setelah pindah ke produksi
kamalav

44
Ini agak anti-pola dalam pengembangan perangkat lunak normal; url API hanyalah konfigurasi. Tidak perlu membangun ulang untuk mengonfigurasi ulang aplikasi untuk lingkungan yang berbeda. Ini harus dibuat sekali, diterapkan berkali-kali (pre-prod, staging, prod, dll).
Matt Tester

3
@MattTester Ini sebenarnya yang sekarang menjadi kisah resmi Angular-CLI. Jika Anda kebetulan memiliki jawaban yang lebih baik untuk pertanyaan ini: silakan posting!
tilo

7
apakah itu dapat dikonfigurasi setelah ng build?
NK

1
Oh oke, saya salah membaca komentar. Saya setuju bahwa ini mendukung anti-pola, saya pikir ada cerita untuk konfigurasi waktu proses yang dinamis.
Jens Bodal

89

Tidak disarankan menggunakan environment.*.tsfile untuk konfigurasi URL API Anda. Sepertinya Anda harus melakukannya karena ini menyebutkan kata "lingkungan".

Menggunakan ini sebenarnya adalah konfigurasi waktu kompilasi . Jika Anda ingin mengubah URL API, Anda perlu membuat ulang. Itu adalah sesuatu yang tidak ingin Anda lakukan ... tanyakan saja departemen QA Anda yang ramah :)

Yang Anda butuhkan adalah konfigurasi waktu proses , yaitu aplikasi memuat konfigurasinya saat dijalankan.

Beberapa jawaban lain menyentuh ini, tetapi perbedaannya adalah bahwa konfigurasi perlu dimuat segera setelah aplikasi dimulai , sehingga dapat digunakan oleh layanan normal kapan pun dibutuhkan.

Untuk mengimplementasikan konfigurasi runtime:

  1. Tambahkan file konfigurasi JSON ke /src/assets/folder (sehingga disalin pada build)
  2. Buat AppConfigServiceuntuk memuat dan mendistribusikan konfigurasi
  3. Muat konfigurasi menggunakan APP_INITIALIZER

1. Tambahkan file Config ke /src/assets

Anda dapat menambahkannya ke folder lain, tetapi Anda harus memberi tahu CLI bahwa itu adalah aset di file angular.json. Mulailah menggunakan folder aset:

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

2. Buat AppConfigService

Ini adalah layanan yang akan disuntikkan setiap kali Anda membutuhkan nilai konfigurasi:

@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. Muat konfigurasi menggunakan file APP_INITIALIZER

Agar AppConfigServicedapat diinjeksi dengan aman, dengan config dimuat penuh, kita perlu memuat konfigurasi pada waktu startup aplikasi. Yang penting, fungsi pabrik inisialisasi perlu mengembalikan a Promisesehingga Angular tahu untuk menunggu sampai selesai menyelesaikan sebelum menyelesaikan startup:

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

Sekarang Anda dapat memasukkannya ke mana pun Anda perlu dan semua konfigurasi akan siap untuk dibaca:

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

}

Saya tidak bisa mengatakannya dengan cukup kuat, mengonfigurasi url API Anda sebagai konfigurasi waktu kompilasi adalah anti-pola . Gunakan konfigurasi runtime.


4
File lokal atau layanan yang berbeda, konfigurasi waktu kompilasi tidak boleh digunakan untuk url API. Bayangkan jika aplikasi Anda dijual sebagai produk (pembeli harus menginstal), Anda tidak ingin mereka mengompilasinya, dll. Apa pun itu, Anda tidak ingin mengompilasi ulang sesuatu yang dibuat 2 tahun lalu, hanya karena url API berubah. Risikonya!!
Matt Tester

1
@Bloodhound Anda dapat memiliki lebih dari satu, APP_INITIALIZERtetapi saya rasa Anda tidak dapat dengan mudah membuat mereka bergantung satu sama lain. Sepertinya Anda memiliki pertanyaan yang bagus untuk ditanyakan, jadi mungkin tautkan ke sini?
Matt Tester

2
@MattTester - Jika Angular pernah mengimplementasikan fitur ini, itu akan menyelesaikan masalah kita: github.com/angular/angular/issues/23279#issuecomment-528417026
Mike Becatti

2
@CrhistianRamirez Ini dari sudut pandang aplikasi: konfigurasi tidak diketahui hingga runtime dan file statis berada di luar build dan dapat disetel dengan banyak cara pada waktu penerapan. File statis baik-baik saja untuk konfigurasi non-sensitif. API atau beberapa titik akhir terlindungi lainnya dimungkinkan dengan teknik yang sama, tetapi cara mengautentikasi agar terlindungi adalah tantangan Anda berikutnya.
Matt Tester

1
@DaleK Membaca yang tersirat, Anda menerapkan menggunakan Web Deploy. Jika Anda menggunakan pipeline penerapan, seperti Azure DevOps, maka dimungkinkan untuk menyetel file konfigurasi dengan benar sebagai langkah berikutnya. Pengaturan konfigurasi adalah tanggung jawab proses / pipeline penerapan, yang dapat mengganti nilai dalam file konfigurasi default. Harapan yang menjelaskan.
Matt Tester

8

Inilah solusi saya, memuat dari .json untuk memungkinkan perubahan tanpa membangun kembali

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

dan config.json

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

1
Masalah dengan pendekatan ini adalah config.json terbuka untuk dunia. Bagaimana Anda mencegah seseorang mengetik www.mywebsite.com/assetts/config.json?
Alberto L. Bonfiglio

1
@ AlbertoL.Bonfiglio Anda mengkonfigurasi server untuk tidak mengizinkan akses dari luar ke file config.json (atau letakkan di direktori yang tidak memiliki akses publik)
Alex Pandrea

Ini adalah solusi favorit saya juga, tetapi tetap memperhatikan risiko keamanan.
ViqMontana

7
Tolong, bisakah Anda membantu saya melakukannya dengan benar? Bagaimana ini lebih berisiko daripada tradisional untuk lingkungan bersudut? Konten lengkap environments.prod.tssetelah ng build --prodakan ada di beberapa .jsfile di beberapa titik. Meskipun dikaburkan, data dari environments.prod.tsakan menjadi teks yang jelas. Dan seperti semua file .js, itu akan tersedia di mesin pengguna akhir.
igann

5
@ AlbertoL.Bonfiglio Karena aplikasi Angular pada dasarnya adalah aplikasi klien, dan JavaScript akan digunakan untuk meneruskan data dan konfigurasi, tidak boleh ada konfigurasi rahasia yang digunakan di dalamnya; semua definisi konfigurasi rahasia harus berada di balik lapisan API yang tidak dapat diakses oleh browser pengguna atau alat browser. Nilai seperti URI dasar API boleh diakses oleh publik karena API harus memiliki kredensial dan keamanannya sendiri berdasarkan pengguna yang masuk (token pembawa melalui https).
Tommy Elliott

5

File konfigurasi orang miskin:

Tambahkan ke index.html Anda sebagai líne pertama di tag body:

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

Tambahkan assets / config.js:

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

Tambahkan config.ts:

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

export interface AppConfig {
    apiBaseUrl: string
}

Serius, +1 untuk meringkas solusi menjadi komponen paling dasar dan tetap menjaga konsistensi jenis.
Bercahaya

5

Saya menemukan bahwa menggunakan APP_INITIALIZERuntuk ini tidak berfungsi dalam situasi di mana penyedia layanan lain memerlukan konfigurasi untuk dimasukkan. Mereka bisa dipakai sebelum APP_INITIALIZERdijalankan.

Saya telah melihat solusi lain yang digunakan fetchuntuk membaca file config.json dan menyediakannya menggunakan token injeksi dalam parameter platformBrowserDynamic()sebelum melakukan bootstrap pada modul root. Namun fetchtidak didukung di semua browser dan di browser WebView tertentu untuk perangkat seluler yang saya targetkan.

Berikut ini adalah solusi yang cocok untuk saya untuk PWA dan perangkat seluler (WebView). Catatan: Sejauh ini saya hanya menguji di Android; bekerja dari rumah berarti saya tidak memiliki akses ke Mac untuk membangun.

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

Kode ini:

  1. memulai permintaan async untuk config.jsonfile tersebut.
  2. Saat permintaan selesai, parsing JSON menjadi objek Javascript
  3. memberikan nilai menggunakan APP_CONFIGtoken injeksi, sebelum melakukan bootstrap.
  4. Dan terakhir, bootstrap modul root.

APP_CONFIGkemudian dapat disuntikkan ke penyedia tambahan app-module.tsdan itu akan ditentukan. Misalnya, saya dapat menginisialisasi FIREBASE_OPTIONStoken injeksi dari @angular/fireyang berikut ini:

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

Saya menemukan semua ini hal yang sangat sulit (dan hacky) untuk dilakukan untuk persyaratan yang sangat umum. Mudah-mudahan dalam waktu dekat akan ada cara yang lebih baik lagi, seperti support untuk pabrik provider async.

Sisa kode untuk kelengkapan ...

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

dan app/lib/config/config.tssaya mendefinisikan antarmuka untuk file konfigurasi JSON saya:

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

Config disimpan di assets/config/config.json:

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

Catatan: Saya menggunakan tugas Azure DevOps untuk menyisipkan Build.BuildNumber dan mengganti pengaturan lain untuk lingkungan penerapan yang berbeda saat sedang diterapkan.


2

Inilah dua solusi saya untuk ini

1. Simpan dalam file json

Buat saja file json dan masuk ke komponen Anda dengan $http.get() metode. Jika saya membutuhkan ini sangat rendah maka itu bagus dan cepat.

2. Simpan dengan menggunakan layanan data

Jika Anda ingin menyimpan dan menggunakan di semua komponen atau memiliki penggunaan yang besar maka sebaiknya gunakan layanan data. Seperti ini :

  1. Buat saja folder statis di dalam src/appfolder.

  2. Buat file dengan nama fuels.tsfolder statis. Anda juga dapat menyimpan file statis lainnya di sini. Mari tentukan data Anda seperti ini. Dengan asumsi Anda memiliki data bahan bakar.

__

export const Fuels {

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

__

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

@Injectable()
export class StaticService {

  constructor() { }

  getFuelData(): Fuels[] {
    return Fuels;
  }
 }`
  1. Sekarang Anda dapat membuat ini tersedia untuk setiap modul

cukup impor di file app.module.ts seperti ini dan ubah penyedia

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

providers: [StaticService]

Sekarang gunakan ini seperti StaticServicepada modul apa pun.

Itu saja.


Solusi bagus karena Anda tidak perlu mengkompilasi ulang. Lingkungan seperti hard-coding di dalam kode. Menjijikan. +1
Terrance00


0

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

  1. 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
  2. Minta pendengar acara mendengarkan acara khusus tersebut.
  3. Pemroses acara menerima acara, mengambil objek dari jendela untuk diteruskan ke yang dapat diamati, dan membersihkan apa yang disimpan di jendela.
  4. Bootstrap Angular

- Di sinilah solusi saya mulai sangat berbeda -

  1. 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 anyketika Anda tahu Anda dapat menentukan sesuatu yang lebih konkret
  2. Buat BehaviorSubject tempat Anda akan meneruskan file json yang sudah diurai di langkah 3.
  3. Gunakan fungsi pabrik untuk mereferensikan bagian berbeda dari konfigurasi Anda untuk memelihara SOC
  4. Buat InjectionTokens untuk penyedia yang membutuhkan hasil dari fungsi pabrik Anda

- dan / atau -

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

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

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

  /**
  * 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/");
  }
}
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.