App.settings-Angular 방식?


87

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 {


}

하지만 작동하지 않고 오류가 발생합니다.

질문:

Angular 방식으로 "app.settings"값을 어떻게 사용할 수 있습니까?

플 런커

NB 물론 Injectable 서비스를 생성하여 NgModule 공급자에 넣을 수 있습니다. 그러나 제가 말했듯 InjectionToken이 Angular 방식으로 하고 싶습니다 .


당신은 내 대답을 확인할 수 있습니다 여기에 현재 공식 문서에 기반을 둔
JavierFuentes

@javier 아니. 두 공급자가 동일한 이름을 제공하면 링크에 문제가 있으므로 이제 문제가 있습니다. opaquetoken Entring
Royi Namir

[OpaqueToken이 더 이상 사용되지 않음]을 알고 있습니다. ( angular.io/api/core/OpaqueToken ) 이 문서 각도 공급자에 이름 충돌을 방지하는 방법에 대해 이야기
JavierFuentes

Yaeh iknow하지만 여전히 연결된 기사가 잘못되었습니다.
Royi Namir

2
아래 링크는 각도 구성 스키마 devblogs.microsoft.com/premier-developer/…
M_Farahmand

답변:


56

InjectionTokens (아래 예제 참조)를 사용하여이 작업을 수행하는 방법을 알아 냈고 프로젝트가를 사용하여 빌드 된 경우 API 엔드 포인트와 같은 정적 Angular CLI환경 파일을 사용할 수 있지만 프로젝트의 요구 사항에 따라 대부분 환경 파일은 객체 리터럴 일 뿐이며 's를 사용 하는 주입 가능한 구성 은 환경 변수를 사용할 수 있으며 클래스이기 때문에 초기 http 요청 데이터, 하위 도메인과 같은 응용 프로그램의 다른 요소를 기반으로 구성하는 논리를 적용 할 수 있습니다. 등/environmentsapplication wide settingsInjectionToken

주입 토큰 예

/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로 어디서든 가져 와서 콘솔에 출력 할 수 있습니다. 나는 이것을 플 런커에서 설정하는 데 더 오래 걸리고 그 단계를 수행하는 데 더 오래 걸릴 것입니다.
mtpultz

오, 나는 당신이 이미 이것에 대한 플 런커를 가지고 있다고 생각했습니다 :-) 감사합니다.
Royi Namir

사람들을 위해 원하는 사람 : plnkr.co/edit/2YMZk5mhP1B4jTgA37B8?p=preview
Royi Namir

1
AppConfig 인터페이스 / 클래스를 내보낼 필요가 없다고 생각합니다. DI를 할 때 꼭 사용할 필요는 없습니다. 하나의 파일에서이 작업을 수행하려면 인터페이스 대신 클래스 여야했지만 중요하지 않습니다. 실제로 스타일 가이드는 인터페이스 대신 클래스를 사용하는 것이 좋습니다. 이는 코드가 적다는 것을 의미하고이를 사용하여 유형 검사를 할 수 있기 때문입니다. 제네릭을 통한 InjectionToken의 사용과 관련하여 포함하고 싶은 것입니다.
mtpultz 2017

1
Azure의 환경 변수와 JSON 변환 기능을 사용하여 API 끝점을 동적으로 주입하려고하는데이 대답은 환경 파일에서 apiEndpoint를 가져 오는 것 같습니다. 구성에서 어떻게 가져 와서 내보낼 수 있습니까?
Archibald

138

사용하는 경우 , 또 다른 옵션이 있습니다.

Angular CLI는 환경 파일을 제공합니다 src/environments(기본 파일 은 environment.ts(dev) 및 environment.prod.ts(production)).

모든 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 (Angular CLI 버전 6) 또는 공식 Angular 가이드 (버전 7)의 애플리케이션 환경에 대해 자세히 알아보세요 .


2
그 bundle.I가에 configuartion을 변경해야합니다으로도 변경 빌드를 이동하면서 됐었 작업 내 제공하지 코드의 생산에 이동 한 후
kamalav

43
이것은 일반적인 소프트웨어 개발에서 다소 반 패턴입니다. API URL은 구성 일뿐입니다. 다른 환경에 맞게 앱을 재구성하기 위해 다시 빌드 할 필요가 없습니다. 한 번 빌드하고 여러 번 배포해야합니다 (사전 프로덕션, 스테이징, 프로덕션 등).
Matt Tester

3
@MattTester 이것은 실제로 공식 Angular-CLI 이야기입니다. 이 질문에 대해 더 나은 답을 찾으면 언제든지 게시하십시오!
tilo

7
빌드 후 구성 할 수 있습니까?
NK

1
오 좋아요, 댓글을 잘못 읽었습니다. 나는 이것이 안티 패턴에 빌려 준다는 데 동의하고 동적 런타임 구성에 대한 이야기가 있다고 생각했습니다.
Jens Bodal

