سرویس ها و تزریق وابستگی (Services & DI)
«سرویس ها در انگولار» منطقِ تکراری را جدا نگه می دارند. «تزریق وابستگی (Dependency Injection)» یعنی گرفتنِ آماده سرویس، مثل برقِ پریز. بنابراین کدها تمیزتر می شوند و بین کامپوننت ها به راحتی اشتراک دارند.
مبانی سرویس و DI
«سرویس (Service)» کلاس منطق و وضعیت اشتراکی است. «DI» نمونه سرویس را هرجا بخواهی می دهد. برای اشتراک سراسری از providedIn: 'root' استفاده کن. برای نمونه جدا، سرویس را داخل یک کامپوننت providers بده.
import { Injectable } from '@angular/core';
@Injectable({ providedIn: 'root' })
export class CounterService {
value = 0;
inc() {
this.value++;
}
}
// Inject in a component
// constructor(public counter: CounterService) {}
نکته: اگر سرویسی سرویس دیگر می گیرد، حتماً @Injectable() بگذار تا DI کار کند.
سرویس پایه با singleton ریشه
با @Injectable({ providedIn: 'root' }) یک نمونه سراسری بساز. سپس آن را در سازنده کامپوننت تزریق کن. در نتیجه، وضعیت بین نماها مشترک می ماند.
import { bootstrapApplication } from '@angular/platform-browser';
import { Component, Injectable } from '@angular/core';
@Injectable({ providedIn: 'root' })
export class CounterService {
value = 0;
inc() {
this.value++;
}
dec() {
this.value--;
}
reset() {
this.value = 0;
}
}
@Component({
selector: 'app-root',
standalone: true,
template: `
<h3>Services</h3>
<p>Counter: {{ counter.value }}</p>
<button (click)="counter.inc()">+</button>
<button (click)="counter.dec()">-</button>
<button (click)="counter.reset()">Reset</button>
`
})
export class App {
constructor(public counter: CounterService) {}
}
bootstrapApplication(App);
اشتراک سرویس بین کامپوننت ها
وقتی سرویس در ریشه است، همه کامپوننت ها همان نمونه را می بینند. بنابراین تغییر در یکی، در دیگری هم دیده می شود. مثل scoreboard مشترک در یک بازی.
import { bootstrapApplication } from '@angular/platform-browser';
import { Component, Injectable } from '@angular/core';
@Injectable({ providedIn: 'root' })
export class CounterService {
value = 0;
inc() {
this.value++;
}
dec() {
this.value--;
}
}
@Component({
selector: 'counter-a',
standalone: true,
template: `
<h4>Counter A</h4>
<p>Value: {{ counter.value }}</p>
<button (click)="counter.inc()">+1</button>
<button (click)="counter.dec()">-1</button>
`
})
export class CounterA {
constructor(public counter: CounterService) {}
}
@Component({
selector: 'counter-b',
standalone: true,
template: `
<h4>Counter B</h4>
<p>Value: {{ counter.value }}</p>
<button (click)="counter.inc()">+1</button>
<button (click)="counter.dec()">-1</button>
`
})
export class CounterB {
constructor(public counter: CounterService) {}
}
@Component({
selector: 'app-root',
standalone: true,
imports: [CounterA, CounterB],
template: `
<h3>Shared Service Across Components</h3>
<counter-a></counter-a>
<counter-b></counter-b>
<p><em>Both components use the same CounterService instance.</em></p>
`
})
export class App {}
bootstrapApplication(App);
DI سلسله مراتبی با providers کامپوننت
اگر سرویس را داخل یک کامپوننت providers بدهی، هر زیر درخت نمونه جدا می گیرد. بنابراین دو پنل مجزا، مقدارهای مستقل دارند. این برای جداسازی عالی است.
import { bootstrapApplication } from '@angular/platform-browser';
import { Component, Injectable } from '@angular/core';
import { CommonModule } from '@angular/common';
@Injectable()
export class LocalCounterService {
id = Math.floor(Math.random() * 10000);
value = 0;
inc() {
this.value++;
}
}
@Component({
selector: 'counter-view',
standalone: true,
template: `
<p>Service #{{ svc.id }} value: {{ svc.value }}</p>
<button (click)="svc.inc()">+1</button>
`
})
export class CounterView {
constructor(public svc: LocalCounterService) {}
}
@Component({
selector: 'panel-a',
standalone: true,
imports: [CommonModule, CounterView],
providers: [LocalCounterService],
template: `
<h4>Panel A (own provider)</h4>
<counter-view></counter-view>
<counter-view></counter-view>
`
})
export class PanelA {}
@Component({
selector: 'panel-b',
standalone: true,
imports: [CommonModule, CounterView],
providers: [LocalCounterService],
template: `
<h4>Panel B (own provider)</h4>
<counter-view></counter-view>
<counter-view></counter-view>
`
})
export class PanelB {}
@Component({
selector: 'app-root',
standalone: true,
imports: [CommonModule, PanelA, PanelB],
styles: [`
.grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 16px; }
h4 { margin: 0 0 8px; }
button { margin-top: 6px; }
`],
template: `
<h3>Component-Provided Service (Hierarchical DI)</h3>
<p>Each panel provides its own service instance.</p>
<p>Counters inside the same panel share the instance.</p>
<p>Different panels do not.</p>
<div class="grid">
<panel-a></panel-a>
<panel-b></panel-b>
</div>
`
})
export class App {}
bootstrapApplication(App);
هشدار: وابستگیِ حلقه ای نساز. اگر دو سرویس هم دیگر را می خواهند، منطق مشترک را جدا کن.
گام های عملی سریع
- یک کلاس سرویس با
@Injectable()بساز. - برای singleton،
providedIn: 'root'تنظیم کن. - سرویس را در سازنده کامپوننت تزریق کن.
جمع بندی سریع
- سرویس ها منطق مشترک را متمرکز می کنند.
- DI نمونه سرویس را آماده می دهد.
- ریشه برای اشتراک، کامپوننت برای جداسازی است.
- @Injectable برای متادیتای تزریق ضروری است.
نکته: برای مسیرها به روتر در انگولار برو. برای دریافت داده به HTTP در انگولار سر بزن.