⚙️ Angular Services و Dependency Injection: إدارة البيانات والـ API باحترافية
تقدمك في المستوى الرابع66%
📦 لماذا نحتاج Services في Angular؟
في الدرس السابق تعلمنا كيفية بناء مكونات Angular ونقل البيانات بينها باستخدام @Input و @Output. لكن في تطبيق حقيقي، البيانات مش بتكون ثابتة - لازم نجلبها من API، نحفظها في قاعدة بيانات، أو نشاركها بين عدة مكونات. هنا بييجي دور Services.
Service في Angular هو فئة TypeScript بتعمل مهمة محددة: جلب بيانات، إدارة حالة، أو تنفيذ عمليات حسابية. الميزة إنها قابلة للمشاركة بين أكتر من مكون، وبتستخدم Dependency Injection (DI) عشان Angular "تحقن" الخدمة في أي مكون يحتاجها.
بنهاية هذا الدرس، هتكون قادر على:
إنشاء Services باستخدام Angular CLI وربطها بالمكونات
فهم Dependency Injection وإزاي Angular بتدير الكائنات
استخدام HttpClient لجلب وإرسال البيانات من/إلى API
التعامل مع Observable و subscribe في العمليات غير المتزامنة
فصل منطق العمل عن منطق العرض (Separation of Concerns)
💡 مثال من الحياة العملية: تخيّل إنك بتبني تطبيق متجر إلكتروني. عندك 10 مكونات مختلفة (قائمة منتجات، صفحة تفاصيل، سلة التسوق، إلخ). كلهم محتاجين نفس البيانات (قائمة المنتجات). بدل ما تكتب كود جلب المنتجات 10 مرات، تكتبه مرة واحدة في ProductService وتستخدمه في كل المكونات.
💡 ما هي Dependency Injection (DI)؟
Dependency Injection هو نمط تصميم (Design Pattern) بيسمح للمكونات "تطلب" الخدمات اللي تحتاجها بدل ما تخلقها بنفسها. Angular بتدير إنشاء وإعادة استخدام الكائنات تلقائياً.
بدل ما تكتب:
// ❌ الطريقة القديمة (بدون DI)export classUserComponent {
// بننشئ instance جديد في كل مكون - ده بيستهلك مواردuserService = newUserService();
}
في Angular بتكتب:
// ✅ الطريقة الحديثة (مع DI)export classUserComponent {
// Angular بتمرر instance جاهز - Singletonconstructor(private userService: UserService) {}
}
الفرق: في الطريقة الأولى، كل مكون بيخلق instance جديد من UserService. في Angular، UserService بيكون Singleton (نسخة واحدة في كل التطبيق) وAngular بتمرره لكل المكونات اللي تطلبه.
⚡ إنشاء Service باستخدام Angular CLI
Angular CLI بتسهل إنشاء Services بنفس سهولة إنشاء المكونات. الأمر ng g s بينشئ Service مع Decorator @Injectable جاهز.
# إنشاء service جديد في مجلد services/
ng g s services/user
# النتيجة:
src/app/services/
└── user.service.ts // ملف Service واحد (مش 4 زي Component)
🔍 مثال: Service لإدارة المستخدمين
// user.service.ts - خدمة لإدارة بيانات المستخدمينimport { Injectable } from'@angular/core';
@Injectable({
// ✅ providedIn: 'root' = الخدمة متاحة في كل التطبيق// Angular بتنشئ نسخة واحدة (Singleton) وتعيد استخدامهاprovidedIn: 'root'
})
export classUserService {
// بيانات محلية (للتجربة - في الواقع هتجيب من API)private users = [
{ id: 1, name: 'أحمد', email: 'ahmad@example.com', role: 'admin' },
{ id: 2, name: 'فاطمة', email: 'fatima@example.com', role: 'user' },
{ id: 3, name: 'علي', email: 'ali@example.com', role: 'user' }
];
// 📋 الحصول على جميع المستخدمينgetUsers() {
returnthis.users;
}
// 🔍 الحصول على مستخدم واحد حسب IDgetUser(id: number) {
returnthis.users.find(u => u.id === id);
}
// ➕ إضافة مستخدم جديدaddUser(user: any) {
const newId = Math.max(...this.users.map(u => u.id)) + 1;
this.users.push({ ...user, id: newId });
}
// 🗑️ حذف مستخدمdeleteUser(id: number) {
this.users = this.users.filter(u => u.id !== id);
}
// ✏️ تحديث مستخدمupdateUser(id: number, updatedData: any) {
const index = this.users.findIndex(u => u.id === id);
if (index !== -1) {
this.users[index] = { ...this.users[index], ...updatedData };
}
}
}
📌 معنى @Injectable({ providedIn: 'root' }):
providedIn: 'root' = الخدمة متاحة في كل التطبيق (Global)
Angular بتنشئ نسخة واحدة (Singleton) وتعيد استخدامها
لو غيرتها لـ providedIn: SomeModule، هتكون متاحة في الموديول ده بس
بدون providedIn، لازم تسجلها يدوياً في providers array في Module
🔌 استخدام Service في Component
عشان تستخدم Service في Component، بتمرره في Constructor. Angular بتشوف الـ Type في Constructor وبتبحث عن Service متاح من نفس النوع. لو لقتها، بتحقنها تلقائياً.
<!-- user-list.component.html --><divclass="user-list-container"><h2>👥 قائمة المستخدمين</h2><div *ngIf="loading"class="loading">
⏳ جاري التحميل...
</div><table *ngIf="!loading"class="user-table"><thead><tr><th>ID</th><th>الاسم</th><th>البريد</th><th>الدور</th><th>إجراءات</th></tr></thead><tbody><tr *ngFor="let user of users"><td>{{ user.id }}</td><td>{{ user.name }}</td><td>{{ user.email }}</td><td><span [ngClass]="user.role">{{ user.role }}</span></td><td><button (click)="removeUser(user.id)"class="btn-danger">
🗑️ حذف
</button></td></tr></tbody></table><button (click)="addNewUser()"class="btn-primary">
➕ إضافة مستخدم
</button></div>
🌐 Service مع API Calls - HttpClient
في التطبيقات الحقيقية، البيانات مش بتكون محلية - بتيجي من Backend API. Angular بتوفّر HttpClient (جزء من @angular/common/http) عشان تتعامل مع HTTP Requests.
HttpClient بيرجع Observable بدل Promise. الفرق إن Observable أكثر قوة - بتديك إمكانية إلغاء الـ Request، إعادة المحاولة، ودمج أكتر من Request مع بعض.
🔍 الخطوة 1: إعداد HttpClient
لازم تضيف HttpClientModule في app.module.ts أولاً:
<!-- user-api.component.html --><divclass="api-demo"><h2>🌐 بيانات من API حقيقي</h2><!-- حالة التحميل --><div *ngIf="loading"class="loading-spinner">
⏳ جاري جلب البيانات...
</div><!-- رسالة خطأ --><div *ngIf="error"class="error-message">
❌ {{ error }}
<button (click)="loadUsers()">إعادة المحاولة</button></div><!-- عرض البيانات --><div *ngIf="!loading && !error"><divclass="user-cards"><div *ngFor="let user of users"class="user-card"><h3>{{ user.name }}</h3><p>📧 {{ user.email }}</p><p>🏢 {{ user.company?.name }}</p></div></div><button (click)="addUser()"class="btn-primary">
➕ إضافة مستخدم
</button></div></div>
🎯 فوائد استخدام Services في Angular
الفائدة
الشرح
مثال عملي
إعادة الاستخدام
نفس Service لعدة مكونات
UserService في قائمة المستخدمين + صفحة التفاصيل + لوحة التحكم
فصل المسؤوليات
Component للعرض، Service للبيانات
Component بيعرض الجدول، Service بيجلب البيانات من API
سهولة الاختبار
اختبار Service بشكل منفصل
Unit Test لـ ApiService من غير Component
إدارة مركزية
مكان واحد لمنطق العمل
تغيير API URL في Service واحد بدل 10 مكونات
الأداء
Singleton = نسخة واحدة
10 مكونات بتشارك نفس الـ Service مش 10 instances
⚠️ أخطاء شائعة في Services وتجنبها
❌ الخطأ 1: نسيان إلغاء الاشتراكات
المشكلة: Observable بيفضل شغال بعد ما المكون يتحذف → Memory Leak.
الحل: استخدم takeUntil مع Subject في ngOnDestroy، أو استخدم async pipe في Template.
❌ الخطأ 2: وضع منطق العرض في Service
المشكلة: Service بيعمل عمل Component (مثلاً يتعامل مع DOM مباشرة).
الحل: Service للبيانات والمنطق، Component للعرض والتفاعل. فصل واضح.
❌ الخطأ 3: عدم معالجة الأخطاء في HTTP
المشكلة: لو الـ API وقع، التطبيق هيقف أو هيدي خطأ غير واضح للمستخدم.
الحل: استخدم catchError في الـ pipe واعرض رسالة واضحة للمستخدم.
💡 معايير احترافية في استخدام Services
ضع منطق العمل في Services ليس في Components: Component بس للعرض والتفاعل.
استخدم @Injectable({ providedIn: 'root' }) افتراضياً: سهل وبيضمن Singleton.
استخدم Observable مع HttpClient: مش Promises، عشان تستفيد من قوة RxJS.
تذكر دائماً الاشتراك بـ Observable:.subscribe() إجباري، وإلغاء الاشتراك إجباري.
استخدم async pipe في Template لو أمكن: بيُلغي الاشتراك تلقائياً.
فصّل Services حسب المجال: UserService, ProductService, AuthService - مش Service واحد كبير.
استخدم Interfaces للبيانات: بدل any، عرّف interface User { id: number; name: string; }.
📚 دروس ذات صلة
❓ أسئلة شائعة عن Angular Services
هل يمكن استخدام Service في Service تاني؟
نعم، وده شائع جداً! مثلاً AuthService ممكن يستخدم ApiService عشان يرسل طلبات تسجيل الدخول. بس حذاري من Circular Dependency (Service A يستخدم B و B يستخدم A) - ده بيسبب خطأ في Angular.
ما الفرق بين providedIn: 'root' و providers في Module؟
providedIn: 'root' = الخدمة متاحة في كل التطبيق وبتُحمل عند أول استخدام (Tree-shakable). providers في Module = الخدمة متاحة في الموديول ده وبتوابعه. لو حطيتها في providers في AppModule، نفس النتيجة لكنها مش Tree-shakable.
هل Observable أفضل من Promise في Angular؟
في Angular، نعم. Observable أقوى لأنها بتديك: إلغاء الـ Request، إعادة المحاولة (retry)، دمج أكتر من Request (merge)، وتحويل البيانات (map). Promise أبسط لكنها أقل مرونة. Angular بيتكامل بشكل طبيعي مع RxJS/Observable.
إزاي أشارك بيانات بين مكونات مش قريبة من بعض؟
استخدم BehaviorSubject في Service. BehaviorSubject هو نوع من Observable بيحفظ آخر قيمة ويبعتها لأي مشترك جديد. مثلاً private userData = new BehaviorSubject<User>(null); - أي مكون يشترك هياخد آخر قيمة أو null.