Пошук уроків, статей та іншого контенту
Створите provider через useFactory і передасте йому залежності для динамічного формування значень.
Factory provider — це provider, значення якого створюється функцією, переданою в useFactory.
Такий підхід корисний, коли значення потрібно:
сформувати динамічно;
отримати з конфігурації;
побудувати на основі інших provider-ів;
створити асинхронно;
передати споживачам як об’єкт із готовими параметрами.
Базова форма factory provider:
{
provide: 'TOKEN',
useFactory: () => {
return {
enabled: true,
};
},
}Після реєстрації provider його можна отримати через токен 'TOKEN'.
Фабрика може використовувати інші залежності. Для цього потрібно:
Додати залежності до параметрів функції useFactory.
Передати відповідні provider-и в масиві inject.
{
provide: 'TOKEN',
useFactory: (dependency: SomeDependency) => {
return dependency.createValue();
},
inject: [SomeDependency],
}Порядок елементів у inject має відповідати порядку параметрів фабрики:
useFactory: (first, second) => {
// first відповідає першому елементу inject
// second відповідає другому елементу inject
},
inject: [FirstDependency, SecondDependency],NestJS сам створить або знайде залежності та передасть їх у фабрику.
Розглянемо provider, який формує налаштування застосунку на основі змінних середовища.
Спочатку встановимо пакет конфігурації:
npm install @nestjs/configСтворимо токен і тип налаштувань:
// app.constants.ts
export const APP_OPTIONS = 'APP_OPTIONS';// app-options.interface.ts
export interface AppOptions {
appName: string;
port: number;
isProduction: boolean;
}Тепер зареєструємо ConfigModule і factory provider:
// app.module.ts
import { Module } from '@nestjs/common';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { APP_OPTIONS } from './app.constants';
import { AppOptions } from './app-options.interface';
import { AppController } from './app.controller';
@Module({
imports: [
ConfigModule.forRoot({
isGlobal: true,
}),
],
controllers: [AppController],
providers: [
{
provide: APP_OPTIONS,
useFactory: (configService: ConfigService): AppOptions => {
const appName = configService.get<string>('APP_NAME', 'Demo app');
const port = configService.get<number>('PORT', 3000);
const nodeEnvironment = configService.get<string>(
'NODE_ENV',
'development',
);
return {
appName,
port,
isProduction: nodeEnvironment === 'production',
};
},
inject: [ConfigService],
},
],
})
export class AppModule {}Функція useFactory отримує ConfigService, читає значення конфігурації та повертає об’єкт типу AppOptions.
Тепер цей об’єкт можна використати в іншому класі:
// app.controller.ts
import { Controller, Get, Inject } from '@nestjs/common';
import { APP_OPTIONS } from './app.constants';
import { AppOptions } from './app-options.interface';
@Controller()
export class AppController {
constructor(
@Inject(APP_OPTIONS)
private readonly appOptions: AppOptions,
) {}
@Get()
getAppInfo() {
return {
name: this.appOptions.appName,
port: this.appOptions.port,
environment: this.appOptions.isProduction
? 'production'
: 'development',
};
}
}Значення APP_OPTIONS — це не назва класу, а рядковий токен. Тому для його ін’єкції використовується декоратор @Inject(APP_OPTIONS).
Наприклад, якщо застосунок запустити з такими змінними:
APP_NAME="Orders API" PORT=4000 NODE_ENV=production npm run start:devЗапит до GET / поверне:
{
"name": "Orders API",
"port": 4000,
"environment": "production"
}Factory provider може залежати від кількох provider-ів.
import { Module } from '@nestjs/common';
class LoggerService {
log(message: string) {
console.log(message);
}
}
class EnvironmentService {
getEnvironment() {
return process.env.NODE_ENV ?? 'development';
}
}
@Module({
providers: [
LoggerService,
EnvironmentService,
{
provide: 'APP_CONTEXT',
useFactory: (
loggerService: LoggerService,
environmentService: EnvironmentService,
) => {
const environment = environmentService.getEnvironment();
loggerService.log(`Запуск у середовищі: ${environment}`);
return {
environment,
startedAt: new Date(),
};
},
inject: [LoggerService, EnvironmentService],
},
],
})
export class AppModule {}У цьому прикладі:
LoggerService передається першим параметром;
EnvironmentService передається другим параметром;
їхній порядок визначається масивом inject;
результат фабрики реєструється під токеном 'APP_CONTEXT'.
Фабрика може бути асинхронною. NestJS дочекається виконання Promise, перш ніж зробити provider доступним для інших залежностей.
import { Module } from '@nestjs/common';
@Module({
providers: [
{
provide: 'REMOTE_CONFIG',
useFactory: async () => {
const response = await fetch('https://example.com/config.json');
if (!response.ok) {
throw new Error('Не вдалося завантажити конфігурацію');
}
return response.json();
},
},
],
})
export class AppModule {}Асинхронний factory provider особливо корисний, коли значення потрібно отримати до запуску залежних компонентів.
У реальному застосунку URL або інші параметри такого запиту зазвичай також передаються через інший provider, наприклад ConfigService.
Залежністю фабрики не обов’язково має бути клас. Це може бути інший provider із власним токеном.
import { Module } from '@nestjs/common';
@Module({
providers: [
{
provide: 'API_PREFIX',
useValue: '/api/v1',
},
{
provide: 'ROUTER_OPTIONS',
useFactory: (apiPrefix: string) => {
return {
prefix: apiPrefix,
version: 'v1',
};
},
inject: ['API_PREFIX'],
},
],
})
export class AppModule {}Тут:
provider 'API_PREFIX' повертає рядок '/api/v1';
фабрика отримує цей рядок;
фабрика створює об’єкт 'ROUTER_OPTIONS'.
useValue і useFactoryuseValue підходить, коли значення вже готове:
{
provide: 'APP_NAME',
useValue: 'Orders API',
}useFactory підходить, коли значення потрібно обчислити:
{
provide: 'APP_NAME',
useFactory: (configService: ConfigService) => {
return configService.get<string>('APP_NAME', 'Orders API');
},
inject: [ConfigService],
}Вибір залежить від того, чи потрібна логіка створення значення.
injectЯкщо фабрика має параметр залежності, але provider не містить inject, NestJS не зможе коректно передати цю залежність.
Неправильно:
{
provide: 'APP_OPTIONS',
useFactory: (configService: ConfigService) => {
return configService.get('APP_NAME');
},
}Правильно:
{
provide: 'APP_OPTIONS',
useFactory: (configService: ConfigService) => {
return configService.get('APP_NAME');
},
inject: [ConfigService],
}Неправильно:
{
provide: 'VALUE',
useFactory: (configService, loggerService) => {
// configService і loggerService отримають неправильні залежності
},
inject: [LoggerService, ConfigService],
}Потрібно узгодити порядок параметрів і inject:
{
provide: 'VALUE',
useFactory: (configService, loggerService) => {
return {
name: configService.get('APP_NAME'),
};
},
inject: [ConfigService, LoggerService],
}Якщо provider зареєстрований із рядковим токеном, його потрібно отримувати з таким самим токеном:
{
provide: 'APP_OPTIONS',
useFactory: () => ({
enabled: true,
}),
}constructor(
@Inject('APP_OPTIONS')
private readonly options: { enabled: boolean },
) {}Використання іншого рядка, наприклад @Inject('OPTIONS'), призведе до помилки пошуку provider-а.
@Inject для нестандартного токенаДля класових provider-ів NestJS може визначити залежність за типом:
constructor(private readonly configService: ConfigService) {}Для рядків, символів або інших нестандартних токенів потрібно явно вказати @Inject:
constructor(
@Inject('APP_OPTIONS')
private readonly options: AppOptions,
) {}useFactoryFactory provider варто використовувати, коли:
конфігурація залежить від змінних середовища;
об’єкт потрібно зібрати з кількох залежностей;
значення залежить від поточного режиму запуску;
потрібно виконати підготовчу логіку під час створення provider-а;
provider має створюватися асинхронно.
Фабрика повинна повертати значення, яке потім буде доступне всім споживачам цього provider-а через його токен.
useFactory створює provider за допомогою функції.
Залежності фабрики описуються параметрами функції.
Реальні provider-и для цих параметрів передаються в масиві inject.
Порядок параметрів фабрики та inject має збігатися.
Результат фабрики можна отримувати через класовий або рядковий токен.
Для ін’єкції нестандартного токена використовується @Inject.
Factory provider може бути асинхронним і повертати Promise.