Expression has changed after it was checked: كيفية التغلب على الخطأ في Angular

مدرب بايثون أونلاين للمبتدئين

تعلم Python بسهولة دون إرهاق نظري. حل مهام عملية مع التحقق التلقائي واكتب الكود مباشرة في المتصفح.

ابدأ الدورة

«Expression has changed after it was checked» — لماذا يحدث هذا وكيف تتعايش معه؟

\n\n

إذا كنت قد كتبت أي تطبيق باستخدام Angular، فبالتأكيد واجهت هذه الرسالة المخيفة في وحدة التحكم:

\n\n
ERROR Error: ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked. Previous value: 'false'. Current value: 'true'.
\n\n

هذا الخطأ هو "آفة" حقيقية للمطورين. آلاف الأسئلة على Stack Overflow، ومئات الساعات من التصحيح، وعشرات الانهيارات العصبية. لكن الخبر السار: لا يمكنك إصلاحه فحسب، بل يمكنك أيضًا فهم سبب حدوثه. في هذه المقالة، سنحلل أهم أسباب الخطأ Expression has changed after it was checked، ونكتب أمثلة بلغة TypeScript ونقدم حلولاً محددة. هيا بنا!

\n\n

ماذا يعني هذا الخطأ بالضبط؟

\n\n

يعمل Angular في وضعين: التطوير (dev mode) والإنتاج. في وضع التطوير، يقوم الإطار بفحص إضافي — بعد كل دورة كشف تغييرات، يقوم بتشغيل دورة ثانية للتأكد من أن البيانات لم تتغير "أثناء التنفيذ". إذا تغيرت القيمة بين الدورة الأولى والثانية، يتوقف Angular مع الخطأ ExpressionChangedAfterItHasBeenCheckedError.

\n\n

بكلمات بسيطة: أخبرت Angular "هذه هي البيانات"، فقام بفحصها، ثم قمت بتغييرها. يغضب Angular ويصرخ.

\n\n

أهم 3 أسباب للخطأ

\n\n

1. تغيير البيانات في خطافات دورة الحياة بعد ngAfterViewInit

\n\n

السبب الأكثر شيوعًا هو محاولة تغيير خاصية المكون في الخطاف ngAfterViewInit أو ngAfterContentInit أو ngAfterViewChecked. في هذه اللحظة، يكون Angular قد أكمل فحص العرض، وأي تغيير يسبب الخطأ.

\n\n

مثال بلغة TypeScript:

\n\n
import { Component, AfterViewInit } from '@angular/core';\n\n@Component({\n  selector: 'app-example',\n  template: '<p>{{ message }}</p>'\n})\nexport class ExampleComponent implements AfterViewInit {\n  message = 'قيمة أولية';\n\n  ngAfterViewInit(): void {\n    // خطأ! تغيير الخاصية بعد فحص العرض\n    this.message = 'قيمة معدلة';\n  }\n}
\n\n

هنا، يقوم Angular أولاً بفحص message (القيمة "قيمة أولية")، ثم يستدعي ngAfterViewInit، حيث تقوم بتغييرها إلى "قيمة معدلة". عند الفحص الثاني، يرى Angular اختلافًا ويطرح الخطأ.

\n\n

2. العمليات غير المتزامنة داخل دورة الحياة

\n\n

إذا قمت بتشغيل كود غير متزامن (setTimeout، Promise، Observable) داخل الخطاف، فإن تحديث البيانات يحدث خارج منطقة سيطرة Angular.

\n\n

مثال:

\n\n
import { Component, OnInit } from '@angular/core';\n\n@Component({\n  selector: 'app-async',\n  template: '<p>{{ data }}</p>'\n})\nexport class AsyncComponent implements OnInit {\n  data = 'بداية';\n\n  ngOnInit(): void {\n    setTimeout(() => {\n      this.data = 'قيمة جديدة';\n    }, 0);\n  }\n}
\n\n

على الرغم من أن setTimeout بتأخير 0 مللي ثانية يتم تنفيذه فورًا تقريبًا، إلا أن Angular قد أكمل الفحص بالفعل. في وضع التطوير، سيؤدي هذا إلى خطأ.

\n\n

3. المكونات المتداخلة ونقل البيانات عبر Input

\n\n

عندما ينقل المكون الأصلي بيانات إلى المكون الفرعي عبر @Input، ويحاول المكون الفرعي تغيير هذه البيانات في دورة حياته، يحدث تعارض.

\n\n

مثال:

\n\n
// المكون الأصلي\n@Component({\n  selector: 'app-parent',\n  template: '<app-child [value]="parentValue"></app-child>'\n})\nexport class ParentComponent {\n  parentValue = 'أصلي';\n}\n\n// المكون الفرعي\n@Component({\n  selector: 'app-child',\n  template: '<p>{{ value }}</p>'\n})\nexport class ChildComponent implements AfterViewInit {\n  @Input() value: string = '';\n\n  ngAfterViewInit(): void {\n    this.value = 'معدل في'                

المدونات

توصيات الكتب