| وجه المقارنة المعمارية | استخدام الدوال التقليدية (Standard Methods) | استخدام الميزة المتقدمة (rxMethod) |
|---|---|---|
| التعامل مع تدفقات الشبكة (RxJS) | يتطلب اشتراكات يدوية معقدة داخل الخدمات أو المكونات وإدارة فوضوية للإلغاء | يتيح ربط تدفقات RxJS مباشرة بالإشارات وإدارة الاشتراكات تلقائياً |
| التحكم في السباق الزمني للطلبات (Race Conditions) | صعب الإدارة ويحتاج لعوامل إضافية يدوية مثل `switchMap` أو `exhaustMap` | يوفر تحكماً مدمجاً وسلساً عبر عوامل RxJS لمعالجة التداخل في الطلبات المتكررة |
| نظافة الكود وقابلية الصيانة | يميل لتضخم الأكواد وصعوبة تتبع أخطاء الاتصال بالخادم | يفصل منطق الاتصال بوضوح ويحافظ على معمارية موحدة ونظيفة داخل المتجر |
لا يقتصر تخزين الحالة على حفظ البيانات محلياً فحسب، بل يمتد ليشمل تنفيذ العمليات الجانبية (Side Effects) مثل جلب البيانات من الخوادم، ومعالجة الأخطاء، وإدارة حالة التحميل بكفاءة عالية داخل مخازن NgRx SignalStore. يتيح لك المكون الإضافي المتقدم rxMethod دمج تدفقات RxJS مع الإشارات بطريقة هندسية نظيفة وخالية من التعقيد.
تعتبر rxMethod الأداة المعمارية الأساسية لتنفيذ العمليات غير المتزامنة داخل المتجر، حيث تتيح لك استقبال تدفق من القيم أو الإشارات وتنفيذ طلبات الشبكة مع تطبيق عوامل RxJS بمرونة مطلقة.
فصل خطوات تحديث حالة المتجر (مثل تفعيل حالة التحميل isLoading: true ثم تحديث البيانات عند النجاح أو إظهار الخطأ عند الفشل) داخل العمليات الجانبية يضمن استقرار التطبيق وتزامن واجهة المستخدم بدقة.
⚠️ تحذير
تجنب إطلاق طلبات شبكة مباشرة داخل الدوال العادية للمتجر! وضع طلبات الشبكة في دوال عادية (Standard Methods) يفقدك القدرة على معالجة السباق الزمني للطلبات (Race Conditions) وإدارة الاشتراكات بصورة صحيحة. احرص دائماً على توظيف rxMethod لهكذا مهام.
| 1 | import { signalStore, withState, withMethods, patchState } from '@ngrx/signals'; |
| 2 | import { rxMethod } from '@ngrx/signals/rxjs-interop'; |
| 3 | import { pipe, switchMap, tap, catchError, of, delay } from 'rxjs'; |
| 4 | import { inject } from '@angular/core'; |
| 5 | |
| 6 | interface UserState { |
| 7 | users: string[]; |
| 8 | isLoading: boolean; |
| 9 | error: string | null; |
| 10 | } |
| 11 | |
| 12 | const initialState: UserState = { |
| 13 | users: [], |
| 14 | isLoading: false, |
| 15 | error: null |
| 16 | }; |
| 17 | |
| 18 | export const UserStore = signalStore( |
| 19 | { providedIn: 'root' }, |
| 20 | withState(initialState), |
| 21 | withMethods((store) => ({ |
| 22 | // تنفيذ العملية الجانبية لجلب البيانات من الخادم باستخدام rxMethod |
| 23 | loadUsers: rxMethod<void>( |
| 24 | pipe( |
| 25 | tap(() => patchState(store, { isLoading: true, error: null })), |
| 26 | switchMap(() => { |
| 27 | // محاكاة طلب شبكة غير متزامن |
| 28 | return of(['أحمد هندسي', 'سارة المعمارية', 'محمد المتقدم']).pipe( |
| 29 | delay(1200), |
| 30 | tap((users) => patchState(store, { users, isLoading: false })), |
| 31 | catchError((err) => { |
| 32 | patchState(store, { isLoading: false, error: 'فشل في جلب البيانات من الخادم' }); |
| 33 | return of([]); |
| 34 | }) |
| 35 | ); |
| 36 | }) |
| 37 | ) |
| 38 | ) |
| 39 | })) |
| 40 | ); |
| 1 | <!-- user-management.component.html --> |
| 2 | <div class="side-effect-container"> |
| 3 | <h3>لوحة إدارة العمليات الجانبية والاتصال بالخادم (NgRx)</h3> |
| 4 | |
| 5 | <button (click)="store.loadUsers()" [disabled]="store.isLoading()" class="action-btn"> |
| 6 | {{ store.isLoading() ? 'جاري الاتصال بالخادم...' : 'جلب قائمة المستخدمين' }} |
| 7 | </button> |
| 8 | |
| 9 | @if (store.isLoading()) { |
| 10 | <div class="loading-state"> |
| 11 | <p>⏳ يتم الآن جلب البيانات ومعالجة الطلب...</p> |
| 12 | </div> |
| 13 | } |
| 14 | |
| 15 | @if (store.error()) { |
| 16 | <div class="error-state"> |
| 17 | <p>❌ خطأ: {{ store.error() }}</p> |
| 18 | </div> |
| 19 | } |
| 20 | |
| 21 | <ul class="user-list"> |
| 22 | @for (user of store.users(); track $index) { |
| 23 | <li>{{ user }}</li> |
| 24 | } @empty { |
| 25 | @if (!store.isLoading()) { |
| 26 | <li>لا توجد بيانات متاحة حالياً، اضغط على الزر لجلب البيانات</li> |
| 27 | } |
| 28 | } |
| 29 | </ul> |
| 30 | </div> |