App.settings - วิธีเชิงมุม?


90

ฉันต้องการเพิ่มApp Settingsส่วนลงในแอปของฉันซึ่งจะมีค่า const และค่าที่กำหนดไว้ล่วงหน้า

ฉันได้อ่านคำตอบนี้แล้วซึ่งใช้OpaqueTokenแต่มันเลิกใช้งานใน Angular แล้ว นี้บทความอธิบายความแตกต่าง แต่ก็ไม่ได้ให้เป็นตัวอย่างเต็มรูปแบบและความพยายามของฉันไม่ประสบความสำเร็จ

นี่คือสิ่งที่ฉันได้ลอง (ฉันไม่รู้ว่ามาถูกทางหรือเปล่า):

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

และนี่คือส่วนประกอบที่ฉันต้องการใช้ 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 {


}

แต่มันไม่ได้ผลและฉันได้รับข้อผิดพลาด

คำถาม:

ฉันจะใช้ค่า "app.settings" ด้วยวิธีเชิงมุมได้อย่างไร

นักเลง

หมายเหตุแน่นอนว่าฉันสามารถสร้างบริการแบบฉีดได้และวางไว้ในผู้ให้บริการของ NgModule แต่อย่างที่ฉันบอกว่าต้องการทำด้วยInjectionTokenวิธีเชิงมุม


คุณสามารถตรวจสอบคำตอบของฉันได้ที่นี่ตามเอกสารอย่างเป็นทางการในปัจจุบัน
JavierFuentes

@javier เลขที่ ลิงก์ของคุณมีปัญหาหากผู้ให้บริการ 2 รายใช้ชื่อเดียวกันดังนั้นตอนนี้คุณมีปัญหา Entring opaquetoken
Royi Namir

คุณรู้ว่า [OpaqueToken เลิกใช้งานแล้ว] ( angular.io/api/core/OpaqueToken ) บทความนี้พูดถึงวิธีป้องกันการชนชื่อใน Angular Providers
JavierFuentes

Yaeh i iknow แต่บทความที่เชื่อมโยงยังไม่ถูกต้อง
Royi Namir

2
อาจจะเป็นลิงค์ด้านล่างนี้จะเป็นประโยชน์สำหรับทุกคนที่ชอบใช้สถาปัตยกรรมใหม่ของ angular config schema devblogs.microsoft.com/premier-developer/…
M_Farahmand

คำตอบ:


58

ฉันคิดหาวิธีดำเนินการกับ InjectionTokens (ดูตัวอย่างด้านล่าง) และหากโครงการของคุณสร้างขึ้นโดยใช้Angular CLIคุณสามารถใช้ไฟล์สภาพแวดล้อมที่พบใน/environmentsแบบคงที่application wide settingsเช่นจุดสิ้นสุด API แต่ทั้งนี้ขึ้นอยู่กับข้อกำหนดของโครงการของคุณคุณมักจะจบลง โดยใช้ทั้งสองอย่างเนื่องจากไฟล์สภาพแวดล้อมเป็นเพียงตัวอักษรของวัตถุในขณะที่การกำหนดค่าแบบฉีดได้โดยใช้InjectionToken'สามารถใช้ตัวแปรสภาพแวดล้อมได้และเนื่องจากคลาสนี้สามารถใช้ตรรกะเพื่อกำหนดค่าตามปัจจัยอื่น ๆ ในแอปพลิเคชันเช่นข้อมูลคำขอ http เริ่มต้นโดเมนย่อย ฯลฯ

ตัวอย่างโทเค็นการฉีด

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

ตอนนี้คุณสามารถ DI ลงในส่วนประกอบบริการ ฯลฯ :

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

  // ...
}

จากนั้นคุณสามารถพิมพ์ตรวจสอบการกำหนดค่าโดยใช้ AppConfig ที่ส่งออก


ไม่ได้ แต่คุณสามารถคัดลอกและวางส่วนแรกลงในไฟล์ได้อย่างแท้จริงนำเข้าในไฟล์ app.module.ts ของคุณและ DI ที่ใดก็ได้และส่งออกไปยังคอนโซล ฉันจะใช้เวลานานกว่าในการตั้งค่านี้ใน plunker จากนั้นก็จะทำตามขั้นตอนเหล่านั้น
mtpultz

โอ้ฉันคิดว่าคุณมีคนที่ชอบเรื่องนี้อยู่แล้ว :-) ขอบคุณ
Royi Namir

