تكوين التطبيق على الزاوي. أفضل الممارسات

كيفية إدارة ملفات تهيئة البيئة والأهداف


عندما تقوم بإنشاء تطبيق زاوي باستخدام أدوات Angular CLI أو Nrwl Nx ، يكون لديك دائمًا مجلد به ملفات تكوين البيئة:


<APP_FOLDER>/src/environments/ └──environment.ts └──environment.prod.ts 

يمكنك إعادة تسمية environment.prod.ts إلى environment.production.ts على سبيل المثال ، يمكنك أيضًا إنشاء ملفات تكوين إضافية مثل environment.qa.ts أو environment.staging.ts .


 <APP_FOLDER>/src/environments/ └──environment.ts └──environment.prod.ts └──environment.qa.ts └──environment.staging.ts 

يتم استخدام ملف environment.ts بشكل افتراضي. لاستخدام الملفات المتبقية ، يجب عليك فتح angular.json وتكوين قسم fileReplacements في تكوين البناء وإضافة كتل إلى تكوينات serv و e2e .


 { "architect":{ "build":{ "configurations":{ "production":{ "fileReplacements":[ { "replace":"<APP_FOLDER>/src/environments/environment.ts", "with":"<APP_FOLDER>/src/environments/environment.production.ts" } ] }, "staging":{ "fileReplacements":[ { "replace":"<APP_FOLDER>/src/environments/environment.ts", "with":"<APP_FOLDER>/src/environments/environment.staging.ts" } ] } } }, "serve":{ "configurations":{ "production":{ "browserTarget":"app-name:build:production" }, "staging":{ "browserTarget":"app-name:build:staging" } } }, "e2e":{ "configurations":{ "production":{ "browserTarget":"app-name:serve:production" }, "staging":{ "browserTarget":"app-name:serve:staging" } } } } } 

لإنشاء تطبيق أو تشغيله مع بيئة محددة ، استخدم الأوامر:


 ng build --configuration=staging ng start --configuration=staging ng e2e --configuration=staging  ng build --prod     ng build --configuration=production 

إنشاء واجهة لملفات البيئة


 // environment-interface.ts export interface EnvironmentInterface { production: boolean; apiUrl: string; } // environment.ts export const environment: EnvironmentInterface = { production: false, apiUrl: 'https://api.url', }; 

لا تستخدم ملفات البيئة مباشرة ، إلا من خلال DI


