در این جلسه یاد می‌گیریم Service چیست، چرا باید منطق برنامه را از کامپوننت جدا کنیم، و Angular چطور با مفهوم Dependency Injection (DI) سرویس‌ها را می‌سازد و در اختیار بخش‌های مختلف برنامه قرار می‌دهد. همچنین با Providerها، DI Container (Injector) و تغییرات مهم Angular جدید مثل Provide API و استفاده از inject() آشنا می‌شویم.


Service در Angular چیست و چرا به آن نیاز داریم؟

کامپوننت‌ها قرار است روی UI تمرکز کنند: نمایش داده، گرفتن ورودی کاربر، مدیریت رویدادها.
اما خیلی زود در پروژه می‌بینی که کارهای تکراری و منطق‌های مشترک داری مثل:

  • گرفتن دیتا از API با HttpClient
  • مدیریت سبد خرید
  • کار با LocalStorage
  • اعتبارسنجی، فرمت کردن داده‌ها
  • اشتراک‌گذاری state بین چند کامپوننت

اگر این منطق‌ها را داخل کامپوننت‌ها بنویسیم:

  • کامپوننت‌ها خیلی بزرگ و شلوغ می‌شوند
  • کد تکراری زیاد می‌شود
  • تست‌نویسی سخت می‌شود
  • نگهداری پروژه در آینده دردسرساز می‌شود

راه درست: این منطق‌ها را داخل Service قرار می‌دهیم و در کامپوننت‌ها فقط استفاده می‌کنیم.


Dependency Injection (DI) یعنی چی؟

DI یعنی:
به جای اینکه هر کلاس خودش وابستگی‌هایش را با new کردن بسازد، از بیرون آن‌ها را دریافت کند.

مثال اشتباه (بدون DI)

export class ProductsComponent {
  private service = new ProductsService(); // بد!

  // ...
}

چرا بد است؟

  • Angular نمی‌تواند این سرویس را مدیریت کند
  • اگر سرویس وابستگی دیگری داشته باشد (مثل HttpClient) به مشکل می‌خوریم
  • تست کردن سخت می‌شود (Mock کردن سرویس دشوار است)
  • کنترل Singleton بودن یا Scope سرویس از دست می‌رود

مثال درست (با DI)

Angular خودش سرویس را می‌سازد و تزریق می‌کند.


DI Container (Injector) در Angular چیست؟

Angular یک سیستم داخلی دارد به نام Injector یا همان DI Container که کارش این است:

  1. بفهمد یک کلاس به چه وابستگی‌هایی نیاز دارد
  2. آن وابستگی‌ها را بسازد (یا از قبل داشته باشد)
  3. تزریق کند

به زبان ساده:

  • Provider می‌گوید: «اگر کسی این Token را خواست، اینطوری نمونه بساز»
  • Injector می‌گوید: «من نگهدارنده Providerها هستم و نمونه‌ها را می‌دهم»

ساخت یک Service واقعی (مثال عملی)

فرض کن می‌خواهیم محصولات را از API بگیریم (همان کاری که جلسه قبل در کامپوننت انجام می‌دادیم). این بار آن را تمیزتر می‌کنیم و می‌بریم داخل سرویس.

1) ساخت سرویس ProductsService

import { Injectable, inject } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';

export interface Product {
  id: number;
  title: string;
  image: string;
  category: string;
  description: string;
}

@Injectable({ providedIn: 'root' })
export class ProductsService {
  private http = inject(HttpClient);

  getProducts(): Observable<Product[]> {
    return this.http.get<Product[]>('https://mytag.ir/api/products.php');
  }

  getProductById(id: number): Observable<Product> {
    return this.http.get<Product>(`https://mytag.ir/api/products.php?id=${id}`);
  }
}

نکته مهم:
@Injectable({ providedIn: 'root' }) یعنی این سرویس به صورت پیش‌فرض در سطح کل برنامه provider می‌شود و یک نمونه singleton خواهد داشت.


تزریق سرویس در کامپوننت (دو روش رایج)

روش ۱: Constructor Injection (کلاسیک و رایج)

import { Component, OnInit } from '@angular/core';
import { ProductsService, Product } from './products.service';

@Component({
  selector: 'app-products',
  standalone: true,
  templateUrl: './products.component.html',
})
export class ProductsComponent implements OnInit {
  products: Product[] = [];

  constructor(private productsService: ProductsService) {}

  ngOnInit(): void {
    this.productsService.getProducts().subscribe(data => {
      this.products = data;
    });
  }
}