สำหรับผู้ที่ต้องการ: plnkr.co/edit/2YMZk5mhP1B4jTgA37B8?p=preview
Royi Namir

1
ฉันไม่เชื่อว่าคุณต้องส่งออกอินเตอร์เฟส / คลาสของ AppConfig แน่นอนคุณไม่จำเป็นต้องใช้เมื่อทำ DI ในการทำให้สิ่งนี้ทำงานได้ในไฟล์เดียวนั้นจะต้องเป็นคลาสแทนที่จะเป็นอินเทอร์เฟซ แต่นั่นไม่สำคัญ ในความเป็นจริงคำแนะนำสไตล์แนะนำให้ใช้คลาสแทนอินเทอร์เฟซเนื่องจากมีรหัสน้อยกว่าและคุณยังสามารถพิมพ์ check โดยใช้พวกเขาได้ เกี่ยวกับการใช้งานโดย InjectionToken ผ่านทางยาสามัญซึ่งเป็นสิ่งที่คุณต้องการรวมไว้ด้วย
mtpultz

1
ฉันพยายามฉีดจุดสิ้นสุด API แบบไดนามิกโดยใช้ตัวแปรสภาพแวดล้อมของ Azure และคุณสมบัติการแปลง JSON แต่ดูเหมือนว่าคำตอบนี้จะได้รับ apiEndpoint จากไฟล์สภาพแวดล้อม คุณจะคว้ามันจาก config และส่งออกได้อย่างไร?
Archibald

138

หากคุณกำลังใช้ ยังมีอีกทางเลือกหนึ่ง:

Angular CLI จัดเตรียมไฟล์สภาพแวดล้อมในsrc/environments( ไฟล์ดีฟอลต์คือenvironment.ts(dev) และenvironment.prod.ts(การผลิต))

โปรดทราบว่าคุณต้องระบุพารามิเตอร์การกำหนดค่าในenvironment.*ไฟล์ทั้งหมดเช่น

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

และใช้ในบริการของคุณ (ไฟล์สภาพแวดล้อมที่ถูกต้องจะถูกเลือกโดยอัตโนมัติ):

api.service.ts

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

@Injectable()
export class ApiService {     

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

// ...
}

อ่านเพิ่มเติมเกี่ยวกับสภาพแวดล้อมที่ใช้บนGithub (เชิงมุม CLI รุ่น 6)หรือในคู่มือเชิงมุมอย่างเป็นทางการ (รุ่น 7)


3
มันใช้งานได้ดี แต่ในขณะที่ย้ายบิลด์มันก็เปลี่ยนเป็นบันเดิลเช่นกันฉันควรเปลี่ยนการกำหนดค่าในการเสิร์ฟของฉันที่ไม่อยู่ในโค้ดหลังจากย้ายไปที่การผลิต
kamalav

44
นี่เป็นการต่อต้านรูปแบบในการพัฒนาซอฟต์แวร์ปกติ URL ของ API เป็นเพียงการกำหนดค่า ไม่ควรใช้การสร้างใหม่เพื่อกำหนดค่าแอปใหม่สำหรับสภาพแวดล้อมอื่น ควรสร้างครั้งเดียวปรับใช้หลายครั้ง (pre-prod, staging, prod ฯลฯ )
Matt Tester

3
@MattTester นี่คือสิ่งที่ตอนนี้เป็นเรื่องราว Angular-CLI อย่างเป็นทางการ หากคุณมีคำตอบที่ดีกว่าสำหรับคำถามนี้: โพสต์ได้ตามต้องการ!
จนถึง

7
สามารถกำหนดค่าได้หลังจากสร้าง ng?
NK

1
โอเคฉันอ่านความคิดเห็นผิด ฉันยอมรับว่าสิ่งนี้ยืมไปสู่การต่อต้านรูปแบบฉันคิดว่ามีเรื่องราวสำหรับการกำหนดค่าเวลาทำงานแบบไดนามิก
Jens Bodal

89

ไม่แนะนำให้ใช้environment.*.tsไฟล์สำหรับการกำหนดค่า URL API ของคุณ ดูเหมือนว่าคุณควรเพราะสิ่งนี้กล่าวถึงคำว่า "สิ่งแวดล้อม"

