| مشغل الدمج (`Operator`) | آلية العمل الهندسي في دمج التدفقات | متى يتم إصدار النتيجة لأول مرة |
|---|---|---|
| `forkJoin` | ينتظر حتى تكتمل كافة التدفقات تماماً ويصدر آخر قيمة من كل تدفق دفعة واحدة | عند اكتمال (`Complete`) جميع التدفقات المشاركة بنجاح |
| `combineLatest` | يصدر قيمة جديدة كلما أصدر أي تدفق من المصادر قيمة جديدة بشرط أن تكون كافة التدفقات قد أصدرت قيمة أولية واحدة على الأقل | فور إصدار أول قيمة من كل التدفقات المعنية |
| `zip` | يقوم بمزاوجة القيم حسب الترتيب والتسلسل الرقمي الدقيق (القيمة الأولى مع الأولى، الثانية مع الثانية) | عند توفر قيمة مقابلة من كل تدفق في نفس الترتيب التسلسلي |
لا تعمل التطبيقات الحقيقية على تدفق بيانات واحد منعزل، بل تتطلب غالباً دمج واستנتاج معلومات متزامنة من مصادر متعددة (مثل جلب بيانات المستخدم، الإعدادات، وقوائم الصلاحيات في نفس اللحظة). توفر RxJS مشغلات دمج قوية تمكنك من مزامنة التدفقات الزمنية بكفاءة تامة.
يُشبه تماماً دالة Promise.all في الجافاسكريبت؛ حيث يُستخدم لجلب عدة طلبات شبكية متوازية لا تبدأ واجهة المستخدم عملها إلا بعد توفر كافة البيانات دفعة واحدة.
مثالي لربط فلاتر البحث المتعددة (مثل البحث النصي مع اختيار الفئة وترتيب التاريخ)؛ حيث يعيد حساب النتيجة فور تغيير أي عنصر من هذه العناصر مع الاحتفاظ بآخر قيم صدرت من البقية.
| 1 | import { Component, OnInit } from '@angular/core'; |
| 2 | import { CommonModule } from '@angular/common'; |
| 3 | import { forkJoin, of, Subscription } from 'rxjs'; |
| 4 | import { delay } from 'rxjs/operators'; |
| 5 | |
| 6 | @Component({ |
| 7 | selector: 'app-forkjoin-dashboard-demo', |
| 8 | standalone: true, |
| 9 | imports: [CommonModule], |
| 10 | template: ` |
| 11 | <div class="combine-card"> |
| 12 | <h3>دمج التدفقات الموازية باستخدام forkJoin</h3> |
| 13 | <p><strong>بيانات المستخدم:</strong> {{ userData }}</p> |
| 14 | <p><strong>إعدادات النظام:</strong> {{ systemSettings }}</p> |
| 15 | </div> |
| 16 | `, |
| 17 | styles: [` |
| 18 | .combine-card { background: #090d16; color: #fff; padding: 24px; border-radius: 12px; border: 1px solid #1e293b; margin-bottom: 16px; } |
| 19 | p { color: #94a3b8; margin: 8px 0; } |
| 20 | strong { color: #38bdf8; } |
| 21 | `] |
| 22 | }) |
| 23 | export class ForkjoinDashboardDemoComponent implements OnInit { |
| 24 | userData: string = ''; |
| 25 | systemSettings: string = ''; |
| 26 | private sub!: Subscription; |
| 27 | |
| 28 | ngOnInit(): void { |
| 29 | // محاكاة طلبين شبكيين مستقلين بأزمنة تأخير مختلفة |
| 30 | const userStream$ = of('ملف المستخدم: أحمد').pipe(delay(1000)); |
| 31 | const settingsStream$ = of('الإعدادات: الوضع الليلي مفعل').pipe(delay(1500)); |
| 32 | |
| 33 | // دمج التدفقات وانتظار اكتمالها بالكامل |
| 34 | this.sub = forkJoin({ |
| 35 | user: userStream$, |
| 36 | settings: settingsStream$ |
| 37 | }).subscribe({ |
| 38 | next: (result) => { |
| 39 | this.userData = result.user; |
| 40 | this.systemSettings = result.settings; |
| 41 | console.log('تم اكتمال كافة الطلبات الموازية بنجاح:', result); |
| 42 | } |
| 43 | }); |
| 44 | } |
| 45 | |
| 46 | ngOnDestroy(): void { |
| 47 | if (this.sub) { |
| 48 | this.sub.unsubscribe(); |
| 49 | } |
| 50 | } |
| 51 | } |
| 1 | import { Component, OnInit } from '@angular/core'; |
| 2 | import { CommonModule } from '@angular/common'; |
| 3 | import { BehaviorSubject, combineLatest, Subscription } from 'rxjs'; |
| 4 | import { map } from 'rxjs/operators'; |
| 5 | |
| 6 | @Component({ |
| 7 | selector: 'app-combinelatest-filters-demo', |
| 8 | standalone: true, |
| 9 | imports: [CommonModule], |
| 10 | template: ` |
| 11 | <div class="combine-card"> |
| 12 | <h3>تحديث النتائج المتزامن باستخدام combineLatest</h3> |
| 13 | <p><strong>نتيجة دمج الفلاتر الحية:</strong> {{ searchSummary }}</p> |
| 14 | </div> |
| 15 | `, |
| 16 | styles: [` |
| 17 | .combine-card { background: #090d16; color: #fff; padding: 24px; border-radius: 12px; border: 1px solid #1e293b; } |
| 18 | p { color: #94a3b8; } |
| 19 | strong { color: #10b981; } |
| 20 | `] |
| 21 | }) |
| 22 | export class CombinelatestFiltersDemoComponent implements OnInit { |
| 23 | searchSummary: string = ''; |
| 24 | private searchKeyword$ = new BehaviorSubject<string>('أنجولر'); |
| 25 | private categoryFilter$ = new BehaviorSubject<string>('تطوير'); |
| 26 | private sub!: Subscription; |
| 27 | |
| 28 | ngOnInit(): void { |
| 29 | // دمج آخر قيم صادرة من كشاف الكلمات وفلتر التصنيف فور تغير أي منهما |
| 30 | this.sub = combineLatest([ |
| 31 | this.searchKeyword$, |
| 32 | this.categoryFilter$ |
| 33 | ]).pipe( |
| 34 | map(([keyword, category]) => `البحث عن: [${keyword}] داخل تصنيف: [${category}]`) |
| 35 | ).subscribe({ |
| 36 | next: (summary) => { |
| 37 | this.searchSummary = summary; |
| 38 | console.log('تحديث واجهة البحث الحية:', summary); |
| 39 | } |
| 40 | }); |
| 41 | } |
| 42 | |
| 43 | ngOnDestroy(): void { |
| 44 | if (this.sub) { |
| 45 | this.sub.unsubscribe(); |
| 46 | } |
| 47 | } |
| 48 | } |