روش ۲: تزریق با inject() (مدرن، مخصوص Standalone و Angular 17+)

import { Component, OnInit, inject } from '@angular/core';
import { ProductsService, Product } from './products.service';

@Component({
  selector: 'app-products',
  standalone: true,
  templateUrl: './products.component.html',
})
export class ProductsComponent implements OnInit {
  private productsService = inject(ProductsService);

  products: Product[] = [];

  ngOnInit(): void {
    this.productsService.getProducts().subscribe(data => {
      this.products = data;
    });
  }
}

کِی inject() بهتره؟

  • وقتی با Standalone و Functional APIها کار می‌کنی (مثل resolver/guard functional)
  • وقتی می‌خواهی کد را از constructor خلوت‌تر کنی
  • وقتی در فایل‌های غیرکلاسی (functional) تزریق لازم داری

Provider چیست و چه مدل‌هایی دارد؟

Provider به Angular می‌گوید یک وابستگی را چطور بسازد. Providerها چند نوع مهم دارند:

1) useClass

یعنی این کلاس را بساز و بده:

{ provide: LoggerService, useClass: LoggerService }

2) useValue

وقتی می‌خواهی یک مقدار ثابت بدهی:

{ provide: 'API_URL', useValue: 'https://mytag.ir/api' }

3) useFactory

وقتی ساختن وابستگی نیاز به منطق دارد:

{
  provide: 'API_URL',
  useFactory: () => (location.hostname.includes('localhost')
    ? 'http://localhost:3000'
    : 'https://mytag.ir/api')
}

4) useExisting

وقتی می‌خواهی یک token به همان نمونه یک token دیگر اشاره کند:

{ provide: OldLogger, useExisting: LoggerService }

(در این جلسه معمولاً providedIn: 'root' و providers: [] برای شروع کافی است، ولی خوب است مدل‌ها را بشناسی.)


Hierarchical DI (سطوح تزریق) — خیلی مهم

Angular DI سلسله‌مراتبی است. یعنی سرویس می‌تواند در سطح‌های مختلف تعریف شود:

  1. Root (کل برنامه)
    providedIn: 'root' → یک نمونه برای همه
  2. Component Level
    اگر داخل providers یک کامپوننت سرویس را ثبت کنی → هر instance کامپوننت یک نمونه جدا می‌گیرد
  3. Route Level (در Angular جدید بسیار کاربردی)
    می‌توانی provider را داخل route تعریف کنی تا فقط در همان مسیر فعال باشد

مثال: اگر داخل کامپوننت بنویسی:

@Component({
  // ...
  providers: [ProductsService]
})

دیگر singleton سراسری نیست؛ هر بار که کامپوننت ساخته شود، سرویس جدید ساخته می‌شود.


تغییرات جدید Angular: Provide API (Angular 15+ تا 17+)

در Angular مدرن (Standalone)، به جای NgModuleها معمولاً با provide... ها کار می‌کنیم.

مثال در app.config.ts:

import { ApplicationConfig } from '@angular/core';
import { provideRouter } from '@angular/router';
import { provideHttpClient } from '@angular/common/http';
import { routes } from './app.routes';

export const appConfig: ApplicationConfig = {
  providers: [
    provideRouter(routes),
    provideHttpClient(),
  ]
};

اینجا:

  • provideHttpClient() یعنی HttpClient و providerهای لازم برایش را به DI Container معرفی کن
  • provideRouter(routes) هم router را provider می‌کند

این سبک جدید:

  • سبک‌تر
  • خواناتر
  • مناسب‌تر برای Standalone
  • نزدیک‌تر به معماری‌های مدرن است

جمع‌بندی

در این جلسه یاد گرفتیم:

  • Service برای جدا کردن منطق از UI است
  • DI یعنی وابستگی‌ها را نسازیم، تزریق کنیم
  • Angular با Injector (DI Container) وابستگی‌ها را مدیریت می‌کند
  • @Injectable({ providedIn: 'root' }) رایج‌ترین روش provider کردن سرویس است
  • تزریق سرویس می‌تواند با constructor یا با inject() انجام شود
  • Angular جدید با Provide API (مثل provideHttpClient) مسیر را ساده‌تر کرده است

دسترسی به ویدیوهای کامل دوره

برای مشاهده ویدیوهای کامل هر جلسه، دریافت تمرین‌ها، فایل‌ها و پشتیبانی، می‌توانید دوره کامل را تهیه کنید.