App.settings - cách Angular?


87

Tôi muốn thêm một App Settingsphần vào Ứng dụng của mình nơi Phần đó sẽ chứa một số khuyết điểm và giá trị được xác định trước.

Tôi đã đọc câu trả lời này sử dụng OpaqueTokenNhưng nó không được dùng trong Angular. Bài viết này giải thích sự khác biệt nhưng nó không cung cấp một ví dụ đầy đủ và những nỗ lực của tôi đã không thành công.

Đây là những gì tôi đã thử (tôi không biết có đúng cách không):

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

Và đây là thành phần mà tôi muốn sử dụng những khuyết điểm đó:

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


}

Nhưng nó không hoạt động và tôi nhận được lỗi.

Câu hỏi:

Làm cách nào để sử dụng các giá trị "app.settings" theo cách Angular?

plunker

NB Chắc chắn tôi có thể tạo dịch vụ Injectable và đưa nó vào nhà cung cấp NgModule, Nhưng như tôi đã nói, tôi muốn làm điều đó InjectionToken, theo cách Angular.


Bạn có thể kiểm tra câu trả lời của tôi ở đây có trụ sở tại tài liệu chính thức hiện nay
JavierFuentes

@javier không. Liên kết của bạn có vấn đề nếu hai nhà cung cấp cung cấp cùng một tên nên hiện tại bạn gặp sự cố. Entring opaquetoken
Royi Namir

bạn biết đấy [OpaqueToken không được dùng nữa]. ( Angular.io/api/core/OpaqueToken ) Điều này nói về làm thế nào để ngăn chặn xung đột tên trong các nhà cung cấp góc
JavierFuentes

Yaeh tôi biết nhưng bài báo được liên kết vẫn sai.
Royi Namir

2
có thể liên kết bên dưới có thể hữu ích cho mọi người thích sử dụng kiến ​​trúc mới của lược đồ cấu hình góc devblogs.microsoft.com/premier-developer/…
M_Farahmand

Câu trả lời:


56

Tôi đã tìm ra cách thực hiện điều này với InjectionTokens (xem ví dụ bên dưới) và nếu dự án của bạn được xây dựng bằng cách Angular CLIsử dụng tệp môi trường, bạn có thể sử dụng tệp môi trường được tìm thấy trong /environmentstĩnh application wide settingsnhư điểm cuối API, nhưng tùy thuộc vào yêu cầu của dự án, bạn rất có thể sẽ kết thúc sử dụng cả hai vì tệp môi trường chỉ là các ký tự đối tượng, trong khi cấu hình InjectionTokencó thể tiêm bằng cách sử dụng có thể sử dụng các biến môi trường và vì đó là một lớp có thể áp dụng logic để định cấu hình nó dựa trên các yếu tố khác trong ứng dụng, chẳng hạn như dữ liệu yêu cầu http ban đầu, tên miền phụ , Vân vân.

Ví dụ về thẻ tiêm

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

Bây giờ bạn có thể DI nó thành bất kỳ thành phần, dịch vụ nào, v.v.:

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

  // ...
}

Sau đó, bạn cũng có thể nhập kiểm tra cấu hình bằng AppConfig đã xuất.


Không, nhưng bạn có thể sao chép và dán phần đầu tiên vào một tệp theo đúng nghĩa đen, nhập nó vào tệp app.module.ts của bạn và DI nó ở bất cứ đâu và xuất nó ra bảng điều khiển. Tôi sẽ mất nhiều thời gian hơn để thiết lập điều này trong một plunker sau đó nó sẽ thực hiện các bước đó.
mtpultz

Ồ, tôi nghĩ Bạn đã có một plunker cho việc này :-) Cảm ơn bạn.
Royi Namir