83

environment.*.tsAPI URL 구성에 파일 을 사용하는 것은 바람직하지 않습니다 . 이것은 "환경"이라는 단어를 언급하기 때문에 그렇게해야 할 것 같습니다.

이것을 사용하는 것은 실제로 컴파일 타임 구성 입니다. API URL을 변경하려면 다시 빌드해야합니다. 하고 싶지 않은 일입니다. 친절한 QA 부서에 문의하세요. :)

필요한 것은 런타임 구성입니다 . 즉, 앱이 시작될 때 구성을로드합니다.

다른 답변은 이것에 대해 다루고 있지만 차이점은 앱이 시작되는 즉시 구성을로드해야 하므로 필요할 때마다 일반 서비스에서 사용할 수 있다는 것입니다.

런타임 구성을 구현하려면 :

  1. /src/assets/폴더에 JSON 구성 파일 추가 (빌드시 복사 됨)
  2. AppConfigService구성을로드하고 배포하기 위한 만들기
  3. 다음을 사용하여 구성로드 APP_INITIALIZER

1. 구성 파일 추가 /src/assets

다른 폴더에 추가 할 수 있지만 CLI에 angular.json. 자산 폴더를 사용하여 시작하십시오.

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

2. 만들기 AppConfigService

구성 값이 필요할 때마다 삽입되는 서비스입니다.

@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구성이 완전히로드 된 상태에서를 안전하게 삽입 하려면 앱 시작시 구성을로드해야합니다. 중요한 것은 초기화 팩토리 함수가 a를 반환 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;
  }

}

충분히 강력하게 말할 수는 없지만 컴파일 타임 구성이 안티 패턴이므로 API URL을 구성합니다 . 런타임 구성을 사용하십시오.


4
로컬 파일 또는 다른 서비스, 컴파일 타임 구성을 API URL에 사용해서는 안됩니다. 당신의 앱이 제품으로 판매된다면 (구매자가 설치하는 것을) 컴파일하는 것을 원하지 않는다고 상상해보십시오. API URL이 변경되었습니다. 위험!!
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 줄 사이를 읽으면서 웹 배포를 사용하여 배포하고 있습니다. 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 파일에 외부에서 액세스를 허용하지 않는 서버를 구성 (또는 공용 액세스가없는 디렉토리에 저장)
알렉스 Pandrea

이것은 내가 가장 좋아하는 솔루션이지만 여전히 보안 위험에 대해 우려하고 있습니다.
Viqas

7
제발, 당신이 그것을 올바르게 할 수 있도록 도와 줄 수 있습니까? 각도 환경에서 기존보다 어떻게 더 위험합니까? environments.prod.tsafter 의 전체 내용은 어느 시점 ng build --prod에서 어떤 .js파일에 있을 것 입니다. 난독 처리 된 경우에도 데이터 environments.prod.ts는 일반 텍스트로 표시됩니다. 그리고 모든 .js 파일과 마찬가지로 최종 사용자 컴퓨터에서 사용할 수 있습니다.
igann

5
@ AlbertoL.Bonfiglio Angular 앱은 본질적으로 클라이언트 응용 프로그램이고 JavaScript는 데이터 및 구성을 전달하는 데 사용되므로 비밀 구성이 사용되지 않아야합니다. 모든 비밀 구성 정의는 사용자의 브라우저 또는 브라우저 도구가 액세스 할 수없는 API 계층 뒤에 있어야합니다. API의 기본 URI와 같은 값은 API에 사용자 로그인 (https를 통한 베어러 토큰)을 기반으로하는 자체 자격 증명과 보안이 있어야하기 때문에 일반인이 액세스 할 수 있습니다.
Tommy Elliott

4

가난한 사람의 구성 파일 :

body 태그의 첫 번째 라인으로 index.html에 추가하십시오.

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

assets / config.js 추가 :

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

config.ts 추가 :

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

export interface AppConfig {
    apiBaseUrl: string
}

진지하게, +1은 솔루션을 가장 기본적인 구성 요소로 끓여서 유형 일관성을 유지합니다.
Luminous

4

APP_INITIALIZER다른 서비스 공급자가 구성을 삽입해야하는 상황에서는 for 를 사용하는 것이 작동하지 않는다는 것을 발견했습니다 . APP_INITIALIZER실행 전에 인스턴스화 할 수 있습니다 .

루트 모듈을 부트 스트랩 fetch하기 platformBrowserDynamic()전에 config.json 파일을 읽고 매개 변수에 주입 토큰을 사용하여 제공하는 데 사용하는 다른 솔루션을 보았습니다 . 그러나 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. 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]
}