ใช้นี้เป็นจริงการกำหนดค่ารวบรวมเวลา หากคุณต้องการเปลี่ยน URL ของ API คุณจะต้องสร้างใหม่ นั่นคือสิ่งที่คุณไม่ต้องการทำ ... เพียงแค่ถามแผนก QA ที่เป็นมิตรของคุณ :)

สิ่งที่คุณต้องการคือการกำหนดค่ารันไทม์นั่นคือแอปจะโหลดการกำหนดค่าเมื่อเริ่มทำงาน

คำตอบอื่น ๆ บางคำตอบเกี่ยวกับเรื่องนี้ แต่ความแตกต่างคือการกำหนดค่าจะต้องโหลดทันทีที่แอปเริ่มทำงานเพื่อให้สามารถใช้บริการปกติได้ทุกเมื่อที่ต้องการ

ในการใช้การกำหนดค่ารันไทม์:

  1. เพิ่มไฟล์กำหนดค่า JSON ลงใน/src/assets/โฟลเดอร์ (เพื่อที่จะถูกคัดลอกในบิลด์)
  2. สร้างAppConfigServiceเพื่อโหลดและแจกจ่าย config
  3. โหลดการกำหนดค่าโดยใช้ไฟล์ APP_INITIALIZER

1. เพิ่มไฟล์ Config ไปที่ /src/assets

คุณสามารถเพิ่มลงในโฟลเดอร์อื่นได้ แต่คุณต้องบอก CLI ว่าเป็นเนื้อหาในไฟล์angular.json. เริ่มต้นโดยใช้โฟลเดอร์สินทรัพย์:

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

2. สร้าง AppConfigService

นี่คือบริการที่จะถูกฉีดทุกครั้งที่คุณต้องการค่า config:

@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. โหลดการกำหนดค่าโดยใช้ไฟล์ APP_INITIALIZER

เพื่อให้AppConfigServiceสามารถฉีดได้อย่างปลอดภัยด้วย config ที่โหลดเต็มเราจำเป็นต้องโหลด config ในเวลาเริ่มต้นแอพ ที่สำคัญฟังก์ชั่น initialisation factory ต้องส่งคืนค่าPromiseเพื่อให้ Angular รู้ว่าต้องรอจนกว่าจะเสร็จสิ้นก่อนที่จะเสร็จสิ้นการเริ่มต้น

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

ตอนนี้คุณสามารถฉีดได้ทุกที่ที่คุณต้องการและการกำหนดค่าทั้งหมดจะพร้อมอ่าน:

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

}

ฉันไม่สามารถพูดได้อย่างมากพอที่กำหนดค่าของคุณURL ของ API การกำหนดค่าเวลารวบรวมเป็นรูปแบบการป้องกัน ใช้การกำหนดค่ารันไทม์


4
ไฟล์ในเครื่องหรือบริการอื่นไม่ควรใช้การกำหนดค่าเวลาคอมไพล์สำหรับ URL API ลองนึกภาพว่าแอปของคุณขายเป็นผลิตภัณฑ์ (ผู้ซื้อเพื่อติดตั้ง) คุณไม่ต้องการให้พวกเขารวบรวมหรืออื่น ๆ ไม่ว่าจะด้วยวิธีใดคุณไม่ต้องการรวบรวมสิ่งที่สร้างขึ้นเมื่อ 2 ปีก่อนอีกครั้งเพียงเพราะ เปลี่ยน URL ของ API แล้ว ความเสี่ยง!!
Matt Tester

1
@Bloodhound คุณสามารถมีได้มากกว่าหนึ่งAPP_INITIALIZERแต่ฉันไม่คิดว่าคุณจะทำให้พวกเขาพึ่งพากันและกันได้ง่ายๆ ดูเหมือนว่าคุณมีคำถามที่ดีที่จะถามดังนั้นอาจจะเชื่อมโยงไปที่นี่?
Matt Tester

2
@MattTester - หาก Angular เคยใช้คุณลักษณะนี้มันจะช่วยแก้ปัญหาของเราได้: github.com/angular/angular/issues/23279#issuecomment-528417026
Mike Becatti

2
@CrhistianRamirez จากมุมมองของแอป: การกำหนดค่าไม่เป็นที่รู้จักจนกว่าจะรันไทม์และไฟล์คงที่อยู่นอกบิลด์และสามารถตั้งค่าได้หลายวิธีในเวลาปรับใช้ ไฟล์คงที่ใช้ได้ดีสำหรับการกำหนดค่าที่ไม่ละเอียดอ่อน API หรือปลายทางที่ได้รับการป้องกันอื่น ๆ สามารถทำได้ด้วยเทคนิคเดียวกัน แต่วิธีการตรวจสอบสิทธิ์เพื่อให้ได้รับการปกป้องเป็นความท้าทายต่อไปของคุณ
Matt Tester

