Saya ingin menambahkan App Settings
bagian ke App saya di mana itu akan berisi beberapa konstanta dan nilai yang telah ditentukan sebelumnya.
Saya sudah membaca jawaban yang menggunakan OpaqueToken
Tapi 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?
NB Tentu saya bisa membuat layanan Injectable dan menaruhnya di penyedia NgModule, Tapi seperti yang saya katakan saya ingin melakukannya dengan InjectionToken
, cara Angular.
sumber
Jawaban:
Saya menemukan cara melakukan ini dengan InjectionTokens (lihat contoh di bawah), dan jika proyek Anda dibangun menggunakan,
Angular CLI
Anda dapat menggunakan file lingkungan yang ditemukan/environments
untuk statisapplication wide settings
seperti 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 menggunakanInjectionToken
dapat 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.
sumber
Jika Anda menggunakan angular-cli, masih ada opsi lain:
CLI Angular menyediakan file lingkungan dalam
src/environments
(yang default adalahenvironment.ts
(dev) danenvironment.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) .
sumber
Tidak disarankan menggunakan
environment.*.ts
file 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:
/src/assets/
folder (sehingga disalin pada build)AppConfigService
untuk memuat dan mendistribusikan konfigurasiAPP_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
AppConfigService
dapat diinjeksi dengan aman, dengan config dimuat penuh, kita perlu memuat konfigurasi pada waktu startup aplikasi. Yang penting, fungsi pabrik inisialisasi perlu mengembalikan aPromise
sehingga 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.
sumber
APP_INITIALIZER
tetapi 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?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" }
sumber
environments.prod.ts
setelahng build --prod
akan ada di beberapa.js
file di beberapa titik. Meskipun dikaburkan, data darienvironments.prod.ts
akan menjadi teks yang jelas. Dan seperti semua file .js, itu akan tersedia di mesin pengguna akhir.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 }
sumber
Saya menemukan bahwa menggunakan
APP_INITIALIZER
untuk ini tidak berfungsi dalam situasi di mana penyedia layanan lain memerlukan konfigurasi untuk dimasukkan. Mereka bisa dipakai sebelumAPP_INITIALIZER
dijalankan.Saya telah melihat solusi lain yang digunakan
fetch
untuk membaca file config.json dan menyediakannya menggunakan token injeksi dalam parameterplatformBrowserDynamic()
sebelum melakukan bootstrap pada modul root. Namunfetch
tidak 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:
config.json
file tersebut.APP_CONFIG
token injeksi, sebelum melakukan bootstrap.APP_CONFIG
kemudian dapat disuntikkan ke penyedia tambahanapp-module.ts
dan itu akan ditentukan. Misalnya, saya dapat menginisialisasiFIREBASE_OPTIONS
token injeksi dari@angular/fire
yang 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.ts
saya 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.
sumber
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 :
Buat saja folder statis di dalam
src/app
folder.Buat file dengan nama
fuels.ts
folder 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" }, ]; }
__
import { Injectable } from "@angular/core"; import { Fuels } from "./static/fuels"; @Injectable() export class StaticService { constructor() { } getFuelData(): Fuels[] { return Fuels; } }`
cukup impor di file app.module.ts seperti ini dan ubah penyedia
import { StaticService } from './static.services'; providers: [StaticService]
Sekarang gunakan ini seperti
StaticService
pada modul apa pun.Itu saja.
sumber
Saya menemukan ini Angular How-to: File Konfigurasi yang Dapat Diedit dari blog Microsoft Dev menjadi solusi terbaik. Anda dapat mengonfigurasi pengaturan build dev atau pengaturan build prod.
sumber
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
- Di sinilah solusi saya mulai sangat berbeda -
{}
atauany
ketika Anda tahu Anda dapat menentukan sesuatu yang lebih konkret- dan / atau -
- 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/"); } }
sumber