1
Tôi không tin rằng bạn cần xuất giao diện / lớp AppConfig. Bạn chắc chắn không cần sử dụng nó khi thực hiện DI. Để làm cho nó hoạt động trong một tệp, nó phải là một lớp thay vì một giao diện, nhưng điều đó không quan trọng. Trên thực tế, hướng dẫn kiểu đề xuất sử dụng các lớp thay vì giao diện vì nó có nghĩa là ít mã hơn và bạn vẫn có thể gõ kiểm tra bằng cách sử dụng chúng. Liên quan đến việc sử dụng nó bởi InjectionToken thông qua generic, đó là thứ bạn sẽ muốn đưa vào.
mtpultz

1
Tôi đang cố gắng chèn động điểm cuối API bằng cách sử dụng các biến môi trường của Azure và các tính năng chuyển đổi JSON, nhưng có vẻ như câu trả lời này chỉ nhận được apiEndpoint từ tệp môi trường. Bạn sẽ lấy nó từ cấu hình và xuất nó như thế nào?
Archibald

138

Nếu bạn đang sử dụng , vẫn còn một tùy chọn khác:

Angular CLI cung cấp các tệp môi trường trong src/environments(mặc định là environment.ts(dev) và environment.prod.ts(production)).

Lưu ý rằng bạn cần cung cấp các tham số cấu hình trong tất cả environment.*các tệp, ví dụ:

môi trường.ts :

export const environment = {
  production: false,
  apiEndpoint: 'http://localhost:8000/api/v1'
};

môi trường.prod.ts :

export const environment = {
  production: true,
  apiEndpoint: '__your_production_server__'
};

và sử dụng chúng trong dịch vụ của bạn (tệp môi trường chính xác được chọn tự động):

api.service.ts

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

@Injectable()
export class ApiService {     

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

// ...
}

Đọc thêm về môi trường ứng dụng trên Github (Angular CLI phiên bản 6) hoặc trong hướng dẫn Angular chính thức (phiên bản 7) .


2
nó fine.But làm việc trong khi di chuyển xây dựng nó cũng được thay đổi như bundle.I nên thay đổi configuartion trong tôi phục vụ không có trong mã sau chuyển sang sản xuất
kamalav

42
Đây là một phần nào đó không theo khuôn mẫu trong phát triển phần mềm thông thường; url API chỉ là cấu hình. Không cần phải xây dựng lại để định cấu hình lại một ứng dụng cho một môi trường khác. Nó nên được xây dựng một lần, triển khai nhiều lần (pre-prod, staging, prod, v.v.).
Matt Tester

3
@MattTester Đây thực sự là một câu chuyện Angular-CLI chính thức. Nếu bạn có câu trả lời tốt hơn cho câu hỏi này: hãy đăng nó lên!
tilo

7
là nó có thể cấu hình sau khi ng xây dựng?
NK

1
Ồ được rồi, tôi đã đọc nhầm các bình luận. Tôi đồng ý rằng điều này có lợi cho một mô hình chống, tôi nghĩ rằng có một câu chuyện cho các cấu hình thời gian chạy động.
Jens Bodal

83

Bạn không nên sử dụng các environment.*.tstệp cho cấu hình URL API của mình. Có vẻ như bạn nên làm vì điều này đề cập đến từ "môi trường".

Sử dụng điều này thực sự là cấu hình thời gian biên dịch . Nếu bạn muốn thay đổi URL API, bạn sẽ cần phải xây dựng lại. Đó là điều bạn không muốn phải làm ... chỉ cần hỏi bộ phận QA thân thiện của bạn :)

Những gì bạn cần là cấu hình thời gian chạy , tức là ứng dụng tải cấu hình của nó khi khởi động.

Một số câu trả lời khác đề cập đến vấn đề này, nhưng sự khác biệt là cấu hình cần được tải ngay khi ứng dụng khởi động , để dịch vụ thông thường có thể sử dụng nó bất cứ khi nào nó cần.

Để triển khai cấu hình thời gian chạy:

  1. Thêm tệp cấu hình JSON vào /src/assets/thư mục (để được sao chép trên bản dựng)
  2. Tạo một AppConfigServiceđể tải và phân phối cấu hình
  3. Tải cấu hình bằng cách sử dụng APP_INITIALIZER

1. Thêm tệp cấu hình vào /src/assets

