Développement Web

أداء Angular: تحسين عملي 2026

4 min de lecture

تطبيق Angular يمكن أن يكون سريعاً جدّاً أو بطيئاً جدّاً، حسب اختيارات البنية و change detection. مع تطوّرات 2024-2026 (signals، zoneless، hydration)، كثير من التحسينات التي كنّا نقوم بها يدوياً أصبحت آلية. هذا الدليل يجمع المنهج العمليّ للقياس وتحديد وتصحيح مشكلات الأداء، مع تمييز ما يحسب فعلاً عمّا هو cargo cult.

للسياق الأوسع → Angular للمؤسسات: دليل عملي.


المحتويات

  1. القياس قبل التحسين
  2. Change detection: الأنماط والاستراتيجيات
  3. OnPush و signals: تركيب رابح
  4. حجم الحزمة و lazy loading
  5. تحسين الصور
  6. Server-Side Rendering و hydration
  7. القوائم المُفتَرضَة
  8. Web Vitals والتجربة المُدرَكة
  9. المزالق الشائعة
  10. أسئلة شائعة

1. القياس قبل التحسين

القاعدة الذهبيّة للتحسين: لا تلمس الكود قبل أن تقيس. كثير من التحسينات « البديهيّة » (OnPush في كلّ مكان، async pipe داخل حلقة) لا تُجدي شيئاً بل قد تُسوء الأداء.

أدوات القياس

  • Angular DevTools (امتداد Chrome/Firefox): profiler لـ change detection، هرميّة المكوّنات، hooks دورة الحياة
  • تبويب Performance في المتصفّح: flame chart تفصيليّ، يكشف long tasks
  • Lighthouse: تدقيق كامل أداء/إمكانيّة الوصول/SEO
  • Web Vitals extension: LCP، INP، CLS مباشرة
  • Bundle analyzer: ng build --stats-json ثمّ webpack-bundle-analyzer
  • Sentry، Datadog RUM: الأداء في الإنتاج الحقيقيّ

المنعكس العمليّ

قبل لمس الكود، أَجِب:

  • ما الذي يكون بطيئاً بالضبط؟ التحميل الابتدائيّ؟ تفاعل ما؟ scroll؟
  • اعتباراً من أيّ عتبة قابلة للقياس؟
  • ما المستخدم المتأثّر (موبايل بطيء، سطح مكتب، عرض نطاق محدود)؟

التحسين دون هدف دقيق رميٌ في الضباب.


2. Change detection: الأنماط والاستراتيجيات

Angular في 2026 (معلومات مُتحقَّق منها في أبريل 2026، عرضة للتطوّر) يدعم ثلاثة أنماط لـ change detection.

Default (ZoneJS)

النمط التاريخيّ. Zone.js يعترض كلّ الأحداث غير المتزامنة (نقر، timeout، HTTP) ويُطلق فحصاً لكلّ المكوّنات. بسيط الفهم، لكنّه قليل الفعّاليّة: كلّ نقرة قد تُطلق فحوصاً على مئات المكوّنات حتى غير المتأثّرة.

OnPush

@Component({
  changeDetection: ChangeDetectionStrategy.OnPush,
  ...
})

المكوّن لا يُعاد فحصه إلّا إذا:

  • تغيّر أحد inputsه (بالمرجع)
  • وقع حدث مستخدم داخله
  • أصدر Observable مُستهلَك عبر async pipe قيمة
  • نُودي يدويّاً على markForCheck()

الأداء يتحسّن بقدر كبير على أشجار معقّدة.

Zoneless (Angular 18+)

provideZonelessChangeDetection()

لا Zone.js. الـ change detection يُطلَق فقط عند تغيّرات signals. أداء أعلى، bundle أصغر (إزالة Zone.js توفّر ~50kb)، debugging أبسط.

متوافق مع OnPush و standalone components. مُوصى به لكلّ مشروع جديد في 2026.

توصية

  • مشروع جديد: zoneless + signals + OnPush + standalone
  • مشروع قائم: انقل تدريجيّاً، OnPush أوّلاً (مكوّن مكوّن)، ثمّ signals، ثمّ zoneless
  • مشروع legacy ثقيل: حافظ على الـ default، حسّن hot paths بـ OnPush مُستهدَف

