شرح Angular ngClass: كيفية إضافة أصناف CSS شرطية باحتراف

دقائق القراءة: 6

مقدمة إلى ngClass في Angular

تُعد ngClass واحدة من أشهر التوجيهات في Angular، إذ تسمح لك بإضافة أصناف CSS إلى عناصر HTML أو إزالتها بصورة ديناميكية وفقاً لحالة البيانات داخل المكوّن. وهذا يجعل واجهات المستخدم أكثر مرونة وسهولة في التخصيص، خاصة عند التعامل مع التحقق من النماذج، أو تلوين الحالات، أو تغيير التنسيق بناءً على القيم.

في هذا المقال سنركز على ngClass الخاصة بـ Angular الحديثة، وليس ng-class في AngularJS.

شرح توجيه ngClass في Angular لإضافة أصناف CSS شرطية بشكل ديناميكي

المتطلبات الأساسية: فهم Property Binding

قبل استخدام ngClass بفاعلية، من المهم فهم مفهومين أساسيين في Angular: الربط النصي Interpolation والربط بالخصائص Property Binding.

لنأخذ الخاصية placeholder داخل العنصر input مثالاً:

<!-- Normal HTML -->
<input placeholder="some text">

<!-- Interpolation -->
<input placeholder="{{ variable }}">

<!-- Property Binding -->
<input [placeholder]="variable">

إذا كانت قيمة variable داخل المكوّن هي 'some text'، فإن جميع الأمثلة السابقة ستنتج النتيجة نفسها.

عملياً، يُعد Property Binding خياراً أنظف وأكثر وضوحاً في كثير من الحالات، لأنه يربط الخاصية مباشرة بالقيمة دون الحاجة إلى صياغة إضافية.

ما هو ngClass في Angular؟

يقبل ngClass ثلاثة أنواع رئيسية من القيم:

  • String
  • Array
  • Object

إليك أمثلة مباشرة:

<div [ngClass]="'first second'"></div>
<div [ngClass]="['first', 'second']"></div>
<div [ngClass]="{first: true, second: true, third: true}"></div>
<div [ngClass]="{'first second': true}"></div>

في الأمثلة السابقة، ستحصل العناصر على الأصناف first وsecond. وعند استخدام كائن Object، تمثل المفاتيح أسماء الأصناف، بينما تحدد القيم ما إذا كان الصنف سيُطبّق أم لا.

إذا كان اسم الصنف يحتوي على مسافة، مثل 'first second'، فيجب وضعه بين علامتي اقتباس داخل الكائن.

كيف يقيّم Angular التعبيرات داخل [ngClass]؟

القيمة التي تضعها داخل [ngClass] ليست بالضرورة قيمة ثابتة، بل يمكن أن تكون تعبيراً قابلاً للتقييم. والمقصود بالتعبير expression أنه شيء يُرجع قيمة، بخلاف التعليمة statement التي تنفّذ إجراءً دون إرجاع قيمة مباشرة.

على سبيل المثال، التعليمة if ليست تعبيراً، لذلك لا يمكن استخدامها مباشرة داخل [ngClass]. أما تعبير مثل num + 10 فهو صالح لأنه يعيد قيمة.

كما أن Angular لا يسمح بعمليات الإسناد داخل الربط، لذلك ستظهر لك رسالة خطأ إذا حاولت استخدام تعبير يحتوي على تعيين قيمة.

في المقابل، الأمثلة التالية جميعها صحيحة:

<!-- Given val = 'foo', class becomes 'foofoo' -->
<div [ngClass]="val + val"></div>

<!-- Given val = 'foo', class becomes 'foo' -->
<div [ngClass]="[val]"></div>

<!-- Given func() returns 'foo' -->
<div [ngClass]="func()"></div>

استخدام ngClass مع شرط بسيط

عندما تحتاج إلى تطبيق صنف بناءً على شرط مباشر، فإن العامل الثلاثي Ternary Operator هو الخيار الأنسب.