استخدام المتغيرات العالمية والواردات المباشرة ينتهك نهج OOP ويعقد إمكانية اختبار الفصول الدراسية. لذلك ، من الأفضل إنشاء خدمة يمكن حقنها في المكونات والخدمات الأخرى. فيما يلي مثال على مثل هذه الخدمة مع القدرة على تحديد قيمة افتراضية.


 export const ENVIRONMENT = new InjectionToken<{ [key: string]: any }>('environment'); @Injectable({ providedIn: 'root', }) export class EnvironmentService { private readonly environment: any; // We need @Optional to be able start app without providing environment file constructor(@Optional() @Inject(ENVIRONMENT) environment: any) { this.environment = environment !== null ? environment : {}; } getValue(key: string, defaultValue?: any): any { return this.environment[key] || defaultValue; } } @NgModule({ imports: [ BrowserModule, HttpClientModule, AppRoutingModule, ], declarations: [ AppComponent, ], // We declare environment as provider to be able to easy test our service providers: [{ provide: ENVIRONMENT, useValue: environment }], bootstrap: [AppComponent], }) export class AppModule { } 

افصل بين التكوين البيئي ومنطق العمل


يتضمن تكوين البيئة فقط الخصائص المتعلقة بالبيئة ، على سبيل المثال apiUrl . من الناحية المثالية ، يجب أن يتكون تكوين البيئة من خاصيتين:


 export const environment = { production: true, apiUrl: 'https://api.url', }; 

أيضًا في هذا التكوين ، يمكنك إضافة خاصية لتمكين debugMode: تصحيح الوضع الصحيح أو يمكنك إضافة اسم الخادم حيث تعمل بيئة التطبيق name : 'QA' قيد التشغيل ، ولكن لا تنسَ أن هذه ممارسة سيئة للغاية إذا كان رمزك يعرف أي شيء عن الخادم الذي يعمل عليه .


لا تقم أبدًا بتخزين أي معلومات أو كلمات مرور حساسة في تهيئة بيئة.


يجب أن يتم تخزين إعدادات التكوين الأخرى مثل maxItemsOnPage أو galleryAnimationSpeed في مكان آخر ، وينصح باستخدام config.service.ts ، والذي يمكنه استلام الإعدادات من بعض نقاط النهاية أو ببساطة تحميل config.json من مجلد الأصول.


1. نهج غير متزامن (استخدام عندما قد يتغير التكوين في وقت التشغيل)


 // assets/config.json { "galleryAnimationSpeed": 5000 } // configuration.service.ts // ------------------------------------------------------ @Injectable({ providedIn: 'root', }) export class ConfigurationService { private configurationSubject = new ReplaySubject<any>(1); constructor(private httpClient: HttpClient) { this.load(); } // method can be used to refresh configuration load(): void { this.httpClient.get('/assets/config.json') .pipe( catchError(() => of(null)), filter(Boolean), ) .subscribe((configuration: any) => this.configurationSubject.next(configuration)); } getValue(key: string, defaultValue?: any): Observable<any> { return this.configurationSubject .pipe( map((configuration: any) => configuration[key] || defaultValue), ); } } // app.component.ts // ------------------------------------------------------ @Component({ selector: 'app-root', changeDetection: ChangeDetectionStrategy.OnPush, templateUrl: './app.component.html', styleUrls: ['./app.component.scss'] }) export class AppComponent { galleryAnimationSpeed$: Observable<number>; constructor(private configurationService: ConfigurationService) { this.galleryAnimationSpeed$ = this.configurationService.getValue('galleryAnimationSpeed', 3000); interval(10000).subscribe(() => this.configurationService.load()); } } 

2. النهج المتزامن (استخدم عندما لا يتغير التكوين أبدًا)


 // assets/config.json { "galleryAnimationSpeed": 5000 } // configuration.service.ts // ------------------------------------------------------ @Injectable({ providedIn: 'root', }) export class ConfigurationService { private configuration = {}; constructor(private httpClient: HttpClient) { } load(): Observable<void> { return this.httpClient.get('/assets/config.json') .pipe( tap((configuration: any) => this.configuration = configuration), mapTo(undefined), ); } getValue(key: string, defaultValue?: any): any { return this.configuration[key] || defaultValue; } } // app.module.ts // ------------------------------------------------------ export function initApp(configurationService: ConfigurationService) { return () => configurationService.load().toPromise(); } @NgModule({ imports: [ BrowserModule, HttpClientModule, AppRoutingModule, ], declarations: [ AppComponent, ], providers: [ { provide: APP_INITIALIZER, useFactory: initApp, multi: true, deps: [ConfigurationService] } ], bootstrap: [AppComponent], }) export class AppModule { } // app.component.ts // ------------------------------------------------------ @Component({ selector: 'app-root', changeDetection: ChangeDetectionStrategy.OnPush, templateUrl: './app.component.html', styleUrls: ['./app.component.scss'] }) export class AppComponent { galleryAnimationSpeed: number; constructor(private configurationService: ConfigurationService) { this.galleryAnimationSpeed = this.configurationService.getValue('galleryAnimationSpeed', 3000); } } 

استبدال متغيرات البيئة أثناء النشر أو وقت التشغيل


تنتهك العديد من الفرق قاعدة "إنشاء مرة واحدة ، ونشر العديد" من خلال تجميع التطبيق لكل بيئة بدلاً من مجرد تغيير التكوين في البنية المدمجة بالفعل.


لا تقم بإنشاء تجميعات منفصلة بتكوينات مختلفة ؛ بدلاً من ذلك ، استخدم مجموعة إنتاج واحدة فقط وقيم بديلة أثناء النشر أو أثناء تنفيذ التعليمات البرمجية. هناك عدة خيارات للقيام بذلك:


استبدال القيم بالعناصر النائبة في ملفات البيئة التي سيتم استبدالها في التجميع النهائي أثناء النشر


 export const environment = { production: true, apiUrl: 'APPLICATION_API_URL', }; 

أثناء النشر ، يجب استبدال السلسلة APPLICATION_API_URL بالعنوان الحقيقي لخادم api.


استخدام المتغيرات العامة وحقن ملفات التكوين مع وحدات التخزين عامل ميناء


 export const environment = { production: true, apiUrl: window.APPLICATION_API_URL, }; // in index.html before angular app bundles <script src="environment.js"></script> 

شكرا لك على اهتمامك بالمقال ، سأكون سعيدًا للنقد والتعليقات البناءة.




انضم أيضًا إلى مجتمعنا على Medium أو Telegram أو Twitter .

Source: https://habr.com/ru/post/ar477214/


All Articles