3. OnPush و signals: تركيب رابح

@Component({
  selector: "app-cart",
  standalone: true,
  changeDetection: ChangeDetectionStrategy.OnPush,
  template: `
    <p>العناصر: {{ count() }}</p>
    <p>المجموع: {{ total() }} F CFA</p>
    @for (item of items(); track item.id) {
      <div>{{ item.nom }}</div>
    }
  `,
})
export class CartComponent {
  private cart = inject(CartService);
  items = this.cart.items;
  count = computed(() => this.items().length);
  total = computed(() => this.items().reduce((s, i) => s + i.prix, 0));
}

OnPush + signals = إعادات رسم في حدّها الأدنى. حين يتغيّر items، فقط أجزاء القالب المعتمدة على items() أو count() أو total() تُحدَّث. باقي الـ DOM يبقى سليماً.

فخّ الكائنات المُعدَّلة (mutated)

// خاطئ: mutation، OnPush لا يكشف شيئاً
const items = this.items();
items.push(nouveau);
// لا إشعار

// صحيح
this.items.update(list => [...list, nouveau]);

أنشئ دائماً مرجعاً جديداً. هذا المبدأ يصحّ في useState في React أيضاً: ثابت من ثوابت النماذج التفاعليّة الحديثة.

track داخل @for

<!-- خاطئ: يستعمل الـ index، يُعيد إنشاء كلّ شيء عند أصغر تغيير -->
@for (item of items(); track $index) { ... }

<!-- صحيح: يستعمل مُعرِّفاً ثابتاً -->
@for (item of items(); track item.id) { ... }

مع track ثابت، Angular لا يُعيد إنشاء إلّا عُقَد DOM المتغيّرة فعلاً. على قوائم طويلة، هذا حاسم.


4. حجم الحزمة و lazy loading

ng build --configuration production --stats-json
npx webpack-bundle-analyzer dist/mon-app/stats.json

حدّد ما يثقل فعلاً. الأنماط المتكرّرة:

  • moment.js: ~70kb. استبدل بـ date-fns أو dayjs (~10kb).
  • lodash كاملاً: ~70kb. استورد ما يلزم فقط: import debounce from "lodash-es/debounce".
  • Angular Material دون lazy: مجموعات UI ثقيلة. استورد مكوّناً مكوّناً، لا الـ module كاملاً.
  • Locales i18n كاملة: استورد فقط locales اللازمة.

Lazy loading

سبق في معمارية Angular المعياريّة: كلّ feature في route lazy-loaded. الـ bundle الابتدائيّ يحوي فقط الصفحة الأولى.

Imports انتقائيّة لـ Angular Material

// خاطئ
import { MatModule } from "@angular/material";

// صحيح
import { MatButtonModule } from "@angular/material/button";
import { MatInputModule } from "@angular/material/input";

مع standalone components، استورد مكوّناً مكوّناً:

import { MatButton } from "@angular/material/button";

@Component({
  imports: [MatButton],
  ...
})

تأجيل الكود غير الحرج

@defer (on viewport) {
  <app-comments [postId]="postId()" />
} @placeholder {
  <div>تحميل التعليقات...</div>
}

الـ block @defer يُحمّل المكوّن فقط حين يصبح مرئياً. يُخفّض بشكل جذريّ الكود المُحمَّل على first paint.


5. تحسين الصور

<img ngSrc="hero.webp" width="1200" height="630" priority />

التوجيه NgOptimizedImage:

  • Lazy loading تلقائيّ ما عدا priority
  • أبعاد صريحة (تتجنّب layout shift)
  • srcset تلقائيّ لأحجام شاشات مختلفة
  • Préconnexion إلى نطاق الـ loader المُعَدّ

إعداد image loader لـ CDN:

provideImgixLoader("https://example.imgix.net");
// أو Cloudinary، Cloudflare Images، إلخ

لصورة واحدة فوق خطّ الطيّ: priority يُسرّع LCP. للأخريات: اترك lazy loading يقوم بعمله.