إذا أردت مثلاً تلوين خلية جدول باللون الأحمر عندما تكون القيمة أكبر من 10، وباللون الأخضر في غير ذلك:

<td [ngClass]="val > 10 ? 'red' : 'green'">
  {{ val }}
</td>

وإذا كان المطلوب إضافة صنف عند تحقق شرط، وإزالته عند عدم تحققه، فيمكنك إعادة سلسلة فارغة '' عند الحالة الثانية:

<input type="text" [ngClass]="control.isInvalid ? 'error' : ''" />

لكن توجد صيغة أكثر أناقة واختصاراً باستخدام الكائنات:

<input type="text" [ngClass]="{ error: control.isInvalid }" />

وإذا كنت تتعامل مع صنف واحد فقط، فقد يكون ربط الصنف مباشرة أوضح:

<input type="text" [class.error]="control.isInvalid" />

فهم Object Literal مع ngClass

عند تمرير كائن إلى ngClass، فإن:

  • المفتاح key يمثل اسم الصنف.
  • القيمة value تحدد إن كان الصنف سيُضاف إلى العنصر.

يُطبّق الصنف فقط إذا كانت القيمة truthy. وهذا يعني أن القيم مثل false أو undefined أو '' لن تؤدي إلى تطبيق الصنف.

ومن الملاحظات المهمة أيضاً أن صياغة computed property name ليست مدعومة هنا بالشكل الكامل، لذلك من الأفضل الاعتماد على مفاتيح واضحة وثابتة كلما أمكن.

التعامل مع الشروط المعقدة في ngClass

أحياناً لا يكون الشرط مجرد حالتين مثل true وfalse، بل قد تحتاج إلى عدة مستويات. لنفترض أنك تريد تطبيق صنف مختلف حسب قيمة val:

  • من 0 إلى 5low
  • من 6 إلى 10medium
  • أكبر من 10high

منطقياً، يمكن كتابة ذلك عبر if/else داخل TypeScript:

if (val >= 0 && val <= 5) {
  return 'low';
} else if (val >= 6 && val <= 10) {
  return 'medium';
} else {
  return 'high';
}

وبما أن استخدام if/else مباشرة داخل [ngClass] غير ممكن، يلجأ كثيرون إلى إنشاء دالة تُرجع اسم الصنف:

class MyComponent {
  getClassOf(val: number) {
    if (val >= 0 && val <= 5) {
      return 'low';
    } else if (val > 5 && val <= 10) {
      return 'medium';
    } else {
      return 'high';
    }
  }
}
<td [ngClass]="getClassOf(val)">{{ val }}</td>

هذا الأسلوب يعمل، لكنه ليس مثالياً دائماً، لأن Angular ChangeDetection قد يستدعي الدالة مرات متعددة أثناء دورة التحديث، ما قد يسبب عبئاً غير ضروري في بعض الواجهات.

بديل أفضل باستخدام كائن داخل القالب

يمكن تنفيذ المنطق نفسه عبر كائن مباشر:

<td
  [ngClass]="{
    low: val >= 0 && val <= 5,
    medium: val > 5 && val <= 10,
    high: val > 10
  }"
>
  {{ val }}
</td>

هذه الصيغة جيدة، لكنها قد تصبح مطولة إذا تعقدت الشروط.

النهج الأنظف: تحويل القيمة مسبقاً

في كثير من المشاريع، الأفضل هو تحويل القيمة أولاً داخل المكوّن، ثم تمرير النتيجة الجاهزة إلى ngClass. هذا يُبقي القالب بسيطاً ويحسن قابلية الصيانة.

type ClassName = 'low' | 'medium' | 'high';

class MyComponent {
  className: ClassName = 'low';

  ngOnChanges(changes: SimpleChanges) {
    if (changes.val) {
      this.className = this.mapValToClass(changes.val.currentValue);
    }
  }

