ДокументацияAngularСервисы и Dependency Injection
Средний 9 мин чтения

Сервисы и Dependency Injection

Сервисы содержат бизнес-логику и данные, используемые несколькими компонентами. Angular DI автоматически предоставляет нужные зависимости.

AngularservicesDIInjectableHttpClient

Что такое сервис?

Сервис — класс с декоратором @Injectable, содержащий логику, не связанную с представлением: HTTP-запросы, бизнес-логика, кэш, аутентификация.

// user.service.ts
import { Injectable } from '@angular/core'
import { HttpClient } from '@angular/common/http'
import { Observable } from 'rxjs'

export interface User {
  id: number
  name: string
  email: string
}

@Injectable({
  providedIn: 'root'  // Синглтон на всё приложение
})
export class UserService {
  private apiUrl = '/api/users'

  constructor(private http: HttpClient) {}

  getAll(): Observable<User[]> {
    return this.http.get<User[]>(this.apiUrl)
  }

  getById(id: number): Observable<User> {
    return this.http.get<User>(`${this.apiUrl}/${id}`)
  }

  create(user: Omit<User, 'id'>): Observable<User> {
    return this.http.post<User>(this.apiUrl, user)
  }

  update(id: number, user: Partial<User>): Observable<User> {
    return this.http.patch<User>(`${this.apiUrl}/${id}`, user)
  }

  delete(id: number): Observable<void> {
    return this.http.delete<void>(`${this.apiUrl}/${id}`)
  }
}

Внедрение зависимостей

// В компоненте
@Component({ ... })
export class UserListComponent implements OnInit {

  // Современный способ (Angular 14+)
  private userService = inject(UserService)

  // Или через конструктор
  constructor(private userService: UserService) {}

  users: User[] = []

  ngOnInit() {
    this.userService.getAll().subscribe(users => {
      this.users = users
    })
  }
}

Scope провайдеров

// Синглтон на всё приложение (один экземпляр)
@Injectable({ providedIn: 'root' })

// Синглтон на конкретный модуль
@Injectable({ providedIn: UserModule })

// Новый экземпляр для каждого компонента
@Component({
  providers: [UserService] // Локальный провайдер
})
export class UserCardComponent {
  private service = inject(UserService) // Свой экземпляр
}

Сервис состояния (State Service)

// store/cart.service.ts
import { Injectable, signal, computed } from '@angular/core'

export interface CartItem {
  id: number
  name: string
  price: number
  quantity: number
}

@Injectable({ providedIn: 'root' })
export class CartService {
  // Angular Signals (v17+)
  private items = signal<CartItem[]>([])

  // Вычисляемые данные
  readonly count = computed(() =>
    this.items().reduce((sum, item) => sum + item.quantity, 0)
  )

  readonly total = computed(() =>
    this.items().reduce((sum, item) => sum + item.price * item.quantity, 0)
  )

  addItem(item: CartItem) {
    this.items.update(items => {
      const existing = items.find(i => i.id === item.id)
      if (existing) {
        return items.map(i =>
          i.id === item.id ? { ...i, quantity: i.quantity + 1 } : i
        )
      }
      return [...items, { ...item, quantity: 1 }]
    })
  }

  removeItem(id: number) {
    this.items.update(items => items.filter(i => i.id !== id))
  }

  clear() {
    this.items.set([])
  }
}
// В компоненте
@Component({
  template: `
    <span>{{ cartService.count() }} товаров</span>
    <span>{{ cartService.total() | currency:'RUB' }}</span>
  `
})
export class HeaderComponent {
  protected cartService = inject(CartService)
}

HttpClient с перехватчиками

// interceptors/auth.interceptor.ts
import { HttpInterceptorFn } from '@angular/common/http'
import { inject } from '@angular/core'

export const authInterceptor: HttpInterceptorFn = (req, next) => {
  const token = inject(AuthService).getToken()

  if (token) {
    req = req.clone({
      setHeaders: { Authorization: `Bearer ${token}` }
    })
  }

  return next(req)
}
// app.config.ts
import { provideHttpClient, withInterceptors } from '@angular/common/http'

export const appConfig: ApplicationConfig = {
  providers: [
    provideHttpClient(withInterceptors([authInterceptor]))
  ]
}

Обработка ошибок в сервисе

import { catchError, throwError } from 'rxjs'

@Injectable({ providedIn: 'root' })
export class ApiService {
  constructor(private http: HttpClient) {}

  get<T>(url: string): Observable<T> {
    return this.http.get<T>(url).pipe(
      catchError(error => {
        if (error.status === 401) {
          // Перенаправить на логин
        }
        return throwError(() => new Error(error.message))
      })
    )
  }
}