Сервисы в Angular представляют собой фундаментальный механизм организации бизнес-логики, работы с данными и взаимодействия между компонентами приложения. Архитектура Angular строится вокруг принципа разделения ответственности, где компоненты отвечают за представление, а сервисы — за обработку данных, состояние и интеграцию с внешними источниками.
Сервис в Angular — это класс, предназначенный для инкапсуляции логики, которая не связана напрямую с отображением интерфейса. Основная задача сервисов заключается в повторном использовании кода и обеспечении централизованного управления данными.
Ключевые функции сервисов:
Такое разделение позволяет избегать дублирования кода и упрощает тестирование.
Angular использует систему Dependency Injection (DI), которая позволяет автоматически создавать и предоставлять экземпляры сервисов там, где они необходимы. Сервисы регистрируются в инжекторах, а компоненты получают их через конструктор.
Основные преимущества DI:
Инжектор хранит дерево зависимостей, обеспечивая возможность создания как глобальных, так и локальных экземпляров сервисов.
Сервис в Angular представляет собой класс с декоратором, который определяет его поведение в системе внедрения зависимостей.
Простейшая структура:
import { Injectable } from '@angular/core';
@Injectable({
providedIn: 'root'
})
export class DataService {
private data: string[] = [];
addItem(item: string): void {
this.data.push(item);
}
getItems(): string[] {
return this.data;
}
}
Ключевой параметр providedIn: 'root' делает сервис
синглтоном на уровне всего приложения.
Сервисы могут быть зарегистрированы в разных уровнях приложения:
Сервис создается один раз и доступен во всем приложении. Используется для хранения общего состояния и работы с API.
Сервис создается отдельно для каждого модуля. Это позволяет изолировать функциональность.
Каждый компонент получает собственный экземпляр сервиса. Это полезно при необходимости локального состояния.
Такой механизм обеспечивает гибкость управления состоянием и зависимостями.
Сервис создается при первом запросе к нему через DI и уничтожается
вместе с соответствующим инжектором. Если сервис зарегистрирован на
уровне root, он живет на протяжении всего жизненного цикла
приложения.
Важно учитывать:
Сервисы часто используются как централизованное хранилище состояния. Это особенно актуально в приложениях средней и высокой сложности.
Пример хранения состояния:
@Injectable({
providedIn: 'root'
})
export class StateService {
private state = {
user: null,
isAuthenticated: false
};
setUser(user: any): void {
this.state.user = user;
this.state.isAuthenticated = true;
}
clear(): void {
this.state.user = null;
this.state.isAuthenticated = false;
}
getState() {
return this.state;
}
}
Такой подход позволяет централизовать управление данными без необходимости передачи через цепочку компонентов.
Сервисы часто выступают как слой взаимодействия с сервером. Обычно используется HttpClient, который инкапсулируется внутри сервиса.
import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';
@Injectable({
providedIn: 'root'
})
export class ApiService {
constructor(private http: HttpClient) {}
getUsers(): Observable<any> {
return this.http.get('/api/users');
}
createUser(data: any): Observable<any> {
return this.http.post('/api/users', data);
}
}
Такой подход отделяет сетевую логику от компонентов и упрощает поддержку кода.
Сервисы часто используют реактивные потоки для управления данными. Это позволяет строить асинхронные цепочки обработки событий.
Основные элементы:
Пример:
import { BehaviorSubject } from 'rxjs';
@Injectable({
providedIn: 'root'
})
export class AuthService {
private loggedIn = new BehaviorSubject<boolean>(false);
isLoggedIn$ = this.loggedIn.asObservable();
login(): void {
this.loggedIn.next(true);
}
logout(): void {
this.loggedIn.next(false);
}
}
Реактивная модель позволяет компонентам подписываться на изменения состояния без прямой зависимости от источника данных.
Сервисы могут зависеть друг от друга, формируя цепочки бизнес-логики. Angular DI автоматически разрешает такие зависимости.
Пример:
@Injectable({
providedIn: 'root'
})
export class LoggerService {
log(message: string): void {
console.log(message);
}
}
@Injectable({
providedIn: 'root'
})
export class UserService {
constructor(private logger: LoggerService) {}
createUser(name: string): void {
this.logger.log(`User created: ${name}`);
}
}
Такой подход способствует модульности и повторному использованию кода.
Сервисы являются одной из самых удобных частей Angular для модульного тестирования, так как не зависят от DOM.
Пример теста:
import { TestBed } from '@angular/core/testing';
describe('DataService', () => {
let service: DataService;
beforeEach(() => {
TestBed.configureTestingModule({});
service = TestBed.inject(DataService);
});
it('should add item', () => {
service.addItem('test');
expect(service.getItems()).toContain('test');
});
});
DI облегчает подмену зависимостей и создание моков.
Часто встречающиеся проблемы:
Правильная архитектура требует строгого разделения ролей между слоями приложения.
В крупных приложениях сервисы группируются по функциональным областям:
Такой подход облегчает масштабирование и сопровождение кода.
Сервисный слой становится центральным элементом архитектуры, связывающим интерфейс, бизнес-логику и внешние источники данных без прямых зависимостей между компонентами.