  private mapValToClass(val: number): ClassName {
    if (val >= 0 && val <= 5) {
      return 'low';
    } else if (val > 5 && val <= 10) {
      return 'medium';
    } else {
      return 'high';
    }
  }
}

ثم يصبح القالب في غاية البساطة:

<td [ngClass]="className"></td>

متى نستخدم Array مع ngClass؟

هناك حالات يكون فيها استخدام المصفوفة Array عملياً، خصوصاً عندما تكون لديك قيم متتابعة تقابل أصنافاً محددة.

مثال على ذلك:

{
  1: 'first-element',
  2: 'second-element',
  3: 'third-element'
}

إذا كانت القيم متتالية مثل 1 و2 و3، فيمكنك استغلال الفهارس داخل المصفوفة:

type Val = 1 | 2 | 3;

class MyComponent {
  classArr = ['first-element', 'second-element', 'third-element'];
  val: Val = 1;
}
<td [ngClass]="classArr[val - 1]"></td>

وهذا الأسلوب مفيد عندما يكون الربط مباشراً وثابت البنية.

استخدام كائن بدلاً من المصفوفة

يمكنك أيضاً تنفيذ الربط نفسه عبر كائن:

type Val = 1 | 2 | 3;

class MyComponent {
  classMap = {
    1: 'first-element',
    2: 'second-element',
    3: 'third-element'
  };

  val: Val = 1;
}
<td [ngClass]="classMap[val]"></td>

هذا الأسلوب قد يكون أوضح عندما لا تكون القيم متتابعة أو عندما ترغب في توثيق العلاقة بين القيمة واسم الصنف بشكل مباشر.

الفرق بين [ngClass] و[class] في Angular

من المفيد معرفة أن Angular يوفر أيضاً الصيغة [class]، وهي متاحة منذ اعتماد Ivy كمترجم ومحرك تشغيل افتراضي ابتداءً من Angular 9.

ورغم أن [class] يشبه [ngClass] في كثير من الاستخدامات، فهناك بعض الفروقات المهمة:

  • الصيغة [ngClass]="{'a b': true}" تعمل، لكن [class]="{'a b': true}" لا تعمل بالطريقة نفسها.
  • قيمة [class] لا تخضع للمراقبة العميقة deep watch كما يحدث في بعض حالات [ngClass].

لذلك، إذا كنت تحتاج إلى مرونة أعلى في إدارة الأصناف، فغالباً يكون ngClass هو الخيار الأكثر أماناً ووضوحاً.

أفضل ممارسات استخدام ngClass

  • استخدم [class.className] عند التعامل مع صنف واحد فقط.
  • استخدم Object عندما تريد تفعيل عدة أصناف بناءً على شروط مختلفة.
  • تجنب وضع منطق معقد جداً داخل القالب.
  • انقل عمليات التحويل والحساب إلى المكوّن Component كلما زاد التعقيد.
  • تجنب الإفراط في استدعاء الدوال مباشرة داخل القالب إذا كانت الواجهة تتحدث باستمرار.
  • احرص على أن تكون أسماء الأصناف معبرة ومنسجمة مع نظام التصميم في المشروع.

الخلاصة التقنية

يوفر ngClass في Angular طريقة مرنة وقوية للتحكم في أصناف CSS ديناميكياً، سواء باستخدام نصوص String أو مصفوفات Array أو كائنات Object. ومن الناحية العملية، يعتمد الاختيار الأفضل على مستوى تعقيد الحالة: فالشروط البسيطة تناسبها العبارات المختصرة، بينما الحالات المركبة يُفضّل تجهيزها داخل المكوّن للحفاظ على أداء جيد وقالب واضح وسهل الصيانة. إذا استُخدم ngClass بطريقة مدروسة، فسيمنحك تحكماً أدق في الواجهة دون التضحية بتنظيم الكود.

اترك تعليقاً

لن يتم نشر عنوان بريدك الإلكتروني. الحقول الإلزامية مشار إليها بـ *