저는이 모든 것이 매우 일반적인 요구 사항에 대해 놀랍도록 어렵고 엉뚱한 일이라고 생각합니다. 가까운 장래에 비동기 공급자 팩토리에 대한 지원과 같은 더 나은 방법이 있기를 바랍니다.

완전성을위한 나머지 코드는 ...

에서 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
    }
}

구성은 다음에 저장됩니다 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

제가 가입하기 몇 년 전에이 문제가 발생했으며 사용자 및 환경 정보에 로컬 스토리지를 사용하는 솔루션을 마련했습니다. 정확한 각도는 1.0 일입니다. 이전에는 런타임에 js 파일을 동적으로 생성 한 다음 생성 된 api URL을 전역 변수에 배치했습니다. 요즘 우리는 OOP를 조금 더 많이 사용하며 로컬 스토리지를 사용하지 않습니다.

환경 결정과 API URL 생성을위한 더 나은 솔루션을 만들었습니다.

이것이 어떻게 다른가요?

config.json 파일이로드되지 않으면 앱이로드되지 않습니다. 공장 기능을 사용하여 더 높은 수준의 SOC를 생성합니다. 나는 이것을 서비스로 캡슐화 할 수 있었지만, 파일의 다른 섹션 사이의 유일한 유사점이 파일에 함께 존재한다는 것 뿐인 이유를 전혀 보지 못했습니다. 팩토리 함수가 있으면 함수를받을 수있는 경우 함수를 모듈에 직접 전달할 수 있습니다. 마지막으로, 공장 기능을 사용할 수있을 때 InjectionTokens를 설정하는 것이 더 쉽습니다.

단점?

구성하려는 모듈이 공장 함수를 forRoot () 또는 forChild ()로 전달하는 것을 허용하지 않고 다른 방법이 없다면이 설정 (및 대부분의 다른 답변)을 사용하면 운이 좋지 않습니다. 공장 기능을 사용하여 패키지를 구성하십시오.

명령

  1. fetch를 사용하여 json 파일을 검색하고 개체를 창에 저장하고 사용자 지정 이벤트를 발생시킵니다. -IE 호환성을 위해 whatwg-fetch를 설치하고 polyfills.ts에 추가하는 것을 잊지 마십시오.
  2. 사용자 정의 이벤트를 수신하는 이벤트 리스너가 있습니다.
  3. 이벤트 리스너는 이벤트를 수신하고 창에서 객체를 검색하여 관찰 가능 항목에 전달하고 창에 저장된 내용을 지 웁니다.
  4. 부트 스트랩 각도

-이것이 내 솔루션이 실제로 달라지기 시작하는 곳입니다-

  1. 구조 당신의 config.json를 나타내는 인터페이스를 내보내기 파일을 생성 - 정말 유형의 일관성에 도움이 코드의 다음 섹션은 유형을 필요로하고, 지정하지 않은 {}또는 any당신이 알고있을 때 당신이 뭔가 더 구체적인를 지정할 수 있습니다
  2. 3 단계에서 구문 분석 된 json 파일을 전달할 BehaviorSubject를 만듭니다.
  3. 공장 기능을 사용하여 구성의 다른 섹션을 참조하여 SOC를 유지합니다.
  4. 공장 함수의 결과를 필요로하는 공급자를위한 InjectionToken 생성

-및 / 또는-

  1. forRoot () 또는 forChild () 메서드에서 함수를받을 수있는 모듈에 팩토리 함수를 직접 전달합니다.

-main.ts

main.ts의 코드가 실행되기 전에 window [ "environment"]가 다른 수단으로 채워지는 솔루션의 가능성을 허용하기 위해 이벤트 리스너를 만들기 전에 window [ "environment"]가 채워지지 않았는지 확인합니다.

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

중복성을 위해 window [ "environment"]를 사용하여 BehaviorSubject에 값을 할당합니다. 구성이 이미 미리로드되고 main.ts의 코드를 포함하여 Angular의 앱 코드가 실행될 때 window [ "environment"]가 이미 채워지는 솔루션을 고안 할 수 있습니다.

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

추가

내 API URL 생성을 어떻게 해결 했습니까?

나는 각 URL이 주석을 통해 무엇을했는지 이해할 수 있기를 원했고 자바 스크립트 (IMO)에 비해 TypeScript의 가장 큰 장점이기 때문에 typechecking을 원했습니다. 또한 다른 개발자가 새로운 엔드 포인트를 추가 할 수있는 환경을 만들고 가능한 한 원활하게 API를 만들고 싶었습니다.

환경 (dev, test, stage, prod, ""등)을받는 클래스를 만들고이 값을 각 API 컬렉션에 대한 기본 URL을 만드는 일련의 클래스 [1-N]에 전달했습니다. . 각 ApiCollection은 각 API 컬렉션에 대한 기본 URL을 생성합니다. 자체 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.