| نمط التكوين المعماري | الخصائص والميزات الأساسية | متى يتم اعتماده هندسياً؟ |
|---|---|---|
| النمط الأساسي البسيط (Base SignalStore) | يكتفي بتعريف الحالة الأساسية (`withState`) ودوال التحديث المباشرة دون إضافات معقدة | عندما تكون الحالة صغرى ومباشرة ولا تتطلب عمليات حسابية أو معالجات غير متزامنة معقدة |
| نمط الخصائص المحسوبة (Computed & Features) | يستفيد من دمج `withComputed` لتصفية واشتقاق بيانات تفاعلية فورية بناءً على الحالة الحالية | حين تحتوي الواجهة على قوائم ضخمة تتطلب فلترة، بحثاً، أو حسابات مستمرة متأثرة بمدخلات المستخدم |
| نمط العمليات غير المتزامنة (rxMethod Extensions) | يوظف `rxMethod` لإدارة طلبات الشبكة (`HTTP Requests`) وتدفقات البيانات القادمة من الـ Observables | عندما يكون المتجر مسؤولاً عن جلب وتخزين بيانات حية من سيرفرات خارجية مع معالجة حالات التحميل والأخطاء |
في معمارية Angular 22.1 المتقدمة، عندما تتجاوز التطبيقات حاجة المتاجر المحلية المصغرة وتتطلب حلاً مركزياً قوياً وموثوقاً لإدارة الحالة، تبرز مكتبة NgRx Signals كمعيار ذهبي حديث. تم بناء هذه المكتبة خصيصاً لتتكامل بسلاسة التامة مع نظام الإشارات (Signals) في أنگولر، مقدمةً نمط SignalStore القائم على الوظائف (Functional Approach) والذي يتخلص من التعقيد والتصاميم التقليدية الثقيلة لمكتبات ريدوكس القديمة.
تعتمد المكتبة على مفهوم التركيب (Composition)؛ حيث يمكنك بناء المتجر عبر دمج ميزات جاهزة مثل حالة أساسية (withState)، خصائص محسوبة (withComputed), ودوال لتحديث البيانات (withMethods)، مما يمنح مرونة معمارية هائلة.
تتيح لك المكتبة دمج تدفقات البيانات القادمة من الـ Observables (مثل طلبات السيرفر HTTP) مباشرة داخل الإشارات بكل سهولة وبدون تعقيد الاشتراكات اليدوية.
⚠️ تحذير
تجنب استخدام المتاجر العامة لكل بيانات التطبيق الصغيرة! لا تقم بوضع كل حالات المكونات البسيطة داخل متجر مركزى عام (Global SignalStore). استخدم المتجر المركزي فقط للحالات المشتركة بين عدة صفحات أو ميزات رئيسية، بينما اترك البيانات الخاصة بكل مكون داخله أو داخل متجره المحلي المصغر للحفاظ على خفة وسرعة أداء التطبيق.
| 1 | import { signalStore, withState, withComputed, withMethods, patchState } from '@ngrx/signals'; |
| 2 | import { computed } from '@angular/core'; |
| 3 | |
| 4 | export interface UserState { |
| 5 | users: { id: number; name: string; role: string }[]; |
| 6 | isLoading: boolean; |
| 7 | searchTerm: string; |
| 8 | } |
| 9 | |
| 10 | const initialState: UserState = { |
| 11 | users: [ |
| 12 | { id: 1, name: 'أحمد المهندس', role: 'مدير النظام' }, |
| 13 | { id: 2, name: 'سارة المبرمجة', role: 'مطور واجهات' }, |
| 14 | { id: 3, name: 'محمود المعماري', role: 'مهندس برمجيات' } |
| 15 | ], |
| 16 | isLoading: false, |
| 17 | searchTerm: '' |
| 18 | }; |
| 19 | |
| 20 | export const UserStore = signalStore( |
| 21 | { providedIn: 'root' }, |
| 22 | withState(initialState), |
| 23 | withComputed((store) => ({ |
| 24 | filteredUsers: computed(() => { |
| 25 | const term = store.searchTerm().toLowerCase(); |
| 26 | return store.users().filter(u => u.name.toLowerCase().includes(term)); |
| 27 | }), |
| 28 | totalUsersCount: computed(() => store.users().length) |
| 29 | })), |
| 30 | withMethods((store) => ({ |
| 31 | updateSearchTerm(term: string) { |
| 32 | patchState(store, { searchTerm: term }); |
| 33 | }, |
| 34 | addUser(newUser: { id: number; name: string; role: string }) { |
| 35 | patchState(store, (state) => ({ users: [...state.users, newUser] })); |
| 36 | } |
| 37 | })) |
| 38 | ); |
| 1 | <!-- ngrx-store-demo.component.html --> |
| 2 | <div class="ngrx-container"> |
| 3 | <h3>إدارة الحالة المتقدمة باستخدام NgRx Signals</h3> |
| 4 | <p>إجمالي المستخدمين المسجلين في المتجر المركزي: <strong>{{ userStore.totalUsersCount() }}</strong></p> |
| 5 | |
| 6 | <div class="search-box"> |
| 7 | <input |
| 8 | type="text" |
| 9 | [value]="userStore.searchTerm()" |
| 10 | (input)="onSearchInput($event)" |
| 11 | placeholder="ابحث عن اسم المستخدم..." |
| 12 | class="search-input" |
| 13 | /> |
| 14 | </div> |
| 15 | |
| 16 | <div class="users-list"> |
| 17 | @for (user of userStore.filteredUsers(); track user.id) { |
| 18 | <div class="user-card"> |
| 19 | <h4>{{ user.name }}</h4> |
| 20 | <p>الدور المعماري: <span>{{ user.role }}</span></p> |
| 21 | </div> |
| 22 | } @empty { |
| 23 | <p class="empty-text">لا توجد نتائج مطابقة لبحثك.</p> |
| 24 | } |
| 25 | </div> |
| 26 | |
| 27 | <button (click)="addNewSystemUser()" class="add-btn">إضافة مستخدم جديد للنظام</button> |
| 28 | </div> |