Bạn có thể thêm nó vào một thư mục khác, nhưng bạn cần nói với CLI rằng nó là một tài sản trong angular.json. Bắt đầu sử dụng thư mục nội dung:

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

2. Tạo AppConfigService

Đây là dịch vụ sẽ được đưa vào bất cứ khi nào bạn cần giá trị cấu hình:

@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. Tải cấu hình bằng cách sử dụng APP_INITIALIZER

Để cho phép AppConfigServicenhập một cách an toàn, với cấu hình được tải đầy đủ, chúng tôi cần tải cấu hình vào thời điểm khởi động ứng dụng. Quan trọng là, hàm nhà máy khởi tạo cần trả về một Promiseđể Angular biết đợi cho đến khi nó hoàn tất giải quyết trước khi kết thúc khởi động:

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

Bây giờ bạn có thể đưa nó vào bất cứ nơi nào bạn cần và tất cả các cấu hình sẽ sẵn sàng để đọc:

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

}

Tôi không thể nói đủ mạnh, việc định cấu hình các url API của bạn dưới dạng cấu hình thời gian biên dịch là một cách chống đối . Sử dụng cấu hình thời gian chạy.


4
Không nên sử dụng tệp cục bộ hoặc dịch vụ khác, cấu hình thời gian biên dịch cho url API. Hãy tưởng tượng nếu ứng dụng của bạn được bán dưới dạng sản phẩm (người mua cài đặt), bạn không muốn họ biên dịch ứng dụng đó, v.v. Dù bằng cách nào, bạn cũng không muốn biên dịch lại thứ gì đó đã được xây dựng cách đây 2 năm, chỉ vì url API đã thay đổi. Rủi ro!!
Matt Tester

1
@Bloodhound Bạn có thể có nhiều hơn một cái APP_INITIALIZERnhưng tôi không nghĩ rằng bạn có thể dễ dàng khiến chúng phụ thuộc vào nhau. Có vẻ như bạn có một câu hỏi hay để hỏi, vì vậy có thể liên kết đến nó ở đây?
Matt Tester

2
@MattTester - Nếu góc bao giờ thực hiện tính năng này nó sẽ giải quyết vấn đề của chúng tôi: github.com/angular/angular/issues/23279#issuecomment-528417026
Mike Becatti

2
@CrhistianRamirez Theo quan điểm của ứng dụng: cấu hình không được biết cho đến thời gian chạy và tệp tĩnh nằm ngoài bản dựng và có thể được đặt theo nhiều cách tại thời điểm triển khai. Tệp tĩnh tốt cho cấu hình không nhạy cảm. API hoặc một số điểm cuối được bảo vệ khác có thể thực hiện được với cùng một kỹ thuật, nhưng làm thế nào để xác thực để làm cho nó được bảo vệ là thách thức tiếp theo của bạn.
Matt Tester

1
@DaleK Đang đọc giữa các dòng, bạn đang triển khai bằng Web Deploy. Nếu bạn đang sử dụng đường dẫn triển khai, như Azure DevOps, thì bạn có thể đặt tệp cấu hình chính xác ở bước tiếp theo. Việc cài đặt cấu hình là trách nhiệm của quy trình / đường dẫn triển khai, quá trình này có thể ghi đè các giá trị trong tệp cấu hình mặc định. Hy vọng rằng làm rõ.
Matt Tester

8

Đây là giải pháp của tôi, tải từ .json để cho phép thay đổi mà không cần xây dựng lại

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

và config.json

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

1
Vấn đề với cách tiếp cận này là config.json đã mở cửa cho cả thế giới. Làm thế nào bạn ngăn chặn ai đó nhập www.mywebsite.com/assetts/config.json?
Alberto L. Bonfiglio

1
@ AlbertoL.Bonfiglio bạn định cấu hình máy chủ không cho phép truy cập từ bên ngoài vào tệp config.json (hoặc đặt nó trong một thư mục không có quyền truy cập công khai)
Alex Pandrea

Đây cũng là giải pháp yêu thích của tôi, nhưng vẫn lo ngại về các rủi ro bảo mật.
Viqas

