| معيار المقارنة | نظام اكتشاف التغييرات التقليدي (Zone.js) | النظام الحديث المعتمد على الإشارات (Signals) |
|---|---|---|
| طريقة تتبع الأحداث والتحديث | مراقبة شاملة لكافة الأحداث غير المتزامنة (النقر، مؤقتات الـ setTimeout، طلبات HTTP) | تتبع دقيق وموجه يعتمد على الاعتماديات المباشرة دون الحاجة لفحص التطبيق بالكامل |
| نطاق الفحص عند التغيير | فحص شجرة المكونات بالكامل من الأب إلى أصغر مكون فرعي (Dirty Checking) | تحديث العنصر أو المكون المتأثر فقط بشكل فوري ودقيق |
| تأثير الأداء في التطبيقات الضخمة | استهلاك أعلى للموارد وبطء محتمل عند كثرة المكونات | أداء فائق السرعة واستجابة فورية وتوفير كبير في استهلاك الذاكرة |
| الاعتمادية الهندسية | يعتمد بشكل افتراضي على مكتبة Zone.js لتعقب دورة حياته | يعمل بتناغم تام مع العمارة التفاعلية الحديثة ويمكن تشغيله بدون Zone.js نهائياً |
ننتقل في هذا الدرس الثاني من الفصل السادس لنغوص عميقاً في أحد أهم أسرار أداء إطار عمل أنجولر، ألا وهو آلية اكتشاف التغييرات (Change Detection Mechanism). لكي يتمكن التطبيق من عكس أي تعديل يحدث في بيانات الكود (TypeScript) على واجهة المستخدم (HTML)، يحتاج أنجولر إلى نظام يراقب الأحداث ويفحص المكونات. في إصدار Angular 22.1، نشهد تطوراً جذرياً يجمع بين الآلية التقليدية القائمة على مكتبة Zone.js والنظام الحديث المتطور القائم على الإشارات (Signals).
خلال سنوات طويلة، اعتمدت أنجولر على مكتبة Zone.js لاعتراض كافة الأحداث غير المتزامنة (Asynchronous Operations) مثل ضغطات الأزرار، استجابات الخوادم عبر HttpClient، ومؤقتات المتصفح. بمجرد وقوع أي حدث، تفترض Zone.js أن حالة التطبيق ربما تكون قد تغيرت، مما يدفع محرك أنجولر لفحص شجرة المكونات بأكملها (Component Tree) تباعاً من الجذور وحتى الأطراف للبحث عن أي تغيير في المتغيرات وعرضها في الواجهة.
في الهندسة الحديثة لأنجولر، أصبحت الإشارات (Signals) هي المعيار الذهبي. بدلاً من الاعتماد على الفحص الشامل لشجرة المكونات (والذي يستهلك وقتاً ومعالجة بلا داعٍ)، تعرف الإشارات بدقة متناهية المكونات التي تعتمد عليها قيمتها حصراً. عند حدوث أي تعديل، يتم إرسال إشعار مباشر وفوري لتلك الأجزاء المستهدفة فقط لتحديث نفسها، مما يلغي الهدر البرمجي تماماً ويمنح التطبيقات سرعة مذهلة تضاهي أحدث الأطر البرمجية العالمية.
تتم عملية اكتشاف التغييرات عبر دورات منتظمة أو محفزة بأحداث معينة. عندما يُطلب من أنجولر فحص مكون ما، فإنه يقيم تعبيرات القالب (Template Expressions) للمكون، فإذا وجد اختلافاً بين القيمة القديمة والقيمة الجديدة، يقوم بتحديث عنصر DOM المقابل. وفهم هذه الدورة يساعد المطورين على تجنب الأخطاء الشائعة مثل أخطاء التحديث المتلاحق (ExpressionChangedAfterItHasBeenCheckedError).
| 1 | import { Component, signal, ChangeDetectorRef, inject } from '@angular/core'; |
| 2 | |
| 3 | @Component({ |
| 4 | selector: 'app-change-detection-demo', |
| 5 | standalone: true, |
| 6 | template: ` |
| 7 | <div class="box"> |
| 8 | <h3>فحص آليات اكتشاف التغييرات</h3> |
| 9 | <p>عداد التحديثات بالطريقة التقليدية: <strong>{{ traditionalCount }}</strong></p> |
| 10 | <p>عداد التحديثات بنظام الإشارات الحديث: <strong>{{ signalCount() }}</strong></p> |
| 11 | |
| 12 | <button (click)="incrementValues()">تنفيذ وتحديث البيانات</button> |
| 13 | </div> |
| 14 | ` |
| 15 | }) |
| 16 | export class ChangeDetectionDemoComponent { |
| 17 | // متغير تقليدي يعتمد على دورة الفحص الشاملة |
| 18 | traditionalCount = 0; |
| 19 | |
| 20 | // إشارة حديثة يتم تحديثها بدقة تامة |
| 21 | signalCount = signal(0); |
| 22 | |
| 23 | // حقن مرجع كاشف التغييرات للتحكم اليدوي المتقدم إذا لزم الأمر |
| 24 | private cdr = inject(ChangeDetectorRef); |
| 25 | |
| 26 | incrementValues() { |
| 27 | // زيادة المتغير التقليدي |
| 28 | this.traditionalCount++; |
| 29 | |
| 30 | // زيادة الإشارة الحديثة |
| 31 | this.signalCount.update(val => val + 1); |
| 32 | |
| 33 | console.log('تم تنفيذ الحدث وإطلاق دورة اكتشاف التغييرات.'); |
| 34 | } |
| 35 | } |
| 1 | import { Component, signal } from '@angular/core'; |
| 2 | import { CommonModule } from '@angular/common'; |
| 3 | |
| 4 | @Component({ |
| 5 | selector: 'app-async-detection-demo', |
| 6 | standalone: true, |
| 7 | imports: [CommonModule], |
| 8 | template: ` |
| 9 | <div class="async-panel"> |
| 10 | <h3>محاكاة الاستجابة غير المتزامنة (Async Operations)</h3> |
| 11 | |
| 12 | @if (isLoading()) { |
| 13 | <p class="loading">جاري معالجة البيانات والتحميل...</p> |
| 14 | } @else { |
| 15 | <p class="success">البيانات الحالية: <strong>{{ fetchedData() }}</strong></p> |
| 16 | } |
| 17 | |
| 18 | <button (click)="loadDataFromServer()" [disabled]="isLoading()">بدء جلب البيانات</button> |
| 19 | </div> |
| 20 | ` |
| 21 | }) |
| 22 | export class AsyncDetectionDemoComponent { |
| 23 | isLoading = signal(false); |
| 24 | fetchedData = signal('لا توجد بيانات حالياً'); |
| 25 | |
| 26 | loadDataFromServer() { |
| 27 | // تفعيل حالة التحميل |
| 28 | this.isLoading.set(true); |
| 29 | |
| 30 | // محاكاة طلب شبكة غير متزامن باستخدام setTimeout |
| 31 | setTimeout(() => { |
| 32 | this.fetchedData.set('تم استلام البيانات بنجاح من الخادم الخارجي!'); |
| 33 | this.isLoading.set(false); |
| 34 | console.log('انتهت عملية جلب البيانات وتم تحديث الواجهة تلقائياً.'); |
| 35 | }, 2000); |
| 36 | } |
| 37 | } |