Avatar ·

Для чего нужен providedIn в декораторе Injectable при создании сервисов в Angular 6?

Всем привет! Я начал изучать Angular 6 и TypeScript, и при генерации сервиса через CLI я заметил такую штуку: ```typescript @Injectable({ providedIn: 'root' }) export class MyService { constructor() { } } ``` Раньше, в Angular 5, я просто писал `@Injectable()` без всяких параметров, и потом регистрировал сервис в модуле в секции `providers`. А теперь CLI генерирует этот `providedIn: 'root'`. Я немного запутался. Вот как я делал раньше: ```typescript // my.service.ts @Injectable() export class MyService { } // app.module.ts @NgModule({ providers: [MyService] }) export class AppModule { } ``` А теперь новый подход: ```typescript // my.service.ts @Injectable({ providedIn: 'root' }) export class MyService { } ``` Вопросы: 1. Что конкретно делает `providedIn: 'root'`? Это просто замена регистрации в модуле? 2. Если я укажу `providedIn: 'root'`, нужно ли всё равно добавлять сервис в `providers` модуля? 3. Что будет, если я укажу `providedIn: SomeModule` вместо `'root'`? В чём разница? 4. Влияет ли это на производительность или на то, как Angular создаёт экземпляры сервисов (синглтон или нет)? 5. Правильно ли я понимаю, что теперь можно вообще не трогать модули для регистрации сервисов? Объясните, пожалуйста, для новичка. Заранее спасибо! <<>>

Что такое providedIn и зачем он нужен?

providedIn — это опция декоратора @Injectable, появившаяся в Angular 6. Она позволяет указать, на каком уровне инжектор должен создать и предоставить экземпляр вашего сервиса. Раньше вы регистрировали сервис вручную в массиве providers модуля. Теперь Angular может сделать это автоматически, причём с дополнительными преимуществами.

1. providedIn: 'root' — что это значит?

providedIn: 'root' означает, что сервис будет зарегистрирован в корневом инжекторе приложения. Это самый распространённый вариант. Он делает сервис доступным во всём приложении (во всех модулях, компонентах, директивах и других сервисах) как синглтон — один экземпляр на всё приложение.

Важно: если вы указали providedIn: 'root', вам НЕ НУЖНО добавлять этот сервис в providers модуля. Если вы сделаете и то, и другое, ничего страшного не произойдёт — Angular просто проигнорирует дублирование, но лучше так не делать, чтобы избежать путаницы.

2. providedIn: SomeModule — что это?

Вы можете указать конкретный модуль, например:

@Injectable({
  providedIn: AdminModule
})
export class AdminService { }

В этом случае сервис будет зарегистрирован в инжекторе модуля AdminModule. Он будет доступен только внутри этого модуля (и его дочерних компонентов/сервисов). Если AdminModule будет лениво загружаться (lazy-loaded), то сервис будет создан только при загрузке этого модуля. Это позволяет экономить память и ресурсы: если модуль никогда не загрузится, сервис не будет создан.

3. Влияние на производительность и синглтон

Синглтон: при providedIn: 'root' сервис — гарантированный синглтон. При регистрации в модуле тоже получается синглтон, но с одним важным нюансом: если один и тот же сервис зарегистрировать в двух разных лениво загружаемых модулях, то каждый модуль получит свой собственный экземпляр. С providedIn: 'root' такого не случится — всегда будет один экземпляр.

Производительность и tree-shaking: это главное преимущество providedIn. Если вы регистрируете сервис в модуле, Angular не может "знать", используется ли сервис на самом деле. Даже если вы нигде не внедряете сервис, он всё равно будет включён в финальную сборку (bundle). С providedIn: 'root' Angular использует технику tree-shaking: если сервис нигде не используется (ни в одном компоненте или другом сервисе), он будет автоматически исключён из финальной сборки. Это уменьшает размер приложения.

4. Практические рекомендации

  • Всегда используйте providedIn: 'root' для сервисов, которые должны быть доступны глобально (например, сервисы данных, авторизации, логирования).
  • Если сервис нужен только в одном конкретном модуле (особенно лениво загружаемом), укажите providedIn: ModuleName.
  • Не регистрируйте сервис одновременно и через providedIn, и в providers модуля — это избыточно.
  • Для компонентных сервисов (которые должны жить только внутри одного компонента) продолжайте использовать providers в декораторе @Component.

Итоговый пример

// Глобальный сервис — доступен везде
@Injectable({
  providedIn: 'root'
})
export class GlobalService {
  getData() { return 'global data'; }
}

// Сервис для лениво загружаемого модуля
@Injectable({
  providedIn: AdminModule
})
export class AdminService {
  getAdminData() { return 'admin data'; }
}

// Сервис для конкретного компонента
@Component({
  selector: 'app-user',
  template: '...',
  providers: [UserService] // только для этого компонента и его детей
})
export class UserComponent {
  constructor(private userService: UserService) { }
}

Таким образом, providedIn — это современный, более эффективный и удобный способ регистрации сервисов. Он делает код чище, уменьшает размер сборки и даёт больше контроля над областью видимости сервиса.

<<>>

Дополнение: providedIn и ленивая загрузка (Lazy Loading)

Коллега выше отлично объяснил основы. Я хочу добавить важный нюанс про ленивую загрузку модулей, который часто вызывает путаницу.

Если у вас есть лениво загружаемый модуль (например, AdminModule), и вы зарегистрировали сервис через providedIn: AdminModule, то этот сервис НЕ БУДЕТ доступен в корневом инжекторе. Это значит, что если какой-то сервис из другого модуля (не из AdminModule) попытается внедрить AdminService, Angular выбросит ошибку. Это логично: зачем создавать сервис для админки, если пользователь даже не заходил в админку?

Однако есть хитрость: если вы укажете providedIn: 'root' для сервиса, который используется только в лениво загружаемом модуле, то сервис всё равно будет создан при старте приложения (потому что корневой инжектор создаётся сразу). Это может быть неэффективно, если сервис тяжёлый. Поэтому правило такое:

  • Сервис нужен везде и сразу → providedIn: 'root'
  • Сервис нужен только внутри лениво загружаемого модуля → providedIn: ModuleName
  • Сервис нужен в нескольких модулях, но не во всех → создайте отдельный shared-модуль и зарегистрируйте сервис в его providers (старый способ). Либо, начиная с Angular 14, можно использовать providedIn: 'platform' для сервисов, которые должны жить дольше, чем само приложение (редкий случай).

Также помните: если вы используете providedIn: SomeModule, этот модуль должен быть импортирован в ваше приложение (или в другой модуль, который его импортирует). Иначе Angular не сможет найти инжектор для этого сервиса.