[javascript] App.settings-Angular 방식?

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 방식으로 하고 싶습니다 .



답변

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를 사용하여 구성 확인을 입력 할 수도 있습니다.


답변

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

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)의 애플리케이션 환경에 대해 자세히 알아보세요 .


답변

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을 구성합니다 . 런타임 구성을 사용하십시오.


답변

여기 내 솔루션이 있습니다. .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"
}


답변

가난한 사람의 구성 파일 :

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
}


답변

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를 삽입하고 배포중인 다른 배포 환경에 대한 다른 설정을 대체합니다.


답변

여기에 대한 두 가지 해결책이 있습니다.

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 다른 모듈 .

그게 다야.