1
@DaleK อ่านระหว่างบรรทัดคุณกำลังปรับใช้โดยใช้ Web Deploy หากคุณใช้ไปป์ไลน์การปรับใช้เช่น Azure DevOps คุณสามารถตั้งค่าไฟล์กำหนดค่าได้อย่างถูกต้องเป็นขั้นตอนถัดไป การตั้งค่าคอนฟิกเป็นความรับผิดชอบของกระบวนการปรับใช้ / ไปป์ไลน์ซึ่งสามารถแทนที่ค่าในไฟล์กำหนดค่าเริ่มต้นได้ หวังว่าจะกระจ่าง
Matt Tester

8

นี่คือวิธีแก้ปัญหาของฉันโหลดจาก. json เพื่ออนุญาตการเปลี่ยนแปลงโดยไม่ต้องสร้างใหม่

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

และ config.json

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

1
ปัญหาของแนวทางนี้คือ config.json เปิดให้คนทั้งโลก คุณจะป้องกันไม่ให้ใครบางคนพิมพ์ www.mywebsite.com/assetts/config.json ได้อย่างไร
Alberto L. Bonfiglio

1
@ AlbertoL.Bonfiglio คุณกำหนดค่าเซิร์ฟเวอร์ไม่ให้เข้าถึงไฟล์ config.json จากภายนอก (หรือวางไว้ในไดเร็กทอรีที่ไม่มีการเข้าถึงสาธารณะ)
Alex Pandrea

นี่เป็นวิธีแก้ปัญหาที่ฉันชอบเช่นกัน แต่ยังคงกังวลเกี่ยวกับความเสี่ยงด้านความปลอดภัย
ViqMontana

7
ได้โปรดคุณช่วยฉันให้ถูกต้องได้ไหม? มีความเสี่ยงมากกว่าแบบดั้งเดิมสำหรับสภาพแวดล้อมเชิงมุมอย่างไร? เนื้อหาทั้งหมดของenvironments.prod.tsafter ng build --prodจะอยู่ในบาง.jsไฟล์ในบางจุด แม้ว่าข้อมูลenvironments.prod.tsจะสับสน แต่ข้อมูลจากจะเป็นข้อความที่ชัดเจน และเช่นเดียวกับไฟล์. js ทั้งหมดจะพร้อมใช้งานบนเครื่องของผู้ใช้ปลายทาง
igann

5
@ AlbertoL.Bonfiglio เนื่องจากแอป Angular เป็นแอปพลิเคชันไคลเอนต์และ JavaScript จะถูกใช้เพื่อส่งผ่านข้อมูลและการกำหนดค่าจึงไม่ควรใช้การกำหนดค่าลับ ข้อกำหนดการกำหนดค่าลับทั้งหมดควรอยู่หลังเลเยอร์ API ที่เบราว์เซอร์ของผู้ใช้หรือเครื่องมือเบราว์เซอร์ไม่สามารถเข้าถึงได้ ค่าเช่น URI พื้นฐานของ API เป็นสิ่งที่ประชาชนสามารถเข้าถึงได้เนื่องจาก API ควรมีข้อมูลรับรองและการรักษาความปลอดภัยของตัวเองตามการลงชื่อเข้าใช้ของผู้ใช้ (โทเค็นผู้ถือผ่าน https)
Tommy Elliott

5

ไฟล์กำหนดค่าของคนจน:

เพิ่มลงใน index.html ของคุณเป็นลินแรกในแท็ก body:

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

เพิ่มเนื้อหา / config.js:

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

เพิ่ม config.ts:

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

export interface AppConfig {
    apiBaseUrl: string
}

อย่างจริงจัง +1 สำหรับการต้มสารละลายลงในส่วนประกอบพื้นฐานที่สุดและยังคงรักษาความสม่ำเสมอของประเภท
Luminous

5

ฉันพบว่าการใช้APP_INITIALIZERสำหรับสิ่งนี้ไม่ได้ผลในสถานการณ์ที่ผู้ให้บริการรายอื่นต้องการให้มีการแทรกการกำหนดค่า สามารถสร้างอินสแตนซ์ก่อนที่APP_INITIALIZERจะรัน