6. Server-Side Rendering و hydration

ng add @angular/ssr

SSR يرسم HTML من الخادم قبل وصول JavaScript. المنافع:

  • LCP أسرع: المحتوى يظهر قبل الـ hydration
  • SEO: Google يرى المحتوى مباشرة (مفيد أيضاً للشبكات الاجتماعيّة و OpenGraph)
  • First paint مُدرَك أسرع على اتّصالات بطيئة

Hydration

Angular 16+ ينفّذ hydration فعّال: الخادم يرسم، العميل يعيد استعمال الـ DOM الموجود ويُضيف التفاعل فقط. لا إعادة رسم كامل من جانب العميل.

Streaming SSR

مع Angular 17+، الـ SSR يستطيع stream الـ HTML: إرسال الهيكل بسرعة ثمّ ملء الأجزاء التي تتطلّب بيانات أبطأ.

@defer (on idle) {
  <app-recommendations />
}

مع SSR، أوّل بايت يصل بسرعة كبيرة، والمحتوى التفاعليّ يلي.

متى يستحقّ SSR العناء

  • مواقع عامّة بـ SEO مهمّ
  • صفحات يكون فيها LCP حرجاً للأعمال
  • صفحات تُشارَك على الشبكات الاجتماعيّة (OpenGraph)

للوحات مُصادَق عليها دون قيود SEO: SSR يُضيف تعقيداً دون منفعة واضحة. SPA يكفي.


7. القوائم المُفتَرضَة (virtual lists)

رسم 10 000 سطر في DOM بطيء. الـ virtualisation لا يرسم إلّا العناصر المرئيّة.

import { CdkVirtualScrollViewport } from "@angular/cdk/scrolling";

@Component({
  selector: "app-virtual-list",
  standalone: true,
  imports: [CdkVirtualScrollViewport, ...],
  template: `
    <cdk-virtual-scroll-viewport itemSize="50" style="height: 400px">
      @for (item of items; track item.id) {
        <div class="row">{{ item.nom }}</div>
      }
    </cdk-virtual-scroll-viewport>
  `,
})

استعملها فور تجاوز قائمة 100-200 عنصر مرئيّ. لا غنى عنها للوحات بجداول طويلة.

لشبكات أو layouts أكثر تعقيداً: @angular/cdk/scrolling يقترح viewports قابلة للتخصيص، أو مكتبات طرف ثالث مثل ngx-virtual-scroller.


8. Web Vitals والتجربة المُدرَكة

المؤشّرات الثلاثة من Google:

  • LCP (Largest Contentful Paint): زمن ظهور العنصر الأكبر. الهدف: < 2.5s
  • INP (Interaction to Next Paint): زمن الاستجابة للتفاعلات. الهدف: < 200ms
  • CLS (Cumulative Layout Shift): استقرار بصريّ أثناء التحميل. الهدف: < 0.1

تحسين LCP

  • الصورة الرئيسيّة بـ priority مع NgOptimizedImage
  • SSR لتقديم محتوى من الخادم
  • تخفيض الـ bundle الابتدائيّ (lazy loading للـ features غير الحرجة)
  • CDN للأصول الثابتة
  • Préconnect إلى النطاقات الحرجة

تحسين INP

  • OnPush + signals يحدّان من عمل change detection على التفاعلات
  • تفكيك long tasks (>50ms) عبر setTimeout أو requestIdleCallback
  • Web Workers للحسابات الكثيفة لـ CPU
  • نمط zoneless لإزالة overhead Zone.js

تحسين CLS

  • أبعاد صريحة على الصور و iframes و فيديوهات
  • حجز المساحة للمحتوى المُحمَّل بشكل مُؤجَّل (skeletons)
  • تجنّب إدراج محتوى فوق ما ينظر إليه المستخدم
  • خطّ ويب بـ font-display: swap و fallback بحجم مشابه

9. المزالق الشائعة

Subscriptions بدون unsubscribe

// خاطئ: memory leak مضمون
this.api.getData().subscribe(data => this.data = data);

