← الدرس السابق المستوى 4 - الدرس 8 من 12

⚙️ Angular Services و Dependency Injection: إدارة البيانات والـ API باحترافية

تقدمك في المستوى الرابع 66%

📦 لماذا نحتاج Services في Angular؟

في الدرس السابق تعلمنا كيفية بناء مكونات Angular ونقل البيانات بينها باستخدام @Input و @Output. لكن في تطبيق حقيقي، البيانات مش بتكون ثابتة - لازم نجلبها من API، نحفظها في قاعدة بيانات، أو نشاركها بين عدة مكونات. هنا بييجي دور Services.

Service في Angular هو فئة TypeScript بتعمل مهمة محددة: جلب بيانات، إدارة حالة، أو تنفيذ عمليات حسابية. الميزة إنها قابلة للمشاركة بين أكتر من مكون، وبتستخدم Dependency Injection (DI) عشان Angular "تحقن" الخدمة في أي مكون يحتاجها.

بنهاية هذا الدرس، هتكون قادر على:

💡 مثال من الحياة العملية: تخيّل إنك بتبني تطبيق متجر إلكتروني. عندك 10 مكونات مختلفة (قائمة منتجات، صفحة تفاصيل، سلة التسوق، إلخ). كلهم محتاجين نفس البيانات (قائمة المنتجات). بدل ما تكتب كود جلب المنتجات 10 مرات، تكتبه مرة واحدة في ProductService وتستخدمه في كل المكونات.

💡 ما هي Dependency Injection (DI)؟

Dependency Injection هو نمط تصميم (Design Pattern) بيسمح للمكونات "تطلب" الخدمات اللي تحتاجها بدل ما تخلقها بنفسها. Angular بتدير إنشاء وإعادة استخدام الكائنات تلقائياً.

بدل ما تكتب:

// ❌ الطريقة القديمة (بدون DI) export class UserComponent { // بننشئ instance جديد في كل مكون - ده بيستهلك موارد userService = new UserService(); }

في Angular بتكتب:

// ✅ الطريقة الحديثة (مع DI) export class UserComponent { // Angular بتمرر instance جاهز - Singleton constructor(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 class UserService { // بيانات محلية (للتجربة - في الواقع هتجيب من 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() { return this.users; } // 🔍 الحصول على مستخدم واحد حسب ID getUser(id: number) { return this.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.ts - استخدام UserService import { Component, OnInit } from '@angular/core'; import { UserService } from '../services/user.service'; @Component({ selector: 'app-user-list', templateUrl: './user-list.component.html', styleUrls: ['./user-list.component.css'] }) export class UserListComponent implements OnInit { users: any[] = []; loading = true; // ✅ حقن UserService في Constructor // Angular بتمرر instance جاهز تلقائياً constructor(private userService: UserService) {} // ✅ جلب البيانات لما المكون يتحمل ngOnInit() { this.users = this.userService.getUsers(); this.loading = false; } // ➕ إضافة مستخدم جديد addNewUser() { const newUser = { name: 'علي', email: 'ali@example.com', role: 'user' }; this.userService.addUser(newUser); // تحديث القائمة بعد الإضافة this.users = this.userService.getUsers(); } // 🗑️ حذف مستخدم removeUser(id: number) { this.userService.deleteUser(id); this.users = this.userService.getUsers(); } }
<!-- user-list.component.html --> <div class="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 أولاً:

// app.module.ts import { HttpClientModule } from '@angular/common/http'; @NgModule({ imports: [ // ... imports أخرى HttpClientModule // ✅ إضافة HttpClientModule ], // ... }) export class AppModule { }

🔍 الخطوة 2: إنشاء ApiService

// api.service.ts - خدمة للتعامل مع API import { Injectable } from '@angular/core'; import { HttpClient, HttpErrorResponse } from '@angular/common/http'; import { Observable, throwError } from 'rxjs'; import { catchError, retry } from 'rxjs/operators'; @Injectable({ providedIn: 'root' }) export class ApiService { // URL الأساسي للـ API private apiUrl = 'https://jsonplaceholder.typicode.com'; // ✅ حقن HttpClient في Constructor constructor(private http: HttpClient) {} // 📋 GET - جلب كل المستخدمين getUsers(): Observable<any[]> { return this.http.get<any[]>(this.apiUrl + '/users') .pipe( retry(2), // إعادة المحاولة مرتين لو فشل catchError(this.handleError) // معالجة الأخطاء ); } // 🔍 GET - جلب مستخدم واحد getUser(id: number): Observable<any> { return this.http.get(`${this.apiUrl}/users/${id}`) .pipe(catchError(this.handleError)); } // ➕ POST - إضافة مستخدم جديد createUser(user: any): Observable<any> { return this.http.post(this.apiUrl + '/users', user) .pipe(catchError(this.handleError)); } // ✏️ PUT - تحديث مستخدم updateUser(id: number, user: any): Observable<any> { return this.http.put(`${this.apiUrl}/users/${id}`, user) .pipe(catchError(this.handleError)); } // 🗑️ DELETE - حذف مستخدم deleteUser(id: number): Observable<any> { return this.http.delete(`${this.apiUrl}/users/${id}`) .pipe(catchError(this.handleError)); } // ⚠️ معالجة الأخطاء private handleError(error: HttpErrorResponse) { let errorMessage = 'حدث خطأ غير معروف'; if (error.error instanceof ErrorEvent) { // خطأ من جانب العميل (شبكة، إلخ) errorMessage = `خطأ: ${error.error.message}`; } else { // خطأ من جانب السيرفر errorMessage = `خطأ ${error.status}: ${error.message}`; } console.error(errorMessage); return throwError(() => new Error(errorMessage)); } }

🔍 الخطوة 3: استخدام ApiService في Component

// user-api.component.ts - جلب بيانات من API حقيقي import { Component, OnInit, OnDestroy } from '@angular/core'; import { Subject } from 'rxjs'; import { takeUntil } from 'rxjs/operators'; import { ApiService } from '../services/api.service'; @Component({ selector: 'app-user-api', templateUrl: './user-api.component.html' }) export class UserApiComponent implements OnInit, OnDestroy { users: any[] = []; loading = true; error = ''; // ✅ Subject للتنظيف الآمن private destroy$ = new Subject<void>(); constructor(private apiService: ApiService) {} ngOnInit() { this.loadUsers(); } loadUsers() { this.loading = true; this.error = ''; this.apiService.getUsers() .pipe( takeUntil(this.destroy$) // ✅ تنظيف تلقائي ) .subscribe({ next: (data) => { this.users = data; this.loading = false; }, error: (err) => { this.error = 'فشل في جلب البيانات. حاول مرة أخرى.'; this.loading = false; } }); } addUser() { const newUser = { name: 'مستخدم جديد', email: 'new@example.com' }; this.apiService.createUser(newUser) .pipe(takeUntil(this.destroy$)) .subscribe({ next: () => { this.loadUsers(); // إعادة تحميل القائمة }, error: (err) => { this.error = 'فشل في إضافة المستخدم'; } }); } ngOnDestroy() { // ✅ تنظيف الاشتراكات عند حذف المكون this.destroy$.next(); this.destroy$.complete(); } }
<!-- user-api.component.html --> <div class="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"> <div class="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

📚 دروس ذات صلة

❓ أسئلة شائعة عن 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.

← الدرس السابق