ฉันเคยเห็นโซลูชันอื่น ๆ ที่ใช้fetchในการอ่านไฟล์ config.json และจัดเตรียมโดยใช้โทเค็นการฉีดในพารามิเตอร์platformBrowserDynamic()ก่อนที่จะบูตโมดูลรูท แต่fetchไม่ได้รับการสนับสนุนในทุกเบราว์เซอร์และโดยเฉพาะเบราว์เซอร์ WebView สำหรับอุปกรณ์มือถือที่ฉันกำหนดเป้าหมาย

ต่อไปนี้เป็นวิธีแก้ปัญหาที่เหมาะกับฉันสำหรับทั้ง PWA และอุปกรณ์เคลื่อนที่ (WebView) หมายเหตุ: จนถึงขณะนี้ฉันได้ทดสอบใน Android เท่านั้น การทำงานจากที่บ้านหมายความว่าฉันไม่มีสิทธิ์เข้าถึง Mac เพื่อสร้าง

ใน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();

รหัสนี้:

  1. เริ่มต้นคำขอ async สำหรับconfig.jsonไฟล์
  2. เมื่อคำขอเสร็จสมบูรณ์ให้แยกวิเคราะห์ JSON เป็นวัตถุ Javascript
  3. ให้ค่าโดยใช้APP_CONFIGโทเค็นการฉีดก่อนที่จะบูตสตราป
  4. และในที่สุดก็บูตสแตรปโมดูลรูท

APP_CONFIGจากนั้นสามารถฉีดเข้าไปในผู้ให้บริการเพิ่มเติมได้app-module.tsและจะมีการกำหนด ตัวอย่างเช่นฉันสามารถเริ่มต้นFIREBASE_OPTIONSโทเค็นการฉีดได้จาก@angular/fireสิ่งต่อไปนี้:

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

ฉันพบว่าสิ่งทั้งหมดนี้เป็นสิ่งที่ยาก (และแฮ็ก) อย่างน่าประหลาดใจที่ต้องทำเพื่อความต้องการทั่วไป หวังว่าในอนาคตอันใกล้จะมีวิธีที่ดีกว่านี้เช่นการสนับสนุนโรงงานผู้ให้บริการ async

ส่วนที่เหลือของรหัสเพื่อความสมบูรณ์ ...

ใน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');

และในapp/lib/config/config.tsฉันกำหนดอินเทอร์เฟซสำหรับไฟล์กำหนดค่า JSON ของฉัน:

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

Config ถูกเก็บไว้ในassets/config/config.json:

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

หมายเหตุ: ฉันใช้งาน Azure DevOps เพื่อแทรก Build.BuildNumber และแทนที่การตั้งค่าอื่น ๆ สำหรับสภาพแวดล้อมการปรับใช้ที่แตกต่างกันในขณะที่กำลังปรับใช้


2

นี่คือสองวิธีของฉันสำหรับสิ่งนี้

1. จัดเก็บในไฟล์ json

เพียงแค่สร้างไฟล์ json และเข้าสู่ส่วนประกอบของคุณโดย $http.get()วิธีการ ถ้าฉันต้องการสิ่งนี้ต่ำมากก็จะดีและรวดเร็ว

2. จัดเก็บโดยใช้บริการข้อมูล

หากคุณต้องการจัดเก็บและใช้งานในส่วนประกอบทั้งหมดหรือมีการใช้งานจำนวนมากควรใช้บริการข้อมูลจะดีกว่า แบบนี้ :

  1. เพียงสร้างโฟลเดอร์แบบคงที่ภายในsrc/appโฟลเดอร์

  2. สร้างไฟล์ชื่อfuels.tsลงในโฟลเดอร์แบบคงที่ คุณสามารถจัดเก็บไฟล์คงที่อื่น ๆ ได้ที่นี่เช่นกัน ให้กำหนดข้อมูลของคุณเช่นนี้ สมมติว่าคุณมีข้อมูลเชื้อเพลิง

__

export const Fuels {

   Fuel: [
    { "id": 1, "type": "A" },
    { "id": 2, "type": "B" },
    { "id": 3, "type": "C" },
    { "id": 4, "type": "D" },
   ];
   }
  1. สร้างชื่อไฟล์ static.services.ts

__

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

@Injectable()
export class StaticService {

  constructor() { }