// صحيح: auto-unsubscribe عند destroy
this.api.getData().pipe(takeUntilDestroyed()).subscribe(data => this.data = data);

// أفضل: signal عبر toSignal
data = toSignal(this.api.getData(), { initialValue: [] });

حسابات ثقيلة في القالب

<!-- خاطئ: يُستدعى عند كلّ change detection -->
<div>{{ calculateExpensiveValue(items) }}</div>

<!-- صحيح: computed signal -->
<div>{{ expensiveValue() }}</div>
expensiveValue = computed(() => calculateExpensiveValue(this.items()));

الـ computed يتذكّر ولا يعيد الحساب إلّا إذا تغيّرت التبعيّات.

Async pipe داخل حلقة

<!-- خاطئ: subscription جديدة لكلّ عنصر -->
@for (id of ids; track id) {
  <div>{{ getUser(id) | async | json }}</div>
}

<!-- صحيح: ادمج الطلبات في الأعلى -->
users = toSignal(forkJoin(ids.map(id => this.api.getUser(id))));

عدم استعمال track في @for

بدون track، Angular يعيد إنشاء كلّ عُقَد DOM عند كلّ تحديث للقائمة. على 100 سطر هذا ثانويّ، على 10 000 كارثيّ.

راجع أيضاً → Angular signals وRxJS عمليّاً لأنماط الاشتراك.


10. أسئلة شائعة

تطبيقي Angular بطيء، من أين أبدأ؟

  1. Profiler Angular DevTools على الإجراء البطيء
  2. حدّد المكوّنات التي تُعاد رسمها بلا فائدة
  3. تحقّق من track على @for الطويلة
  4. تحقّق من subscriptions المنسيّة
  5. قِس الـ bundle الابتدائيّ بـ bundle-analyzer

التحسين موجَّه بالقياس لا بالحدس.

OnPush في كلّ مكان أم انتقائيّ؟

مع signals: OnPush في كلّ مكان هو الوضع الافتراضيّ الحديث والمفيد. بدون signals (legacy): OnPush انتقائيّ على المكوّنات التي تُعاد رسمها بلا فائدة، مُحَدَّدة عبر الـ profiler.

هل zoneless جاهز للإنتاج؟

نعم منذ Angular 18+. اجمعه مع مكوّنات signal-based. بعض المكتبات الطرفيّة قد تشهد مشاكل (نادر في 2026)، اختبر قبل الهجرة الكاملة.

Bundle ابتدائيّ بـ 800kb، كيف أخفّضه؟

خطوات: 1) Bundle analyzer لرؤية ما يثقل، 2) lazy load الـ routes، 3) استورد مكوّنات Material واحداً واحداً، 4) استبدل moment.js بـ date-fns، 5) tree-shake lodash، 6) أجّل المكوّنات غير الحرجة بـ @defer.

هل يُبطئ SSR التطبيق للمستخدمين الموجودين على الموقع؟

لا، إذا أُحسن الـ hydration. الـ SSR يجلب first paint أسرع، والـ hydration يضيف التفاعل بعدها. بتنفيذ جيّد، أفضل من SPA صرفة لأغلب الحالات العامّة.

رسوم متحرّكة تتعثّر: ماذا أفعل؟

فضّل CSS animations على Angular Animations للحالات البسيطة (transform، opacity). للرسوم المعقّدة: Web Animations API الأصيل أو GSAP. حرّك transform بدل top/left/width (التي تُجبر layout).

كيف أراقب الأداء في الإنتاج؟

Sentry Performance أو Datadog RUM أو مكتبة web-vitals من Google لإرسال المقاييس إلى لوحة مراقبة. حدّد الانحدارات المرتبطة بالنشرات. Lighthouse CI كي لا تتراجع على كلّ PR.


مقالات مرتبطة (سلسلة Angular)

مقالات ذات صلة

Service ITSkillsCenter

Site ou application web sur mesure

Conception Pro + Nom de domaine 1 an + Hébergement 1 an + Formation + Support 6 mois. Accès et code livrés. À partir de 350 000 FCFA.

Demander un devis
Publicité