| مستوى التكامل المعماري | الأسلوب التقليدي (عبر RxJS Observables) | الأسلوب الحديث (باستخدام Signals) |
|---|---|---|
| قراءة قيمة الحقل لحظياً | الاشتراك في `valueChanges` واستخدام متغير محلي لتخزين القيمة | تحويل تدفق القيم مباشرة إلى إشارة عبر `toSignal(form.valueChanges)` |
| إدارة دورة الحياة والاشتراكات | تتطلب يدوياً استخدام `takeUntil` أو اشتراكات متداخلة لمنع تسرب الذاكرة | تلقائية بالكامل (`Automatic Cleanup`) دون الحاجة لأي اشتراكات يدوية في المكون |
| التعامل داخل قالب العرض (HTML) | استخدام أنابيب الاستماع مثل `| async` مع كل حقل أو تدفق | استدعاء مباشر لإشارة القيمة (`formValue()`) بدون أنابيب أو تعقيد |
يمثل دمج النماذج التفاعلية (Reactive Forms) مع الإشارات الحديثة (Signals) النقلة النوعية الأبرز نحو بناء تطبيقات عالية الأداء وذات تفاعل تصريحي بحت. يتيح لك هذا الدمج الاستفادة من قوة وصلاحيات FormGroup و FormControl في إدارة وتدقيق البيانات، مع نقل حالة القيم والمخرجات بسلاسة إلى نظام الإشارات الحديث عبر أدوات التحويل المدمجة مثل toSignal و toObservable، مما يلغي الحاجة لاشتراكات RxJS المعقدة في قوالب العرض.
يوفر أنگولر أدوات مساعدة رسمية (@angular/core/rxjs-interop) تتيح لك تحويل أي Observable صادر عن النماذج (مثل valueChanges أو statusChanges) إلى Signal مقروءة بوضوح، والعكس صحيح عند الحاجة لتمرير الإشارات كتدفقات.
باستخدام toSignal، تصبح حالة وحجم وقيم النموذج متاحة كمقادير إشارية يمكن قراءتها مباشرة في قوالب الـ HTML أو داخل الحوسبة المتقدمة (computed) دون الحاجة لاستخدام | async المتكررة.
| 1 | import { Component, OnInit, computed } from '@angular/core'; |
| 2 | import { CommonModule } from '@angular/common'; |
| 3 | import { ReactiveFormsModule, FormBuilder, FormGroup, Validators } from '@angular/forms'; |
| 4 | import { toSignal } from '@angular/core/rxjs-interop'; |
| 5 | |
| 6 | @Component({ |
| 7 | selector: 'app-form-signals-bridge', |
| 8 | standalone: true, |
| 9 | imports: [CommonModule, ReactiveFormsModule], |
| 10 | template: ` |
| 11 | <div class="signals-bridge-card"> |
| 12 | <h3>هندسة ربط النماذج التفاعلية مع الإشارات الحديثة (Signals)</h3> |
| 13 | |
| 14 | <form [formGroup]="productOrderForm" class="form-container"> |
| 15 | |
| 16 | <!-- حقل كمية المنتجات --> |
| 17 | <div class="field-block"> |
| 18 | <label>الكمية المطلوبة:</label> |
| 19 | <input type="number" formControlName="quantity" class="styled-input-field" /> |
| 20 | </div> |
| 21 | |
| 22 | <!-- حقل سعر الوحدة --> |
| 23 | <div class="field-block"> |
| 24 | <label>سعر الوحدة (دولار):</label> |
| 25 | <input type="number" formControlName="unitPrice" class="styled-input-field" /> |
| 26 | </div> |
| 27 | |
| 28 | <!-- عرض الحسابات التراكمية الفورية باستخدام Signals و computed --> |
| 29 | <div class="live-calculation-box"> |
| 30 | <h4>الحساب التراكمي الفوري (مدعوم بالإشارات):</h4> |
| 31 | <p><strong>إجمالي السعر المحسوب:</strong> \${{ calculatedTotalPrice() }}</p> |
| 32 | <p><strong>حالة صلاحية النموذج:</strong> <span [class.valid-badge]="isFormValid()" [class.invalid-badge]="!isFormValid()">{{ isFormValid() ? 'صالح للاعتماد' : 'غير مكتمل' }}</span></p> |
| 33 | </div> |
| 34 | </form> |
| 35 | </div> |
| 36 | `, |
| 37 | styles: [` |
| 38 | .signals-bridge-card { background: #090d16; color: #fff; padding: 24px; border-radius: 12px; border: 1px solid #1e293b; } |
| 39 | .form-container { display: flex; flex-direction: column; gap: 16px; } |
| 40 | .field-block { display: flex; flex-direction: column; gap: 6px; } |
| 41 | .styled-input-field { background: #1e293b; border: 1px solid #334155; padding: 10px; border-radius: 6px; color: #fff; } |
| 42 | .live-calculation-box { background: #1e293b; border: 1px solid #334155; padding: 16px; border-radius: 8px; font-size: 14px; } |
| 43 | .live-calculation-box p { margin: 6px 0; color: #94a3b8; } |
| 44 | .live-calculation-box strong { color: #38bdf8; } |
| 45 | .valid-badge { color: #10b981; font-weight: bold; } |
| 46 | .invalid-badge { color: #f43f5e; font-weight: bold; } |
| 47 | `] |
| 48 | }) |
| 49 | export class FormSignalsBridgeComponent implements OnInit { |
| 50 | productOrderForm!: FormGroup; |
| 51 | |
| 52 | // تحويل تدفقات القيم والحالة إلى إشارات حديثة باستخدام toSignal |
| 53 | formValuesSignal: any; |
| 54 | formStatusSignal: any; |
| 55 | |
| 56 | // حوسبة إشارية لحساب السعر الإجمالي فور تغير القيم دون اشتراكات يدوية |
| 57 | calculatedTotalPrice = computed(() => { |
| 58 | const values = this.formValuesSignal(); |
| 59 | const qty = values?.quantity || 0; |
| 60 | const price = values?.unitPrice || 0; |
| 61 | return qty * price; |
| 62 | }); |
| 63 | |
| 64 | // حوسبة إشارية لمعرفة صلاحية النموذج |
| 65 | isFormValid = computed(() => { |
| 66 | return this.formStatusSignal() === 'VALID'; |
| 67 | }); |
| 68 | |
| 69 | constructor(private fb: FormBuilder) {} |
| 70 | |
| 71 | ngOnInit(): void { |
| 72 | // بناء هيكل النموذج التفاعلي |
| 73 | this.productOrderForm = this.fb.group({ |
| 74 | quantity: [1, [Validators.required, Validators.min(1)]], |
| 75 | unitPrice: [10, [Validators.required, Validators.min(0.1)]] |
| 76 | }); |
| 77 | |
| 78 | // ربط الـ valueChanges والـ statusChanges بالإشارات عبر rxjs-interop |
| 79 | this.formValuesSignal = toSignal(this.productOrderForm.valueChanges, { |
| 80 | initialValue: this.productOrderForm.value |
| 81 | }); |
| 82 | |
| 83 | this.formStatusSignal = toSignal(this.productOrderForm.statusChanges, { |
| 84 | initialValue: this.productOrderForm.status |
| 85 | }); |
| 86 | } |
| 87 | } |
⚠️ تحذير
لا تنسَ تمرير قيمة أولية (initialValue) عند استخدام toSignal مع تدفقات النماذج! بما أن valueChanges لا يصدر قيمة فورية عند الإنشاء إلا بعد أول تغيير، فإن تحديد initialValue يمنع وقوع أخطاء undefined في الحسابات الإشارية المبكرة (computed).