  getFuelData(): Fuels[] {
    return Fuels;
  }
 }`
  1. ตอนนี้คุณสามารถทำให้พร้อมใช้งานสำหรับทุกโมดูล

เพียงแค่นำเข้าในไฟล์ app.module.ts เช่นนี้และเปลี่ยนผู้ให้บริการ

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

providers: [StaticService]

ตอนนี้ใช้สิ่งนี้StaticServiceในโมดูลใดก็ได้

นั่นคือทั้งหมด


ทางออกที่ดีเนื่องจากคุณไม่ต้องคอมไพล์ใหม่ สภาพแวดล้อมก็เหมือนกับการเข้ารหัสในโค้ด น่ารังเกียจ. +1
Terrance00


0

เรามีปัญหานี้เมื่อหลายปีก่อนก่อนที่ฉันจะเข้าร่วมและได้วางโซลูชันที่ใช้พื้นที่จัดเก็บข้อมูลภายในสำหรับข้อมูลผู้ใช้และสภาพแวดล้อม Angular 1.0 วันที่แน่นอน ก่อนหน้านี้เราเคยสร้างไฟล์ js แบบไดนามิกที่รันไทม์ซึ่งจะวาง URL ของ api ที่สร้างไว้ในตัวแปรส่วนกลาง วันนี้เราขับเคลื่อน OOP มากขึ้นเล็กน้อยและไม่ได้ใช้พื้นที่เก็บข้อมูลในตัวเครื่องเพื่ออะไรเลย

ฉันสร้างโซลูชันที่ดีกว่าสำหรับทั้งการกำหนดสภาพแวดล้อมและการสร้าง URL ของ API

สิ่งนี้แตกต่างกันอย่างไร?

แอปจะไม่โหลดเว้นแต่จะโหลดไฟล์ config.json ใช้ฟังก์ชันของโรงงานเพื่อสร้าง SOC ในระดับที่สูงขึ้น ฉันสามารถห่อหุ้มสิ่งนี้เป็นบริการได้ แต่ฉันไม่เคยเห็นเหตุผลใด ๆ เมื่อความคล้ายคลึงกันเพียงอย่างเดียวระหว่างส่วนต่างๆของไฟล์คือสิ่งเหล่านี้มีอยู่ด้วยกันในไฟล์ การมีฟังก์ชั่นโรงงานทำให้ฉันสามารถส่งผ่านฟังก์ชันไปยังโมดูลได้โดยตรงหากสามารถรับฟังก์ชันได้ สุดท้ายนี้ฉันมีเวลาที่ง่ายขึ้นในการตั้งค่า InjectionTokens เมื่อมีฟังก์ชั่นจากโรงงานให้ใช้งานได้

ข้อเสีย?

คุณโชคไม่ดีที่ใช้การตั้งค่านี้ (และคำตอบอื่น ๆ ส่วนใหญ่) หากโมดูลที่คุณต้องการกำหนดค่าไม่อนุญาตให้ส่งฟังก์ชันจากโรงงานไปยัง forRoot () หรือ forChild () และไม่มีวิธีอื่นในการ กำหนดค่าแพ็คเกจโดยใช้ฟังก์ชั่นโรงงาน

คำแนะนำ

  1. การใช้ fetch เพื่อดึงไฟล์ json ฉันเก็บวัตถุในหน้าต่างและเพิ่มเหตุการณ์ที่กำหนดเอง - อย่าลืมติดตั้ง whatwg-fetch และเพิ่มลงใน polyfills.ts ของคุณเพื่อความเข้ากันได้ของ IE
  2. ให้ผู้ฟังเหตุการณ์ฟังเหตุการณ์ที่กำหนดเอง
  3. ผู้ฟังเหตุการณ์ได้รับเหตุการณ์ดึงวัตถุจากหน้าต่างเพื่อส่งผ่านไปยังสิ่งที่สังเกตได้และล้างสิ่งที่เก็บไว้ในหน้าต่าง
  4. Bootstrap เชิงมุม

- นี่คือจุดที่การแก้ปัญหาของฉันเริ่มแตกต่างไปจากเดิม -

  1. สร้างไฟล์ที่ส่งออกอินเทอร์เฟซที่มีโครงสร้างแสดงถึง config.json ของคุณซึ่งช่วยในเรื่องความสอดคล้องของประเภทและส่วนถัดไปของโค้ดต้องใช้ประเภทและอย่าระบุ{}หรือanyเมื่อคุณรู้ว่าคุณสามารถระบุสิ่งที่เป็นรูปธรรมมากขึ้นได้
  2. สร้าง BehaviorSubject ที่คุณจะส่งผ่านไฟล์ json ที่แยกวิเคราะห์ในขั้นตอนที่ 3
  3. ใช้ฟังก์ชันจากโรงงานเพื่ออ้างอิงส่วนต่างๆของการกำหนดค่าของคุณเพื่อรักษา SOC
  4. สร้าง InjectionTokens สำหรับผู้ให้บริการที่ต้องการผลการทำงานจากโรงงานของคุณ

- และ / หรือ -

  1. ส่งผ่านฟังก์ชั่นจากโรงงานไปยังโมดูลที่สามารถรับฟังก์ชันได้ทั้งในเมธอด forRoot () หรือ forChild ()

- main.ts

ฉันตรวจสอบหน้าต่าง ["สภาพแวดล้อม"] ไม่ได้รับการเติมข้อมูลก่อนที่จะสร้างตัวฟังเหตุการณ์เพื่อให้ความเป็นไปได้ของโซลูชันที่มีการเติมหน้าต่าง ["สภาพแวดล้อม"] ด้วยวิธีการอื่นก่อนที่โค้ดใน main.ts จะดำเนินการ

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

--- สภาพแวดล้อม resolvers.ts

ฉันกำหนดค่าให้กับ BehaviorSubject โดยใช้ window ["environment"] เพื่อความซ้ำซ้อน คุณสามารถคิดค้นโซลูชันที่กำหนดค่าของคุณไว้ล่วงหน้าแล้วและหน้าต่าง ["สภาพแวดล้อม"] จะถูกเติมไว้แล้วเมื่อมีการเรียกใช้โค้ดแอป Angular ของคุณรวมถึงโค้ดใน 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 - ถอดออกเพื่อให้เข้าใจง่ายขึ้น

ช่วงเวลาสนุก! NGXLogger เวอร์ชันเก่ากว่ากำหนดให้คุณส่งผ่านวัตถุไปยัง LoggerModule.forRoot () ในความเป็นจริง LoggerModule ยังคงทำ! NGXLogger กรุณาแสดง LoggerConfig ซึ่งคุณสามารถแทนที่เพื่อให้คุณใช้ฟังก์ชันจากโรงงานในการตั้งค่าได้

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

ภาคผนวก

ฉันจะแก้ปัญหาการสร้าง URL API ของฉันได้อย่างไร

ฉันต้องการที่จะเข้าใจว่าแต่ละ URL ทำอะไรผ่านความคิดเห็นและต้องการตรวจสอบการพิมพ์เนื่องจากเป็นจุดแข็งที่ยิ่งใหญ่ที่สุดของ TypeScript เมื่อเทียบกับจาวาสคริปต์ (IMO) ฉันยังต้องการสร้างประสบการณ์ให้กับนักพัฒนาคนอื่น ๆ เพื่อเพิ่มจุดสิ้นสุดใหม่และ apis ที่ราบรื่นที่สุด

ฉันสร้างคลาสที่อยู่ในสภาพแวดล้อม (dev, test, stage, prod, "" และอื่น ๆ ) และส่งผ่านค่านี้ไปยังชุดของคลาส [1-N] ซึ่งมีหน้าที่สร้าง url พื้นฐานสำหรับแต่ละคอลเล็กชัน API . ApiCollection แต่ละตัวมีหน้าที่สร้าง URL พื้นฐานสำหรับแต่ละคอลเล็กชันของ API อาจเป็น API ของเราเอง API ของผู้ขายหรือแม้แต่ลิงก์ภายนอก คลาสนั้นจะส่ง URL พื้นฐานที่สร้างขึ้นไปยังแต่ละ api ที่ตามมาซึ่งมีอยู่ อ่านโค้ดด้านล่างเพื่อดูตัวอย่างกระดูกเปลือย เมื่อตั้งค่าแล้วมันง่ายมากสำหรับนักพัฒนารายอื่นในการเพิ่มจุดสิ้นสุดอื่นในคลาส Api โดยไม่ต้องสัมผัสสิ่งอื่นใด

TLDR; หลักการ OOP พื้นฐานและผู้ขี้เกียจสำหรับการเพิ่มประสิทธิภาพหน่วยความจำ

@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/");
  }
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.