7
Làm ơn, bạn có thể giúp tôi làm đúng không? Làm thế nào nó rủi ro hơn so với truyền thống cho môi trường góc cạnh? Nội dung đầy đủ của environments.prod.tsafter ng build --prodsẽ ở một số .jstệp tại một số thời điểm. Ngay cả khi bị xáo trộn, dữ liệu từ environments.prod.tssẽ ở dạng văn bản rõ ràng. Và như tất cả các tệp .js, nó sẽ có sẵn trên máy người dùng cuối.
igann

5
@ AlbertoL.Bonfiglio Bởi vì ứng dụng Angular về bản chất là một ứng dụng khách và JavaScript sẽ được sử dụng để truyền dữ liệu và cấu hình, nên không có cấu hình bí mật nào được sử dụng trong đó; tất cả các định nghĩa cấu hình bí mật phải nằm sau một lớp API nơi trình duyệt của người dùng hoặc các công cụ trình duyệt không thể truy cập nó. Các giá trị như URI cơ sở của một API có thể được công chúng truy cập vì API phải có thông tin đăng nhập và bảo mật riêng dựa trên việc người dùng đăng nhập (mã thông báo mang tên https).
Tommy Elliott

4

Tệp cấu hình của người đàn ông nghèo:

Thêm vào index.html của bạn dưới dạng líne đầu tiên trong thẻ body:

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

Thêm nội dung / config.js:

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

Thêm config.ts:

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

export interface AppConfig {
    apiBaseUrl: string
}

Nghiêm túc mà nói, +1 để đun sôi dung dịch thành các thành phần cơ bản nhất và vẫn duy trì tính nhất quán của loại.
Sáng

4

Tôi thấy rằng việc sử dụng một APP_INITIALIZERcho điều này không hoạt động trong các tình huống mà các nhà cung cấp dịch vụ khác yêu cầu cấu hình được đưa vào. Chúng có thể được khởi tạo trước khi APP_INITIALIZERchạy.

Tôi đã thấy các giải pháp khác sử dụng fetchđể đọc tệp config.json và cung cấp nó bằng cách sử dụng mã thông báo tiêm trong một tham số platformBrowserDynamic()trước khi khởi động mô-đun gốc. Nhưng fetchkhông được hỗ trợ trong tất cả các trình duyệt và cụ thể là các trình duyệt WebView cho các thiết bị di động mà tôi nhắm mục tiêu.

Sau đây là một giải pháp phù hợp với tôi cho cả PWA và thiết bị di động (WebView). Lưu ý: Tôi chỉ mới thử nghiệm trên Android cho đến nay; làm việc tại nhà có nghĩa là tôi không có quyền truy cập vào máy Mac để xây dựng.

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

Mã này:

  1. bắt đầu một yêu cầu không đồng bộ cho config.jsontệp.
  2. Khi yêu cầu hoàn tất, phân tích cú pháp JSON thành một đối tượng Javascript
  3. cung cấp giá trị bằng cách sử dụng APP_CONFIGmã thông báo tiêm, trước khi khởi động.
  4. Và cuối cùng khởi động mô-đun gốc.

APP_CONFIGsau đó có thể được tiêm vào bất kỳ nhà cung cấp bổ sung nào app-module.tsvà nó sẽ được xác định. Ví dụ: tôi có thể khởi tạo FIREBASE_OPTIONSmã thông báo tiêm từ @angular/firenhư sau:

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

Tôi thấy toàn bộ điều này là một điều đáng ngạc nhiên (và khó thực hiện) đối với một yêu cầu rất phổ biến. Hy vọng trong thời gian sắp tới sẽ có cách tốt hơn như hỗ trợ các nhà máy cung cấp async.

Phần còn lại của mã cho sự hoàn chỉnh ...

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

và trong app/lib/config/config.tstôi xác định giao diện cho tệp cấu hình JSON của mình:

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

Cấu hình được lưu trữ trong assets/config/config.json:

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

