| اسم المشغل (`Operator`) | دوره المعماري في الـ Pipeline | التأثير المباشر على البيانات المارة |
|---|---|---|
| `map` | تحويل كل قيمة صادرة من التدفق إلى شكل أو قيمة جديدة بناءً على منطق برمجي محدد | تغيير محتوى أو هيكل البيانات المارة عبر التدفق |
| `filter` | تمرير القيم التي تطابق شرطاً منطقياً معيناً فقط وحظر البقية | تقليص عدد القيم الصادرة ومنع وصول البيانات غير المرغوبة للمستهلك |
| `tap` | تنفيذ آثار جانبية (`Side Effects`) مثل التسجيل أو المراقبة دون تغيير البيانات | تمرير البيانات الأصلية كما هي تماماً للمشغلات التالية دون أي تعديل |
، لا يتم استخدام التدفقات الخام كما هي غالباً، بل يتم إخضاعها لعمليات معالجة، تنقية، وتحويل داخل خط الأنابيب (pipe). تعتبر المشغلات الأساسية map و filter و tap الأدوات الجوهرية لبناء تدفقات بيانات نظيفة، مفصولة المسؤوليات، وخالية من الآثار الجانبية غير المدروسة.
يستخدم لتحويل البيانات الخام (مثل استجابة خادم بصيغة JSON) إلى نماذج بيانات مهيكلة تناسب واجهة المستخدم مباشرة، مما يفصل منطق جلب البيانات عن منطق العرض.
يضمن عدم مرور أي بيانات لا تستوفي الشروط البرمجية (مثل القيم الفارغة أو غير الصالحة)، مما يحمي المكون من الأخطاء المفاجئة.
أداة ممتازة لتتبع وتصحيح الأخطاء (Debugging) أو طباعة القيم في وحدة التحكم (Console) دون العبث بمسار البيانات الأصلي.
| 1 | import { Component, OnInit } from '@angular/core'; |
| 2 | import { CommonModule } from '@angular/common'; |
| 3 | import { interval, Subscription } from 'rxjs'; |
| 4 | import { map, filter, tap, take } from 'rxjs/operators'; |
| 5 | |
| 6 | @Component({ |
| 7 | selector: 'app-core-operators-pipeline', |
| 8 | standalone: true, |
| 9 | imports: [CommonModule], |
| 10 | template: ` |
| 11 | <div class="operators-card"> |
| 12 | <h3>معالجة التدفقات باستخدام map و filter و tap</h3> |
| 13 | <p><strong>القيمة الأصلية للعداد:</strong> {{ rawValue }}</p> |
| 14 | <p><strong>القيمة النهائية بعد الفلترة والتحويل (الأزواج المربعة):</strong> {{ processedValue }}</p> |
| 15 | </div> |
| 16 | `, |
| 17 | styles: [` |
| 18 | .operators-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 CoreOperatorsPipelineComponent implements OnInit { |
| 24 | rawValue: number = 0; |
| 25 | processedValue: number = 0; |
| 26 | private sub!: Subscription; |
| 27 | |
| 28 | ngOnInit(): void { |
| 29 | // إنشاء تدفق زمني مستمر واختباره عبر المشغلات الأساسية |
| 30 | this.sub = interval(1000).pipe( |
| 31 | take(10), // تحديد أول 10 قيم فقط للاختبار |
| 32 | tap(val => { |
| 33 | this.rawValue = val; |
| 34 | console.log('[tap] القيمة الخام الواردة:', val); |
| 35 | }), |
| 36 | filter(val => val % 2 === 0), // الاحتفاظ بالأرقام الزوجية فقط وحظر الفردية |
| 37 | tap(val => console.log('[tap بعد الـ filter] الأرقام الزوجية المتبقية:', val)), |
| 38 | map(val => val * val) // تربيع الرقم الزوجي المتبقي |
| 39 | ).subscribe({ |
| 40 | next: (result) => { |
| 41 | this.processedValue = result; |
| 42 | console.log('[subscribe النهائي] القيمة المحولة:', result); |
| 43 | } |
| 44 | }); |
| 45 | } |
| 46 | |
| 47 | ngOnDestroy(): void { |
| 48 | if (this.sub) { |
| 49 | this.sub.unsubscribe(); |
| 50 | } |
| 51 | } |
| 52 | } |
⚠️ تحذير
تذكر أن ترتيب وضع المشغلات داخل الـ pipe يصنع الفارق الكلي في الأداء والنتيجة! وضع filter قبل map يقلل من عدد العمليات الحسابية أو التحويلات الثقيلة عبر تمرير العناصر المطابقة فقط، بينما وضع map أولاً قد يجبر النظام على معالجة كافة العناصر بلا استثناء ثم تصفيتها لاحقاً، وهو ما يضر بكفاءة التطبيق.
| 1 | import { Component, OnInit } from '@angular/core'; |
| 2 | import { CommonModule } from '@angular/common'; |
| 3 | import { of, Subscription } from 'rxjs'; |
| 4 | import { map, filter, tap } from 'rxjs/operators'; |
| 5 | |
| 6 | interface User { |
| 7 | id: number; |
| 8 | name: string; |
| 9 | isActive: boolean; |
| 10 | } |
| 11 | |
| 12 | @Component({ |
| 13 | selector: 'app-users-stream-filter', |
| 14 | standalone: true, |
| 15 | imports: [CommonModule], |
| 16 | template: ` |
| 17 | <div class="operators-card"> |
| 18 | <h3>تصفية وتحويل قائمة المستخدمين النشطين</h3> |
| 19 | <ul> |
| 20 | <li *ngFor="let user of activeUserNames">{{ user }}</li> |
| 21 | </ul> |
| 22 | </div> |
| 23 | `, |
| 24 | styles: [` |
| 25 | .operators-card { background: #090d16; color: #fff; padding: 24px; border-radius: 12px; border: 1px solid #1e293b; } |
| 26 | ul { color: #94a3b8; padding-right: 20px; } |
| 27 | li { color: #10b981; margin: 4px 0; font-weight: bold; } |
| 28 | `] |
| 29 | }) |
| 30 | export class UsersStreamFilterComponent implements OnInit { |
| 31 | activeUserNames: string[] = []; |
| 32 | private userSub!: Subscription; |
| 33 | |
| 34 | ngOnInit(): void { |
| 35 | const mockUsersList: User[] = [ |
| 36 | { id: 1, name: 'أحمد محمد', isActive: true }, |
| 37 | { id: 2, name: 'سارة خالد', isActive: false }, |
| 38 | { id: 3, name: 'محمود علي', isActive: true }, |
| 39 | { id: 4, name: 'فاطمة عمر', isActive: false } |
| 40 | ]; |
| 41 | |
| 42 | // استخدام from/of لمحاكاة تدفق بيانات المستخدمين |
| 43 | this.userSub = of(mockUsersList).pipe( |
| 44 | tap(users => console.log('إجمالي المستخدمين الخام:', users.length)), |
| 45 | // تحويل مصفوفة المستخدمين لفلترتها |
| 46 | map(users => users.filter(user => user.isActive)), |
| 47 | tap(activeUsers => console.log('عدد المستخدمين النشطين بعد الفلترة الداخلية:', activeUsers.length)), |
| 48 | // تحويل مصفوفة الكائنات إلى مصفوفة نصوص تمثل الأسماء فقط |
| 49 | map(activeUsers => activeUsers.map(u => u.name)) |
| 50 | ).subscribe({ |
| 51 | next: (names) => { |
| 52 | this.activeUserNames = names; |
| 53 | console.log('الأسماء النهائية المعروضة:', names); |
| 54 | } |
| 55 | }); |
| 56 | } |
| 57 | |
| 58 | ngOnDestroy(): void { |
| 59 | if (this.userSub) { |
| 60 | this.userSub.unsubscribe(); |
| 61 | } |
| 62 | } |
| 63 | } |