Что такое сервисы и для чего они нужны?
Сервисы в Angular — это классы, предназначенные для инкапсуляции и переиспользования логики, которая не связана напрямую с отображением. Они позволяют разделить бизнес-логику приложения и представление (компоненты), обеспечивая повторное использование, тестируемость и слабую связанность между частями приложения. Сервисы — важный элемент архитектуры Angular, особенно в больших и модульных проектах.
1. Назначение сервисов
Сервисы решают задачи, которые не должны обрабатываться компонентами:
-
Получение данных с сервера (HTTP-запросы);
-
Работа с кешем, localStorage, cookies;
-
Обмен данными между компонентами;
-
Управление состоянием;
-
Авторизация и аутентификация;
-
Логирование, уведомления, модальные окна;
-
Обработка бизнес-логики;
-
Вспомогательные утилиты (форматирование, преобразования и пр.).
2. Создание сервиса
Сервис — обычный TypeScript-класс с декоратором @Injectable(). Он регистрируется в системе внедрения зависимостей (DI) Angular.
Пример:
import { Injectable } from '@angular/core';
@Injectable({ providedIn: 'root' })
export class LoggerService {
log(message: string) {
console.log('LOG:', message);
}
}
Декоратор @Injectable() сообщает Angular, что этот класс может иметь зависимости, и его можно инжектировать в другие классы.
3. Регистрация сервиса: providedIn
Существует два способа зарегистрировать сервис:
3.1. В декораторе
@Injectable({ providedIn: 'root' })
Angular создаст singleton-экземпляр и сделает его доступным во всём приложении. Это предпочтительный и современный способ.
3.2. В модуле
Альтернативный способ — вручную зарегистрировать сервис в providers:
@NgModule({
providers: \[MyService\]
})
export class AppModule {}
Можно также ограничить область действия, зарегистрировав сервис только в определённом модуле или компоненте.
4. Внедрение сервиса (Dependency Injection)
Для использования сервиса в компоненте (или в другом сервисе), его нужно инжектировать через конструктор:
import { Component } from '@angular/core';
import { LoggerService } from './logger.service';
@Component({
selector: 'app-root',
template: \`<h1>Пример</h1>\`
})
export class AppComponent {
constructor(private logger: LoggerService) {
this.logger.log('Приложение запущено');
}
}
Angular сам создаёт экземпляр сервиса и передаёт его в компонент. Это называется внедрением зависимостей (DI).
5. Область действия сервиса
Сервис по умолчанию — singleton во всей области действия, в которой он зарегистрирован:
-
providedIn: 'root' — глобальный singleton;
-
providedIn: SomeModule — один экземпляр на модуль;
-
Регистрация в providers компонента — создаёт новый экземпляр для каждого экземпляра компонента.
Пример локального сервиса:
@Component({
providers: \[MyService\]
})
export class SomeComponent { }
6. Работа с HTTP-запросами в сервисах
Один из главных кейсов использования сервисов — работа с REST API через HttpClient.
import { HttpClient } from '@angular/common/http';
@Injectable({ providedIn: 'root' })
export class ApiService {
constructor(private http: HttpClient) {}
getUsers() {
return this.http.get<User\[\]>('/api/users');
}
createUser(user: User) {
return this.http.post('/api/users', user);
}
}
Компонент:
constructor(private api: ApiService) {}
ngOnInit() {
this.api.getUsers().subscribe(users => this.users = users);
}
7. Реактивность в сервисах: Subject / BehaviorSubject
Сервисы могут использовать RxJS для управления состоянием и обмена данными между компонентами.
@Injectable({ providedIn: 'root' })
export class MessageService {
private message$ = new BehaviorSubject<string>('Начальное сообщение');
get messageStream$() {
return this.message$.asObservable();
}
updateMessage(msg: string) {
this.message$.next(msg);
}
}
Использование:
this.messageService.updateMessage('Новое сообщение');
this.messageService.messageStream$.subscribe(msg => {
console.log(msg);
});
8. Использование сервисов внутри сервисов
Сервисы могут зависеть друг от друга:
@Injectable({ providedIn: 'root' })
export class AuthService {
constructor(private logger: LoggerService) {}
login() {
this.logger.log('Пользователь вошёл');
}
}
9. Интерфейсы и типизация в сервисах
Хорошая практика — использовать типы и интерфейсы для описания данных:
export interface User {
id: number;
name: string;
}
getUser(id: number): Observable<User> {
return this.http.get<User>(\`/api/users/${id}\`);
}
10. Примеры типов сервисов
-
ApiService — обёртка над HttpClient;
-
AuthService — аутентификация, хранение токенов, логин/логаут;
-
UserService — работа с пользователями;
-
CartService — корзина покупок;
-
StateService — управление состоянием;
-
ModalService — управление модальными окнами;
-
NotificationService — отображение уведомлений (тосты, алерты);
-
LoggerService — логирование ошибок и действий.
11. Тестирование сервисов
Сервисы легко тестируются отдельно от компонентов.
Пример:
describe('LoggerService', () => {
let service: LoggerService;
beforeEach(() => {
TestBed.configureTestingModule({});
service = TestBed.inject(LoggerService);
});
it('должен логировать сообщение', () => {
spyOn(console, 'log');
service.log('Test');
expect(console.log).toHaveBeenCalledWith('LOG:', 'Test');
});
});
12. Отличие от компонентов
Компонент | Сервис | |
---|---|---|
Имеет шаблон | Да | Нет |
--- | --- | --- |
Отображает UI | Да | Нет |
--- | --- | --- |
Логика | Только UI-логика | Бизнес-логика, данные |
--- | --- | --- |
Внедрение | Через @Component | Через @Injectable() |
--- | --- | --- |
Жизненный цикл | Зависит от шаблона | Singleton (по умолчанию) |
--- | --- | --- |
13. Использование сервисов с InjectionToken
Для создания абстракций или подмены сервисов можно использовать InjectionToken.
export const API_URL = new InjectionToken<string>('API_URL');
@NgModule({
providers: \[
{ provide: API_URL, useValue: 'https://api.example.com' }
\]
})
14. Lazy-loading и сервисы
Если сервис используется в ленивом модуле и зарегистрирован в providers этого модуля, он будет иметь свой экземпляр. Это позволяет реализовать scoped-сервисы.
15. Жизненный цикл сервисов
Сервисы живут столько, сколько живёт область, в которой они зарегистрированы:
-
providedIn: 'root' — пока работает всё приложение;
-
В providers модуля — пока работает модуль;
-
В providers компонента — пока существует компонент.
Это позволяет контролировать расход памяти и количество инстансов.