Lưu ý: Tôi sử dụng tác vụ Azure DevOps để chèn Build.BuildNumber và thay thế các cài đặt khác cho các môi trường triển khai khác khi nó đang được triển khai.


2

Đây là hai giải pháp của tôi cho việc này

1. Lưu trữ trong tệp json

Chỉ cần tạo một tệp json và nhận thành phần của bạn theo $http.get()phương thức. Nếu tôi cần điều này rất thấp thì nó tốt và nhanh chóng.

2. Lưu trữ bằng cách sử dụng các dịch vụ dữ liệu

Nếu bạn muốn lưu trữ và sử dụng trong tất cả các thành phần hoặc có mức sử dụng lớn thì tốt hơn nên sử dụng dịch vụ dữ liệu. Như thế này :

  1. Chỉ cần tạo thư mục tĩnh bên trong src/appthư mục.

  2. Tạo một tệp có tên là fuels.tsvào thư mục tĩnh. Bạn cũng có thể lưu trữ các tệp tĩnh khác tại đây. Hãy xác định dữ liệu của bạn như thế này. Giả sử bạn có dữ liệu về nhiên liệu.

__

export const Fuels {

   Fuel: [
    { "id": 1, "type": "A" },
    { "id": 2, "type": "B" },
    { "id": 3, "type": "C" },
    { "id": 4, "type": "D" },
   ];
   }
  1. Tạo tên tệp static.services.ts

__

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

@Injectable()
export class StaticService {

  constructor() { }

