| وجه المقارنة المعمارية | المخازن التقليدية (Classic NgRx / Services) | مخازن NgRx SignalStore مع الميزات المتقدمة |
|---|---|---|
| مرونة التوسعة وإعادة الاستخدام | تتطلب تكرار الأكواد (Boilerplate) عند محاولة مشاركة منطق الحالة بين عدة خدمات | تعتمد على ميزات مخصصة قابلة للتركيب (`Feature Composition`) بكل سهولة |
| استهلاك الذاكرة والأداء | تعتمد على تدفقات وإشتراكات قد تستهلك موارد إضافية إن لم تُدار بحذر | مصممة خصيصاً للإشارات (`Signals`) لتوفير أداء فائق وتحديث فوري للمكونات |
| نظافة الكود وفصل المسؤوليات | تميل لتضخم ملفات الخدمة أو المخزن مع كبر حجم التطبيق | تفصل منطق التحميل، التخزين المؤقت، والعمليات في ميزات مستقلة ونظيفة |
يمثل مكتب NgRx SignalStore الطريقة الأكثر مرونة وقوة لإدارة الحالة المركزية بفضل تصميمه القائم على الميزات القابلة لإعادة الاستخدام (Custom Features). يتيح لك هذا النهج هندسة مخازن بيانات نظيفة، مفصولة، وقابلة للتوسعة دون تعقيدات البرمجة الكلاسيكية.
يتيح لك SignalStore تجميع الوظائف المختلفة (مثل ميزة التخزين المؤقت، ميزة البحث، أو ميزة التصفية) في وحدة تخزين مركزية واحدة عبر دالة withFeatures أو الميزات المخصصة، مما يرفع كفاءة إعادة استخدام الكود.
تضمن الميزات المتقدمة بقاء الحالة محمية وغير قابلة للتعديل العشوائي من خارج المتجر، مع توفير إشارات مشتقة جاهزة للاستخدام المباشر في المكونات.
⚠️ تحذير
تجنب تعديل حالة المتجر خارج الدوال المعتمدة (patchState)! محاولة تعديل بيانات المتجر بصورة مباشرة خارج الطرق المخصصة تفقد النظام ميزة تتبع التغيرات وتؤدي إلى سلوكيات غير متوقعة في واجهة المستخدم. احرص دائماً على حصر التعديلات داخل دوال المتجر.
| 1 | import { signalStore, withState, withMethods, patchState, withComputed } from '@ngrx/signals'; |
| 2 | import { computed } from '@angular/core'; |
| 3 | |
| 4 | interface ProductState { |
| 5 | products: string[]; |
| 6 | isLoading: boolean; |
| 7 | filter: string; |
| 8 | } |
| 9 | |
| 10 | const initialState: ProductState = { |
| 11 | products: ['منتج هندسي متقدم 1', 'نظام إدارة الإشارات 2', 'حزمة أنگولر المعمارية'], |
| 12 | isLoading: false, |
| 13 | filter: '' |
| 14 | }; |
| 15 | |
| 16 | export const ProductStore = signalStore( |
| 17 | { providedIn: 'root' }, |
| 18 | withState(initialState), |
| 19 | withComputed((store) => ({ |
| 20 | filteredProducts: computed(() => |
| 21 | store.products().filter(p => p.toLowerCase().includes(store.filter().toLowerCase())) |
| 22 | ) |
| 23 | })), |
| 24 | withMethods((store) => ({ |
| 25 | updateFilter(filter: string) { |
| 26 | patchState(store, { filter }); |
| 27 | }, |
| 28 | addProduct(product: string) { |
| 29 | patchState(store, (state) => ({ products: [...state.products, product] })); |
| 30 | } |
| 31 | })) |
| 32 | ); |
| 1 | <!-- product-list.component.html --> |
| 2 | <div class="store-container"> |
| 3 | <h3>لوحة إدارة المتاجر المتقدمة (NgRx SignalStore)</h3> |
| 4 | |
| 5 | <input |
| 6 | type="text" |
| 7 | (input)="onSearch($event)" |
| 8 | placeholder="ابحث في المنتجات المتاحة..." |
| 9 | class="store-input" |
| 10 | /> |
| 11 | |
| 12 | <ul class="product-list"> |
| 13 | @for (product of store.filteredProducts(); track $index) { |
| 14 | <li>{{ product }}</li> |
| 15 | } @empty { |
| 16 | <li>لا توجد منتجات تطابق بحثك</li> |
| 17 | } |
| 18 | </ul> |
| 19 | |
| 20 | <button (click)="addNewProduct()" class="store-btn">إضافة منتج جديد للمتجر</button> |
| 21 | </div> |
| 1 | /* product-list.component.css */ |
| 2 | .store-container { |
| 3 | background: #090d16; |
| 4 | color: #fff; |
| 5 | padding: 24px; |
| 6 | border-radius: 12px; |
| 7 | border: 1px solid #1e293b; |
| 8 | } |
| 9 | |
| 10 | .store-input { |
| 11 | width: 100%; |
| 12 | padding: 10px; |
| 13 | background: #0f172a; |
| 14 | border: 1px solid #475569; |
| 15 | color: #fff; |
| 16 | border-radius: 6px; |
| 17 | outline: none; |
| 18 | margin-bottom: 16px; |
| 19 | } |
| 20 | |
| 21 | .store-input:focus { |
| 22 | border-color: #38bdf8; |
| 23 | } |
| 24 | |
| 25 | .product-list { |
| 26 | list-style: none; |
| 27 | padding: 0; |
| 28 | margin: 0 0 16px 0; |
| 29 | } |
| 30 | |
| 31 | .product-list li { |
| 32 | background: #1e293b; |
| 33 | padding: 10px 14px; |
| 34 | border-radius: 6px; |
| 35 | margin-bottom: 8px; |
| 36 | border: 1px solid #334155; |
| 37 | color: #38bdf8; |
| 38 | } |
| 39 | |
| 40 | .store-btn { |
| 41 | background: #38bdf8; |
| 42 | color: #090d16; |
| 43 | border: none; |
| 44 | padding: 10px 20px; |
| 45 | border-radius: 6px; |
| 46 | font-weight: bold; |
| 47 | cursor: pointer; |
| 48 | } |
| 1 | import { Component, inject } from '@angular/core'; |
| 2 | import { CommonModule } from '@angular/common'; |
| 3 | import { ProductStore } from './product-store'; |
| 4 | |
| 5 | @Component({ |
| 6 | selector: 'app-product-list', |
| 7 | standalone: true, |
| 8 | imports: [CommonModule], |
| 9 | templateUrl: './product-list.component.html', |
| 10 | styleUrls: ['./product-list.component.css'] |
| 11 | }) |
| 12 | export class ProductListComponent { |
| 13 | readonly store = inject(ProductStore); |
| 14 | |
| 15 | onSearch(event: Event) { |
| 16 | const value = (event.target as HTMLInputElement).value; |
| 17 | this.store.updateFilter(value); |
| 18 | } |
| 19 | |
| 20 | addNewProduct() { |
| 21 | this.store.addProduct(`منتج هندسي جديد رقم ${this.store.products().length + 1}`); |
| 22 | } |
| 23 | } |