  getFuelData(): Fuels[] {
    return Fuels;
  }
 }`
  1. Bây giờ bạn có thể cung cấp tính năng này cho mọi mô-đun

chỉ cần nhập vào tệp app.module.ts như thế này và thay đổi nhà cung cấp

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

providers: [StaticService]

Bây giờ sử dụng nó như StaticServicetrong bất kỳ mô-đun nào.

Đó là tất cả.


Giải pháp tốt vì bạn không phải biên dịch lại. Môi trường giống như mã hóa cứng nó trong mã. Bẩn thỉu. +1
Terrance00


0

Chúng tôi đã gặp sự cố này cách đây nhiều năm trước khi tôi tham gia và đưa ra giải pháp sử dụng bộ nhớ cục bộ cho thông tin người dùng và môi trường. Angular chính xác là 1,0 ngày. Trước đây, chúng tôi đã tạo động một tệp js trong thời gian chạy, sau đó sẽ đặt các url api đã tạo vào một biến toàn cục. Ngày nay, chúng ta đang sử dụng OOP nhiều hơn một chút và không sử dụng bộ nhớ cục bộ cho bất kỳ thứ gì.

Tôi đã tạo một giải pháp tốt hơn cho cả việc xác định môi trường và tạo url api.

Điều này khác nhau như thế nào?

Ứng dụng sẽ không tải trừ khi tệp config.json được tải. Nó sử dụng các chức năng của nhà máy để tạo ra mức độ SOC cao hơn. Tôi có thể đóng gói điều này vào một dịch vụ, nhưng tôi chưa bao giờ thấy lý do gì khi điểm giống nhau duy nhất giữa các phần khác nhau của tệp là chúng tồn tại cùng nhau trong tệp. Có một chức năng gốc cho phép tôi chuyển trực tiếp chức năng vào một mô-đun nếu nó có khả năng chấp nhận một chức năng. Cuối cùng, tôi có thời gian dễ dàng hơn khi thiết lập InjectionTokens khi các chức năng của nhà máy có sẵn để sử dụng.

Nhược điểm?

Bạn không gặp may khi sử dụng thiết lập này (và hầu hết các câu trả lời khác) nếu mô-đun bạn muốn định cấu hình không cho phép chuyển một hàm gốc vào forRoot () hoặc forChild () và không có cách nào khác để cấu hình gói bằng cách sử dụng một chức năng gốc.

Hướng dẫn

  1. Sử dụng tìm nạp để truy xuất tệp json, tôi lưu trữ đối tượng trong cửa sổ và đưa ra một sự kiện tùy chỉnh. - nhớ cài đặt whatwg-fetch và thêm nó vào polyfills.ts của bạn để tương thích với IE
  2. Có người nghe sự kiện lắng nghe sự kiện tùy chỉnh.
  3. Trình nghe sự kiện nhận sự kiện, truy xuất đối tượng từ cửa sổ để chuyển cho một đối tượng có thể quan sát được và xóa những gì đã được lưu trữ trong cửa sổ.
  4. Bootstrap Angular

- Đây là lúc giải pháp của tôi bắt đầu thực sự khác biệt -

  1. Tạo tệp xuất một giao diện có cấu trúc đại diện cho config.json của bạn - nó thực sự giúp ích cho sự nhất quán về kiểu và phần mã tiếp theo yêu cầu một kiểu và không chỉ định {}hoặc anykhi bạn biết bạn có thể chỉ định một cái gì đó cụ thể hơn
  2. Tạo BehaviorSubject mà bạn sẽ chuyển tệp json đã phân tích cú pháp vào trong bước 3.
  3. Sử dụng các chức năng của nhà máy để tham chiếu các phần khác nhau trong cấu hình của bạn để duy trì SOC
  4. Tạo InjectionTokens cho các nhà cung cấp cần kết quả của các chức năng xuất xưởng của bạn

- và / hoặc -

  1. Chuyển trực tiếp các hàm của nhà máy vào các mô-đun có khả năng chấp nhận một hàm trong các phương thức forRoot () hoặc forChild () của nó.

- main.ts

Tôi kiểm tra window ["environment"] không được điền trước khi tạo trình xử lý sự kiện để cho phép khả năng giải pháp trong đó window ["environment"] được điền bằng một số phương tiện khác trước khi mã trong main.ts thực thi.

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

--- môi trường phân giải.ts

Tôi chỉ định một giá trị cho BehaviorSubject bằng cách sử dụng window ["environment"] để dự phòng. Bạn có thể nghĩ ra một giải pháp trong đó cấu hình của bạn đã được tải trước và cửa sổ ["environment"] đã được điền vào thời điểm bất kỳ mã ứng dụng Angular nào của bạn được chạy, bao gồm cả mã trong 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 - Đã lược bớt để dễ hiểu hơn

Sự thật thú vị! Các phiên bản cũ hơn của NGXLogger yêu cầu bạn chuyển một đối tượng vào LoggerModule.forRoot (). Trên thực tế, LoggerModule vẫn hoạt động! NGXLogger vui lòng tiết lộ LoggerConfig mà bạn có thể ghi đè cho phép bạn sử dụng một chức năng gốc để thiết lập.

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

Phụ lục

Tôi đã giải quyết việc tạo url API của mình như thế nào?

Tôi muốn có thể hiểu từng url đã làm gì thông qua một nhận xét và muốn đánh máy vì đó là điểm mạnh nhất của TypeScript so với javascript (IMO). Tôi cũng muốn tạo trải nghiệm cho các nhà phát triển khác để thêm các điểm cuối mới và các apis càng liền mạch càng tốt.

Tôi đã tạo một lớp sử dụng môi trường (dev, test, stage, prod, "" và v.v.) và chuyển giá trị này cho một loạt các lớp [1-N] có công việc là tạo url cơ sở cho mỗi bộ sưu tập API . Mỗi ApiCollection chịu trách nhiệm tạo url cơ sở cho mỗi bộ sưu tập API. Có thể là API của riêng chúng tôi, API của nhà cung cấp hoặc thậm chí là liên kết bên ngoài. Lớp đó sẽ chuyển url cơ sở đã tạo vào mỗi api tiếp theo mà nó chứa. Đọc đoạn mã dưới đây để xem ví dụ về xương trần. Sau khi thiết lập, rất đơn giản để một nhà phát triển khác thêm điểm cuối khác vào lớp Api mà không cần phải chạm vào bất kỳ thứ gì khác.

TLDR; các nguyên tắc cơ bản của OOP và lười biếng để tối ưu hóa bộ nhớ

@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/");
  }
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.